Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Escape command line arguments in c#

Short version:

Is it enough to wrap the argument in quotes and escape \ and " ?

Code version

I want to pass the command line arguments string[] args to another process using ProcessInfo.Arguments.

ProcessStartInfo info = new ProcessStartInfo(); info.FileName = Application.ExecutablePath; info.UseShellExecute = true; info.Verb = "runas"; // Provides Run as Administrator info.Arguments = EscapeCommandLineArguments(args); Process.Start(info); 

The problem is that I get the arguments as an array and must merge them into a single string. An arguments could be crafted to trick my program.

my.exe "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry" 

According to this answer I have created the following function to escape a single argument, but I might have missed something.

private static string EscapeCommandLineArguments(string[] args) {     string arguments = "";     foreach (string arg in args)     {         arguments += " \"" +             arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") +             "\"";     }     return arguments; } 

Is this good enough or is there any framework function for this?

like image 493
hultqvist Avatar asked Apr 01 '11 07:04

hultqvist


People also ask

How do I escape from command line?

Three Ways to Escape Spaces on WindowsBy enclosing the path (or parts of it) in double quotation marks ( ” ). By adding a caret character ( ^ ) before each space. (This only works in Command Prompt/CMD, and it doesn't seem to work with every command.) By adding a grave accent character ( ` ) before each space.

What are argv and argc in C?

The first parameter, argc (argument count) is an integer that indicates how many arguments were entered on the command line when the program was started. The second parameter, argv (argument vector), is an array of pointers to arrays of character objects.

What is the significance of argc and argv in command line arguments?

They are parameters/arguments supplied to the program when it is invoked. They are used to control program from outside instead of hard coding those values inside the code. argv[argc] is a NULL pointer. argv[0] holds the name of the program.


2 Answers

It's more complicated than that though!

I was having related problem (writing front-end .exe that will call the back-end with all parameters passed + some extra ones) and so i looked how people do that, ran into your question. Initially all seemed good doing it as you suggest arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote).

However when i call with arguments c:\temp a\\b, this gets passed as c:\temp and a\\b, which leads to the back-end being called with "c:\\temp" "a\\\\b" - which is incorrect, because there that will be two arguments c:\\temp and a\\\\b - not what we wanted! We have been overzealous in escapes (windows is not unix!).

And so i read in detail http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx and it actually describes there how those cases are handled: backslashes are treated as escape only in front of double quote.

There is a twist to it in how multiple \ are handled there, the explanation can leave one dizzy for a while. I'll try to re-phrase said unescape rule here: say we have a substring of N \, followed by ". When unescaping, we replace that substring with int(N/2) \ and iff N was odd, we add " at the end.

The encoding for such decoding would go like that: for an argument, find each substring of 0-or-more \ followed by " and replace it by twice-as-many \, followed by \". Which we can do like so:

s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\""); 

That's all...

PS. ... not. Wait, wait - there is more! :)

We did the encoding correctly but there is a twist because you are enclosing all parameters in double-quotes (in case there are spaces in some of them). There is a boundary issue - in case a parameter ends on \, adding " after it will break the meaning of closing quote. Example c:\one\ two parsed to c:\one\ and two then will be re-assembled to "c:\one\" "two" that will me (mis)understood as one argument c:\one" two (I tried that, i am not making it up). So what we need in addition is to check if argument ends on \ and if so, double the number of backslashes at the end, like so:

s = "\"" + Regex.Replace(s, @"(\\+)$", @"$1$1") + "\""; 
like image 63
Nas Banov Avatar answered Oct 13 '22 04:10

Nas Banov


My answer was similar to Nas Banov's answer but I wanted double quotes only if necessary.

Cutting out extra unnecessary double quotes

My code saves unnecessarily putting double quotes around it all the time which is important *when you are getting up close to the character limit for parameters.

/// <summary> /// Encodes an argument for passing into a program /// </summary> /// <param name="original">The value that should be received by the program</param> /// <returns>The value which needs to be passed to the program for the original value  /// to come through</returns> public static string EncodeParameterArgument(string original) {     if( string.IsNullOrEmpty(original))         return original;     string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");     value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");     return value; }  // This is an EDIT // Note that this version does the same but handles new lines in the arugments public static string EncodeParameterArgumentMultiLine(string original) {     if (string.IsNullOrEmpty(original))         return original;     string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");     value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"", RegexOptions.Singleline);      return value; } 

explanation

To escape the backslashes and double quotes correctly you can just replace any instances of multiple backslashes followed by a single double quote with:

string value = Regex.Replace(original, @"(\\*)" + "\"", @"\$1$0"); 

An extra twice the original backslashes + 1 and the original double quote. i.e., '\' + originalbackslashes + originalbackslashes + '"'. I used $1$0 since $0 has the original backslashes and the original double quote so it makes the replacement a nicer one to read.

value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\""); 

This can only ever match an entire line that contains a whitespace.

If it matches then it adds double quotes to the beginning and end.

If there was originally backslashes on the end of the argument they will not have been quoted, now that there is a double quote on the end they need to be. So they are duplicated, which quotes them all, and prevents unintentionally quoting the final double quote

It does a minimal matching for the first section so that the last .*? doesn't eat into matching the final backslashes

Output

So these inputs produce the following outputs

hello

hello

\hello\12\3\

\hello\12\3\

hello world

"hello world"

\"hello\"

\\"hello\\\"

\"hello\ world

"\\"hello\ world"

\"hello\\\ world\

"\\"hello\\\ world\\"

hello world\\

"hello world\\\\"

like image 34
Matt Vukomanovic Avatar answered Oct 13 '22 05:10

Matt Vukomanovic