2007-06-19 Dick Porter <dick@ximian.com>
[mono.git] / mcs / class / System / System.Net / 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 using System;
35 using System.Collections;
36 using System.Collections.Specialized;
37 using System.Runtime.InteropServices;
38 using System.Runtime.Serialization;
39 using System.Text;
40     
41 // See RFC 2068 par 4.2 Message Headers
42     
43 namespace System.Net 
44 {
45         [Serializable]
46         [ComVisible(true)]
47         public class WebHeaderCollection : NameValueCollection, ISerializable
48         {
49                 private static readonly Hashtable restricted;
50                 private static readonly Hashtable multiValue;
51                 private bool internallyCreated = false;
52                 
53                 // Static Initializer
54                 
55                 static WebHeaderCollection () 
56                 {
57                         // the list of restricted header names as defined 
58                         // by the ms.net spec
59                         restricted = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
60                                                     CaseInsensitiveComparer.DefaultInvariant);
61
62                         restricted.Add ("accept", true);
63                         restricted.Add ("connection", true);
64                         restricted.Add ("content-length", true);
65                         restricted.Add ("content-type", true);
66                         restricted.Add ("date", true);
67                         restricted.Add ("expect", true);
68                         restricted.Add ("host", true);
69                         restricted.Add ("if-modified-since", true);
70                         restricted.Add ("range", true);
71                         restricted.Add ("referer", true);
72                         restricted.Add ("transfer-encoding", true);
73                         restricted.Add ("user-agent", true);                    
74                         
75                         // see par 14 of RFC 2068 to see which header names
76                         // accept multiple values each separated by a comma
77                         multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
78                                                     CaseInsensitiveComparer.DefaultInvariant);
79
80                         multiValue.Add ("accept", true);
81                         multiValue.Add ("accept-charset", true);
82                         multiValue.Add ("accept-encoding", true);
83                         multiValue.Add ("accept-language", true);
84                         multiValue.Add ("accept-ranges", true);
85                         multiValue.Add ("allow", true);
86                         multiValue.Add ("authorization", true);
87                         multiValue.Add ("cache-control", true);
88                         multiValue.Add ("connection", true);
89                         multiValue.Add ("content-encoding", true);
90                         multiValue.Add ("content-language", true);                      
91                         multiValue.Add ("expect", true);                
92                         multiValue.Add ("if-match", true);
93                         multiValue.Add ("if-none-match", true);
94                         multiValue.Add ("proxy-authenticate", true);
95                         multiValue.Add ("public", true);                        
96                         multiValue.Add ("range", true);
97                         multiValue.Add ("transfer-encoding", true);
98                         multiValue.Add ("upgrade", true);
99                         multiValue.Add ("vary", true);
100                         multiValue.Add ("via", true);
101                         multiValue.Add ("warning", true);
102                         multiValue.Add ("www-authenticate", true);
103
104                         // Extra
105                         multiValue.Add ("set-cookie", true);
106                         multiValue.Add ("set-cookie2", true);
107                 }
108                 
109                 // Constructors
110                 
111                 public WebHeaderCollection () { }       
112                 
113                 protected WebHeaderCollection (SerializationInfo serializationInfo, 
114                                                StreamingContext streamingContext)
115                 {
116                         int count;
117
118                         try {
119                                 count = serializationInfo.GetInt32("Count");
120                                 for (int i = 0; i < count; i++) 
121                                         this.Add (serializationInfo.GetString (i.ToString ()),
122                                                   serializationInfo.GetString ((count + i).ToString ()));
123                         } catch (SerializationException){
124                                 count = serializationInfo.GetInt32("count");
125                                 for (int i = 0; i < count; i++) 
126                                         this.Add (serializationInfo.GetString ("k" + i),
127                                                   serializationInfo.GetString ("v" + i));
128                         }
129                         
130                 }
131                 
132                 internal WebHeaderCollection (bool internallyCreated)
133                 {       
134                         this.internallyCreated = internallyCreated;
135                 }               
136                 
137                 // Methods
138                 
139                 public void Add (string header)
140                 {
141                         if (header == null)
142                                 throw new ArgumentNullException ("header");
143                         int pos = header.IndexOf (':');
144                         if (pos == -1)
145                                 throw new ArgumentException ("no colon found", "header");                               
146                         this.Add (header.Substring (0, pos), 
147                                   header.Substring (pos + 1));
148                 }
149                 
150                 public override void Add (string name, string value)
151                 {
152                         if (name == null)
153                                 throw new ArgumentNullException ("name");
154                         if (internallyCreated && IsRestricted (name))
155                                 throw new ArgumentException ("This header must be modified with the appropiate property.");
156                         this.AddWithoutValidate (name, value);
157                 }
158
159                 protected void AddWithoutValidate (string headerName, string headerValue)
160                 {
161                         if (!IsHeaderName (headerName))
162                                 throw new ArgumentException ("invalid header name: " + headerName, "headerName");
163                         if (headerValue == null)
164                                 headerValue = String.Empty;
165                         else
166                                 headerValue = headerValue.Trim ();
167                         if (!IsHeaderValue (headerValue))
168                                 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
169                         base.Add (headerName, headerValue);                     
170                 }
171
172                 public override string [] GetValues (string header)
173                 {
174                         if (header == null)
175                                 throw new ArgumentNullException ("header");
176
177                         string [] values = base.GetValues (header);
178                         if (values == null || values.Length == 0)
179                                 return null;
180
181                         /*
182                         if (IsMultiValue (header)) {
183                                 values = GetMultipleValues (values);
184                         }
185                         */
186
187                         return values;
188                 }
189
190                 public override string[] GetValues (int index)
191                 {
192                         string[] values = base.GetValues (index);
193                         if (values == null || values.Length == 0) {
194                                 return(null);
195                         }
196                         
197                         return(values);
198                 }
199
200                 /* Now i wonder why this is here...
201                 static string [] GetMultipleValues (string [] values)
202                 {
203                         ArrayList mvalues = new ArrayList (values.Length);
204                         StringBuilder sb = null;
205                         for (int i = 0; i < values.Length; ++i) {
206                                 string val = values [i];
207                                 if (val.IndexOf (',') == -1) {
208                                         mvalues.Add (val);
209                                         continue;
210                                 }
211
212                                 if (sb == null)
213                                         sb = new StringBuilder ();
214
215                                 bool quote = false;
216                                 for (int k = 0; k < val.Length; k++) {
217                                         char c = val [k];
218                                         if (c == '"') {
219                                                 quote = !quote;
220                                         } else if (!quote && c == ',') {
221                                                 mvalues.Add (sb.ToString ().Trim ());
222                                                 sb.Length = 0;
223                                                 continue;
224                                         }
225                                         sb.Append (c);
226                                 }
227
228                                 if (sb.Length > 0) {
229                                         mvalues.Add (sb.ToString ().Trim ());
230                                         sb.Length = 0;
231                                 }
232                         }
233
234                         return (string []) mvalues.ToArray (typeof (string));
235                 }
236                 */
237
238                 public static bool IsRestricted (string headerName)
239                 {
240                         if (headerName == null)
241                                 throw new ArgumentNullException ("headerName");
242
243                         if (headerName == "") // MS throw nullexception here!
244                                 throw new ArgumentException ("empty string", "headerName");
245
246                         return restricted.ContainsKey (headerName);
247                 }
248
249 #if NET_2_0
250                 [MonoNotSupported("")]
251                 public static bool IsRestricted (string headerName, bool response)
252                 {
253                         throw new NotImplementedException ();
254                 }
255 #endif
256
257                 public override void OnDeserialization (object sender)
258                 {
259                 }
260
261                 public override void Remove (string name)
262                 {
263                         if (name == null)
264                                 throw new ArgumentNullException ("name");
265                         if (internallyCreated && IsRestricted (name))
266                                 throw new ArgumentException ("restricted header");
267                         base.Remove (name);
268                 }
269
270                 public override void Set (string name, string value)
271                 {
272                         if (name == null)
273                                 throw new ArgumentNullException ("name");
274                         if (internallyCreated && IsRestricted (name))
275                                 throw new ArgumentException ("restricted header");
276                         if (!IsHeaderName (name))
277                                 throw new ArgumentException ("invalid header name");
278                         if (value == null)
279                                 value = String.Empty;
280                         else
281                                 value = value.Trim ();
282                         if (!IsHeaderValue (value))
283                                 throw new ArgumentException ("invalid header value");
284                         base.Set (name, value);                 
285                 }
286
287                 public byte[] ToByteArray ()
288                 {
289                         return Encoding.UTF8.GetBytes(ToString ());
290                 }
291
292                 public override string ToString ()
293                 {
294                         StringBuilder sb = new StringBuilder();
295
296                         int count = base.Count;
297                         for (int i = 0; i < count ; i++)
298                                 sb.Append (GetKey (i))
299                                   .Append (": ")
300                                   .Append (Get (i))
301                                   .Append ("\r\n");
302                                   
303                         return sb.Append("\r\n").ToString();
304                 }
305
306                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
307                                                   StreamingContext streamingContext)
308                 {
309                         GetObjectData (serializationInfo, streamingContext);
310                 }
311
312                 public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
313                 {
314                         int count = base.Count;
315                         serializationInfo.AddValue ("Count", count);
316                         for (int i = 0; i < count; i++) {
317                                 serializationInfo.AddValue (i.ToString (), GetKey (i));
318                                 serializationInfo.AddValue ((count + i).ToString (), Get (i));
319                         }
320                 }
321
322                 public override string[] AllKeys
323                 {
324                         get {
325                                 return(base.AllKeys);
326                         }
327                 }
328                 
329                 public override int Count 
330                 {
331                         get {
332                                 return(base.Count);
333                         }
334                 }
335
336                 public override KeysCollection Keys
337                 {
338                         get {
339                                 return(base.Keys);
340                         }
341                 }
342
343                 public override string Get (int index)
344                 {
345                         return(base.Get (index));
346                 }
347                 
348                 public override string Get (string name)
349                 {
350                         return(base.Get (name));
351                 }
352                 
353                 public override string GetKey (int index)
354                 {
355                         return(base.GetKey (index));
356                 }
357
358 #if NET_2_0
359                 public void Add (HttpRequestHeader header, string value)
360                 {
361                         Add (RequestHeaderToString (header), value);
362                 }
363
364                 public void Remove (HttpRequestHeader header)
365                 {
366                         Remove (RequestHeaderToString (header));
367                 }
368
369                 public void Set (HttpRequestHeader header, string value)
370                 {
371                         Set (RequestHeaderToString (header), value);
372                 }
373
374                 public void Add (HttpResponseHeader header, string value)
375                 {
376                         Add (ResponseHeaderToString (header), value);
377                 }
378
379                 public void Remove (HttpResponseHeader header)
380                 {
381                         Remove (ResponseHeaderToString (header));
382                 }
383
384                 public void Set (HttpResponseHeader header, string value)
385                 {
386                         Set (ResponseHeaderToString (header), value);
387                 }
388
389                 string RequestHeaderToString (HttpRequestHeader value)
390                 {
391                         switch (value){
392                         case HttpRequestHeader.CacheControl:
393                                 return "cache-control";
394                         case HttpRequestHeader.Connection:
395                                 return "connection";
396                         case HttpRequestHeader.Date:
397                                 return "date";
398                         case HttpRequestHeader.KeepAlive:
399                                 return "keep-alive";
400                         case HttpRequestHeader.Pragma:
401                                 return "pragma";
402                         case HttpRequestHeader.Trailer:
403                                 return "trailer";
404                         case HttpRequestHeader.TransferEncoding:
405                                 return "transfer-encoding";
406                         case HttpRequestHeader.Upgrade:
407                                 return "upgrade";
408                         case HttpRequestHeader.Via:
409                                 return "via";
410                         case HttpRequestHeader.Warning:
411                                 return "warning";
412                         case HttpRequestHeader.Allow:
413                                 return "allow";
414                         case HttpRequestHeader.ContentLength:
415                                 return "content-length";
416                         case HttpRequestHeader.ContentType:
417                                 return "content-type";
418                         case HttpRequestHeader.ContentEncoding:
419                                 return "content-encoding";
420                         case HttpRequestHeader.ContentLanguage:
421                                 return "content-language";
422                         case HttpRequestHeader.ContentLocation:
423                                 return "content-location";
424                         case HttpRequestHeader.ContentMd5:
425                                 return "content-md5";
426                         case HttpRequestHeader.ContentRange:
427                                 return "content-range";
428                         case HttpRequestHeader.Expires:
429                                 return "expires";
430                         case HttpRequestHeader.LastModified:
431                                 return "last-modified";
432                         case HttpRequestHeader.Accept:
433                                 return "accept";
434                         case HttpRequestHeader.AcceptCharset:
435                                 return "accept-charset";
436                         case HttpRequestHeader.AcceptEncoding:
437                                 return "accept-encoding";
438                         case HttpRequestHeader.AcceptLanguage:
439                                 return "accept-language";
440                         case HttpRequestHeader.Authorization:
441                                 return "authorization";
442                         case HttpRequestHeader.Cookie:
443                                 return "cookie";
444                         case HttpRequestHeader.Expect:
445                                 return "expect";
446                         case HttpRequestHeader.From:
447                                 return "from";
448                         case HttpRequestHeader.Host:
449                                 return "host";
450                         case HttpRequestHeader.IfMatch:
451                                 return "if-match";
452                         case HttpRequestHeader.IfModifiedSince:
453                                 return "if-modified-since";
454                         case HttpRequestHeader.IfNoneMatch:
455                                 return "if-none-match";
456                         case HttpRequestHeader.IfRange:
457                                 return "if-range";
458                         case HttpRequestHeader.IfUnmodifiedSince:
459                                 return "if-unmodified-since";
460                         case HttpRequestHeader.MaxForwards:
461                                 return "max-forwards";
462                         case HttpRequestHeader.ProxyAuthorization:
463                                 return "proxy-authorization";
464                         case HttpRequestHeader.Referer:
465                                 return "referer";
466                         case HttpRequestHeader.Range:
467                                 return "range";
468                         case HttpRequestHeader.Te:
469                                 return "te";
470                         case HttpRequestHeader.Translate:
471                                 return "translate";
472                         case HttpRequestHeader.UserAgent:
473                                 return "user-agent";
474                         default:
475                                 throw new InvalidOperationException ();
476                         }
477                 }
478                 
479                 
480                 public string this[HttpRequestHeader hrh]
481                 {
482                         get {
483                                 return Get (RequestHeaderToString (hrh));
484                         }
485                         
486                         set {
487                                 Add (RequestHeaderToString (hrh), value);
488                         }
489                 }
490
491                 string ResponseHeaderToString (HttpResponseHeader value)
492                 {
493                         switch (value){
494                         case HttpResponseHeader.CacheControl:
495                                 return "cache-control";
496                         case HttpResponseHeader.Connection:
497                                 return "connection";
498                         case HttpResponseHeader.Date:
499                                 return "date";
500                         case HttpResponseHeader.KeepAlive:
501                                 return "keep-alive";
502                         case HttpResponseHeader.Pragma:
503                                 return "pragma";
504                         case HttpResponseHeader.Trailer:
505                                 return "trailer";
506                         case HttpResponseHeader.TransferEncoding:
507                                 return "transfer-encoding";
508                         case HttpResponseHeader.Upgrade:
509                                 return "upgrade";
510                         case HttpResponseHeader.Via:
511                                 return "via";
512                         case HttpResponseHeader.Warning:
513                                 return "warning";
514                         case HttpResponseHeader.Allow:
515                                 return "allow";
516                         case HttpResponseHeader.ContentLength:
517                                 return "content-length";
518                         case HttpResponseHeader.ContentType:
519                                 return "content-type";
520                         case HttpResponseHeader.ContentEncoding:
521                                 return "content-encoding";
522                         case HttpResponseHeader.ContentLanguage:
523                                 return "content-language";
524                         case HttpResponseHeader.ContentLocation:
525                                 return "content-location";
526                         case HttpResponseHeader.ContentMd5:
527                                 return "content-md5";
528                         case HttpResponseHeader.ContentRange:
529                                 return "content-range";
530                         case HttpResponseHeader.Expires:
531                                 return "expires";
532                         case HttpResponseHeader.LastModified:
533                                 return "last-modified";
534                         case HttpResponseHeader.AcceptRanges:
535                                 return "accept-ranges";
536                         case HttpResponseHeader.Age:
537                                 return "age";
538                         case HttpResponseHeader.ETag:
539                                 return "etag";
540                         case HttpResponseHeader.Location:
541                                 return "location";
542                         case HttpResponseHeader.ProxyAuthenticate:
543                                 return "proxy-authenticate";
544                         case HttpResponseHeader.RetryAfter:
545                                 return "RetryAfter";
546                         case HttpResponseHeader.Server:
547                                 return "server";
548                         case HttpResponseHeader.SetCookie:
549                                 return "set-cookie";
550                         case HttpResponseHeader.Vary:
551                                 return "vary";
552                         case HttpResponseHeader.WwwAuthenticate:
553                                 return "www-authenticate";
554                         default:
555                                 throw new InvalidOperationException ();
556                         }
557                 }
558                 public string this[HttpResponseHeader hrh]
559                 {
560                         get
561                         {
562                                 return Get (ResponseHeaderToString (hrh));
563                         }
564
565                         set
566                         {
567                                 Add (ResponseHeaderToString (hrh), value);
568                         }
569                 }
570
571                 public override void Clear ()
572                 {
573                         base.Clear ();
574                 }
575
576
577                 public override IEnumerator GetEnumerator ()
578                 {
579                         return(base.GetEnumerator ());
580                 }
581 #endif
582
583                 // Internal Methods
584                 
585                 // With this we don't check for invalid characters in header. See bug #55994.
586                 internal void SetInternal (string header)
587                 {
588                         int pos = header.IndexOf (':');
589                         if (pos == -1)
590                                 throw new ArgumentException ("no colon found", "header");                               
591
592                         SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
593                 }
594
595                 internal void SetInternal (string name, string value)
596                 {
597                         if (value == null)
598                                 value = String.Empty;
599                         else
600                                 value = value.Trim ();
601                         if (!IsHeaderValue (value))
602                                 throw new ArgumentException ("invalid header value");
603
604                         if (IsMultiValue (name)) {
605                                 base.Add (name, value);
606                         } else {
607                                 base.Remove (name);
608                                 base.Set (name, value); 
609                         }
610                 }
611
612                 internal void RemoveAndAdd (string name, string value)
613                 {
614                         if (value == null)
615                                 value = String.Empty;
616                         else
617                                 value = value.Trim ();
618
619                         base.Remove (name);
620                         base.Set (name, value);
621                 }
622
623                 internal void RemoveInternal (string name)
624                 {
625                         if (name == null)
626                                 throw new ArgumentNullException ("name");
627                         base.Remove (name);
628                 }               
629                 
630                 // Private Methods
631                 
632                 internal static bool IsMultiValue (string headerName)
633                 {
634                         if (headerName == null || headerName == "")
635                                 return false;
636
637                         return multiValue.ContainsKey (headerName);
638                 }               
639                 
640                 internal static bool IsHeaderValue (string value)
641                 {
642                         // TEXT any 8 bit value except CTL's (0-31 and 127)
643                         //      but including \r\n space and \t
644                         //      after a newline at least one space or \t must follow
645                         //      certain header fields allow comments ()
646                                 
647                         int len = value.Length;
648                         for (int i = 0; i < len; i++) {                 
649                                 char c = value [i];
650                                 if (c == 127)
651                                         return false;
652                                 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
653                                         return false;
654                                 if (c == '\n' && ++i < len) {
655                                         c = value [i];
656                                         if (c != ' ' && c != '\t')
657                                                 return false;
658                                 }
659                         }
660                         
661                         return true;
662                 }
663                 
664                 internal static bool IsHeaderName (string name)
665                 {
666                         // token          = 1*<any CHAR except CTLs or tspecials>
667                         // tspecials      = "(" | ")" | "<" | ">" | "@"
668                         //                | "," | ";" | ":" | "\" | <">
669                         //                | "/" | "[" | "]" | "?" | "="
670                         //                | "{" | "}" | SP | HT
671                         
672                         if (name == null || name.Length == 0)
673                                 return false;
674
675                         int len = name.Length;
676                         for (int i = 0; i < len; i++) {                 
677                                 char c = name [i];
678                                 if (c < 0x20 || c >= 0x7f)
679                                         return false;
680                         }
681                         
682                         return name.IndexOfAny (tspecials) == -1;
683                 }
684
685                 private static char [] tspecials = 
686                                 new char [] {'(', ')', '<', '>', '@',
687                                              ',', ';', ':', '\\', '"',
688                                              '/', '[', ']', '?', '=',
689                                              '{', '}', ' ', '\t'};
690                                                         
691         }
692 }
693
694