diff options
Diffstat (limited to 'src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs')
-rw-r--r-- | src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs | 824 |
1 files changed, 824 insertions, 0 deletions
diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs new file mode 100644 index 00000000..87ad0da8 --- /dev/null +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs | |||
@@ -0,0 +1,824 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Core.ExtensibilityServices | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Diagnostics; | ||
8 | using System.Globalization; | ||
9 | using System.IO; | ||
10 | using System.Security.Cryptography; | ||
11 | using System.Text; | ||
12 | using System.Text.RegularExpressions; | ||
13 | using System.Xml.Linq; | ||
14 | using WixToolset.Data; | ||
15 | using WixToolset.Data.Tuples; | ||
16 | using WixToolset.Extensibility; | ||
17 | using WixToolset.Extensibility.Services; | ||
18 | |||
19 | internal class ParseHelper : IParseHelper | ||
20 | { | ||
21 | private const string LegalLongFilenameCharacters = @"[^\\\?|><:/\*""]"; // opposite of illegal above. | ||
22 | private static readonly Regex LegalLongFilename = new Regex(String.Concat("^", LegalLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled); | ||
23 | |||
24 | private const string LegalRelativeLongFilenameCharacters = @"[^\?|><:/\*""]"; // (like legal long, but we allow '\') illegal: ? | > < : / * " | ||
25 | private static readonly Regex LegalRelativeLongFilename = new Regex(String.Concat("^", LegalRelativeLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled); | ||
26 | |||
27 | private const string LegalWildcardLongFilenameCharacters = @"[^\\|><:/""]"; // illegal: \ | > < : / " | ||
28 | private static readonly Regex LegalWildcardLongFilename = new Regex(String.Concat("^", LegalWildcardLongFilenameCharacters, @"{1,259}$")); | ||
29 | |||
30 | private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); | ||
31 | |||
32 | private static readonly Regex PutGuidHere = new Regex(@"PUT\-GUID\-(?:\d+\-)?HERE", RegexOptions.Singleline); | ||
33 | |||
34 | public ParseHelper(IServiceProvider serviceProvider) | ||
35 | { | ||
36 | this.ServiceProvider = serviceProvider; | ||
37 | } | ||
38 | |||
39 | private IServiceProvider ServiceProvider { get; } | ||
40 | |||
41 | private ITupleDefinitionCreator Creator { get; set; } | ||
42 | |||
43 | public bool ContainsProperty(string possibleProperty) | ||
44 | { | ||
45 | return Common.ContainsProperty(possibleProperty); | ||
46 | } | ||
47 | |||
48 | public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) | ||
49 | { | ||
50 | var wixComplexReferenceRow = (WixComplexReferenceTuple)this.CreateRow(section, sourceLineNumbers, TupleDefinitionType.WixComplexReference); | ||
51 | wixComplexReferenceRow.Parent = parentId; | ||
52 | wixComplexReferenceRow.ParentType = parentType; | ||
53 | wixComplexReferenceRow.ParentLanguage = parentLanguage; | ||
54 | wixComplexReferenceRow.Child = childId; | ||
55 | wixComplexReferenceRow.ChildType = childType; | ||
56 | wixComplexReferenceRow.IsPrimary = isPrimary; | ||
57 | |||
58 | this.CreateWixGroupRow(section, sourceLineNumbers, parentType, parentId, childType, childId); | ||
59 | } | ||
60 | |||
61 | public Identifier CreateDirectoryRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null, ISet<string> sectionInlinedDirectoryIds = null) | ||
62 | { | ||
63 | string defaultDir = null; | ||
64 | |||
65 | if (name.Equals("SourceDir") || this.IsValidShortFilename(name, false)) | ||
66 | { | ||
67 | defaultDir = name; | ||
68 | } | ||
69 | else | ||
70 | { | ||
71 | if (String.IsNullOrEmpty(shortName)) | ||
72 | { | ||
73 | shortName = this.CreateShortName(name, false, false, "Directory", parentId); | ||
74 | } | ||
75 | |||
76 | defaultDir = String.Concat(shortName, "|", name); | ||
77 | } | ||
78 | |||
79 | if (!String.IsNullOrEmpty(sourceName)) | ||
80 | { | ||
81 | if (this.IsValidShortFilename(sourceName, false)) | ||
82 | { | ||
83 | defaultDir = String.Concat(defaultDir, ":", sourceName); | ||
84 | } | ||
85 | else | ||
86 | { | ||
87 | if (String.IsNullOrEmpty(shortSourceName)) | ||
88 | { | ||
89 | shortSourceName = this.CreateShortName(sourceName, false, false, "Directory", parentId); | ||
90 | } | ||
91 | |||
92 | defaultDir = String.Concat(defaultDir, ":", shortSourceName, "|", sourceName); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | // For anonymous directories, create the identifier. If this identifier already exists in the | ||
97 | // active section, bail so we don't add duplicate anonymous directory rows (which are legal | ||
98 | // but bloat the intermediate and ultimately make the linker do "busy work"). | ||
99 | if (null == id) | ||
100 | { | ||
101 | id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); | ||
102 | |||
103 | if (!sectionInlinedDirectoryIds.Add(id.Id)) | ||
104 | { | ||
105 | return id; | ||
106 | } | ||
107 | } | ||
108 | |||
109 | var row = this.CreateRow(section, sourceLineNumbers, TupleDefinitionType.Directory, id); | ||
110 | row.Set(1, parentId); | ||
111 | row.Set(2, defaultDir); | ||
112 | return id; | ||
113 | } | ||
114 | |||
115 | public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId) | ||
116 | { | ||
117 | string id = null; | ||
118 | string[] inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true); | ||
119 | |||
120 | if (null != inlineSyntax) | ||
121 | { | ||
122 | // Special case the single entry in the inline syntax since it is the most common case | ||
123 | // and needs no extra processing. It's just a reference to an existing directory. | ||
124 | if (1 == inlineSyntax.Length) | ||
125 | { | ||
126 | id = inlineSyntax[0]; | ||
127 | this.CreateSimpleReference(section, sourceLineNumbers, "Directory", id); | ||
128 | } | ||
129 | else // start creating rows for the entries in the inline syntax | ||
130 | { | ||
131 | id = parentId; | ||
132 | |||
133 | int pathStartsAt = 0; | ||
134 | if (inlineSyntax[0].EndsWith(":")) | ||
135 | { | ||
136 | // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide? | ||
137 | //if (null != parentId) | ||
138 | //{ | ||
139 | // this.core.OnMessage(WixErrors.Xxx(sourceLineNumbers)); | ||
140 | //} | ||
141 | |||
142 | id = inlineSyntax[0].TrimEnd(':'); | ||
143 | this.CreateSimpleReference(section, sourceLineNumbers, "Directory", id); | ||
144 | |||
145 | pathStartsAt = 1; | ||
146 | } | ||
147 | |||
148 | for (int i = pathStartsAt; i < inlineSyntax.Length; ++i) | ||
149 | { | ||
150 | Identifier inlineId = this.CreateDirectoryRow(section, sourceLineNumbers, null, id, inlineSyntax[i]); | ||
151 | id = inlineId.Id; | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | return id; | ||
157 | } | ||
158 | |||
159 | public string CreateGuid(Guid namespaceGuid, string value) | ||
160 | { | ||
161 | return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); | ||
162 | } | ||
163 | |||
164 | public Identifier CreateIdentifier(string prefix, params string[] args) | ||
165 | { | ||
166 | var id = Common.GenerateIdentifier(prefix, args); | ||
167 | return new Identifier(id, AccessModifier.Private); | ||
168 | } | ||
169 | |||
170 | public Identifier CreateIdentifierFromFilename(string filename) | ||
171 | { | ||
172 | var id = Common.GetIdentifierFromName(filename); | ||
173 | return new Identifier(id, AccessModifier.Private); | ||
174 | } | ||
175 | |||
176 | public Identifier CreateRegistryRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId, bool escapeLeadingHash) | ||
177 | { | ||
178 | Identifier id = null; | ||
179 | |||
180 | if (-1 > root || 3 < root) | ||
181 | { | ||
182 | throw new ArgumentOutOfRangeException("root"); | ||
183 | } | ||
184 | |||
185 | if (null == key) | ||
186 | { | ||
187 | throw new ArgumentNullException("key"); | ||
188 | } | ||
189 | |||
190 | if (null == componentId) | ||
191 | { | ||
192 | throw new ArgumentNullException("componentId"); | ||
193 | } | ||
194 | |||
195 | // Escape the leading '#' character for string registry values. | ||
196 | if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal)) | ||
197 | { | ||
198 | value = String.Concat("#", value); | ||
199 | } | ||
200 | |||
201 | id = this.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name)); | ||
202 | |||
203 | var row = this.CreateRow(section, sourceLineNumbers, TupleDefinitionType.Registry, id); | ||
204 | row.Set(1, root); | ||
205 | row.Set(2, key); | ||
206 | row.Set(3, name); | ||
207 | row.Set(4, value); | ||
208 | row.Set(5, componentId); | ||
209 | |||
210 | return id; | ||
211 | } | ||
212 | |||
213 | public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys) | ||
214 | { | ||
215 | var joinedKeys = String.Join("/", primaryKeys); | ||
216 | var id = String.Concat(tableName, ":", joinedKeys); | ||
217 | |||
218 | var wixSimpleReferenceRow = (WixSimpleReferenceTuple)this.CreateRow(section, sourceLineNumbers, TupleDefinitionType.WixSimpleReference); | ||
219 | wixSimpleReferenceRow.Table = tableName; | ||
220 | wixSimpleReferenceRow.PrimaryKeys = joinedKeys; | ||
221 | } | ||
222 | |||
223 | public void CreateWixGroupRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) | ||
224 | { | ||
225 | if (null == parentId || ComplexReferenceParentType.Unknown == parentType) | ||
226 | { | ||
227 | return; | ||
228 | } | ||
229 | |||
230 | if (null == childId) | ||
231 | { | ||
232 | throw new ArgumentNullException("childId"); | ||
233 | } | ||
234 | |||
235 | var row = (WixGroupTuple)this.CreateRow(section, sourceLineNumbers, TupleDefinitionType.WixGroup); | ||
236 | row.ParentId = parentId; | ||
237 | row.ParentType = parentType; | ||
238 | row.ChildId = childId; | ||
239 | row.ChildType = childType; | ||
240 | } | ||
241 | |||
242 | public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) | ||
243 | { | ||
244 | if (this.Creator == null) | ||
245 | { | ||
246 | this.CreateTupleDefinitionCreator(); | ||
247 | } | ||
248 | |||
249 | if (!this.Creator.TryGetTupleDefinitionByName(tableName, out var tupleDefinition)) | ||
250 | { | ||
251 | throw new ArgumentException(nameof(tableName)); | ||
252 | } | ||
253 | |||
254 | return CreateRow(section, sourceLineNumbers, tupleDefinition, identifier); | ||
255 | } | ||
256 | |||
257 | public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, TupleDefinitionType tupleType, Identifier identifier = null) | ||
258 | { | ||
259 | var tupleDefinition = TupleDefinitions.ByType(tupleType); | ||
260 | |||
261 | return CreateRow(section, sourceLineNumbers, tupleDefinition, identifier); | ||
262 | } | ||
263 | |||
264 | public string CreateShortName(string longName, bool keepExtension, bool allowWildcards, params string[] args) | ||
265 | { | ||
266 | // canonicalize the long name if its not a localization identifier (they are case-sensitive) | ||
267 | if (!this.IsValidLocIdentifier(longName)) | ||
268 | { | ||
269 | longName = longName.ToLowerInvariant(); | ||
270 | } | ||
271 | |||
272 | // collect all the data | ||
273 | List<string> strings = new List<string>(1 + args.Length); | ||
274 | strings.Add(longName); | ||
275 | strings.AddRange(args); | ||
276 | |||
277 | // prepare for hashing | ||
278 | string stringData = String.Join("|", strings); | ||
279 | byte[] data = Encoding.UTF8.GetBytes(stringData); | ||
280 | |||
281 | // hash the data | ||
282 | byte[] hash; | ||
283 | using (var sha1 = new SHA1CryptoServiceProvider()) | ||
284 | { | ||
285 | hash = sha1.ComputeHash(data); | ||
286 | } | ||
287 | |||
288 | // generate the short file/directory name without an extension | ||
289 | StringBuilder shortName = new StringBuilder(Convert.ToBase64String(hash)); | ||
290 | shortName.Remove(8, shortName.Length - 8).Replace('+', '-').Replace('/', '_'); | ||
291 | |||
292 | if (keepExtension) | ||
293 | { | ||
294 | string extension = Path.GetExtension(longName); | ||
295 | |||
296 | if (4 < extension.Length) | ||
297 | { | ||
298 | extension = extension.Substring(0, 4); | ||
299 | } | ||
300 | |||
301 | shortName.Append(extension); | ||
302 | |||
303 | // check the generated short name to ensure its still legal (the extension may not be legal) | ||
304 | if (!this.IsValidShortFilename(shortName.ToString(), allowWildcards)) | ||
305 | { | ||
306 | // remove the extension (by truncating the generated file name back to the generated characters) | ||
307 | shortName.Length -= extension.Length; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | return shortName.ToString().ToLowerInvariant(); | ||
312 | } | ||
313 | |||
314 | public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName) | ||
315 | { | ||
316 | var row = this.CreateRow(section, sourceLineNumbers, TupleDefinitionType.WixEnsureTable); | ||
317 | row.Set(0, tableName); | ||
318 | |||
319 | if (this.Creator == null) | ||
320 | { | ||
321 | this.CreateTupleDefinitionCreator(); | ||
322 | } | ||
323 | |||
324 | // We don't add custom table definitions to the tableDefinitions collection, | ||
325 | // so if it's not in there, it better be a custom table. If the Id is just wrong, | ||
326 | // instead of a custom table, we get an unresolved reference at link time. | ||
327 | if (!this.Creator.TryGetTupleDefinitionByName(tableName, out var ignored)) | ||
328 | { | ||
329 | this.CreateSimpleReference(section, sourceLineNumbers, "WixCustomTable", tableName); | ||
330 | } | ||
331 | } | ||
332 | |||
333 | public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) | ||
334 | { | ||
335 | if (null == attribute) | ||
336 | { | ||
337 | throw new ArgumentNullException("attribute"); | ||
338 | } | ||
339 | |||
340 | EmptyRule emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; | ||
341 | string value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); | ||
342 | |||
343 | if (String.IsNullOrEmpty(value) && canBeEmpty) | ||
344 | { | ||
345 | return String.Empty; | ||
346 | } | ||
347 | else if (!String.IsNullOrEmpty(value)) | ||
348 | { | ||
349 | // If the value starts and ends with braces or parenthesis, accept that and strip them off. | ||
350 | if ((value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal)) | ||
351 | || (value.StartsWith("(", StringComparison.Ordinal) && value.EndsWith(")", StringComparison.Ordinal))) | ||
352 | { | ||
353 | value = value.Substring(1, value.Length - 2); | ||
354 | } | ||
355 | |||
356 | if (generatable && "*".Equals(value, StringComparison.Ordinal)) | ||
357 | { | ||
358 | return value; | ||
359 | } | ||
360 | |||
361 | if (ParseHelper.PutGuidHere.IsMatch(value)) | ||
362 | { | ||
363 | Messaging.Instance.OnMessage(WixErrors.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
364 | return CompilerConstants.IllegalGuid; | ||
365 | } | ||
366 | else if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) | ||
367 | { | ||
368 | return value; | ||
369 | } | ||
370 | else if (Guid.TryParse(value, out var guid)) | ||
371 | { | ||
372 | var uppercaseGuid = guid.ToString().ToUpperInvariant(); | ||
373 | |||
374 | // TODO: This used to be a pedantic error, what should it be now? | ||
375 | //if (uppercaseGuid != value) | ||
376 | //{ | ||
377 | // Messaging.Instance.OnMessage(WixErrors.GuidContainsLowercaseLetters(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
378 | //} | ||
379 | |||
380 | return String.Concat("{", uppercaseGuid, "}"); | ||
381 | } | ||
382 | else | ||
383 | { | ||
384 | Messaging.Instance.OnMessage(WixErrors.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
385 | } | ||
386 | } | ||
387 | |||
388 | return CompilerConstants.IllegalGuid; | ||
389 | } | ||
390 | |||
391 | public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
392 | { | ||
393 | var access = AccessModifier.Public; | ||
394 | var value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); | ||
395 | |||
396 | var match = ParseHelper.LegalIdentifierWithAccess.Match(value); | ||
397 | if (!match.Success) | ||
398 | { | ||
399 | return null; | ||
400 | } | ||
401 | else if (match.Groups["access"].Success) | ||
402 | { | ||
403 | access = (AccessModifier)Enum.Parse(typeof(AccessModifier), match.Groups["access"].Value, true); | ||
404 | } | ||
405 | |||
406 | value = match.Groups["id"].Value; | ||
407 | |||
408 | if (Common.IsIdentifier(value) && 72 < value.Length) | ||
409 | { | ||
410 | Messaging.Instance.OnMessage(WixWarnings.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
411 | } | ||
412 | |||
413 | return new Identifier(value, access); | ||
414 | } | ||
415 | |||
416 | public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
417 | { | ||
418 | return Common.GetAttributeIdentifierValue(sourceLineNumbers, attribute); | ||
419 | } | ||
420 | |||
421 | public string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false) | ||
422 | { | ||
423 | string[] result = null; | ||
424 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
425 | |||
426 | if (!String.IsNullOrEmpty(value)) | ||
427 | { | ||
428 | int pathStartsAt = 0; | ||
429 | result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); | ||
430 | if (result[0].EndsWith(":", StringComparison.Ordinal)) | ||
431 | { | ||
432 | string id = result[0].TrimEnd(':'); | ||
433 | if (1 == result.Length) | ||
434 | { | ||
435 | Messaging.Instance.OnMessage(WixErrors.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id)); | ||
436 | return null; | ||
437 | } | ||
438 | else if (!this.IsValidIdentifier(id)) | ||
439 | { | ||
440 | Messaging.Instance.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id)); | ||
441 | return null; | ||
442 | } | ||
443 | |||
444 | pathStartsAt = 1; | ||
445 | } | ||
446 | else if (resultUsedToCreateReference && 1 == result.Length) | ||
447 | { | ||
448 | if (value.EndsWith("\\")) | ||
449 | { | ||
450 | if (!this.IsValidLongFilename(result[0], false, false)) | ||
451 | { | ||
452 | Messaging.Instance.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0])); | ||
453 | return null; | ||
454 | } | ||
455 | } | ||
456 | else if (!this.IsValidIdentifier(result[0])) | ||
457 | { | ||
458 | Messaging.Instance.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0])); | ||
459 | return null; | ||
460 | } | ||
461 | |||
462 | return result; // return early to avoid additional checks below. | ||
463 | } | ||
464 | |||
465 | // Check each part of the relative path to ensure that it is a valid directory name. | ||
466 | for (int i = pathStartsAt; i < result.Length; ++i) | ||
467 | { | ||
468 | if (!this.IsValidLongFilename(result[i], false, false)) | ||
469 | { | ||
470 | Messaging.Instance.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i])); | ||
471 | return null; | ||
472 | } | ||
473 | } | ||
474 | |||
475 | if (1 < result.Length && !value.EndsWith("\\")) | ||
476 | { | ||
477 | Messaging.Instance.OnMessage(WixWarnings.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
478 | } | ||
479 | } | ||
480 | |||
481 | return result; | ||
482 | } | ||
483 | |||
484 | public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) | ||
485 | { | ||
486 | return Common.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum); | ||
487 | } | ||
488 | |||
489 | public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative) | ||
490 | { | ||
491 | if (null == attribute) | ||
492 | { | ||
493 | throw new ArgumentNullException("attribute"); | ||
494 | } | ||
495 | |||
496 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
497 | |||
498 | if (0 < value.Length) | ||
499 | { | ||
500 | if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) | ||
501 | { | ||
502 | if (allowRelative) | ||
503 | { | ||
504 | Messaging.Instance.OnMessage(WixErrors.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
505 | } | ||
506 | else | ||
507 | { | ||
508 | Messaging.Instance.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
509 | } | ||
510 | } | ||
511 | else if (allowRelative) | ||
512 | { | ||
513 | string normalizedPath = value.Replace('\\', '/'); | ||
514 | if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) | ||
515 | { | ||
516 | Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
517 | } | ||
518 | } | ||
519 | else if (CompilerCore.IsAmbiguousFilename(value)) | ||
520 | { | ||
521 | Messaging.Instance.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
522 | } | ||
523 | } | ||
524 | |||
525 | return value; | ||
526 | } | ||
527 | |||
528 | public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) | ||
529 | { | ||
530 | Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing."); | ||
531 | |||
532 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
533 | |||
534 | if (0 < value.Length) | ||
535 | { | ||
536 | try | ||
537 | { | ||
538 | long longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat); | ||
539 | |||
540 | if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue) | ||
541 | { | ||
542 | Messaging.Instance.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, longValue)); | ||
543 | } | ||
544 | else if (minimum > longValue || maximum < longValue) | ||
545 | { | ||
546 | Messaging.Instance.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum)); | ||
547 | longValue = CompilerConstants.IllegalLong; | ||
548 | } | ||
549 | |||
550 | return longValue; | ||
551 | } | ||
552 | catch (FormatException) | ||
553 | { | ||
554 | Messaging.Instance.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
555 | } | ||
556 | catch (OverflowException) | ||
557 | { | ||
558 | Messaging.Instance.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
559 | } | ||
560 | } | ||
561 | |||
562 | return CompilerConstants.IllegalLong; | ||
563 | } | ||
564 | |||
565 | public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) | ||
566 | { | ||
567 | return Common.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); | ||
568 | } | ||
569 | |||
570 | public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
571 | { | ||
572 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
573 | |||
574 | if (!String.IsNullOrEmpty(value)) | ||
575 | { | ||
576 | if (Version.TryParse(value, out var version)) | ||
577 | { | ||
578 | return version.ToString(); | ||
579 | } | ||
580 | |||
581 | // Allow versions to contain binder variables. | ||
582 | if (Common.ContainsValidBinderVariable(value)) | ||
583 | { | ||
584 | return value; | ||
585 | } | ||
586 | |||
587 | Messaging.Instance.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
588 | } | ||
589 | |||
590 | return null; | ||
591 | } | ||
592 | |||
593 | public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
594 | { | ||
595 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
596 | |||
597 | switch (value) | ||
598 | { | ||
599 | case "yes": | ||
600 | case "true": | ||
601 | return YesNoDefaultType.Yes; | ||
602 | |||
603 | case "no": | ||
604 | case "false": | ||
605 | return YesNoDefaultType.No; | ||
606 | |||
607 | case "default": | ||
608 | return YesNoDefaultType.Default; | ||
609 | |||
610 | default: | ||
611 | Messaging.Instance.OnMessage(WixErrors.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
612 | return YesNoDefaultType.IllegalValue; | ||
613 | } | ||
614 | } | ||
615 | |||
616 | public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
617 | { | ||
618 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
619 | |||
620 | switch (value) | ||
621 | { | ||
622 | case "yes": | ||
623 | case "true": | ||
624 | return YesNoType.Yes; | ||
625 | |||
626 | case "no": | ||
627 | case "false": | ||
628 | return YesNoType.No; | ||
629 | |||
630 | default: | ||
631 | Messaging.Instance.OnMessage(WixErrors.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
632 | return YesNoType.IllegalValue; | ||
633 | } | ||
634 | } | ||
635 | |||
636 | public string GetConditionInnerText(XElement element) | ||
637 | { | ||
638 | var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); | ||
639 | |||
640 | // Return null for a non-existant condition. | ||
641 | return String.IsNullOrEmpty(value) ? null : value; | ||
642 | } | ||
643 | |||
644 | public string GetTrimmedInnerText(XElement element) | ||
645 | { | ||
646 | var value = Common.GetInnerText(element); | ||
647 | return value?.Trim(); | ||
648 | } | ||
649 | |||
650 | public bool IsValidIdentifier(string value) | ||
651 | { | ||
652 | return Common.IsIdentifier(value); | ||
653 | } | ||
654 | |||
655 | public bool IsValidLocIdentifier(string identifier) | ||
656 | { | ||
657 | if (String.IsNullOrEmpty(identifier)) | ||
658 | { | ||
659 | return false; | ||
660 | } | ||
661 | |||
662 | var match = Common.WixVariableRegex.Match(identifier); | ||
663 | |||
664 | return (match.Success && "loc" == match.Groups["namespace"].Value && 0 == match.Index && identifier.Length == match.Length); | ||
665 | } | ||
666 | |||
667 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) | ||
668 | { | ||
669 | if (String.IsNullOrEmpty(filename)) | ||
670 | { | ||
671 | return false; | ||
672 | } | ||
673 | |||
674 | // Check for a non-period character (all periods is not legal) | ||
675 | bool nonPeriodFound = false; | ||
676 | foreach (char character in filename) | ||
677 | { | ||
678 | if ('.' != character) | ||
679 | { | ||
680 | nonPeriodFound = true; | ||
681 | break; | ||
682 | } | ||
683 | } | ||
684 | |||
685 | if (allowWildcards) | ||
686 | { | ||
687 | return (nonPeriodFound && ParseHelper.LegalWildcardLongFilename.IsMatch(filename)); | ||
688 | } | ||
689 | else if (allowRelative) | ||
690 | { | ||
691 | return (nonPeriodFound && ParseHelper.LegalRelativeLongFilename.IsMatch(filename)); | ||
692 | } | ||
693 | else | ||
694 | { | ||
695 | return (nonPeriodFound && ParseHelper.LegalLongFilename.IsMatch(filename)); | ||
696 | } | ||
697 | } | ||
698 | |||
699 | public bool IsValidShortFilename(string filename, bool allowWildcards = false) | ||
700 | { | ||
701 | return Common.IsValidShortFilename(filename, allowWildcards); | ||
702 | } | ||
703 | |||
704 | public void ParseExtensionAttribute(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary<string, string> context = null) | ||
705 | { | ||
706 | // Ignore attributes defined by the W3C because we'll assume they are always right. | ||
707 | if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || | ||
708 | attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)) | ||
709 | { | ||
710 | return; | ||
711 | } | ||
712 | |||
713 | if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension)) | ||
714 | { | ||
715 | extension.ParseAttribute(intermediate, section, element, attribute, context); | ||
716 | } | ||
717 | else | ||
718 | { | ||
719 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
720 | Messaging.Instance.OnMessage(WixErrors.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName)); | ||
721 | } | ||
722 | } | ||
723 | |||
724 | public void ParseExtensionElement(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context = null) | ||
725 | { | ||
726 | if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) | ||
727 | { | ||
728 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(parentElement); | ||
729 | extension.ParseElement(intermediate, section, parentElement, element, context); | ||
730 | } | ||
731 | else | ||
732 | { | ||
733 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
734 | Messaging.Instance.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); | ||
735 | } | ||
736 | } | ||
737 | |||
738 | public ComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) | ||
739 | { | ||
740 | ComponentKeyPath keyPath = null; | ||
741 | |||
742 | if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) | ||
743 | { | ||
744 | keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context); | ||
745 | } | ||
746 | else | ||
747 | { | ||
748 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
749 | Messaging.Instance.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); | ||
750 | } | ||
751 | |||
752 | return keyPath; | ||
753 | } | ||
754 | |||
755 | public void ParseForExtensionElements(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement element) | ||
756 | { | ||
757 | foreach (XElement child in element.Elements()) | ||
758 | { | ||
759 | if (element.Name.Namespace == child.Name.Namespace) | ||
760 | { | ||
761 | this.UnexpectedElement(element, child); | ||
762 | } | ||
763 | else | ||
764 | { | ||
765 | this.ParseExtensionElement(extensions, intermediate, section, element, child); | ||
766 | } | ||
767 | } | ||
768 | } | ||
769 | |||
770 | public void UnexpectedAttribute(XElement element, XAttribute attribute) | ||
771 | { | ||
772 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
773 | Common.UnexpectedAttribute(sourceLineNumbers, attribute); | ||
774 | } | ||
775 | |||
776 | public void UnexpectedElement(XElement parentElement, XElement childElement) | ||
777 | { | ||
778 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement); | ||
779 | Messaging.Instance.OnMessage(WixErrors.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName)); | ||
780 | } | ||
781 | |||
782 | private void CreateTupleDefinitionCreator() | ||
783 | { | ||
784 | this.Creator = (ITupleDefinitionCreator)this.ServiceProvider.GetService(typeof(ITupleDefinitionCreator)); | ||
785 | } | ||
786 | |||
787 | private static IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateTupleDefinition tupleDefinition, Identifier identifier) | ||
788 | { | ||
789 | var row = tupleDefinition.CreateTuple(sourceLineNumbers, identifier); | ||
790 | |||
791 | if (null != identifier) | ||
792 | { | ||
793 | if (row.Definition.FieldDefinitions[0].Type == IntermediateFieldType.Number) | ||
794 | { | ||
795 | row.Set(0, Convert.ToInt32(identifier.Id)); | ||
796 | } | ||
797 | else | ||
798 | { | ||
799 | row.Set(0, identifier.Id); | ||
800 | } | ||
801 | } | ||
802 | |||
803 | section.Tuples.Add(row); | ||
804 | |||
805 | return row; | ||
806 | } | ||
807 | |||
808 | private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension) | ||
809 | { | ||
810 | extension = null; | ||
811 | |||
812 | foreach (var ext in extensions) | ||
813 | { | ||
814 | if (ext.Namespace == ns) | ||
815 | { | ||
816 | extension = ext; | ||
817 | break; | ||
818 | } | ||
819 | } | ||
820 | |||
821 | return extension != null; | ||
822 | } | ||
823 | } | ||
824 | } | ||