updating to the latest module.
[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 ("if-modified-since", true);\r
68                         restricted.Add ("range", true);\r
69                         restricted.Add ("referer", true);\r
70                         restricted.Add ("transfer-encoding", true);\r
71                         restricted.Add ("user-agent", true);                    \r
72                         \r
73                         // see par 14 of RFC 2068 to see which header names\r
74                         // accept multiple values each separated by a comma\r
75                         multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.Default,\r
76                                                     CaseInsensitiveComparer.Default);\r
77 \r
78                         multiValue.Add ("accept", true);\r
79                         multiValue.Add ("accept-charset", true);\r
80                         multiValue.Add ("accept-encoding", true);\r
81                         multiValue.Add ("accept-language", true);\r
82                         multiValue.Add ("accept-ranges", true);\r
83                         multiValue.Add ("allow", true);\r
84                         multiValue.Add ("authorization", true);\r
85                         multiValue.Add ("cache-control", true);\r
86                         multiValue.Add ("connection", true);\r
87                         multiValue.Add ("content-encoding", true);\r
88                         multiValue.Add ("content-language", true);                      \r
89                         multiValue.Add ("expect", true);                \r
90                         multiValue.Add ("if-match", true);\r
91                         multiValue.Add ("if-none-match", true);\r
92                         multiValue.Add ("proxy-authenticate", true);\r
93                         multiValue.Add ("public", true);                        \r
94                         multiValue.Add ("range", true);\r
95                         multiValue.Add ("transfer-encoding", true);\r
96                         multiValue.Add ("upgrade", true);\r
97                         multiValue.Add ("vary", true);\r
98                         multiValue.Add ("via", true);\r
99                         multiValue.Add ("warning", true);\r
100
101                         // Extra
102                         multiValue.Add ("set-cookie", true);\r
103                         multiValue.Add ("set-cookie2", true);\r
104                 }\r
105                 \r
106                 // Constructors\r
107                 \r
108                 public WebHeaderCollection () { }       \r
109                 \r
110                 protected WebHeaderCollection (SerializationInfo serializationInfo, \r
111                                                StreamingContext streamingContext)\r
112                 {\r
113                         // TODO: test for compatibility with ms.net\r
114                         int count = serializationInfo.GetInt32("count");\r
115                         for (int i = 0; i < count; i++) \r
116                                 this.Add (serializationInfo.GetString ("k" + i),\r
117                                           serializationInfo.GetString ("v" + i));\r
118                 }\r
119                 \r
120                 internal WebHeaderCollection (bool internallyCreated)\r
121                 {       \r
122                         this.internallyCreated = internallyCreated;\r
123                 }               \r
124                 \r
125                 // Methods\r
126                 \r
127                 public void Add (string header)\r
128                 {\r
129                         if (header == null)\r
130                                 throw new ArgumentNullException ("header");\r
131                         int pos = header.IndexOf (':');\r
132                         if (pos == -1)\r
133                                 throw new ArgumentException ("no colon found", "header");                               \r
134                         this.Add (header.Substring (0, pos), \r
135                                   header.Substring (pos + 1));\r
136                 }\r
137                 \r
138                 public override void Add (string name, string value)\r
139                 {\r
140                         if (name == null)\r
141                                 throw new ArgumentNullException ("name");\r
142                         if (internallyCreated && IsRestricted (name))\r
143                                 throw new ArgumentException ("This header must be modified with the appropiate property.");
144                         this.AddWithoutValidate (name, value);\r
145                 }\r
146 \r
147                 protected void AddWithoutValidate (string headerName, string headerValue)\r
148                 {\r
149                         if (!IsHeaderName (headerName))\r
150                                 throw new ArgumentException ("invalid header name: " + headerName, "headerName");\r
151                         if (headerValue == null)\r
152                                 headerValue = String.Empty;\r
153                         else\r
154                                 headerValue = headerValue.Trim ();\r
155                         if (!IsHeaderValue (headerValue))\r
156                                 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");\r
157                         base.Add (headerName, headerValue);                     \r
158                 }\r
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)\r
217                 {\r
218                         if (headerName == null)\r
219                                 throw new ArgumentNullException ("headerName");\r
220 \r
221                         if (headerName == "") // MS throw nullexception here!\r
222                                 throw new ArgumentException ("empty string", "headerName");\r
223 \r
224                         return restricted.ContainsKey (headerName);\r
225                 }\r
226 \r
227                 public override void OnDeserialization (object sender)\r
228                 {\r
229                 }\r
230 \r
231                 public override void Remove (string name)\r
232                 {\r
233                         if (name == null)\r
234                                 throw new ArgumentNullException ("name");\r
235                         if (internallyCreated && IsRestricted (name))\r
236                                 throw new ArgumentException ("restricted header");\r
237                         base.Remove (name);\r
238                 }\r
239 \r
240                 public override void Set (string name, string value)\r
241                 {\r
242                         if (name == null)\r
243                                 throw new ArgumentNullException ("name");\r
244                         if (internallyCreated && IsRestricted (name))\r
245                                 throw new ArgumentException ("restricted header");\r
246                         if (!IsHeaderName (name))\r
247                                 throw new ArgumentException ("invalid header name");\r
248                         if (value == null)\r
249                                 value = String.Empty;\r
250                         else\r
251                                 value = value.Trim ();\r
252                         if (!IsHeaderValue (value))\r
253                                 throw new ArgumentException ("invalid header value");\r
254                         base.Set (name, value);                 \r
255                 }\r
256 \r
257                 public byte[] ToByteArray ()\r
258                 {\r
259                         return Encoding.UTF8.GetBytes(ToString ());\r
260                 }\r
261 \r
262                 public override string ToString ()\r
263                 {\r
264                         StringBuilder sb = new StringBuilder();\r
265 \r
266                         int count = base.Count;\r
267                         for (int i = 0; i < count ; i++)\r
268                                 sb.Append (GetKey (i))\r
269                                   .Append (": ")\r
270                                   .Append (Get (i))\r
271                                   .Append ("\r\n");\r
272                                   \r
273                         return sb.Append("\r\n").ToString();\r
274                 }\r
275                 \r
276                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,\r
277                                                   StreamingContext streamingContext)\r
278                 {\r
279                         int count = base.Count;\r
280                         serializationInfo.AddValue ("count", count);\r
281                         for (int i = 0; i < count ; i++) {\r
282                                 serializationInfo.AddValue ("k" + i, GetKey (i));\r
283                                 serializationInfo.AddValue ("v" + i, Get (i));\r
284                         }\r
285                 }\r
286                 \r
287                 // Internal Methods\r
288                 \r
289                 // With this we don't check for invalid characters in header. See bug #55994.\r
290                 internal void SetInternal (string header)\r
291                 {\r
292                         int pos = header.IndexOf (':');\r
293                         if (pos == -1)\r
294                                 throw new ArgumentException ("no colon found", "header");                               \r
295 \r
296                         SetInternal (header.Substring (0, pos), header.Substring (pos + 1));\r
297                 }\r
298 \r
299                 internal void SetInternal (string name, string value)\r
300                 {\r
301                         if (value == null)\r
302                                 value = String.Empty;\r
303                         else\r
304                                 value = value.Trim ();\r
305                         if (!IsHeaderValue (value))\r
306                                 throw new ArgumentException ("invalid header value");\r
307 \r
308                         if (IsMultiValue (name)) {
309                                 base.Add (name, value);
310                         } else {
311                                 base.Remove (name);\r
312                                 base.Set (name, value); \r
313                         }
314                 }\r
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)\r
328                 {\r
329                         if (name == null)\r
330                                 throw new ArgumentNullException ("name");\r
331                         base.Remove (name);\r
332                 }               \r
333                 \r
334                 // Private Methods\r
335                 \r
336                 internal static bool IsMultiValue (string headerName)\r
337                 {\r
338                         if (headerName == null || headerName == "")\r
339                                 return false;\r
340 \r
341                         return multiValue.ContainsKey (headerName);\r
342                 }               \r
343                 \r
344                 internal static bool IsHeaderValue (string value)\r
345                 {\r
346                         // TEXT any 8 bit value except CTL's (0-31 and 127)\r
347                         //      but including \r\n space and \t\r
348                         //      after a newline at least one space or \t must follow\r
349                         //      certain header fields allow comments ()\r
350                                 \r
351                         int len = value.Length;\r
352                         for (int i = 0; i < len; i++) {                 \r
353                                 char c = value [i];\r
354                                 if (c == 127)\r
355                                         return false;\r
356                                 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))\r
357                                         return false;\r
358                                 if (c == '\n' && ++i < len) {\r
359                                         c = value [i];\r
360                                         if (c != ' ' && c != '\t')\r
361                                                 return false;\r
362                                 }\r
363                         }\r
364                         \r
365                         return true;\r
366                 }\r
367                 \r
368                 internal static bool IsHeaderName (string name)\r
369                 {\r
370                         // token          = 1*<any CHAR except CTLs or tspecials>\r
371                         // tspecials      = "(" | ")" | "<" | ">" | "@"\r
372                         //                | "," | ";" | ":" | "\" | <">\r
373                         //                | "/" | "[" | "]" | "?" | "="\r
374                         //                | "{" | "}" | SP | HT\r
375                         \r
376                         if (name == null || name.Length == 0)\r
377                                 return false;\r
378 \r
379                         int len = name.Length;\r
380                         for (int i = 0; i < len; i++) {                 \r
381                                 char c = name [i];\r
382                                 if (c < 0x20 || c >= 0x7f)\r
383                                         return false;\r
384                         }\r
385                         \r
386                         return name.IndexOfAny (tspecials) == -1;\r
387                 }\r
388 \r
389                 private static char [] tspecials = \r
390                                 new char [] {'(', ')', '<', '>', '@',\r
391                                              ',', ';', ':', '\\', '"',\r
392                                              '/', '[', ']', '?', '=',\r
393                                              '{', '}', ' ', '\t'};\r
394                                                         \r
395         }\r
396 }\r