2 // System.Web.HttpRequest.cs
6 // Miguel de Icaza (miguel@novell.com)
7 // Gonzalo Paniagua Javier (gonzalo@novell.com)
8 // Marek Habersack <mhabersack@novell.com>
12 // Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
13 // Copyright (C) 2011-2012 Xamarin, Inc (http://xamarin.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Collections;
36 using System.Collections.Specialized;
38 using System.Runtime.InteropServices;
39 using System.Security;
40 using System.Security.Permissions;
41 using System.Security.Principal;
42 using System.Threading;
43 using System.Web.Configuration;
44 using System.Web.Management;
46 using System.Web.Util;
47 using System.Globalization;
49 using System.Security.Authentication.ExtendedProtection;
50 using System.Web.Routing;
54 // CAS - no InheritanceDemand here as the class is sealed
55 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
56 public sealed partial class HttpRequest
58 HttpWorkerRequest worker_request;
60 WebROCollection query_string_nvc;
64 string orig_url = null;
65 UriBuilder url_components;
70 // On-demand computed values
72 HttpBrowserCapabilities browser_capabilities;
73 string file_path, base_virtual_dir, root_virtual_dir, client_file_path;
75 int content_length = -1;
77 string current_exe_path;
79 string unescaped_path;
82 string path_info_unvalidated;
84 string raw_url_unvalidated;
85 WebROCollection all_params;
86 NameValueCollection headers;
87 WebROCollection headers_unvalidated;
89 InputFilterStream input_filter;
91 HttpCookieCollection cookies;
92 HttpCookieCollection cookies_unvalidated;
96 HttpFileCollection files;
98 ServerVariablesCollection server_variables;
99 HttpClientCertificate client_cert;
102 string [] accept_types;
103 string [] user_languages;
105 TempFileStream request_file;
107 readonly static System.Net.IPAddress [] host_addresses;
110 bool validate_cookies, validate_query_string, validate_form;
111 bool checked_cookies, checked_query_string, checked_form;
112 static readonly UrlMappingCollection urlMappings;
113 readonly static char [] queryTrimChars = {'?'};
114 bool lazyFormValidation;
115 bool lazyQueryStringValidation;
116 bool inputValidationEnabled;
117 RequestContext requestContext;
118 BufferlessInputStream bufferlessInputStream;
120 static bool validateRequestNewMode;
121 internal static bool ValidateRequestNewMode {
122 get { return validateRequestNewMode; }
125 internal bool InputValidationEnabled {
126 get { return inputValidationEnabled; }
129 private static char[] RequestPathInvalidCharacters {
133 private static char[] CharsFromList (string list)
135 // List format is very strict and enforced by the Configuration
136 // there must be a single char separated by commas with no trailing comma
137 // whitespace is allowed though and should be trimmed.
139 string [] pieces = list.Split (',');
141 char [] chars = new char [pieces.Length];
142 for (int i = 0; i < chars.Length; i++) {
143 string trimmed = pieces [i].Trim ();
144 if (trimmed.Length != 1) {
145 // This should have been caught by System.Web.Configuration
146 // and throw a configuration error. This is just here for sanity
147 throw new System.Configuration.ConfigurationErrorsException ();
150 chars [i] = trimmed [0];
156 static HttpRequest ()
159 UrlMappingsSection ums = WebConfigurationManager.GetWebApplicationSection ("system.web/urlMappings") as UrlMappingsSection;
160 if (ums != null && ums.IsEnabled) {
161 urlMappings = ums.UrlMappings;
162 if (urlMappings.Count == 0)
166 Version validationMode = HttpRuntime.Section.RequestValidationMode;
168 if (validationMode >= new Version (4, 0)) {
169 validateRequestNewMode = true;
170 string invalidChars = HttpRuntime.Section.RequestPathInvalidCharacters;
171 if (!String.IsNullOrEmpty (invalidChars))
172 RequestPathInvalidCharacters = CharsFromList (invalidChars);
175 // unlikely to happen
178 host_addresses = GetLocalHostAddresses ();
181 public HttpRequest (string filename, string url, string queryString)
183 // warning 169: what are we supposed to do with filename?
185 //this.filename = filename;
188 url_components = new UriBuilder (url);
189 url_components.Query = queryString;
191 query_string_nvc = new WebROCollection ();
192 if (queryString != null)
193 HttpUtility.ParseQueryString (queryString, Encoding.Default, query_string_nvc);
194 query_string_nvc.Protect ();
197 internal HttpRequest (HttpWorkerRequest worker_request, HttpContext context)
199 this.worker_request = worker_request;
200 this.context = context;
203 internal UriBuilder UrlComponents {
205 if (url_components == null) {
207 byte[] queryStringRaw = worker_request.GetQueryStringRawBytes();
208 if(queryStringRaw != null)
209 query = ContentEncoding.GetString(queryStringRaw);
211 query = worker_request.GetQueryString();
213 BuildUrlComponents (ApplyUrlMapping (worker_request.GetUriPath ()), query);
215 return url_components;
219 void BuildUrlComponents (string path, string query)
221 if (url_components != null)
223 url_components = new UriBuilder ();
224 url_components.Scheme = worker_request.GetProtocol ();
225 url_components.Host = worker_request.GetServerName ();
226 url_components.Port = worker_request.GetLocalPort ();
227 url_components.Path = path;
228 if (query != null && query.Length > 0)
229 url_components.Query = query.TrimStart (queryTrimChars);
232 internal string ApplyUrlMapping (string url)
234 if (urlMappings == null)
237 string relUrl = VirtualPathUtility.ToAppRelative (url);
238 UrlMapping um = null;
240 foreach (UrlMapping u in urlMappings) {
243 if (String.Compare (relUrl, u.Url, StringComparison.Ordinal) == 0) {
252 string rawUrl = VirtualPathUtility.ToAbsolute (um.MappedUrl.Trim ());
253 Uri newUrl = new Uri ("http://host.com" + rawUrl);
255 if (url_components != null) {
256 url_components.Path = newUrl.AbsolutePath;
257 url_components.Query = newUrl.Query.TrimStart (queryTrimChars);
258 query_string_nvc = new WebROCollection ();
259 HttpUtility.ParseQueryString (newUrl.Query, Encoding.Default, query_string_nvc);
260 query_string_nvc.Protect ();
262 BuildUrlComponents (newUrl.AbsolutePath, newUrl.Query);
264 return url_components.Path;
267 string [] SplitHeader (int header_index)
269 string [] result = null;
270 string header = worker_request.GetKnownRequestHeader (header_index);
271 if (header != null && header != "" && header.Trim () != "") {
272 result = header.Split (',');
273 for (int i = result.Length - 1; i >= 0; i--)
274 result [i] = result [i].Trim ();
279 public string [] AcceptTypes {
281 if (worker_request == null)
284 if (accept_types == null)
285 accept_types = SplitHeader (HttpWorkerRequest.HeaderAccept);
291 public WindowsIdentity LogonUserIdentity {
292 get { throw new NotImplementedException (); }
296 public string AnonymousID {
301 anonymous_id = value;
305 public string ApplicationPath {
307 if (worker_request == null)
309 return worker_request.GetAppPath ();
313 public HttpBrowserCapabilities Browser {
315 if (browser_capabilities == null)
316 browser_capabilities = HttpCapabilitiesBase.BrowserCapabilitiesProvider.GetBrowserCapabilities (this);
318 return browser_capabilities;
322 browser_capabilities = value;
326 internal bool BrowserMightHaveSpecialWriter {
328 return (browser_capabilities != null
329 || HttpApplicationFactory.AppBrowsersFiles.Length > 0);
333 internal bool BrowserMightHaveAdapters {
335 return (browser_capabilities != null
336 || HttpApplicationFactory.AppBrowsersFiles.Length > 0);
340 public HttpClientCertificate ClientCertificate {
342 if (client_cert == null)
343 client_cert = new HttpClientCertificate (worker_request);
348 static internal string GetParameter (string header, string attr)
350 int ap = header.IndexOf (attr);
355 if (ap >= header.Length)
358 char ending = header [ap];
362 int end = header.IndexOf (ending, ap+1);
364 return (ending == '"') ? null : header.Substring (ap);
366 return header.Substring (ap+1, end-ap-1);
369 public Encoding ContentEncoding {
371 if (encoding == null){
372 if (worker_request == null)
373 throw HttpException.NewWithCode ("No HttpWorkerRequest", WebEventCodes.RuntimeErrorRequestAbort);
375 string content_type = ContentType;
376 string parameter = GetParameter (content_type, "; charset=");
377 if (parameter == null) {
378 encoding = WebEncoding.RequestEncoding;
381 // Do what the #1 web server does
382 encoding = Encoding.GetEncoding (parameter);
384 encoding = WebEncoding.RequestEncoding;
396 public int ContentLength {
398 if (content_length == -1){
399 if (worker_request == null)
402 string cl = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderContentLength);
406 content_length = Int32.Parse (cl);
411 // content_length will still be < 0, but we know we gotta read from the client
412 if (content_length < 0)
415 return content_length;
419 public string ContentType {
421 if (content_type == null){
422 if (worker_request != null)
423 content_type = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderContentType);
425 if (content_type == null)
426 content_type = String.Empty;
433 content_type = value;
437 internal HttpCookieCollection CookiesNoValidation {
439 if (cookies_unvalidated == null) {
440 if (worker_request == null) {
441 cookies_unvalidated = new HttpCookieCollection ();
443 string cookie_hv = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderCookie);
444 cookies_unvalidated = new HttpCookieCollection (cookie_hv);
448 return cookies_unvalidated;
452 public HttpCookieCollection Cookies {
454 if (cookies == null) {
455 cookies = CookiesNoValidation;
458 bool needValidation = validate_cookies;
459 needValidation |= validateRequestNewMode;
460 if (needValidation && !checked_cookies) {
461 // Setting this before calling the validator prevents
462 // possible endless recursion
463 checked_cookies = true;
464 ValidateCookieCollection (cookies);
472 public string CurrentExecutionFilePath {
474 if (current_exe_path != null)
475 return current_exe_path;
480 public string CurrentExecutionFilePathExtension {
481 get { return global::System.IO.Path.GetExtension (CurrentExecutionFilePath); }
483 public string AppRelativeCurrentExecutionFilePath {
485 return VirtualPathUtility.ToAppRelative (CurrentExecutionFilePath);
489 public string FilePath {
491 if (worker_request == null)
492 return "/"; // required for 2.0
494 if (file_path == null)
495 file_path = UrlUtils.Canonic (ApplyUrlMapping (worker_request.GetFilePath ()));
501 internal string ClientFilePath {
503 if (client_file_path == null) {
504 if (worker_request == null)
507 return UrlUtils.Canonic (ApplyUrlMapping (worker_request.GetFilePath ()));
510 return client_file_path;
514 if (value == null || value.Length == 0)
515 client_file_path = null;
517 client_file_path = value;
521 internal string BaseVirtualDir {
523 if (base_virtual_dir == null){
524 base_virtual_dir = FilePath;
525 if (UrlUtils.HasSessionId (base_virtual_dir))
526 base_virtual_dir = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (base_virtual_dir), base_virtual_dir);
528 int p = base_virtual_dir.LastIndexOf ('/');
532 base_virtual_dir = base_virtual_dir.Substring (0, p);
534 base_virtual_dir = "/";
536 return base_virtual_dir;
540 public HttpFileCollection Files {
543 files = new HttpFileCollection ();
544 if ((worker_request != null) && IsContentType ("multipart/form-data", true)) {
545 form = new WebROCollection ();
554 public Stream Filter {
559 if (input_filter == null)
560 input_filter = new InputFilterStream ();
566 // This checks that get_ was called before.
567 if (input_filter == null)
568 throw new HttpException ("Invalid filter");
574 // GetSubStream returns a 'copy' of the InputStream with Position set to 0.
575 static Stream GetSubStream (Stream stream)
577 if (stream is IntPtrStream)
578 return new IntPtrStream (stream);
580 if (stream is MemoryStream) {
581 MemoryStream other = (MemoryStream) stream;
582 return new MemoryStream (other.GetBuffer (), 0, (int) other.Length, false, true);
585 if (stream is TempFileStream) {
586 ((TempFileStream) stream).SavePosition ();
590 throw new NotSupportedException ("The stream is " + stream.GetType ());
593 static void EndSubStream (Stream stream)
595 if (stream is TempFileStream) {
596 ((TempFileStream) stream).RestorePosition ();
601 // Loads the data on the form for multipart/form-data
603 void LoadMultiPart ()
605 string boundary = GetParameter (ContentType, "; boundary=");
606 if (boundary == null)
609 Stream input = GetSubStream (InputStream);
610 HttpMultipart multi_part = new HttpMultipart (input, boundary, ContentEncoding);
612 HttpMultipart.Element e;
613 while ((e = multi_part.ReadNextElement ()) != null) {
614 if (e.Filename == null){
615 byte [] copy = new byte [e.Length];
617 input.Position = e.Start;
618 input.Read (copy, 0, (int) e.Length);
620 form.Add (e.Name, ContentEncoding.GetString (copy));
623 // We use a substream, as in 2.x we will support large uploads streamed to disk,
625 HttpPostedFile sub = new HttpPostedFile (e.Filename, e.ContentType, input, e.Start, e.Length);
626 files.AddFile (e.Name, sub);
629 EndSubStream (input);
633 // Adds the key/value to the form, and sets the argumets to empty
635 void AddRawKeyValue (StringBuilder key, StringBuilder value)
637 string decodedKey = HttpUtility.UrlDecode (key.ToString (), ContentEncoding);
638 form.Add (decodedKey,
639 HttpUtility.UrlDecode (value.ToString (), ContentEncoding));
646 // Loads the form data from on a application/x-www-form-urlencoded post
650 using (Stream input = GetSubStream (InputStream)) {
651 using (StreamReader s = new StreamReader (input, ContentEncoding)) {
652 StringBuilder key = new StringBuilder ();
653 StringBuilder value = new StringBuilder ();
656 while ((c = s.Read ()) != -1){
659 while ((c = s.Read ()) != -1){
661 AddRawKeyValue (key, value);
664 value.Append ((char) c);
667 AddRawKeyValue (key, value);
671 AddRawKeyValue (key, value);
673 key.Append ((char) c);
676 AddRawKeyValue (key, value);
678 EndSubStream (input);
683 bool IsContentType (string ct, bool starts_with)
686 return StrUtils.StartsWith (ContentType, ct, true);
688 return String.Compare (ContentType, ct, true, Helpers.InvariantCulture) == 0;
691 internal WebROCollection FormUnvalidated {
694 form = new WebROCollection ();
695 files = new HttpFileCollection ();
697 if (IsContentType ("multipart/form-data", true))
700 IsContentType ("application/x-www-form-urlencoded", true))
710 public NameValueCollection Form {
712 NameValueCollection form = FormUnvalidated;
713 if (validateRequestNewMode && !checked_form) {
714 if (!lazyFormValidation) {
715 // Setting this before calling the validator prevents
716 // possible endless recursion
718 ValidateNameValueCollection ("Form", form, RequestValidationSource.Form);
721 if (validate_form && !checked_form){
723 ValidateNameValueCollection ("Form", form);
730 internal NameValueCollection HeadersNoValidation {
732 if (headers_unvalidated == null) {
733 headers_unvalidated = new HeadersCollection (this);
736 return headers_unvalidated;
740 public NameValueCollection Headers {
742 if (headers == null) {
743 headers = HeadersNoValidation;
744 if (validateRequestNewMode) {
745 RequestValidator validator = RequestValidator.Current;
746 int validationFailureIndex;
748 foreach (string hkey in headers.AllKeys) {
749 string value = headers [hkey];
751 if (!validator.IsValidRequestString (HttpContext.Current, value, RequestValidationSource.Headers, hkey, out validationFailureIndex))
752 ThrowValidationException ("Headers", hkey, value);
761 public string HttpMethod {
763 if (http_method == null){
764 if (worker_request != null)
765 http_method = worker_request.GetHttpVerbName ();
773 void DoFilter (byte [] buffer)
775 if (input_filter == null || filter == null)
778 if (buffer.Length < 1024)
779 buffer = new byte [1024];
781 // Replace the input with the filtered input
782 input_filter.BaseStream = input_stream;
783 MemoryStream ms = new MemoryStream ();
785 int n = filter.Read (buffer, 0, buffer.Length);
788 ms.Write (buffer, 0, n);
790 // From now on input_stream has the filtered input
791 input_stream = new MemoryStream (ms.GetBuffer (), 0, (int) ms.Length, false, true);
794 const int INPUT_BUFFER_SIZE = 32*1024;
796 TempFileStream GetTempStream ()
798 string tempdir = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
799 TempFileStream f = null;
801 Random rnd = new Random ();
806 path = System.IO.Path.Combine (tempdir, "tmp" + num.ToString("x") + ".req");
809 f = new TempFileStream (path);
810 } catch (SecurityException) {
811 // avoid an endless loop
819 void MakeInputStream ()
821 if (input_stream != null)
824 if (worker_request == null) {
825 input_stream = new MemoryStream (new byte [0], 0, 0, false, true);
826 DoFilter (new byte [1024]);
831 // Use an unmanaged memory block as this might be a large
834 int content_length = ContentLength;
835 int content_length_kb = content_length / 1024;
836 HttpRuntimeSection config = HttpRuntime.Section;
837 if (content_length_kb > config.MaxRequestLength)
838 throw HttpException.NewWithCode (400, "Upload size exceeds httpRuntime limit.", WebEventCodes.RuntimeErrorPostTooLarge);
842 buffer = worker_request.GetPreloadedEntityBody ();
843 // we check the instance field 'content_length' here, not the local var.
844 if (this.content_length <= 0 || worker_request.IsEntireEntityBodyIsPreloaded ()) {
845 if (buffer == null || content_length == 0) {
846 input_stream = new MemoryStream (new byte [0], 0, 0, false, true);
848 input_stream = new MemoryStream (buffer, 0, buffer.Length, false, true);
850 DoFilter (new byte [1024]);
855 total = buffer.Length;
857 if (content_length > 0 && content_length_kb >= config.RequestLengthDiskThreshold) {
858 // Writes the request to disk
859 total = Math.Min (content_length, total);
860 request_file = GetTempStream ();
861 Stream output = request_file;
863 output.Write (buffer, 0, total);
865 if (total < content_length) {
866 buffer = new byte [Math.Min (content_length, INPUT_BUFFER_SIZE)];
869 int min = Math.Min (content_length - total, INPUT_BUFFER_SIZE);
870 n = worker_request.ReadEntityBody (buffer, min);
873 output.Write (buffer, 0, n);
875 } while (total < content_length);
878 request_file.SetReadOnly ();
879 input_stream = request_file;
880 } else if (content_length > 0) {
881 // Buffers the request in an IntPtrStream
882 total = Math.Min (content_length, total);
883 IntPtr content = Marshal.AllocHGlobal (content_length);
884 if (content == (IntPtr) 0)
885 throw HttpException.NewWithCode (
886 String.Format ("Not enough memory to allocate {0} bytes.", content_length),
887 WebEventCodes.WebErrorOtherError);
890 Marshal.Copy (buffer, 0, content, total);
892 if (total < content_length) {
893 buffer = new byte [Math.Min (content_length, INPUT_BUFFER_SIZE)];
896 int min = Math.Min (content_length - total, INPUT_BUFFER_SIZE);
897 n = worker_request.ReadEntityBody (buffer, min);
900 Marshal.Copy (buffer, 0, (IntPtr) ((long)content + total), n);
902 } while (total < content_length);
905 input_stream = new IntPtrStream (content, total);
907 // Buffers the request in a MemoryStream or writes to disk if threshold exceeded
908 MemoryStream ms = new MemoryStream ();
911 ms.Write (buffer, 0, total);
913 buffer = new byte [INPUT_BUFFER_SIZE];
914 long maxlength = config.MaxRequestLength * 1024L;
915 long disk_th = config.RequestLengthDiskThreshold * 1024L;
918 n = worker_request.ReadEntityBody (buffer, INPUT_BUFFER_SIZE);
922 if (total < 0 || total > maxlength)
923 throw HttpException.NewWithCode (400, "Upload size exceeds httpRuntime limit.", WebEventCodes.RuntimeErrorPostTooLarge);
925 if (ms != null && total > disk_th) {
926 // Swith to on-disk file.
927 request_file = GetTempStream ();
928 ms.WriteTo (request_file);
930 output = request_file;
932 output.Write (buffer, 0, n);
936 input_stream = new MemoryStream (ms.GetBuffer (), 0, (int) ms.Length, false, true);
938 request_file.SetReadOnly ();
939 input_stream = request_file;
944 if (total < content_length)
945 throw HttpException.NewWithCode (411, "The request body is incomplete.", WebEventCodes.WebErrorOtherError);
948 internal void ReleaseResources ()
951 if (input_stream != null){
952 stream = input_stream;
959 if (request_file != null) {
960 stream = request_file;
967 public RequestContext RequestContext {
969 if (requestContext == null)
970 requestContext = new RequestContext (new HttpContextWrapper (this.context ?? HttpContext.Current), new RouteData ());
972 return requestContext;
975 internal set { requestContext = value; }
978 public ChannelBinding HttpChannelBinding {
980 throw new PlatformNotSupportedException ("This property is not supported.");
984 public Stream GetBufferedInputStream ()
989 public Stream GetBufferlessInputStream ()
991 if (bufferlessInputStream == null) {
992 if (input_stream != null)
993 throw new HttpException ("Input stream has already been created");
995 // we don't need to hook up the filter here, because the raw stream should be returned
996 bufferlessInputStream = new BufferlessInputStream (this);
999 return bufferlessInputStream;
1002 public Stream GetBufferlessInputStream (bool disableMaxRequestLength)
1004 return GetBufferlessInputStream ();
1008 // Stream that returns the data as it is read, without buffering
1010 class BufferlessInputStream : Stream {
1011 HttpRequest request;
1013 // cached, the request content-length
1016 // buffer that holds preloaded data
1017 byte [] preloadedBuffer;
1019 // indicates if we already served the whole preloaded buffer
1020 bool preloaded_served;
1022 // indicates if we already checked the request content-length against httpRuntime limit
1023 bool checked_maxRequestLength;
1025 // our stream position
1029 // @request: the containing request that created us, used to find out content length
1030 public BufferlessInputStream (HttpRequest request)
1032 this.request = request;
1033 content_length = request.ContentLength;
1036 public override bool CanRead {
1037 get { return true; }
1040 public override bool CanSeek {
1041 get { return false; }
1044 public override bool CanWrite {
1045 get { return false; }
1048 public override long Length {
1050 return content_length;
1054 public override long Position {
1059 throw new NotSupportedException ("This is a readonly stream");
1063 public override void Flush ()
1067 public override int Read (byte [] buffer, int offset, int count)
1070 throw new ArgumentNullException ("buffer");
1072 if (offset < 0 || count < 0)
1073 throw new ArgumentOutOfRangeException ("offset or count less than zero.");
1075 if (buffer.Length - offset < count )
1076 throw new ArgumentException ("offset+count",
1077 "The size of the buffer is less than offset + count.");
1079 if (count == 0 || request.worker_request == null)
1082 if (!checked_maxRequestLength) {
1083 int content_length_kb = content_length / 1024;
1084 HttpRuntimeSection config = HttpRuntime.Section;
1085 if (content_length_kb > config.MaxRequestLength)
1086 throw HttpException.NewWithCode (400, "Upload size exceeds httpRuntime limit.", WebEventCodes.RuntimeErrorPostTooLarge);
1088 checked_maxRequestLength = true;
1091 // Serve the bytes we might have preloaded already.
1092 if (!preloaded_served) {
1093 if (preloadedBuffer == null)
1094 preloadedBuffer = request.worker_request.GetPreloadedEntityBody ();
1096 if (preloadedBuffer != null) {
1097 long bytes_left = preloadedBuffer.Length-position;
1098 int n = (int) Math.Min (count, bytes_left);
1099 Array.Copy (preloadedBuffer, position, buffer, offset, n);
1102 if (n == bytes_left)
1103 preloaded_served = true;
1108 preloaded_served = true;
1111 // serve bytes from worker request if available
1112 if (position < content_length) {
1113 long bytes_left = content_length-position;
1116 if (bytes_left < count)
1117 n = (int) bytes_left;
1119 int bytes_read = request.worker_request.ReadEntityBody (buffer, offset, n);
1120 position += bytes_read;
1127 public override long Seek (long offset, SeekOrigin origin)
1129 throw new NotSupportedException ("Can not seek on the HttpRequest.BufferlessInputStream");
1132 public override void SetLength (long value)
1134 throw new NotSupportedException ("Can not set length on the HttpRequest.BufferlessInputStream");
1137 public override void Write (byte [] buffer, int offset, int count)
1139 throw new NotSupportedException ("Can not write on the HttpRequest.BufferlessInputStream");
1143 // TODO: explicitly support the async methods if there is a convenient way of doing it
1146 public Stream InputStream {
1148 if (input_stream == null)
1151 return input_stream;
1155 public bool IsAuthenticated {
1157 if (context.User == null || context.User.Identity == null)
1159 return context.User.Identity.IsAuthenticated;
1163 public bool IsSecureConnection {
1165 if (worker_request == null)
1167 return worker_request.IsSecure ();
1171 public string this [string key] {
1172 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
1174 // "The QueryString, Form, Cookies, or ServerVariables collection member
1175 // specified in the key parameter."
1176 string val = QueryString [key];
1180 HttpCookie cookie = Cookies [key];
1185 val = ServerVariables [key];
1191 public NameValueCollection Params {
1192 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
1194 if (all_params == null)
1195 all_params = new HttpParamsCollection (QueryString, Form, ServerVariables, Cookies);
1201 internal string PathNoValidation {
1203 if (original_path == null) {
1204 if (url_components != null)
1205 // use only if it's already been instantiated, so that we can't go into endless
1206 // recursion in some scenarios
1207 original_path = UrlComponents.Path;
1209 original_path = ApplyUrlMapping (worker_request.GetUriPath ());
1212 return original_path;
1216 public string Path {
1218 if (unescaped_path == null) {
1219 unescaped_path = PathNoValidation;
1220 if (validateRequestNewMode) {
1221 RequestValidator validator = RequestValidator.Current;
1222 int validationFailureIndex;
1224 if (!validator.IsValidRequestString (HttpContext.Current, unescaped_path, RequestValidationSource.Path, null, out validationFailureIndex))
1225 ThrowValidationException ("Path", "Path", unescaped_path);
1229 return unescaped_path;
1233 internal string PathInfoNoValidation {
1235 if (path_info_unvalidated == null) {
1236 if (worker_request == null)
1237 return String.Empty;
1239 path_info_unvalidated = worker_request.GetPathInfo () ?? String.Empty;
1242 return path_info_unvalidated;
1246 public string PathInfo {
1248 if (path_info == null) {
1249 path_info = PathInfoNoValidation;
1250 if (validateRequestNewMode) {
1251 RequestValidator validator = RequestValidator.Current;
1252 int validationFailureIndex;
1254 if (!validator.IsValidRequestString (HttpContext.Current, path_info, RequestValidationSource.PathInfo, null, out validationFailureIndex))
1255 ThrowValidationException ("PathInfo", "PathInfo", path_info);
1263 public string PhysicalApplicationPath {
1265 if (worker_request == null)
1266 throw new ArgumentNullException (); // like 2.0, 1.x throws TypeInitializationException
1268 string path = HttpRuntime.AppDomainAppPath;
1269 if (SecurityManager.SecurityEnabled) {
1270 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, path).Demand ();
1276 public string PhysicalPath {
1278 if (worker_request == null)
1279 return String.Empty; // don't check security with an empty string!
1281 if (physical_path == null) {
1282 // Don't call HttpRequest.MapPath here, as that one *trims* the input
1283 physical_path = worker_request.MapPath (FilePath);
1286 if (SecurityManager.SecurityEnabled) {
1287 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, physical_path).Demand ();
1289 return physical_path;
1293 internal string RootVirtualDir {
1295 if (root_virtual_dir == null){
1296 string fp = FilePath;
1297 int p = fp.LastIndexOf ('/');
1300 root_virtual_dir = "/";
1302 root_virtual_dir = fp.Substring (0, p);
1304 return root_virtual_dir;
1308 internal WebROCollection QueryStringUnvalidated {
1310 if (query_string_nvc == null) {
1311 query_string_nvc = new WebROCollection ();
1312 string q = UrlComponents.Query;
1317 HttpUtility.ParseQueryString (q, ContentEncoding, query_string_nvc);
1320 query_string_nvc.Protect();
1323 return query_string_nvc;
1327 public NameValueCollection QueryString {
1329 NameValueCollection query_string_nvc = QueryStringUnvalidated;
1330 if (validateRequestNewMode && !checked_query_string) {
1331 if (!lazyQueryStringValidation) {
1332 // Setting this before calling the validator prevents
1333 // possible endless recursion
1334 checked_query_string = true;
1335 ValidateNameValueCollection ("QueryString", query_string_nvc, RequestValidationSource.QueryString);
1338 if (validate_query_string && !checked_query_string) {
1339 // Setting this before calling the validator prevents
1340 // possible endless recursion
1341 checked_query_string = true;
1342 ValidateNameValueCollection ("QueryString", query_string_nvc);
1345 return query_string_nvc;
1349 internal string RawUrlUnvalidated {
1351 if (raw_url_unvalidated == null) {
1352 if (worker_request != null)
1353 raw_url_unvalidated = worker_request.GetRawUrl ();
1355 raw_url_unvalidated = UrlComponents.Path + UrlComponents.Query;
1357 if (raw_url_unvalidated == null)
1358 raw_url_unvalidated = String.Empty;
1361 return raw_url_unvalidated;
1365 public string RawUrl {
1367 if (raw_url == null) {
1368 raw_url = RawUrlUnvalidated;
1369 if (validateRequestNewMode) {
1370 RequestValidator validator = RequestValidator.Current;
1371 int validationFailureIndex;
1373 if (!validator.IsValidRequestString (HttpContext.Current, raw_url, RequestValidationSource.RawUrl, null, out validationFailureIndex))
1374 ThrowValidationException ("RawUrl", "RawUrl", raw_url);
1385 public string RequestType {
1387 if (request_type == null){
1388 if (worker_request != null) {
1389 request_type = worker_request.GetHttpVerbName ();
1390 http_method = request_type;
1392 request_type = "GET";
1395 return request_type;
1399 request_type = value;
1403 public NameValueCollection ServerVariables {
1404 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Low)]
1406 if (server_variables == null)
1407 server_variables = new ServerVariablesCollection (this);
1409 return server_variables;
1413 public CancellationToken TimedOutToken {
1415 throw new NotImplementedException ();
1419 public int TotalBytes {
1421 Stream ins = InputStream;
1422 return (int) ins.Length;
1426 public UnvalidatedRequestValues Unvalidated {
1428 var vals = new UnvalidatedRequestValues ();
1430 vals.Cookies = CookiesNoValidation;
1432 vals.Form = FormUnvalidated;
1433 vals.Headers = HeadersNoValidation;
1434 vals.Path = PathNoValidation;
1435 vals.PathInfo = PathInfoNoValidation;
1436 vals.QueryString = QueryStringUnvalidated;
1437 vals.RawUrl = RawUrlUnvalidated;
1446 if (cached_url == null) {
1447 if (orig_url == null)
1448 cached_url = UrlComponents.Uri;
1450 cached_url = new Uri (orig_url);
1457 public Uri UrlReferrer {
1459 if (worker_request == null)
1462 string hr = worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderReferer);
1469 } catch (UriFormatException) {}
1474 public string UserAgent {
1476 if (worker_request == null)
1479 return worker_request.GetKnownRequestHeader (HttpWorkerRequest.HeaderUserAgent);
1483 public string UserHostAddress {
1485 if (worker_request == null)
1488 return worker_request.GetRemoteAddress ();
1492 public string UserHostName {
1494 if (worker_request == null)
1497 return worker_request.GetRemoteName ();
1501 public string [] UserLanguages {
1503 if (worker_request == null)
1506 if (user_languages == null)
1507 user_languages = SplitHeader (HttpWorkerRequest.HeaderAcceptLanguage);
1509 return user_languages;
1513 public byte [] BinaryRead (int count)
1516 throw new ArgumentException ("count is < 0");
1518 Stream s = InputStream;
1519 byte [] ret = new byte [count];
1520 if (s.Read (ret, 0, count) != count)
1521 throw new ArgumentException (
1522 String.Format ("count {0} exceeds length of available input {1}",
1523 count, s.Length - s.Position));
1527 public int [] MapImageCoordinates (string imageFieldName)
1529 string[] parameters = GetImageCoordinatesParameters (imageFieldName);
1530 if (parameters == null)
1532 int [] result = new int [2];
1534 result [0] = Int32.Parse (parameters [0]);
1535 result [1] = Int32.Parse (parameters [1]);
1543 public double [] MapRawImageCoordinates (string imageFieldName)
1545 string[] parameters = GetImageCoordinatesParameters (imageFieldName);
1546 if (parameters == null)
1548 double [] result = new double [2];
1550 result [0] = Double.Parse (parameters [0]);
1551 result [1] = Double.Parse (parameters [1]);
1559 string [] GetImageCoordinatesParameters (string imageFieldName)
1561 string method = HttpMethod;
1562 NameValueCollection coll = null;
1563 if (method == "HEAD" || method == "GET")
1565 else if (method == "POST")
1571 string x = coll [imageFieldName + ".x"];
1572 if (x == null || x == "")
1575 string y = coll [imageFieldName + ".y"];
1576 if (y == null || y == "")
1578 string[] result = new string [] { x, y };
1583 public string MapPath (string virtualPath)
1585 if (worker_request == null)
1588 return MapPath (virtualPath, BaseVirtualDir, true);
1591 public string MapPath (string virtualPath, string baseVirtualDir, bool allowCrossAppMapping)
1593 if (worker_request == null)
1594 throw HttpException.NewWithCode ("No HttpWorkerRequest", WebEventCodes.RuntimeErrorRequestAbort);
1596 if (virtualPath == null)
1599 virtualPath = virtualPath.Trim ();
1600 if (virtualPath.Length == 0)
1604 if (!VirtualPathUtility.IsValidVirtualPath (virtualPath))
1605 throw HttpException.NewWithCode (String.Format ("'{0}' is not a valid virtual path.", virtualPath), WebEventCodes.RuntimeErrorRequestAbort);
1607 string appVirtualPath = HttpRuntime.AppDomainAppVirtualPath;
1608 if (!VirtualPathUtility.IsRooted (virtualPath)) {
1609 if (StrUtils.IsNullOrEmpty (baseVirtualDir))
1610 baseVirtualDir = appVirtualPath;
1611 virtualPath = VirtualPathUtility.Combine (VirtualPathUtility.AppendTrailingSlash (baseVirtualDir), virtualPath);
1612 if (!VirtualPathUtility.IsAbsolute (virtualPath))
1613 virtualPath = VirtualPathUtility.ToAbsolute (virtualPath, false);
1614 } else if (!VirtualPathUtility.IsAbsolute (virtualPath))
1615 virtualPath = VirtualPathUtility.ToAbsolute (virtualPath, false);
1617 bool isAppVirtualPath = String.Compare (virtualPath, appVirtualPath, RuntimeHelpers.StringComparison) == 0;
1618 appVirtualPath = VirtualPathUtility.AppendTrailingSlash (appVirtualPath);
1619 if (!allowCrossAppMapping){
1620 if (!StrUtils.StartsWith (virtualPath, appVirtualPath, true))
1621 throw new ArgumentException ("MapPath: Mapping across applications not allowed");
1622 if (appVirtualPath.Length > 1 && virtualPath.Length > 1 && virtualPath [0] != '/')
1623 throw HttpException.NewWithCode ("MapPath: Mapping across applications not allowed", WebEventCodes.RuntimeErrorRequestAbort);
1626 if (!isAppVirtualPath && !virtualPath.StartsWith (appVirtualPath, RuntimeHelpers.StringComparison))
1627 throw new InvalidOperationException (String.Format ("Failed to map path '{0}'", virtualPath));
1628 string path = worker_request.MapPath (virtualPath);
1629 if (virtualPath [virtualPath.Length - 1] != '/' && path [path.Length - 1] == System.IO.Path.DirectorySeparatorChar)
1630 path = path.TrimEnd (System.IO.Path.DirectorySeparatorChar);
1634 public void SaveAs (string filename, bool includeHeaders)
1636 Stream output = new FileStream (filename, FileMode.Create);
1637 if (includeHeaders) {
1638 StringBuilder sb = new StringBuilder ();
1639 string version = String.Empty;
1641 if (worker_request != null) {
1642 version = worker_request.GetHttpVersion ();
1643 path = UrlComponents.Path;
1645 string qs = UrlComponents.Query;
1647 sb.AppendFormat ("{0} {1}{2} {3}\r\n", HttpMethod, path, qs, version);
1648 NameValueCollection coll = Headers;
1649 foreach (string k in coll.AllKeys) {
1652 sb.Append (coll [k]);
1657 byte [] bytes = Encoding.GetEncoding (28591).GetBytes (sb.ToString ());
1658 output.Write (bytes, 0, bytes.Length);
1661 // More than 1 call to SaveAs works fine on MS, so we "copy" the stream
1662 // to keep InputStream in its state.
1663 Stream input = GetSubStream (InputStream);
1665 long len = input.Length;
1666 int buf_size = (int) Math.Min ((len < 0 ? 0 : len), 8192);
1667 byte [] data = new byte [buf_size];
1669 while (len > 0 && (count = input.Read (data, 0, buf_size)) > 0) {
1670 output.Write (data, 0, count);
1676 EndSubStream (input);
1680 public void ValidateInput ()
1682 validate_cookies = true;
1683 validate_query_string = true;
1684 validate_form = true;
1685 inputValidationEnabled = true;
1687 internal void Validate ()
1689 var cfg = HttpRuntime.Section;
1690 string query = UrlComponents.Query;
1692 if (query != null && query.Length > cfg.MaxQueryStringLength)
1693 throw new HttpException (400, "The length of the query string for this request exceeds the configured maxQueryStringLength value.");
1695 string path = PathNoValidation;
1697 if (path.Length > cfg.MaxUrlLength)
1698 throw new HttpException (400, "The length of the URL for this request exceeds the configured maxUrlLength value.");
1700 char[] invalidChars = RequestPathInvalidCharacters;
1701 if (invalidChars != null) {
1702 int idx = path.IndexOfAny (invalidChars);
1704 throw HttpException.NewWithCode (
1705 String.Format ("A potentially dangerous Request.Path value was detected from the client ({0}).", path [idx]),
1706 WebEventCodes.RuntimeErrorValidationFailure
1711 if (validateRequestNewMode)
1714 #region internal routines
1715 internal string ClientTarget {
1717 return client_target;
1721 client_target = value;
1725 public bool IsLocal {
1727 string address = worker_request.GetRemoteAddress ();
1729 if (StrUtils.IsNullOrEmpty (address))
1732 if (address == "127.0.0.1")
1735 System.Net.IPAddress remoteAddr = System.Net.IPAddress.Parse (address);
1736 if (System.Net.IPAddress.IsLoopback (remoteAddr))
1739 for (int i = 0; i < host_addresses.Length; i++)
1740 if (remoteAddr.Equals (host_addresses [i]))
1747 internal void SetFilePath (string path)
1750 physical_path = null;
1751 original_path = null;
1754 internal void SetCurrentExePath (string path)
1757 current_exe_path = path;
1758 UrlComponents.Path = path + PathInfo;
1759 // recreated on demand
1760 root_virtual_dir = null;
1761 base_virtual_dir = null;
1762 physical_path = null;
1763 unescaped_path = null;
1764 original_path = null;
1767 internal void SetPathInfo (string pi)
1771 original_path = null;
1773 string path = UrlComponents.Path;
1774 UrlComponents.Path = path + PathInfo;
1776 internal void SetFormCollection (WebROCollection coll, bool lazyValidation)
1781 lazyFormValidation = lazyValidation;
1784 internal void SetQueryStringCollection (WebROCollection coll, bool lazyValidation)
1788 query_string_nvc = coll;
1789 lazyQueryStringValidation = lazyValidation;
1791 // Headers is ReadOnly, so we need this hack for cookie-less sessions.
1792 internal void SetHeader (string name, string value)
1794 WebROCollection h = (WebROCollection) Headers;
1800 // Notice: there is nothing raw about this querystring.
1801 internal string QueryStringRaw {
1803 UriBuilder urlComponents = UrlComponents;
1805 if (urlComponents == null) {
1806 string ret = worker_request.GetQueryString ();
1808 if (ret == null || ret.Length == 0)
1809 return String.Empty;
1817 return UrlComponents.Query;
1821 UrlComponents.Query = value;
1823 query_string_nvc = null;
1827 // Internal, dont know what it does, so flagged as public so we can see it.
1828 internal void SetForm (WebROCollection coll)
1833 internal HttpWorkerRequest WorkerRequest {
1835 return worker_request;
1839 internal HttpContext Context {
1840 get { return context; }
1841 set { context = value; }
1844 static void ValidateNameValueCollection (string name, NameValueCollection coll)
1849 foreach (string key in coll.Keys) {
1850 string val = coll [key];
1851 if (val != null && val.Length > 0 && IsInvalidString (val))
1852 ThrowValidationException (name, key, val);
1855 static void ValidateNameValueCollection (string name, NameValueCollection coll, RequestValidationSource source)
1860 RequestValidator validator = RequestValidator.Current;
1861 int validationFailureIndex;
1862 HttpContext context = HttpContext.Current;
1864 foreach (string key in coll.Keys) {
1865 string val = coll [key];
1866 if (val != null && val.Length > 0 && !validator.IsValidRequestString (context, val, source, key, out validationFailureIndex))
1867 ThrowValidationException (name, key, val);
1871 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
1872 public void InsertEntityBody ()
1874 throw new PlatformNotSupportedException ("This method is not supported.");
1877 [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.High)]
1878 public void InsertEntityBody (byte[] buffer, int offset, int count)
1880 throw new PlatformNotSupportedException ("This method is not supported.");
1882 static void ValidateCookieCollection (HttpCookieCollection cookies)
1884 if (cookies == null)
1887 int size = cookies.Count;
1889 RequestValidator validator = RequestValidator.Current;
1890 int validationFailureIndex;
1891 HttpContext context = HttpContext.Current;
1894 for (int i = 0 ; i < size ; i++) {
1895 cookie = cookies[i];
1899 string value = cookie.Value;
1900 string name = cookie.Name;
1902 if (!String.IsNullOrEmpty (value)) {
1903 if (validateRequestNewMode)
1904 invalid = !validator.IsValidRequestString (context, value, RequestValidationSource.Cookies, name, out validationFailureIndex);
1906 invalid = IsInvalidString (value);
1909 ThrowValidationException ("Cookies", name, value);
1914 static void ThrowValidationException (string name, string key, string value)
1916 string v = "\"" + value + "\"";
1918 v = v.Substring (0, 16) + "...\"";
1920 string msg = String.Format ("A potentially dangerous Request.{0} value was " +
1921 "detected from the client ({1}={2}).", name, key, v);
1923 throw new HttpRequestValidationException (msg);
1925 internal static void ValidateString (string key, string value, RequestValidationSource source)
1927 if (String.IsNullOrEmpty (value))
1929 #pragma warning disable 219
1931 #pragma warning restore 219
1932 if (IsInvalidString (value, out ignore))
1933 ThrowValidationException (source.ToString (), key, value);
1935 internal static bool IsInvalidString (string val)
1937 #pragma warning disable 219
1938 int validationFailureIndex;
1939 #pragma warning restore 219
1940 return IsInvalidString (val, out validationFailureIndex);
1943 internal static bool IsInvalidString (string val, out int validationFailureIndex)
1945 validationFailureIndex = 0;
1947 int len = val.Length;
1951 char current = val [0];
1952 for (int idx = 1; idx < len; idx++) {
1953 char next = val [idx];
1954 // See http://secunia.com/advisories/14325
1955 if (current == '<' || current == '\xff1c') {
1956 if (next == '!' || next < ' '
1957 || (next >= 'a' && next <= 'z')
1958 || (next >= 'A' && next <= 'Z')) {
1959 validationFailureIndex = idx - 1;
1962 } else if (current == '&' && next == '#') {
1963 validationFailureIndex = idx - 1;
1973 static System.Net.IPAddress [] GetLocalHostAddresses ()
1976 string hostName = System.Net.Dns.GetHostName ();
1977 System.Net.IPAddress [] ipaddr = System.Net.Dns.GetHostAddresses (hostName);
1980 return new System.Net.IPAddress[0];
1986 #region Helper classes
1989 // Stream-based multipart handling.
1991 // In this incarnation deals with an HttpInputStream as we are now using
1992 // IntPtr-based streams instead of byte []. In the future, we will also
1993 // send uploads above a certain threshold into the disk (to implement
1994 // limit-less HttpInputFiles).
1997 class HttpMultipart {
1999 public class Element {
2000 public string ContentType;
2002 public string Filename;
2006 public override string ToString ()
2008 return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
2009 Start.ToString () + ", Length " + Length.ToString ();
2015 byte [] boundary_bytes;
2021 const byte HYPHEN = (byte) '-', LF = (byte) '\n', CR = (byte) '\r';
2024 // In the case of multipart entities, in which one or more different
2025 // sets of data are combined in a single body, a "multipart" media type
2026 // field must appear in the entity's header. The body must then contain
2027 // one or more body parts, each preceded by a boundary delimiter line,
2028 // and the last one followed by a closing boundary delimiter line.
2029 // After its boundary delimiter line, each body part then consists of a
2030 // header area, a blank line, and a body area. Thus a body part is
2031 // similar to an RFC 822 message in syntax, but different in meaning.
2033 public HttpMultipart (Stream data, string b, Encoding encoding)
2037 boundary_bytes = encoding.GetBytes (b);
2038 buffer = new byte [boundary_bytes.Length + 2]; // CRLF or '--'
2039 this.encoding = encoding;
2040 sb = new StringBuilder ();
2045 // CRLF or LF are ok as line endings.
2046 bool got_cr = false;
2050 b = data.ReadByte ();
2059 sb.Append ((char) b);
2065 return sb.ToString ();
2069 static string GetContentDispositionAttribute (string l, string name)
2071 int idx = l.IndexOf (name + "=\"");
2074 int begin = idx + name.Length + "=\"".Length;
2075 int end = l.IndexOf ('"', begin);
2080 return l.Substring (begin, end - begin);
2083 string GetContentDispositionAttributeWithEncoding (string l, string name)
2085 int idx = l.IndexOf (name + "=\"");
2088 int begin = idx + name.Length + "=\"".Length;
2089 int end = l.IndexOf ('"', begin);
2095 string temp = l.Substring (begin, end - begin);
2096 byte [] source = new byte [temp.Length];
2097 for (int i = temp.Length - 1; i >= 0; i--)
2098 source [i] = (byte) temp [i];
2100 return encoding.GetString (source);
2103 bool ReadBoundary ()
2106 string line = ReadLine ();
2109 if (line [0] != '-' || line [1] != '-')
2112 if (!StrUtils.EndsWith (line, boundary, false))
2120 string ReadHeaders ()
2122 string s = ReadLine ();
2129 bool CompareBytes (byte [] orig, byte [] other)
2131 for (int i = orig.Length - 1; i >= 0; i--)
2132 if (orig [i] != other [i])
2138 long MoveToNextBoundary ()
2141 bool got_cr = false;
2144 int c = data.ReadByte ();
2149 if (state == 0 && c == LF) {
2150 retval = data.Position - 1;
2154 c = data.ReadByte ();
2155 } else if (state == 0) {
2157 c = data.ReadByte ();
2158 } else if (state == 1 && c == '-') {
2159 c = data.ReadByte ();
2166 continue; // no ReadByte() here
2169 int nread = data.Read (buffer, 0, buffer.Length);
2170 int bl = buffer.Length;
2174 if (!CompareBytes (boundary_bytes, buffer)) {
2176 data.Position = retval + 2;
2181 c = data.ReadByte ();
2185 if (buffer [bl - 2] == '-' && buffer [bl - 1] == '-') {
2187 } else if (buffer [bl - 2] != CR || buffer [bl - 1] != LF) {
2189 data.Position = retval + 2;
2194 c = data.ReadByte ();
2197 data.Position = retval + 2;
2203 state = 0; // no ReadByte() here
2210 public Element ReadNextElement ()
2212 if (at_eof || ReadBoundary ())
2215 Element elem = new Element ();
2217 while ((header = ReadHeaders ()) != null) {
2218 if (StrUtils.StartsWith (header, "Content-Disposition:", true)) {
2219 elem.Name = GetContentDispositionAttribute (header, "name");
2220 elem.Filename = StripPath (GetContentDispositionAttributeWithEncoding (header, "filename"));
2221 } else if (StrUtils.StartsWith (header, "Content-Type:", true)) {
2222 elem.ContentType = header.Substring ("Content-Type:".Length).Trim ();
2226 long start = data.Position;
2228 long pos = MoveToNextBoundary ();
2232 elem.Length = pos - start;
2236 static string StripPath (string path)
2238 if (path == null || path.Length == 0)
2241 if (path.IndexOf (":\\") != 1 && !path.StartsWith ("\\\\"))
2243 return path.Substring (path.LastIndexOf ('\\') + 1);