354 lines
9.4 KiB
C#
354 lines
9.4 KiB
C#
|
using System;
|
||
|
using System.IO;
|
||
|
using System.Net;
|
||
|
using System.Threading.Tasks;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Threading;
|
||
|
using System.Text;
|
||
|
using System.Net.Http;
|
||
|
|
||
|
namespace Tesses.Http
|
||
|
{
|
||
|
|
||
|
public struct FirstLine
|
||
|
{
|
||
|
public FirstLine(string line)
|
||
|
{
|
||
|
LineText=line;
|
||
|
}
|
||
|
public string LineText {get;set;}
|
||
|
|
||
|
public static implicit operator FirstLine(string text)
|
||
|
{
|
||
|
return new FirstLine(text);
|
||
|
}
|
||
|
public static implicit operator string(FirstLine line)
|
||
|
{
|
||
|
return line.ToString();
|
||
|
}
|
||
|
public override string ToString()
|
||
|
{
|
||
|
return LineText;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
public struct RequestLine
|
||
|
{
|
||
|
|
||
|
public RequestLine(string method,string path,string httpVersion)
|
||
|
{
|
||
|
Method = method;
|
||
|
Path= path;
|
||
|
HttpVersion=httpVersion;
|
||
|
}
|
||
|
public RequestLine(string method,string path)
|
||
|
{
|
||
|
Method=method;
|
||
|
Path=path;
|
||
|
HttpVersion = "HTTP/1.1";
|
||
|
}
|
||
|
|
||
|
public string Method {get;set;}
|
||
|
|
||
|
public string Path {get;set;}
|
||
|
|
||
|
public string HttpVersion {get;set;}
|
||
|
|
||
|
public static implicit operator RequestLine(FirstLine line)
|
||
|
{
|
||
|
string[] data=line.LineText.Split(' ');
|
||
|
RequestLine Rline=new RequestLine(data[0],data[1],data[2]);
|
||
|
|
||
|
return Rline;
|
||
|
}
|
||
|
|
||
|
public static implicit operator FirstLine(RequestLine line)
|
||
|
{
|
||
|
return new FirstLine(line.ToString());
|
||
|
}
|
||
|
public static implicit operator RequestLine(string path)
|
||
|
{
|
||
|
RequestLine req=new RequestLine();
|
||
|
req.Method="GET";
|
||
|
req.Path=path;
|
||
|
req.HttpVersion="HTTP/1.1";
|
||
|
return req;
|
||
|
}
|
||
|
public override string ToString()
|
||
|
{
|
||
|
return $"{Method} {Path} {HttpVersion}";
|
||
|
}
|
||
|
}
|
||
|
public struct StatusLine
|
||
|
{
|
||
|
public StatusLine(string httpVersion,HttpStatusCode code)
|
||
|
{
|
||
|
HttpVersion = httpVersion;
|
||
|
StatusCode=(int)code;
|
||
|
}
|
||
|
public StatusLine(string httpVersion,int code)
|
||
|
{
|
||
|
HttpVersion = httpVersion;
|
||
|
StatusCode = code;
|
||
|
}
|
||
|
|
||
|
public string HttpVersion {get;set;}
|
||
|
public int StatusCode {get;set;}
|
||
|
public static implicit operator StatusLine(FirstLine line)
|
||
|
{
|
||
|
//HTTP/1.1 200 OK
|
||
|
StatusLine Sline=new StatusLine();
|
||
|
string[] data=line.LineText.Split(' ');
|
||
|
Sline.HttpVersion = data[0];
|
||
|
int n=200;
|
||
|
if(!int.TryParse(data[1],out n))
|
||
|
{
|
||
|
n=500;
|
||
|
}
|
||
|
Sline.StatusCode=n;
|
||
|
return Sline;
|
||
|
}
|
||
|
public static implicit operator StatusLine(int code)
|
||
|
{
|
||
|
StatusLine line = new StatusLine();
|
||
|
line.HttpVersion= "HTTP/1.1";
|
||
|
line.StatusCode=code;
|
||
|
return line;
|
||
|
}
|
||
|
public static implicit operator FirstLine(StatusLine line)
|
||
|
{
|
||
|
return new FirstLine(line.ToString());
|
||
|
}
|
||
|
|
||
|
public override string ToString()
|
||
|
{
|
||
|
return $"{HttpVersion} {StatusCode}";
|
||
|
}
|
||
|
}
|
||
|
public class HeaderCollection : Dictionary<string,List<string>>
|
||
|
{
|
||
|
public HeaderCollection()
|
||
|
{
|
||
|
FirstLine="";
|
||
|
}
|
||
|
public HeaderCollection(string firstLine)
|
||
|
{
|
||
|
FirstLine=firstLine;
|
||
|
}
|
||
|
public HeaderCollection(Stream strm)
|
||
|
{
|
||
|
string[] headers=strm.ReadHttpHeaders().Split(new string[]{"\r\n"},StringSplitOptions.RemoveEmptyEntries);
|
||
|
foreach(var hdr in headers)
|
||
|
{
|
||
|
Add(hdr);
|
||
|
}
|
||
|
}
|
||
|
public FirstLine FirstLine {get;set;}
|
||
|
public void Add(string header)
|
||
|
{
|
||
|
if(header.Contains(":"))
|
||
|
{
|
||
|
//its a kvp
|
||
|
string[] hdr=header.Split(new string[]{": "},2,StringSplitOptions.None);
|
||
|
if(hdr.Length ==1)
|
||
|
{
|
||
|
this.Add(hdr[0],"");
|
||
|
}else{
|
||
|
this.Add(hdr[0],hdr[1]);
|
||
|
}
|
||
|
}else{
|
||
|
FirstLine = header;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override string ToString()
|
||
|
{
|
||
|
StringBuilder b=new StringBuilder();
|
||
|
b.Append($"{FirstLine}\r\n");
|
||
|
|
||
|
foreach(var kvps in this)
|
||
|
{
|
||
|
foreach(var value in kvps.Value)
|
||
|
{
|
||
|
b.Append($"{kvps.Key}: {value}\r\n");
|
||
|
}
|
||
|
}
|
||
|
b.Append("\r\n");
|
||
|
return b.ToString();
|
||
|
}
|
||
|
internal byte[] AsBytes()
|
||
|
{
|
||
|
|
||
|
return Encoding.UTF8.GetBytes(ToString());
|
||
|
}
|
||
|
public void WriteHeaders(Stream strm)
|
||
|
{
|
||
|
byte[] buff=AsBytes();
|
||
|
strm.Write(buff,0,buff.Length);
|
||
|
strm.Flush();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class HttpParser
|
||
|
{
|
||
|
private class HttpBodyStream : Stream
|
||
|
{
|
||
|
Stream strm;
|
||
|
long length;
|
||
|
public HttpBodyStream(Stream src,long len)
|
||
|
{
|
||
|
this.strm = src;
|
||
|
this.length=len;
|
||
|
}
|
||
|
public override bool CanRead => strm.CanRead;
|
||
|
|
||
|
public override bool CanSeek => false;
|
||
|
|
||
|
public override bool CanWrite => false;
|
||
|
|
||
|
public override long Length => length;
|
||
|
|
||
|
private long read=0;
|
||
|
public override long Position { get {return read;} set => throw new NotImplementedException(); }
|
||
|
|
||
|
public override void Flush()
|
||
|
{
|
||
|
strm.Flush();
|
||
|
}
|
||
|
public override int ReadByte()
|
||
|
{
|
||
|
if(length > 0 && read >= length) return -1;
|
||
|
return strm.ReadByte();
|
||
|
}
|
||
|
|
||
|
public override int Read(byte[] buffer, int offset, int count)
|
||
|
{
|
||
|
int read0=0;
|
||
|
if(length == 0) {
|
||
|
|
||
|
read0= strm.Read(buffer,offset,count);
|
||
|
read+=read0;
|
||
|
return read0;
|
||
|
}
|
||
|
|
||
|
//i want to read some data
|
||
|
read0=(int)Math.Min(count,length-read);
|
||
|
if(read0 == 0) return 0;
|
||
|
|
||
|
read0=strm.Read(buffer,offset,read0);
|
||
|
|
||
|
read+=read0;
|
||
|
|
||
|
return read0;
|
||
|
}
|
||
|
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||
|
{
|
||
|
int read0=0;
|
||
|
if(length == 0) {
|
||
|
|
||
|
read0= await strm.ReadAsync(buffer,offset,count);
|
||
|
read+=read0;
|
||
|
return read0;
|
||
|
}
|
||
|
|
||
|
//i want to read some data
|
||
|
read0=(int)Math.Min(count,length-read);
|
||
|
if(read0 == 0) return 0;
|
||
|
|
||
|
read0=await strm.ReadAsync(buffer,offset,read0);
|
||
|
|
||
|
read+=read0;
|
||
|
|
||
|
return read0;
|
||
|
}
|
||
|
|
||
|
public override long Seek(long offset, SeekOrigin origin)
|
||
|
{
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
|
||
|
public override void SetLength(long value)
|
||
|
{
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
|
||
|
public override void Write(byte[] buffer, int offset, int count)
|
||
|
{
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
public override void Close()
|
||
|
{
|
||
|
strm.Close();
|
||
|
}
|
||
|
protected override void Dispose(bool disposing)
|
||
|
{
|
||
|
if(disposing)
|
||
|
{
|
||
|
strm.Dispose();
|
||
|
}
|
||
|
}
|
||
|
public override async Task FlushAsync(CancellationToken cancellationToken)
|
||
|
{
|
||
|
await strm.FlushAsync(cancellationToken);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Stream _strm;
|
||
|
public HttpParser(Stream strm)
|
||
|
{
|
||
|
|
||
|
_strm=strm;
|
||
|
|
||
|
}
|
||
|
public Stream GetRawStream()
|
||
|
{
|
||
|
return _strm;
|
||
|
}
|
||
|
public HeaderCollection SentHeaders {get;set;}
|
||
|
|
||
|
public HeaderCollection ReceivedHeaders {get;set;}
|
||
|
|
||
|
|
||
|
|
||
|
public void SendHeaders()
|
||
|
{
|
||
|
SentHeaders.WriteHeaders(_strm);
|
||
|
|
||
|
}
|
||
|
|
||
|
public void ReceiveHeaders()
|
||
|
{
|
||
|
ReceivedHeaders = new HeaderCollection(_strm);
|
||
|
}
|
||
|
public void WriteBody(Stream strm)
|
||
|
{
|
||
|
strm.CopyTo(_strm);
|
||
|
_strm.Flush();
|
||
|
}
|
||
|
public void WriteBody(string text)
|
||
|
{
|
||
|
WriteBody(System.Text.Encoding.UTF8.GetBytes(text));
|
||
|
}
|
||
|
public void WriteBody(byte[] data)
|
||
|
{
|
||
|
using(var ms = new MemoryStream())
|
||
|
{
|
||
|
ms.Write(data,0,data.Length);
|
||
|
ms.Seek(0,SeekOrigin.Begin);
|
||
|
WriteBody(ms);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Stream ReadBody()
|
||
|
{
|
||
|
long res=0;
|
||
|
|
||
|
if(!ReceivedHeaders.TryGetFirst("Content-Length",out res))
|
||
|
{
|
||
|
res=0;
|
||
|
}
|
||
|
|
||
|
return new HttpBodyStream(_strm,res);
|
||
|
}
|
||
|
}
|
||
|
}
|