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