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