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