1// Copyright (c) Microsoft Corporation. All rights reserved. 2// Licensed under the MIT License. 3 4// Copied from https://github.com/aspnet/AspNetCore/tree/master/src/Http/Headers/src 5 6using System; 7using System.Collections.Generic; 8using System.Diagnostics.Contracts; 9using System.Globalization; 10using System.Linq; 11using System.Text; 12 13#pragma warning disable IDE0008 // Use explicit type 14#pragma warning disable IDE0017 // initialization can be simplified 15#pragma warning disable IDE0019 // Use pattern matching 16#pragma warning disable IDE0032 // Use auto property 17#pragma warning disable IDE0034 // Default expression can be simplified 18#pragma warning disable IDE0054 // Use compound assignment 19#pragma warning disable IDE0059 // Unnecessary assignment 20#pragma warning disable IDE1006 // Missing s_ prefix 21 22namespace Azure.Core.Http.Multipart 23{ 24    /// <summary> 25    /// Representation of the media type header. See <see href="https://tools.ietf.org/html/rfc6838">Media Type Specific 26    /// </summary> 27    internal class MediaTypeHeaderValue 28    { 29        private const string BoundaryString = "boundary"; 30        private const string CharsetString = "charset"; 31        private const string MatchesAllString = "*/*"; 32        private const string QualityString = "q"; 33        private const string WildcardString = "*"; 34 35        private const char ForwardSlashCharacter = '/'; 36        private const char PeriodCharacter = '.'; 37        private const char PlusCharacter = '+'; 38 039        private static readonly char[] PeriodCharacterArray = new char[] { PeriodCharacter }; 40 041        private static readonly HttpHeaderParser<MediaTypeHeaderValue> SingleValueParser 042            = new GenericHeaderParser<MediaTypeHeaderValue>(false, GetMediaTypeLength); 043        private static readonly HttpHeaderParser<MediaTypeHeaderValue> MultipleValueParser 044            = new GenericHeaderParser<MediaTypeHeaderValue>(true, GetMediaTypeLength); 45 46        // Use a collection instead of a dictionary since we may have multiple parameters with the same name. 47        private ObjectCollection<NameValueHeaderValue> _parameters; 48        private StringSegment _mediaType; 49        private bool _isReadOnly; 50 051        private MediaTypeHeaderValue() 52        { 53            // Used by the parser to create a new instance of this type. 054        } 55 56        /// <summary> 57        /// Initializes a <see cref="MediaTypeHeaderValue"/> instance. 58        /// </summary> 59        /// <param name="mediaType">A <see cref="StringSegment"/> representation of a media type. 60        /// The text provided must be a single media type without parameters. </param> 061        public MediaTypeHeaderValue(StringSegment mediaType) 62        { 063            CheckMediaTypeFormat(mediaType, nameof(mediaType)); 064            _mediaType = mediaType; 065        } 66 67        /// <summary> 68        /// Initializes a <see cref="MediaTypeHeaderValue"/> instance. 69        /// </summary> 70        /// <param name="mediaType">A <see cref="StringSegment"/> representation of a media type. 71        /// The text provided must be a single media type without parameters. </param> 72        /// <param name="quality">The <see cref="double"/> with the quality of the media type.</param> 73        public MediaTypeHeaderValue(StringSegment mediaType, double quality) 074            : this(mediaType) 75        { 076            Quality = quality; 077        } 78 79        /// <summary> 80        /// Gets or sets the value of the charset parameter. Returns <see cref="StringSegment.Empty"/> 81        /// if there is no charset. 82        /// </summary> 83        public StringSegment Charset 84        { 85            get 86            { 087                return NameValueHeaderValue.Find(_parameters, CharsetString)?.Value.Value; 88            } 89            set 90            { 091                HeaderUtilities.ThrowIfReadOnly(IsReadOnly); 92                // We don't prevent a user from setting whitespace-only charsets. Like we can't prevent a user from 93                // setting a non-existing charset. 094                var charsetParameter = NameValueHeaderValue.Find(_parameters, CharsetString); 095                if (StringSegment.IsNullOrEmpty(value)) 96                { 97                    // Remove charset parameter 098                    if (charsetParameter != null) 99                    { 0100                        Parameters.Remove(charsetParameter); 101                    } 102                } 103                else 104                { 0105                    if (charsetParameter != null) 106                    { 0107                        charsetParameter.Value = value; 108                    } 109                    else 110                    { 0111                        Parameters.Add(new NameValueHeaderValue(CharsetString, value)); 112                    } 113                } 0114            } 115        } 116 117        /// <summary> 118        /// Gets or sets the value of the Encoding parameter. Setting the Encoding will set 119        /// the <see cref="Charset"/> to <see cref="Encoding.WebName"/>. 120        /// </summary> 121        public Encoding Encoding 122        { 123            get 124            { 0125                var charset = Charset; 0126                if (!StringSegment.IsNullOrEmpty(charset)) 127                { 128                    try 129                    { 0130                        return Encoding.GetEncoding(charset.Value); 131                    } 0132                    catch (ArgumentException) 133                    { 134                        // Invalid or not supported 0135                    } 136                } 0137                return null; 0138            } 139            set 140            { 0141                HeaderUtilities.ThrowIfReadOnly(IsReadOnly); 0142                if (value == null) 143                { 0144                    Charset = null; 145                } 146                else 147                { 0148                    Charset = value.WebName; 149                } 0150            } 151        } 152 153        /// <summary> 154        /// Gets or sets the value of the boundary parameter. Returns <see cref="StringSegment.Empty"/> 155        /// if there is no boundary. 156        /// </summary> 157        public StringSegment Boundary 158        { 159            get 160            { 0161                return NameValueHeaderValue.Find(_parameters, BoundaryString)?.Value ?? default(StringSegment); 162            } 163            set 164            { 0165                HeaderUtilities.ThrowIfReadOnly(IsReadOnly); 0166                var boundaryParameter = NameValueHeaderValue.Find(_parameters, BoundaryString); 0167                if (StringSegment.IsNullOrEmpty(value)) 168                { 169                    // Remove charset parameter 0170                    if (boundaryParameter != null) 171                    { 0172                        Parameters.Remove(boundaryParameter); 173                    } 174                } 175                else 176                { 0177                    if (boundaryParameter != null) 178                    { 0179                        boundaryParameter.Value = value; 180                    } 181                    else 182                    { 0183                        Parameters.Add(new NameValueHeaderValue(BoundaryString, value)); 184                    } 185                } 0186            } 187        } 188 189        /// <summary> 190        /// Gets or sets the media type's parameters. Returns an empty <see cref="IList{T}"/> 191        /// if there are no parameters. 192        /// </summary> 193        public IList<NameValueHeaderValue> Parameters 194        { 195            get 196            { 0197                if (_parameters == null) 198                { 0199                    if (IsReadOnly) 200                    { 0201                        _parameters = ObjectCollection<NameValueHeaderValue>.EmptyReadOnlyCollection; 202                    } 203                    else 204                    { 0205                        _parameters = new ObjectCollection<NameValueHeaderValue>(); 206                    } 207                } 0208                return _parameters; 209            } 210        } 211 212        /// <summary> 213        /// Gets or sets the value of the quality parameter. Returns null 214        /// if there is no quality. 215        /// </summary> 216        public double? Quality 217        { 0218            get { return HeaderUtilities.GetQuality(_parameters); } 219            set 220            { 0221                HeaderUtilities.ThrowIfReadOnly(IsReadOnly); 0222                HeaderUtilities.SetQuality(Parameters, value); 0223            } 224        } 225 226        /// <summary> 227        /// Gets or sets the value of the media type. Returns <see cref="StringSegment.Empty"/> 228        /// if there is no media type. 229        /// </summary> 230        /// <example> 231        /// For the media type <c>"application/json"</c>, the property gives the value 232        /// <c>"application/json"</c>. 233        /// </example> 234        public StringSegment MediaType 235        { 0236            get { return _mediaType; } 237            set 238            { 0239                HeaderUtilities.ThrowIfReadOnly(IsReadOnly); 0240                CheckMediaTypeFormat(value, nameof(value)); 0241                _mediaType = value; 0242            } 243        } 244 245        /// <summary> 246        /// Gets the type of the <see cref="MediaTypeHeaderValue"/>. 247        /// </summary> 248        /// <example> 249        /// For the media type <c>"application/json"</c>, the property gives the value <c>"application"</c>. 250        /// </example> 251        /// <remarks>See <see href="https://tools.ietf.org/html/rfc6838#section-4.2">Naming Requirements</see> for more  252        public StringSegment Type 253        { 254            get 255            { 0256                return _mediaType.Subsegment(0, _mediaType.IndexOf(ForwardSlashCharacter)); 257            } 258        } 259 260        /// <summary> 261        /// Gets the subtype of the <see cref="MediaTypeHeaderValue"/>. 262        /// </summary> 263        /// <example> 264        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value 265        /// <c>"vnd.example+json"</c>. 266        /// </example> 267        /// <remarks>See <see href="https://tools.ietf.org/html/rfc6838#section-4.2">Naming Requirements</see> for more  268        public StringSegment SubType 269        { 270            get 271            { 0272                return _mediaType.Subsegment(_mediaType.IndexOf(ForwardSlashCharacter) + 1); 273            } 274        } 275 276        /// <summary> 277        /// Gets subtype of the <see cref="MediaTypeHeaderValue"/>, excluding any structured syntax suffix. Returns <see 278        /// if there is no subtype without suffix. 279        /// </summary> 280        /// <example> 281        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value 282        /// <c>"vnd.example"</c>. 283        /// </example> 284        public StringSegment SubTypeWithoutSuffix 285        { 286            get 287            { 0288                var subType = SubType; 0289                var startOfSuffix = subType.LastIndexOf(PlusCharacter); 0290                if (startOfSuffix == -1) 291                { 0292                    return subType; 293                } 294                else 295                { 0296                    return subType.Subsegment(0, startOfSuffix); 297                } 298            } 299        } 300 301        /// <summary> 302        /// Gets the structured syntax suffix of the <see cref="MediaTypeHeaderValue"/> if it has one. 303        /// See <see href="https://tools.ietf.org/html/rfc6838#section-4.8">The RFC documentation on structured syntaxes 304        /// </summary> 305        /// <example> 306        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value 307        /// <c>"json"</c>. 308        /// </example> 309        public StringSegment Suffix 310        { 311            get 312            { 0313                var subType = SubType; 0314                var startOfSuffix = subType.LastIndexOf(PlusCharacter); 0315                if (startOfSuffix == -1) 316                { 0317                    return default(StringSegment); 318                } 319                else 320                { 0321                    return subType.Subsegment(startOfSuffix + 1); 322                } 323            } 324        } 325 326        /// <summary> 327        /// Get a <see cref="IList{T}"/> of facets of the <see cref="MediaTypeHeaderValue"/>. Facets are a 328        /// period separated list of StringSegments in the <see cref="SubTypeWithoutSuffix"/>. 329        /// See <see href="https://tools.ietf.org/html/rfc6838#section-3">The RFC documentation on facets.</see> 330        /// </summary> 331        /// <example> 332        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value: 333        /// <c>{"vnd", "example"}</c> 334        /// </example> 335        public IEnumerable<StringSegment> Facets 336        { 337            get 338            { 0339                return SubTypeWithoutSuffix.Split(PeriodCharacterArray); 340            } 341        } 342 343        /// <summary> 344        /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all types. 345        /// </summary> 0346        public bool MatchesAllTypes => MediaType.Equals(MatchesAllString, StringComparison.Ordinal); 347 348        /// <summary> 349        /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all subtypes. 350        /// </summary> 351        /// <example> 352        /// For the media type <c>"application/*"</c>, this property is <c>true</c>. 353        /// </example> 354        /// <example> 355        /// For the media type <c>"application/json"</c>, this property is <c>false</c>. 356        /// </example> 0357        public bool MatchesAllSubTypes => SubType.Equals(WildcardString, StringComparison.Ordinal); 358 359        /// <summary> 360        /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all subtypes, ignoring any structured syntax su 361        /// </summary> 362        /// <example> 363        /// For the media type <c>"application/*+json"</c>, this property is <c>true</c>. 364        /// </example> 365        /// <example> 366        /// For the media type <c>"application/vnd.example+json"</c>, this property is <c>false</c>. 367        /// </example> 368        public bool MatchesAllSubTypesWithoutSuffix => 0369            SubTypeWithoutSuffix.Equals(WildcardString, StringComparison.OrdinalIgnoreCase); 370 371        /// <summary> 372        /// Gets whether the <see cref="MediaTypeHeaderValue"/> is readonly. 373        /// </summary> 374        public bool IsReadOnly 375        { 0376            get { return _isReadOnly; } 377        } 378 379        /// <summary> 380        /// Gets a value indicating whether this <see cref="MediaTypeHeaderValue"/> is a subset of 381        /// <paramref name="otherMediaType"/>. A "subset" is defined as the same or a more specific media type 382        /// according to the precedence described in https://www.ietf.org/rfc/rfc2068.txt section 14.1, Accept. 383        /// </summary> 384        /// <param name="otherMediaType">The <see cref="MediaTypeHeaderValue"/> to compare.</param> 385        /// <returns> 386        /// A value indicating whether this <see cref="MediaTypeHeaderValue"/> is a subset of 387        /// <paramref name="otherMediaType"/>. 388        /// </returns> 389        /// <remarks> 390        /// For example "multipart/mixed; boundary=1234" is a subset of "multipart/mixed; boundary=1234", 391        /// "multipart/mixed", "multipart/*", and "*/*" but not "multipart/mixed; boundary=2345" or 392        /// "multipart/message; boundary=1234". 393        /// </remarks> 394        public bool IsSubsetOf(MediaTypeHeaderValue otherMediaType) 395        { 0396            if (otherMediaType == null) 397            { 0398                return false; 399            } 400 401            // "text/plain" is a subset of "text/plain", "text/*" and "*/*". "*/*" is a subset only of "*/*". 0402            return MatchesType(otherMediaType) && 0403                MatchesSubtype(otherMediaType) && 0404                MatchesParameters(otherMediaType); 405        } 406 407        /// <summary> 408        /// Performs a deep copy of this object and all of it's NameValueHeaderValue sub components, 409        /// while avoiding the cost of re-validating the components. 410        /// </summary> 411        /// <returns>A deep copy.</returns> 412        public MediaTypeHeaderValue Copy() 413        { 0414            var other = new MediaTypeHeaderValue(); 0415            other._mediaType = _mediaType; 416 0417            if (_parameters != null) 418            { 0419                other._parameters = new ObjectCollection<NameValueHeaderValue>( 0420                    _parameters.Select(item => item.Copy())); 421            } 0422            return other; 423        } 424 425        /// <summary> 426        /// Performs a deep copy of this object and all of it's NameValueHeaderValue sub components, 427        /// while avoiding the cost of re-validating the components. This copy is read-only. 428        /// </summary> 429        /// <returns>A deep, read-only, copy.</returns> 430        public MediaTypeHeaderValue CopyAsReadOnly() 431        { 0432            if (IsReadOnly) 433            { 0434                return this; 435            } 436 0437            var other = new MediaTypeHeaderValue(); 0438            other._mediaType = _mediaType; 0439            if (_parameters != null) 440            { 0441                other._parameters = new ObjectCollection<NameValueHeaderValue>( 0442                    _parameters.Select(item => item.CopyAsReadOnly()), isReadOnly: true); 443            } 0444            other._isReadOnly = true; 0445            return other; 446        } 447 448        public override string ToString() 449        { 0450            var builder = new StringBuilder(); 0451            builder.Append(_mediaType.AsSpan()); 0452            NameValueHeaderValue.ToString(_parameters, separator: ';', leadingSeparator: true, destination: builder); 0453            return builder.ToString(); 454        } 455 456        public override bool Equals(object obj) 457        { 0458            var other = obj as MediaTypeHeaderValue; 459 0460            if (other == null) 461            { 0462                return false; 463            } 464 0465            return _mediaType.Equals(other._mediaType, StringComparison.OrdinalIgnoreCase) && 0466                HeaderUtilities.AreEqualCollections(_parameters, other._parameters); 467        } 468 469        public override int GetHashCode() 470        { 471            // The media-type string is case-insensitive. 0472            return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_p 473        } 474 475        /// <summary> 476        /// Takes a media type and parses it into the <see cref="MediaTypeHeaderValue" /> and its associated parameters. 477        /// </summary> 478        /// <param name="input">The <see cref="StringSegment"/> with the media type.</param> 479        /// <returns>The parsed <see cref="MediaTypeHeaderValue"/>.</returns> 480        public static MediaTypeHeaderValue Parse(StringSegment input) 481        { 0482            var index = 0; 0483            return SingleValueParser.ParseValue(input, ref index); 484        } 485 486        /// <summary> 487        /// Takes a media type, which can include parameters, and parses it into the <see cref="MediaTypeHeaderValue" /> 488        /// </summary> 489        /// <param name="input">The <see cref="StringSegment"/> with the media type. The media type constructed here mus 490        /// <param name="parsedValue">The parsed <see cref="MediaTypeHeaderValue"/></param> 491        /// <returns>True if the value was successfully parsed.</returns> 492        public static bool TryParse(StringSegment input, out MediaTypeHeaderValue parsedValue) 493        { 0494            var index = 0; 0495            return SingleValueParser.TryParseValue(input, ref index, out parsedValue); 496        } 497 498        /// <summary> 499        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderVal 500        /// </summary> 501        /// <param name="inputs">A list of media types</param> 502        /// <returns>The parsed <see cref="MediaTypeHeaderValue"/>.</returns> 503        public static IList<MediaTypeHeaderValue> ParseList(IList<string> inputs) 504        { 0505            return MultipleValueParser.ParseValues(inputs); 506        } 507 508        /// <summary> 509        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderVal 510        /// Throws if there is invalid data in a string. 511        /// </summary> 512        /// <param name="inputs">A list of media types</param> 513        /// <returns>The parsed <see cref="MediaTypeHeaderValue"/>.</returns> 514        public static IList<MediaTypeHeaderValue> ParseStrictList(IList<string> inputs) 515        { 0516            return MultipleValueParser.ParseStrictValues(inputs); 517        } 518 519        /// <summary> 520        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderVal 521        /// </summary> 522        /// <param name="inputs">A list of media types</param> 523        /// <param name="parsedValues">The parsed <see cref="MediaTypeHeaderValue"/>.</param> 524        /// <returns>True if the value was successfully parsed.</returns> 525        public static bool TryParseList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues) 526        { 0527            return MultipleValueParser.TryParseValues(inputs, out parsedValues); 528        } 529 530        /// <summary> 531        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderVal 532        /// </summary> 533        /// <param name="inputs">A list of media types</param> 534        /// <param name="parsedValues">The parsed <see cref="MediaTypeHeaderValue"/>.</param> 535        /// <returns>True if the value was successfully parsed.</returns> 536        public static bool TryParseStrictList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues) 537        { 0538            return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues); 539        } 540 541        private static int GetMediaTypeLength(StringSegment input, int startIndex, out MediaTypeHeaderValue parsedValue) 542        { 543            Contract.Requires(startIndex >= 0); 544 0545            parsedValue = null; 546 0547            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length)) 548            { 0549                return 0; 550            } 551 552            // Caller must remove leading whitespace. If not, we'll return 0. 0553            var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out var mediaType 554 0555            if (mediaTypeLength == 0) 556            { 0557                return 0; 558            } 559 0560            var current = startIndex + mediaTypeLength; 0561            current = current + HttpRuleParser.GetWhitespaceLength(input, current); 0562            MediaTypeHeaderValue mediaTypeHeader = null; 563 564            // If we're not done and we have a parameter delimiter, then we have a list of parameters. 0565            if ((current < input.Length) && (input[current] == ';')) 566            { 0567                mediaTypeHeader = new MediaTypeHeaderValue(); 0568                mediaTypeHeader._mediaType = mediaType; 569 0570                current++; // skip delimiter. 0571                var parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';', 0572                    mediaTypeHeader.Parameters); 573 0574                parsedValue = mediaTypeHeader; 0575                return current + parameterLength - startIndex; 576            } 577 578            // We have a media type without parameters. 0579            mediaTypeHeader = new MediaTypeHeaderValue(); 0580            mediaTypeHeader._mediaType = mediaType; 0581            parsedValue = mediaTypeHeader; 0582            return current - startIndex; 583        } 584 585        private static int GetMediaTypeExpressionLength(StringSegment input, int startIndex, out StringSegment mediaType 586        { 587            Contract.Requires((input != null) && (input.Length > 0) && (startIndex < input.Length)); 588 589            // This method just parses the "type/subtype" string, it does not parse parameters. 0590            mediaType = null; 591 592            // Parse the type, i.e. <type> in media type string "<type>/<subtype>; param1=value1; param2=value2" 0593            var typeLength = HttpRuleParser.GetTokenLength(input, startIndex); 594 0595            if (typeLength == 0) 596            { 0597                return 0; 598            } 599 0600            var current = startIndex + typeLength; 0601            current = current + HttpRuleParser.GetWhitespaceLength(input, current); 602 603            // Parse the separator between type and subtype 0604            if ((current >= input.Length) || (input[current] != '/')) 605            { 0606                return 0; 607            } 0608            current++; // skip delimiter. 0609            current = current + HttpRuleParser.GetWhitespaceLength(input, current); 610 611            // Parse the subtype, i.e. <subtype> in media type string "<type>/<subtype>; param1=value1; param2=value2" 0612            var subtypeLength = HttpRuleParser.GetTokenLength(input, current); 613 0614            if (subtypeLength == 0) 615            { 0616                return 0; 617            } 618 619            // If there is no whitespace between <type> and <subtype> in <type>/<subtype> get the media type using 620            // one Substring call. Otherwise get substrings for <type> and <subtype> and combine them. 0621            var mediaTypeLength = current + subtypeLength - startIndex; 0622            if (typeLength + subtypeLength + 1 == mediaTypeLength) 623            { 0624                mediaType = input.Subsegment(startIndex, mediaTypeLength); 625            } 626            else 627            { 0628                mediaType = input.Substring(startIndex, typeLength) + ForwardSlashCharacter + input.Substring(current, s 629            } 630 0631            return mediaTypeLength; 632        } 633 634        private static void CheckMediaTypeFormat(StringSegment mediaType, string parameterName) 635        { 0636            if (StringSegment.IsNullOrEmpty(mediaType)) 637            { 0638                throw new ArgumentException("An empty string is not allowed.", parameterName); 639            } 640 641            // When adding values using strongly typed objects, no leading/trailing LWS (whitespace) is allowed. 642            // Also no LWS between type and subtype is allowed. 0643            var mediaTypeLength = GetMediaTypeExpressionLength(mediaType, 0, out var tempMediaType); 0644            if ((mediaTypeLength == 0) || (tempMediaType.Length != mediaType.Length)) 645            { 0646                throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid media type '{0}'.", media 647            } 0648        } 649 650        private bool MatchesType(MediaTypeHeaderValue set) 651        { 0652            return set.MatchesAllTypes || 0653                set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase); 654        } 655 656        private bool MatchesSubtype(MediaTypeHeaderValue set) 657        { 0658            if (set.MatchesAllSubTypes) 659            { 0660                return true; 661            } 662 0663            if (set.Suffix.HasValue) 664            { 0665                if (Suffix.HasValue) 666                { 0667                    return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set); 668                } 669                else 670                { 0671                    return false; 672                } 673            } 674            else 675            { 676                // If this subtype or suffix matches the subtype of the set, 677                // it is considered a subtype. 678                // Ex: application/json > application/val+json 0679                return MatchesEitherSubtypeOrSuffix(set); 680            } 681        } 682 683        private bool MatchesSubtypeWithoutSuffix(MediaTypeHeaderValue set) 684        { 0685            return set.MatchesAllSubTypesWithoutSuffix || 0686                set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase); 687        } 688 689        private bool MatchesEitherSubtypeOrSuffix(MediaTypeHeaderValue set) 690        { 0691            return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase) || 0692                set.SubType.Equals(Suffix, StringComparison.OrdinalIgnoreCase); 693        } 694 695        private bool MatchesParameters(MediaTypeHeaderValue set) 696        { 0697            if (set._parameters != null && set._parameters.Count != 0) 698            { 699                // Make sure all parameters in the potential superset are included locally. Fine to have additional 700                // parameters locally; they make this one more specific. 0701                foreach (var parameter in set._parameters) 702                { 0703                    if (parameter.Name.Equals(WildcardString, StringComparison.OrdinalIgnoreCase)) 704                    { 705                        // A parameter named "*" has no effect on media type matching, as it is only used as an indicati 706                        // that the entire media type string should be treated as a wildcard. 707                        continue; 708                    } 709 0710                    if (parameter.Name.Equals(QualityString, StringComparison.OrdinalIgnoreCase)) 711                    { 712                        // "q" and later parameters are not involved in media type matching. Quoting the RFC: The first 713                        // "q" parameter (if any) separates the media-range parameter(s) from the accept-params. 0714                        break; 715                    } 716 0717                    var localParameter = NameValueHeaderValue.Find(_parameters, parameter.Name); 0718                    if (localParameter == null) 719                    { 720                        // Not found. 0721                        return false; 722                    } 723 0724                    if (!StringSegment.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase) 725                    { 0726                        return false; 727                    } 728                } 729            } 0730            return true; 0731        } 732 733        private bool MatchesSubtypeSuffix(MediaTypeHeaderValue set) 734        { 735            // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*") 736            // because there's no clear use case for it. 0737            return set.Suffix.Equals(Suffix, StringComparison.OrdinalIgnoreCase); 738        } 739    } 740}

ncG1vNJzZmiZqqq%2Fpr%2FDpJirrJmbrqTA0meZpaeSY7CwvsRnrqKmlKTEtHrNnqtomaqqv6Z50p2iZp6fp3qvsdNoeqiclVp%2FcY%2FOr5yrmZeafILG1KucZ4ukpL%2Bis8RneaWnkqh7g63TnJ%2BYhZWZtqKg2KmcgZ2RmbKzosClrJ5mmKm6rQ%3D%3D