|
| 1 | +using System.Buffers; |
| 2 | + |
1 | 3 | namespace Common.Utilities; |
2 | 4 |
|
3 | 5 | #pragma warning disable S1144 |
4 | 6 | public static class Extensions |
5 | 7 | { |
6 | | - private static readonly char[] CharsThatRequireQuoting = [' ', '"']; |
7 | | - private static readonly char[] CharsThatRequireEscaping = ['\\', '"']; |
| 8 | + private static readonly SearchValues<char> CharsRequiringQuoting = SearchValues.Create(' ', '"'); |
| 9 | + private static readonly SearchValues<char> CharsRequiringEscaping = SearchValues.Create('\\', '"'); |
8 | 10 |
|
9 | 11 | extension(Assembly assembly) |
10 | 12 | { |
@@ -63,57 +65,68 @@ public static DirectoryPath GetRootDirectory() |
63 | 65 | public string ToSuffix() => arch.ToString().ToLower(); |
64 | 66 | } |
65 | 67 |
|
66 | | - extension(string literalValue) |
| 68 | + extension(string value) |
| 69 | + { |
| 70 | + public bool IsNullOrWhiteSpace() => |
| 71 | + string.IsNullOrWhiteSpace(value); |
| 72 | + |
| 73 | + public bool IsEqualInvariant(string other) => |
| 74 | + string.Equals(value, other, StringComparison.InvariantCulture); |
| 75 | + } |
| 76 | + |
| 77 | + /// <summary> |
| 78 | + /// Escapes arbitrary values so that the process receives the exact string you intend and injection is impossible. |
| 79 | + /// Spec: https://msdn.microsoft.com/en-us/library/bb776391.aspx |
| 80 | + /// </summary> |
| 81 | + public static string EscapeProcessArgument(this string literalValue, bool alwaysQuote = false) |
67 | 82 | { |
68 | | - /// <summary> |
69 | | - /// Escapes arbitrary values so that the process receives the exact string you intend and injection is impossible. |
70 | | - /// Spec: https://msdn.microsoft.com/en-us/library/bb776391.aspx |
71 | | - /// </summary> |
72 | | - public string EscapeProcessArgument(bool alwaysQuote = false) |
| 83 | + if (string.IsNullOrEmpty(literalValue)) return "\"\""; |
| 84 | + |
| 85 | + if (literalValue.AsSpan().IndexOfAny(CharsRequiringQuoting) == -1) // Happy path |
73 | 86 | { |
74 | | - if (string.IsNullOrEmpty(literalValue)) return "\"\""; |
| 87 | + if (!alwaysQuote) return literalValue; |
| 88 | + if (literalValue[^1] != '\\') return $"\"{literalValue}\""; |
| 89 | + } |
75 | 90 |
|
76 | | - if (literalValue.IndexOfAny(CharsThatRequireQuoting) == -1) // Happy path |
77 | | - { |
78 | | - if (!alwaysQuote) return literalValue; |
79 | | - if (literalValue[^1] != '\\') return "\"" + literalValue + "\""; |
80 | | - } |
| 91 | + return BuildEscapedArgument(literalValue); |
| 92 | + } |
| 93 | + |
| 94 | + private static string BuildEscapedArgument(string s) |
| 95 | + { |
| 96 | + var sb = new StringBuilder(s.Length + 8).Append('"'); |
| 97 | + var nextPosition = 0; |
81 | 98 |
|
82 | | - var sb = new StringBuilder(literalValue.Length + 8).Append('"'); |
| 99 | + while (true) |
| 100 | + { |
| 101 | + var relativeIndex = s.AsSpan(nextPosition).IndexOfAny(CharsRequiringEscaping); |
| 102 | + if (relativeIndex == -1) break; |
83 | 103 |
|
84 | | - var nextPosition = 0; |
85 | | - while (true) |
86 | | - { |
87 | | - var nextEscapeChar = literalValue.IndexOfAny(CharsThatRequireEscaping, nextPosition); |
88 | | - if (nextEscapeChar == -1) break; |
89 | | - |
90 | | - sb.Append(literalValue, nextPosition, nextEscapeChar - nextPosition); |
91 | | - nextPosition = nextEscapeChar + 1; |
92 | | - |
93 | | - switch (literalValue[nextEscapeChar]) |
94 | | - { |
95 | | - case '"': |
96 | | - sb.Append("\\\""); |
97 | | - break; |
98 | | - case '\\': |
99 | | - var numBackslashes = 1; |
100 | | - while (nextPosition < literalValue.Length && literalValue[nextPosition] == '\\') |
101 | | - { |
102 | | - numBackslashes++; |
103 | | - nextPosition++; |
104 | | - } |
105 | | - if (nextPosition == literalValue.Length || literalValue[nextPosition] == '"') |
106 | | - numBackslashes <<= 1; |
107 | | - |
108 | | - for (; numBackslashes != 0; numBackslashes--) |
109 | | - sb.Append('\\'); |
110 | | - break; |
111 | | - } |
112 | | - } |
| 104 | + var nextEscapeChar = nextPosition + relativeIndex; |
| 105 | + sb.Append(s, nextPosition, relativeIndex); |
| 106 | + nextPosition = nextEscapeChar + 1; |
113 | 107 |
|
114 | | - sb.Append(literalValue, nextPosition, literalValue.Length - nextPosition).Append('"'); |
115 | | - return sb.ToString(); |
| 108 | + if (s[nextEscapeChar] == '"') |
| 109 | + sb.Append("\\\""); |
| 110 | + else |
| 111 | + nextPosition = AppendEscapedBackslashes(sb, s, nextPosition); |
116 | 112 | } |
| 113 | + |
| 114 | + return sb.Append(s, nextPosition, s.Length - nextPosition).Append('"').ToString(); |
| 115 | + } |
| 116 | + |
| 117 | + private static int AppendEscapedBackslashes(StringBuilder sb, string s, int nextPosition) |
| 118 | + { |
| 119 | + var numBackslashes = 1; |
| 120 | + while (nextPosition < s.Length && s[nextPosition] == '\\') |
| 121 | + { |
| 122 | + numBackslashes++; |
| 123 | + nextPosition++; |
| 124 | + } |
| 125 | + if (nextPosition == s.Length || s[nextPosition] == '"') |
| 126 | + numBackslashes <<= 1; |
| 127 | + |
| 128 | + sb.Append('\\', numBackslashes); |
| 129 | + return nextPosition; |
117 | 130 | } |
118 | 131 | } |
119 | 132 | #pragma warning restore S1144 |
0 commit comments