Merge pull request #1245 from StephenMcConnel/bug-22483
[mono.git] / mcs / class / System.Net.Http / System.Net.Http.Headers / CacheControlHeaderValue.cs
1 //
2 // CacheControlHeaderValue.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System.Collections.Generic;
30 using System.Text;
31 using System.Globalization;
32
33 namespace System.Net.Http.Headers
34 {
35         public class CacheControlHeaderValue : ICloneable
36         {
37                 List<NameValueHeaderValue> extensions;
38                 List<string> no_cache_headers, private_headers;
39
40                 public ICollection<NameValueHeaderValue> Extensions {
41                         get {
42                                 return extensions ?? (extensions = new List<NameValueHeaderValue> ());
43                         }
44                 }
45
46                 public TimeSpan? MaxAge { get; set; }
47
48                 public bool MaxStale { get; set; }
49
50                 public TimeSpan? MaxStaleLimit { get; set; }
51
52                 public TimeSpan? MinFresh { get; set; }
53
54                 public bool MustRevalidate { get; set; }
55
56                 public bool NoCache { get; set; }
57
58                 public ICollection<string> NoCacheHeaders {
59                         get {
60                                 return no_cache_headers ?? (no_cache_headers = new List<string> ());
61                         }
62                 }
63
64                 public bool NoStore { get; set; }
65
66                 public bool NoTransform { get; set; }
67
68                 public bool OnlyIfCached { get; set; }
69
70                 public bool Private { get; set; }
71
72                 public ICollection<string> PrivateHeaders {
73                         get {
74                                 return private_headers ?? (private_headers = new List<string> ());
75                         }
76                 }
77
78                 public bool ProxyRevalidate { get; set; }
79
80                 public bool Public { get; set; }
81
82                 public TimeSpan? SharedMaxAge { get; set; }
83
84                 object ICloneable.Clone ()
85                 {
86                         var copy = (CacheControlHeaderValue) MemberwiseClone ();
87                         if (extensions != null) {
88                                 copy.extensions = new List<NameValueHeaderValue> ();
89                                 foreach (var entry in extensions) {
90                                         copy.extensions.Add (entry);
91                                 }
92                         }
93
94                         if (no_cache_headers != null) {
95                                 copy.no_cache_headers = new List<string> ();
96                                 foreach (var entry in no_cache_headers) {
97                                         copy.no_cache_headers.Add (entry);
98                                 }
99                         }
100
101                         if (private_headers != null) {
102                                 copy.private_headers = new List<string> ();
103                                 foreach (var entry in private_headers) {
104                                         copy.private_headers.Add (entry);
105                                 }
106                         }
107
108                         return copy;
109                 }
110
111                 public override bool Equals (object obj)
112                 {
113                         var source = obj as CacheControlHeaderValue;
114                         if (source == null)
115                                 return false;
116
117                         if (MaxAge != source.MaxAge || MaxStale != source.MaxStale || MaxStaleLimit != source.MaxStaleLimit ||
118                                 MinFresh != source.MinFresh || MustRevalidate != source.MustRevalidate || NoCache != source.NoCache ||
119                                 NoStore != source.NoStore || NoTransform != source.NoTransform || OnlyIfCached != source.OnlyIfCached ||
120                                 Private != source.Private || ProxyRevalidate != source.ProxyRevalidate || Public != source.Public ||
121                                 SharedMaxAge != source.SharedMaxAge)
122                                 return false;
123
124                         return extensions.SequenceEqual (source.extensions) &&
125                                 no_cache_headers.SequenceEqual (source.no_cache_headers) &&
126                                 private_headers.SequenceEqual (source.private_headers);
127                 }
128
129                 public override int GetHashCode ()
130                 {
131                         int hc = 29;
132                         unchecked {
133                                 hc = hc * 29 + HashCodeCalculator.Calculate (extensions);
134                                 hc = hc * 29 + MaxAge.GetHashCode ();
135                                 hc = hc * 29 + MaxStale.GetHashCode ();
136                                 hc = hc * 29 + MaxStaleLimit.GetHashCode ();
137                                 hc = hc * 29 + MinFresh.GetHashCode ();
138                                 hc = hc * 29 + MustRevalidate.GetHashCode ();
139                                 hc = hc * 29 + HashCodeCalculator.Calculate (no_cache_headers);
140                                 hc = hc * 29 + NoCache.GetHashCode ();
141                                 hc = hc * 29 + NoStore.GetHashCode ();
142                                 hc = hc * 29 + NoTransform.GetHashCode ();
143                                 hc = hc * 29 + OnlyIfCached.GetHashCode ();
144                                 hc = hc * 29 + Private.GetHashCode ();
145                                 hc = hc * 29 + HashCodeCalculator.Calculate (private_headers);
146                                 hc = hc * 29 + ProxyRevalidate.GetHashCode ();
147                                 hc = hc * 29 + Public.GetHashCode ();
148                                 hc = hc * 29 + SharedMaxAge.GetHashCode ();
149                         }
150
151                         return hc;
152                 }
153
154                 public static CacheControlHeaderValue Parse (string input)
155                 {
156                         CacheControlHeaderValue value;
157                         if (TryParse (input, out value))
158                                 return value;
159
160                         throw new FormatException (input);
161                 }
162
163                 public static bool TryParse (string input, out CacheControlHeaderValue parsedValue)
164                 {
165                         parsedValue = null;
166                         if (input == null)
167                                 return true;
168
169                         var value = new CacheControlHeaderValue ();
170
171                         var lexer = new Lexer (input);
172                         Token t;
173                         do {
174                                 t = lexer.Scan ();
175                                 if (t != Token.Type.Token)
176                                         return false;
177
178                                 string s = lexer.GetStringValue (t);
179                                 bool token_read = false;
180                                 TimeSpan? ts;
181                                 switch (s) {
182                                 case "no-store":
183                                         value.NoStore = true;
184                                         break;
185                                 case "no-transform":
186                                         value.NoTransform = true;
187                                         break;
188                                 case "only-if-cached":
189                                         value.OnlyIfCached = true;
190                                         break;
191                                 case "public":
192                                         value.Public = true;
193                                         break;
194                                 case "must-revalidate":
195                                         value.MustRevalidate = true;
196                                         break;
197                                 case "proxy-revalidate":
198                                         value.ProxyRevalidate = true;
199                                         break;
200                                 case "max-stale":
201                                         value.MaxStale = true;
202                                         t = lexer.Scan ();
203                                         if (t != Token.Type.SeparatorEqual) {
204                                                 token_read = true;
205                                                 break;
206                                         }
207
208                                         t = lexer.Scan ();
209                                         if (t != Token.Type.Token)
210                                                 return false;
211
212                                         ts = lexer.TryGetTimeSpanValue (t);
213                                         if (ts == null)
214                                                 return false;
215
216                                         value.MaxStaleLimit = ts;
217                                         break;
218                                 case "max-age":
219                                 case "s-maxage":
220                                 case "min-fresh":
221                                         t = lexer.Scan ();
222                                         if (t != Token.Type.SeparatorEqual) {
223                                                 return false;
224                                         }
225
226                                         t = lexer.Scan ();
227                                         if (t != Token.Type.Token)
228                                                 return false;
229
230                                         ts = lexer.TryGetTimeSpanValue (t);
231                                         if (ts == null)
232                                                 return false;
233
234                                         switch (s.Length) {
235                                         case 7:
236                                                 value.MaxAge = ts;
237                                                 break;
238                                         case 8:
239                                                 value.SharedMaxAge = ts;
240                                                 break;
241                                         default:
242                                                 value.MinFresh = ts;
243                                                 break;
244                                         }
245
246                                         break;
247                                 case "private":
248                                 case "no-cache":
249                                         if (s.Length == 7) {
250                                                 value.Private = true;
251                                         } else {
252                                                 value.NoCache = true;
253                                         }
254
255                                         t = lexer.Scan ();
256                                         if (t != Token.Type.SeparatorEqual) {
257                                                 token_read = true;
258                                                 break;
259                                         }
260
261                                         t = lexer.Scan ();
262                                         if (t != Token.Type.QuotedString)
263                                                 return false;
264
265                                         foreach (var entry in lexer.GetQuotedStringValue (t).Split (',')) {
266                                                 var qs = entry.Trim ('\t', ' ');
267
268                                                 if (s.Length == 7) {
269                                                         value.PrivateHeaders.Add (qs);
270                                                 } else {
271                                                         value.NoCache = true;
272                                                         value.NoCacheHeaders.Add (qs);
273                                                 }
274                                         }
275                                         break;
276                                 default:
277                                         string name = lexer.GetStringValue (t);
278                                         string svalue = null;
279
280                                         t = lexer.Scan ();
281                                         if (t == Token.Type.SeparatorEqual) {
282                                                 t = lexer.Scan ();
283                                                 switch (t.Kind) {
284                                                 case Token.Type.Token:
285                                                 case Token.Type.QuotedString:
286                                                         svalue = lexer.GetStringValue (t);
287                                                         break;
288                                                 default:
289                                                         return false;
290                                                 }
291                                         } else {
292                                                 token_read = true;
293                                         }
294
295                                         value.Extensions.Add (NameValueHeaderValue.Create (name, svalue));
296                                         break;
297                                 }
298
299                                 if (!token_read)
300                                         t = lexer.Scan ();
301                         } while (t == Token.Type.SeparatorComma);
302
303                         if (t != Token.Type.End)
304                                 return false;
305
306                         parsedValue = value;
307                         return true;
308                 }
309
310                 public override string ToString ()
311                 {
312                         const string separator = ", ";
313
314                         var sb = new StringBuilder ();
315                         if (NoStore) {
316                                 sb.Append ("no-store");
317                                 sb.Append (separator);
318                         }
319
320                         if (NoTransform) {
321                                 sb.Append ("no-transform");
322                                 sb.Append (separator);
323                         }
324
325                         if (OnlyIfCached) {
326                                 sb.Append ("only-if-cached");
327                                 sb.Append (separator);
328                         }
329
330                         if (Public) {
331                                 sb.Append ("public");
332                                 sb.Append (separator);
333                         }
334
335                         if (MustRevalidate) {
336                                 sb.Append ("must-revalidate");
337                                 sb.Append (separator);
338                         }
339
340                         if (ProxyRevalidate) {
341                                 sb.Append ("proxy-revalidate");
342                                 sb.Append (separator);
343                         }
344
345                         if (NoCache) {
346                                 sb.Append ("no-cache");
347                                 if (no_cache_headers != null) {
348                                         sb.Append ("=\"");
349                                         no_cache_headers.ToStringBuilder (sb);
350                                         sb.Append ("\"");
351                                 }
352
353                                 sb.Append (separator);
354                         }
355
356                         if (MaxAge != null) {
357                                 sb.Append ("max-age=");
358                                 sb.Append (MaxAge.Value.TotalSeconds.ToString (CultureInfo.InvariantCulture));
359                                 sb.Append (separator);
360                         }
361
362                         if (SharedMaxAge != null) {
363                                 sb.Append ("s-maxage=");
364                                 sb.Append (SharedMaxAge.Value.TotalSeconds.ToString (CultureInfo.InvariantCulture));
365                                 sb.Append (separator);
366                         }
367
368                         if (MaxStale) {
369                                 sb.Append ("max-stale");
370                                 if (MaxStaleLimit != null) {
371                                         sb.Append ("=");
372                                         sb.Append (MaxStaleLimit.Value.TotalSeconds.ToString (CultureInfo.InvariantCulture));
373                                 }
374
375                                 sb.Append (separator);
376                         }
377
378                         if (MinFresh != null) {
379                                 sb.Append ("min-fresh=");
380                                 sb.Append (MinFresh.Value.TotalSeconds.ToString (CultureInfo.InvariantCulture));
381                                 sb.Append (separator);
382                         }
383
384                         if (Private) {
385                                 sb.Append ("private");
386                                 if (private_headers != null) {
387                                         sb.Append ("=\"");
388                                         private_headers.ToStringBuilder (sb);
389                                         sb.Append ("\"");
390                                 }
391
392                                 sb.Append (separator);
393                         }
394
395                         CollectionExtensions.ToStringBuilder (extensions, sb);
396
397                         if (sb.Length > 2 && sb[sb.Length - 2] == ',' && sb[sb.Length - 1] == ' ')
398                                 sb.Remove (sb.Length - 2, 2);
399
400                         return sb.ToString ();
401                 }
402         }
403 }