Added hammock project to debug streaming issues
[pithos-ms-client] / trunk / hammock / src / net40 / Hammock / Mono / WebHeaderCollection.cs
1 //
2 // System.Net.WebHeaderCollection
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //      Miguel de Icaza (miguel@novell.com)
8 //
9 // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
10 // Copyright 2007 Novell, Inc. (http://www.novell.com)
11 //
12 //
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34
35 using System;
36 using System.Collections;
37 using System.Collections.Specialized;
38 using System.Net;
39 using System.Runtime.InteropServices;
40 using System.Text;
41
42 // See RFC 2068 par 4.2 Message Headers
43
44 namespace Mono.Net
45 {
46     [Serializable]
47     [ComVisible(true)]
48     public class WebHeaderCollection : NameValueCollection
49     {
50         private static readonly Hashtable _restricted;
51         private static readonly Hashtable _multiValue;
52
53         private static readonly char[] tspecials =
54             new[]
55                 {
56                     '(', ')', '<', '>', '@',
57                     ',', ';', ':', '\\', '"',
58                     '/', '[', ']', '?', '=',
59                     '{', '}', ' ', '\t'
60                 };
61
62         private readonly bool internallyCreated;
63
64         // Static Initializer
65
66         static WebHeaderCollection()
67         {
68             // the list of restricted header names as defined 
69             // by the ms.net spec
70             _restricted = new Hashtable(StringComparer.InvariantCultureIgnoreCase)
71                              {
72                                  {"accept", true},
73                                  {"connection", true},
74                                  {"content-length", true},
75                                  {"content-type", true},
76                                  {"date", true},
77                                  {"expect", true},
78                                  {"host", true},
79                                  {"if-modified-since", true},
80                                  {"range", true},
81                                  {"referer", true},
82                                  {"transfer-encoding", true},
83                                  {"user-agent", true}
84                              };
85
86             // see par 14 of RFC 2068 to see which header names
87             // accept multiple values each separated by a comma
88             _multiValue = new Hashtable(StringComparer.InvariantCultureIgnoreCase)
89                              {
90                                  {"accept", true},
91                                  {"accept-charset", true},
92                                  {"accept-encoding", true},
93                                  {"accept-language", true},
94                                  {"accept-ranges", true},
95                                  {"allow", true},
96                                  {"authorization", true},
97                                  {"cache-control", true},
98                                  {"connection", true},
99                                  {"content-encoding", true},
100                                  {"content-language", true},
101                                  {"expect", true},
102                                  {"if-match", true},
103                                  {"if-none-match", true},
104                                  {"proxy-authenticate", true},
105                                  {"public", true},
106                                  {"range", true},
107                                  {"transfer-encoding", true},
108                                  {"upgrade", true},
109                                  {"vary", true},
110                                  {"via", true},
111                                  {"warning", true},
112                                  {"www-authenticate", true},
113                                  {"set-cookie", true},
114                                  {"set-cookie2", true}
115                              };
116
117             // Extra
118         }
119
120         // Constructors
121
122         public WebHeaderCollection()
123         {
124         }
125
126         internal WebHeaderCollection(bool internallyCreated)
127         {
128             this.internallyCreated = internallyCreated;
129         }
130
131         public override string[] AllKeys
132         {
133             get { return (base.AllKeys); }
134         }
135
136         public override int Count
137         {
138             get { return (base.Count); }
139         }
140
141         public override KeysCollection Keys
142         {
143             get { return (base.Keys); }
144         }
145
146         public string this[HttpRequestHeader hrh]
147         {
148             get { return Get(RequestHeaderToString(hrh)); }
149
150             set { Add(RequestHeaderToString(hrh), value); }
151         }
152
153         public string this[HttpResponseHeader hrh]
154         {
155             get { return Get(ResponseHeaderToString(hrh)); }
156
157             set { Add(ResponseHeaderToString(hrh), value); }
158         }
159
160         // Methods
161
162         public void Add(string header)
163         {
164             if (header == null)
165                 throw new ArgumentNullException("header");
166             var pos = header.IndexOf(':');
167             if (pos == -1)
168                 throw new ArgumentException("no colon found", "header");
169             Add(header.Substring(0, pos),
170                 header.Substring(pos + 1));
171         }
172
173         public override void Add(string name, string value)
174         {
175             if (name == null)
176                 throw new ArgumentNullException("name");
177             if (internallyCreated && IsRestricted(name))
178                 throw new ArgumentException("This header must be modified with the appropiate property.");
179             AddWithoutValidate(name, value);
180         }
181
182         protected void AddWithoutValidate(string headerName, string headerValue)
183         {
184             if (!IsHeaderName(headerName))
185                 throw new ArgumentException("invalid header name: " + headerName, "headerName");
186             headerValue = headerValue == null ? String.Empty : headerValue.Trim();
187             if (!IsHeaderValue(headerValue))
188                 throw new ArgumentException("invalid header value: " + headerValue, "headerValue");
189             base.Add(headerName, headerValue);
190         }
191
192         public override string[] GetValues(string header)
193         {
194             if (header == null)
195                 throw new ArgumentNullException("header");
196
197             var values = base.GetValues(header);
198             if (values == null || values.Length == 0)
199                 return null;
200
201             /*
202             if (IsMultiValue (header)) {
203                 values = GetMultipleValues (values);
204             }
205             */
206
207             return values;
208         }
209
210         public override string[] GetValues(int index)
211         {
212             var values = base.GetValues(index);
213             if (values == null || values.Length == 0)
214             {
215                 return (null);
216             }
217
218             return (values);
219         }
220
221         /* Now i wonder why this is here...
222         static string [] GetMultipleValues (string [] values)
223         {
224             ArrayList mvalues = new ArrayList (values.Length);
225             StringBuilder sb = null;
226             for (int i = 0; i < values.Length; ++i) {
227                 string val = values [i];
228                 if (val.IndexOf (',') == -1) {
229                     mvalues.Add (val);
230                     continue;
231                 }
232
233                 if (sb == null)
234                     sb = new StringBuilder ();
235
236                 bool quote = false;
237                 for (int k = 0; k < val.Length; k++) {
238                     char c = val [k];
239                     if (c == '"') {
240                         quote = !quote;
241                     } else if (!quote && c == ',') {
242                         mvalues.Add (sb.ToString ().Trim ());
243                         sb.Length = 0;
244                         continue;
245                     }
246                     sb.Append (c);
247                 }
248
249                 if (sb.Length > 0) {
250                     mvalues.Add (sb.ToString ().Trim ());
251                     sb.Length = 0;
252                 }
253             }
254
255             return (string []) mvalues.ToArray (typeof (string));
256         }
257         */
258
259         public static bool IsRestricted(string headerName)
260         {
261             if (headerName == null)
262                 throw new ArgumentNullException("headerName");
263
264             if (headerName == "") // MS throw nullexception here!
265                 throw new ArgumentException("empty string", "headerName");
266
267             return _restricted.ContainsKey(headerName);
268         }
269
270         public static bool IsRestricted(string headerName, bool response)
271         {
272             throw new NotImplementedException();
273         }
274
275         public override void Remove(string name)
276         {
277             if (name == null)
278                 throw new ArgumentNullException("name");
279             if (internallyCreated && IsRestricted(name))
280                 throw new ArgumentException("restricted header");
281             base.Remove(name);
282         }
283
284         public override void Set(string name, string value)
285         {
286             if (name == null)
287                 throw new ArgumentNullException("name");
288             if (internallyCreated && IsRestricted(name))
289                 throw new ArgumentException("restricted header");
290             if (!IsHeaderName(name))
291                 throw new ArgumentException("invalid header name");
292             value = value == null ? String.Empty : value.Trim();
293             if (!IsHeaderValue(value))
294                 throw new ArgumentException("invalid header value");
295             base.Set(name, value);
296         }
297
298         public byte[] ToByteArray()
299         {
300             return Encoding.UTF8.GetBytes(ToString());
301         }
302
303         public override string ToString()
304         {
305             var sb = new StringBuilder();
306
307             var count = base.Count;
308             for (var i = 0; i < count; i++)
309                 sb.Append(GetKey(i))
310                     .Append(": ")
311                     .Append(Get(i))
312                     .Append("\r\n");
313
314             return sb.Append("\r\n").ToString();
315         }
316
317         public override string Get(int index)
318         {
319             return (base.Get(index));
320         }
321
322         public override string Get(string name)
323         {
324             return (base.Get(name));
325         }
326
327         public override string GetKey(int index)
328         {
329             return (base.GetKey(index));
330         }
331
332         public void Add(HttpRequestHeader header, string value)
333         {
334             Add(RequestHeaderToString(header), value);
335         }
336
337         public void Remove(HttpRequestHeader header)
338         {
339             Remove(RequestHeaderToString(header));
340         }
341
342         public void Set(HttpRequestHeader header, string value)
343         {
344             Set(RequestHeaderToString(header), value);
345         }
346
347         public void Add(HttpResponseHeader header, string value)
348         {
349             Add(ResponseHeaderToString(header), value);
350         }
351
352         public void Remove(HttpResponseHeader header)
353         {
354             Remove(ResponseHeaderToString(header));
355         }
356
357         public void Set(HttpResponseHeader header, string value)
358         {
359             Set(ResponseHeaderToString(header), value);
360         }
361
362         private static string RequestHeaderToString(HttpRequestHeader value)
363         {
364             switch (value)
365             {
366                 case HttpRequestHeader.CacheControl:
367                     return "cache-control";
368                 case HttpRequestHeader.Connection:
369                     return "connection";
370                 case HttpRequestHeader.Date:
371                     return "date";
372                 case HttpRequestHeader.KeepAlive:
373                     return "keep-alive";
374                 case HttpRequestHeader.Pragma:
375                     return "pragma";
376                 case HttpRequestHeader.Trailer:
377                     return "trailer";
378                 case HttpRequestHeader.TransferEncoding:
379                     return "transfer-encoding";
380                 case HttpRequestHeader.Upgrade:
381                     return "upgrade";
382                 case HttpRequestHeader.Via:
383                     return "via";
384                 case HttpRequestHeader.Warning:
385                     return "warning";
386                 case HttpRequestHeader.Allow:
387                     return "allow";
388                 case HttpRequestHeader.ContentLength:
389                     return "content-length";
390                 case HttpRequestHeader.ContentType:
391                     return "content-type";
392                 case HttpRequestHeader.ContentEncoding:
393                     return "content-encoding";
394                 case HttpRequestHeader.ContentLanguage:
395                     return "content-language";
396                 case HttpRequestHeader.ContentLocation:
397                     return "content-location";
398                 case HttpRequestHeader.ContentMd5:
399                     return "content-md5";
400                 case HttpRequestHeader.ContentRange:
401                     return "content-range";
402                 case HttpRequestHeader.Expires:
403                     return "expires";
404                 case HttpRequestHeader.LastModified:
405                     return "last-modified";
406                 case HttpRequestHeader.Accept:
407                     return "accept";
408                 case HttpRequestHeader.AcceptCharset:
409                     return "accept-charset";
410                 case HttpRequestHeader.AcceptEncoding:
411                     return "accept-encoding";
412                 case HttpRequestHeader.AcceptLanguage:
413                     return "accept-language";
414                 case HttpRequestHeader.Authorization:
415                     return "authorization";
416                 case HttpRequestHeader.Cookie:
417                     return "cookie";
418                 case HttpRequestHeader.Expect:
419                     return "expect";
420                 case HttpRequestHeader.From:
421                     return "from";
422                 case HttpRequestHeader.Host:
423                     return "host";
424                 case HttpRequestHeader.IfMatch:
425                     return "if-match";
426                 case HttpRequestHeader.IfModifiedSince:
427                     return "if-modified-since";
428                 case HttpRequestHeader.IfNoneMatch:
429                     return "if-none-match";
430                 case HttpRequestHeader.IfRange:
431                     return "if-range";
432                 case HttpRequestHeader.IfUnmodifiedSince:
433                     return "if-unmodified-since";
434                 case HttpRequestHeader.MaxForwards:
435                     return "max-forwards";
436                 case HttpRequestHeader.ProxyAuthorization:
437                     return "proxy-authorization";
438                 case HttpRequestHeader.Referer:
439                     return "referer";
440                 case HttpRequestHeader.Range:
441                     return "range";
442                 case HttpRequestHeader.Te:
443                     return "te";
444                 case HttpRequestHeader.Translate:
445                     return "translate";
446                 case HttpRequestHeader.UserAgent:
447                     return "user-agent";
448                 default:
449                     throw new InvalidOperationException();
450             }
451         }
452
453
454         private static string ResponseHeaderToString(HttpResponseHeader value)
455         {
456             switch (value)
457             {
458                 case HttpResponseHeader.CacheControl:
459                     return "cache-control";
460                 case HttpResponseHeader.Connection:
461                     return "connection";
462                 case HttpResponseHeader.Date:
463                     return "date";
464                 case HttpResponseHeader.KeepAlive:
465                     return "keep-alive";
466                 case HttpResponseHeader.Pragma:
467                     return "pragma";
468                 case HttpResponseHeader.Trailer:
469                     return "trailer";
470                 case HttpResponseHeader.TransferEncoding:
471                     return "transfer-encoding";
472                 case HttpResponseHeader.Upgrade:
473                     return "upgrade";
474                 case HttpResponseHeader.Via:
475                     return "via";
476                 case HttpResponseHeader.Warning:
477                     return "warning";
478                 case HttpResponseHeader.Allow:
479                     return "allow";
480                 case HttpResponseHeader.ContentLength:
481                     return "content-length";
482                 case HttpResponseHeader.ContentType:
483                     return "content-type";
484                 case HttpResponseHeader.ContentEncoding:
485                     return "content-encoding";
486                 case HttpResponseHeader.ContentLanguage:
487                     return "content-language";
488                 case HttpResponseHeader.ContentLocation:
489                     return "content-location";
490                 case HttpResponseHeader.ContentMd5:
491                     return "content-md5";
492                 case HttpResponseHeader.ContentRange:
493                     return "content-range";
494                 case HttpResponseHeader.Expires:
495                     return "expires";
496                 case HttpResponseHeader.LastModified:
497                     return "last-modified";
498                 case HttpResponseHeader.AcceptRanges:
499                     return "accept-ranges";
500                 case HttpResponseHeader.Age:
501                     return "age";
502                 case HttpResponseHeader.ETag:
503                     return "etag";
504                 case HttpResponseHeader.Location:
505                     return "location";
506                 case HttpResponseHeader.ProxyAuthenticate:
507                     return "proxy-authenticate";
508                 case HttpResponseHeader.RetryAfter:
509                     return "RetryAfter";
510                 case HttpResponseHeader.Server:
511                     return "server";
512                 case HttpResponseHeader.SetCookie:
513                     return "set-cookie";
514                 case HttpResponseHeader.Vary:
515                     return "vary";
516                 case HttpResponseHeader.WwwAuthenticate:
517                     return "www-authenticate";
518                 default:
519                     throw new InvalidOperationException();
520             }
521         }
522
523         public override IEnumerator GetEnumerator()
524         {
525             return (base.GetEnumerator());
526         }
527
528         // Internal Methods
529
530         // With this we don't check for invalid characters in header. See bug #55994.
531         internal void SetInternal(string header)
532         {
533             var pos = header.IndexOf(':');
534             if (pos == -1)
535                 throw new ArgumentException("no colon found", "header");
536
537             SetInternal(header.Substring(0, pos), header.Substring(pos + 1));
538         }
539
540         internal void SetInternal(string name, string value)
541         {
542             value = value == null ? String.Empty : value.Trim();
543             if (!IsHeaderValue(value))
544                 throw new ArgumentException("invalid header value");
545
546             if (IsMultiValue(name))
547             {
548                 base.Add(name, value);
549             }
550             else
551             {
552                 base.Remove(name);
553                 base.Set(name, value);
554             }
555         }
556
557         internal void RemoveAndAdd(string name, string value)
558         {
559             value = value == null ? String.Empty : value.Trim();
560
561             base.Remove(name);
562             base.Set(name, value);
563         }
564
565         internal void RemoveInternal(string name)
566         {
567             if (name == null)
568                 throw new ArgumentNullException("name");
569             base.Remove(name);
570         }
571
572         internal static bool IsMultiValue(string headerName)
573         {
574             return !string.IsNullOrEmpty(headerName) && _multiValue.ContainsKey(headerName);
575         }
576
577         internal static bool IsHeaderValue(string value)
578         {
579             // TEXT any 8 bit value except CTL's (0-31 and 127)
580             //      but including \r\n space and \t
581             //      after a newline at least one space or \t must follow
582             //      certain header fields allow comments ()
583
584             var len = value.Length;
585             for (var i = 0; i < len; i++)
586             {
587                 var c = value[i];
588                 if (c == 127)
589                     return false;
590                 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
591                     return false;
592                 if (c == '\n' && ++i < len)
593                 {
594                     c = value[i];
595                     if (c != ' ' && c != '\t')
596                         return false;
597                 }
598             }
599
600             return true;
601         }
602
603         internal static bool IsHeaderName(string name)
604         {
605             // token          = 1*<any CHAR except CTLs or tspecials>
606             // tspecials      = "(" | ")" | "<" | ">" | "@"
607             //                | "," | ";" | ":" | "\" | <">
608             //                | "/" | "[" | "]" | "?" | "="
609             //                | "{" | "}" | SP | HT
610
611             if (string.IsNullOrEmpty(name))
612                 return false;
613
614             var len = name.Length;
615             for (var i = 0; i < len; i++)
616             {
617                 var c = name[i];
618                 if (c < 0x20 || c >= 0x7f)
619                     return false;
620             }
621
622             return name.IndexOfAny(tspecials) == -1;
623         }
624     }
625 }