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