/// <summary> /// Grab a parameter (_not_ the first) off of incoming stream, then pass /// all the rest of the characters unchanged. Quoting rules are /// complicated, see http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES /// and https://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.120).aspx /// for details. /// </summary> /// <remarks> /// Engineered from the D.Deley article above into a state machine. /// </remarks> public static IEnumerable<char> RemoveArgument( this IEnumerable<char> source, StringBuilder argument, /*out*/ RemoveArgumentStateLog log = null) { ArgStates state = ArgStates.Start; int nBackslashes = 0; foreach (char c in source) { if (null != log) log.Add("fetchChar", state, c, nBackslashes, argument); goto stateAction; reinspectChar: if (null != log) log.Add("reinspectChar", state, c, nBackslashes, argument); goto stateAction; stateAction: switch (state) { case ArgStates.Start: state = ArgStates.LeadingWhitespace; goto reinspectChar; case ArgStates.LeadingWhitespace: if (' ' == c || '\t' == c) { continue; } else { state = ArgStates.ScanningBackslashesOutsideQuotes; nBackslashes = 0; goto reinspectChar; } case ArgStates.ScanningBackslashesOutsideQuotes: if ('\\' == c) { nBackslashes++; continue; } else if ('\"' == c) { state = ArgStates.QuoteAfterBackslashesOutsideQuotes; goto reinspectChar; } else { state = ArgStates.EmittingBackslashesOutsideQuotes; goto reinspectChar; } case ArgStates.QuoteAfterBackslashesOutsideQuotes: { bool oddBackslashes = 1 == nBackslashes % 2; nBackslashes /= 2; if (oddBackslashes) { state = ArgStates.EmittingBackslashesOutsideQuotes; goto reinspectChar; } else { state = ArgStates.EmittingBackslashesOutsideQuotesGoingInsideQuotes; continue; } } case ArgStates.EmittingBackslashesOutsideQuotes: argument.Append('\\', nBackslashes); nBackslashes = 0; state = ArgStates.CheckForArgumentEndOutsideQuotes; goto reinspectChar; case ArgStates.CheckForArgumentEndOutsideQuotes: if (' ' == c || '\t' == c) { state = ArgStates.CopyToEnd; goto reinspectChar; } else { argument.Append(c); nBackslashes = 0; state = ArgStates.ScanningBackslashesOutsideQuotes; continue; } case ArgStates.EmittingBackslashesOutsideQuotesGoingInsideQuotes: argument.Append('\\', nBackslashes); nBackslashes = 0; state = ArgStates.ScanningBackslashesInsideQuotes; goto reinspectChar; case ArgStates.ScanningBackslashesInsideQuotes: if ('\\' == c) { nBackslashes++; continue; } else if ('\"' == c) { state = ArgStates.QuoteAfterBackslashesInsideQuotes; goto reinspectChar; } else { state = ArgStates.EmittingBackslashesInsideQuotes; goto reinspectChar; } case ArgStates.QuoteAfterBackslashesInsideQuotes: { bool oddBackslashes = 1 == nBackslashes % 2; nBackslashes /= 2; if (oddBackslashes) { state = ArgStates.EmittingBackslashesInsideQuotes; goto reinspectChar; } else { state = ArgStates.CheckForTwoQuotesAfterBackslashesInsideQuotes; continue; } } case ArgStates.EmittingBackslashesInsideQuotes: argument.Append('\\', nBackslashes).Append(c); nBackslashes = 0; state = ArgStates.ScanningBackslashesInsideQuotes; continue; case ArgStates.CheckForTwoQuotesAfterBackslashesInsideQuotes: if ('\"' == c) { state = ArgStates.EmittingBackslashesInsideQuotesStayingInsideQuotes; goto reinspectChar; } else { state = ArgStates.EmittingBackslashesInsideQuotesGoingOutsideQuotes; goto reinspectChar; } case ArgStates.EmittingBackslashesInsideQuotesGoingOutsideQuotes: argument.Append('\\', nBackslashes); nBackslashes = 0; state = ArgStates.CheckForArgumentEndInsideQuotesGoingOutsideQuotes; goto reinspectChar; case ArgStates.EmittingBackslashesInsideQuotesStayingInsideQuotes: argument.Append('\\', nBackslashes).Append('\"'); nBackslashes = 0; state = ArgStates.ScanningBackslashesInsideQuotes; continue; case ArgStates.CheckForArgumentEndInsideQuotesGoingOutsideQuotes: if (' ' == c || '\t' == c) { state = ArgStates.CopyToEnd; goto reinspectChar; } else { argument.Append(c); nBackslashes = 0; state = ArgStates.ScanningBackslashesOutsideQuotes; continue; } case ArgStates.CopyToEnd: yield return c; state = ArgStates.CopyToEnd; continue; } } // Flush final backslashes if any (there's only one state where this is necessary) if (ArgStates.CheckForTwoQuotesAfterBackslashesInsideQuotes == state) { if (null != log) log.Add("flushing final backslashes", state, '¶', nBackslashes, argument); argument.Append('\\', nBackslashes); } if (null != log) log.Add("final result", ArgStates.End, '¶', 0, argument); }
static void ParseCommandLine(string[] args) { ArgStates argState = ArgStates.Command; for (int index = 0; index < args.Length; index++) { string arg = args[index]; switch (argState) { case ArgStates.Command: switch (arg.ToUpperInvariant()) { case "SIGN": command = Command.Sign; break; case "VERIFY": command = Command.Verify; break; default: throw new UsageException(); } argState = ArgStates.Options; break; case ArgStates.Options: if (arg.Substring(0, 1) == "-" || arg.Substring(0, 1) == "/") { switch (arg.Substring(1).ToUpperInvariant()) { case "L": storeLocationSpecified = true; argState = ArgStates.Location; break; case "SHA1": argState = ArgStates.SHA1; break; case "PWD": argState = ArgStates.Password; break; case "SUBJECT": argState = ArgStates.Subject; break; case "ISSUER": argState = ArgStates.Issuer; break; case "PFX": argState = ArgStates.PFX; break; case "INCLUDE": argState = ArgStates.Include; break; case "V": verbose = true; break; case "DETACHED": detached = true; break; case "?": goto default; default: throw new UsageException(); } } else { fileNames.Clear(); fileNames.Add(arg); argState = ArgStates.FileName; } break; case ArgStates.Location: switch (arg.ToUpperInvariant()) { case "CU": storeLocation = StoreLocation.CurrentUser; break; case "LM": storeLocation = StoreLocation.LocalMachine; break; default: throw new UsageException(); } argState = ArgStates.Options; break; case ArgStates.Password: password = arg; argState = ArgStates.Options; break; case ArgStates.SHA1: sha1 = arg; argState = ArgStates.Options; break; case ArgStates.Subject: subjects.Add(arg); argState = ArgStates.Options; break; case ArgStates.Issuer: issuers.Add(arg); argState = ArgStates.Options; break; case ArgStates.PFX: pfxFile = arg; argState = ArgStates.Options; break; case ArgStates.Include: try { includeOptions.Add((IncludeOptions)Convert.ToInt32(arg)); } catch (FormatException) { throw new UsageException(); } argState = ArgStates.Options; break; case ArgStates.FileName: if (arg.Substring(0, 1) == "-" || arg.Substring(0, 1) == "/") { throw new UsageException(); } fileNames.Add(arg); break; default: throw new InternalException("Internal error: Unknown argument state (argState = " + argState.ToString() + ")."); } } // Make sure we are in good state. if (argState != ArgStates.FileName) { throw new UsageException(); } // Make sure all required options are valid. // Note: As stated in the help screen, non-fatal invalid options for // the specific command is ignore. You can add the logic here // to further handle these invalid options if desired. switch (command) { case Command.Sign: // -l and -pfxFile are exclusive. if (null != pfxFile) { if (storeLocationSpecified) { throw new UsageException(); } } break; case Command.Verify: break; default: throw new InternalException("Internal error: Unknown command state (Command = " + command.ToString() + ")."); } }