Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #232 #235

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/CommandLine/CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
<Compile Include="ParserSettings.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Core\NameExtensions.cs" />
<Compile Include="StringToCommandLine\CSharpStyleCommandLineParser.cs" />
<Compile Include="StringToCommandLine\DefaultWindowsCommandLineParser.cs" />
<Compile Include="StringToCommandLine\StringToCommandLineParserBase.cs" />
<Compile Include="Text\AssemblyLicenseAttribute.cs">
<SubType>Code</SubType>
</Compile>
Expand Down Expand Up @@ -167,7 +170,16 @@
</Reference>
</ItemGroup>
</When>
<When Condition="($(TargetFrameworkIdentifier) == '.NETCore') Or ($(TargetFrameworkProfile) == 'Profile7') Or ($(TargetFrameworkProfile) == 'Profile44')">
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.1' Or $(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6')">
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>..\..\packages\FSharp.Core\lib\net40\FSharp.Core.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
<When Condition="($(TargetFrameworkIdentifier) == '.NETCore') Or ($(TargetFrameworkIdentifier) == 'Xamarin.Mac') Or ($(TargetFrameworkProfile) == 'Profile7') Or ($(TargetFrameworkProfile) == 'Profile44')">
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>..\..\packages\FSharp.Core\lib\portable-net45+netcore45\FSharp.Core.dll</HintPath>
Expand All @@ -176,10 +188,10 @@
</Reference>
</ItemGroup>
</When>
<When Condition="($(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.1' Or $(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6')) Or ($(TargetFrameworkIdentifier) == 'MonoAndroid') Or ($(TargetFrameworkIdentifier) == 'MonoTouch')">
<When Condition="($(TargetFrameworkIdentifier) == 'MonoAndroid') Or ($(TargetFrameworkIdentifier) == 'MonoTouch') Or ($(TargetFrameworkIdentifier) == 'Xamarin.iOS')">
<ItemGroup>
<Reference Include="FSharp.Core">
<HintPath>..\..\packages\FSharp.Core\lib\net40\FSharp.Core.dll</HintPath>
<HintPath>..\..\packages\FSharp.Core\lib\portable-net45+monoandroid10+monotouch10+xamarinios10\FSharp.Core.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
Expand Down
194 changes: 194 additions & 0 deletions src/CommandLine/StringToCommandLine/CSharpStyleCommandLineParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace CommandLine.StringToCommandLine
{
/// <summary>
/// Parse commandlines like C# would parse a string, splitting at each unquoted space:
/// * "" ->
/// * "abc" -> abc
/// * abc abc -> abc, abc
/// * "\"" -> "
/// * asd"asd -> error
/// * "asd -> error unterminated string
/// * \ -> error unterminated escape
/// * \['"\0abfnrtUuvx] -> https://msdn.microsoft.com/en-us/library/ms228362.aspx?f=255&MSPPError=-2147217396
/// * \other -> error
/// </summary>
public class CSharpStyleCommandLineParser : StringToCommandLineParserBase
{
public override IEnumerable<string> Parse(string commandLine)
{
if (string.IsNullOrWhiteSpace(commandLine))
yield break;
var currentArg = new StringBuilder();
var quoting = false;

var pos = 0;
while (pos < commandLine.Length)
{
var c = commandLine[pos];
if (c == '\\')
{
// --- Handle escape sequences
pos++;
if (pos >= commandLine.Length) throw new UnterminatedEscapeException();
switch (commandLine[pos])
{
case '\'':
c = '\'';
break;
case '\"':
c = '\"';
break;
case '\\':
c = '\\';
break;
case '0':
c = '\0';
break;
case 'a':
c = '\a';
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = ' ';
break;
case 'r':
c = ' ';
break;
case 't':
c = '\t';
break;
case 'v':
c = '\v';
break;
case 'x':
// --- Hexa escape (1-4 digits)
var hexa = new StringBuilder(10);
pos++;
if (pos >= commandLine.Length)
throw new UnterminatedEscapeException();
c = commandLine[pos];
if (char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
{
hexa.Append(c);
pos++;
if (pos < commandLine.Length)
{
c = commandLine[pos];
if (char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
{
hexa.Append(c);
pos++;
if (pos < commandLine.Length)
{
c = commandLine[pos];
if (char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
{
hexa.Append(c);
pos++;
if (pos < commandLine.Length)
{
c = commandLine[pos];
if (char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
{
hexa.Append(c);
pos++;
}
}
}
}
}
}
}
c = (char) int.Parse(hexa.ToString(), NumberStyles.HexNumber);
pos--;
break;
case 'u':
// Unicode hexa escape (exactly 4 digits)
pos++;
if (pos + 3 >= commandLine.Length)
throw new UnterminatedEscapeException();
try
{
var charValue = uint.Parse(commandLine.Substring(pos, 4), NumberStyles.HexNumber);
c = (char) charValue;
pos += 3;
}
catch (SystemException)
{
throw new UnrecognizedEscapeSequenceException();
}
break;
case 'U':
// Unicode hexa escape (exactly 8 digits, first four must be 0000)
pos++;
if (pos + 7 >= commandLine.Length)
throw new UnterminatedEscapeException();
try
{
var charValue = uint.Parse(commandLine.Substring(pos, 8), NumberStyles.HexNumber);
if (charValue > 0xffff)
throw new UnrecognizedEscapeSequenceException();
c = (char) charValue;
pos += 7;
}
catch (SystemException)
{
throw new UnrecognizedEscapeSequenceException();
}
break;
default:
throw new UnrecognizedEscapeSequenceException();
}
pos++;
currentArg.Append(c);
continue;
}
if (c == '"')
{
if (quoting)
{
pos++; //skip space
//check that it actually IS a space or EOF
if (pos < commandLine.Length && !char.IsWhiteSpace(commandLine[pos]))
throw new UnquotedQuoteException();
yield return currentArg.ToString();
currentArg.Clear();
quoting = false;
}
else
{
if (currentArg.Length > 0)
throw new UnquotedQuoteException();
quoting = true;
}
pos++;
continue;
}
if (char.IsWhiteSpace(c) && !quoting)
{
if (currentArg.Length > 0)
yield return currentArg.ToString();
currentArg.Clear();
pos++;
continue;
}
pos++;
currentArg.Append(c);
}
if (quoting && currentArg.Length > 0)
throw new UnterminatedStringException();
if (currentArg.Length > 0)
yield return currentArg.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.Text;

namespace CommandLine.StringToCommandLine
{
/// <summary>
/// Parse commandlines like CommandLineToArgvW:
/// * 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark.
/// * (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// * n backslashes not followed by a quotation mark simply produce n backslashes.
/// * Unterminated quoted strings at the end of the line ignores the missing quote.
/// </summary>
public class DefaultWindowsCommandLineParser : StringToCommandLineParserBase
{
public override IEnumerable<string> Parse(string commandLine)
{
if (string.IsNullOrWhiteSpace(commandLine))
yield break;
var currentArg = new StringBuilder();
var quoting = false;
var emptyIsAnArgument = false;
var lastC = '\0';
// Iterate all characters from the input string
foreach (var c in commandLine)
{
if (c == '"')
{
var nrbackslash = 0;
for (var i = currentArg.Length - 1; i >= 0; i--)
{
if (currentArg[i] != '\\') break;
nrbackslash++;
}
//* 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark.
//also cover nrbackslack == 0
if (nrbackslash%2 == 0)
{
if (nrbackslash > 0)
currentArg.Length = currentArg.Length - nrbackslash/2;
// Toggle quoted range
quoting = !quoting;
emptyIsAnArgument = true;
if (quoting && lastC == '"')
{
// Doubled quote within a quoted range is like escaping
currentArg.Append(c);
lastC = '\0'; //prevent double quoting
continue;
}
}
else
{
// * (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
currentArg.Length = currentArg.Length - nrbackslash/2 - 1;
currentArg.Append(c);
}
}
else if (!quoting && char.IsWhiteSpace(c))
{
// Accept empty arguments only if they are quoted
if (currentArg.Length > 0 || emptyIsAnArgument)
yield return currentArg.ToString();
// Reset for next argument
currentArg.Clear();
emptyIsAnArgument = false;
}
else
{
// Copy character from input, no special meaning
currentArg.Append(c);
}
lastC = c;
}
// Save last argument
if (currentArg.Length > 0 || emptyIsAnArgument)
yield return currentArg.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;

namespace CommandLine.StringToCommandLine
{
public abstract class StringToCommandLineParserBase
{
public abstract IEnumerable<string> Parse(string commandLine);

public class UnterminatedStringException : ArgumentException {}

public class UnrecognizedEscapeSequenceException : ArgumentException {}

public class UnquotedQuoteException : ArgumentException {}

public class UnterminatedEscapeException : ArgumentException {}
}
}
Loading