Esempio n. 1
0
        public void CreatingAConnectionWithInvalidUpgradeShouldThrow()
        {
            var inPipe  = new BufferedPipe(1024);
            var outPipe = new BufferedPipe(1024);

            var config = new ConnectionConfigurationBuilder(true)
                         .UseStreamListener(s => false)
                         .Build();

            var builder = new ServerUpgradeRequestBuilder();

            builder.SetHeaders(DefaultGetHeaders.ToList());
            builder.SetHttp2Settings("!");
            var upgrade = builder.Build();

            Assert.False(upgrade.IsValid);

            var ex = Assert.Throws <ArgumentException>(() =>
            {
                var conn = new Connection(
                    config, inPipe, outPipe,
                    new Connection.Options
                {
                    Logger = loggerProvider.CreateLogger(""),
                    ServerUpgradeRequest = upgrade,
                });
            });

            Assert.Equal(
                "The ServerUpgradeRequest is invalid.\n" +
                "Invalid upgrade requests must be denied by the HTTP/1 handler",
                ex.Message);
        }
Esempio n. 2
0
        public void UpgradesWithPayloadMustHaveMatchingContentLength(
            int?contentLength, int?payloadLength, bool isOk)
        {
            var builder = new ServerUpgradeRequestBuilder();

            builder.SetHttp2Settings("");
            var headers = DefaultGetHeaders.ToList();

            if (contentLength != null)
            {
                headers.Add(new HeaderField()
                {
                    Name  = "content-length",
                    Value = contentLength.ToString()
                });
            }
            builder.SetHeaders(headers);

            if (payloadLength != null)
            {
                const int padding = 3;
                var       pl      = new byte[padding + payloadLength.Value];
                builder.SetPayload(new ArraySegment <byte>(pl, padding, payloadLength.Value));
            }

            var upgrade = builder.Build();

            Assert.Equal(isOk, upgrade.IsValid);
        }
Esempio n. 3
0
        public void UpgradesWithInvalidEncodedHttp2SettingsStringShouldBeInvalid(
            string encodedSettings)
        {
            var builder = new ServerUpgradeRequestBuilder();

            builder.SetHeaders(DefaultGetHeaders.ToList());
            builder.SetHttp2Settings(encodedSettings);
            var upgrade = builder.Build();

            Assert.False(upgrade.IsValid);
        }
Esempio n. 4
0
        public void UpgradesWithInvalidHeadersShouldBeInvalid(
            HeaderField[] headers)
        {
            var builder = new ServerUpgradeRequestBuilder();

            builder.SetHttp2Settings("");
            builder.SetHeaders(headers.ToList());
            var upgrade = builder.Build();

            Assert.False(upgrade.IsValid);
        }
Esempio n. 5
0
        public void ValidHttp2SettingsShouldBeAccepted(
            byte[] payload)
        {
            var builder = new ServerUpgradeRequestBuilder();

            builder.SetHeaders(DefaultGetHeaders.ToList());
            var base64 = Convert.ToBase64String(payload);

            base64 = base64.Replace('/', '_');
            base64 = base64.Replace('+', '-');
            builder.SetHttp2Settings(base64);
            var upgrade = builder.Build();

            Assert.True(upgrade.IsValid);
        }
Esempio n. 6
0
        public async Task ServerUpgradeRequestsShouldDispatchStream1(
            int payloadLength)
        {
            var     inPipe            = new BufferedPipe(1024);
            var     outPipe           = new BufferedPipe(1024);
            int     nrAcceptedStreams = 0;
            IStream stream            = null;
            var     handlerDone       = new SemaphoreSlim(0);

            Func <IStream, bool> listener = (s) =>
            {
                Interlocked.Increment(ref nrAcceptedStreams);
                Task.Run(() =>
                {
                    stream = s;
                    handlerDone.Release();
                });
                return(true);
            };

            var startOfPayload = 44;

            byte[] payload = new byte[payloadLength];
            for (var i = 0; i < payloadLength; i++)
            {
                payload[i] = (byte)(startOfPayload + i);
            }

            var builder = new ServerUpgradeRequestBuilder();
            var headers = DefaultGetHeaders.ToList();

            if (payloadLength != 0)
            {
                builder.SetPayload(new ArraySegment <byte>(payload));
                headers.Add(new HeaderField()
                {
                    Name  = "content-length",
                    Value = payloadLength.ToString()
                });
            }
            builder.SetHeaders(headers);
            builder.SetHttp2Settings("");
            var upgrade = builder.Build();

            var config = new ConnectionConfigurationBuilder(true)
                         .UseStreamListener(listener)
                         .Build();

            var conn = new Connection(
                config, inPipe, outPipe,
                new Connection.Options
            {
                Logger = loggerProvider.CreateLogger("http2Con"),
                ServerUpgradeRequest = upgrade,
            });

            await conn.PerformHandshakes(inPipe, outPipe);

            var requestDone = await handlerDone.WaitAsync(
                ReadableStreamTestExtensions.ReadTimeout);

            Assert.True(requestDone, "Expected handler to complete within timeout");

            Assert.Equal(1u, stream.Id);
            Assert.Equal(StreamState.HalfClosedRemote, stream.State);
            var rcvdHeaders = await stream.ReadHeadersAsync();

            Assert.True(headers.SequenceEqual(rcvdHeaders));
            var allData = await stream.ReadAllToArrayWithTimeout();

            Assert.Equal(payloadLength, allData.Length);
            Assert.Equal(payload, allData);

            Assert.Equal(1, nrAcceptedStreams);
        }
Esempio n. 7
0
    static async Task HandleConnection(
        ILoggerProvider logProvider,
        ConnectionConfiguration config,
        bool useUpgrade,
        IReadableByteStream inputStream,
        IWriteAndCloseableByteStream outputStream,
        int connectionId)
    {
        var upgradeReadStream        = new UpgradeReadStream(inputStream);
        ServerUpgradeRequest upgrade = null;

        try
        {
            // Wait for either HTTP/1 upgrade header or HTTP/2 magic header
            await upgradeReadStream.WaitForHttpHeader();

            var headerBytes = upgradeReadStream.HeaderBytes;
            if (MaybeHttpStart(headerBytes))
            {
                // This seems to be a HTTP/2 request
                // No upgrade necessary
                // Make the header rereadable by the stream reader consumer,
                // so that the library can also read the preface
                upgradeReadStream.UnreadHttpHeader();
            }
            else
            {
                // This seems to be a HTTP/1 request
                // Parse the header and check whether it's an upgrade
                var request = Http1Request.ParseFrom(
                    Encoding.ASCII.GetString(
                        headerBytes.Array, headerBytes.Offset, headerBytes.Count - 4));
                // Assure that the HTTP/2 library does not get passed the HTTP/1 request
                upgradeReadStream.ConsumeHttpHeader();

                if (request.Protocol != "HTTP/1.1")
                {
                    throw new Exception("Invalid upgrade request");
                }

                // If the request has some payload we can't process it in this demo
                string contentLength;
                if (request.Headers.TryGetValue("content-length", out contentLength))
                {
                    await outputStream.WriteAsync(
                        new ArraySegment <byte>(UpgradeErrorResponse));

                    await outputStream.CloseAsync();

                    return;
                }

                string connectionHeader;
                string hostHeader;
                string upgradeHeader;
                string http2SettingsHeader;
                if (!request.Headers.TryGetValue("connection", out connectionHeader) ||
                    !request.Headers.TryGetValue("host", out hostHeader) ||
                    !request.Headers.TryGetValue("upgrade", out upgradeHeader) ||
                    !request.Headers.TryGetValue("http2-settings", out http2SettingsHeader) ||
                    upgradeHeader != "h2c" ||
                    http2SettingsHeader.Length == 0)
                {
                    throw new Exception("Invalid upgrade request");
                }

                var connParts =
                    connectionHeader
                    .Split(new char[] { ',' })
                    .Select(p => p.Trim())
                    .ToArray();
                if (connParts.Length != 2 ||
                    !connParts.Contains("Upgrade") ||
                    !connParts.Contains("HTTP2-Settings"))
                {
                    throw new Exception("Invalid upgrade request");
                }

                var headers = new List <HeaderField>();
                headers.Add(new HeaderField {
                    Name = ":method", Value = request.Method
                });
                headers.Add(new HeaderField {
                    Name = ":path", Value = request.Path
                });
                headers.Add(new HeaderField {
                    Name = ":scheme", Value = "http"
                });
                foreach (var kvp in request.Headers)
                {
                    // Skip Connection upgrade related headers
                    if (kvp.Key == "connection" ||
                        kvp.Key == "upgrade" ||
                        kvp.Key == "http2-settings")
                    {
                        continue;
                    }
                    headers.Add(new HeaderField
                    {
                        Name  = kvp.Key,
                        Value = kvp.Value,
                    });
                }

                var upgradeBuilder = new ServerUpgradeRequestBuilder();
                upgradeBuilder.SetHeaders(headers);
                upgradeBuilder.SetHttp2Settings(http2SettingsHeader);
                upgrade = upgradeBuilder.Build();

                if (!upgrade.IsValid)
                {
                    await outputStream.WriteAsync(
                        new ArraySegment <byte>(UpgradeErrorResponse));

                    await outputStream.CloseAsync();

                    return;
                }

                // Respond to upgrade
                await outputStream.WriteAsync(
                    new ArraySegment <byte>(UpgradeSuccessResponse));
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error during connection upgrade: {0}", e.Message);
            await outputStream.CloseAsync();

            return;
        }

        // Build a HTTP connection on top of the stream abstraction
        var http2Con = new Connection(
            config, upgradeReadStream, outputStream,
            options: new Connection.Options
        {
            Logger = logProvider.CreateLogger("HTTP2Conn" + connectionId),
            ServerUpgradeRequest = upgrade,
        });

        // Close the connection if we get a GoAway from the client
        var remoteGoAwayTask      = http2Con.RemoteGoAwayReason;
        var closeWhenRemoteGoAway = Task.Run(async() =>
        {
            await remoteGoAwayTask;
            await http2Con.GoAwayAsync(ErrorCode.NoError, true);
        });
    }
Esempio n. 8
0
        private async Task HandleConnection(
            ILogger logger,
            ConnectionByteStream stream)
        {
            var config = new ConnectionConfigurationBuilder(true)
                         .UseStreamListener(arg =>
            {
                Task.Run(() =>
                {
                    var headers  = arg.ReadHeadersAsync().GetAwaiter().GetResult();
                    var optional = HttpRequest.ParseFrom(new HttpHeaderCollection(headers),
                                                         new HttpMethodResolver(Methods));
                    if (optional.ExpectPayload)
                    {
                        var seg = new ArraySegment <byte>(new byte[int.Parse(optional.Headers["content-length"])]);
                        // TODO: Use the StreamReadResult, as sometimes the packets won't all be sent at once
                        arg.ReadAsync(seg).GetAwaiter().GetResult();
                        optional.Payload = seg.ToArray();
                    }

                    var req = optional.Request;
                    PassContext(new HttpContext(req, new HttpTwoResponse(arg), stream.Connection, logger));
                });
                return(true);
            })
                         .UseSettings(Settings.Default)
                         .UseHuffmanStrategy(HuffmanStrategy.IfSmaller)
                         .Build();
            var upgradeReadStream           = new HttpStream(stream);
            ServerUpgradeRequest upgrade    = null;
            var writeAndCloseableByteStream = stream;

            try
            {
                // Wait for either HTTP/1 upgrade header or HTTP/2 magic header
                await upgradeReadStream.WaitForHttpHeader();

                var headerBytes = upgradeReadStream.HeaderBytes;
                if (MaybeHttpStart(headerBytes))
                {
                    // This seems to be a HTTP/2 request
                    upgradeReadStream.Unread();
                }
                else
                {
                    // This seems to be a HTTP/1 request
                    // Parse the header and check whether it's an upgrade
                    var req = HttpRequest.ParseFrom(
                        Encoding.ASCII.GetString(
                            headerBytes.Array, headerBytes.Offset, headerBytes.Count - 4),
                        (MethodResolver <HttpMethod>)MethodResolver, stream.Connection);

                    if (req.ExpectPayload && req.Headers.TryGetValue("content-length", out string contentLength))
                    {
                        if (int.TryParse(contentLength, out var length))
                        {
                            await upgradeReadStream.WaitForPayloadAsync(length);

                            req.Payload = upgradeReadStream.Payload.ToArray();
                        }
                        else
                        {
                            await stream.WriteAsync(
                                new ArraySegment <byte>(Encoding.ASCII.GetBytes(
                                                            "HTTP/1.1 400 Bad Request\r\nServer: SimpleServer/1.0 sshttp2\r\nContent-Type: text/html\r\nContent-Length: 26\r\n\r\nThe payload was too large.")));

                            await stream.CloseAsync();

                            return;
                        }
                    }
                    else if (req.ExpectPayload)
                    {
                        await stream.WriteAsync(
                            new ArraySegment <byte>(Encoding.ASCII.GetBytes(
                                                        "HTTP/1.1 400 Bad Request\r\nServer: SimpleServer/1.0 sshttp2\r\nContent-Type: text/html\r\nContent-Length: 25\r\n\r\nNo content-length header.")));

                        await stream.CloseAsync();

                        return;
                    }

                    // Assure that the HTTP/2 library does not get passed the HTTP/1 request
                    upgradeReadStream.Consume();

                    var request = req.Request;

#pragma warning disable 618
                    if (request.Protocol != "HTTP/1.1")
#pragma warning restore 618
                    {
                        PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection),
                                                    stream.Connection, logger));
                        return;
                    }

                    if (!request.Headers.TryGetValue("connection", out string connectionHeader) ||
                        !request.Headers.TryGetValue("host", out string _) ||
                        !request.Headers.TryGetValue("upgrade", out string upgradeHeader) ||
                        !request.Headers.TryGetValue("http2-settings", out string http2SettingsHeader) ||
                        upgradeHeader != "h2c" ||
                        http2SettingsHeader.Length == 0)
                    {
                        // STAY H1
                        PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection),
                                                    stream.Connection, logger));
                        return;
                    }

                    var connParts =
                        connectionHeader
                        .Split(',')
                        .Select(p => p.Trim())
                        .ToArray();
                    if (connParts.Length != 2 ||
                        !connParts.Contains("Upgrade") ||
                        !connParts.Contains("HTTP2-Settings"))
                    {
                        PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection),
                                                    stream.Connection, logger));
                    }

                    var headers = new List <HeaderField>
                    {
                        new HeaderField {
                            Name = ":method", Value = request.Method.Id
                        },
                        new HeaderField {
                            Name = ":path", Value = request.RawUrl
                        },
                        new HeaderField
                        {
                            Name = ":scheme",
#if NETCOREAPP2_1 || NETSTANDARD2_1
                            Value = stream.Connection is SslListener.SecureConnection ? "https" : "http"
                            #else
                            Value = "http"
#endif
                        }
                    };
                    foreach (var kvp in request.Headers)
                    {
                        // Skip Connection upgrade related headers
                        if (kvp.Name == "connection" ||
                            kvp.Name == "upgrade" ||
                            kvp.Name == "http2-settings")
                        {
                            continue;
                        }
                        headers.Add(new HeaderField
                        {
                            Name  = kvp.Name,
                            Value = kvp.Value
                        });
                    }

                    var upgradeBuilder = new ServerUpgradeRequestBuilder();
                    upgradeBuilder.SetHeaders(headers);
                    upgradeBuilder.SetPayload(new ArraySegment <byte>(req.Payload));
                    upgradeBuilder.SetHttp2Settings(http2SettingsHeader);
                    upgrade = upgradeBuilder.Build();

                    if (!upgrade.IsValid)
                    {
                        // STAY H1
                        PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection),
                                                    stream.Connection, logger));
                        return;
                    }

                    // Respond to upgrade
                    await writeAndCloseableByteStream.WriteAsync(
                        new ArraySegment <byte>(UpgradeSuccessResponse));
                }
            }
            catch (Exception e)
            {
                logger?.LogError(new EventId(stream.Connection.Id, "StartFail"), e,
                                 "An error occured while trying to upgrade a HTTP/1.X request to a H2 request.");
                await writeAndCloseableByteStream.CloseAsync();

                return;
            }

            // Build a H2 connection
            var http2Con = new Connection(
                config, upgradeReadStream, writeAndCloseableByteStream,
                new Connection.Options
            {
                Logger = logger,
                ServerUpgradeRequest = upgrade
            });

            // Close the connection if we get a GoAway from the client
            var remoteGoAwayTask = http2Con.RemoteGoAwayReason;
#pragma warning disable 4014
            Task.Run(async() =>
#pragma warning restore 4014
            {
                await remoteGoAwayTask;
                await http2Con.GoAwayAsync(ErrorCode.NoError, true);
            });
        }