2 // System.Net.WebHeaderCollection
5 // Lawrence Pit (loz@cable.a2000.nl)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // Miguel de Icaza (miguel@novell.com)
9 // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
10 // Copyright 2007 Novell, Inc. (http://www.novell.com)
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:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
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.
35 using System.Collections;
37 using System.Collections.Generic;
39 using System.Collections.Specialized;
40 using System.Runtime.InteropServices;
41 using System.Runtime.Serialization;
44 // See RFC 2068 par 4.2 Message Headers
50 public class WebHeaderCollection : NameValueCollection, ISerializable
52 private static readonly Hashtable restricted;
53 private static readonly Hashtable multiValue;
55 static readonly Dictionary<string, bool> restricted_response;
57 private bool internallyCreated = false;
61 static WebHeaderCollection ()
63 // the list of restricted header names as defined
65 restricted = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
66 CaseInsensitiveComparer.DefaultInvariant);
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);
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);
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);
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);
119 multiValue.Add ("set-cookie", true);
120 multiValue.Add ("set-cookie2", true);
125 public WebHeaderCollection () { }
127 protected WebHeaderCollection (SerializationInfo serializationInfo,
128 StreamingContext streamingContext)
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));
146 internal WebHeaderCollection (bool internallyCreated)
148 this.internallyCreated = internallyCreated;
153 public void Add (string header)
156 throw new ArgumentNullException ("header");
157 int pos = header.IndexOf (':');
159 throw new ArgumentException ("no colon found", "header");
160 this.Add (header.Substring (0, pos),
161 header.Substring (pos + 1));
164 public override void Add (string name, string value)
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);
173 protected void AddWithoutValidate (string headerName, string headerValue)
175 if (!IsHeaderName (headerName))
176 throw new ArgumentException ("invalid header name: " + headerName, "headerName");
177 if (headerValue == null)
178 headerValue = String.Empty;
180 headerValue = headerValue.Trim ();
181 if (!IsHeaderValue (headerValue))
182 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
183 base.Add (headerName, headerValue);
186 public override string [] GetValues (string header)
189 throw new ArgumentNullException ("header");
191 string [] values = base.GetValues (header);
192 if (values == null || values.Length == 0)
196 if (IsMultiValue (header)) {
197 values = GetMultipleValues (values);
204 public override string[] GetValues (int index)
206 string[] values = base.GetValues (index);
207 if (values == null || values.Length == 0) {
214 /* Now i wonder why this is here...
215 static string [] GetMultipleValues (string [] values)
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) {
227 sb = new StringBuilder ();
230 for (int k = 0; k < val.Length; k++) {
234 } else if (!quote && c == ',') {
235 mvalues.Add (sb.ToString ().Trim ());
243 mvalues.Add (sb.ToString ().Trim ());
248 return (string []) mvalues.ToArray (typeof (string));
252 public static bool IsRestricted (string headerName)
254 if (headerName == null)
255 throw new ArgumentNullException ("headerName");
257 if (headerName == "") // MS throw nullexception here!
258 throw new ArgumentException ("empty string", "headerName");
260 if (!IsHeaderName (headerName))
261 throw new ArgumentException ("Invalid character in header");
263 return restricted.ContainsKey (headerName);
267 public static bool IsRestricted (string headerName, bool response)
269 if (String.IsNullOrEmpty (headerName))
270 throw new ArgumentNullException ("headerName");
272 if (!IsHeaderName (headerName))
273 throw new ArgumentException ("Invalid character in header");
277 return restricted_response.ContainsKey (headerName);
278 return restricted.ContainsKey (headerName);
282 public override void OnDeserialization (object sender)
286 public override void Remove (string name)
289 throw new ArgumentNullException ("name");
290 if (internallyCreated && IsRestricted (name))
291 throw new ArgumentException ("restricted header");
295 public override void Set (string name, string value)
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");
304 value = String.Empty;
306 value = value.Trim ();
307 if (!IsHeaderValue (value))
308 throw new ArgumentException ("invalid header value");
309 base.Set (name, value);
312 public byte[] ToByteArray ()
314 return Encoding.UTF8.GetBytes(ToString ());
317 public override string ToString ()
319 StringBuilder sb = new StringBuilder();
321 int count = base.Count;
322 for (int i = 0; i < count ; i++)
323 sb.Append (GetKey (i))
328 return sb.Append("\r\n").ToString();
331 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
332 StreamingContext streamingContext)
334 GetObjectData (serializationInfo, streamingContext);
337 public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
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));
347 public override string[] AllKeys
350 return(base.AllKeys);
354 public override int Count
361 public override KeysCollection Keys
368 public override string Get (int index)
370 return(base.Get (index));
373 public override string Get (string name)
375 return(base.Get (name));
378 public override string GetKey (int index)
380 return(base.GetKey (index));
384 public void Add (HttpRequestHeader header, string value)
386 Add (RequestHeaderToString (header), value);
389 public void Remove (HttpRequestHeader header)
391 Remove (RequestHeaderToString (header));
394 public void Set (HttpRequestHeader header, string value)
396 Set (RequestHeaderToString (header), value);
399 public void Add (HttpResponseHeader header, string value)
401 Add (ResponseHeaderToString (header), value);
404 public void Remove (HttpResponseHeader header)
406 Remove (ResponseHeaderToString (header));
409 public void Set (HttpResponseHeader header, string value)
411 Set (ResponseHeaderToString (header), value);
414 string RequestHeaderToString (HttpRequestHeader value)
417 case HttpRequestHeader.CacheControl:
418 return "cache-control";
419 case HttpRequestHeader.Connection:
421 case HttpRequestHeader.Date:
423 case HttpRequestHeader.KeepAlive:
425 case HttpRequestHeader.Pragma:
427 case HttpRequestHeader.Trailer:
429 case HttpRequestHeader.TransferEncoding:
430 return "transfer-encoding";
431 case HttpRequestHeader.Upgrade:
433 case HttpRequestHeader.Via:
435 case HttpRequestHeader.Warning:
437 case HttpRequestHeader.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:
455 case HttpRequestHeader.LastModified:
456 return "last-modified";
457 case HttpRequestHeader.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:
469 case HttpRequestHeader.Expect:
471 case HttpRequestHeader.From:
473 case HttpRequestHeader.Host:
475 case HttpRequestHeader.IfMatch:
477 case HttpRequestHeader.IfModifiedSince:
478 return "if-modified-since";
479 case HttpRequestHeader.IfNoneMatch:
480 return "if-none-match";
481 case HttpRequestHeader.IfRange:
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:
491 case HttpRequestHeader.Range:
493 case HttpRequestHeader.Te:
495 case HttpRequestHeader.Translate:
497 case HttpRequestHeader.UserAgent:
500 throw new InvalidOperationException ();
505 public string this[HttpRequestHeader hrh]
508 return Get (RequestHeaderToString (hrh));
512 Add (RequestHeaderToString (hrh), value);
516 string ResponseHeaderToString (HttpResponseHeader value)
519 case HttpResponseHeader.CacheControl:
520 return "cache-control";
521 case HttpResponseHeader.Connection:
523 case HttpResponseHeader.Date:
525 case HttpResponseHeader.KeepAlive:
527 case HttpResponseHeader.Pragma:
529 case HttpResponseHeader.Trailer:
531 case HttpResponseHeader.TransferEncoding:
532 return "transfer-encoding";
533 case HttpResponseHeader.Upgrade:
535 case HttpResponseHeader.Via:
537 case HttpResponseHeader.Warning:
539 case HttpResponseHeader.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:
557 case HttpResponseHeader.LastModified:
558 return "last-modified";
559 case HttpResponseHeader.AcceptRanges:
560 return "accept-ranges";
561 case HttpResponseHeader.Age:
563 case HttpResponseHeader.ETag:
565 case HttpResponseHeader.Location:
567 case HttpResponseHeader.ProxyAuthenticate:
568 return "proxy-authenticate";
569 case HttpResponseHeader.RetryAfter:
571 case HttpResponseHeader.Server:
573 case HttpResponseHeader.SetCookie:
575 case HttpResponseHeader.Vary:
577 case HttpResponseHeader.WwwAuthenticate:
578 return "www-authenticate";
580 throw new InvalidOperationException ();
583 public string this[HttpResponseHeader hrh]
587 return Get (ResponseHeaderToString (hrh));
592 Add (ResponseHeaderToString (hrh), value);
596 public override void Clear ()
602 public override IEnumerator GetEnumerator ()
604 return(base.GetEnumerator ());
610 // With this we don't check for invalid characters in header. See bug #55994.
611 internal void SetInternal (string header)
613 int pos = header.IndexOf (':');
615 throw new ArgumentException ("no colon found", "header");
617 SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
620 internal void SetInternal (string name, string value)
623 value = String.Empty;
625 value = value.Trim ();
626 if (!IsHeaderValue (value))
627 throw new ArgumentException ("invalid header value");
629 if (IsMultiValue (name)) {
630 base.Add (name, value);
633 base.Set (name, value);
637 internal void RemoveAndAdd (string name, string value)
640 value = String.Empty;
642 value = value.Trim ();
645 base.Set (name, value);
648 internal void RemoveInternal (string name)
651 throw new ArgumentNullException ("name");
657 internal static bool IsMultiValue (string headerName)
659 if (headerName == null || headerName == "")
662 return multiValue.ContainsKey (headerName);
665 internal static bool IsHeaderValue (string value)
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 ()
672 int len = value.Length;
673 for (int i = 0; i < len; i++) {
677 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
679 if (c == '\n' && ++i < len) {
681 if (c != ' ' && c != '\t')
689 internal static bool IsHeaderName (string name)
691 if (name == null || name.Length == 0)
694 int len = name.Length;
695 for (int i = 0; i < len; i++) {
697 if (c > 126 || !allowed_chars [(int) c])
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,