From 7ef60bfb214636af85f269e1279f2453047908e3 Mon Sep 17 00:00:00 2001 From: Oleksiy Glib Date: Tue, 23 Oct 2012 15:00:28 +0300 Subject: [PATCH] Adding files upload functionality --- .gitignore | 1 + .../Gate.Middleware/ShowExceptions.View.cs | 4 +- src/Main/Gate/Form/Form.cs | 127 ++++++++++++++++++ src/Main/Gate/Form/FormFile.cs | 60 +++++++++ src/Main/Gate/Gate.csproj | 4 + src/Main/Gate/IForm.cs | 10 ++ src/Main/Gate/IFormFile.cs | 13 ++ src/Main/Gate/Request.cs | 70 +++++++--- 8 files changed, 271 insertions(+), 18 deletions(-) create mode 100644 src/Main/Gate/Form/Form.cs create mode 100644 src/Main/Gate/Form/FormFile.cs create mode 100644 src/Main/Gate/IForm.cs create mode 100644 src/Main/Gate/IFormFile.cs diff --git a/.gitignore b/.gitignore index 07f1aa9..e0ddd7b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ obj *.suo *.user _ReSharper.* +*.ReSharper *.DS_Store *.userprefs *.pidb diff --git a/src/Main/Gate.Middleware/ShowExceptions.View.cs b/src/Main/Gate.Middleware/ShowExceptions.View.cs index c5580b9..dd6c3c2 100644 --- a/src/Main/Gate.Middleware/ShowExceptions.View.cs +++ b/src/Main/Gate.Middleware/ShowExceptions.View.cs @@ -353,7 +353,7 @@ unknown location "); var form = request.ReadForm(); - if (form.Any()) + if (form.Fields.Any()) { write(@" @@ -365,7 +365,7 @@ unknown location "); - foreach (var kv in form.OrderBy(kv => kv.Key)) + foreach (var kv in form.Fields.OrderBy(kv => kv.Key)) { write(@" diff --git a/src/Main/Gate/Form/Form.cs b/src/Main/Gate/Form/Form.cs new file mode 100644 index 0000000..f94e836 --- /dev/null +++ b/src/Main/Gate/Form/Form.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Gate.Utils; + +namespace Gate.Form +{ + internal class Form : IForm + { + readonly Encoding _encoding = new ASCIIEncoding(); + + public Form() + { + Fields = ParamDictionary.Parse(""); + Files = new Dictionary(); + } + + public Form(string text) + { + Fields = ParamDictionary.Parse(text); + Files = new Dictionary(); + } + + public Form(string boundary, Stream stream) + { + Fields = new Dictionary(); + Files = new Dictionary(); + if (stream == null) + return; + if (stream.CanSeek) + stream.Seek(0, SeekOrigin.Begin); + ReadData(boundary, stream); + } + + private static int IndexOf(byte[] searchIn, byte[] searchBytes, int start = 0) + { + var found = -1; + if (searchIn.Length > 0 && searchBytes.Length > 0 && start <= (searchIn.Length - searchBytes.Length) && searchIn.Length >= searchBytes.Length) + { + for (var i = start; i <= searchIn.Length - searchBytes.Length; i++) + { + if (searchIn[i] != searchBytes[0]) continue; + if (searchIn.Length > 1) + { + var matched = true; + for (var y = 1; y <= searchBytes.Length - 1; y++) + { + if (searchIn[i + y] == searchBytes[y]) continue; + matched = false; + break; + } + if (matched) + { + found = i; + break; + } + } + else + { + found = i; + break; + } + } + } + return found; + } + + private static byte[] ReadFully(Stream input) + { + var buffer = new byte[16 * 1024]; + using (var ms = new MemoryStream()) + { + int read; + while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + { + ms.Write(buffer, 0, read); + } + return ms.ToArray(); + } + } + + private static byte[] SubArray(byte[] data, int index, int length) + { + var result = new byte[length]; + Array.Copy(data, index, result, 0, length); + return result; + } + + private void ReadData(string boundary, Stream input) + { + var data = ReadFully(input); + + var b = _encoding.GetBytes(boundary); + var ret1 = _encoding.GetBytes("\r\n--"+boundary); + var ret2 = _encoding.GetBytes("\r\n\r\n"); + + var offset = 0; + int pos; + while ((pos = IndexOf(data, b, offset)) != -1) + { + // pos - boundary starting point + if (pos + b.Length + 2 - data.Length > -5) + return; + // pos2 - boundary header endpoint/boundary body start point + var pos2 = IndexOf(data, ret2, pos + 1); + if (pos2 == -1) + return; + pos2 += ret2.Length; + // pos3 - boundary endpoint + var pos3 = IndexOf(data, ret1, pos2); + if (pos3 == -1) + return; + var file = new FormFile(SubArray(data, pos, pos2 - pos), SubArray(data, pos2, pos3 - pos2), _encoding); + if (file.IsFile) + Files[file.Name] = file; + else + Fields[file.Name] = file.FileName; + offset = pos3; + } + } + + public IDictionary Fields { get; private set; } + + public IDictionary Files { get; private set; } + } +} diff --git a/src/Main/Gate/Form/FormFile.cs b/src/Main/Gate/Form/FormFile.cs new file mode 100644 index 0000000..4ee08c6 --- /dev/null +++ b/src/Main/Gate/Form/FormFile.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace Gate.Form +{ + internal class FormFile : IFormFile + { + readonly Stream _stream; + + public FormFile(byte[] infob, byte[] content, Encoding encoding) + { + var info = encoding.GetString(infob); + ContentType = null; + Size = -1; + var parts = info.Split(new[] {";", "\r\n"}, StringSplitOptions.RemoveEmptyEntries); + foreach (var part in parts.Select(x => x.Trim())) + { + if (part.StartsWith("name=", StringComparison.OrdinalIgnoreCase)) + { + Name = UnEscape(part.Substring(5)); + } + else if (part.StartsWith("filename=", StringComparison.OrdinalIgnoreCase)) + { + FileName = UnEscape(part.Substring(9)); + Size = content.Length; + } + else if (part.StartsWith("Content-Type: ", StringComparison.OrdinalIgnoreCase)) + { + ContentType = UnEscape(part.Substring(14)); + } + } + + _stream = ContentType != null ? new MemoryStream(content) : null; + if (ContentType == null) + FileName = encoding.GetString(content); + } + + private static string UnEscape(string v) + { + return v.Trim(' ', '"'); + } + + public bool IsFile { get { return Stream != null; } } + + public string Name { get; private set; } + + public string FileName { get; private set; } + + public string ContentType { get; private set; } + + public long Size { get; private set; } + + public Stream Stream + { + get { return _stream; } + } + } +} diff --git a/src/Main/Gate/Gate.csproj b/src/Main/Gate/Gate.csproj index cb9c6d6..05d9596 100644 --- a/src/Main/Gate/Gate.csproj +++ b/src/Main/Gate/Gate.csproj @@ -48,7 +48,11 @@ + + + + diff --git a/src/Main/Gate/IForm.cs b/src/Main/Gate/IForm.cs new file mode 100644 index 0000000..0b42693 --- /dev/null +++ b/src/Main/Gate/IForm.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Gate +{ + public interface IForm + { + IDictionary Fields { get; } + IDictionary Files { get; } + } +} diff --git a/src/Main/Gate/IFormFile.cs b/src/Main/Gate/IFormFile.cs new file mode 100644 index 0000000..8aea206 --- /dev/null +++ b/src/Main/Gate/IFormFile.cs @@ -0,0 +1,13 @@ +using System.IO; + +namespace Gate +{ + public interface IFormFile + { + string Name { get; } + string FileName { get; } + string ContentType { get; } + long Size { get; } + Stream Stream { get; } + } +} diff --git a/src/Main/Gate/Request.cs b/src/Main/Gate/Request.cs index 044d2e1..407ff1c 100644 --- a/src/Main/Gate/Request.cs +++ b/src/Main/Gate/Request.cs @@ -2,11 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; using Gate.Utils; -using Owin; namespace Gate { @@ -189,6 +187,14 @@ public bool HasParseableData } } + public bool HasBoundary + { + get + { + var ct = ContentType; + return ct != null && ContentType.IndexOf("boundary=", System.StringComparison.Ordinal) != -1; + } + } public string ContentType { @@ -210,6 +216,22 @@ public string MediaType } } + public string Boundary + { + get + { + var contentType = ContentType; + if (contentType == null) + return null; + var idx = contentType.IndexOf("boundary=", System.StringComparison.Ordinal); + if (idx == -1) + return null; + var boundary = contentType.Substring(idx + "boundary=".Length); + var delimiterPos = boundary.IndexOfAny(CommaSemicolon); + return delimiterPos < 0 ? boundary : boundary.Substring(0, delimiterPos); + } + } + public Task CopyToStreamAsync(Stream stream) { if (Body == null) @@ -291,14 +313,14 @@ public string ReadText() return text; } - public Task> ReadFormAsync() + public Task ReadFormAsync() { - if (!HasFormData && !HasParseableData) + if (!HasFormData && !HasParseableData && !HasBoundary) { - return TaskHelpers.FromResult(ParamDictionary.Parse("")); + return TaskHelpers.FromResult((IForm) new Form.Form()); } - var form = Environment.Get>("Gate.Request.Form"); + var form = Environment.Get("Gate.Request.Form"); var thisInput = Body; var lastInput = Environment.Get("Gate.Request.Form#input"); if (form != null && ReferenceEquals(thisInput, lastInput)) @@ -308,23 +330,31 @@ public string ReadText() Request thisRequest = this; - return ReadTextAsync().Then(text => + var boundary = Boundary; + if (boundary == null) + return ReadTextAsync().Then(text => + { + form = new Form.Form(text); + thisRequest.Environment.Set("Gate.Request.Form#input", thisInput); + thisRequest.Environment.Set("Gate.Request.Form", form); + return form; + }); + return TaskHelpers.FromResult(form = new Form.Form(boundary, thisInput)).Then(x => { - form = ParamDictionary.Parse(text); thisRequest.Environment.Set("Gate.Request.Form#input", thisInput); - thisRequest.Environment.Set("Gate.Request.Form", form); - return form; + thisRequest.Environment.Set("Gate.Request.Form", x); + return x; }); } - public IDictionary ReadForm() + public IForm ReadForm() { - if (!HasFormData && !HasParseableData) + if (!HasFormData && !HasParseableData && !HasBoundary) { - return ParamDictionary.Parse(""); + return new Form.Form(); } - var form = Environment.Get>("Gate.Request.Form"); + var form = Environment.Get("Gate.Request.Form"); var thisInput = Body; var lastInput = Environment.Get("Gate.Request.Form#input"); if (form != null && ReferenceEquals(thisInput, lastInput)) @@ -332,8 +362,16 @@ public string ReadText() return form; } - var text = ReadText(); - form = ParamDictionary.Parse(text); + var boundary = Boundary; + if (boundary == null) + { + var text = ReadText(); + form = new Form.Form(text); + } + else + { + form = new Form.Form(boundary, thisInput); + } Environment.Set("Gate.Request.Form#input", thisInput); Environment.Set("Gate.Request.Form", form); return form;