diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs index 0e2cb80e9..9e3ba6f57 100644 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs +++ b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs @@ -152,6 +152,11 @@ await Dispatcher.InvokeAsync(() => private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) { + if (e.HttpClient.ConnectRequest?.TunnelType != TunnelType.Http2) + { + return; + } + SessionListItem item = null; await Dispatcher.InvokeAsync(() => { item = addSession(e); }); @@ -159,6 +164,9 @@ private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) //{ //} + //if (!e.HttpClient.Request.RequestUri.ToString().Contains("/mail/u/")) + // return; + if (e.HttpClient.Request.HasBody) { e.HttpClient.Request.KeepBody = true; @@ -168,6 +176,11 @@ private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e) { + if (e.HttpClient.ConnectRequest?.TunnelType != TunnelType.Http2) + { + return; + } + SessionListItem item = null; await Dispatcher.InvokeAsync(() => { @@ -177,9 +190,9 @@ await Dispatcher.InvokeAsync(() => } }); - //if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2) - //{ - //} + //e.HttpClient.Response.Headers.AddHeader("X-Titanium-Header", "HTTP/2 works"); + + //e.SetResponseBody(Encoding.ASCII.GetBytes("TITANIUMMMM!!!!")); if (item != null) { diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index 52ee23464..e93a417c6 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -28,6 +28,11 @@ public class SessionEventArgs : SessionEventArgsBase /// private bool reRequest; + /// + /// Is this session a HTTP/2 promise? + /// + public bool IsPromise { get; internal set; } + /// /// Constructor to initialize the proxy /// @@ -91,6 +96,9 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) { if (request.HttpVersion == HttpHeader.Version20) { + // do not send to the remote endpoint + request.Http2IgnoreBodyFrames = true; + request.Http2BodyData = new MemoryStream(); var tcs = new TaskCompletionSource(); @@ -100,6 +108,10 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) request.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true); await tcs.Task; + + // Now set the flag to true + // So that next time we can deliver body from cache + request.IsBodyRead = true; } else { @@ -157,6 +169,9 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) { if (response.HttpVersion == HttpHeader.Version20) { + // do not send to the remote endpoint + response.Http2IgnoreBodyFrames = true; + response.Http2BodyData = new MemoryStream(); var tcs = new TaskCompletionSource(); @@ -166,6 +181,10 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) response.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true); await tcs.Task; + + // Now set the flag to true + // So that next time we can deliver body from cache + response.IsBodyRead = true; } else { diff --git a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs index 9e3e988c0..e8134f735 100644 --- a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs +++ b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs @@ -56,6 +56,15 @@ public abstract class RequestResponseBase internal MemoryStream Http2BodyData; + internal bool Http2IgnoreBodyFrames; + + internal Task Http2BeforeHandlerTask; + + /// + /// Priority used only in HTTP/2 + /// + internal long? Priority; + /// /// Keeps the body data after the session is finished. /// @@ -201,6 +210,8 @@ internal set /// internal bool Locked { get; set; } + internal bool BodyAvailable => BodyInternal != null; + internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true); /// diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs b/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs index 5a90913cf..cf503a4d8 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs @@ -61,7 +61,9 @@ public Encoder(int maxHeaderTableSize) /// Name. /// Value. /// If set to true sensitive. - public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false) + /// Index type. + /// Use static name. + public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false, HpackUtil.IndexType indexType = HpackUtil.IndexType.Incremental, bool useStaticName = true) { // If the header value is sensitive then it must never be indexed if (sensitive) @@ -116,10 +118,9 @@ public void EncodeHeader(BinaryWriter output, string name, string value, bool se } else { - int nameIndex = getNameIndex(name); + int nameIndex = useStaticName ? getNameIndex(name) : -1; ensureCapacity(headerSize); - var indexType = HpackUtil.IndexType.Incremental; encodeLiteral(output, name, value, indexType, nameIndex); add(name, value); } @@ -309,7 +310,7 @@ private HeaderEntry getEntry(string name, string value) int i = index(h); for (var e = headerFields[i]; e != null; e = e.Next) { - if (e.Hash == h && Equals(name, e.Name) && Equals(value, e.Value)) + if (e.Hash == h && name.Equals(e.Name, StringComparison.OrdinalIgnoreCase) && Equals(value, e.Value)) { return e; } @@ -336,7 +337,7 @@ private int getIndex(string name) int index = -1; for (var e = headerFields[i]; e != null; e = e.Next) { - if (e.Hash == h && HpackUtil.Equals(name, e.Name)) + if (e.Hash == h && name.Equals(e.Name, StringComparison.OrdinalIgnoreCase)) { index = e.Index; break; @@ -513,7 +514,7 @@ private class HeaderEntry : HttpHeader /// Value. /// Index. /// Next. - public HeaderEntry(int hash, string name, string value, int index, HeaderEntry next) : base(name, value) + public HeaderEntry(int hash, string name, string value, int index, HeaderEntry next) : base(name, value, true) { Index = index; Hash = hash; diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs b/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs index 771574f59..3198a3c34 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs @@ -154,7 +154,7 @@ public static class StaticTable new HttpHeader("WWW-Authenticate", string.Empty) }; - private static readonly Dictionary staticIndexByName = CreateMap(); + private static readonly Dictionary staticIndexByName = createMap(); /// /// The number of header fields in the static table. @@ -180,12 +180,12 @@ public static HttpHeader Get(int index) /// Name. public static int GetIndex(string name) { - if (!staticIndexByName.ContainsKey(name)) + if (!staticIndexByName.TryGetValue(name, out int index)) { return -1; } - return staticIndexByName[name]; + return index; } /// @@ -207,7 +207,7 @@ public static int GetIndex(string name, string value) while (index <= Length) { var entry = Get(index); - if (!HpackUtil.Equals(name, entry.Name)) + if (!name.Equals(entry.Name, StringComparison.OrdinalIgnoreCase)) { break; } @@ -227,7 +227,7 @@ public static int GetIndex(string name, string value) /// create a map of header name to index value to allow quick lookup /// /// The map. - private static Dictionary CreateMap() + private static Dictionary createMap() { int length = staticTable.Count; var ret = new Dictionary(length); @@ -237,7 +237,7 @@ private static Dictionary CreateMap() for (int index = length; index > 0; index--) { var entry = Get(index); - string name = entry.Name; + string name = entry.Name.ToLower(); ret[name] = index; } diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs new file mode 100644 index 000000000..ba7eea53b --- /dev/null +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs @@ -0,0 +1,14 @@ +using System; + +namespace Titanium.Web.Proxy.Http2 +{ + [Flags] + internal enum Http2FrameFlag : byte + { + Ack = 0x01, + EndStream = 0x01, + EndHeaders = 0x04, + Padded = 0x08, + Priority = 0x20, + } +} \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs new file mode 100644 index 000000000..f211511d5 --- /dev/null +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs @@ -0,0 +1,32 @@ +namespace Titanium.Web.Proxy.Http2 +{ + internal class Http2FrameHeader + { + public int Length; + + public Http2FrameType Type; + + public Http2FrameFlag Flags; + + public int StreamId; + + public byte[] Buffer; + + public byte[] CopyToBuffer() + { + int length = Length; + var buf = /*new byte[9];*/Buffer; + buf[0] = (byte)((length >> 16) & 0xff); + buf[1] = (byte)((length >> 8) & 0xff); + buf[2] = (byte)(length & 0xff); + buf[3] = (byte)Type; + buf[4] = (byte)Flags; + int streamId = StreamId; + //buf[5] = (byte)((streamId >> 24) & 0xff); + //buf[6] = (byte)((streamId >> 16) & 0xff); + //buf[7] = (byte)((streamId >> 8) & 0xff); + //buf[8] = (byte)(streamId & 0xff); + return buf; + } + } +} diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameType.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameType.cs new file mode 100644 index 000000000..a6dcbadf4 --- /dev/null +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameType.cs @@ -0,0 +1,16 @@ +namespace Titanium.Web.Proxy.Http2 +{ + internal enum Http2FrameType : byte + { + Data = 0x00, + Headers = 0x01, + Priority = 0x02, + RstStream = 0x03, + Settings = 0x04, + PushPromise = 0x05, + Ping = 0x06, + GoAway = 0x07, + WindowUpdate = 0x08, + Continuation = 0x09, + } +} \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index df31c6e08..2fe85d194 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; +using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Http; @@ -13,16 +15,6 @@ namespace Titanium.Web.Proxy.Http2 { - [Flags] - internal enum Http2FrameFlag - { - Ack = 0x01, - EndStream = 0x01, - EndHeaders = 0x04, - Padded = 0x08, - Priority = 0x20, - } - internal class Http2Helper { /// @@ -70,22 +62,29 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio int headerTableSize = 0; Decoder decoder = null; - var headerBuffer = new byte[9]; + var frameHeader = new Http2FrameHeader(); + frameHeader.Buffer = new byte[9]; byte[] buffer = null; while (true) { - int read = await forceRead(input, headerBuffer, 0, 9, cancellationToken); - onCopy(headerBuffer, 0, read); + var frameHeaderBuffer = frameHeader.Buffer; + int read = await forceRead(input, frameHeaderBuffer, 0, 9, cancellationToken); + onCopy(frameHeaderBuffer, 0, read); if (read != 9) { return; } - int length = (headerBuffer[0] << 16) + (headerBuffer[1] << 8) + headerBuffer[2]; - byte type = headerBuffer[3]; - byte flags = headerBuffer[4]; - int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) + - headerBuffer[8]; + int length = (frameHeaderBuffer[0] << 16) + (frameHeaderBuffer[1] << 8) + frameHeaderBuffer[2]; + var type = (Http2FrameType)frameHeaderBuffer[3]; + var flags = (Http2FrameFlag)frameHeaderBuffer[4]; + int streamId = ((frameHeaderBuffer[5] & 0x7f) << 24) + (frameHeaderBuffer[6] << 16) + + (frameHeaderBuffer[7] << 8) + frameHeaderBuffer[8]; + + frameHeader.Length = length; + frameHeader.Type = type; + frameHeader.Flags = flags; + frameHeader.StreamId = streamId; if (buffer == null || buffer.Length < localSettings.MaxFrameSize) { @@ -99,70 +98,71 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio return; } + bool sendPacket = true; bool endStream = false; SessionEventArgs args = null; RequestResponseBase rr = null; - if (type == 0 || type == 1) + if (type == Http2FrameType.Data || type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/) { if (!sessions.TryGetValue(streamId, out args)) { - if (type == 0) - { - throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args); - } + //if (type == Http2FrameType.Data) + //{ + // throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args); + //} - if (!isClient) + //if (type == Http2FrameType.Headers && !isClient) + //{ + // throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args); + //} + + if (type == Http2FrameType.PushPromise && isClient) { - throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args); + throw new ProxyHttpException("HTTP Push promise received from the client.", null, args); } - - args = sessionFactory(); - sessions.TryAdd(streamId, args); } - - rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response; } //System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); - if (type == 0 /* data */) + if (type == Http2FrameType.Data && args != null) { - bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; + rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response; + + bool padded = (flags & Http2FrameFlag.Padded) != 0; + bool endStreamFlag = (flags & Http2FrameFlag.EndStream) != 0; if (endStreamFlag) { endStream = true; } + if (rr.Http2IgnoreBodyFrames) + { + sendPacket = false; + } + if (rr.ReadHttp2BodyTaskCompletionSource != null) { // Get body method was called in the "before" event handler var data = rr.Http2BodyData; - data.Write(buffer, 0, length); - - if (endStream) + int offset = 0; + if (padded) { - rr.Body = data.ToArray(); - rr.IsBodyRead = true; - - var tcs = rr.ReadHttp2BodyTaskCompletionSource; - rr.ReadHttp2BodyTaskCompletionSource = null; - - if (!tcs.Task.IsCompleted) - { - tcs.SetResult(true); - } - - rr.Http2BodyData = null; + offset++; + length--; + length -= buffer[0]; } - } + + data.Write(buffer, offset, length); + } } - else if (type == 1 /* headers */) + else if (type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/) { - bool endHeaders = (flags & (int)Http2FrameFlag.EndHeaders) != 0; - bool padded = (flags & (int)Http2FrameFlag.Padded) != 0; - bool priority = (flags & (int)Http2FrameFlag.Priority) != 0; - bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; + bool endHeaders = (flags & Http2FrameFlag.EndHeaders) != 0; + bool padded = (flags & Http2FrameFlag.Padded) != 0; + bool priority = (flags & Http2FrameFlag.Priority) != 0; + bool endStreamFlag = (flags & Http2FrameFlag.EndStream) != 0; if (endStreamFlag) { endStream = true; @@ -172,12 +172,47 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio if (padded) { offset = 1; + breakpoint(); } - - if (priority) + + if (type == Http2FrameType.PushPromise) { - offset += 5; + int promisedStreamId = (buffer[offset++] << 24) + (buffer[offset++] << 16) + (buffer[offset++] << 8) + buffer[offset++]; + if (!sessions.TryGetValue(streamId, out args)) + { + args = sessionFactory(); + args.IsPromise = true; + sessions.TryAdd(streamId, args); + sessions.TryAdd(promisedStreamId, args); + } + + System.Diagnostics.Debug.WriteLine("PROMISE STREAM: " + streamId + ", " + promisedStreamId + + ", CONN: " + connectionId); + rr = args.HttpClient.Request; + + if (isClient) + { + // push_promise from client??? + breakpoint(); + } } + else + { + if (!sessions.TryGetValue(streamId, out args)) + { + args = sessionFactory(); + sessions.TryAdd(streamId, args); + } + + rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response; + if (priority) + { + var priorityData = ((long)buffer[offset++] << 32) + ((long)buffer[offset++] << 24) + + (buffer[offset++] << 16) + (buffer[offset++] << 8) + buffer[offset++]; + rr.Priority = priorityData; + } + } + int dataLength = length - offset; if (padded) @@ -205,17 +240,17 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio headerListener); decoder.EndHeaderBlock(); - if (isClient) + if (rr is Request request) { - var request = args.HttpClient.Request; request.HttpVersion = HttpVersion.Version20; request.Method = headerListener.Method; - request.OriginalUrl = headerListener.Status; + request.OriginalUrl = headerListener.Path; + request.RequestUri = headerListener.GetUri(); } else { - var response = args.HttpClient.Response; + var response = (Response)rr; response.HttpVersion = HttpVersion.Version20; int.TryParse(headerListener.Status, out int statusCode); response.StatusCode = statusCode; @@ -226,23 +261,41 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio exceptionFunc(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args)); } + if (!endHeaders) + { + breakpoint(); + } + if (endHeaders) { var tcs = new TaskCompletionSource(); rr.ReadHttp2BeforeHandlerTaskCompletionSource = tcs; var handler = onBeforeRequestResponse(args); + rr.Http2BeforeHandlerTask = handler; if (handler == await Task.WhenAny(tcs.Task, handler)) { rr.ReadHttp2BeforeHandlerTaskCompletionSource = null; tcs.SetResult(true); + await sendHeader(remoteSettings, frameHeader, rr, endStream, output, args.IsPromise); + } + else + { + rr.Http2IgnoreBodyFrames = true; } rr.Locked = true; } + + sendPacket = false; } - else if (type == 4 /* settings */) + else if (type == Http2FrameType.Continuation) + { + // todo: implementing this type is mandatory for multi-part headers + breakpoint(); + } + else if (type == Http2FrameType.Settings) { if (length % 6 != 0) { @@ -269,7 +322,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } } - if (type == 3 /* rst_stream */) + if (type == Http2FrameType.RstStream) { int errorCode = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]; if (streamId == 0) @@ -290,15 +343,68 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } } + if (endStream && rr.ReadHttp2BodyTaskCompletionSource != null) + { + if (!rr.BodyAvailable) + { + var data = rr.Http2BodyData; + var body = data.ToArray(); + + if (rr.ContentEncoding != null) + { + using (var ms = new MemoryStream()) + { + using (var zip = + DecompressionFactory.Create(rr.ContentEncoding, new MemoryStream(body))) + { + zip.CopyTo(ms); + } + + body = ms.ToArray(); + } + } + + rr.Body = body; + } + + rr.IsBodyRead = true; + + var tcs = rr.ReadHttp2BodyTaskCompletionSource; + rr.ReadHttp2BodyTaskCompletionSource = null; + + if (!tcs.Task.IsCompleted) + { + tcs.SetResult(true); + } + + rr.Http2BodyData = null; + + if (rr.Http2BeforeHandlerTask != null) + { + await rr.Http2BeforeHandlerTask; + } + + if (args.IsPromise) + { + breakpoint(); + } + + await sendBody(remoteSettings, rr, frameHeader, buffer, output); + } + if (!isClient && endStream) { sessions.TryRemove(streamId, out _); - //System.Diagnostics.Debug.WriteLine("REMOVED CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); + System.Diagnostics.Debug.WriteLine("REMOVED CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); } - // do not cancel the write operation - await output.WriteAsync(headerBuffer, 0, headerBuffer.Length/*, cancellationToken*/); - await output.WriteAsync(buffer, 0, length/*, cancellationToken*/); + if (sendPacket) + { + // do not cancel the write operation + var buf = frameHeader.CopyToBuffer(); + await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); + await output.WriteAsync(buffer, 0, length /*, cancellationToken*/); + } if (cancellationToken.IsCancellationRequested) { @@ -313,6 +419,104 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } } + [Conditional("DEBUG")] + private static void breakpoint() + { + // when this method is called something received which is not yet implemented + ; + } + + private static async Task sendHeader(Http2Settings settings, Http2FrameHeader frameHeader, RequestResponseBase rr, bool endStream, Stream output, bool pushPromise) + { + var encoder = new Encoder(settings.HeaderTableSize); + var ms = new MemoryStream(); + var writer = new BinaryWriter(ms); + if (rr.Priority.HasValue) + { + long p = rr.Priority.Value; + writer.Write((byte)((p >> 32) & 0xff)); + writer.Write((byte)((p >> 24) & 0xff)); + writer.Write((byte)((p >> 16) & 0xff)); + writer.Write((byte)((p >> 8) & 0xff)); + writer.Write((byte)(p & 0xff)); + } + + if (rr is Request request) + { + encoder.EncodeHeader(writer, ":method", request.Method); + encoder.EncodeHeader(writer, ":authority", request.RequestUri.Host); + encoder.EncodeHeader(writer, ":scheme", request.RequestUri.Scheme); + encoder.EncodeHeader(writer, ":path", request.RequestUriString, false, + HpackUtil.IndexType.None, false); + } + else + { + var response = (Response)rr; + encoder.EncodeHeader(writer, ":status", response.StatusCode.ToString()); + } + + foreach (var header in rr.Headers) + { + encoder.EncodeHeader(writer, header.Name.ToLower(), header.Value); + } + + var data = ms.ToArray(); + int newLength = data.Length; + + frameHeader.Length = newLength; + frameHeader.Type = pushPromise ? Http2FrameType.PushPromise : Http2FrameType.Headers; + + var flags = Http2FrameFlag.EndHeaders; + if (endStream) + { + flags |= Http2FrameFlag.EndStream; + } + + if (rr.Priority.HasValue) + { + flags |= Http2FrameFlag.Priority; + } + + frameHeader.Flags = flags; + + // clear the padding flag + //headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded)); + + // send the header + var buf = frameHeader.CopyToBuffer(); + await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); + await output.WriteAsync(data, 0, data.Length /*, cancellationToken*/); + } + + private static async Task sendBody(Http2Settings settings, RequestResponseBase rr, Http2FrameHeader frameHeader, byte[] buffer, Stream output) + { + var body = rr.CompressBodyAndUpdateContentLength(); + await sendHeader(settings, frameHeader, rr, !(rr.HasBody && rr.IsBodyRead), output, false); + + if (rr.HasBody && rr.IsBodyRead) + { + int pos = 0; + while (pos < body.Length) + { + int bodyFrameLength = Math.Min(buffer.Length, body.Length - pos); + Buffer.BlockCopy(body, pos, buffer, 0, bodyFrameLength); + pos += bodyFrameLength; + + frameHeader.Length = bodyFrameLength; + frameHeader.Type = Http2FrameType.Data; + frameHeader.Flags = pos < body.Length ? (Http2FrameFlag)0 : Http2FrameFlag.EndStream; + + var buf = frameHeader.CopyToBuffer(); + await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); + await output.WriteAsync(buffer, 0, bodyFrameLength /*, cancellationToken*/); + } + } + else + { + ; + } + } + private static async Task forceRead(Stream input, byte[] buffer, int offset, int bytesToRead, CancellationToken cancellationToken) { diff --git a/src/Titanium.Web.Proxy/Models/HttpHeader.cs b/src/Titanium.Web.Proxy/Models/HttpHeader.cs index 030008c60..02383fab7 100644 --- a/src/Titanium.Web.Proxy/Models/HttpHeader.cs +++ b/src/Titanium.Web.Proxy/Models/HttpHeader.cs @@ -35,13 +35,20 @@ public HttpHeader(string name, string value) { if (string.IsNullOrEmpty(name)) { - throw new Exception("Name cannot be null"); + throw new Exception("Name cannot be null or empty"); } Name = name.Trim(); Value = value.Trim(); } + protected HttpHeader(string name, string value, bool headerEntry) + { + // special header entry created in inherited class with empty name + Name = name.Trim(); + Value = value.Trim(); + } + /// /// Header Name. /// diff --git a/src/Titanium.Web.Proxy/ResponseHandler.cs b/src/Titanium.Web.Proxy/ResponseHandler.cs index f727f01a4..39156f637 100644 --- a/src/Titanium.Web.Proxy/ResponseHandler.cs +++ b/src/Titanium.Web.Proxy/ResponseHandler.cs @@ -125,7 +125,6 @@ await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, } args.TimeLine["Response Sent"] = DateTime.Now; - } ///