2005-11-02 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpRequest.cs
1 //
2 // System.Web.HttpRequest.cs 
3 //
4 // 
5 // Author:
6 //      Miguel de Icaza (miguel@novell.com)
7 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
8 //
9
10 //
11 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32 using System.Text;
33 using System.Collections;
34 using System.Collections.Specialized;
35 using System.IO;
36 using System.Runtime.InteropServices;
37 using System.Security;
38 using System.Security.Permissions;
39 using System.Web.Configuration;
40 using System.Web.UI;
41 using System.Web.Util;
42 using System.Globalization;
43
44 namespace System.Web {
45         
46         // CAS - no InheritanceDemand here as the class is sealed
47         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
48         public sealed class HttpRequest {
49                 HttpWorkerRequest worker_request;
50                 HttpContext context;
51                 WebROCollection query_string_nvc;
52
53                 //
54                 string filename, query_string;
55                 UriBuilder uri_builder;
56
57                 string client_target;
58
59                 //
60                 // On-demand computed values
61                 //
62                 HttpBrowserCapabilities browser_capabilities;
63                 string file_path, base_virtual_dir, root_virtual_dir;
64                 string content_type;
65                 int content_length = -1;
66                 Encoding encoding;
67                 string current_exe_path;
68                 string physical_path;
69                 string path_info;
70                 WebROCollection all_params;
71                 WebROCollection headers;
72                 Stream input_stream;
73                 InputFilterStream input_filter;
74                 Stream filter;
75                 HttpCookieCollection cookies;
76                 string http_method;
77
78                 WebROCollection form;
79                 HttpFileCollection files;
80                 
81                 ServerVariablesCollection server_variables;
82                 HttpClientCertificate client_cert;
83                 
84                 string request_type;
85                 string [] accept_types;
86                 string [] user_languages;
87                 
88                 // Validations
89                 bool validate_cookies, validate_query_string, validate_form;
90                 bool checked_cookies, checked_query_string, checked_form;
91                 
92                 public HttpRequest (string filename, string url, string queryString)
93                 {
94                         // warning 169: what are we supposed to do with filename?
95                         
96                         this.filename = filename;
97
98                         uri_builder = new UriBuilder (url);
99                         query_string = queryString;
100                 }
101
102                 void InitUriBuilder ()
103                 {
104                         uri_builder = new UriBuilder ();
105                         uri_builder.Scheme = worker_request.GetProtocol ();
106                         uri_builder.Host = worker_request.GetServerName ();
107                         int port = worker_request.GetLocalPort ();
108                         uri_builder.Port = port;
109                         uri_builder.Path = worker_request.GetUriPath ();
110                         if (query_string != null && query_string != "")
111                                 uri_builder.Query = query_string;
112                 }
113                 
114                 internal HttpRequest (HttpWorkerRequest worker_request, HttpContext context)
115                 {
116                         this.worker_request = worker_request;
117                         this.context = context;
118                         if (worker_request != null)
119                                 query_string = worker_request.GetQueryString ();
120                 }
121
122                 string [] SplitHeader (int header_index)
123                 {
124                         string [] result = null;
125                         string header = worker_request.GetKnownRequestHeader (header_index);
126                         if (header != null && header != "" && header.Trim () != "") {
127                                 result = header.Split (',');
128                                 for (int i = result.Length - 1; i >= 0; i--)
129                                         result [i] = result [i].Trim ();
130                         }
131                         return result;
132                 }
133
134                 public string [] AcceptTypes {
135                         get {
136                                 if (worker_request == null)
137                                         return null;
138
139                                 if (accept_types == null)
140                                         accept_types = SplitHeader (HttpWorkerRequest.HeaderAccept);
141
142                                 return accept_types;
143                         }
144                 }
145
146                 public string ApplicationPath {
147                         get {
148                                 if (worker_request == null)
149                                         return null;
150                                 return worker_request.GetAppPath ();
151                         }
152                 }
153
154                 public HttpBrowserCapabilities Browser {
155                         get {
156                                 if (browser_capabilities == null)
157                                         browser_capabilities = (HttpBrowserCapabilities)
158                                                 HttpCapabilitiesBase.GetConfigCapabilities (null, this);
159
160                                 return browser_capabilities;
161                         }
162
163                         set {
164                                 browser_capabilities = value;
165                         }
166                 }
167
168                 public HttpClientCertificate ClientCertificate {
169                         get {
170                                 if (client_cert == null)
171                                         client_cert = new HttpClientCertificate (worker_request);
172                                 return client_cert;
173                         }
174                 }
175
176                 static internal string GetParameter (string header, string attr)
177                 {
178                         int ap = header.IndexOf (attr);
179                         if (ap == -1)
180                                 return null;
181
182                         ap += attr.Length;
183                         if (ap >= header.Length)
184                                 return null;
185                         
186                         char ending = header [ap];
187                         if (ending != '"')
188                                 ending = ' ';
189                         
190                         int end = header.IndexOf (ending, ap+1);
191                         if (end == -1)
192                                 return (ending == '"') ? null : header.Substring (ap);
193
194                         return header.Substring (ap+1, end-ap-1);
195                 }
196
197                 public Encoding ContentEncoding {
198                         get {
199                                 if (encoding == null){
200                                         if (worker_request == null)
201                                                 throw new HttpException ("No HttpWorkerRequest");
202                                         
203                                         string content_type = ContentType;
204                                         string parameter = GetParameter (content_type, "; charset=");
205                                         if (parameter == null) {
206                                                 encoding = WebEncoding.RequestEncoding;
207                                         } else {
208                                                 try {
209                                                         // Do what the #1 web server does
210                                                         encoding = Encoding.GetEncoding (parameter);
211                                                 } catch {
212                                                         encoding = WebEncoding.RequestEncoding;
213                                                 }
214                                         }
215                                 }
216                                 return encoding;
217                         }
218
219                         set {
220                                 encoding = value;
221                         }
222                 }
223
224                 public int ContentLength {
225                         get {
226                                 if (content_length == -1){
227                                         if (worker_request == null)
228                                                 return 0;
229
230                                         string cl = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderContentLength);
231
232                                         if (cl != null) {
233                                                 try {
234                                                         content_length = Int32.Parse (cl);
235                                                 } catch { }
236                                         }
237                                 }
238
239                                 // content_length will still be < 0, but we know we gotta read from the client
240                                 if (content_length < 0)
241                                         return 0;
242
243                                 return content_length;
244                         }
245                 }
246
247                 public string ContentType {
248                         get {
249                                 if (content_type == null){
250                                         if (worker_request != null)
251                                                 content_type = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderContentType);
252
253                                         if (content_type == null)
254                                                 content_type = String.Empty;
255                                 }
256                                 
257                                 return content_type;
258                         }
259
260                         set {
261                                 content_type = value;
262                         }
263                 }
264
265                 public HttpCookieCollection Cookies {
266                         get {
267                                 if (cookies == null) {
268                                         if (worker_request == null) {
269                                                 cookies = new HttpCookieCollection ();
270                                         } else {
271                                                 string cookie_hv = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderCookie);
272                                                 cookies = new HttpCookieCollection (cookie_hv);
273                                         }
274                                 }
275
276                                 if (validate_cookies && !checked_cookies){
277                                         ValidateCookieCollection (cookies);
278                                         checked_cookies = true;
279                                 }
280
281                                 return cookies;
282                         }
283
284                 }
285
286                 public string CurrentExecutionFilePath {
287                         get {
288                                 if (current_exe_path != null)
289                                         return current_exe_path;
290
291                                 return FilePath;
292                         }
293                 }
294
295                 public string FilePath {
296                         get {
297                                 if (worker_request == null)
298                                         return "/"; // required for 2.0
299
300                                 if (file_path == null)
301                                         file_path = UrlUtils.Canonic (worker_request.GetFilePath ());
302
303                                 return file_path;
304                         }
305                 }
306
307                 internal string BaseVirtualDir {
308                         get {
309                                 if (base_virtual_dir == null){
310                                         base_virtual_dir = FilePath;
311                                         int p = base_virtual_dir.LastIndexOf ('/');
312                                         if (p != -1)
313                                                 base_virtual_dir = base_virtual_dir.Substring (0, p);
314                                 }
315                                 return base_virtual_dir;
316                         }
317                 }
318                 
319                 public HttpFileCollection Files {
320                         get {
321                                 if (files == null) {
322                                         files = new HttpFileCollection ();
323                                         if ((worker_request != null) && IsContentType ("multipart/form-data", true)) {
324                                                 form = new WebROCollection ();
325                                                 LoadMultiPart ();
326                                                 form.Protect ();
327                                         }
328                                 }
329                                 return files;
330                         }
331                 }
332
333                 public Stream Filter {
334                         get {
335                                 if (filter != null)
336                                         return filter;
337
338                                 if (input_filter == null)
339                                         input_filter = new InputFilterStream ();
340
341                                 return input_filter;
342                         }
343
344                         set {
345                                 // This checks that get_ was called before.
346                                 if (input_filter == null)
347                                         throw new HttpException ("Invalid filter");
348
349                                 filter = value;
350                         }
351                 }
352
353                 Stream StreamCopy (Stream stream)
354                 {
355 #if !TARGET_JVM
356                         if (stream is IntPtrStream)
357                                 return new IntPtrStream (stream);
358 #endif
359
360                         if (stream is MemoryStream) {
361                                 MemoryStream other = (MemoryStream) stream;
362                                 return new MemoryStream (other.GetBuffer (), 0, (int) other.Length, false, true);
363                         }
364
365                         throw new NotSupportedException ("The stream is " + stream.GetType ());
366                 }
367
368                 //
369                 // Loads the data on the form for multipart/form-data
370                 //
371                 void LoadMultiPart ()
372                 {
373                         string boundary = GetParameter (ContentType, "; boundary=");
374                         if (boundary == null)
375                                 return;
376
377                         Stream input = StreamCopy (InputStream);
378                         HttpMultipart multi_part = new HttpMultipart (input, boundary, ContentEncoding);
379
380                         HttpMultipart.Element e;
381                         while ((e = multi_part.ReadNextElement ()) != null) {
382                                 if (e.Filename == null){
383                                         byte [] copy = new byte [e.Length];
384                                 
385                                         input.Position = e.Start;
386                                         input.Read (copy, 0, (int) e.Length);
387
388                                         form.Add (e.Name, ContentEncoding.GetString (copy));
389                                 } else {
390                                         //
391                                         // We use a substream, as in 2.x we will support large uploads streamed to disk,
392                                         //
393                                         HttpPostedFile sub = new HttpPostedFile (e.Filename, e.ContentType, input, e.Start, e.Length);
394                                         files.AddFile (e.Name, sub);
395                                 }
396                         }
397                 }
398
399                 //
400                 // Adds the key/value to the form, and sets the argumets to empty
401                 //
402                 void AddRawKeyValue (StringBuilder key, StringBuilder value)
403                 {
404                         form.Add (HttpUtility.UrlDecode (key.ToString (), ContentEncoding),
405                                   HttpUtility.UrlDecode (value.ToString (), ContentEncoding));
406
407                         key.Length = 0;
408                         value.Length = 0;
409                 }
410
411                 //
412                 // Loads the form data from on a application/x-www-form-urlencoded post
413                 // 
414                 void LoadWwwForm ()
415                 {
416                         Stream input = StreamCopy (InputStream);
417                         StreamReader s = new StreamReader (input, ContentEncoding);
418
419                         StringBuilder key = new StringBuilder ();
420                         StringBuilder value = new StringBuilder ();
421                         int c;
422
423                         while ((c = s.Read ()) != -1){
424                                 if (c == '='){
425                                         value.Length = 0;
426                                         while ((c = s.Read ()) != -1){
427                                                 if (c == '&'){
428                                                         AddRawKeyValue (key, value);
429                                                         break;
430                                                 } else
431                                                         value.Append ((char) c);
432                                         }
433                                         if (c == -1){
434                                                 AddRawKeyValue (key, value);
435                                                 return;
436                                         }
437                                 } else if (c == '&')
438                                         AddRawKeyValue (key, value);
439                                 else
440                                         key.Append ((char) c);
441                         }
442                         if (c == -1)
443                                 AddRawKeyValue (key, value);
444                 }
445                 
446                 bool IsContentType (string ct, bool starts_with)
447                 {
448                         if (starts_with)
449                                 return StrUtils.StartsWith (ContentType, ct, true);
450
451                         return String.Compare (ContentType, ct, true, CultureInfo.InvariantCulture) == 0;
452                 }
453                 
454                 public NameValueCollection Form {
455                         get {
456                                 if (form == null){
457                                         form = new WebROCollection ();
458                                         files = new HttpFileCollection ();
459
460                                         if (IsContentType ("application/x-www-form-urlencoded", false))
461                                                 LoadWwwForm ();
462                                         else if (IsContentType ("multipart/form-data", true))
463                                                 LoadMultiPart ();
464
465                                         form.Protect ();
466                                 }
467
468                                 if (validate_form && !checked_form){
469                                         ValidateNameValueCollection ("Form", form);
470                                         checked_form = true;
471                                 }
472                                 
473                                 return form;
474                         }
475                 }
476
477                 public NameValueCollection Headers {
478                         get {
479                                 if (headers == null){
480                                         headers = new WebROCollection ();
481                                         if (worker_request == null) {
482                                                 headers.Protect ();
483                                                 return headers;
484                                         }
485
486                                         for (int i = 0; i < HttpWorkerRequest.RequestHeaderMaximum; i++){
487                                                 string hval = worker_request.GetKnownRequestHeader (i);
488
489                                                 if (hval == null || hval == "")
490                                                         continue;
491                                                 
492                                                 headers.Add (HttpWorkerRequest.GetKnownRequestHeaderName (i), hval);
493                                         }
494                                 
495                                         string [][] unknown = worker_request.GetUnknownRequestHeaders ();
496                                         if (unknown != null && unknown.GetUpperBound (0) != -1){
497                                                 int top = unknown.GetUpperBound (0) + 1;
498                                                 
499                                                 for (int i = 0; i < top; i++){
500                                                         // should check if unknown [i] is not null, but MS does not. 
501                                                         
502                                                         headers.Add (unknown [i][0], unknown [i][1]);
503                                                 }
504                                         }
505                                         headers.Protect ();
506                                 }
507                                 return headers;
508                         }
509                 }
510
511                 public string HttpMethod {
512                         get {
513                                 if (http_method == null){
514                                         if (worker_request != null)
515                                                 http_method = worker_request.GetHttpVerbName ();
516                                         else
517                                                 http_method = "GET";
518                                 }
519                                 return http_method;
520                         }
521                 }
522
523
524 #if TARGET_JVM  
525                 const int INPUT_BUFFER_SIZE = 1024;
526
527                 void MakeInputStream ()
528                 {
529                         if (worker_request == null)
530                                 throw new HttpException ("No HttpWorkerRequest");
531
532                         // consider for perf:
533                         //    return ((ServletWorkerRequest)worker_request).InputStream();
534
535                         //
536                         // Use an unmanaged memory block as this might be a large
537                         // upload
538                         //
539                         int content_length = ContentLength;
540
541                         if (content_length == 0 && HttpMethod == "POST")
542                                 throw new HttpException (411, "Length expected");
543                         
544                         HttpRuntimeConfig config = (HttpRuntimeConfig) HttpContext.GetAppConfig ("system.web/httpRuntime");
545                         
546                         if (content_length > (config.MaxRequestLength * 1024))
547                                 throw new HttpException ("File exceeds httpRuntime limit");
548                         
549                         byte[] content = new byte[content_length];
550                         if (content == null)
551                                 throw new HttpException (String.Format ("Not enough memory to allocate {0} bytes", content_length));
552
553                         int total;
554                         byte [] buffer;
555                         buffer = worker_request.GetPreloadedEntityBody ();
556                         if (buffer != null){
557                                 total = buffer.Length;
558                                 Array.Copy (buffer, content, total);
559                         } else
560                                 total = 0;
561                         
562                         
563                         buffer = new byte [INPUT_BUFFER_SIZE];
564                         while (total < content_length){
565                                 int n;
566                                 n = worker_request.ReadEntityBody (buffer, Math.Min (content_length-total, INPUT_BUFFER_SIZE));
567                                 if (n <= 0)
568                                         break;
569                                 Array.Copy (buffer, 0, content, total, n);
570                                 total += n;
571                         } 
572                         if (total < content_length)
573                                 throw new HttpException (411, "The uploaded file is incomplete");
574                                                          
575                         input_stream = new MemoryStream (content, 0, content.Length, false, true);
576                 }
577 #else
578                 const int INPUT_BUFFER_SIZE = 32*1024;
579
580                 void DoFilter (byte [] buffer)
581                 {
582                         if (input_filter == null || filter == null)
583                                 return;
584
585                         // Replace the input with the filtered input
586                         input_filter.BaseStream = input_stream;
587                         MemoryStream ms = new MemoryStream ();
588                         while (true) {
589                                 int n = filter.Read (buffer, 0, buffer.Length);
590                                 if (n <= 0)
591                                         break;
592                                 ms.Write (buffer, 0, n);
593                         }
594                         // From now on input_stream has the filtered input
595                         input_stream = new MemoryStream (ms.GetBuffer (), 0, (int) ms.Length, false, true);
596                 }
597
598                 void MakeInputStream ()
599                 {
600                         if (input_stream != null)
601                                 return;
602
603                         if (worker_request == null) {
604                                 input_stream = new MemoryStream (new byte [0], 0, 0, false, true);
605                                 DoFilter (new byte [1024]);
606                                 return;
607                         }
608
609                         //
610                         // Use an unmanaged memory block as this might be a large
611                         // upload
612                         //
613                         int content_length = ContentLength;
614
615                         HttpRuntimeConfig config = (HttpRuntimeConfig) HttpContext.GetAppConfig ("system.web/httpRuntime");
616                         if ((content_length / 1024) > config.MaxRequestLength)
617                                 throw new HttpException (400, "Upload size exceeds httpRuntime limit.");
618
619                         int total = 0;
620                         byte [] buffer;
621                         buffer = worker_request.GetPreloadedEntityBody ();
622                         // we check the instance field 'content_length' here, not the local var.
623                         if (this.content_length == 0 || worker_request.IsEntireEntityBodyIsPreloaded ()) {
624                                 if (buffer == null || content_length == 0) {
625                                         input_stream = new MemoryStream (new byte [0], 0, 0, false, true);
626                                 } else {
627                                         input_stream = new MemoryStream (buffer, 0, buffer.Length, false, true);
628                                 }
629                                 DoFilter (new byte [1024]);
630                                 return;
631                         }
632
633                         if (buffer != null)
634                                 total = buffer.Length;
635
636                         if (content_length > 0) {
637                                 IntPtr content = Marshal.AllocHGlobal (content_length);
638                                 if (content == (IntPtr) 0)
639                                         throw new HttpException (String.Format ("Not enough memory to allocate {0} bytes.",
640                                                                         content_length));
641
642                                 if (total > 0)
643                                         Marshal.Copy (buffer, 0, content, total);
644
645                                 buffer = new byte [Math.Min (content_length, INPUT_BUFFER_SIZE)];
646                                 while (total < content_length){
647                                         int n;
648                                         n = worker_request.ReadEntityBody (buffer, Math.Min (content_length-total, INPUT_BUFFER_SIZE));
649                                         if (n <= 0)
650                                                 break;
651                                         Marshal.Copy (buffer, 0, (IntPtr) ((long)content + total), n);
652                                         total += n;
653                                 }
654                                 input_stream = new IntPtrStream (content, total);
655                                 DoFilter (buffer);
656                         } else {
657                                 MemoryStream ms = new MemoryStream ();
658                                 if (total > 0)
659                                         ms.Write (buffer, 0, total);
660                                 buffer = new byte [INPUT_BUFFER_SIZE];
661                                 long maxlength = config.MaxRequestLength * 1024;
662                                 int n;
663                                 while (true) {
664                                         n = worker_request.ReadEntityBody (buffer, INPUT_BUFFER_SIZE);
665                                         if (n <= 0)
666                                                 break;
667                                         total += n;
668                                         if (total < 0 || total > maxlength)
669                                                 throw new HttpException (400, "Upload size exceeds httpRuntime limit.");
670                                         ms.Write (buffer, 0, n);
671                                 }
672                                 input_stream = new MemoryStream (ms.GetBuffer (), 0, (int) ms.Length, false, true);
673                                 DoFilter (buffer);
674                         }
675
676                         if (total < content_length)
677                                 throw new HttpException (411, "The request body is incomplete.");
678                 }
679 #endif
680                 internal void ReleaseResources ()
681                 {
682                         if (input_stream != null){
683                                 input_stream.Close ();
684                                 input_stream = null;
685                         }
686                 }
687                 
688                 public Stream InputStream {
689                         get {
690                                 if (input_stream == null)
691                                         MakeInputStream ();
692
693                                 return input_stream;
694                         }
695                 }
696
697                 public bool IsAuthenticated {
698                         get {
699                                 if (context.User == null || context.User.Identity == null)
700                                         return false;
701                                 return context.User.Identity.IsAuthenticated;
702                         }
703                 }
704
705                 public bool IsSecureConnection {
706                         get {
707                                 if (worker_request == null)
708                                         return false;
709                                 return worker_request.IsSecure ();
710                         }
711                 }
712
713                 public string this [string key] {
714                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
715                         get {
716                                 // "The QueryString, Form, Cookies, or ServerVariables collection member
717                                 // specified in the key parameter."
718                                 string val = QueryString [key];
719                                 if (val == null)
720                                         val = Form [key];
721                                 if (val == null) {
722                                         HttpCookie cookie = Cookies [key];
723                                         if (cookie != null)
724                                                 val = cookie.Value;
725                                 }
726                                 if (val == null)
727                                         val = ServerVariables [key];
728
729                                 return val;
730                         }
731                 }
732
733                 public NameValueCollection Params {
734                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
735                         get {
736                                 if (all_params == null) {
737                                         all_params = new WebROCollection ();
738
739                                         all_params.Add (QueryString);
740
741                                         /* special handling for Cookies since
742                                          * it isn't a NameValueCollection. */
743                                         foreach (string key in Cookies.AllKeys) {
744                                                 all_params.Add (key, Cookies[key].Value);
745                                         }
746
747                                         all_params.Add (Form);
748                                         all_params.Add (ServerVariables);
749                                         all_params.Protect ();
750                                 }
751
752                                 return all_params;
753                         }
754                 }
755
756                 public string Path {
757                         get {
758                                 if (uri_builder == null)
759                                         InitUriBuilder ();
760                                 
761                                 return uri_builder.Path;
762                         }
763                 }
764
765                 public string PathInfo {
766                         get {
767                                 if (path_info == null) {
768                                         if (worker_request == null)
769                                                 return String.Empty;
770                                         path_info = worker_request.GetPathInfo ();
771                                 }
772
773                                 return path_info;
774                         }
775                 }
776
777                 public string PhysicalApplicationPath {
778                         get {
779                                 if (worker_request == null)
780                                         throw new ArgumentNullException (); // like 2.0, 1.x throws TypeInitializationException
781
782                                 string path = HttpRuntime.AppDomainAppPath;
783                                 if (SecurityManager.SecurityEnabled) {
784                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, path).Demand ();
785                                 }
786                                 return path;
787                         }
788                 }
789
790                 public string PhysicalPath {
791                         get {
792                                 if (worker_request == null)
793                                         return String.Empty; // don't check security with an empty string!
794
795                                 if (physical_path == null)
796                                         physical_path = MapPath (CurrentExecutionFilePath);
797
798                                 if (SecurityManager.SecurityEnabled) {
799                                         new FileIOPermission (FileIOPermissionAccess.PathDiscovery, physical_path).Demand ();
800                                 }
801                                 return physical_path;
802                         }
803                 }
804
805                 internal string RootVirtualDir {
806                         get {
807                                 if (root_virtual_dir == null){
808                                         string fp = FilePath;
809                                         int p = fp.LastIndexOf ('/');
810
811                                         if (p == -1)
812                                                 root_virtual_dir = "/";
813                                         else
814                                                 root_virtual_dir = fp.Substring (0, p);
815                                 }
816
817                                 return root_virtual_dir;
818                         }
819                 }
820
821                 public NameValueCollection QueryString {
822                         get {
823                                 if (query_string_nvc == null){
824                                         query_string_nvc = new WebROCollection ();
825
826                                         if (uri_builder == null)
827                                                 InitUriBuilder ();
828                                         
829                                         string q = query_string;
830                                         if (q != null && q != ""){
831                                                 string [] components = q.Split ('&');
832                                                 foreach (string kv in components){
833                                                         int pos = kv.IndexOf ('=');
834                                                         if (pos == -1){
835                                                                 query_string_nvc.Add (null, HttpUtility.UrlDecode (kv));
836                                                         } else {
837                                                                 string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
838                                                                 string val = HttpUtility.UrlDecode (kv.Substring (pos+1));
839                                                                 
840                                                                 query_string_nvc.Add (key, val);
841                                                         }
842                                                 }
843                                         }
844                                         query_string_nvc.Protect ();
845                                 }
846                                 
847                                 if (validate_query_string && !checked_query_string) {
848                                         ValidateNameValueCollection ("QueryString", query_string_nvc);
849                                         checked_query_string = true;
850                                 }
851                                 
852                                 return query_string_nvc;
853                         }
854                 }
855
856                 public string RawUrl {
857                         get {
858                                 if (worker_request != null)
859                                         return worker_request.GetRawUrl ();
860                                 else {
861                                         if (query_string != null && query_string != "")
862                                                 return uri_builder.Path + "?" + query_string;
863                                         else
864                                                 return uri_builder.Path;
865                                 }
866                         }
867                 }
868
869                 //
870                 // "GET" or "SET"
871                 //
872                 public string RequestType {
873                         get {
874                                 if (request_type == null){
875                                         if (worker_request != null) {
876                                                 request_type = worker_request.GetHttpVerbName ();
877                                                 http_method = request_type;
878                                         } else {
879                                                 request_type = "GET";
880                                         }
881                                 }
882                                 return request_type;
883                         }
884
885                         set {
886                                 request_type = value;
887                         }
888                 }
889
890                 public NameValueCollection ServerVariables {
891                         [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
892                         get {
893                                 if (server_variables == null)
894                                         server_variables = new ServerVariablesCollection (this);
895
896                                 return server_variables;
897                         }
898                 }
899
900                 public int TotalBytes {
901                         get {
902                                 Stream ins = InputStream;
903                                 return (int) ins.Length;
904                         }
905                 }
906
907                 public Uri Url {
908                         get {
909                                 if (uri_builder == null)
910                                         InitUriBuilder ();
911                                 
912                                 return uri_builder.Uri;
913                         }
914                 }
915
916                 public Uri UrlReferrer {
917                         get {
918                                 if (worker_request == null)
919                                         return null;
920
921                                 string hr = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderReferer);
922                                 if (hr == null)
923                                         return null;
924
925                                 return new Uri (hr);
926                         }
927                 }
928
929                 public string UserAgent {
930                         get {
931                                 if (worker_request == null)
932                                         return null;
933
934                                 return worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderUserAgent);
935                         }
936                 }
937
938                 public string UserHostAddress {
939                         get {
940                                 if (worker_request == null)
941                                         return null;
942
943                                 return worker_request.GetRemoteAddress ();
944                         }
945                 }
946
947                 public string UserHostName {
948                         get {
949                                 if (worker_request == null)
950                                         return null;
951
952                                 return worker_request.GetRemoteName ();
953                         }
954                 }
955
956                 public string [] UserLanguages {
957                         get {
958                                 if (worker_request == null)
959                                         return null;
960
961                                 if (user_languages == null)
962                                         user_languages = SplitHeader (HttpWorkerRequest.HeaderAcceptLanguage);
963
964                                 return user_languages;
965                         }
966                 }
967
968                 public byte [] BinaryRead (int count)
969                 {
970                         if (count < 0)
971                                 throw new ArgumentException ("count is < 0");
972
973                         Stream s = InputStream;
974                         byte [] ret = new byte [count];
975                         if (s.Read (ret, 0, count) != count)
976                                 throw new ArgumentException (
977                                         String.Format ("count {0} exceeds length of available input {1}",
978                                                 count, s.Length - s.Position));
979                         return ret;
980                 }
981
982                 public int [] MapImageCoordinates (string imageFieldName)
983                 {
984                         string method = HttpMethod;
985                         NameValueCollection coll = null;
986                         if (method == "HEAD" || method == "GET")
987                                 coll = QueryString;
988                         else if (method == "POST")
989                                 coll = Form;
990
991                         if (coll == null)
992                                 return null;
993
994                         string x = coll [imageFieldName + ".x"];
995                         if (x == null || x == "")
996                                 return null;
997
998                         string y = coll [imageFieldName + ".y"];
999                         if (y == null || y == "")
1000                                 return null;
1001
1002                         int [] result = new int [2];
1003                         try {
1004                                 result [0] = Int32.Parse (x);
1005                                 result [1] = Int32.Parse (y);
1006                         } catch {
1007                                 return null;
1008                         }
1009
1010                         return result;
1011                 }
1012
1013                 public string MapPath (string virtualPath)
1014                 {
1015                         if (worker_request == null)
1016                                 return null;
1017
1018                         return MapPath (virtualPath, BaseVirtualDir, true);
1019                 }
1020
1021                 public string MapPath (string virtualPath, string baseVirtualDir, bool allowCrossAppMapping)
1022                 {
1023                         if (worker_request == null)
1024                                 throw new HttpException ("No HttpWorkerRequest");
1025
1026                         if (virtualPath == null || virtualPath == "")
1027                                 virtualPath = "";
1028                         else
1029                                 virtualPath = virtualPath.Trim ();
1030
1031                         if (virtualPath.IndexOf (':') != -1)
1032                                 throw new ArgumentNullException (
1033                                         String.Format ("MapPath: Invalid path '{0}', only virtual paths are accepted", virtualPath));
1034
1035                         if (System.IO.Path.DirectorySeparatorChar != '/')
1036                                 virtualPath.Replace (System.IO.Path.DirectorySeparatorChar, '/');
1037
1038                         if (UrlUtils.IsRooted (virtualPath))
1039                                 virtualPath = UrlUtils.Canonic (virtualPath);
1040                         else {
1041                                 if (baseVirtualDir == null)
1042                                         baseVirtualDir = RootVirtualDir;
1043                                 virtualPath = UrlUtils.Combine (baseVirtualDir, virtualPath);
1044                         }
1045
1046                         if (!allowCrossAppMapping){
1047                                 if (!StrUtils.StartsWith (virtualPath, RootVirtualDir, true))
1048                                         throw new HttpException ("MapPath: Mapping across applications not allowed");
1049                                 if (RootVirtualDir.Length > 1 && virtualPath.Length > 1 && virtualPath [0] != '/')
1050                                         throw new HttpException ("MapPath: Mapping across applications not allowed");
1051                         }
1052                         return worker_request.MapPath (virtualPath);
1053                 }
1054
1055                 public void SaveAs (string filename, bool includeHeaders)
1056                 {
1057                         Stream output = new FileStream (filename, FileMode.Create);
1058                         if (includeHeaders) {
1059                                 StringBuilder sb = new StringBuilder ();
1060                                 string version = String.Empty;
1061                                 string path = "/";
1062                                 if (worker_request != null) {
1063                                         version = worker_request.GetHttpVersion ();
1064                                         InitUriBuilder ();
1065                                         path = uri_builder.Path;
1066                                 }
1067                                 string qs = null;
1068                                 if (query_string != null && query_string != "")
1069                                         qs = "?" + query_string;
1070
1071                                 sb.AppendFormat ("{0} {1}{2} {3}\r\n", HttpMethod, path, qs, version);
1072                                 NameValueCollection coll = Headers;
1073                                 foreach (string k in coll.AllKeys) {
1074                                         sb.Append (k);
1075                                         sb.Append (':');
1076                                         sb.Append (coll [k]);
1077                                         sb.Append ("\r\n");
1078                                 }
1079                                 sb.Append ("\r\n");
1080                                 // latin1
1081                                 byte [] bytes = Encoding.GetEncoding (28591).GetBytes (sb.ToString ());
1082                                 output.Write (bytes, 0, bytes.Length);
1083                         }
1084
1085                         // More than 1 call to SaveAs works fine on MS, so we "copy" the stream
1086                         // to keep InputStream in its state.
1087                         Stream input = StreamCopy (InputStream);
1088                         try {
1089                                 long len = input.Length;
1090                                 int buf_size = (int) Math.Min ((len < 0 ? 0 : len), 8192);
1091                                 byte [] data = new byte [buf_size];
1092                                 int count = 0;
1093                                 while (len > 0 && (count = input.Read (data, 0, buf_size)) > 0) {
1094                                         output.Write (data, 0, count);
1095                                         len -= count;
1096                                 }
1097                         } finally {
1098                                 output.Flush ();
1099                                 output.Close ();
1100                         }
1101                 }
1102
1103                 public void ValidateInput ()
1104                 {
1105                         validate_cookies = true;
1106                         validate_query_string = true;
1107                         validate_form = true;
1108                 }
1109
1110 #region internal routines
1111                 internal string ClientTarget {
1112                         get {
1113                                 return client_target;
1114                         }
1115
1116                         set {
1117                                 client_target = value;
1118                         }
1119                 }
1120
1121 #if NET_2_0
1122                 public
1123 #else
1124                 internal
1125 #endif
1126                 bool IsLocal {
1127                         get {
1128                                 string address = worker_request.GetRemoteAddress ();
1129
1130                                 return (address == "127.0.0.1");
1131                         }
1132                 }
1133
1134                 internal void SetFilePath (string path)
1135                 {
1136                         file_path = path;
1137                 }
1138
1139                 internal void SetCurrentExePath (string path)
1140                 {
1141                         current_exe_path = path;
1142                         //if (uri_builder == null)
1143                         //      InitUriBuilder ();
1144
1145                         //uri_builder.Path = path;
1146                          // recreated on demand
1147                         root_virtual_dir = null;
1148                         base_virtual_dir = null;
1149                         physical_path = null;
1150                 }
1151
1152                 internal void SetPathInfo (string pi)
1153                 {
1154                         path_info = pi;
1155                 }
1156
1157                 // Headers is ReadOnly, so we need this hack for cookie-less sessions.
1158                 internal void SetHeader (string name, string value)
1159                 {
1160                         WebROCollection h = (WebROCollection) Headers;
1161                         h.Unprotect ();
1162                         h [name] = value;
1163                         h.Protect ();
1164                 }
1165
1166                 // Notice: there is nothing raw about this querystring.
1167                 internal string QueryStringRaw {
1168                         get {
1169                                 if (uri_builder == null)
1170                                         InitUriBuilder ();
1171                                 
1172                                 return query_string;
1173                         }
1174
1175                         set {
1176                                 if (uri_builder == null)
1177                                         InitUriBuilder ();
1178
1179                                 query_string = value;
1180                                 query_string_nvc = null;
1181                                 if (uri_builder != null)
1182                                         uri_builder.Query = value;
1183                         }
1184                 }
1185
1186                 // Internal, dont know what it does, so flagged as public so we can see it.
1187                 internal void SetForm (WebROCollection coll)
1188                 {
1189                         form = coll;
1190                 }
1191
1192                 internal HttpWorkerRequest WorkerRequest {
1193                         get {
1194                                 return worker_request;
1195                         }
1196                 }
1197
1198                 internal HttpContext Context {
1199                         get {
1200                                 return context;
1201                         }
1202                 }
1203
1204                 static void ValidateNameValueCollection (string name, NameValueCollection coll)
1205                 {
1206                         if (coll == null)
1207                                 return;
1208                 
1209                         foreach (string key in coll.Keys) {
1210                                 string val = coll [key];
1211                                 if (val != null && val != "" && CheckString (val))
1212                                         ThrowValidationException (name, key, val);
1213                         }
1214                 }
1215                 
1216                 static void ValidateCookieCollection (HttpCookieCollection cookies)
1217                 {
1218                         if (cookies == null)
1219                                 return;
1220                 
1221                         int size = cookies.Count;
1222                         HttpCookie cookie;
1223                         for (int i = 0 ; i < size ; i++) {
1224                                 cookie = cookies[i];
1225                                 string value = cookie.Value;
1226                                 
1227                                 if (value != null && value != "" && CheckString (value))
1228                                         ThrowValidationException ("Cookies", cookie.Name, cookie.Value);
1229                         }
1230                 }
1231                 
1232                 static void ThrowValidationException (string name, string key, string value)
1233                 {
1234                         string v = "\"" + value + "\"";
1235                         if (v.Length > 20)
1236                                 v = v.Substring (0, 16) + "...\"";
1237                 
1238                         string msg = String.Format ("A potentially dangerous Request.{0} value was " +
1239                                                     "detected from the client ({1}={2}).", name, key, v);
1240                 
1241                         throw new HttpRequestValidationException (msg);
1242                 }
1243                 
1244                 static bool CheckString (string val)
1245                 {
1246                         foreach (char c in val) {
1247                                 if (c == '<' || c == '>' || c == '\xff1c' || c == '\xff1e')
1248                                         return true;
1249                         }
1250                 
1251                         return false;
1252                 }
1253
1254         }
1255 #endregion
1256
1257 #region Helper classes
1258         
1259         //
1260         // Stream-based multipart handling.
1261         //
1262         // In this incarnation deals with an HttpInputStream as we are now using
1263         // IntPtr-based streams instead of byte [].   In the future, we will also
1264         // send uploads above a certain threshold into the disk (to implement
1265         // limit-less HttpInputFiles). 
1266         //
1267         
1268         class HttpMultipart {
1269
1270                 public class Element {
1271                         public string ContentType;
1272                         public string Name;
1273                         public string Filename;
1274                         public long Start;
1275                         public long Length;
1276                         
1277                         public override string ToString ()
1278                         {
1279                                 return string.Format ("ContentType {0}, Name {1}, Filename {2}, Start {3}, Length {4}",
1280                                         ContentType, Name, Filename, Start, Length);
1281                         }
1282                 }
1283                 
1284                 Stream data;
1285                 string boundary;
1286                 byte [] boundary_bytes;
1287                 byte [] buffer;
1288                 bool at_eof;
1289                 Encoding encoding;
1290                 StringBuilder sb;
1291                 
1292                 const byte HYPHEN = (byte) '-', LF = (byte) '\n', CR = (byte) '\r';
1293                 
1294                 // See RFC 2046 
1295                 // In the case of multipart entities, in which one or more different
1296                 // sets of data are combined in a single body, a "multipart" media type
1297                 // field must appear in the entity's header.  The body must then contain
1298                 // one or more body parts, each preceded by a boundary delimiter line,
1299                 // and the last one followed by a closing boundary delimiter line.
1300                 // After its boundary delimiter line, each body part then consists of a
1301                 // header area, a blank line, and a body area.  Thus a body part is
1302                 // similar to an RFC 822 message in syntax, but different in meaning.
1303                 
1304                 public HttpMultipart (Stream data, string b, Encoding encoding)
1305                 {
1306                         this.data = data;
1307                         boundary = b;
1308                         boundary_bytes = encoding.GetBytes (b);
1309                         buffer = new byte [boundary_bytes.Length + 2]; // CRLF or '--'
1310                         this.encoding = encoding;
1311                         sb = new StringBuilder ();
1312                 }
1313
1314                 string ReadLine ()
1315                 {
1316                         // CRLF or LF are ok as line endings.
1317                         bool got_cr = false;
1318                         int b = 0;
1319                         sb.Length = 0;
1320                         while (true) {
1321                                 b = data.ReadByte ();
1322                                 if (b == -1) {
1323                                         return null;
1324                                 }
1325
1326                                 if (b == LF) {
1327                                         break;
1328                                 }
1329                                 got_cr = (b == CR);
1330                                 sb.Append ((char) b);
1331                         }
1332
1333                         if (got_cr)
1334                                 sb.Length--;
1335
1336                         return sb.ToString ();
1337
1338                 }
1339
1340                 static string GetContentDispositionAttribute (string l, string name)
1341                 {
1342                         int idx = l.IndexOf (name + "=\"");
1343                         if (idx < 0)
1344                                 return null;
1345                         int begin = idx + name.Length + "=\"".Length;
1346                         int end = l.IndexOf ('"', begin);
1347                         if (end < 0)
1348                                 return null;
1349                         if (begin == end)
1350                                 return "";
1351                         return l.Substring (begin, end - begin);
1352                 }
1353
1354                 bool ReadBoundary ()
1355                 {
1356                         try {
1357                                 string line = ReadLine ();
1358                                 while (line == "")
1359                                         line = ReadLine ();
1360                                 int ll = line.Length;
1361                                 int bl = boundary.Length;
1362                                 if (line [0] != '-' || line [1] != '-')
1363                                         return false;
1364
1365                                 if (!StrUtils.EndsWith (line, boundary, false))
1366                                         return true;
1367                         } catch {
1368                         }
1369
1370                         return false;
1371                 }
1372
1373                 bool IsBoundary (string line)
1374                 {
1375                         if (line.Length < 2)
1376                                 return false;
1377
1378                         int ll = line.Length;
1379                         int bl = boundary.Length;
1380                         if (line [0] != '-' || line [1] != '-')
1381                                 return false;
1382
1383                         if (line.IndexOf (boundary) != 2)
1384                                 return false;
1385
1386                         if (ll == bl + 4 && line [ll -1] == '-' && line [ll - 2] == '-')
1387                                 at_eof = true;
1388
1389                         return  (at_eof || ll == bl + 2);
1390                 }
1391
1392                 string ReadHeaders ()
1393                 {
1394                         string s = ReadLine ();
1395                         if (s == "")
1396                                 return null;
1397
1398                         return s;
1399                 }
1400
1401                 bool CompareBytes (byte [] orig, byte [] other)
1402                 {
1403                         for (int i = orig.Length - 1; i >= 0; i--)
1404                                 if (orig [i] != other [i])
1405                                         return false;
1406
1407                         return true;
1408                 }
1409
1410                 long MoveToNextBoundary ()
1411                 {
1412                         long retval = 0;
1413                         bool got_cr = false;
1414
1415                         int state = 0;
1416                         int c = data.ReadByte ();
1417                         while (true) {
1418                                 if (c == -1)
1419                                         return -1;
1420
1421                                 if (state == 0 && c == LF) {
1422                                         retval = data.Position - 1;
1423                                         if (got_cr)
1424                                                 retval--;
1425                                         state = 1;
1426                                         c = data.ReadByte ();
1427                                 } else if (state == 0) {
1428                                         got_cr = (c == CR);
1429                                         c = data.ReadByte ();
1430                                 } else if (state == 1 && c == '-') {
1431                                         c = data.ReadByte ();
1432                                         if (c == -1)
1433                                                 return -1;
1434
1435                                         if (c != '-') {
1436                                                 state = 0;
1437                                                 got_cr = false;
1438                                                 continue; // no ReadByte() here
1439                                         }
1440
1441                                         int nread = data.Read (buffer, 0, buffer.Length);
1442                                         int bl = buffer.Length;
1443                                         if (nread != bl)
1444                                                 return -1;
1445
1446                                         if (!CompareBytes (boundary_bytes, buffer)) {
1447                                                 state = 0;
1448                                                 data.Position = retval + 2;
1449                                                 if (got_cr) {
1450                                                         data.Position++;
1451                                                         got_cr = false;
1452                                                 }
1453                                                 c = data.ReadByte ();
1454                                                 continue;
1455                                         }
1456
1457                                         if (buffer [bl - 2] == '-' && buffer [bl - 1] == '-') {
1458                                                 at_eof = true;
1459                                         } else if (buffer [bl - 2] != CR || buffer [bl - 1] != LF) {
1460                                                 state = 0;
1461                                                 data.Position = retval + 2;
1462                                                 if (got_cr) {
1463                                                         data.Position++;
1464                                                         got_cr = false;
1465                                                 }
1466                                                 c = data.ReadByte ();
1467                                                 continue;
1468                                         }
1469                                         data.Position = retval + 2;
1470                                         if (got_cr)
1471                                                 data.Position++;
1472                                         break;
1473                                 } else {
1474                                         // state == 1
1475                                         state = 0; // no ReadByte() here
1476                                 }
1477                         }
1478
1479                         return retval;
1480                 }
1481
1482                 public Element ReadNextElement ()
1483                 {
1484                         if (at_eof || ReadBoundary ())
1485                                 return null;
1486
1487                         Element elem = new Element ();
1488                         string header;
1489                         while ((header = ReadHeaders ()) != null) {
1490                                 if (StrUtils.StartsWith (header, "Content-Disposition:", true)) {
1491                                         elem.Name = GetContentDispositionAttribute (header, "name");
1492                                         elem.Filename = GetContentDispositionAttribute (header, "filename");      
1493                                 } else if (StrUtils.StartsWith (header, "Content-Type:", true)) {
1494                                         elem.ContentType = header.Substring ("Content-Type:".Length).Trim ();
1495                                 }
1496                         }
1497
1498                         long start = data.Position;
1499                         elem.Start = start;
1500                         long pos = MoveToNextBoundary ();
1501                         if (pos == -1)
1502                                 return null;
1503
1504                         elem.Length = pos - start;
1505                         return elem;
1506                 }
1507                 
1508         }
1509 #endregion
1510 }
1511