Beispiel #1
0
 private static void VerifyAllPseudoHeadersPresent(IHttp2Headers headers)
 {
     foreach (PseudoHeaderName pseudoName in PseudoHeaderName.All)
     {
         Assert.NotNull(headers.Get(pseudoName.Value, null));
     }
 }
Beispiel #2
0
        private static void SetHttp2Scheme(HttpHeaders input, Uri uri, IHttp2Headers output)
        {
            var value = uri.Scheme;

            if (value is object)
            {
                output.Scheme = new AsciiString(value);
                return;
            }

            // Consume the Scheme extension header if present
            if (input.TryGet(ExtensionHeaderNames.Scheme, out var cValue))
            {
                output.Scheme = AsciiString.Of(cValue);
                return;
            }

            if (uri.Port == HttpScheme.Https.Port)
            {
                output.Scheme = HttpScheme.Https.Name;
            }
            else if (uri.Port == HttpScheme.Http.Port)
            {
                output.Scheme = HttpScheme.Http.Name;
            }

            ThrowHelper.ThrowArgumentException_SchemeMustBeSpecified();
        }
Beispiel #3
0
        public void TestEncodeEmptyFullRequestWithTrailers()
        {
            EmbeddedChannel  ch      = new EmbeddedChannel(new Http2StreamFrameToHttpObjectCodec(false));
            IFullHttpRequest request = new DefaultFullHttpRequest(
                HttpVersion.Http11, HttpMethod.Put, "/hello/world");

            HttpHeaders trailers = request.TrailingHeaders;

            trailers.Set((AsciiString)"key", "value");
            Assert.True(ch.WriteOutbound(request));

            var           headersFrame = ch.ReadOutbound <IHttp2HeadersFrame>();
            IHttp2Headers headers      = headersFrame.Headers;

            Assert.Equal("http", headers.Scheme);
            Assert.Equal("PUT", headers.Method);
            Assert.Equal("/hello/world", headers.Path);
            Assert.False(headersFrame.IsEndStream);

            IHttp2HeadersFrame trailersFrame = ch.ReadOutbound <IHttp2HeadersFrame>();

            Assert.Equal("value", trailersFrame.Headers.Get((AsciiString)"key", null));
            Assert.True(trailersFrame.IsEndStream);

            Assert.Null(ch.ReadOutbound <object>());
            Assert.False(ch.Finish());
        }
        private static void TestIteratorReadOnly(IHttp2Headers headers)
        {
            var enumerator = headers.GetEnumerator();

            Assert.True(enumerator.MoveNext());
            headers.Remove(enumerator.Current.Key);
        }
Beispiel #5
0
 public Http2HeadersSink(int streamId, IHttp2Headers headers, long maxHeaderListSize, bool validate)
 {
     _headers           = headers;
     _maxHeaderListSize = maxHeaderListSize;
     _streamId          = streamId;
     _validate          = validate;
 }
Beispiel #6
0
        public static void ToHttp2Headers(HttpHeaders inHeaders, IHttp2Headers output)
        {
            // Choose 8 as a default size because it is unlikely we will see more than 4 Connection headers values, but
            // still allowing for "enough" space in the map to reduce the chance of hash code collision.
            var connectionBlacklist = ToLowercaseMap(inHeaders.GetAll(HttpHeaderNames.Connection), 8);

            foreach (var entry in inHeaders)
            {
                AsciiString aName = entry.Key.ToLowerCase();
                if (!HttpToHttp2HeaderBlacklist.Contains(aName) && !connectionBlacklist.Contains(aName))
                {
                    // https://tools.ietf.org/html/rfc7540#section-8.1.2.2 makes a special exception for TE
                    if (aName.ContentEqualsIgnoreCase(HttpHeaderNames.Te))
                    {
                        ToHttp2HeadersFilterTE(entry, output);
                    }
                    else if (aName.ContentEqualsIgnoreCase(HttpHeaderNames.Cookie))
                    {
                        AsciiString value       = AsciiString.Of(entry.Value);
                        uint        uValueCount = (uint)value.Count;
                        // split up cookies to allow for better compression
                        // https://tools.ietf.org/html/rfc7540#section-8.1.2.5
                        try
                        {
                            int index = value.ForEachByte(ByteProcessor.FindSemicolon);
                            if (uValueCount > (uint)index) // != -1
                            {
                                int start = 0;
                                do
                                {
                                    _ = output.Add(HttpHeaderNames.Cookie, value.SubSequence(start, index, false));
                                    // skip 2 characters "; " (see https://tools.ietf.org/html/rfc6265#section-4.2.1)
                                    start = index + 2;
                                } while ((uint)start < uValueCount &&
                                         uValueCount > (uint)(index = value.ForEachByte(start, value.Count - start, ByteProcessor.FindSemicolon))); // != -1
                                if ((uint)start >= uValueCount)
                                {
                                    ThrowHelper.ThrowArgumentException_CookieValueIsOfUnexpectedFormat(value);
                                }
                                _ = output.Add(HttpHeaderNames.Cookie, value.SubSequence(start, value.Count, false));
                            }
                            else
                            {
                                _ = output.Add(HttpHeaderNames.Cookie, value);
                            }
                        }
                        catch (Exception)
                        {
                            // This is not expect to happen because FIND_SEMI_COLON never throws but must be caught
                            // because of the ByteProcessor interface.
                            ThrowHelper.ThrowInvalidOperationException();
                        }
                    }
                    else
                    {
                        _ = output.Add(aName, entry.Value);
                    }
                }
            }
        }
Beispiel #7
0
 // note that this may behave strangely when used for the initial upgrade
 // message when using h2c, since that message is ineligible for flow
 // control, but there is not yet an API for signaling that.
 internal static void Handle(IChannelHandlerContext ctx, IHttp2Connection connection,
                             IHttp2FrameListener listener, IFullHttpMessage message)
 {
     try
     {
         int          streamId = GetStreamId(connection, message.Headers);
         IHttp2Stream stream   = connection.Stream(streamId);
         if (stream is null)
         {
             stream = connection.Remote.CreateStream(streamId, false);
         }
         _ = message.Headers.Set(HttpConversionUtil.ExtensionHeaderNames.Scheme, HttpScheme.Http.Name);
         IHttp2Headers messageHeaders = HttpConversionUtil.ToHttp2Headers(message, true);
         var           hasContent     = message.Content.IsReadable();
         var           hasTrailers    = !message.TrailingHeaders.IsEmpty;
         listener.OnHeadersRead(ctx, streamId, messageHeaders, 0, !(hasContent || hasTrailers));
         if (hasContent)
         {
             _ = listener.OnDataRead(ctx, streamId, message.Content, 0, !hasTrailers);
         }
         if (hasTrailers)
         {
             IHttp2Headers headers = HttpConversionUtil.ToHttp2Headers(message.TrailingHeaders, true);
             listener.OnHeadersRead(ctx, streamId, headers, 0, true);
         }
         _ = stream.CloseRemoteSide();
     }
     finally
     {
         _ = message.Release();
     }
 }
Beispiel #8
0
 /// <summary>
 /// Translate and add HTTP/2 headers to HTTP/1.x headers.
 /// </summary>
 /// <param name="streamId">The stream associated with <paramref name="sourceHeaders"/>.</param>
 /// <param name="sourceHeaders">The HTTP/2 headers to convert.</param>
 /// <param name="destinationMessage">The object which will contain the resulting HTTP/1.x headers.</param>
 /// <param name="addToTrailer"><c>true</c> to add to trailing headers. <c>false</c> to add to initial headers.</param>
 /// <exception cref="Http2Exception">If not all HTTP/2 headers can be translated to HTTP/1.x.</exception>
 public static void AddHttp2ToHttpHeaders(int streamId, IHttp2Headers sourceHeaders,
                                          IFullHttpMessage destinationMessage, bool addToTrailer)
 {
     AddHttp2ToHttpHeaders(streamId, sourceHeaders,
                           addToTrailer ? destinationMessage.TrailingHeaders : destinationMessage.Headers,
                           destinationMessage.ProtocolVersion, addToTrailer, destinationMessage is IHttpRequest);
 }
Beispiel #9
0
        /// <summary>
        /// Translate and add HTTP/2 headers to HTTP/1.x headers.
        /// </summary>
        /// <param name="streamId">The stream associated with <paramref name="outputHeaders"/>.</param>
        /// <param name="inputHeaders">The HTTP/2 headers to convert.</param>
        /// <param name="outputHeaders">The object which will contain the resulting HTTP/1.x headers..</param>
        /// <param name="httpVersion">What HTTP/1.x version <paramref name="outputHeaders"/> should be treated as when doing the conversion.</param>
        /// <param name="isTrailer"><c>true</c> if <paramref name="outputHeaders"/> should be treated as trailing headers.
        /// <c>false</c> otherwise.</param>
        /// <param name="isRequest"><c>true</c> if the <paramref name="outputHeaders"/> will be used in a request message.
        /// <c>false</c> for response message.</param>
        /// <exception cref="Http2Exception">If not all HTTP/2 headers can be translated to HTTP/1.x.</exception>
        public static void AddHttp2ToHttpHeaders(int streamId, IHttp2Headers inputHeaders, HttpHeaders outputHeaders,
                                                 DotNettyHttpVersion httpVersion, bool isTrailer, bool isRequest)
        {
            Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, outputHeaders, isRequest);

            try
            {
                translator.TranslateHeaders(inputHeaders);
            }
            catch (Http2Exception)
            {
                throw;
            }
            catch (Exception t)
            {
                ThrowHelper.ThrowStreamError_Http2ToHttp1HeadersConversionError(streamId, t);
            }

            _ = outputHeaders.Remove(HttpHeaderNames.TransferEncoding);
            _ = outputHeaders.Remove(HttpHeaderNames.Trailer);
            if (!isTrailer)
            {
                _ = outputHeaders.SetInt(ExtensionHeaderNames.StreamId, streamId);
                HttpUtil.SetKeepAlive(outputHeaders, httpVersion, true);
            }
        }
Beispiel #10
0
        /// <summary>
        /// Create a new object to contain the response data
        /// </summary>
        /// <param name="streamId">The stream associated with the response</param>
        /// <param name="http2Headers">The initial set of HTTP/2 headers to create the response with</param>
        /// <param name="alloc">The <see cref="IByteBufferAllocator"/> to use to generate the content of the message</param>
        /// <param name="validateHttpHeaders"><c>true</c> to validate HTTP headers in the http-codec
        /// <para><c>false</c> not to validate HTTP headers in the http-codec</para></param>
        /// <returns>A new response object which represents headers/data</returns>
        /// <exception cref="Http2Exception">see <see cref="AddHttp2ToHttpHeaders(int, IHttp2Headers, IFullHttpMessage, bool)"/></exception>
        public static IFullHttpResponse ToFullHttpResponse(int streamId, IHttp2Headers http2Headers, IByteBufferAllocator alloc,
                                                           bool validateHttpHeaders)
        {
            HttpResponseStatus status = ParseStatus(http2Headers.Status);
            // HTTP/2 does not define a way to carry the version or reason phrase that is included in an
            // HTTP/1.1 status line.
            IFullHttpResponse msg = new DefaultFullHttpResponse(DotNettyHttpVersion.Http11, status, alloc.Buffer(),
                                                                validateHttpHeaders);

            try
            {
                AddHttp2ToHttpHeaders(streamId, http2Headers, msg, false);
            }
            catch (Http2Exception)
            {
                _ = msg.Release();
                throw;
            }
            catch (Exception t)
            {
                _ = msg.Release();
                ThrowHelper.ThrowStreamError_Http2ToHttp1HeadersConversionError(streamId, t);
            }
            return(msg);
        }
Beispiel #11
0
        /// <summary>
        /// Create a new object to contain the request data.
        /// </summary>
        /// <param name="streamId">The stream associated with the request</param>
        /// <param name="http2Headers">The initial set of HTTP/2 headers to create the request with</param>
        /// <param name="validateHttpHeaders"><c>true</c> to validate HTTP headers in the http-codec
        /// <para><c>false</c> not to validate HTTP headers in the http-codec</para></param>
        /// <returns>A new request object which represents headers for a chunked request</returns>
        /// <exception cref="Http2Exception">See <see cref="AddHttp2ToHttpHeaders(int, IHttp2Headers, HttpHeaders, DotNettyHttpVersion, bool, bool)"/></exception>
        public static IHttpRequest ToHttpRequest(int streamId, IHttp2Headers http2Headers, bool validateHttpHeaders)
        {
            // HTTP/2 does not define a way to carry the version identifier that is included in the HTTP/1.1 request line.
            var method = http2Headers.Method;

            if (method is null)
            {
                ThrowHelper.ThrowArgumentNullException_MethodHeader();
            }
            var path = http2Headers.Path;

            if (path is null)
            {
                ThrowHelper.ThrowArgumentNullException_PathHeader();
            }

            var msg = new DefaultHttpRequest(DotNettyHttpVersion.Http11, HttpMethod.ValueOf(AsciiString.Of(method)),
                                             path.ToString(), validateHttpHeaders);

            try
            {
                AddHttp2ToHttpHeaders(streamId, http2Headers, msg.Headers, msg.ProtocolVersion, false, true);
            }
            catch (Http2Exception)
            {
                throw;
            }
            catch (Exception t)
            {
                ThrowHelper.ThrowStreamError_Http2ToHttp1HeadersConversionError(streamId, t);
            }
            return(msg);
        }
Beispiel #12
0
 public Task WriteHeadersAsync(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int streamDependency, short weight, bool exclusive, int padding, bool endOfStream, IPromise promise)
 {
     _logger.LogHeaders(Direction.Outbound, ctx, streamId, headers, streamDependency, weight, exclusive,
                        padding, endOfStream);
     return(_writer.WriteHeadersAsync(ctx, streamId, headers, streamDependency, weight,
                                      exclusive, padding, endOfStream, promise));
 }
 public void OnHeadersRead(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int streamDependency, short weight, bool exclusive, int padding, bool endOfStream)
 {
     _logger.LogHeaders(Direction.Inbound, ctx, streamId, headers, streamDependency, weight, exclusive,
                        padding, endOfStream);
     _listener.OnHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive,
                             padding, endOfStream);
 }
Beispiel #14
0
        public void TestEncodeNonEmptyFullRequest()
        {
            EmbeddedChannel ch    = new EmbeddedChannel(new Http2StreamFrameToHttpObjectCodec(false));
            IByteBuffer     hello = Unpooled.CopiedBuffer("hello world", Encoding.UTF8);

            Assert.True(ch.WriteOutbound(new DefaultFullHttpRequest(
                                             HttpVersion.Http11, HttpMethod.Put, "/hello/world", hello)));

            var           headersFrame = ch.ReadOutbound <IHttp2HeadersFrame>();
            IHttp2Headers headers      = headersFrame.Headers;

            Assert.Equal("http", headers.Scheme);
            Assert.Equal("PUT", headers.Method);
            Assert.Equal("/hello/world", headers.Path);
            Assert.False(headersFrame.IsEndStream);

            var dataFrame = ch.ReadOutbound <IHttp2DataFrame>();

            try
            {
                Assert.Equal("hello world", dataFrame.Content.ToString(Encoding.UTF8));
                Assert.True(dataFrame.IsEndStream);
            }
            finally
            {
                dataFrame.Release();
            }

            Assert.Null(ch.ReadOutbound <object>());
            Assert.False(ch.Finish());
        }
        /// <summary>
        /// Checks if a new compressor object is needed for the stream identified by <c>streamId</c>. This method will
        /// modify the <c>content-encoding</c> header contained in <paramref name="headers"/>.
        /// </summary>
        /// <param name="ctx">the context.</param>
        /// <param name="headers">Object representing headers which are to be written</param>
        /// <param name="endOfStream">Indicates if the stream has ended</param>
        /// <exception cref="Http2Exception">if any problems occur during initialization.</exception>
        /// <returns>The channel used to compress data.</returns>
        private EmbeddedChannel NewCompressor(IChannelHandlerContext ctx, IHttp2Headers headers, bool endOfStream)
        {
            if (endOfStream)
            {
                return(null);
            }


            if (!headers.TryGet(HttpHeaderNames.ContentEncoding, out var encoding))
            {
                encoding = HttpHeaderValues.Identity;
            }
            var compressor = NewContentCompressor(ctx, encoding);

            if (compressor is object)
            {
                var targetContentEncoding = GetTargetContentEncoding(encoding);
                if (HttpHeaderValues.Identity.ContentEqualsIgnoreCase(targetContentEncoding))
                {
                    _ = headers.Remove(HttpHeaderNames.ContentEncoding);
                }
                else
                {
                    _ = headers.Set(HttpHeaderNames.ContentEncoding, targetContentEncoding);
                }

                // The content length will be for the decompressed data. Since we will compress the data
                // this content-length will not be correct. Instead of queuing messages or delaying sending
                // header frames...just remove the content-length header
                _ = headers.Remove(HttpHeaderNames.ContentLength);
            }

            return(compressor);
        }
        private static void TestIteratorEntryReadOnly(IHttp2Headers headers)
        {
            var enumerator = headers.GetEnumerator();

            Assert.True(enumerator.MoveNext());
            enumerator.Current.SetValue((AsciiString)"foo");
        }
        private byte[] BuildLargeHeaderPayload(int streamId, IHttp2Headers headers, byte padding, int maxFrameSize)
        {
            var outputStream = new MemoryStream();
            var bw           = new BinaryWriter(outputStream);

            try
            {
                bw.Write(padding);
                byte[] payload          = HeaderPayload(streamId, headers);
                int    firstPayloadSize = maxFrameSize - (padding + 1); //1 for padding length
                outputStream.Write(payload, 0, firstPayloadSize);
#if DESKTOPCLR
                outputStream.Write(new byte[padding], 0, padding);
#else
                outputStream.Write(new byte[padding]);
#endif
                outputStream.Write(payload, firstPayloadSize, payload.Length - firstPayloadSize);
                return(outputStream.ToArray());
            }
            finally
            {
                outputStream.Close();
                bw.Dispose();
            }
        }
        private void AssertRoundtripSuccessful(IHttp2Headers input)
        {
            encoder.EncodeHeaders(3 /* randomly chosen */, input, buffer);

            IHttp2Headers output = decoder.DecodeHeaders(0, buffer);

            Assert.Equal(input, output);
        }
        private static void TestValueIteratorSingleValue(IHttp2Headers headers, ICharSequence name, ICharSequence value)
        {
            var itr = headers.GetAll(name).GetEnumerator();

            Assert.True(itr.MoveNext());
            Assert.True(AsciiString.ContentEqualsIgnoreCase(value, itr.Current));
            Assert.False(itr.MoveNext());
        }
Beispiel #20
0
 public void OnHeadersRead(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int streamDependency, short weight, bool exclusive, int padding, bool endOfStream)
 {
     this.listener.OnHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream);
     this.messageLatch.SafeSignal();
     if (this.trailersLatch != null && endOfStream)
     {
         this.trailersLatch.SafeSignal();
     }
 }
Beispiel #21
0
 void EncodeHeadersIgnoreMaxHeaderListSize(IByteBuffer output, IHttp2Headers headers, ISensitivityDetector sensitivityDetector)
 {
     foreach (HeaderEntry <ICharSequence, ICharSequence> header in headers)
     {
         ICharSequence name  = header.Key;
         ICharSequence value = header.Value;
         EncodeHeader(output, name, value, sensitivityDetector.IsSensitive(name, value), HpackHeaderField.SizeOf(name, value));
     }
 }
 public void HeadersExceedMaxSetSizeShouldFail()
 {
     Assert.Throws <HeaderListSizeException>(() =>
     {
         IHttp2Headers headers = Headers();
         encoder.SetMaxHeaderListSize(2);
         encoder.EncodeHeaders(3 /* randomly chosen */, headers, Unpooled.Buffer());
     });
 }
Beispiel #23
0
 public void LogPushPromise(Direction direction, IChannelHandlerContext ctx, int streamId, int promisedStreamId,
                            IHttp2Headers headers, int padding)
 {
     if (IsEnabled())
     {
         _logger.Log(_level, "{} {} PUSH_PROMISE: streamId={} promisedStreamId={} headers={} padding={}",
                     ctx.Channel, direction, streamId, promisedStreamId, headers, padding);
     }
 }
 /// <summary>
 /// Create a new <see cref="IFullHttpMessage"/> based upon the current connection parameters
 /// </summary>
 /// <param name="stream">The stream to create a message for</param>
 /// <param name="headers">The headers associated with <paramref name="stream"/>.</param>
 /// <param name="validateHttpHeaders"><c>true</c> to validate HTTP headers in the http-codec
 /// <para><c>false</c> not to validate HTTP headers in the http-codec</para></param>
 /// <param name="alloc">The <see cref="IByteBufferAllocator"/> to use to generate the content of the message</param>
 /// <returns></returns>
 protected virtual IFullHttpMessage NewMessage(IHttp2Stream stream, IHttp2Headers headers, bool validateHttpHeaders,
                                               IByteBufferAllocator alloc)
 {
     if (_connection.IsServer)
     {
         return(HttpConversionUtil.ToFullHttpRequest(stream.Id, headers, alloc, validateHttpHeaders));
     }
     return(HttpConversionUtil.ToFullHttpResponse(stream.Id, headers, alloc, validateHttpHeaders));
 }
Beispiel #25
0
        /// <summary>
        /// Decode the header block into header fields.
        /// <para>This method assumes the entire header block is contained in <paramref name="input"/>.</para>
        /// </summary>
        /// <param name="streamId"></param>
        /// <param name="input"></param>
        /// <param name="headers"></param>
        /// <param name="validateHeaders"></param>
        public void Decode(int streamId, IByteBuffer input, IHttp2Headers headers, bool validateHeaders)
        {
            var sink = new Http2HeadersSink(streamId, headers, _maxHeaderListSize, validateHeaders);

            Decode(input, sink);

            // Now that we've read all of our headers we can perform the validation steps. We must
            // delay throwing until this point to prevent dynamic table corruption.
            sink.Finish();
        }
 public void OnHeadersRead(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int padding, bool endOfStream)
 {
     if (endOfStream)
     {
         var content = ctx.Allocator.Buffer();
         content.WriteBytes(HelloWorldHttp1Handler.RESPONSE_BYTES.Duplicate());
         ByteBufferUtil.WriteAscii(content, " - via HTTP/2");
         this.SendResponse(ctx, streamId, content);
     }
 }
        private static IHttp2Headers DummyHeaders(IHttp2Headers headers, int times)
        {
            string largeValue = Repeat("dummy-value", 100);

            for (int i = 0; i < times; i++)
            {
                headers.Add((AsciiString)string.Format("dummy-{0}", i), (AsciiString)largeValue);
            }
            return(headers);
        }
        private static bool ValidateHeadersSentState(IHttp2Stream stream, IHttp2Headers headers, bool isServer, bool endOfStream)
        {
            var isInformational = isServer && HttpStatusClass.ValueOf(headers.Status) == HttpStatusClass.Informational;

            if ((isInformational || !endOfStream) && stream.IsHeadersSent || stream.IsTrailersSent)
            {
                ThrowHelper.ThrowInvalidOperationException_StreamSentTooManyHeadersEOS(stream.Id, endOfStream);
            }
            return(isInformational);
        }
        public override void OnHeadersRead(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int padding, bool endOfStream)
        {
            IHttp2Stream     stream = _connection.Stream(streamId);
            IFullHttpMessage msg    = ProcessHeadersBegin(ctx, stream, headers, endOfStream, true, true);

            if (msg is object)
            {
                ProcessHeadersEnd(ctx, stream, msg, endOfStream);
            }
        }
Beispiel #30
0
 /// <summary>
 /// Encode the header field into the header block.
 /// The given <see cref="ICharSequence"/>s must be immutable!
 /// </summary>
 /// <param name="streamId"></param>
 /// <param name="output"></param>
 /// <param name="headers"></param>
 /// <param name="sensitivityDetector"></param>
 public void EncodeHeaders(int streamId, IByteBuffer output, IHttp2Headers headers, ISensitivityDetector sensitivityDetector)
 {
     if (_ignoreMaxHeaderListSize)
     {
         EncodeHeadersIgnoreMaxHeaderListSize(output, headers, sensitivityDetector);
     }
     else
     {
         EncodeHeadersEnforceMaxHeaderListSize(streamId, output, headers, sensitivityDetector);
     }
 }