2005-11-17 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System / System.Net / WebHeaderCollection.cs
1 //
2 // System.Net.WebHeaderCollection
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections;
34 using System.Collections.Specialized;
35 using System.Runtime.InteropServices;
36 using System.Runtime.Serialization;
37 using System.Text;
38     
39 // See RFC 2068 par 4.2 Message Headers
40     
41 namespace System.Net 
42 {
43         [Serializable]
44         [ComVisible(true)]
45         public class WebHeaderCollection : NameValueCollection, ISerializable
46         {
47                 private static readonly Hashtable restricted;
48                 private static readonly Hashtable multiValue;
49                 private bool internallyCreated = false;
50                 
51                 // Static Initializer
52                 
53                 static WebHeaderCollection () 
54                 {
55                         // the list of restricted header names as defined 
56                         // by the ms.net spec
57                         restricted = new Hashtable (CaseInsensitiveHashCodeProvider.Default,
58                                                     CaseInsensitiveComparer.Default);
59
60                         restricted.Add ("accept", true);
61                         restricted.Add ("connection", true);
62                         restricted.Add ("content-length", true);
63                         restricted.Add ("content-type", true);
64                         restricted.Add ("date", true);
65                         restricted.Add ("expect", true);
66                         restricted.Add ("host", true);
67                         restricted.Add ("if-modified-since", true);
68                         restricted.Add ("range", true);
69                         restricted.Add ("referer", true);
70                         restricted.Add ("transfer-encoding", true);
71                         restricted.Add ("user-agent", true);                    
72                         
73                         // see par 14 of RFC 2068 to see which header names
74                         // accept multiple values each separated by a comma
75                         multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.Default,
76                                                     CaseInsensitiveComparer.Default);
77
78                         multiValue.Add ("accept", true);
79                         multiValue.Add ("accept-charset", true);
80                         multiValue.Add ("accept-encoding", true);
81                         multiValue.Add ("accept-language", true);
82                         multiValue.Add ("accept-ranges", true);
83                         multiValue.Add ("allow", true);
84                         multiValue.Add ("authorization", true);
85                         multiValue.Add ("cache-control", true);
86                         multiValue.Add ("connection", true);
87                         multiValue.Add ("content-encoding", true);
88                         multiValue.Add ("content-language", true);                      
89                         multiValue.Add ("expect", true);                
90                         multiValue.Add ("if-match", true);
91                         multiValue.Add ("if-none-match", true);
92                         multiValue.Add ("proxy-authenticate", true);
93                         multiValue.Add ("public", true);                        
94                         multiValue.Add ("range", true);
95                         multiValue.Add ("transfer-encoding", true);
96                         multiValue.Add ("upgrade", true);
97                         multiValue.Add ("vary", true);
98                         multiValue.Add ("via", true);
99                         multiValue.Add ("warning", true);
100
101                         // Extra
102                         multiValue.Add ("set-cookie", true);
103                         multiValue.Add ("set-cookie2", true);
104                 }
105                 
106                 // Constructors
107                 
108                 public WebHeaderCollection () { }       
109                 
110                 protected WebHeaderCollection (SerializationInfo serializationInfo, 
111                                                StreamingContext streamingContext)
112                 {
113                         // TODO: test for compatibility with ms.net
114                         int count = serializationInfo.GetInt32("count");
115                         for (int i = 0; i < count; i++) 
116                                 this.Add (serializationInfo.GetString ("k" + i),
117                                           serializationInfo.GetString ("v" + i));
118                 }
119                 
120                 internal WebHeaderCollection (bool internallyCreated)
121                 {       
122                         this.internallyCreated = internallyCreated;
123                 }               
124                 
125                 // Methods
126                 
127                 public void Add (string header)
128                 {
129                         if (header == null)
130                                 throw new ArgumentNullException ("header");
131                         int pos = header.IndexOf (':');
132                         if (pos == -1)
133                                 throw new ArgumentException ("no colon found", "header");                               
134                         this.Add (header.Substring (0, pos), 
135                                   header.Substring (pos + 1));
136                 }
137                 
138                 public override void Add (string name, string value)
139                 {
140                         if (name == null)
141                                 throw new ArgumentNullException ("name");
142                         if (internallyCreated && IsRestricted (name))
143                                 throw new ArgumentException ("This header must be modified with the appropiate property.");
144                         this.AddWithoutValidate (name, value);
145                 }
146
147                 protected void AddWithoutValidate (string headerName, string headerValue)
148                 {
149                         if (!IsHeaderName (headerName))
150                                 throw new ArgumentException ("invalid header name: " + headerName, "headerName");
151                         if (headerValue == null)
152                                 headerValue = String.Empty;
153                         else
154                                 headerValue = headerValue.Trim ();
155                         if (!IsHeaderValue (headerValue))
156                                 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
157                         base.Add (headerName, headerValue);                     
158                 }
159
160                 public override string [] GetValues (string header)
161                 {
162                         if (header == null)
163                                 throw new ArgumentNullException ("header");
164
165                         string [] values = base.GetValues (header);
166                         if (values == null || values.Length == 0)
167                                 return null;
168
169                         /*
170                         if (IsMultiValue (header)) {
171                                 values = GetMultipleValues (values);
172                         }
173                         */
174
175                         return values;
176                 }
177
178                 /* Now i wonder why this is here...
179                 static string [] GetMultipleValues (string [] values)
180                 {
181                         ArrayList mvalues = new ArrayList (values.Length);
182                         StringBuilder sb = null;
183                         for (int i = 0; i < values.Length; ++i) {
184                                 string val = values [i];
185                                 if (val.IndexOf (',') == -1) {
186                                         mvalues.Add (val);
187                                         continue;
188                                 }
189
190                                 if (sb == null)
191                                         sb = new StringBuilder ();
192
193                                 bool quote = false;
194                                 for (int k = 0; k < val.Length; k++) {
195                                         char c = val [k];
196                                         if (c == '"') {
197                                                 quote = !quote;
198                                         } else if (!quote && c == ',') {
199                                                 mvalues.Add (sb.ToString ().Trim ());
200                                                 sb.Length = 0;
201                                                 continue;
202                                         }
203                                         sb.Append (c);
204                                 }
205
206                                 if (sb.Length > 0) {
207                                         mvalues.Add (sb.ToString ().Trim ());
208                                         sb.Length = 0;
209                                 }
210                         }
211
212                         return (string []) mvalues.ToArray (typeof (string));
213                 }
214                 */
215
216                 public static bool IsRestricted (string headerName)
217                 {
218                         if (headerName == null)
219                                 throw new ArgumentNullException ("headerName");
220
221                         if (headerName == "") // MS throw nullexception here!
222                                 throw new ArgumentException ("empty string", "headerName");
223
224                         return restricted.ContainsKey (headerName);
225                 }
226
227                 public override void OnDeserialization (object sender)
228                 {
229                 }
230
231                 public override void Remove (string name)
232                 {
233                         if (name == null)
234                                 throw new ArgumentNullException ("name");
235                         if (internallyCreated && IsRestricted (name))
236                                 throw new ArgumentException ("restricted header");
237                         base.Remove (name);
238                 }
239
240                 public override void Set (string name, string value)
241                 {
242                         if (name == null)
243                                 throw new ArgumentNullException ("name");
244                         if (internallyCreated && IsRestricted (name))
245                                 throw new ArgumentException ("restricted header");
246                         if (!IsHeaderName (name))
247                                 throw new ArgumentException ("invalid header name");
248                         if (value == null)
249                                 value = String.Empty;
250                         else
251                                 value = value.Trim ();
252                         if (!IsHeaderValue (value))
253                                 throw new ArgumentException ("invalid header value");
254                         base.Set (name, value);                 
255                 }
256
257                 public byte[] ToByteArray ()
258                 {
259                         return Encoding.UTF8.GetBytes(ToString ());
260                 }
261
262                 public override string ToString ()
263                 {
264                         StringBuilder sb = new StringBuilder();
265
266                         int count = base.Count;
267                         for (int i = 0; i < count ; i++)
268                                 sb.Append (GetKey (i))
269                                   .Append (": ")
270                                   .Append (Get (i))
271                                   .Append ("\r\n");
272                                   
273                         return sb.Append("\r\n").ToString();
274                 }
275                 
276                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
277                                                   StreamingContext streamingContext)
278                 {
279                         int count = base.Count;
280                         serializationInfo.AddValue ("count", count);
281                         for (int i = 0; i < count ; i++) {
282                                 serializationInfo.AddValue ("k" + i, GetKey (i));
283                                 serializationInfo.AddValue ("v" + i, Get (i));
284                         }
285                 }
286                 
287                 // Internal Methods
288                 
289                 // With this we don't check for invalid characters in header. See bug #55994.
290                 internal void SetInternal (string header)
291                 {
292                         int pos = header.IndexOf (':');
293                         if (pos == -1)
294                                 throw new ArgumentException ("no colon found", "header");                               
295
296                         SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
297                 }
298
299                 internal void SetInternal (string name, string value)
300                 {
301                         if (value == null)
302                                 value = String.Empty;
303                         else
304                                 value = value.Trim ();
305                         if (!IsHeaderValue (value))
306                                 throw new ArgumentException ("invalid header value");
307
308                         if (IsMultiValue (name)) {
309                                 base.Add (name, value);
310                         } else {
311                                 base.Remove (name);
312                                 base.Set (name, value); 
313                         }
314                 }
315
316                 internal void RemoveAndAdd (string name, string value)
317                 {
318                         if (value == null)
319                                 value = String.Empty;
320                         else
321                                 value = value.Trim ();
322
323                         base.Remove (name);
324                         base.Set (name, value);
325                 }
326
327                 internal void RemoveInternal (string name)
328                 {
329                         if (name == null)
330                                 throw new ArgumentNullException ("name");
331                         base.Remove (name);
332                 }               
333                 
334                 // Private Methods
335                 
336                 internal static bool IsMultiValue (string headerName)
337                 {
338                         if (headerName == null || headerName == "")
339                                 return false;
340
341                         return multiValue.ContainsKey (headerName);
342                 }               
343                 
344                 internal static bool IsHeaderValue (string value)
345                 {
346                         // TEXT any 8 bit value except CTL's (0-31 and 127)
347                         //      but including \r\n space and \t
348                         //      after a newline at least one space or \t must follow
349                         //      certain header fields allow comments ()
350                                 
351                         int len = value.Length;
352                         for (int i = 0; i < len; i++) {                 
353                                 char c = value [i];
354                                 if (c == 127)
355                                         return false;
356                                 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
357                                         return false;
358                                 if (c == '\n' && ++i < len) {
359                                         c = value [i];
360                                         if (c != ' ' && c != '\t')
361                                                 return false;
362                                 }
363                         }
364                         
365                         return true;
366                 }
367                 
368                 internal static bool IsHeaderName (string name)
369                 {
370                         // token          = 1*<any CHAR except CTLs or tspecials>
371                         // tspecials      = "(" | ")" | "<" | ">" | "@"
372                         //                | "," | ";" | ":" | "\" | <">
373                         //                | "/" | "[" | "]" | "?" | "="
374                         //                | "{" | "}" | SP | HT
375                         
376                         if (name == null || name.Length == 0)
377                                 return false;
378
379                         int len = name.Length;
380                         for (int i = 0; i < len; i++) {                 
381                                 char c = name [i];
382                                 if (c < 0x20 || c >= 0x7f)
383                                         return false;
384                         }
385                         
386                         return name.IndexOfAny (tspecials) == -1;
387                 }
388
389                 private static char [] tspecials = 
390                                 new char [] {'(', ')', '<', '>', '@',
391                                              ',', ';', ':', '\\', '"',
392                                              '/', '[', ']', '?', '=',
393                                              '{', '}', ' ', '\t'};
394                                                         
395         }
396 }
397