using System.Net; using System.Collections.Generic; using System.IO; using System.Text; using System.Net.Http; using System; using System.Linq; using System.Web; using Newtonsoft.Json; using System.Net.Mime; using HeyRed.Mime; namespace Tesses.Http { public class ServerContext { public ServerRequest Request {get;private set;} public ServerResponse Response {get;private set;} internal HttpParser p; public Stream GetRawStreamWithHeaders() { return new PrependStream(Encoding.UTF8.GetBytes(p.ReceivedHeaders.ToString()),p.GetRawStream()); } public Stream GetRawStream() { return p.GetRawStream(); } public bool IsConnected {get{ if(_isConnected!=null) return _isConnected(); return false; }} Func _isConnected; internal ServerContext(Func isConnected,HttpParser parser,IPEndPoint local,IPEndPoint remote) { _isConnected=isConnected; p=parser; Request=new ServerRequest(parser,remote); Response=new ServerResponse(isConnected,Request.RequestLine,parser,local); } } public class ServerResponse { RequestLine ln; Func _isConnected; string ogurl; internal ServerResponse(Func isConnected,RequestLine line,HttpParser parser,IPEndPoint local) { ln=line; Address=local; _isConnected=isConnected; ogurl = ln.Path.Split('?')[0]; this.parser=parser; StatusLine=200; } HttpParser parser; public IPEndPoint Address {get;set;} public Dictionary> Headers {get {return parser.SentHeaders;}} public StatusLine StatusLine {get {return parser.SentHeaders.FirstLine;} set { parser.SentHeaders.FirstLine=value;}} public ServerResponse WithContentType(string contentType) { Headers.Add("Content-Type",contentType); return this; } public void SendStatusCodeHtml() { StringBuilder builder=new StringBuilder(); builder.AppendLine(""); builder.AppendLine($"{StatusLine.StatusCode} {StatusLine.GetReasonPhrase()}"); builder.AppendLine(""); builder.AppendLine($"

{StatusLine.StatusCode} {StatusLine.GetReasonPhrase()}

"); builder.AppendLine($"

{ogurl}

"); builder.AppendLine("
Tesses.Http
"); builder.AppendLine(""); builder.AppendLine(""); WithContentType("text/html").SendText(builder); /* 404 Not Found

404 Not Found


nginx/1.18.0 (Ubuntu)
*/ } public ServerResponse WithFileName(string filename,bool inline=false) { ContentDisposition contentDisposition=new ContentDisposition(); contentDisposition.FileName=filename; contentDisposition.Inline=inline; Headers.Add("Content-Disposition",contentDisposition.ToString()); return this; } public ServerResponse WithHeader(string key,string value) { Headers.Add(key,value); return this; } public void ServerSentEvents(SendEvents evt) { bool __connected=true; if(_isConnected == null) return; WithContentType("text/event-stream").WithHeader("Cache-Control","no-cache").parser.SendHeaders(); try{ EventHandler cb= (sender,e0)=>{ if(__connected) parser.WriteBody($"data: {e0.Data}\n\n"); }; evt.EventReceived += cb; while(_isConnected()); evt.EventReceived -= cb; __connected=false; }catch(Exception ex) { _=ex; } } public void SendText(StringBuilder b) { SendText(b.ToString()); } public void SendJson(object o) { WithContentType("application/json").SendText(JsonConvert.SerializeObject(o)); } public void SendText(string text) { MemoryStream strm=new MemoryStream(); using(var sw=new StreamWriter(strm)) { sw.Write(text); sw.Flush(); strm.Seek(0,SeekOrigin.Begin); SendResponseStream(strm); } } private string _getFileNameWithoutQuery(string url) { int r = url.IndexOf('?'); if(r > -1) { return Path.GetFileName(url.Remove(r).TrimEnd('/')) + (url.EndsWith("/") ? "/" : ""); } return Path.GetFileName(url.TrimEnd('/')) + (url.EndsWith("/") ? "/" : ""); } public ServerResponse WithContextTypeFromFileName(string filename) { return WithContentType(MimeTypesMap.GetMimeType(filename)); } public void SendRedirect(string redirectTo) { StatusLine=301; WithHeader("Location",redirectTo).WithHeader("Cache-Control","no-cache").parser.SendHeaders(); } public void SendFileListing(string title,string desc,IEnumerable entries) { if(!this.ln.Path.EndsWith("/")) { SendRedirect(this.ln.Path + '/'); return; } StringBuilder b=new StringBuilder(); b.Append($"{HttpUtility.HtmlEncode(title)}

{HttpUtility.HtmlEncode(title)}

{HttpUtility.HtmlEncode(desc)}

../
"); foreach(var entry in entries) { //{HttpUtility.HtmlEncode(_getFileNameWithoutQuery(entry))}
"); } b.Append("

Tesses.Http
"); WithContentType("text/html").SendText(b); } public void SendFileAsDownload(string file) { WithFileName(Path.GetFileName(file)).SendFile(file); } public void SendFileAsDownload(VirtualStorage storage,string file) { WithFileName(Path.GetFileName(file)).SendFile(storage,file); } public void SendFile(string file) { using(var f = File.OpenRead(file)) { WithContextTypeFromFileName(file).SendRangableResponseStream(f); } } public void SendFile(VirtualStorage storage,string file) { using(var f = storage.Open(file,FileMode.Open,FileAccess.Read,FileShare.Read)) { WithContextTypeFromFileName(file).SendRangableResponseStream(f); } } public void SendResponseStream(Stream strm) { Headers.Add("Content-Length",strm.Length.ToString()); parser.SendHeaders(); if(ln.Method != "HEAD") parser.WriteBody(strm); } public void SendRangableResponseStream(Stream strm) { if(parser.ReceivedHeaders.ContainsKey("Range") && strm.CanSeek) { int start = 0, end = (int)strm.Length - 1; if (parser.ReceivedHeaders["Range"].Count > 1) { throw new NotSupportedException("Multiple 'Range' headers are not supported."); } var range = parser.ReceivedHeaders["Range"][0].Replace("bytes=", String.Empty) .Split(new string[] { "-" }, StringSplitOptions.RemoveEmptyEntries) .Select(x => Int32.Parse(x)) .ToArray(); start = (range.Length > 0) ? range[0] : 0; end = (range.Length > 1) ? range[1] : (int)(strm.Length - 1); var hdrs = parser.SentHeaders; hdrs.Add("Accept-Ranges", "bytes"); hdrs.Add("Content-Range", "bytes " + start + "-" + end + "/" + strm.Length); hdrs.Add("Content-Length", (end - start + 1).ToString()); StatusLine = 206; parser.SendHeaders(); if(ln.Method != "HEAD") parser.WriteBody(new RangeStream(strm,start,end-start+1)); }else{ SendResponseStream(strm); } } } public class ServerRequest { public MultipartParser GetMultipartParser() { return new MultipartParser(this.parser); } public ServerRequest(HttpParser parser,IPEndPoint remote) { Address=remote; this.parser=parser; OriginalUrl = RequestLine.Path; } HttpParser parser; public IPEndPoint Address {get;set;} public string GetQueryParameters(string pathAndQuery,Dictionary> p) { string[] qParm = pathAndQuery.Split(new char[]{'?'},2,StringSplitOptions.RemoveEmptyEntries); if(qParm.Length == 0) return ""; if(qParm.Length == 1) { return qParm[0]; }else{ foreach(var kvp in qParm[1].Split(new char[]{'&'},StringSplitOptions.RemoveEmptyEntries)) { string[] _kvp = kvp.Split(new char[]{'='},2,StringSplitOptions.RemoveEmptyEntries); if(_kvp.Length > 0) { if(_kvp.Length == 2) { p.Add(_kvp[0],HttpUtility.UrlEncode(_kvp[1])); }else{ p.Add(_kvp[0],""); } } } return qParm[0]; } } public string OriginalUrl {get;private set;} public string CurrentUrl {get{return RequestLine.Path;} set{RequestLine=new RequestLine(RequestLine.Method,value,RequestLine.HttpVersion);}} public string GetQueryParameters(Dictionary> p) { return GetQueryParameters(RequestLine.Path,p); } public RequestLine RequestLine {get{return parser.ReceivedHeaders.FirstLine;} set {parser.ReceivedHeaders.FirstLine=value;}} public Dictionary> Headers {get {return parser.ReceivedHeaders;}} public string GetRequestString() { using(var sr=new StreamReader(GetRequestStream())) { return sr.ReadToEnd(); } } public T GetRequestJson() { return JsonConvert.DeserializeObject(GetRequestString()); } public void GetUrlEncodedPost(Dictionary> p) { string urlData=$"{OriginalUrl}?{GetRequestString()}"; GetQueryParameters(urlData,p); } public Stream GetRequestStream() { return parser.ReadBody(); } } }