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.
36 using System.Collections;
37 using System.Collections.Specialized;
39 using System.Runtime.InteropServices;
42 // See RFC 2068 par 4.2 Message Headers
48 public class WebHeaderCollection : NameValueCollection
50 private static readonly Hashtable _restricted;
51 private static readonly Hashtable _multiValue;
53 private static readonly char[] tspecials =
56 '(', ')', '<', '>', '@',
57 ',', ';', ':', '\\', '"',
58 '/', '[', ']', '?', '=',
62 private readonly bool internallyCreated;
66 static WebHeaderCollection()
68 // the list of restricted header names as defined
70 _restricted = new Hashtable(StringComparer.InvariantCultureIgnoreCase)
74 {"content-length", true},
75 {"content-type", true},
79 {"if-modified-since", true},
82 {"transfer-encoding", true},
86 // see par 14 of RFC 2068 to see which header names
87 // accept multiple values each separated by a comma
88 _multiValue = new Hashtable(StringComparer.InvariantCultureIgnoreCase)
91 {"accept-charset", true},
92 {"accept-encoding", true},
93 {"accept-language", true},
94 {"accept-ranges", true},
96 {"authorization", true},
97 {"cache-control", true},
99 {"content-encoding", true},
100 {"content-language", true},
103 {"if-none-match", true},
104 {"proxy-authenticate", true},
107 {"transfer-encoding", true},
112 {"www-authenticate", true},
113 {"set-cookie", true},
114 {"set-cookie2", true}
122 public WebHeaderCollection()
126 internal WebHeaderCollection(bool internallyCreated)
128 this.internallyCreated = internallyCreated;
131 public override string[] AllKeys
133 get { return (base.AllKeys); }
136 public override int Count
138 get { return (base.Count); }
141 public override KeysCollection Keys
143 get { return (base.Keys); }
146 public string this[HttpRequestHeader hrh]
148 get { return Get(RequestHeaderToString(hrh)); }
150 set { Add(RequestHeaderToString(hrh), value); }
153 public string this[HttpResponseHeader hrh]
155 get { return Get(ResponseHeaderToString(hrh)); }
157 set { Add(ResponseHeaderToString(hrh), value); }
162 public void Add(string header)
165 throw new ArgumentNullException("header");
166 var pos = header.IndexOf(':');
168 throw new ArgumentException("no colon found", "header");
169 Add(header.Substring(0, pos),
170 header.Substring(pos + 1));
173 public override void Add(string name, string value)
176 throw new ArgumentNullException("name");
177 if (internallyCreated && IsRestricted(name))
178 throw new ArgumentException("This header must be modified with the appropiate property.");
179 AddWithoutValidate(name, value);
182 protected void AddWithoutValidate(string headerName, string headerValue)
184 if (!IsHeaderName(headerName))
185 throw new ArgumentException("invalid header name: " + headerName, "headerName");
186 headerValue = headerValue == null ? String.Empty : headerValue.Trim();
187 if (!IsHeaderValue(headerValue))
188 throw new ArgumentException("invalid header value: " + headerValue, "headerValue");
189 base.Add(headerName, headerValue);
192 public override string[] GetValues(string header)
195 throw new ArgumentNullException("header");
197 var values = base.GetValues(header);
198 if (values == null || values.Length == 0)
202 if (IsMultiValue (header)) {
203 values = GetMultipleValues (values);
210 public override string[] GetValues(int index)
212 var values = base.GetValues(index);
213 if (values == null || values.Length == 0)
221 /* Now i wonder why this is here...
222 static string [] GetMultipleValues (string [] values)
224 ArrayList mvalues = new ArrayList (values.Length);
225 StringBuilder sb = null;
226 for (int i = 0; i < values.Length; ++i) {
227 string val = values [i];
228 if (val.IndexOf (',') == -1) {
234 sb = new StringBuilder ();
237 for (int k = 0; k < val.Length; k++) {
241 } else if (!quote && c == ',') {
242 mvalues.Add (sb.ToString ().Trim ());
250 mvalues.Add (sb.ToString ().Trim ());
255 return (string []) mvalues.ToArray (typeof (string));
259 public static bool IsRestricted(string headerName)
261 if (headerName == null)
262 throw new ArgumentNullException("headerName");
264 if (headerName == "") // MS throw nullexception here!
265 throw new ArgumentException("empty string", "headerName");
267 return _restricted.ContainsKey(headerName);
270 public static bool IsRestricted(string headerName, bool response)
272 throw new NotImplementedException();
275 public override void Remove(string name)
278 throw new ArgumentNullException("name");
279 if (internallyCreated && IsRestricted(name))
280 throw new ArgumentException("restricted header");
284 public override void Set(string name, string value)
287 throw new ArgumentNullException("name");
288 if (internallyCreated && IsRestricted(name))
289 throw new ArgumentException("restricted header");
290 if (!IsHeaderName(name))
291 throw new ArgumentException("invalid header name");
292 value = value == null ? String.Empty : value.Trim();
293 if (!IsHeaderValue(value))
294 throw new ArgumentException("invalid header value");
295 base.Set(name, value);
298 public byte[] ToByteArray()
300 return Encoding.UTF8.GetBytes(ToString());
303 public override string ToString()
305 var sb = new StringBuilder();
307 var count = base.Count;
308 for (var i = 0; i < count; i++)
314 return sb.Append("\r\n").ToString();
317 public override string Get(int index)
319 return (base.Get(index));
322 public override string Get(string name)
324 return (base.Get(name));
327 public override string GetKey(int index)
329 return (base.GetKey(index));
332 public void Add(HttpRequestHeader header, string value)
334 Add(RequestHeaderToString(header), value);
337 public void Remove(HttpRequestHeader header)
339 Remove(RequestHeaderToString(header));
342 public void Set(HttpRequestHeader header, string value)
344 Set(RequestHeaderToString(header), value);
347 public void Add(HttpResponseHeader header, string value)
349 Add(ResponseHeaderToString(header), value);
352 public void Remove(HttpResponseHeader header)
354 Remove(ResponseHeaderToString(header));
357 public void Set(HttpResponseHeader header, string value)
359 Set(ResponseHeaderToString(header), value);
362 private static string RequestHeaderToString(HttpRequestHeader value)
366 case HttpRequestHeader.CacheControl:
367 return "cache-control";
368 case HttpRequestHeader.Connection:
370 case HttpRequestHeader.Date:
372 case HttpRequestHeader.KeepAlive:
374 case HttpRequestHeader.Pragma:
376 case HttpRequestHeader.Trailer:
378 case HttpRequestHeader.TransferEncoding:
379 return "transfer-encoding";
380 case HttpRequestHeader.Upgrade:
382 case HttpRequestHeader.Via:
384 case HttpRequestHeader.Warning:
386 case HttpRequestHeader.Allow:
388 case HttpRequestHeader.ContentLength:
389 return "content-length";
390 case HttpRequestHeader.ContentType:
391 return "content-type";
392 case HttpRequestHeader.ContentEncoding:
393 return "content-encoding";
394 case HttpRequestHeader.ContentLanguage:
395 return "content-language";
396 case HttpRequestHeader.ContentLocation:
397 return "content-location";
398 case HttpRequestHeader.ContentMd5:
399 return "content-md5";
400 case HttpRequestHeader.ContentRange:
401 return "content-range";
402 case HttpRequestHeader.Expires:
404 case HttpRequestHeader.LastModified:
405 return "last-modified";
406 case HttpRequestHeader.Accept:
408 case HttpRequestHeader.AcceptCharset:
409 return "accept-charset";
410 case HttpRequestHeader.AcceptEncoding:
411 return "accept-encoding";
412 case HttpRequestHeader.AcceptLanguage:
413 return "accept-language";
414 case HttpRequestHeader.Authorization:
415 return "authorization";
416 case HttpRequestHeader.Cookie:
418 case HttpRequestHeader.Expect:
420 case HttpRequestHeader.From:
422 case HttpRequestHeader.Host:
424 case HttpRequestHeader.IfMatch:
426 case HttpRequestHeader.IfModifiedSince:
427 return "if-modified-since";
428 case HttpRequestHeader.IfNoneMatch:
429 return "if-none-match";
430 case HttpRequestHeader.IfRange:
432 case HttpRequestHeader.IfUnmodifiedSince:
433 return "if-unmodified-since";
434 case HttpRequestHeader.MaxForwards:
435 return "max-forwards";
436 case HttpRequestHeader.ProxyAuthorization:
437 return "proxy-authorization";
438 case HttpRequestHeader.Referer:
440 case HttpRequestHeader.Range:
442 case HttpRequestHeader.Te:
444 case HttpRequestHeader.Translate:
446 case HttpRequestHeader.UserAgent:
449 throw new InvalidOperationException();
454 private static string ResponseHeaderToString(HttpResponseHeader value)
458 case HttpResponseHeader.CacheControl:
459 return "cache-control";
460 case HttpResponseHeader.Connection:
462 case HttpResponseHeader.Date:
464 case HttpResponseHeader.KeepAlive:
466 case HttpResponseHeader.Pragma:
468 case HttpResponseHeader.Trailer:
470 case HttpResponseHeader.TransferEncoding:
471 return "transfer-encoding";
472 case HttpResponseHeader.Upgrade:
474 case HttpResponseHeader.Via:
476 case HttpResponseHeader.Warning:
478 case HttpResponseHeader.Allow:
480 case HttpResponseHeader.ContentLength:
481 return "content-length";
482 case HttpResponseHeader.ContentType:
483 return "content-type";
484 case HttpResponseHeader.ContentEncoding:
485 return "content-encoding";
486 case HttpResponseHeader.ContentLanguage:
487 return "content-language";
488 case HttpResponseHeader.ContentLocation:
489 return "content-location";
490 case HttpResponseHeader.ContentMd5:
491 return "content-md5";
492 case HttpResponseHeader.ContentRange:
493 return "content-range";
494 case HttpResponseHeader.Expires:
496 case HttpResponseHeader.LastModified:
497 return "last-modified";
498 case HttpResponseHeader.AcceptRanges:
499 return "accept-ranges";
500 case HttpResponseHeader.Age:
502 case HttpResponseHeader.ETag:
504 case HttpResponseHeader.Location:
506 case HttpResponseHeader.ProxyAuthenticate:
507 return "proxy-authenticate";
508 case HttpResponseHeader.RetryAfter:
510 case HttpResponseHeader.Server:
512 case HttpResponseHeader.SetCookie:
514 case HttpResponseHeader.Vary:
516 case HttpResponseHeader.WwwAuthenticate:
517 return "www-authenticate";
519 throw new InvalidOperationException();
523 public override IEnumerator GetEnumerator()
525 return (base.GetEnumerator());
530 // With this we don't check for invalid characters in header. See bug #55994.
531 internal void SetInternal(string header)
533 var pos = header.IndexOf(':');
535 throw new ArgumentException("no colon found", "header");
537 SetInternal(header.Substring(0, pos), header.Substring(pos + 1));
540 internal void SetInternal(string name, string value)
542 value = value == null ? String.Empty : value.Trim();
543 if (!IsHeaderValue(value))
544 throw new ArgumentException("invalid header value");
546 if (IsMultiValue(name))
548 base.Add(name, value);
553 base.Set(name, value);
557 internal void RemoveAndAdd(string name, string value)
559 value = value == null ? String.Empty : value.Trim();
562 base.Set(name, value);
565 internal void RemoveInternal(string name)
568 throw new ArgumentNullException("name");
572 internal static bool IsMultiValue(string headerName)
574 return !string.IsNullOrEmpty(headerName) && _multiValue.ContainsKey(headerName);
577 internal static bool IsHeaderValue(string value)
579 // TEXT any 8 bit value except CTL's (0-31 and 127)
580 // but including \r\n space and \t
581 // after a newline at least one space or \t must follow
582 // certain header fields allow comments ()
584 var len = value.Length;
585 for (var i = 0; i < len; i++)
590 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
592 if (c == '\n' && ++i < len)
595 if (c != ' ' && c != '\t')
603 internal static bool IsHeaderName(string name)
605 // token = 1*<any CHAR except CTLs or tspecials>
606 // tspecials = "(" | ")" | "<" | ">" | "@"
607 // | "," | ";" | ":" | "\" | <">
608 // | "/" | "[" | "]" | "?" | "="
609 // | "{" | "}" | SP | HT
611 if (string.IsNullOrEmpty(name))
614 var len = name.Length;
615 for (var i = 0; i < len; i++)
618 if (c < 0x20 || c >= 0x7f)
622 return name.IndexOfAny(tspecials) == -1;