Created
September 18, 2016 20:57
-
-
Save duncansmart/523a893f8c6c07c8bd4eeeeb736ac7e8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Web; | |
using FakeItEasy; | |
using MimeKit; | |
using System.Collections.Specialized; | |
using System.Collections.Generic; | |
public class FileUploadHelper | |
{ | |
const string _sampleContentType = "multipart/form-data; boundary=----WebKitFormBoundaryllf4hN4RsKBbOCoZ"; | |
const string _sample = @"------WebKitFormBoundaryllf4hN4RsKBbOCoZ | |
Content-Disposition: form-data; name=""foo"" | |
bar1 | |
------WebKitFormBoundaryllf4hN4RsKBbOCoZ | |
Content-Disposition: form-data; name=""foo"" | |
bar2 | |
------WebKitFormBoundaryllf4hN4RsKBbOCoZ | |
Content-Disposition: form-data; name=""baz"" | |
😂 | |
------WebKitFormBoundaryllf4hN4RsKBbOCoZ | |
Content-Disposition: form-data; name=""file1""; filename=""hello world.xml"" | |
Content-Type: text/xml | |
<?xml version=""1.0"" encoding=""utf-8""?> | |
<hello>world</hello> | |
------WebKitFormBoundaryllf4hN4RsKBbOCoZ | |
Content-Disposition: form-data; name=""file2""; filename=""hello world.zip"" | |
Content-Type: application/x-zip-compressed | |
binary here ... | |
------WebKitFormBoundaryllf4hN4RsKBbOCoZ-- | |
"; | |
static void ParseMultipartFormData_TEST() | |
{ | |
var request = A.Fake<HttpRequestBase>(); | |
A.CallTo(() => request.GetBufferlessInputStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(_sample))); | |
A.CallTo(() => request.ContentType).Returns(_sampleContentType); | |
var mime = ParseMultipartFormData(request); | |
foreach (var item in mime.OfType<MimePart>()) | |
{ | |
if (item.FileName != null) | |
{ | |
var saveStream = new MemoryStream(); | |
item.WriteTo(saveStream, contentOnly: true); | |
var content = Encoding.UTF8.GetString(saveStream.ToArray()); | |
Debug.WriteLine(content); | |
} | |
} | |
} | |
// https://github.com/jstedfast/MimeKit/blob/master/FAQ.md#SaveAttachments | |
static Multipart ParseMultipartFormData(HttpRequestBase request) | |
{ | |
// create a temporary, DeleteOnClose file to store our large HTTP data stream | |
// will be deleted when Multipart's GC kicks in or when process closes | |
var stream = new FileStream( | |
Path.Combine(Path.GetTempPath(), "~formdata-" + Path.GetRandomFileName()), | |
FileMode.Create, | |
FileAccess.ReadWrite, | |
FileShare.Read | FileShare.Delete, | |
4096, | |
FileOptions.DeleteOnClose); | |
// check that upload is multipart/form-data | |
var requestContentType = ContentType.Parse(request.ContentType); | |
if (!requestContentType.MimeType.Equals("multipart/form-data", StringComparison.OrdinalIgnoreCase)) | |
throw new ArgumentException($"Expected Content-Type: multipart/form-data, but was '{requestContentType.MimeType}'"); | |
// create a header for the multipart/form-data MIME entity based on the Content-Type value of the HTTP | |
// response | |
var header = Encoding.UTF8.GetBytes(string.Format("Content-Type: {0}\r\n\r\n", request.ContentType)); | |
// write the header to the stream | |
stream.Write(header, 0, header.Length); | |
// copy the content of the HTTP response to our temporary stream | |
request.GetBufferlessInputStream().CopyTo(stream); | |
// reset the stream back to the beginning | |
stream.Position = 0; | |
// parse the MIME entity with persistent = true, telling the parser not to load the content into memory | |
return (Multipart)MimeEntity.Load(stream, persistent: true); | |
} | |
static void ParseLargeFileUpload_TEST() | |
{ | |
var request = A.Fake<HttpRequestBase>(); | |
A.CallTo(() => request.GetBufferlessInputStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(_sample))); | |
A.CallTo(() => request.ContentType).Returns(_sampleContentType); | |
NameValueCollection form; | |
List<KeyValuePair<string, HttpPostedFileBase>> files; | |
ParseLargeFileUpload(request, out form, out files); | |
Debug.Assert(form.Count == 2); | |
Debug.Assert(files.Count == 2); | |
} | |
static void ParseLargeFileUpload(HttpRequestBase request, out NameValueCollection form, out List<KeyValuePair<string, HttpPostedFileBase>> files) | |
{ | |
var multipart = ParseMultipartFormData(request); | |
form = new NameValueCollection(); | |
files = new List<KeyValuePair<string, HttpPostedFileBase>>(); | |
foreach (var part in multipart.OfType<MimePart>()) | |
{ | |
var name = part.ContentDisposition.Parameters["name"]; | |
if (part.FileName != null) | |
{ | |
files.Add(new KeyValuePair<string, HttpPostedFileBase>(name, new LargePostedFile(part))); | |
} | |
else | |
{ | |
var textpart = part as TextPart; | |
if (textpart != null) | |
form.Add(name, textpart.Text); | |
} | |
} | |
} | |
class LargePostedFile : HttpPostedFileBase | |
{ | |
MimePart _mimePart; | |
internal LargePostedFile(MimePart mimePart) | |
{ | |
_mimePart = mimePart; | |
} | |
public override int ContentLength => (int)_mimePart.ContentObject.Stream.Length; | |
public override string ContentType => _mimePart.ContentType.MimeType; | |
public override string FileName => _mimePart.FileName; | |
public override Stream InputStream => _mimePart.ContentObject.Stream; | |
public override void SaveAs(string filename) | |
{ | |
_mimePart.WriteTo(filename); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment