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> { 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 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); } } }