2007-04-17 Joel Reed <joelwreed@gmail.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.Default,
60                                                     CaseInsensitiveComparer.Default);
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.Default,
78                                                     CaseInsensitiveComparer.Default);
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 = serializationInfo.GetInt32("Count");
117                         for (int i = 0; i < count; i++) 
118                                 this.Add (serializationInfo.GetString (i.ToString ()),
119                                         serializationInfo.GetString ((count + i).ToString ()));
120                 }
121                 
122                 internal WebHeaderCollection (bool internallyCreated)
123                 {       
124                         this.internallyCreated = internallyCreated;
125                 }               
126                 
127                 // Methods
128                 
129                 public void Add (string header)
130                 {
131                         if (header == null)
132                                 throw new ArgumentNullException ("header");
133                         int pos = header.IndexOf (':');
134                         if (pos == -1)
135                                 throw new ArgumentException ("no colon found", "header");                               
136                         this.Add (header.Substring (0, pos), 
137                                   header.Substring (pos + 1));
138                 }
139                 
140                 public override void Add (string name, string value)
141                 {
142                         if (name == null)
143                                 throw new ArgumentNullException ("name");
144                         if (internallyCreated && IsRestricted (name))
145                                 throw new ArgumentException ("This header must be modified with the appropiate property.");
146                         this.AddWithoutValidate (name, value);
147                 }
148
149                 protected void AddWithoutValidate (string headerName, string headerValue)
150                 {
151                         if (!IsHeaderName (headerName))
152                                 throw new ArgumentException ("invalid header name: " + headerName, "headerName");
153                         if (headerValue == null)
154                                 headerValue = String.Empty;
155                         else
156                                 headerValue = headerValue.Trim ();
157                         if (!IsHeaderValue (headerValue))
158                                 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
159                         base.Add (headerName, headerValue);                     
160                 }
161
162                 public override string [] GetValues (string header)
163                 {
164                         if (header == null)
165                                 throw new ArgumentNullException ("header");
166
167                         string [] values = base.GetValues (header);
168                         if (values == null || values.Length == 0)
169                                 return null;
170
171                         /*
172                         if (IsMultiValue (header)) {
173                                 values = GetMultipleValues (values);
174                         }
175                         */
176
177                         return values;
178                 }
179
180                 /* Now i wonder why this is here...
181                 static string [] GetMultipleValues (string [] values)
182                 {
183                         ArrayList mvalues = new ArrayList (values.Length);
184                         StringBuilder sb = null;
185                         for (int i = 0; i < values.Length; ++i) {
186                                 string val = values [i];
187                                 if (val.IndexOf (',') == -1) {
188                                         mvalues.Add (val);
189                                         continue;
190                                 }
191
192                                 if (sb == null)
193                                         sb = new StringBuilder ();
194
195                                 bool quote = false;
196                                 for (int k = 0; k < val.Length; k++) {
197                                         char c = val [k];
198                                         if (c == '"') {
199                                                 quote = !quote;
200                                         } else if (!quote && c == ',') {
201                                                 mvalues.Add (sb.ToString ().Trim ());
202                                                 sb.Length = 0;
203                                                 continue;
204                                         }
205                                         sb.Append (c);
206                                 }
207
208                                 if (sb.Length > 0) {
209                                         mvalues.Add (sb.ToString ().Trim ());
210                                         sb.Length = 0;
211                                 }
212                         }
213
214                         return (string []) mvalues.ToArray (typeof (string));
215                 }
216                 */
217
218                 public static bool IsRestricted (string headerName)
219                 {
220                         if (headerName == null)
221                                 throw new ArgumentNullException ("headerName");
222
223                         if (headerName == "") // MS throw nullexception here!
224                                 throw new ArgumentException ("empty string", "headerName");
225
226                         return restricted.ContainsKey (headerName);
227                 }
228
229                 public override void OnDeserialization (object sender)
230                 {
231                 }
232
233                 public override void Remove (string name)
234                 {
235                         if (name == null)
236                                 throw new ArgumentNullException ("name");
237                         if (internallyCreated && IsRestricted (name))
238                                 throw new ArgumentException ("restricted header");
239                         base.Remove (name);
240                 }
241
242                 public override void Set (string name, string value)
243                 {
244                         if (name == null)
245                                 throw new ArgumentNullException ("name");
246                         if (internallyCreated && IsRestricted (name))
247                                 throw new ArgumentException ("restricted header");
248                         if (!IsHeaderName (name))
249                                 throw new ArgumentException ("invalid header name");
250                         if (value == null)
251                                 value = String.Empty;
252                         else
253                                 value = value.Trim ();
254                         if (!IsHeaderValue (value))
255                                 throw new ArgumentException ("invalid header value");
256                         base.Set (name, value);                 
257                 }
258
259                 public byte[] ToByteArray ()
260                 {
261                         return Encoding.UTF8.GetBytes(ToString ());
262                 }
263
264                 public override string ToString ()
265                 {
266                         StringBuilder sb = new StringBuilder();
267
268                         int count = base.Count;
269                         for (int i = 0; i < count ; i++)
270                                 sb.Append (GetKey (i))
271                                   .Append (": ")
272                                   .Append (Get (i))
273                                   .Append ("\r\n");
274                                   
275                         return sb.Append("\r\n").ToString();
276                 }
277
278                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
279                                                   StreamingContext streamingContext)
280                 {
281                         int count = base.Count;
282                         serializationInfo.AddValue ("Count", count);
283                         for (int i = 0; i < count; i++) {
284                                 serializationInfo.AddValue (i.ToString (), GetKey (i));
285                                 serializationInfo.AddValue ((count + i).ToString (), Get (i));
286                         }
287                 }
288
289 #if NET_2_0
290                 string RequestHeaderToString (HttpRequestHeader value)
291                 {
292                         switch (value){
293                         case HttpRequestHeader.CacheControl:
294                                 return "cache-control";
295                         case HttpRequestHeader.Connection:
296                                 return "connection";
297                         case HttpRequestHeader.Date:
298                                 return "date";
299                         case HttpRequestHeader.KeepAlive:
300                                 return "keep-alive";
301                         case HttpRequestHeader.Pragma:
302                                 return "pragma";
303                         case HttpRequestHeader.Trailer:
304                                 return "trailer";
305                         case HttpRequestHeader.TransferEncoding:
306                                 return "transfer-encoding";
307                         case HttpRequestHeader.Upgrade:
308                                 return "upgrade";
309                         case HttpRequestHeader.Via:
310                                 return "via";
311                         case HttpRequestHeader.Warning:
312                                 return "warning";
313                         case HttpRequestHeader.Allow:
314                                 return "allow";
315                         case HttpRequestHeader.ContentLength:
316                                 return "content-length";
317                         case HttpRequestHeader.ContentType:
318                                 return "content-type";
319                         case HttpRequestHeader.ContentEncoding:
320                                 return "content-encoding";
321                         case HttpRequestHeader.ContentLanguage:
322                                 return "content-language";
323                         case HttpRequestHeader.ContentLocation:
324                                 return "content-location";
325                         case HttpRequestHeader.ContentMd5:
326                                 return "content-md5";
327                         case HttpRequestHeader.ContentRange:
328                                 return "content-range";
329                         case HttpRequestHeader.Expires:
330                                 return "expires";
331                         case HttpRequestHeader.LastModified:
332                                 return "last-modified";
333                         case HttpRequestHeader.Accept:
334                                 return "accept";
335                         case HttpRequestHeader.AcceptCharset:
336                                 return "accept-charset";
337                         case HttpRequestHeader.AcceptEncoding:
338                                 return "accept-encoding";
339                         case HttpRequestHeader.AcceptLanguage:
340                                 return "accept-language";
341                         case HttpRequestHeader.Authorization:
342                                 return "authorization";
343                         case HttpRequestHeader.Cookie:
344                                 return "cookie";
345                         case HttpRequestHeader.Expect:
346                                 return "expect";
347                         case HttpRequestHeader.From:
348                                 return "from";
349                         case HttpRequestHeader.Host:
350                                 return "host";
351                         case HttpRequestHeader.IfMatch:
352                                 return "if-match";
353                         case HttpRequestHeader.IfModifiedSince:
354                                 return "if-modified-since";
355                         case HttpRequestHeader.IfNoneMatch:
356                                 return "if-none-match";
357                         case HttpRequestHeader.IfRange:
358                                 return "if-range";
359                         case HttpRequestHeader.IfUnmodifiedSince:
360                                 return "if-unmodified-since";
361                         case HttpRequestHeader.MaxForwards:
362                                 return "max-forwards";
363                         case HttpRequestHeader.ProxyAuthorization:
364                                 return "proxy-authorization";
365                         case HttpRequestHeader.Referer:
366                                 return "referer";
367                         case HttpRequestHeader.Range:
368                                 return "range";
369                         case HttpRequestHeader.Te:
370                                 return "te";
371                         case HttpRequestHeader.Translate:
372                                 return "translate";
373                         case HttpRequestHeader.UserAgent:
374                                 return "user-agent";
375                         default:
376                                 throw new InvalidOperationException ();
377                         }
378                 }
379                 
380                 public string this[HttpRequestHeader hrh]
381                 {
382                         get {
383                                 return Get (RequestHeaderToString (hrh));
384                         }
385                         
386                         set {
387                                 Add (RequestHeaderToString (hrh), value);
388                         }
389                 }
390
391                 string ResponseHeaderToString (HttpResponseHeader value)
392                 {
393                         switch (value){
394                         case HttpResponseHeader.CacheControl:
395                                 return "cache-control";
396                         case HttpResponseHeader.Connection:
397                                 return "connection";
398                         case HttpResponseHeader.Date:
399                                 return "date";
400                         case HttpResponseHeader.KeepAlive:
401                                 return "keep-alive";
402                         case HttpResponseHeader.Pragma:
403                                 return "pragma";
404                         case HttpResponseHeader.Trailer:
405                                 return "trailer";
406                         case HttpResponseHeader.TransferEncoding:
407                                 return "transfer-encoding";
408                         case HttpResponseHeader.Upgrade:
409                                 return "upgrade";
410                         case HttpResponseHeader.Via:
411                                 return "via";
412                         case HttpResponseHeader.Warning:
413                                 return "warning";
414                         case HttpResponseHeader.Allow:
415                                 return "allow";
416                         case HttpResponseHeader.ContentLength:
417                                 return "content-length";
418                         case HttpResponseHeader.ContentType:
419                                 return "content-type";
420                         case HttpResponseHeader.ContentEncoding:
421                                 return "content-encoding";
422                         case HttpResponseHeader.ContentLanguage:
423                                 return "content-language";
424                         case HttpResponseHeader.ContentLocation:
425                                 return "content-location";
426                         case HttpResponseHeader.ContentMd5:
427                                 return "content-md5";
428                         case HttpResponseHeader.ContentRange:
429                                 return "content-range";
430                         case HttpResponseHeader.Expires:
431                                 return "expires";
432                         case HttpResponseHeader.LastModified:
433                                 return "last-modified";
434                         case HttpResponseHeader.AcceptRanges:
435                                 return "accept-ranges";
436                         case HttpResponseHeader.Age:
437                                 return "age";
438                         case HttpResponseHeader.ETag:
439                                 return "etag";
440                         case HttpResponseHeader.Location:
441                                 return "location";
442                         case HttpResponseHeader.ProxyAuthenticate:
443                                 return "proxy-authenticate";
444                         case HttpResponseHeader.RetryAfter:
445                                 return "RetryAfter";
446                         case HttpResponseHeader.Server:
447                                 return "server";
448                         case HttpResponseHeader.SetCookie:
449                                 return "set-cookie";
450                         case HttpResponseHeader.Vary:
451                                 return "vary";
452                         case HttpResponseHeader.WwwAuthenticate:
453                                 return "www-authenticate";
454                         default:
455                                 throw new InvalidOperationException ();
456                         }
457                 }
458                 public string this[HttpResponseHeader hrh]
459                 {
460                         get
461                         {
462                                 return Get (ResponseHeaderToString (hrh));
463                         }
464
465                         set
466                         {
467                                 Add (ResponseHeaderToString (hrh), value);
468                         }
469                 }
470 #endif
471
472                 // Internal Methods
473                 
474                 // With this we don't check for invalid characters in header. See bug #55994.
475                 internal void SetInternal (string header)
476                 {
477                         int pos = header.IndexOf (':');
478                         if (pos == -1)
479                                 throw new ArgumentException ("no colon found", "header");                               
480
481                         SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
482                 }
483
484                 internal void SetInternal (string name, string value)
485                 {
486                         if (value == null)
487                                 value = String.Empty;
488                         else
489                                 value = value.Trim ();
490                         if (!IsHeaderValue (value))
491                                 throw new ArgumentException ("invalid header value");
492
493                         if (IsMultiValue (name)) {
494                                 base.Add (name, value);
495                         } else {
496                                 base.Remove (name);
497                                 base.Set (name, value); 
498                         }
499                 }
500
501                 internal void RemoveAndAdd (string name, string value)
502                 {
503                         if (value == null)
504                                 value = String.Empty;
505                         else
506                                 value = value.Trim ();
507
508                         base.Remove (name);
509                         base.Set (name, value);
510                 }
511
512                 internal void RemoveInternal (string name)
513                 {
514                         if (name == null)
515                                 throw new ArgumentNullException ("name");
516                         base.Remove (name);
517                 }               
518                 
519                 // Private Methods
520                 
521                 internal static bool IsMultiValue (string headerName)
522                 {
523                         if (headerName == null || headerName == "")
524                                 return false;
525
526                         return multiValue.ContainsKey (headerName);
527                 }               
528                 
529                 internal static bool IsHeaderValue (string value)
530                 {
531                         // TEXT any 8 bit value except CTL's (0-31 and 127)
532                         //      but including \r\n space and \t
533                         //      after a newline at least one space or \t must follow
534                         //      certain header fields allow comments ()
535                                 
536                         int len = value.Length;
537                         for (int i = 0; i < len; i++) {                 
538                                 char c = value [i];
539                                 if (c == 127)
540                                         return false;
541                                 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
542                                         return false;
543                                 if (c == '\n' && ++i < len) {
544                                         c = value [i];
545                                         if (c != ' ' && c != '\t')
546                                                 return false;
547                                 }
548                         }
549                         
550                         return true;
551                 }
552                 
553                 internal static bool IsHeaderName (string name)
554                 {
555                         // token          = 1*<any CHAR except CTLs or tspecials>
556                         // tspecials      = "(" | ")" | "<" | ">" | "@"
557                         //                | "," | ";" | ":" | "\" | <">
558                         //                | "/" | "[" | "]" | "?" | "="
559                         //                | "{" | "}" | SP | HT
560                         
561                         if (name == null || name.Length == 0)
562                                 return false;
563
564                         int len = name.Length;
565                         for (int i = 0; i < len; i++) {                 
566                                 char c = name [i];
567                                 if (c < 0x20 || c >= 0x7f)
568                                         return false;
569                         }
570                         
571                         return name.IndexOfAny (tspecials) == -1;
572                 }
573
574                 private static char [] tspecials = 
575                                 new char [] {'(', ')', '<', '>', '@',
576                                              ',', ';', ':', '\\', '"',
577                                              '/', '[', ']', '?', '=',
578                                              '{', '}', ' ', '\t'};
579                                                         
580         }
581 }
582