Skip to content

Commit 3b2808b

Browse files
authored
Remove superfluous string[] and string from System.IO.Packaging.ContentType (#66764)
1 parent 3175a8a commit 3b2808b

File tree

2 files changed

+64
-115
lines changed

2 files changed

+64
-115
lines changed

src/libraries/System.IO.Packaging/src/System.IO.Packaging.csproj

+5
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,16 @@
4848
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
4949
<Reference Include="System.Collections" />
5050
<Reference Include="System.IO.Compression" />
51+
<Reference Include="System.Memory" />
5152
<Reference Include="System.Runtime" />
5253
<Reference Include="System.Threading" />
5354
<Reference Include="System.Xml.ReaderWriter" />
5455
</ItemGroup>
5556

57+
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
58+
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
59+
</ItemGroup>
60+
5661
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
5762
<Reference Include="WindowsBase" />
5863
</ItemGroup>

src/libraries/System.IO.Packaging/src/System/IO/Packaging/ContentType.cs

+59-115
Original file line numberDiff line numberDiff line change
@@ -86,28 +86,20 @@ internal ContentType(string contentType!!)
8686
ValidateCarriageReturns(contentType);
8787

8888
//Begin Parsing
89-
int semiColonIndex = contentType.IndexOf(SemicolonSeparator);
89+
int semiColonIndex = contentType.IndexOf(';');
9090

9191
if (semiColonIndex == -1)
9292
{
9393
// Parse content type similar to - type/subtype
94-
ParseTypeAndSubType(contentType);
94+
ParseTypeAndSubType(contentType.AsSpan());
9595
}
9696
else
9797
{
9898
// Parse content type similar to - type/subtype ; param1=value1 ; param2=value2 ; param3="value3"
99-
ParseTypeAndSubType(contentType.Substring(0, semiColonIndex));
100-
ParseParameterAndValue(contentType.Substring(semiColonIndex));
99+
ParseTypeAndSubType(contentType.AsSpan(0, semiColonIndex));
100+
ParseParameterAndValue(contentType.AsSpan(semiColonIndex));
101101
}
102102
}
103-
104-
// keep this untouched for return from OriginalString property
105-
_originalString = contentType;
106-
107-
//This variable is used to print out the correct content type string representation
108-
//using the ToString method. This is mainly important while debugging and seeing the
109-
//value of the content type object in the debugger.
110-
_isInitialized = true;
111103
}
112104

113105
#endregion Internal Constructors
@@ -146,14 +138,8 @@ internal string SubTypeComponent
146138
/// type/subtype ; param1=value1 ; param2=value2 ; param3="value3"
147139
/// This will return an enumerator over a dictionary of the parameter/value pairs.
148140
/// </summary>
149-
internal Dictionary<string, string>.Enumerator ParameterValuePairs
150-
{
151-
get
152-
{
153-
EnsureParameterDictionary();
154-
return _parameterDictionary.GetEnumerator();
155-
}
156-
}
141+
internal Dictionary<string, string>.Enumerator ParameterValuePairs =>
142+
(_parameterDictionary ??= new()).GetEnumerator();
157143
#endregion Internal Properties
158144

159145
#region Internal Methods
@@ -225,13 +211,7 @@ public override string ToString()
225211
{
226212
if (_contentType == null)
227213
{
228-
//This is needed so that while debugging we get the correct
229-
//string
230-
if (!_isInitialized)
231-
return string.Empty;
232-
233-
Debug.Assert(string.CompareOrdinal(_type, string.Empty) != 0
234-
|| string.CompareOrdinal(_subType, string.Empty) != 0);
214+
Debug.Assert(!string.IsNullOrEmpty(_type) || !string.IsNullOrEmpty(_subType));
235215

236216
StringBuilder stringBuilder = new StringBuilder(_type);
237217
stringBuilder.Append(PackUriHelper.ForwardSlashChar);
@@ -242,10 +222,10 @@ public override string ToString()
242222
foreach (string parameterKey in _parameterDictionary.Keys)
243223
{
244224
stringBuilder.Append(s_linearWhiteSpaceChars[0]);
245-
stringBuilder.Append(SemicolonSeparator);
225+
stringBuilder.Append(';');
246226
stringBuilder.Append(s_linearWhiteSpaceChars[0]);
247227
stringBuilder.Append(parameterKey);
248-
stringBuilder.Append(EqualSeparator);
228+
stringBuilder.Append('=');
249229
stringBuilder.Append(_parameterDictionary[parameterKey]);
250230
}
251231
}
@@ -284,7 +264,9 @@ private static void ValidateCarriageReturns(string contentType)
284264
index = contentType.IndexOf(s_linearWhiteSpaceChars[2], ++index);
285265
}
286266
else
267+
{
287268
throw new ArgumentException(SR.InvalidLinearWhiteSpaceCharacter);
269+
}
288270
}
289271
}
290272

@@ -294,18 +276,20 @@ private static void ValidateCarriageReturns(string contentType)
294276
/// </summary>
295277
/// <param name="typeAndSubType">substring that has the type and subType of the content type</param>
296278
/// <exception cref="ArgumentException">If the typeAndSubType parameter does not have the "/" character</exception>
297-
private void ParseTypeAndSubType(string typeAndSubType)
279+
private void ParseTypeAndSubType(ReadOnlySpan<char> typeAndSubType)
298280
{
299281
//okay to trim at this point the end of the string as Linear White Spaces(LWS) chars are allowed here.
300282
typeAndSubType = typeAndSubType.TrimEnd(s_linearWhiteSpaceChars);
301283

302-
string[] splitBasedOnForwardSlash = typeAndSubType.Split(PackUriHelper.s_forwardSlashCharArray);
303-
304-
if (splitBasedOnForwardSlash.Length != 2)
284+
int forwardSlashPos = typeAndSubType.IndexOf('/');
285+
if (forwardSlashPos < 0 || // no slashes
286+
typeAndSubType.Slice(forwardSlashPos + 1).IndexOf('/') >= 0) // more than one slash
287+
{
305288
throw new ArgumentException(SR.InvalidTypeSubType);
289+
}
306290

307-
_type = ValidateToken(splitBasedOnForwardSlash[0]);
308-
_subType = ValidateToken(splitBasedOnForwardSlash[1]);
291+
_type = ValidateToken(typeAndSubType.Slice(0, forwardSlashPos).ToString());
292+
_subType = ValidateToken(typeAndSubType.Slice(forwardSlashPos + 1).ToString());
309293
}
310294

311295
/// <summary>
@@ -314,13 +298,13 @@ private void ParseTypeAndSubType(string typeAndSubType)
314298
/// <param name="parameterAndValue">This string has the parameter and value pair of the form
315299
/// parameter=value</param>
316300
/// <exception cref="ArgumentException">If the string does not have the required "="</exception>
317-
private void ParseParameterAndValue(string parameterAndValue)
301+
private void ParseParameterAndValue(ReadOnlySpan<char> parameterAndValue)
318302
{
319-
while (parameterAndValue != string.Empty)
303+
while (!parameterAndValue.IsEmpty)
320304
{
321305
//At this point the first character MUST be a semi-colon
322306
//First time through this test is serving more as an assert.
323-
if (parameterAndValue[0] != SemicolonSeparator)
307+
if (parameterAndValue[0] != ';')
324308
throw new ArgumentException(SR.ExpectingSemicolon);
325309

326310
//At this point if we have just one semicolon, then its an error.
@@ -330,13 +314,13 @@ private void ParseParameterAndValue(string parameterAndValue)
330314
throw new ArgumentException(SR.ExpectingParameterValuePairs);
331315

332316
//Removing the leading ; from the string
333-
parameterAndValue = parameterAndValue.Substring(1);
317+
parameterAndValue = parameterAndValue.Slice(1);
334318

335319
//okay to trim start as there can be spaces before the beginning
336320
//of the parameter name.
337321
parameterAndValue = parameterAndValue.TrimStart(s_linearWhiteSpaceChars);
338322

339-
int equalSignIndex = parameterAndValue.IndexOf(EqualSeparator);
323+
int equalSignIndex = parameterAndValue.IndexOf('=');
340324

341325
if (equalSignIndex <= 0 || equalSignIndex == (parameterAndValue.Length - 1))
342326
throw new ArgumentException(SR.InvalidParameterValuePair);
@@ -346,13 +330,11 @@ private void ParseParameterAndValue(string parameterAndValue)
346330
//Get length of the parameter value
347331
int parameterValueLength = GetLengthOfParameterValue(parameterAndValue, parameterStartIndex);
348332

349-
EnsureParameterDictionary();
333+
(_parameterDictionary ??= new()).Add(
334+
ValidateToken(parameterAndValue.Slice(0, equalSignIndex).ToString()),
335+
ValidateQuotedStringOrToken(parameterAndValue.Slice(parameterStartIndex, parameterValueLength).ToString()));
350336

351-
_parameterDictionary.Add(
352-
ValidateToken(parameterAndValue.Substring(0, equalSignIndex)),
353-
ValidateQuotedStringOrToken(parameterAndValue.Substring(parameterStartIndex, parameterValueLength)));
354-
355-
parameterAndValue = parameterAndValue.Substring(parameterStartIndex + parameterValueLength).TrimStart(s_linearWhiteSpaceChars);
337+
parameterAndValue = parameterAndValue.Slice(parameterStartIndex + parameterValueLength).TrimStart(s_linearWhiteSpaceChars);
356338
}
357339
}
358340

@@ -362,7 +344,7 @@ private void ParseParameterAndValue(string parameterAndValue)
362344
/// <param name="s"></param>
363345
/// <param name="startIndex">Starting index for parsing</param>
364346
/// <returns></returns>
365-
private static int GetLengthOfParameterValue(string s, int startIndex)
347+
private static int GetLengthOfParameterValue(ReadOnlySpan<char> s, int startIndex)
366348
{
367349
Debug.Assert(s != null);
368350

@@ -373,23 +355,20 @@ private static int GetLengthOfParameterValue(string s, int startIndex)
373355
//a ';' as the terminator for the token value.
374356
if (s[startIndex] != '"')
375357
{
376-
int semicolonIndex = s.IndexOf(SemicolonSeparator, startIndex);
358+
int semicolonIndex = s.Slice(startIndex).IndexOf(';');
377359

378360
if (semicolonIndex != -1)
379361
{
380-
int lwsIndex = s.IndexOfAny(s_linearWhiteSpaceChars, startIndex);
381-
if (lwsIndex != -1 && lwsIndex < semicolonIndex)
382-
length = lwsIndex;
383-
else
384-
length = semicolonIndex;
362+
int lwsIndex = s.Slice(startIndex).IndexOfAny(s_linearWhiteSpaceChars);
363+
length = lwsIndex != -1 && lwsIndex < semicolonIndex ? lwsIndex : semicolonIndex;
364+
length += startIndex; // the indexes from IndexOf{Any} are based on slicing from startIndex
385365
}
386366
else
387-
length = semicolonIndex;
388-
389-
//If there is no linear whitespace found we treat the entire remaining string as
390-
//parameter value.
391-
if (length == -1)
367+
{
368+
//If there is no linear white space found we treat the entire remaining string as
369+
//parameter value.
392370
length = s.Length;
371+
}
393372
}
394373
else
395374
{
@@ -400,10 +379,14 @@ private static int GetLengthOfParameterValue(string s, int startIndex)
400379

401380
while (!found)
402381
{
403-
length = s.IndexOf('"', ++length);
382+
int startingLength = ++length;
383+
length = s.Slice(startingLength).IndexOf('"');
404384

405385
if (length == -1)
386+
{
406387
throw new ArgumentException(SR.InvalidParameterValue);
388+
}
389+
length += startingLength; // IndexOf result is based on slicing from startingLength
407390

408391
if (s[length - 1] != '\\')
409392
{
@@ -453,11 +436,15 @@ private static string ValidateQuotedStringOrToken(string parameterValue)
453436
throw new ArgumentException(SR.InvalidParameterValue);
454437

455438
if (parameterValue.Length >= 2 &&
456-
parameterValue.StartsWith(Quote, StringComparison.Ordinal) &&
457-
parameterValue.EndsWith(Quote, StringComparison.Ordinal))
458-
ValidateQuotedText(parameterValue.Substring(1, parameterValue.Length - 2));
439+
parameterValue[0] == '"' &&
440+
parameterValue[parameterValue.Length - 1] == '"')
441+
{
442+
ValidateQuotedText(parameterValue.AsSpan(1, parameterValue.Length - 2));
443+
}
459444
else
445+
{
460446
ValidateToken(parameterValue);
447+
}
461448

462449
return parameterValue;
463450
}
@@ -466,7 +453,7 @@ private static string ValidateQuotedStringOrToken(string parameterValue)
466453
/// This method validates if the text in the quoted string
467454
/// </summary>
468455
/// <param name="quotedText"></param>
469-
private static void ValidateQuotedText(string quotedText)
456+
private static void ValidateQuotedText(ReadOnlySpan<char> quotedText)
470457
{
471458
//empty is okay
472459

@@ -477,9 +464,8 @@ private static void ValidateQuotedText(string quotedText)
477464

478465
if (quotedText[i] <= ' ' || quotedText[i] >= 0xFF)
479466
throw new ArgumentException(SR.InvalidParameterValue);
480-
else
481-
if (quotedText[i] == '"' &&
482-
(i == 0 || quotedText[i - 1] != '\\'))
467+
468+
if (quotedText[i] == '"' && (i == 0 || quotedText[i - 1] != '\\'))
483469
throw new ArgumentException(SR.InvalidParameterValue);
484470
}
485471
}
@@ -490,34 +476,18 @@ private static void ValidateQuotedText(string quotedText)
490476
/// </summary>
491477
/// <param name="character">input character</param>
492478
/// <returns></returns>
493-
private static bool IsAllowedCharacter(char character)
494-
{
495-
return Array.IndexOf(s_allowedCharacters, character) >= 0;
496-
}
479+
private static bool IsAllowedCharacter(char character) =>
480+
Array.IndexOf(s_allowedCharacters, character) >= 0;
497481

498482
/// <summary>
499483
/// Returns true if the input character is an ASCII digit or letter
500484
/// Returns false if the input character is not an ASCII digit or letter
501485
/// </summary>
502486
/// <param name="character">input character</param>
503487
/// <returns></returns>
504-
private static bool IsAsciiLetterOrDigit(char character)
505-
{
506-
return (IsAsciiLetter(character) || (character >= '0' && character <= '9'));
507-
}
508-
509-
/// <summary>
510-
/// Returns true if the input character is an ASCII letter
511-
/// Returns false if the input character is not an ASCII letter
512-
/// </summary>
513-
/// <param name="character">input character</param>
514-
/// <returns></returns>
515-
private static bool IsAsciiLetter(char character)
516-
{
517-
return
518-
(character >= 'a' && character <= 'z') ||
519-
(character >= 'A' && character <= 'Z');
520-
}
488+
private static bool IsAsciiLetterOrDigit(char character) =>
489+
((((uint)character - 'A') & ~0x20) < 26) ||
490+
(((uint)character - '0') < 10);
521491

522492
/// <summary>
523493
/// Returns true if the input character is one of the Linear White Space characters -
@@ -526,28 +496,8 @@ private static bool IsAsciiLetter(char character)
526496
/// </summary>
527497
/// <param name="ch">input character</param>
528498
/// <returns></returns>
529-
private static bool IsLinearWhiteSpaceChar(char ch)
530-
{
531-
if (ch > ' ')
532-
{
533-
return false;
534-
}
535-
536-
int whiteSpaceIndex = Array.IndexOf(s_linearWhiteSpaceChars, ch);
537-
return whiteSpaceIndex != -1;
538-
}
539-
540-
/// <summary>
541-
/// Lazy initialization for the ParameterDictionary
542-
/// </summary>
543-
[MemberNotNull(nameof(_parameterDictionary))]
544-
private void EnsureParameterDictionary()
545-
{
546-
if (_parameterDictionary == null)
547-
{
548-
_parameterDictionary = new Dictionary<string, string>(); //initial size 0
549-
}
550-
}
499+
private static bool IsLinearWhiteSpaceChar(char ch) =>
500+
ch <= ' ' && Array.IndexOf(s_linearWhiteSpaceChars, ch) != -1;
551501

552502
#endregion Private Methods
553503

@@ -556,13 +506,7 @@ private void EnsureParameterDictionary()
556506
private string? _contentType;
557507
private string _type = string.Empty;
558508
private string _subType = string.Empty;
559-
private readonly string _originalString;
560509
private Dictionary<string, string>? _parameterDictionary;
561-
private readonly bool _isInitialized;
562-
563-
private const string Quote = "\"";
564-
private const char SemicolonSeparator = ';';
565-
private const char EqualSeparator = '=';
566510

567511
//This array is sorted by the ascii value of these characters.
568512
private static readonly char[] s_allowedCharacters =

0 commit comments

Comments
 (0)