2003-04-11 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / System / System.Net / WebHeaderCollection.cs
1 //\r
2 // System.Net.WebHeaderCollection\r
3 //\r
4 // Author:\r
5 //   Lawrence Pit (loz@cable.a2000.nl)\r
6 //\r
7 \r
8 using System;\r
9 using System.Collections;\r
10 using System.Collections.Specialized;\r
11 using System.Runtime.InteropServices;\r
12 using System.Runtime.Serialization;\r
13 using System.Text;\r
14     \r
15 // See RFC 2068 par 4.2 Message Headers\r
16     \r
17 namespace System.Net \r
18 {\r
19         [Serializable]\r
20         [ComVisible(true)]\r
21         public class WebHeaderCollection : NameValueCollection, ISerializable\r
22         {\r
23                 private static readonly int [] restricted;\r
24                 private static readonly int [] multiValue;\r
25                 private bool internallyCreated = false;\r
26                 \r
27                 // Static Initializer\r
28                 \r
29                 static WebHeaderCollection () \r
30                 {\r
31                         // For performance reasons we initialize the following\r
32                         // tables by taking the hashcode of header names.\r
33                         // When you add a header make sure all characters are in \r
34                         // lowercase.\r
35                         \r
36                         // the list of restricted header names as defined \r
37                         // by the ms.net spec\r
38                         ArrayList a = new ArrayList ();\r
39                         a.Add ("accept".GetHashCode ());\r
40                         a.Add ("connection".GetHashCode ());\r
41                         a.Add ("content-length".GetHashCode ());\r
42                         a.Add ("content-type".GetHashCode ());\r
43                         a.Add ("date".GetHashCode ());\r
44                         a.Add ("expect".GetHashCode ());    // ??? What is this anyway?\r
45                         a.Add ("host".GetHashCode ());\r
46                         a.Add ("range".GetHashCode ());\r
47                         a.Add ("referer".GetHashCode ());\r
48                         a.Add ("transfer-encoding".GetHashCode ());\r
49                         a.Add ("user-agent".GetHashCode ());                    \r
50                         restricted = (int []) a.ToArray (typeof (int));\r
51                         \r
52                         // see par 14 of RFC 2068 to see which header names\r
53                         // accept multiple values each separated by a comma\r
54                         a = new ArrayList ();\r
55                         a.Add ("accept".GetHashCode ());\r
56                         a.Add ("accept-charset".GetHashCode ());\r
57                         a.Add ("accept-encoding".GetHashCode ());\r
58                         a.Add ("accept-language".GetHashCode ());\r
59                         a.Add ("accept-ranges".GetHashCode ());\r
60                         a.Add ("allow".GetHashCode ());\r
61                         a.Add ("authorization".GetHashCode ());\r
62                         a.Add ("cache-control".GetHashCode ());\r
63                         a.Add ("connection".GetHashCode ());\r
64                         a.Add ("content-encoding".GetHashCode ());\r
65                         a.Add ("content-language".GetHashCode ());                      \r
66                         a.Add ("expect".GetHashCode ());                \r
67                         a.Add ("if-match".GetHashCode ());\r
68                         a.Add ("if-none-match".GetHashCode ());\r
69                         a.Add ("proxy-authenticate".GetHashCode ());\r
70                         a.Add ("public".GetHashCode ());                        \r
71                         a.Add ("range".GetHashCode ());\r
72                         a.Add ("transfer-encoding".GetHashCode ());\r
73                         a.Add ("upgrade".GetHashCode ());\r
74                         a.Add ("vary".GetHashCode ());\r
75                         a.Add ("via".GetHashCode ());\r
76                         a.Add ("warning".GetHashCode ());\r
77                         multiValue = (int []) a.ToArray (typeof (int));\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 dummy) : base ()\r
95                 {       \r
96                         this.internallyCreated = true;\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");                         \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");\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");\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                         int hashCode = headerName.ToLower ().GetHashCode ();\r
158                         for (int i = 0; i < restricted.Length; i++) \r
159                                 if (restricted [i] == hashCode)\r
160                                         return true;\r
161                         return false;\r
162                 }\r
163 \r
164                 [MonoTODO]\r
165                 public override void OnDeserialization (object sender)\r
166                 {\r
167                         // no idea what to do here... spec doesn't say much\r
168                         throw new NotImplementedException ();\r
169                 }\r
170 \r
171                 public override void Remove (string name)\r
172                 {\r
173                         if (name == null)\r
174                                 throw new ArgumentNullException ("name");\r
175                         if (internallyCreated && IsRestricted (name))\r
176                                 throw new ArgumentException ("restricted header");\r
177                         base.Remove (name);\r
178                 }\r
179 \r
180                 public override void Set (string name, string value)\r
181                 {\r
182                         if (name == null)\r
183                                 throw new ArgumentNullException ("name");\r
184                         if (internallyCreated && IsRestricted (name))\r
185                                 throw new ArgumentException ("restricted header");\r
186                         if (!IsHeaderName (name))\r
187                                 throw new ArgumentException ("invalid header name");\r
188                         if (value == null)\r
189                                 value = String.Empty;\r
190                         else\r
191                                 value = value.Trim ();\r
192                         if (!IsHeaderValue (value))\r
193                                 throw new ArgumentException ("invalid header value");\r
194                         base.Set (name, value);                 \r
195                 }\r
196 \r
197                 public byte[] ToByteArray ()\r
198                 {\r
199                         return Encoding.UTF8.GetBytes(ToString ());\r
200                 }\r
201 \r
202                 public override string ToString ()\r
203                 {\r
204                         StringBuilder sb = new StringBuilder();\r
205 \r
206                         int count = base.Count;\r
207                         for (int i = 0; i < count ; i++)\r
208                                 sb.Append (GetKey (i))\r
209                                   .Append (": ")\r
210                                   .Append (Get (i))\r
211                                   .Append ("\r\n");\r
212                                   \r
213                         return sb.Append("\r\n").ToString();\r
214                 }\r
215                 \r
216                 void ISerializable.GetObjectData (SerializationInfo serializationInfo,\r
217                                                   StreamingContext streamingContext)\r
218                 {\r
219                         int count = base.Count;\r
220                         serializationInfo.AddValue ("count", count);\r
221                         for (int i = 0; i < count ; i++) {\r
222                                 serializationInfo.AddValue ("k" + i, GetKey (i));\r
223                                 serializationInfo.AddValue ("v" + i, Get (i));\r
224                         }\r
225                 }\r
226                 \r
227                 // Internal Methods\r
228                 \r
229                 internal void SetInternal (string name, string value)\r
230                 {\r
231                         if (value == null)\r
232                                 value = String.Empty;\r
233                         else\r
234                                 value = value.Trim ();\r
235                         if (!IsHeaderValue (value))\r
236                                 throw new ArgumentException ("invalid header value");\r
237                         base.Set (name, value); \r
238                 }\r
239                 \r
240                 internal void RemoveInternal (string name)\r
241                 {\r
242                         if (name == null)\r
243                                 throw new ArgumentNullException ("name");\r
244                         base.Remove (name);\r
245                 }               \r
246                 \r
247                 // Private Methods\r
248                 \r
249                 private static bool IsMultiValue (string headerName)\r
250                 {\r
251                         int hashCode = headerName.ToLower ().GetHashCode ();\r
252                         for (int i = 0; i < multiValue.Length; i++) \r
253                                 if (multiValue [i] == hashCode)\r
254                                         return true;\r
255                         return false;\r
256                 }               \r
257                 \r
258                 private 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                 private 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 }