Implemented Servlet session management. Servlet hosting moved to Mainsoft.Web package
[mono.git] / mcs / class / System / System.Net / HttpListenerRequest.cs
1 //
2 // System.Net.HttpListenerRequest
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //
7 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 #if NET_2_0 && SECURITY_DEP
30
31 using System.Collections;
32 using System.Collections.Specialized;
33 using System.Globalization;
34 using System.IO;
35 using System.Security.Cryptography.X509Certificates;
36 using System.Text;
37 namespace System.Net {
38         public sealed class HttpListenerRequest
39         {
40                 string [] accept_types;
41                 int client_cert_error;
42                 Encoding content_encoding;
43                 long content_length;
44                 bool cl_set;
45                 CookieCollection cookies;
46                 WebHeaderCollection headers;
47                 string method;
48                 Stream input_stream;
49                 bool is_authenticated;
50                 Version version;
51                 NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
52                 string raw_url;
53                 Guid identifier;
54                 Uri url;
55                 Uri referrer;
56                 string [] user_languages;
57                 bool no_get_certificate;
58                 HttpListenerContext context;
59                 bool is_chunked;
60                 static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
61
62                 internal HttpListenerRequest (HttpListenerContext context)
63                 {
64                         this.context = context;
65                         headers = new WebHeaderCollection ();
66                         input_stream = Stream.Null;
67                 }
68
69                 static char [] separators = new char [] { ' ' };
70                 // From WebRequestMethods.Http
71                 static readonly string [] methods = new string [] { "GET", "POST", "HEAD",
72                                                                 "PUT", "CONNECT", "MKCOL" };
73                 internal void SetRequestLine (string req)
74                 {
75                         string [] parts = req.Split (separators, 3);
76                         if (parts.Length != 3) {
77                                 context.ErrorMessage = "Invalid request line (parts).";
78                                 return;
79                         }
80
81                         method = parts [0];
82                         if (Array.IndexOf (methods, method) == -1) {
83                                 context.ErrorMessage = "Invalid request line (verb).";
84                                 return;
85                         }
86
87                         raw_url = parts [1];
88                         if (parts [2].Length != 8 || !parts [2].StartsWith ("HTTP/")) {
89                                 context.ErrorMessage = "Invalid request line (version).";
90                                 return;
91                         }
92
93                         try {
94                                 version = new Version (parts [2].Substring (5));
95                                 if (version.Major < 1)
96                                         throw new Exception ();
97                         } catch {
98                                 context.ErrorMessage = "Invalid request line (version).";
99                                 return;
100                         }
101                 }
102
103                 void CreateQueryString (string query)
104                 {
105                         query_string = new NameValueCollection ();
106                         if (query == null || query.Length == 0)
107                                 return;
108
109                         string [] components = query.Split ('&');
110                         foreach (string kv in components) {
111                                 int pos = kv.IndexOf ('=');
112                                 if (pos == -1) {
113                                         query_string.Add (null, HttpUtility.UrlDecode (kv));
114                                 } else {
115                                         string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
116                                         string val = HttpUtility.UrlDecode (kv.Substring (pos + 1));
117                                         
118                                         query_string.Add (key, val);
119                                 }
120                         }
121                 }
122
123                 internal void FinishInitialization ()
124                 {
125                         string host = UserHostName;
126                         if (version > HttpVersion.Version10 && (host == null || host == "")) {
127                                 context.ErrorMessage = "Invalid host name";
128                                 return;
129                         }
130
131                         if (host == null || host == "")
132                                 host = UserHostAddress;
133
134                         int colon = host.IndexOf (':');
135                         if (colon >= 0)
136                                 host = host.Substring (0, colon);
137
138                         string base_uri = String.Format ("{0}://{1}:{2}",
139                                                                 (IsSecureConnection) ? "https" : "http",
140                                                                 host,
141                                                                 LocalEndPoint.Port);
142                         try {
143                                 url = new Uri (base_uri + raw_url);
144                         } catch {
145                                 context.ErrorMessage = "Invalid url";
146                                 return;
147                         }
148
149                         CreateQueryString (url.Query);
150
151                         if (method == "GET" || method == "HEAD")
152                                 return;
153
154                         string t_encoding = null;
155                         if (version >= HttpVersion.Version11) {
156                                 t_encoding = Headers ["Transfer-Encoding"];
157                                 // 'identity' is not valid!
158                                 if (t_encoding != null && t_encoding != "chunked") {
159                                         context.Connection.SendError (null, 501);
160                                         return;
161                                 }
162                         }
163
164                         bool is_chunked = (t_encoding == "chunked");
165                         if (!is_chunked && !cl_set) {
166                                 context.Connection.SendError (null, 411);
167                                 return;
168                         }
169
170                         if (is_chunked || content_length > 0) {
171                                 input_stream = context.Connection.GetRequestStream (is_chunked, content_length);
172                         }
173
174                         if (Headers ["Expect"] == "100-continue") {
175                                 ResponseStream output = context.Connection.GetResponseStream ();
176                                 output.InternalWrite (_100continue, 0, _100continue.Length);
177                         }
178                 }
179
180                 internal void AddHeader (string header)
181                 {
182                         int colon = header.IndexOf (':');
183                         if (colon == -1 || colon == 0) {
184                                 context.ErrorMessage = "Bad Request";
185                                 return;
186                         }
187
188                         string name = header.Substring (0, colon).Trim ();
189                         string val = header.Substring (colon + 1).Trim ();
190                         string lower = name.ToLower (CultureInfo.InvariantCulture);
191                         headers.SetInternal (name, val);
192                         switch (lower) {
193                                 case "accept-language":
194                                         user_languages = val.Split (','); // yes, only split with a ','
195                                         break;
196                                 case "accept-types":
197                                         accept_types = val.Split (','); // yes, only split with a ','
198                                         break;
199                                 case "content-length":
200                                         try {
201                                                 //TODO: max. content_length?
202                                                 content_length = Int64.Parse (val.Trim ());
203                                                 if (content_length < 0)
204                                                         context.ErrorMessage = "Invalid Content-Length.";
205                                                 cl_set = true;
206                                         } catch {
207                                                 context.ErrorMessage = "Invalid Content-Length.";
208                                         }
209
210                                         break;
211                                 case "referer":
212                                         try {
213                                                 referrer = new Uri (val);
214                                         } catch {
215                                                 referrer = new Uri ("http://someone.is.screwing.with.the.headers.com/");
216                                         }
217                                         break;
218                                 //TODO: cookie headers
219                         }
220                 }
221
222                 public string [] AcceptTypes {
223                         get { return accept_types; }
224                 }
225
226                 public int ClientCertificateError {
227                         get {
228                                 if (no_get_certificate)
229                                         throw new InvalidOperationException (
230                                                 "Call GetClientCertificate() before calling this method.");
231                                 return client_cert_error;
232                         }
233                 }
234
235                 public Encoding ContentEncoding {
236                         get {
237                                 if (content_encoding == null)
238                                         content_encoding = Encoding.Default;
239                                 return content_encoding;
240                         }
241                 }
242
243                 public long ContentLength64 {
244                         get { return content_length; }
245                 }
246
247                 public string ContentType {
248                         get { return headers ["content-type"]; }
249                 }
250
251                 public CookieCollection Cookies {
252                         get {
253                                 // TODO: check if the collection is read-only
254                                 if (cookies == null)
255                                         cookies = new CookieCollection ();
256                                 return cookies;
257                         }
258                 }
259
260                 public bool HasEntityBody {
261                         get { return (method == "GET" || method == "HEAD" || content_length <= 0 || is_chunked); }
262                 }
263
264                 public NameValueCollection Headers {
265                         get { return headers; }
266                 }
267
268                 public string HttpMethod {
269                         get { return method; }
270                 }
271
272                 public Stream InputStream {
273                         get { return input_stream; }
274                 }
275
276                 public bool IsAuthenticated {
277                         get { return is_authenticated; }
278                 }
279
280                 public bool IsLocal {
281                         get { return IPAddress.IsLoopback (RemoteEndPoint.Address); }
282                 }
283
284                 public bool IsSecureConnection {
285                         get { return context.Connection.IsSecure; } 
286                 }
287
288                 public bool KeepAlive {
289                         get { return false; }
290                 }
291
292                 public IPEndPoint LocalEndPoint {
293                         get { return context.Connection.LocalEndPoint; }
294                 }
295
296                 public Version ProtocolVersion {
297                         get { return version; }
298                 }
299
300                 public NameValueCollection QueryString {
301                         get { return query_string; }
302                 }
303
304                 public string RawUrl {
305                         get { return raw_url; }
306                 }
307
308                 public IPEndPoint RemoteEndPoint {
309                         get { return context.Connection.RemoteEndPoint; }
310                 }
311
312                 public Guid RequestTraceIdentifier {
313                         get { return identifier; }
314                 }
315
316                 public Uri Url {
317                         get { return url; }
318                 }
319
320                 public Uri UrlReferrer {
321                         get { return referrer; }
322                 }
323
324                 public string UserAgent {
325                         get { return headers ["user-agent"]; }
326                 }
327
328                 public string UserHostAddress {
329                         get { return LocalEndPoint.ToString (); }
330                 }
331
332                 public string UserHostName {
333                         get { return headers ["host"]; }
334                 }
335
336                 public string [] UserLanguages {
337                         get { return user_languages; }
338                 }
339
340                 public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, Object state)
341                 {
342                         return null;
343                 }
344 #if SECURITY_DEP
345                 public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
346                 {
347                         return null;
348                         // set no_client_certificate once done.
349                 }
350
351                 public X509Certificate2 GetClientCertificate ()
352                 {
353                         // set no_client_certificate once done.
354
355                         // InvalidOp if call in progress.
356                         return null;
357                 }
358 #endif
359         }
360 }
361 #endif
362