215 lines
7.2 KiB
C#
215 lines
7.2 KiB
C#
|
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<MultipartEntry> 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/
|
||
|
/// <summary>
|
||
|
/// Delegate executed when a file is about to be read from a body stream.
|
||
|
/// </summary>
|
||
|
/// <param name="fieldName">Field name.</param>
|
||
|
/// <param name="fileName">name of the file.</param>
|
||
|
/// <param name="contentType">Content type.</param>
|
||
|
/// <returns>Stream to be populated.</returns>
|
||
|
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<string,List<string>> 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<MultipartEntry> Parse(bool disposeStream=true)
|
||
|
{
|
||
|
return Parse((e,f,g)=>{return new MemoryStream();},disposeStream);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class MultipartEntry : IDisposable
|
||
|
{
|
||
|
internal MultipartEntry(string name,Dictionary<string,List<string>> 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<string,List<string>> 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();
|
||
|
}
|
||
|
}
|
||
|
}
|