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