2004-01-27 Nick Drochak <ndrochak@ieee.org>
[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 \r
11 using System;\r
12 using System.Collections;\r
13 using System.Collections.Specialized;\r
14 using System.Runtime.InteropServices;\r
15 using System.Runtime.Serialization;\r
16 using System.Text;\r
17     \r
18 // See RFC 2068 par 4.2 Message Headers\r
19     \r
20 namespace System.Net \r
21 {\r
22         [Serializable]\r
23         [ComVisible(true)]\r
24         public class WebHeaderCollection : NameValueCollection, ISerializable\r
25         {\r
26                 private static readonly Hashtable restricted;\r
27                 private static readonly Hashtable multiValue;\r
28                 private bool internallyCreated = false;\r
29                 \r
30                 // Static Initializer\r
31                 \r
32                 static WebHeaderCollection () \r
33                 {\r
34                         // the list of restricted header names as defined \r
35                         // by the ms.net spec\r
36                         restricted = new Hashtable (CaseInsensitiveHashCodeProvider.Default,\r
37                                                     CaseInsensitiveComparer.Default);\r
38 \r
39                         restricted.Add ("accept", true);\r
40                         restricted.Add ("connection", true);\r
41                         restricted.Add ("content-length", true);\r
42                         restricted.Add ("content-type", true);\r
43                         restricted.Add ("date", true);\r
44                         restricted.Add ("expect", true);\r
45                         restricted.Add ("host", true);\r
46                         restricted.Add ("range", true);\r
47                         restricted.Add ("referer", true);\r
48                         restricted.Add ("transfer-encoding", true);\r
49                         restricted.Add ("user-agent", true);                    \r
50                         \r
51                         // see par 14 of RFC 2068 to see which header names\r
52                         // accept multiple values each separated by a comma\r
53                         multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.Default,\r
54                                                     CaseInsensitiveComparer.Default);\r
55 \r
56                         multiValue.Add ("accept", true);\r
57                         multiValue.Add ("accept-charset", true);\r
58                         multiValue.Add ("accept-encoding", true);\r
59                         multiValue.Add ("accept-language", true);\r
60                         multiValue.Add ("accept-ranges", true);\r
61                         multiValue.Add ("allow", true);\r
62                         multiValue.Add ("authorization", true);\r
63                         multiValue.Add ("cache-control", true);\r
64                         multiValue.Add ("connection", true);\r
65                         multiValue.Add ("content-encoding", true);\r
66                         multiValue.Add ("content-language", true);                      \r
67                         multiValue.Add ("expect", true);                \r
68                         multiValue.Add ("if-match", true);\r
69                         multiValue.Add ("if-none-match", true);\r
70                         multiValue.Add ("proxy-authenticate", true);\r
71                         multiValue.Add ("public", true);                        \r
72                         multiValue.Add ("range", true);\r
73                         multiValue.Add ("transfer-encoding", true);\r
74                         multiValue.Add ("upgrade", true);\r
75                         multiValue.Add ("vary", true);\r
76                         multiValue.Add ("via", true);\r
77                         multiValue.Add ("warning", true);\r
78                 }\r
79                 \r
80                 // Constructors\r
81                 \r
82                 public WebHeaderCollection () { }       \r
83                 \r
84                 protected WebHeaderCollection (SerializationInfo serializationInfo, \r
85                                                StreamingContext streamingContext)\r
86                 {\r
87                         // TODO: test for compatibility with ms.net\r
88                         int count = serializationInfo.GetInt32("count");\r
89                         for (int i = 0; i < count; i++) \r
90                                 this.Add (serializationInfo.GetString ("k" + i),\r
91                                           serializationInfo.GetString ("v" + i));\r
92                 }\r
93                 \r
94                 internal WebHeaderCollection (bool internallyCreated)\r
95                 {       \r
96                         this.internallyCreated = internallyCreated;\r
97                 }               \r
98                 \r
99                 // Methods\r
100                 \r
101                 public void Add (string header)\r
102                 {\r
103                         if (header == null)\r
104                                 throw new ArgumentNullException ("header");\r
105                         int pos = header.IndexOf (':');\r
106                         if (pos == -1)\r
107                                 throw new ArgumentException ("no colon found", "header");                               \r
108                         this.Add (header.Substring (0, pos), \r
109                                   header.Substring (pos + 1));\r
110                 }\r
111                 \r
112                 public override void Add (string name, string value)\r
113                 {\r
114                         if (name == null)\r
115                                 throw new ArgumentNullException ("name");\r
116                         if (internallyCreated && IsRestricted (name))\r
117                                 throw new ArgumentException ("restricted header");\r
118                         this.AddWithoutValidate (name, value);\r
119                 }\r
120 \r
121                 protected void AddWithoutValidate (string headerName, string headerValue)\r
122                 {\r
123                         if (!IsHeaderName (headerName))\r
124                                 throw new ArgumentException ("invalid header name: " + headerName, "headerName");\r
125                         if (headerValue == null)\r
126                                 headerValue = String.Empty;\r
127                         else\r
128                                 headerValue = headerValue.Trim ();\r
129                         if (!IsHeaderValue (headerValue))\r
130                                 throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");\r
131                         base.Add (headerName, headerValue);                     \r
132                 }\r
133                 \r
134                 public override string [] GetValues (string header)\r
135                 {\r
136                         if (header == null)\r
137                                 throw new ArgumentNullException ("header");\r
138                         string [] values = base.GetValues (header);\r
139                         if (values == null || values.Length == 0) \r
140                                 return null;\r
141                         if (!IsMultiValue (header))\r
142                                 return values;\r
143                         StringCollection col = new StringCollection ();\r
144                         for (int i = 0; i < values.Length; i++) {\r
145                                 string [] s = values [i].Split (new char [] {','});\r
146                                 for (int j = 0; j < s.Length; j++) \r
147                                         s [j] = s [j].Trim ();\r
148                                 col.AddRange (s);\r
149                         }\r
150                         values = new string [col.Count];\r
151                         col.CopyTo (values, 0);\r
152                         return values;\r
153                 }\r
154 \r
155                 public static bool IsRestricted (string headerName)\r
156                 {\r
157                         if (headerName == null)\r
158                                 throw new ArgumentNullException ("headerName");\r
159 \r
160                         if (headerName == "") // MS throw nullexception here!\r
161                                 throw new ArgumentException ("empty string", "headerName");\r
162 \r
163                         return restricted.ContainsKey (headerName);\r
164                 }\r
165 \r
166                 public override void OnDeserialization (object sender)\r
167                 {\r
168                 }\r
169 \r
170                 public override void Remove (string name)\r
171                 {\r
172                         if (name == null)\r
173                                 throw new ArgumentNullException ("name");\r
174                         if (internallyCreated && IsRestricted (name))\r
175                                 throw new ArgumentException ("restricted header");\r
176                         base.Remove (name);\r
177                 }\r
178 \r
179                 public override void Set (string name, string value)\r
180                 {\r
181                         if (name == null)\r
182                                 throw new ArgumentNullException ("name");\r
183                         if (internallyCreated && IsRestricted (name))\r
184                                 throw new ArgumentException ("restricted header");\r
185                         if (!IsHeaderName (name))\r
186                                 throw new ArgumentException ("invalid header name");\r
187                         if (value == null)\r
188                                 value = String.Empty;\r
189                         else\r
190                                 value = value.Trim ();\r
191                         if (!IsHeaderValue (value))\r
192                                 throw new ArgumentException ("invalid header value");\r
193                         base.Set (name, value);                 \r
194                 }\r
195 \r
196                 public byte[] ToByteArray ()\r
197                 {\r
198                         return Encoding.UTF8.GetBytes(ToString ());\r
199                 }\r
200 \r
201                 public override string ToString ()\r
202                 {\r
203                         StringBuilder sb = new StringBuilder();\r
204 \r
205                         int count = base.Count;\r
206                         for (int i = 0; i < count ; i++)\r
207                                 sb.Append (GetKey (i))\r
208                                   .Append (": ")\r
209                                   .Append (Get (i))\r
210                                   .Append ("\r\n");\r
211                                   \r
212                         return sb.Append("\r\n").ToString();\r
213                 }\r
214                 \r
215                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,\r
216                                                   StreamingContext streamingContext)\r
217                 {\r
218                         int count = base.Count;\r
219                         serializationInfo.AddValue ("count", count);\r
220                         for (int i = 0; i < count ; i++) {\r
221                                 serializationInfo.AddValue ("k" + i, GetKey (i));\r
222                                 serializationInfo.AddValue ("v" + i, Get (i));\r
223                         }\r
224                 }\r
225                 \r
226                 // Internal Methods\r
227                 \r
228                 internal void SetInternal (string name, string value)\r
229                 {\r
230                         if (value == null)\r
231                                 value = String.Empty;\r
232                         else\r
233                                 value = value.Trim ();\r
234                         if (!IsHeaderValue (value))\r
235                                 throw new ArgumentException ("invalid header value");\r
236 \r
237                         base.Remove (name);\r
238                         base.Set (name, value); \r
239                 }\r
240                 \r
241                 internal void RemoveInternal (string name)\r
242                 {\r
243                         if (name == null)\r
244                                 throw new ArgumentNullException ("name");\r
245                         base.Remove (name);\r
246                 }               \r
247                 \r
248                 // Private Methods\r
249                 \r
250                 internal static bool IsMultiValue (string headerName)\r
251                 {\r
252                         if (headerName == null || headerName == "")\r
253                                 return false;\r
254 \r
255                         return multiValue.ContainsKey (headerName);\r
256                 }               \r
257                 \r
258                 internal static bool IsHeaderValue (string value)\r
259                 {\r
260                         // TEXT any 8 bit value except CTL's (0-31 and 127)\r
261                         //      but including \r\n space and \t\r
262                         //      after a newline at least one space or \t must follow\r
263                         //      certain header fields allow comments ()\r
264                                 \r
265                         int len = value.Length;\r
266                         for (int i = 0; i < len; i++) {                 \r
267                                 char c = value [i];\r
268                                 if (c == 127)\r
269                                         return false;\r
270                                 if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))\r
271                                         return false;\r
272                                 if (c == '\n' && ++i < len) {\r
273                                         c = value [i];\r
274                                         if (c != ' ' && c != '\t')\r
275                                                 return false;\r
276                                 }\r
277                         }\r
278                         \r
279                         return true;\r
280                 }\r
281                 \r
282                 internal static bool IsHeaderName (string name)\r
283                 {\r
284                         // token          = 1*<any CHAR except CTLs or tspecials>\r
285                         // tspecials      = "(" | ")" | "<" | ">" | "@"\r
286                         //                | "," | ";" | ":" | "\" | <">\r
287                         //                | "/" | "[" | "]" | "?" | "="\r
288                         //                | "{" | "}" | SP | HT\r
289                         \r
290                         if (name == null || name.Length == 0)\r
291                                 return false;\r
292 \r
293                         int len = name.Length;\r
294                         for (int i = 0; i < len; i++) {                 \r
295                                 char c = name [i];\r
296                                 if (c < 0x20 || c >= 0x7f)\r
297                                         return false;\r
298                         }\r
299                         \r
300                         return name.IndexOfAny (tspecials) == -1;\r
301                 }\r
302 \r
303                 private static char [] tspecials = \r
304                                 new char [] {'(', ')', '<', '>', '@',\r
305                                              ',', ';', ':', '\\', '"',\r
306                                              '/', '[', ']', '?', '=',\r
307                                              '{', '}', ' ', '\t'};\r
308                                                         \r
309         }\r
310 }\r