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;
36 using System.Collections.Generic;
37 using System.Collections.Specialized;
38 using System.Runtime.InteropServices;
39 using System.Runtime.Serialization;
42 // See RFC 2068 par 4.2 Message Headers
47 internal class WebHeaderCollection : NameValueCollection, ISerializable {
51 public class WebHeaderCollection : NameValueCollection, ISerializable {
53 private static readonly Hashtable restricted;
54 private static readonly Hashtable multiValue;
55 static readonly Dictionary<string, bool> restricted_response;
56 private bool internallyCreated = false;
60 static WebHeaderCollection ()
62 // the list of restricted header names as defined
64 restricted = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
65 CaseInsensitiveComparer.DefaultInvariant);
67 restricted.Add ("accept", true);
68 restricted.Add ("connection", true);
69 restricted.Add ("content-length", true);
70 restricted.Add ("content-type", true);
71 restricted.Add ("date", true);
72 restricted.Add ("expect", true);
73 restricted.Add ("host", true);
74 restricted.Add ("if-modified-since", true);
75 restricted.Add ("range", true);
76 restricted.Add ("referer", true);
77 restricted.Add ("transfer-encoding", true);
78 restricted.Add ("user-agent", true);
79 restricted.Add ("proxy-connection", true);
82 restricted_response = new Dictionary<string, bool> (StringComparer.InvariantCultureIgnoreCase);
83 restricted_response.Add ("Content-Length", true);
84 restricted_response.Add ("Transfer-Encoding", true);
85 restricted_response.Add ("WWW-Authenticate", true);
87 // see par 14 of RFC 2068 to see which header names
88 // accept multiple values each separated by a comma
89 multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
90 CaseInsensitiveComparer.DefaultInvariant);
92 multiValue.Add ("accept", true);
93 multiValue.Add ("accept-charset", true);
94 multiValue.Add ("accept-encoding", true);
95 multiValue.Add ("accept-language", true);
96 multiValue.Add ("accept-ranges", true);
97 multiValue.Add ("allow", true);
98 multiValue.Add ("authorization", true);
99 multiValue.Add ("cache-control", true);
100 multiValue.Add ("connection", true);
101 multiValue.Add ("content-encoding", true);
102 multiValue.Add ("content-language", true);
103 multiValue.Add ("expect", true);
104 multiValue.Add ("if-match", true);
105 multiValue.Add ("if-none-match", true);
106 multiValue.Add ("proxy-authenticate", true);
107 multiValue.Add ("public", true);
108 multiValue.Add ("range", true);
109 multiValue.Add ("transfer-encoding", true);
110 multiValue.Add ("upgrade", true);
111 multiValue.Add ("vary", true);
112 multiValue.Add ("via", true);
113 multiValue.Add ("warning", true);
114 multiValue.Add ("www-authenticate", true);
117 multiValue.Add ("set-cookie", true);
118 multiValue.Add ("set-cookie2", true);
123 public WebHeaderCollection () { }
125 protected WebHeaderCollection (SerializationInfo serializationInfo,
126 StreamingContext streamingContext)
131 count = serializationInfo.GetInt32("Count");
132 for (int i = 0; i < count; i++)
133 this.Add (serializationInfo.GetString (i.ToString ()),
134 serializationInfo.GetString ((count + i).ToString ()));
135 } catch (SerializationException){
136 count = serializationInfo.GetInt32("count");
137 for (int i = 0; i < count; i++)
138 this.Add (serializationInfo.GetString ("k" + i),
139 serializationInfo.GetString ("v" + i));
144 internal WebHeaderCollection (bool internallyCreated)
146 this.internallyCreated = internallyCreated;
151 public void Add (string header)
154 throw new ArgumentNullException ("header");
155 int pos = header.IndexOf (':');
157 throw new ArgumentException ("no colon found", "header");
158 this.Add (header.Substring (0, pos),
159 header.Substring (pos + 1));
162 public override void Add (string name, string value)
165 throw new ArgumentNullException ("name");
166 if (internallyCreated && IsRestricted (name))
167 throw new ArgumentException ("This header must be modified with the appropiate property.");
168 this.AddWithoutValidate (name, value);
171 protected void AddWithoutValidate (string headerName, string headerValue)
173 if (!IsHeaderName (headerName))
174 throw new ArgumentException ("invalid header name: " + headerName, "headerName");
175 if (headerValue == null)
176 headerValue = String.Empty;
178 headerValue = headerValue.Trim ();
179 if (!IsHeaderValue (headerValue))
180 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
181 base.Add (headerName, headerValue);
184 public override string [] GetValues (string header)
187 throw new ArgumentNullException ("header");
189 string [] values = base.GetValues (header);
190 if (values == null || values.Length == 0)
194 if (IsMultiValue (header)) {
195 values = GetMultipleValues (values);
202 public override string[] GetValues (int index)
204 string[] values = base.GetValues (index);
205 if (values == null || values.Length == 0) {
212 /* Now i wonder why this is here...
213 static string [] GetMultipleValues (string [] values)
215 ArrayList mvalues = new ArrayList (values.Length);
216 StringBuilder sb = null;
217 for (int i = 0; i < values.Length; ++i) {
218 string val = values [i];
219 if (val.IndexOf (',') == -1) {
225 sb = new StringBuilder ();
228 for (int k = 0; k < val.Length; k++) {
232 } else if (!quote && c == ',') {
233 mvalues.Add (sb.ToString ().Trim ());
241 mvalues.Add (sb.ToString ().Trim ());
246 return (string []) mvalues.ToArray (typeof (string));
250 public static bool IsRestricted (string headerName)
252 if (headerName == null)
253 throw new ArgumentNullException ("headerName");
255 if (headerName == "") // MS throw nullexception here!
256 throw new ArgumentException ("empty string", "headerName");
258 if (!IsHeaderName (headerName))
259 throw new ArgumentException ("Invalid character in header");
261 return restricted.ContainsKey (headerName);
264 public static bool IsRestricted (string headerName, bool response)
266 if (String.IsNullOrEmpty (headerName))
267 throw new ArgumentNullException ("headerName");
269 if (!IsHeaderName (headerName))
270 throw new ArgumentException ("Invalid character in header");
274 return restricted_response.ContainsKey (headerName);
275 return restricted.ContainsKey (headerName);
278 public override void OnDeserialization (object sender)
282 public override void Remove (string name)
285 throw new ArgumentNullException ("name");
286 if (internallyCreated && IsRestricted (name))
287 throw new ArgumentException ("restricted header");
291 public override void Set (string name, string value)
294 throw new ArgumentNullException ("name");
295 if (internallyCreated && IsRestricted (name))
296 throw new ArgumentException ("restricted header");
297 if (!IsHeaderName (name))
298 throw new ArgumentException ("invalid header name");
300 value = String.Empty;
302 value = value.Trim ();
303 if (!IsHeaderValue (value))
304 throw new ArgumentException ("invalid header value");
305 base.Set (name, value);
308 public byte[] ToByteArray ()
310 return Encoding.UTF8.GetBytes(ToString ());
313 internal string ToStringMultiValue ()
315 StringBuilder sb = new StringBuilder();
317 int count = base.Count;
318 for (int i = 0; i < count ; i++) {
319 string key = GetKey (i);
320 if (IsMultiValue (key)) {
321 foreach (string v in GetValues (i)) {
334 return sb.Append("\r\n").ToString();
337 public override string ToString ()
339 StringBuilder sb = new StringBuilder();
341 int count = base.Count;
342 for (int i = 0; i < count ; i++)
343 sb.Append (GetKey (i))
348 return sb.Append("\r\n").ToString();
351 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
352 StreamingContext streamingContext)
354 GetObjectData (serializationInfo, streamingContext);
357 public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
359 int count = base.Count;
360 serializationInfo.AddValue ("Count", count);
361 for (int i = 0; i < count; i++) {
362 serializationInfo.AddValue (i.ToString (), GetKey (i));
363 serializationInfo.AddValue ((count + i).ToString (), Get (i));
367 public override string[] AllKeys
370 return(base.AllKeys);
374 public override int Count
381 public override KeysCollection Keys
388 public override string Get (int index)
390 return(base.Get (index));
393 public override string Get (string name)
395 return(base.Get (name));
398 public override string GetKey (int index)
400 return(base.GetKey (index));
403 public void Add (HttpRequestHeader header, string value)
405 Add (RequestHeaderToString (header), value);
408 public void Remove (HttpRequestHeader header)
410 Remove (RequestHeaderToString (header));
413 public void Set (HttpRequestHeader header, string value)
415 Set (RequestHeaderToString (header), value);
418 public void Add (HttpResponseHeader header, string value)
420 Add (ResponseHeaderToString (header), value);
423 public void Remove (HttpResponseHeader header)
425 Remove (ResponseHeaderToString (header));
428 public void Set (HttpResponseHeader header, string value)
430 Set (ResponseHeaderToString (header), value);
433 static string RequestHeaderToString (HttpRequestHeader value)
436 case HttpRequestHeader.CacheControl:
437 return "Cache-Control";
438 case HttpRequestHeader.Connection:
440 case HttpRequestHeader.Date:
442 case HttpRequestHeader.KeepAlive:
444 case HttpRequestHeader.Pragma:
446 case HttpRequestHeader.Trailer:
448 case HttpRequestHeader.TransferEncoding:
449 return "Transfer-Encoding";
450 case HttpRequestHeader.Upgrade:
452 case HttpRequestHeader.Via:
454 case HttpRequestHeader.Warning:
456 case HttpRequestHeader.Allow:
458 case HttpRequestHeader.ContentLength:
459 return "Content-Length";
460 case HttpRequestHeader.ContentType:
461 return "Content-Type";
462 case HttpRequestHeader.ContentEncoding:
463 return "Content-Encoding";
464 case HttpRequestHeader.ContentLanguage:
465 return "Content-Language";
466 case HttpRequestHeader.ContentLocation:
467 return "Content-Location";
468 case HttpRequestHeader.ContentMd5:
469 return "Content-MD5";
470 case HttpRequestHeader.ContentRange:
471 return "Content-Range";
472 case HttpRequestHeader.Expires:
474 case HttpRequestHeader.LastModified:
475 return "Last-Modified";
476 case HttpRequestHeader.Accept:
478 case HttpRequestHeader.AcceptCharset:
479 return "Accept-Charset";
480 case HttpRequestHeader.AcceptEncoding:
481 return "Accept-Encoding";
482 case HttpRequestHeader.AcceptLanguage:
483 return "accept-language";
484 case HttpRequestHeader.Authorization:
485 return "Authorization";
486 case HttpRequestHeader.Cookie:
488 case HttpRequestHeader.Expect:
490 case HttpRequestHeader.From:
492 case HttpRequestHeader.Host:
494 case HttpRequestHeader.IfMatch:
496 case HttpRequestHeader.IfModifiedSince:
497 return "If-Modified-Since";
498 case HttpRequestHeader.IfNoneMatch:
499 return "If-None-Match";
500 case HttpRequestHeader.IfRange:
502 case HttpRequestHeader.IfUnmodifiedSince:
503 return "If-Unmodified-Since";
504 case HttpRequestHeader.MaxForwards:
505 return "Max-Forwards";
506 case HttpRequestHeader.ProxyAuthorization:
507 return "Proxy-Authorization";
508 case HttpRequestHeader.Referer:
510 case HttpRequestHeader.Range:
512 case HttpRequestHeader.Te:
514 case HttpRequestHeader.Translate:
516 case HttpRequestHeader.UserAgent:
519 throw new InvalidOperationException ();
524 public string this[HttpRequestHeader header]
527 return Get (RequestHeaderToString (header));
531 Add (RequestHeaderToString (header), value);
535 string ResponseHeaderToString (HttpResponseHeader value)
538 case HttpResponseHeader.CacheControl:
539 return "Cache-Control";
540 case HttpResponseHeader.Connection:
542 case HttpResponseHeader.Date:
544 case HttpResponseHeader.KeepAlive:
546 case HttpResponseHeader.Pragma:
548 case HttpResponseHeader.Trailer:
550 case HttpResponseHeader.TransferEncoding:
551 return "Transfer-Encoding";
552 case HttpResponseHeader.Upgrade:
554 case HttpResponseHeader.Via:
556 case HttpResponseHeader.Warning:
558 case HttpResponseHeader.Allow:
560 case HttpResponseHeader.ContentLength:
561 return "Content-Length";
562 case HttpResponseHeader.ContentType:
563 return "Content-Type";
564 case HttpResponseHeader.ContentEncoding:
565 return "Content-Encoding";
566 case HttpResponseHeader.ContentLanguage:
567 return "Content-Language";
568 case HttpResponseHeader.ContentLocation:
569 return "Content-Location";
570 case HttpResponseHeader.ContentMd5:
571 return "Content-MD5";
572 case HttpResponseHeader.ContentRange:
573 return "Content-Range";
574 case HttpResponseHeader.Expires:
576 case HttpResponseHeader.LastModified:
577 return "Last-Modified";
578 case HttpResponseHeader.AcceptRanges:
579 return "Accept-Ranges";
580 case HttpResponseHeader.Age:
582 case HttpResponseHeader.ETag:
584 case HttpResponseHeader.Location:
586 case HttpResponseHeader.ProxyAuthenticate:
587 return "Proxy-Authenticate";
588 case HttpResponseHeader.RetryAfter:
589 return "Retry-After";
590 case HttpResponseHeader.Server:
592 case HttpResponseHeader.SetCookie:
594 case HttpResponseHeader.Vary:
596 case HttpResponseHeader.WwwAuthenticate:
597 return "WWW-Authenticate";
599 throw new InvalidOperationException ();
602 public string this[HttpResponseHeader header]
606 return Get (ResponseHeaderToString (header));
611 Add (ResponseHeaderToString (header), value);
615 public override void Clear ()
621 public override IEnumerator GetEnumerator ()
623 return(base.GetEnumerator ());
628 // With this we don't check for invalid characters in header. See bug #55994.
629 internal void SetInternal (string header)
631 int pos = header.IndexOf (':');
633 throw new ArgumentException ("no colon found", "header");
635 SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
638 internal void SetInternal (string name, string value)
641 value = String.Empty;
643 value = value.Trim ();
644 if (!IsHeaderValue (value))
645 throw new ArgumentException ("invalid header value");
647 if (IsMultiValue (name)) {
648 base.Add (name, value);
651 base.Set (name, value);
655 internal void RemoveAndAdd (string name, string value)
658 value = String.Empty;
660 value = value.Trim ();
663 base.Set (name, value);
666 internal void RemoveInternal (string name)
669 throw new ArgumentNullException ("name");
675 internal static bool IsMultiValue (string headerName)
677 if (headerName == null || headerName == "")
680 return multiValue.ContainsKey (headerName);
683 internal static bool IsHeaderValue (string value)
685 // TEXT any 8 bit value except CTL's (0-31 and 127)
686 // but including \r\n space and \t
687 // after a newline at least one space or \t must follow
688 // certain header fields allow comments ()
690 int len = value.Length;
691 for (int i = 0; i < len; i++) {
695 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
697 if (c == '\n' && ++i < len) {
699 if (c != ' ' && c != '\t')
707 internal static bool IsHeaderName (string name)
709 if (name == null || name.Length == 0)
712 int len = name.Length;
713 for (int i = 0; i < len; i++) {
715 if (c > 126 || !allowed_chars [(int) c])
722 static bool [] allowed_chars = new bool [126] {
723 false, false, false, false, false, false, false, false, false, false, false, false, false, false,
724 false, false, false, false, false, false, false, false, false, false, false, false, false, false,
725 false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
726 true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
727 false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
728 true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
729 false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
730 true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,