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