< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Web.AcceptHeaderElementComparer
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Web/IncomingWebRequestContext.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 37
Coverable lines: 37
Total lines: 437
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 42
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
Compare(...)0%30300%
GetQualityFactor(...)0%880%
HasParameters(...)0%440%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Web/IncomingWebRequestContext.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Collections.ObjectModel;
 7using System.Globalization;
 8using System.Net;
 9using System.Net.Mime;
 10using CoreWCF.Channels;
 11using CoreWCF.Runtime;
 12using Microsoft.Net.Http.Headers;
 13
 14namespace CoreWCF.Web
 15{
 16    public class IncomingWebRequestContext
 17    {
 18        private static readonly string s_httpGetMethod = "GET";
 19        private static readonly string s_httpHeadMethod = "HEAD";
 20        private static readonly string s_httpPutMethod = "PUT";
 21        private static readonly string s_httpPostMethod = "POST";
 22        private static readonly string s_httpDeleteMethod = "DELETE";
 23
 24        private Collection<ContentType> _cachedAcceptHeaderElements;
 25        private string _acceptHeaderWhenHeaderElementsCached;
 26        private readonly OperationContext _operationContext;
 27
 28        internal const string UriTemplateMatchResultsPropertyName = "UriTemplateMatchResults";
 29
 30        internal IncomingWebRequestContext(OperationContext operationContext)
 31        {
 32            Fx.Assert(operationContext != null, "operationContext is null");
 33            _operationContext = operationContext;
 34        }
 35
 36        public string Accept => EnsureMessageProperty().Headers[HttpRequestHeader.Accept];
 37
 38        public long ContentLength => long.Parse(EnsureMessageProperty().Headers[HttpRequestHeader.ContentLength], Cultur
 39
 40        public string ContentType => EnsureMessageProperty().Headers[HttpRequestHeader.ContentType];
 41
 42        public IEnumerable<string> IfMatch
 43        {
 44            get
 45            {
 46                string ifMatchHeader = MessageProperty.Headers[HttpRequestHeader.IfMatch];
 47                return (string.IsNullOrEmpty(ifMatchHeader)) ? null : Utility.QuoteAwareStringSplit(ifMatchHeader);
 48            }
 49        }
 50
 51        public IEnumerable<string> IfNoneMatch
 52        {
 53            get
 54            {
 55                string ifNoneMatchHeader = MessageProperty.Headers[HttpRequestHeader.IfNoneMatch];
 56                return (string.IsNullOrEmpty(ifNoneMatchHeader)) ? null : Utility.QuoteAwareStringSplit(ifNoneMatchHeade
 57            }
 58        }
 59
 60        public DateTime? IfModifiedSince
 61        {
 62            get
 63            {
 64                string dateTime = this.MessageProperty.Headers[HttpRequestHeader.IfModifiedSince];
 65                if (!string.IsNullOrEmpty(dateTime))
 66                {
 67                    if (HeaderUtilities.TryParseDate(dateTime, out DateTimeOffset parsedDateTime))
 68                    {
 69                        return parsedDateTime.LocalDateTime;
 70                    }
 71                }
 72
 73                return null;
 74            }
 75        }
 76
 77        public DateTime? IfUnmodifiedSince
 78        {
 79            get
 80            {
 81                string dateTime = MessageProperty.Headers[HttpRequestHeader.IfUnmodifiedSince];
 82                if (!string.IsNullOrEmpty(dateTime))
 83                {
 84                    if (HeaderUtilities.TryParseDate(dateTime, out DateTimeOffset parsedDateTime))
 85                    {
 86                        return parsedDateTime.LocalDateTime;
 87                    }
 88                }
 89
 90                return null;
 91            }
 92        }
 93
 94        public WebHeaderCollection Headers => EnsureMessageProperty().Headers;
 95
 96        public string Method => EnsureMessageProperty().Method;
 97
 98        public UriTemplateMatch UriTemplateMatch
 99        {
 100            get
 101            {
 102                if (_operationContext.IncomingMessageProperties.ContainsKey(UriTemplateMatchResultsPropertyName))
 103                {
 104                    return _operationContext.IncomingMessageProperties[UriTemplateMatchResultsPropertyName] as UriTempla
 105                }
 106                else
 107                {
 108                    return null;
 109                }
 110            }
 111            set
 112            {
 113                _operationContext.IncomingMessageProperties[UriTemplateMatchResultsPropertyName] = value;
 114            }
 115        }
 116
 117        public string UserAgent => EnsureMessageProperty().Headers[HttpRequestHeader.UserAgent];
 118
 119        private HttpRequestMessageProperty MessageProperty
 120        {
 121            get
 122            {
 123                if (_operationContext.IncomingMessageProperties == null)
 124                {
 125                    return null;
 126                }
 127
 128                if (!_operationContext.IncomingMessageProperties.ContainsKey(HttpRequestMessageProperty.Name))
 129                {
 130                    return null;
 131                }
 132
 133                return _operationContext.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessag
 134            }
 135        }
 136
 137        public void CheckConditionalRetrieve(string entityTag)
 138        {
 139            string validEtag = OutgoingWebResponseContext.GenerateValidEtagFromString(entityTag);
 140            CheckConditionalRetrieveWithValidatedEtag(validEtag);
 141        }
 142
 143        public void CheckConditionalRetrieve(int entityTag)
 144        {
 145            string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag);
 146            CheckConditionalRetrieveWithValidatedEtag(validEtag);
 147        }
 148
 149        public void CheckConditionalRetrieve(long entityTag)
 150        {
 151            string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag);
 152            CheckConditionalRetrieveWithValidatedEtag(validEtag);
 153        }
 154
 155        public void CheckConditionalRetrieve(Guid entityTag)
 156        {
 157            string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag);
 158            CheckConditionalRetrieveWithValidatedEtag(validEtag);
 159        }
 160
 161        public void CheckConditionalRetrieve(DateTime lastModified)
 162        {
 163            if (!string.Equals(Method, s_httpGetMethod, StringComparison.OrdinalIgnoreCase) &&
 164                !string.Equals(Method, s_httpHeadMethod, StringComparison.OrdinalIgnoreCase))
 165            {
 166                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 167                    SR.Format(SR.ConditionalRetrieveGetAndHeadOnly, Method)));
 168            }
 169
 170            DateTime? ifModifiedSince = IfModifiedSince;
 171            if (ifModifiedSince.HasValue)
 172            {
 173                long ticksDifference = lastModified.ToUniversalTime().Ticks - ifModifiedSince.Value.ToUniversalTime().Ti
 174                if (ticksDifference < TimeSpan.TicksPerSecond)
 175                {
 176                    WebOperationContext.Current.OutgoingResponse.LastModified = lastModified;
 177                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.NotMo
 178                }
 179            }
 180        }
 181
 182        public void CheckConditionalUpdate(string entityTag)
 183        {
 184            string validEtag = OutgoingWebResponseContext.GenerateValidEtagFromString(entityTag);
 185            CheckConditionalUpdateWithValidatedEtag(validEtag);
 186        }
 187
 188        public void CheckConditionalUpdate(int entityTag)
 189        {
 190            string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag);
 191            CheckConditionalUpdateWithValidatedEtag(validEtag);
 192        }
 193
 194        public void CheckConditionalUpdate(long entityTag)
 195        {
 196            string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag);
 197            CheckConditionalUpdateWithValidatedEtag(validEtag);
 198        }
 199
 200        public void CheckConditionalUpdate(Guid entityTag)
 201        {
 202            string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag);
 203            CheckConditionalUpdateWithValidatedEtag(validEtag);
 204        }
 205
 206        public Collection<ContentType> GetAcceptHeaderElements()
 207        {
 208            string acceptHeader = Accept;
 209            if (_cachedAcceptHeaderElements == null ||
 210                (!string.Equals(_acceptHeaderWhenHeaderElementsCached, acceptHeader, StringComparison.OrdinalIgnoreCase)
 211            {
 212                if (string.IsNullOrEmpty(acceptHeader))
 213                {
 214                    _cachedAcceptHeaderElements = new Collection<ContentType>();
 215                    _acceptHeaderWhenHeaderElementsCached = acceptHeader;
 216                }
 217                else
 218                {
 219                    List<ContentType> contentTypeList = new List<ContentType>();
 220                    int offset = 0;
 221                    while (true)
 222                    {
 223                        string nextItem = Utility.QuoteAwareSubString(acceptHeader, ref offset);
 224                        if (nextItem == null)
 225                        {
 226                            break;
 227                        }
 228
 229                        ContentType contentType = Utility.GetContentTypeOrNull(nextItem);
 230                        if (contentType != null)
 231                        {
 232                            contentTypeList.Add(contentType);
 233                        }
 234                    }
 235
 236                    contentTypeList.Sort(new AcceptHeaderElementComparer());
 237                    _cachedAcceptHeaderElements = new Collection<ContentType>(contentTypeList);
 238                    _acceptHeaderWhenHeaderElementsCached = acceptHeader;
 239                }
 240            }
 241
 242            return _cachedAcceptHeaderElements;
 243        }
 244
 245        private HttpRequestMessageProperty EnsureMessageProperty()
 246        {
 247            if (MessageProperty == null)
 248            {
 249                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 250                    SR.Format(SR.HttpContextNoIncomingMessageProperty, typeof(HttpRequestMessageProperty).Name)));
 251            }
 252
 253            return MessageProperty;
 254        }
 255
 256
 257        private void CheckConditionalRetrieveWithValidatedEtag(string entityTag)
 258        {
 259            if (!string.Equals(Method, s_httpGetMethod, StringComparison.OrdinalIgnoreCase) &&
 260                !string.Equals(Method, s_httpHeadMethod, StringComparison.OrdinalIgnoreCase))
 261            {
 262                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 263                    SR.Format(SR.ConditionalRetrieveGetAndHeadOnly, Method)));
 264            }
 265
 266            if (!string.IsNullOrEmpty(entityTag))
 267            {
 268                string entityTagHeader = Headers[HttpRequestHeader.IfNoneMatch];
 269                if (!string.IsNullOrEmpty(entityTagHeader))
 270                {
 271                    if (IsWildCardCharacter(entityTagHeader) ||
 272                        DoesHeaderContainEtag(entityTagHeader, entityTag))
 273                    {
 274                        // set response entityTag directly because it has already been validated
 275                        WebOperationContext.Current.OutgoingResponse.ETag = entityTag;
 276                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.N
 277                    }
 278                }
 279            }
 280        }
 281
 282        private void CheckConditionalUpdateWithValidatedEtag(string entityTag)
 283        {
 284            bool isPutMethod = string.Equals(Method, s_httpPutMethod, StringComparison.OrdinalIgnoreCase);
 285            if (!isPutMethod &&
 286                !string.Equals(Method, s_httpPostMethod, StringComparison.OrdinalIgnoreCase) &&
 287                !string.Equals(Method, s_httpDeleteMethod, StringComparison.OrdinalIgnoreCase))
 288            {
 289                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
 290                    SR.Format(SR.ConditionalUpdatePutPostAndDeleteOnly, Method)));
 291            }
 292
 293            string headerOfInterest;
 294
 295            // if the current entityTag is null then the resource doesn't currently exist and the
 296            //   a PUT request should only succeed if If-None-Match equals '*'.
 297            if (isPutMethod && string.IsNullOrEmpty(entityTag))
 298            {
 299                headerOfInterest = Headers[HttpRequestHeader.IfNoneMatch];
 300                if (string.IsNullOrEmpty(headerOfInterest) ||
 301                    !IsWildCardCharacter(headerOfInterest))
 302                {
 303                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.Preco
 304                }
 305            }
 306            else
 307            {
 308                // all remaining cases are with an If-Match header
 309                headerOfInterest = Headers[HttpRequestHeader.IfMatch];
 310                if (string.IsNullOrEmpty(headerOfInterest) ||
 311                    (!IsWildCardCharacter(headerOfInterest) &&
 312                    !DoesHeaderContainEtag(headerOfInterest, entityTag)))
 313                {
 314                    // set response entityTag directly because it has already been validated
 315                    WebOperationContext.Current.OutgoingResponse.ETag = entityTag;
 316                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.Preco
 317                }
 318            }
 319        }
 320
 321        private static bool DoesHeaderContainEtag(string header, string entityTag)
 322        {
 323            int offset = 0;
 324            while (true)
 325            {
 326                string nextEntityTag = Utility.QuoteAwareSubString(header, ref offset);
 327                if (nextEntityTag == null)
 328                {
 329                    break;
 330                }
 331
 332                if (string.Equals(nextEntityTag, entityTag, StringComparison.Ordinal))
 333                {
 334                    return true;
 335                }
 336            }
 337
 338            return false;
 339        }
 340
 341        private static bool IsWildCardCharacter(string header) => header.Trim() == "*";
 342    }
 343
 344    internal class AcceptHeaderElementComparer : IComparer<ContentType>
 345    {
 0346        private static readonly NumberStyles s_numberStyles = NumberStyles.AllowDecimalPoint;
 347
 348        public int Compare(ContentType x, ContentType y)
 349        {
 0350            string[] xTypeSubType = x.MediaType.Split('/');
 0351            string[] yTypeSubType = y.MediaType.Split('/');
 352
 353            Fx.Assert(xTypeSubType.Length == 2, "The creation of the ContentType would have failed if there wasn't a typ
 354            Fx.Assert(yTypeSubType.Length == 2, "The creation of the ContentType would have failed if there wasn't a typ
 355
 0356            if (string.Equals(xTypeSubType[0], yTypeSubType[0], StringComparison.OrdinalIgnoreCase))
 357            {
 0358                if (string.Equals(xTypeSubType[1], yTypeSubType[1], StringComparison.OrdinalIgnoreCase))
 359                {
 360                    // need to check the number of parameters to determine which is more specific
 0361                    bool xHasParam = HasParameters(x);
 0362                    bool yHasParam = HasParameters(y);
 0363                    if (xHasParam && !yHasParam)
 364                    {
 0365                        return 1;
 366                    }
 0367                    else if (!xHasParam && yHasParam)
 368                    {
 0369                        return -1;
 370                    }
 371                }
 372                else
 373                {
 0374                    if (xTypeSubType[1][0] == '*' && xTypeSubType[1].Length == 1)
 375                    {
 0376                        return 1;
 377                    }
 0378                    if (yTypeSubType[1][0] == '*' && yTypeSubType[1].Length == 1)
 379                    {
 0380                        return -1;
 381                    }
 382                }
 383            }
 0384            else if (xTypeSubType[0][0] == '*' && xTypeSubType[0].Length == 1)
 385            {
 0386                return 1;
 387            }
 0388            else if (yTypeSubType[0][0] == '*' && yTypeSubType[0].Length == 1)
 389            {
 0390                return -1;
 391            }
 392
 0393            decimal qualityDifference = GetQualityFactor(x) - GetQualityFactor(y);
 0394            if (qualityDifference < 0)
 395            {
 0396                return 1;
 397            }
 0398            else if (qualityDifference > 0)
 399            {
 0400                return -1;
 401            }
 402
 0403            return 0;
 404        }
 405
 406        private decimal GetQualityFactor(ContentType contentType)
 407        {
 0408            foreach (string key in contentType.Parameters.Keys)
 409            {
 0410                if (string.Equals("q", key, StringComparison.OrdinalIgnoreCase))
 411                {
 0412                    if (decimal.TryParse(contentType.Parameters[key], s_numberStyles, CultureInfo.InvariantCulture, out 
 0413                        (result <= (decimal)1.0))
 414                    {
 0415                        return result;
 416                    }
 417                }
 418            }
 419
 0420            return (decimal)1.0;
 0421        }
 422
 423        private bool HasParameters(ContentType contentType)
 424        {
 0425            int number = 0;
 0426            foreach (string param in contentType.Parameters.Keys)
 427            {
 0428                if (!string.Equals("q", param, StringComparison.OrdinalIgnoreCase))
 429                {
 0430                    number++;
 431                }
 432            }
 433
 0434            return number > 0;
 435        }
 436    }
 437}