using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Net.Mime; namespace Tesses.Http { public sealed class MultipartParser { string boundary=""; HttpParser p; public MultipartParser(HttpParser parser) { p=parser; //boundary=----WebKitFormBoundaryyGTVag4OQVNjqdSl string _bstr; if(p.ReceivedHeaders.TryGetFirst("Content-Type",out _bstr)) { //multipart/*; boundary=somevalue int offsetOfBoundary = _bstr.IndexOf("boundary="); if(!_bstr.StartsWith("multipart/")) throw new Exception("not a multipart request/response"); if(offsetOfBoundary < 0) throw new Exception("No \"boundary=\" in multipart header"); offsetOfBoundary+=9; int offsetOfSemiAfterBoundary = _bstr.IndexOf(';',offsetOfBoundary); if(offsetOfSemiAfterBoundary < 0) { boundary=_bstr.Substring(offsetOfBoundary); } else { boundary=_bstr.Substring(offsetOfBoundary,offsetOfSemiAfterBoundary - offsetOfBoundary); } //we got the boundary str }else{ throw new Exception("No Content-Type found"); } } #region DAJURIC_SIMPLE_HTTP_CODE public IEnumerable Parse(OnFile onFile,bool disposeStream=true) { string bound = "--" + boundary; //Console.WriteLine("BEFORE STREAM"); var inputStream = new BufferedStream(p.ReadBody()); //Print("Before ParseUntillBoundaryEnd"); //Console.WriteLine("Before ParseUntilBoundaryEnd"); parseUntillBoundaryEnd(inputStream, new MemoryStream(), bound); //Console.WriteLine("After ParseUntilBoundaryEnd"); //Print("After ParseUntillBoundaryEnd"); while (true) { //Print("Before ParseSection"); var (n, v, fn, hdrs,ok) = parseSection(inputStream, "\r\n" + bound, onFile); if(!ok) break; //Print("After ParseSection"); v.Position = 0; yield return new MultipartEntry(n,hdrs,fn,v); if(disposeStream) { v.Dispose(); } } } //this region of code is somewhat modified from https://github.com/dajuric/simple-http/ /// /// Delegate executed when a file is about to be read from a body stream. /// /// Field name. /// name of the file. /// Content type. /// Stream to be populated. public delegate Stream OnFile(string fieldName, string fileName, string contentType); private static bool parseUntillBoundaryEnd(Stream source, Stream destination, string boundary) { var checkBuffer = new byte[boundary.Length]; //for boundary checking int b, i = 0; while ((b = source.ReadByte()) != -1) { //Console.WriteLine(i); if (i == boundary.Length) //boundary found -> go to the end of line { if (b == '\n') break; if(b == '-') { source.ReadByte(); return false; } continue; } if (b == boundary[i]) //start filling the check buffer { checkBuffer[i] = (byte)b; i++; } else { var idx = 0; while (idx < i) //write the buffer data to stream { destination.WriteByte(checkBuffer[idx]); idx++; } i = 0; destination.WriteByte((byte)b); //write the current byte } } return true; } private static (string Name, Stream Value, string FileName, Dictionary> headers,bool isOk) parseSection(Stream source, string boundary, OnFile onFile) { //we must read two bytes HeaderCollection coll=new HeaderCollection(source); ContentDisposition disposition=new ContentDisposition(); string ct="application/octet-stream"; string n=""; string fn=""; if(coll.ContainsKey("Content-Disposition")) { disposition=new ContentDisposition(coll["Content-Disposition"][0]); n=disposition.Parameters["name"]; fn=disposition.FileName; } if(!coll.TryGetFirst("Content-Type",out ct)) { ct="application/octet-stream"; } //Print("Before ReadContentDisposition"); //var (n, fn, ct) = readContentDisposition(source); //Print("After ReadContentDisposition"); //\r\n (empty row) var dst = String.IsNullOrEmpty(fn) ? new MemoryStream() : onFile(n, fn, ct); if (dst == null) throw new ArgumentException(nameof(onFile), "The on-file callback must return a stream."); //Print("Before ParseUntillBodyEnd"); bool isOk = parseUntillBoundaryEnd(source, dst, boundary); //Print("Before ParseUntillBodyEnd"); return (n, dst, fn, coll,isOk); } #endregion public IEnumerable Parse(bool disposeStream=true) { return Parse((e,f,g)=>{return new MemoryStream();},disposeStream); } } public class MultipartEntry : IDisposable { internal MultipartEntry(string name,Dictionary> headers,string filename,Stream strm) { Name=name; this.Stream=strm; FileName = filename; HasFileName=!string.IsNullOrWhiteSpace(filename); Headers =headers; string ct; if(Headers.TryGetFirst("Content-Type",out ct)) { this.MimeType=ct; } } public Dictionary> Headers {get;set;} public bool HasFileName {get;private set;} public string MimeType {get;private set;} public string FileName {get;private set;} public string Name {get;private set;} public Stream Stream {get;private set;} public void Dispose() { Stream.Dispose(); } public byte[] GetData() { return Stream.AsArray(); } public string GetStringData() { return GetData().AsString(); } } }