PageRenderTime 44ms CodeModel.GetById 11ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/WCFWebApi/src/System.Net.Http/System/Net/Http/Headers/CacheControlHeaderValue.cs

#
C# | 647 lines | 523 code | 102 blank | 22 comment | 104 complexity | 887f335742010ab98fd924bfb8e610b6 MD5 | raw file
  1using System.Collections.Generic;
  2using System.Diagnostics.Contracts;
  3using System.Globalization;
  4using System.Text;
  5
  6namespace System.Net.Http.Headers
  7{
  8    public class CacheControlHeaderValue : ICloneable
  9    {
 10        private const string maxAgeString = "max-age";
 11        private const string maxStaleString = "max-stale";
 12        private const string minFreshString = "min-fresh";
 13        private const string mustRevalidateString = "must-revalidate";
 14        private const string noCacheString = "no-cache";
 15        private const string noStoreString = "no-store";
 16        private const string noTransformString = "no-transform";
 17        private const string onlyIfCachedString = "only-if-cached";
 18        private const string privateString = "private";
 19        private const string proxyRevalidateString = "proxy-revalidate";
 20        private const string publicString = "public";
 21        private const string sharedMaxAgeString = "s-maxage";
 22
 23        private static readonly HttpHeaderParser nameValueListParser = GenericHeaderParser.MultipleValueNameValueParser;
 24        private static readonly Action<string> checkIsValidToken = CheckIsValidToken;
 25
 26        private bool noCache;
 27        private ICollection<string> noCacheHeaders;
 28        private bool noStore;
 29        private TimeSpan? maxAge;
 30        private TimeSpan? sharedMaxAge;
 31        private bool maxStale;
 32        private TimeSpan? maxStaleLimit;
 33        private TimeSpan? minFresh;
 34        private bool noTransform;
 35        private bool onlyIfCached;
 36        private bool publicField;
 37        private bool privateField;
 38        private ICollection<string> privateHeaders;
 39        private bool mustRevalidate;
 40        private bool proxyRevalidate;
 41        private ICollection<NameValueHeaderValue> extensions;
 42
 43        public bool NoCache
 44        {
 45            get { return noCache; }
 46            set { noCache = value; }
 47        }
 48
 49        public ICollection<string> NoCacheHeaders
 50        {
 51            get
 52            {
 53                if (noCacheHeaders == null)
 54                {
 55                    noCacheHeaders = new ObjectCollection<string>(checkIsValidToken);
 56                }
 57                return noCacheHeaders;
 58            }
 59        }
 60
 61        public bool NoStore
 62        {
 63            get { return noStore; }
 64            set { noStore = value; }
 65        }
 66
 67        public TimeSpan? MaxAge
 68        {
 69            get { return maxAge; }
 70            set { maxAge = value; }
 71        }
 72
 73        public TimeSpan? SharedMaxAge
 74        {
 75            get { return sharedMaxAge; }
 76            set { sharedMaxAge = value; }
 77        }
 78
 79        public bool MaxStale
 80        {
 81            get { return maxStale; }
 82            set { maxStale = value; }
 83        }
 84
 85        public TimeSpan? MaxStaleLimit
 86        {
 87            get { return maxStaleLimit; }
 88            set { maxStaleLimit = value; }
 89        }
 90
 91        public TimeSpan? MinFresh
 92        {
 93            get { return minFresh; }
 94            set { minFresh = value; }
 95        }
 96
 97        public bool NoTransform
 98        {
 99            get { return noTransform; }
100            set { noTransform = value; }
101        }
102
103        public bool OnlyIfCached
104        {
105            get { return onlyIfCached; }
106            set { onlyIfCached = value; }
107        }
108
109        public bool Public
110        {
111            get { return publicField; }
112            set { publicField = value; }
113        }
114
115        public bool Private
116        {
117            get { return privateField; }
118            set { privateField = value; }
119        }
120
121        public ICollection<string> PrivateHeaders
122        {
123            get
124            {
125                if (privateHeaders == null)
126                {
127                    privateHeaders = new ObjectCollection<string>(checkIsValidToken);
128                }
129                return privateHeaders;
130            }
131        }
132
133        public bool MustRevalidate
134        {
135            get { return mustRevalidate; }
136            set { mustRevalidate = value; }
137        }
138
139        public bool ProxyRevalidate
140        {
141            get { return proxyRevalidate; }
142            set { proxyRevalidate = value; }
143        }
144
145        public ICollection<NameValueHeaderValue> Extensions
146        {
147            get
148            {
149                if (extensions == null)
150                {
151                    extensions = new ObjectCollection<NameValueHeaderValue>();
152                }
153                return extensions;
154            }
155        }
156
157        public CacheControlHeaderValue()
158        {
159        }
160
161        private CacheControlHeaderValue(CacheControlHeaderValue source)
162        {
163            Contract.Requires(source != null);
164
165            noCache = source.noCache;
166            noStore = source.noStore;
167            maxAge = source.maxAge;
168            sharedMaxAge = source.sharedMaxAge;
169            maxStale = source.maxStale;
170            maxStaleLimit = source.maxStaleLimit;
171            minFresh = source.minFresh;
172            noTransform = source.noTransform;
173            onlyIfCached = source.onlyIfCached;
174            publicField = source.publicField;
175            privateField = source.privateField;
176            mustRevalidate = source.mustRevalidate;
177            proxyRevalidate = source.proxyRevalidate;
178
179            if (source.noCacheHeaders != null)
180            {
181                foreach (var noCacheHeader in source.noCacheHeaders)
182                {
183                    NoCacheHeaders.Add(noCacheHeader);
184                }
185            }
186
187            if (source.privateHeaders != null)
188            {
189                foreach (var privateHeader in source.privateHeaders)
190                {
191                    PrivateHeaders.Add(privateHeader);
192                }
193            }
194
195            if (source.extensions != null)
196            {
197                foreach (var extension in source.extensions)
198                {
199                    Extensions.Add((NameValueHeaderValue)((ICloneable)extension).Clone());
200                }
201            }
202        }
203
204        public override string ToString()
205        {
206            StringBuilder sb = new StringBuilder();
207
208            AppendValueIfRequired(sb, noStore, noStoreString);
209            AppendValueIfRequired(sb, noTransform, noTransformString);
210            AppendValueIfRequired(sb, onlyIfCached, onlyIfCachedString);
211            AppendValueIfRequired(sb, publicField, publicString);
212            AppendValueIfRequired(sb, mustRevalidate, mustRevalidateString);
213            AppendValueIfRequired(sb, proxyRevalidate, proxyRevalidateString);
214
215            if (noCache)
216            {
217                AppendValueWithSeparatorIfRequired(sb, noCacheString);
218                if ((noCacheHeaders != null) && (noCacheHeaders.Count > 0))
219                {
220                    sb.Append("=\"");
221                    AppendValues(sb, noCacheHeaders);
222                    sb.Append('\"');
223                }
224            }
225
226            if (maxAge.HasValue)
227            {
228                AppendValueWithSeparatorIfRequired(sb, maxAgeString);
229                sb.Append('=');
230                sb.Append(((int)maxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
231            }
232
233            if (sharedMaxAge.HasValue)
234            {
235                AppendValueWithSeparatorIfRequired(sb, sharedMaxAgeString);
236                sb.Append('=');
237                sb.Append(((int)sharedMaxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
238            }
239
240            if (maxStale)
241            {
242                AppendValueWithSeparatorIfRequired(sb, maxStaleString);
243                if (maxStaleLimit.HasValue)
244                {
245                    sb.Append('=');
246                    sb.Append(((int)maxStaleLimit.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
247                }
248            }
249
250            if (minFresh.HasValue)
251            {
252                AppendValueWithSeparatorIfRequired(sb, minFreshString);
253                sb.Append('=');
254                sb.Append(((int)minFresh.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
255            }
256
257            if (privateField)
258            {
259                AppendValueWithSeparatorIfRequired(sb, privateString);
260                if ((privateHeaders != null) && (privateHeaders.Count > 0))
261                {
262                    sb.Append("=\"");
263                    AppendValues(sb, privateHeaders);
264                    sb.Append('\"');
265                }
266            }
267
268            NameValueHeaderValue.ToString(extensions, ',', false, sb);
269
270            return sb.ToString();
271        }
272
273        public override bool Equals(object obj)
274        {
275            CacheControlHeaderValue other = obj as CacheControlHeaderValue;
276
277            if (other == null)
278            {
279                return false;
280            }
281
282            if ((noCache != other.noCache) || (noStore != other.noStore) || (maxAge != other.maxAge) ||
283                (sharedMaxAge != other.sharedMaxAge) || (maxStale != other.maxStale) ||
284                (maxStaleLimit != other.maxStaleLimit) || (minFresh != other.minFresh) ||
285                (noTransform != other.noTransform) || (onlyIfCached != other.onlyIfCached) ||
286                (publicField != other.publicField) || (privateField != other.privateField) ||
287                (mustRevalidate != other.mustRevalidate) || (proxyRevalidate != other.proxyRevalidate))
288            {
289                return false;
290            }
291
292            if (!HeaderUtilities.AreEqualCollections(noCacheHeaders, other.noCacheHeaders,
293                HeaderUtilities.CaseInsensitiveStringComparer))
294            {
295                return false;
296            }
297
298            if (!HeaderUtilities.AreEqualCollections(privateHeaders, other.privateHeaders,
299                HeaderUtilities.CaseInsensitiveStringComparer))
300            {
301                return false;
302            }
303
304            if (!HeaderUtilities.AreEqualCollections(extensions, other.extensions))
305            {
306                return false;
307            }
308
309            return true;
310        }
311
312        public override int GetHashCode()
313        {
314            // Use a different bit for bool fields: bool.GetHashCode() will return 0 (false) or 1 (true). So we would
315            // end up having the same hash code for e.g. two instances where one has only noCache set and the other
316            // only noStore.
317            int result = noCache.GetHashCode() ^ (noStore.GetHashCode() << 1) ^ (maxStale.GetHashCode() << 2) ^
318                (noTransform.GetHashCode() << 3) ^ (onlyIfCached.GetHashCode() << 4) ^
319                (publicField.GetHashCode() << 5) ^ (privateField.GetHashCode() << 6) ^
320                (mustRevalidate.GetHashCode() << 7) ^ (proxyRevalidate.GetHashCode() << 8);
321
322            // XOR the hashcode of timespan values with different numbers to make sure two instances with the same
323            // timespan set on different fields result in different hashcodes.
324            result = result ^ (maxAge.HasValue ? maxAge.Value.GetHashCode() ^ 1 : 0) ^
325                (sharedMaxAge.HasValue ? sharedMaxAge.Value.GetHashCode() ^ 2 : 0) ^
326                (maxStaleLimit.HasValue ? maxStaleLimit.Value.GetHashCode() ^ 4 : 0) ^
327                (minFresh.HasValue ? minFresh.Value.GetHashCode() ^ 8 : 0);
328
329            if ((noCacheHeaders != null) && (noCacheHeaders.Count > 0))
330            {
331                foreach (var noCacheHeader in noCacheHeaders)
332                {
333                    result = result ^ noCacheHeader.ToLowerInvariant().GetHashCode();
334                }
335            }
336
337            if ((privateHeaders != null) && (privateHeaders.Count > 0))
338            {
339                foreach (var privateHeader in privateHeaders)
340                {
341                    result = result ^ privateHeader.ToLowerInvariant().GetHashCode();
342                }
343            }
344
345            if ((extensions != null) && (extensions.Count > 0))
346            {
347                foreach (var extension in extensions)
348                {
349                    result = result ^ extension.GetHashCode();
350                }
351            }
352
353            return result;
354        }
355
356        public static CacheControlHeaderValue Parse(string input)
357        {
358            int index = 0;
359            return (CacheControlHeaderValue)CacheControlHeaderParser.Parser.ParseValue(input, null, ref index);
360        }
361
362        public static bool TryParse(string input, out CacheControlHeaderValue parsedValue)
363        {
364            int index = 0;
365            object output;
366            parsedValue = null;
367
368            if (CacheControlHeaderParser.Parser.TryParseValue(input, null, ref index, out output))
369            {
370                parsedValue = (CacheControlHeaderValue)output;
371                return true;
372            }
373            return false;
374        }
375
376        internal static int GetCacheControlLength(string input, int startIndex, CacheControlHeaderValue storeValue,
377            out CacheControlHeaderValue parsedValue)
378        {
379            Contract.Requires(startIndex >= 0);
380
381            parsedValue = null;
382
383            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
384            {
385                return 0;
386            }
387
388            // Cache-Control header consists of a list of name/value pairs, where the value is optional. So use an
389            // instance of NameValueHeaderParser to parse the string.
390            int current = startIndex;
391            object nameValue = null;
392            List<NameValueHeaderValue> nameValueList = new List<NameValueHeaderValue>();
393            while (current < input.Length)
394            {
395                if (!nameValueListParser.TryParseValue(input, null, ref current, out nameValue))
396                {
397                    return 0;
398                }
399
400                nameValueList.Add(nameValue as NameValueHeaderValue);
401            }
402
403            // If we get here, we were able to successfully parse the string as list of name/value pairs. Now analyze
404            // the name/value pairs.
405
406            // Cache-Control is a header supporting lists of values. However, expose the header as an instance of
407            // CacheControlHeaderValue. So if we already have an instance of CacheControlHeaderValue, add the values
408            // from this string to the existing instances. 
409            CacheControlHeaderValue result = storeValue;
410            if (result == null)
411            {
412                result = new CacheControlHeaderValue();
413            }
414
415            if (!TrySetCacheControlValues(result, nameValueList))
416            {
417                return 0;
418            }
419
420            // If we had an existing store value and we just updated that instance, return 'null' to indicate that 
421            // we don't have a new instance of CacheControlHeaderValue, but just updated an existing one. This is the
422            // case if we have multiple 'Cache-Control' headers set in a request/response message.
423            if (storeValue == null)
424            {
425                parsedValue = result;
426            }
427
428            // If we get here we successfully parsed the whole string.
429            return input.Length - startIndex;
430        }
431
432        private static bool TrySetCacheControlValues(CacheControlHeaderValue cc,
433            List<NameValueHeaderValue> nameValueList)
434        {
435            foreach (NameValueHeaderValue nameValue in nameValueList)
436            {
437                bool success = true;
438                string name = nameValue.Name.ToLowerInvariant();
439
440                switch (name)
441                {
442                    case noCacheString:
443                        success = TrySetOptionalTokenList(nameValue, ref cc.noCache, ref cc.noCacheHeaders);
444                        break;
445
446                    case noStoreString:
447                        success = TrySetTokenOnlyValue(nameValue, ref cc.noStore);
448                        break;
449
450                    case maxAgeString:
451                        success = TrySetTimeSpan(nameValue, ref cc.maxAge);
452                        break;
453
454                    case maxStaleString:
455                        success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc.maxStaleLimit));
456                        if (success)
457                        {
458                            cc.maxStale = true;
459                        }
460                        break;
461
462                    case minFreshString:
463                        success = TrySetTimeSpan(nameValue, ref cc.minFresh);
464                        break;
465
466                    case noTransformString:
467                        success = TrySetTokenOnlyValue(nameValue, ref cc.noTransform);
468                        break;
469
470                    case onlyIfCachedString:
471                        success = TrySetTokenOnlyValue(nameValue, ref cc.onlyIfCached);
472                        break;
473
474                    case publicString:
475                        success = TrySetTokenOnlyValue(nameValue, ref cc.publicField);
476                        break;
477
478                    case privateString:
479                        success = TrySetOptionalTokenList(nameValue, ref cc.privateField, ref cc.privateHeaders);
480                        break;
481
482                    case mustRevalidateString:
483                        success = TrySetTokenOnlyValue(nameValue, ref cc.mustRevalidate);
484                        break;
485
486                    case proxyRevalidateString:
487                        success = TrySetTokenOnlyValue(nameValue, ref cc.proxyRevalidate);
488                        break;
489
490                    case sharedMaxAgeString:
491                        success = TrySetTimeSpan(nameValue, ref cc.sharedMaxAge);
492                        break;
493
494                    default:
495                        cc.Extensions.Add(nameValue); // success is always true
496                        break;
497                }
498
499                if (!success)
500                {
501                    return false;
502                }
503            }
504
505            return true;
506        }
507
508        private static bool TrySetTokenOnlyValue(NameValueHeaderValue nameValue, ref bool boolField)
509        {
510            if (nameValue.Value != null)
511            {
512                return false;
513            }
514
515            boolField = true;
516            return true;
517        }
518
519        private static bool TrySetOptionalTokenList(NameValueHeaderValue nameValue, ref bool boolField,
520            ref ICollection<string> destination)
521        {
522            Contract.Requires(nameValue != null);
523
524            if (nameValue.Value == null)
525            {
526                boolField = true;
527                return true;
528            }
529
530            // We need the string to be at least 3 chars long: 2x quotes and at least 1 character. Also make sure we
531            // have a quoted string. Note that NameValueHeaderValue will never have leading/trailing whitespaces.
532            string valueString = nameValue.Value;
533            if ((valueString.Length < 3) || (valueString[0] != '\"') || (valueString[valueString.Length - 1] != '\"'))
534            {
535                return false;
536            }
537
538            // We have a quoted string. Now verify that the string contains a list of valid tokens separated by ','.
539            int current = 1; // skip the initial '"' character.
540            int maxLength = valueString.Length - 1; // -1 because we don't want to parse the final '"'.
541            bool separatorFound = false;
542            int originalValueCount = destination == null ? 0 : destination.Count;
543            while (current < maxLength)
544            {
545                current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
546                    out separatorFound);
547
548                if (current == maxLength)
549                {
550                    break;
551                }
552
553                int tokenLength = HttpRuleParser.GetTokenLength(valueString, current);
554
555                if (tokenLength == 0)
556                {
557                    // We already skipped whitespaces and separators. If we don't have a token it must be an invalid
558                    // character.
559                    return false; 
560                }
561
562                if (destination == null)
563                {
564                    destination = new ObjectCollection<string>(checkIsValidToken);
565                }
566                
567                destination.Add(valueString.Substring(current, tokenLength));
568                
569                current = current + tokenLength;
570            }
571
572            // After parsing a valid token list, we expect to have at least one value
573            if ((destination != null) && (destination.Count > originalValueCount))
574            {
575                boolField = true;
576                return true;
577            }
578            
579            return false;
580        }
581
582        private static bool TrySetTimeSpan(NameValueHeaderValue nameValue, ref TimeSpan? timeSpan)
583        {
584            Contract.Requires(nameValue != null);
585            
586            if (nameValue.Value == null)
587            {
588                return false;
589            }
590
591            int seconds;
592            if (!HeaderUtilities.TryParseInt32(nameValue.Value, out seconds))
593            {
594                return false;
595            }
596
597            timeSpan = new TimeSpan(0, 0, seconds);
598
599            return true;
600        }
601
602        private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
603        {
604            if (appendValue)
605            {
606                AppendValueWithSeparatorIfRequired(sb, value);
607            }
608        }
609
610        private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
611        {
612            if (sb.Length > 0)
613            {
614                sb.Append(", ");
615            }
616            sb.Append(value);
617        }
618
619        private static void AppendValues(StringBuilder sb, IEnumerable<string> values)
620        {
621            bool first = true;
622            foreach (string value in values)
623            {
624                if (first)
625                {
626                    first = false;
627                }
628                else
629                {
630                    sb.Append(", ");
631                }
632
633                sb.Append(value);
634            }
635        }
636
637        private static void CheckIsValidToken(string item)
638        {
639            HeaderUtilities.CheckValidToken(item, "item");
640        }
641
642        object ICloneable.Clone()
643        {
644            return new CacheControlHeaderValue(this);
645        }
646    }
647}