aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/CommandLine/CommandLineArguments.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/CommandLine/CommandLineArguments.cs')
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineArguments.cs211
1 files changed, 211 insertions, 0 deletions
diff --git a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs
new file mode 100644
index 00000000..37adcfd3
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs
@@ -0,0 +1,211 @@
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
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using WixToolset.Extensibility.Services;
11
12 internal class CommandLineArguments : ICommandLineArguments
13 {
14 public string[] OriginalArguments { get; set; }
15
16 public string[] Arguments { get; set; }
17
18 public string[] Extensions { get; set; }
19
20 public string ErrorArgument { get; set; }
21
22 private IServiceProvider ServiceProvider { get; }
23
24 public CommandLineArguments(IServiceProvider serviceProvider)
25 {
26 this.ServiceProvider = serviceProvider;
27 }
28
29 public void Populate(string commandLine)
30 {
31 var args = CommandLineArguments.ParseArgumentsToArray(commandLine);
32
33 this.Populate(args.ToArray());
34 }
35
36 public void Populate(string[] args)
37 {
38 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args);
39
40 this.ProcessArgumentsAndParseExtensions(this.OriginalArguments);
41 }
42
43 public IParseCommandLine Parse()
44 {
45 var messaging = (IMessaging)this.ServiceProvider.GetService(typeof(IMessaging));
46
47 return new ParseCommandLine(messaging, this.Arguments, this.ErrorArgument);
48 }
49
50 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
51 {
52 List<string> args = new List<string>();
53
54 foreach (var arg in commandLineArguments)
55 {
56 if ('@' == arg[0])
57 {
58 var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1));
59 args.AddRange(responseFileArguments);
60 }
61 else
62 {
63 args.Add(arg);
64 }
65 }
66
67 this.OriginalArguments = args.ToArray();
68 }
69
70 private void ProcessArgumentsAndParseExtensions(string[] args)
71 {
72 var arguments = new List<string>();
73 var extensions = new List<string>();
74
75 for (var i = 0; i < args.Length; ++i)
76 {
77 var arg = args[i];
78
79 if ("-ext" == arg || "/ext" == arg)
80 {
81 if (!CommandLineArguments.IsSwitchAt(args, ++i))
82 {
83 extensions.Add(args[i]);
84 }
85 else
86 {
87 this.ErrorArgument = arg;
88 break;
89 }
90 }
91 else
92 {
93 arguments.Add(arg);
94 }
95 }
96
97 this.Arguments = arguments.ToArray();
98 this.Extensions = extensions.ToArray();
99 }
100
101 private static List<string> ParseResponseFile(string responseFile)
102 {
103 string arguments;
104
105 using (StreamReader reader = new StreamReader(responseFile))
106 {
107 arguments = reader.ReadToEnd();
108 }
109
110 return CommandLineArguments.ParseArgumentsToArray(arguments);
111 }
112
113 private static List<string> ParseArgumentsToArray(string arguments)
114 {
115 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
116 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
117 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
118 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
119
120 // Collects the final list of arguments to be returned.
121 var argsList = new List<string>();
122
123 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
124 var insideQuote = false;
125
126 // Index of the start of the current argument substring; either the start of the argument
127 // or the start of a quoted or unquoted sequence within it.
128 var partStart = 0;
129
130 // The current argument string being built; when completed it will be added to the list.
131 var arg = new StringBuilder();
132
133 for (int i = 0; i <= arguments.Length; i++)
134 {
135 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
136 {
137 // Reached a whitespace separator or the end of the string.
138
139 // Finish building the current argument.
140 arg.Append(arguments.Substring(partStart, i - partStart));
141
142 // Skip over the whitespace character.
143 partStart = i + 1;
144
145 // Add the argument to the list if it's not empty.
146 if (arg.Length > 0)
147 {
148 argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString()));
149 arg.Length = 0;
150 }
151 }
152 else if (i > partStart && arguments[i - 1] == '\\')
153 {
154 // Check the character following an unprocessed backslash.
155 // Unescape quotes, and backslashes followed by a quote.
156 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
157 {
158 // Unescape the quote or backslash by skipping the preceeding backslash.
159 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
160 arg.Append(arguments[i]);
161 partStart = i + 1;
162 }
163 }
164 else if (arguments[i] == '"')
165 {
166 // Add the quoted or unquoted section to the argument string.
167 arg.Append(arguments.Substring(partStart, i - partStart));
168
169 // And skip over the quote character.
170 partStart = i + 1;
171
172 insideQuote = !insideQuote;
173 }
174 }
175
176 return argsList;
177 }
178
179 private static string ExpandEnvironmentVariables(string arguments)
180 {
181 var id = Environment.GetEnvironmentVariables();
182
183 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
184 MatchCollection matches = regex.Matches(arguments);
185
186 string value = String.Empty;
187 for (int i = 0; i <= (matches.Count - 1); i++)
188 {
189 try
190 {
191 var key = matches[i].Value;
192 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
193 value = id[key].ToString();
194 arguments = regex.Replace(arguments, value);
195 }
196 catch (NullReferenceException)
197 {
198 // Collapse unresolved environment variables.
199 arguments = regex.Replace(arguments, value);
200 }
201 }
202
203 return arguments;
204 }
205
206 private static bool IsSwitchAt(string[] args, int index)
207 {
208 return args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]);
209 }
210 }
211}