root / trunk / hammock / src / net40 / Hammock.ClientProfile / Mono / WebHeaderCollection.cs @ 0eea575a
History | View | Annotate | Download (21.7 kB)
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 |
|
35 |
using System; |
36 |
using System.Collections; |
37 |
using System.Collections.Specialized; |
38 |
using System.Net; |
39 |
using System.Runtime.InteropServices; |
40 |
using System.Text; |
41 |
|
42 |
// See RFC 2068 par 4.2 Message Headers |
43 |
|
44 |
namespace Mono.Net |
45 |
{ |
46 |
[Serializable] |
47 |
[ComVisible(true)] |
48 |
public class WebHeaderCollection : NameValueCollection |
49 |
{ |
50 |
private static readonly Hashtable _restricted; |
51 |
private static readonly Hashtable _multiValue; |
52 |
|
53 |
private static readonly char[] tspecials = |
54 |
new[] |
55 |
{ |
56 |
'(', ')', '<', '>', '@', |
57 |
',', ';', ':', '\\', '"', |
58 |
'/', '[', ']', '?', '=', |
59 |
'{', '}', ' ', '\t' |
60 |
}; |
61 |
|
62 |
private readonly bool internallyCreated; |
63 |
|
64 |
// Static Initializer |
65 |
|
66 |
static WebHeaderCollection() |
67 |
{ |
68 |
// the list of restricted header names as defined |
69 |
// by the ms.net spec |
70 |
_restricted = new Hashtable(StringComparer.InvariantCultureIgnoreCase) |
71 |
{ |
72 |
{"accept", true}, |
73 |
{"connection", true}, |
74 |
{"content-length", true}, |
75 |
{"content-type", true}, |
76 |
{"date", true}, |
77 |
{"expect", true}, |
78 |
{"host", true}, |
79 |
{"if-modified-since", true}, |
80 |
{"range", true}, |
81 |
{"referer", true}, |
82 |
{"transfer-encoding", true}, |
83 |
{"user-agent", true} |
84 |
}; |
85 |
|
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) |
89 |
{ |
90 |
{"accept", true}, |
91 |
{"accept-charset", true}, |
92 |
{"accept-encoding", true}, |
93 |
{"accept-language", true}, |
94 |
{"accept-ranges", true}, |
95 |
{"allow", true}, |
96 |
{"authorization", true}, |
97 |
{"cache-control", true}, |
98 |
{"connection", true}, |
99 |
{"content-encoding", true}, |
100 |
{"content-language", true}, |
101 |
{"expect", true}, |
102 |
{"if-match", true}, |
103 |
{"if-none-match", true}, |
104 |
{"proxy-authenticate", true}, |
105 |
{"public", true}, |
106 |
{"range", true}, |
107 |
{"transfer-encoding", true}, |
108 |
{"upgrade", true}, |
109 |
{"vary", true}, |
110 |
{"via", true}, |
111 |
{"warning", true}, |
112 |
{"www-authenticate", true}, |
113 |
{"set-cookie", true}, |
114 |
{"set-cookie2", true} |
115 |
}; |
116 |
|
117 |
// Extra |
118 |
} |
119 |
|
120 |
// Constructors |
121 |
|
122 |
public WebHeaderCollection() |
123 |
{ |
124 |
} |
125 |
|
126 |
internal WebHeaderCollection(bool internallyCreated) |
127 |
{ |
128 |
this.internallyCreated = internallyCreated; |
129 |
} |
130 |
|
131 |
public override string[] AllKeys |
132 |
{ |
133 |
get { return (base.AllKeys); } |
134 |
} |
135 |
|
136 |
public override int Count |
137 |
{ |
138 |
get { return (base.Count); } |
139 |
} |
140 |
|
141 |
public override KeysCollection Keys |
142 |
{ |
143 |
get { return (base.Keys); } |
144 |
} |
145 |
|
146 |
public string this[HttpRequestHeader hrh] |
147 |
{ |
148 |
get { return Get(RequestHeaderToString(hrh)); } |
149 |
|
150 |
set { Add(RequestHeaderToString(hrh), value); } |
151 |
} |
152 |
|
153 |
public string this[HttpResponseHeader hrh] |
154 |
{ |
155 |
get { return Get(ResponseHeaderToString(hrh)); } |
156 |
|
157 |
set { Add(ResponseHeaderToString(hrh), value); } |
158 |
} |
159 |
|
160 |
// Methods |
161 |
|
162 |
public void Add(string header) |
163 |
{ |
164 |
if (header == null) |
165 |
throw new ArgumentNullException("header"); |
166 |
var pos = header.IndexOf(':'); |
167 |
if (pos == -1) |
168 |
throw new ArgumentException("no colon found", "header"); |
169 |
Add(header.Substring(0, pos), |
170 |
header.Substring(pos + 1)); |
171 |
} |
172 |
|
173 |
public override void Add(string name, string value) |
174 |
{ |
175 |
if (name == null) |
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); |
180 |
} |
181 |
|
182 |
protected void AddWithoutValidate(string headerName, string headerValue) |
183 |
{ |
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); |
190 |
} |
191 |
|
192 |
public override string[] GetValues(string header) |
193 |
{ |
194 |
if (header == null) |
195 |
throw new ArgumentNullException("header"); |
196 |
|
197 |
var values = base.GetValues(header); |
198 |
if (values == null || values.Length == 0) |
199 |
return null; |
200 |
|
201 |
/* |
202 |
if (IsMultiValue (header)) { |
203 |
values = GetMultipleValues (values); |
204 |
} |
205 |
*/ |
206 |
|
207 |
return values; |
208 |
} |
209 |
|
210 |
public override string[] GetValues(int index) |
211 |
{ |
212 |
var values = base.GetValues(index); |
213 |
if (values == null || values.Length == 0) |
214 |
{ |
215 |
return (null); |
216 |
} |
217 |
|
218 |
return (values); |
219 |
} |
220 |
|
221 |
/* Now i wonder why this is here... |
222 |
static string [] GetMultipleValues (string [] values) |
223 |
{ |
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) { |
229 |
mvalues.Add (val); |
230 |
continue; |
231 |
} |
232 |
|
233 |
if (sb == null) |
234 |
sb = new StringBuilder (); |
235 |
|
236 |
bool quote = false; |
237 |
for (int k = 0; k < val.Length; k++) { |
238 |
char c = val [k]; |
239 |
if (c == '"') { |
240 |
quote = !quote; |
241 |
} else if (!quote && c == ',') { |
242 |
mvalues.Add (sb.ToString ().Trim ()); |
243 |
sb.Length = 0; |
244 |
continue; |
245 |
} |
246 |
sb.Append (c); |
247 |
} |
248 |
|
249 |
if (sb.Length > 0) { |
250 |
mvalues.Add (sb.ToString ().Trim ()); |
251 |
sb.Length = 0; |
252 |
} |
253 |
} |
254 |
|
255 |
return (string []) mvalues.ToArray (typeof (string)); |
256 |
} |
257 |
*/ |
258 |
|
259 |
public static bool IsRestricted(string headerName) |
260 |
{ |
261 |
if (headerName == null) |
262 |
throw new ArgumentNullException("headerName"); |
263 |
|
264 |
if (headerName == "") // MS throw nullexception here! |
265 |
throw new ArgumentException("empty string", "headerName"); |
266 |
|
267 |
return _restricted.ContainsKey(headerName); |
268 |
} |
269 |
|
270 |
public static bool IsRestricted(string headerName, bool response) |
271 |
{ |
272 |
throw new NotImplementedException(); |
273 |
} |
274 |
|
275 |
public override void Remove(string name) |
276 |
{ |
277 |
if (name == null) |
278 |
throw new ArgumentNullException("name"); |
279 |
if (internallyCreated && IsRestricted(name)) |
280 |
throw new ArgumentException("restricted header"); |
281 |
base.Remove(name); |
282 |
} |
283 |
|
284 |
public override void Set(string name, string value) |
285 |
{ |
286 |
if (name == null) |
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); |
296 |
} |
297 |
|
298 |
public byte[] ToByteArray() |
299 |
{ |
300 |
return Encoding.UTF8.GetBytes(ToString()); |
301 |
} |
302 |
|
303 |
public override string ToString() |
304 |
{ |
305 |
var sb = new StringBuilder(); |
306 |
|
307 |
var count = base.Count; |
308 |
for (var i = 0; i < count; i++) |
309 |
sb.Append(GetKey(i)) |
310 |
.Append(": ") |
311 |
.Append(Get(i)) |
312 |
.Append("\r\n"); |
313 |
|
314 |
return sb.Append("\r\n").ToString(); |
315 |
} |
316 |
|
317 |
public override string Get(int index) |
318 |
{ |
319 |
return (base.Get(index)); |
320 |
} |
321 |
|
322 |
public override string Get(string name) |
323 |
{ |
324 |
return (base.Get(name)); |
325 |
} |
326 |
|
327 |
public override string GetKey(int index) |
328 |
{ |
329 |
return (base.GetKey(index)); |
330 |
} |
331 |
|
332 |
public void Add(HttpRequestHeader header, string value) |
333 |
{ |
334 |
Add(RequestHeaderToString(header), value); |
335 |
} |
336 |
|
337 |
public void Remove(HttpRequestHeader header) |
338 |
{ |
339 |
Remove(RequestHeaderToString(header)); |
340 |
} |
341 |
|
342 |
public void Set(HttpRequestHeader header, string value) |
343 |
{ |
344 |
Set(RequestHeaderToString(header), value); |
345 |
} |
346 |
|
347 |
public void Add(HttpResponseHeader header, string value) |
348 |
{ |
349 |
Add(ResponseHeaderToString(header), value); |
350 |
} |
351 |
|
352 |
public void Remove(HttpResponseHeader header) |
353 |
{ |
354 |
Remove(ResponseHeaderToString(header)); |
355 |
} |
356 |
|
357 |
public void Set(HttpResponseHeader header, string value) |
358 |
{ |
359 |
Set(ResponseHeaderToString(header), value); |
360 |
} |
361 |
|
362 |
private static string RequestHeaderToString(HttpRequestHeader value) |
363 |
{ |
364 |
switch (value) |
365 |
{ |
366 |
case HttpRequestHeader.CacheControl: |
367 |
return "cache-control"; |
368 |
case HttpRequestHeader.Connection: |
369 |
return "connection"; |
370 |
case HttpRequestHeader.Date: |
371 |
return "date"; |
372 |
case HttpRequestHeader.KeepAlive: |
373 |
return "keep-alive"; |
374 |
case HttpRequestHeader.Pragma: |
375 |
return "pragma"; |
376 |
case HttpRequestHeader.Trailer: |
377 |
return "trailer"; |
378 |
case HttpRequestHeader.TransferEncoding: |
379 |
return "transfer-encoding"; |
380 |
case HttpRequestHeader.Upgrade: |
381 |
return "upgrade"; |
382 |
case HttpRequestHeader.Via: |
383 |
return "via"; |
384 |
case HttpRequestHeader.Warning: |
385 |
return "warning"; |
386 |
case HttpRequestHeader.Allow: |
387 |
return "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: |
403 |
return "expires"; |
404 |
case HttpRequestHeader.LastModified: |
405 |
return "last-modified"; |
406 |
case HttpRequestHeader.Accept: |
407 |
return "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: |
417 |
return "cookie"; |
418 |
case HttpRequestHeader.Expect: |
419 |
return "expect"; |
420 |
case HttpRequestHeader.From: |
421 |
return "from"; |
422 |
case HttpRequestHeader.Host: |
423 |
return "host"; |
424 |
case HttpRequestHeader.IfMatch: |
425 |
return "if-match"; |
426 |
case HttpRequestHeader.IfModifiedSince: |
427 |
return "if-modified-since"; |
428 |
case HttpRequestHeader.IfNoneMatch: |
429 |
return "if-none-match"; |
430 |
case HttpRequestHeader.IfRange: |
431 |
return "if-range"; |
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: |
439 |
return "referer"; |
440 |
case HttpRequestHeader.Range: |
441 |
return "range"; |
442 |
case HttpRequestHeader.Te: |
443 |
return "te"; |
444 |
case HttpRequestHeader.Translate: |
445 |
return "translate"; |
446 |
case HttpRequestHeader.UserAgent: |
447 |
return "user-agent"; |
448 |
default: |
449 |
throw new InvalidOperationException(); |
450 |
} |
451 |
} |
452 |
|
453 |
|
454 |
private static string ResponseHeaderToString(HttpResponseHeader value) |
455 |
{ |
456 |
switch (value) |
457 |
{ |
458 |
case HttpResponseHeader.CacheControl: |
459 |
return "cache-control"; |
460 |
case HttpResponseHeader.Connection: |
461 |
return "connection"; |
462 |
case HttpResponseHeader.Date: |
463 |
return "date"; |
464 |
case HttpResponseHeader.KeepAlive: |
465 |
return "keep-alive"; |
466 |
case HttpResponseHeader.Pragma: |
467 |
return "pragma"; |
468 |
case HttpResponseHeader.Trailer: |
469 |
return "trailer"; |
470 |
case HttpResponseHeader.TransferEncoding: |
471 |
return "transfer-encoding"; |
472 |
case HttpResponseHeader.Upgrade: |
473 |
return "upgrade"; |
474 |
case HttpResponseHeader.Via: |
475 |
return "via"; |
476 |
case HttpResponseHeader.Warning: |
477 |
return "warning"; |
478 |
case HttpResponseHeader.Allow: |
479 |
return "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: |
495 |
return "expires"; |
496 |
case HttpResponseHeader.LastModified: |
497 |
return "last-modified"; |
498 |
case HttpResponseHeader.AcceptRanges: |
499 |
return "accept-ranges"; |
500 |
case HttpResponseHeader.Age: |
501 |
return "age"; |
502 |
case HttpResponseHeader.ETag: |
503 |
return "etag"; |
504 |
case HttpResponseHeader.Location: |
505 |
return "location"; |
506 |
case HttpResponseHeader.ProxyAuthenticate: |
507 |
return "proxy-authenticate"; |
508 |
case HttpResponseHeader.RetryAfter: |
509 |
return "RetryAfter"; |
510 |
case HttpResponseHeader.Server: |
511 |
return "server"; |
512 |
case HttpResponseHeader.SetCookie: |
513 |
return "set-cookie"; |
514 |
case HttpResponseHeader.Vary: |
515 |
return "vary"; |
516 |
case HttpResponseHeader.WwwAuthenticate: |
517 |
return "www-authenticate"; |
518 |
default: |
519 |
throw new InvalidOperationException(); |
520 |
} |
521 |
} |
522 |
|
523 |
public override IEnumerator GetEnumerator() |
524 |
{ |
525 |
return (base.GetEnumerator()); |
526 |
} |
527 |
|
528 |
// Internal Methods |
529 |
|
530 |
// With this we don't check for invalid characters in header. See bug #55994. |
531 |
internal void SetInternal(string header) |
532 |
{ |
533 |
var pos = header.IndexOf(':'); |
534 |
if (pos == -1) |
535 |
throw new ArgumentException("no colon found", "header"); |
536 |
|
537 |
SetInternal(header.Substring(0, pos), header.Substring(pos + 1)); |
538 |
} |
539 |
|
540 |
internal void SetInternal(string name, string value) |
541 |
{ |
542 |
value = value == null ? String.Empty : value.Trim(); |
543 |
if (!IsHeaderValue(value)) |
544 |
throw new ArgumentException("invalid header value"); |
545 |
|
546 |
if (IsMultiValue(name)) |
547 |
{ |
548 |
base.Add(name, value); |
549 |
} |
550 |
else |
551 |
{ |
552 |
base.Remove(name); |
553 |
base.Set(name, value); |
554 |
} |
555 |
} |
556 |
|
557 |
internal void RemoveAndAdd(string name, string value) |
558 |
{ |
559 |
value = value == null ? String.Empty : value.Trim(); |
560 |
|
561 |
base.Remove(name); |
562 |
base.Set(name, value); |
563 |
} |
564 |
|
565 |
internal void RemoveInternal(string name) |
566 |
{ |
567 |
if (name == null) |
568 |
throw new ArgumentNullException("name"); |
569 |
base.Remove(name); |
570 |
} |
571 |
|
572 |
internal static bool IsMultiValue(string headerName) |
573 |
{ |
574 |
return !string.IsNullOrEmpty(headerName) && _multiValue.ContainsKey(headerName); |
575 |
} |
576 |
|
577 |
internal static bool IsHeaderValue(string value) |
578 |
{ |
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 () |
583 |
|
584 |
var len = value.Length; |
585 |
for (var i = 0; i < len; i++) |
586 |
{ |
587 |
var c = value[i]; |
588 |
if (c == 127) |
589 |
return false; |
590 |
if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t')) |
591 |
return false; |
592 |
if (c == '\n' && ++i < len) |
593 |
{ |
594 |
c = value[i]; |
595 |
if (c != ' ' && c != '\t') |
596 |
return false; |
597 |
} |
598 |
} |
599 |
|
600 |
return true; |
601 |
} |
602 |
|
603 |
internal static bool IsHeaderName(string name) |
604 |
{ |
605 |
// token = 1*<any CHAR except CTLs or tspecials> |
606 |
// tspecials = "(" | ")" | "<" | ">" | "@" |
607 |
// | "," | ";" | ":" | "\" | <"> |
608 |
// | "/" | "[" | "]" | "?" | "=" |
609 |
// | "{" | "}" | SP | HT |
610 |
|
611 |
if (string.IsNullOrEmpty(name)) |
612 |
return false; |
613 |
|
614 |
var len = name.Length; |
615 |
for (var i = 0; i < len; i++) |
616 |
{ |
617 |
var c = name[i]; |
618 |
if (c < 0x20 || c >= 0x7f) |
619 |
return false; |
620 |
} |
621 |
|
622 |
return name.IndexOfAny(tspecials) == -1; |
623 |
} |
624 |
} |
625 |
} |