Statistics
| Branch: | Revision:

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
}