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.Specialized;
37 using System.Runtime.InteropServices;
38 using System.Runtime.Serialization;
41 // See RFC 2068 par 4.2 Message Headers
47 public class WebHeaderCollection : NameValueCollection, ISerializable
49 private static readonly Hashtable restricted;
50 private static readonly Hashtable multiValue;
51 private bool internallyCreated = false;
55 static WebHeaderCollection ()
57 // the list of restricted header names as defined
59 restricted = new Hashtable (CaseInsensitiveHashCodeProvider.Default,
60 CaseInsensitiveComparer.Default);
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);
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);
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);
104 multiValue.Add ("set-cookie", true);
105 multiValue.Add ("set-cookie2", true);
110 public WebHeaderCollection () { }
112 protected WebHeaderCollection (SerializationInfo serializationInfo,
113 StreamingContext streamingContext)
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 ()));
121 internal WebHeaderCollection (bool internallyCreated)
123 this.internallyCreated = internallyCreated;
128 public void Add (string header)
131 throw new ArgumentNullException ("header");
132 int pos = header.IndexOf (':');
134 throw new ArgumentException ("no colon found", "header");
135 this.Add (header.Substring (0, pos),
136 header.Substring (pos + 1));
139 public override void Add (string name, string value)
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);
148 protected void AddWithoutValidate (string headerName, string headerValue)
150 if (!IsHeaderName (headerName))
151 throw new ArgumentException ("invalid header name: " + headerName, "headerName");
152 if (headerValue == null)
153 headerValue = String.Empty;
155 headerValue = headerValue.Trim ();
156 if (!IsHeaderValue (headerValue))
157 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
158 base.Add (headerName, headerValue);
161 public override string [] GetValues (string header)
164 throw new ArgumentNullException ("header");
166 string [] values = base.GetValues (header);
167 if (values == null || values.Length == 0)
171 if (IsMultiValue (header)) {
172 values = GetMultipleValues (values);
179 /* Now i wonder why this is here...
180 static string [] GetMultipleValues (string [] values)
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) {
192 sb = new StringBuilder ();
195 for (int k = 0; k < val.Length; k++) {
199 } else if (!quote && c == ',') {
200 mvalues.Add (sb.ToString ().Trim ());
208 mvalues.Add (sb.ToString ().Trim ());
213 return (string []) mvalues.ToArray (typeof (string));
217 public static bool IsRestricted (string headerName)
219 if (headerName == null)
220 throw new ArgumentNullException ("headerName");
222 if (headerName == "") // MS throw nullexception here!
223 throw new ArgumentException ("empty string", "headerName");
225 return restricted.ContainsKey (headerName);
228 public override void OnDeserialization (object sender)
232 public override void Remove (string name)
235 throw new ArgumentNullException ("name");
236 if (internallyCreated && IsRestricted (name))
237 throw new ArgumentException ("restricted header");
241 public override void Set (string name, string value)
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");
250 value = String.Empty;
252 value = value.Trim ();
253 if (!IsHeaderValue (value))
254 throw new ArgumentException ("invalid header value");
255 base.Set (name, value);
258 public byte[] ToByteArray ()
260 return Encoding.UTF8.GetBytes(ToString ());
263 public override string ToString ()
265 StringBuilder sb = new StringBuilder();
267 int count = base.Count;
268 for (int i = 0; i < count ; i++)
269 sb.Append (GetKey (i))
274 return sb.Append("\r\n").ToString();
277 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
278 StreamingContext streamingContext)
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));
289 string RequestHeaderToString (HttpRequestHeader value)
292 case HttpRequestHeader.CacheControl:
293 return "cache-control";
294 case HttpRequestHeader.Connection:
296 case HttpRequestHeader.Date:
298 case HttpRequestHeader.KeepAlive:
300 case HttpRequestHeader.Pragma:
302 case HttpRequestHeader.Trailer:
304 case HttpRequestHeader.TransferEncoding:
305 return "transfer-encoding";
306 case HttpRequestHeader.Upgrade:
308 case HttpRequestHeader.Via:
310 case HttpRequestHeader.Warning:
312 case HttpRequestHeader.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:
330 case HttpRequestHeader.LastModified:
331 return "last-modified";
332 case HttpRequestHeader.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:
344 case HttpRequestHeader.Expect:
346 case HttpRequestHeader.From:
348 case HttpRequestHeader.Host:
350 case HttpRequestHeader.IfMatch:
352 case HttpRequestHeader.IfModifiedSince:
353 return "if-modified-since";
354 case HttpRequestHeader.IfNoneMatch:
355 return "if-none-match";
356 case HttpRequestHeader.IfRange:
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:
366 case HttpRequestHeader.Range:
368 case HttpRequestHeader.Te:
370 case HttpRequestHeader.Translate:
372 case HttpRequestHeader.UserAgent:
375 throw new InvalidOperationException ();
379 public string this[HttpRequestHeader hrh]
382 return Get (RequestHeaderToString (hrh));
386 Add (RequestHeaderToString (hrh), value);
390 string ResponseHeaderToString (HttpResponseHeader value)
393 case HttpResponseHeader.CacheControl:
394 return "cache-control";
395 case HttpResponseHeader.Connection:
397 case HttpResponseHeader.Date:
399 case HttpResponseHeader.KeepAlive:
401 case HttpResponseHeader.Pragma:
403 case HttpResponseHeader.Trailer:
405 case HttpResponseHeader.TransferEncoding:
406 return "transfer-encoding";
407 case HttpResponseHeader.Upgrade:
409 case HttpResponseHeader.Via:
411 case HttpResponseHeader.Warning:
413 case HttpResponseHeader.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:
431 case HttpResponseHeader.LastModified:
432 return "last-modified";
433 case HttpResponseHeader.AcceptRanges:
434 return "accept-ranges";
435 case HttpResponseHeader.Age:
437 case HttpResponseHeader.ETag:
439 case HttpResponseHeader.Location:
441 case HttpResponseHeader.ProxyAuthenticate:
442 return "proxy-authenticate";
443 case HttpResponseHeader.RetryAfter:
445 case HttpResponseHeader.Server:
447 case HttpResponseHeader.SetCookie:
449 case HttpResponseHeader.Vary:
451 case HttpResponseHeader.WwwAuthenticate:
452 return "www-authenticate";
454 throw new InvalidOperationException ();
457 public string this[HttpResponseHeader hrh]
461 return Get (ResponseHeaderToString (hrh));
466 Add (ResponseHeaderToString (hrh), value);
473 // With this we don't check for invalid characters in header. See bug #55994.
474 internal void SetInternal (string header)
476 int pos = header.IndexOf (':');
478 throw new ArgumentException ("no colon found", "header");
480 SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
483 internal void SetInternal (string name, string value)
486 value = String.Empty;
488 value = value.Trim ();
489 if (!IsHeaderValue (value))
490 throw new ArgumentException ("invalid header value");
492 if (IsMultiValue (name)) {
493 base.Add (name, value);
496 base.Set (name, value);
500 internal void RemoveAndAdd (string name, string value)
503 value = String.Empty;
505 value = value.Trim ();
508 base.Set (name, value);
511 internal void RemoveInternal (string name)
514 throw new ArgumentNullException ("name");
520 internal static bool IsMultiValue (string headerName)
522 if (headerName == null || headerName == "")
525 return multiValue.ContainsKey (headerName);
528 internal static bool IsHeaderValue (string value)
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 ()
535 int len = value.Length;
536 for (int i = 0; i < len; i++) {
540 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
542 if (c == '\n' && ++i < len) {
544 if (c != ' ' && c != '\t')
552 internal static bool IsHeaderName (string name)
554 // token = 1*<any CHAR except CTLs or tspecials>
555 // tspecials = "(" | ")" | "<" | ">" | "@"
556 // | "," | ";" | ":" | "\" | <">
557 // | "/" | "[" | "]" | "?" | "="
558 // | "{" | "}" | SP | HT
560 if (name == null || name.Length == 0)
563 int len = name.Length;
564 for (int i = 0; i < len; i++) {
566 if (c < 0x20 || c >= 0x7f)
570 return name.IndexOfAny (tspecials) == -1;
573 private static char [] tspecials =
574 new char [] {'(', ')', '<', '>', '@',
575 ',', ';', ':', '\\', '"',
576 '/', '[', ']', '?', '=',
577 '{', '}', ' ', '\t'};