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