View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.codec.http3;
18  
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.channel.ChannelPromise;
21  import io.netty.handler.codec.http.HttpHeaderNames;
22  import io.netty.handler.codec.http.HttpHeaderValues;
23  import io.netty.handler.codec.quic.QuicStreamChannel;
24  import io.netty.util.ReferenceCountUtil;
25  import io.netty.util.internal.StringUtil;
26  
27  import java.util.function.BooleanSupplier;
28  
29  import static io.netty.handler.codec.http.HttpUtil.normalizeAndGetContentLength;
30  import static io.netty.handler.codec.http3.Http3ErrorCode.H3_MESSAGE_ERROR;
31  import static io.netty.handler.codec.http3.Http3FrameValidationUtils.frameTypeUnexpected;
32  
33  final class Http3RequestStreamValidationUtils {
34      static final long CONTENT_LENGTH_NOT_MODIFIED = -1;
35      static final long INVALID_FRAME_READ = -2;
36  
37      private Http3RequestStreamValidationUtils() {
38          // No instances
39      }
40  
41      /**
42       * Validate write of the passed {@link Http3RequestStreamFrame} for a client and takes appropriate error handling
43       * for invalid frames.
44       *
45       * @param frame                  to validate.
46       * @param promise                for the write.
47       * @param ctx                    for the handler.
48       * @param goAwayReceivedSupplier for the channel.
49       * @param encodeState            for the stream.
50       * @return {@code true} if the frame is valid.
51       */
52      static boolean validateClientWrite(Http3RequestStreamFrame frame, ChannelPromise promise, ChannelHandlerContext ctx,
53                                         BooleanSupplier goAwayReceivedSupplier,
54                                         Http3RequestStreamCodecState encodeState) {
55          if (goAwayReceivedSupplier.getAsBoolean() && !encodeState.started()) {
56              String type = StringUtil.simpleClassName(frame);
57              ReferenceCountUtil.release(frame);
58              promise.setFailure(new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED,
59                      "Frame of type " + type + " unexpected as we received a GOAWAY already."));
60              ctx.close();
61              return false;
62          }
63          if (frame instanceof Http3PushPromiseFrame) {
64              // Only supported on the server.
65              // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-4.1
66              frameTypeUnexpected(promise, frame);
67              return false;
68          }
69          return true;
70      }
71  
72      static long validateHeaderFrameRead(Http3HeadersFrame headersFrame, ChannelHandlerContext ctx,
73                                          Http3RequestStreamCodecState decodeState) {
74          if (headersFrame.headers().contains(HttpHeaderNames.CONNECTION)) {
75              headerUnexpected(ctx, headersFrame, "connection header included");
76              return INVALID_FRAME_READ;
77          }
78          CharSequence value = headersFrame.headers().get(HttpHeaderNames.TE);
79          if (value != null && !HttpHeaderValues.TRAILERS.equals(value)) {
80              headerUnexpected(ctx, headersFrame, "te header field included with invalid value: " + value);
81              return INVALID_FRAME_READ;
82          }
83          if (decodeState.receivedFinalHeaders()) {
84              long length = normalizeAndGetContentLength(
85                      headersFrame.headers().getAll(HttpHeaderNames.CONTENT_LENGTH), false, true);
86              if (length != CONTENT_LENGTH_NOT_MODIFIED) {
87                  headersFrame.headers().setLong(HttpHeaderNames.CONTENT_LENGTH, length);
88              }
89              return length;
90          }
91          return CONTENT_LENGTH_NOT_MODIFIED;
92      }
93  
94      static long validateDataFrameRead(Http3DataFrame dataFrame, ChannelHandlerContext ctx,
95                                        long expectedLength, long seenLength, boolean clientHeadRequest) {
96          try {
97              return verifyContentLength(dataFrame.content().readableBytes(), expectedLength, seenLength, false,
98                      clientHeadRequest);
99          } catch (Http3Exception e) {
100             ReferenceCountUtil.release(dataFrame);
101             failStream(ctx, e);
102             return INVALID_FRAME_READ;
103         }
104     }
105 
106     static boolean validateOnStreamClosure(ChannelHandlerContext ctx, long expectedLength, long seenLength,
107                                            boolean clientHeadRequest) {
108         try {
109             verifyContentLength(0, expectedLength, seenLength, true, clientHeadRequest);
110             return true;
111         } catch (Http3Exception e) {
112             ctx.fireExceptionCaught(e);
113             Http3CodecUtils.streamError(ctx, e.errorCode());
114             return false;
115         }
116     }
117 
118     static void sendStreamAbandonedIfRequired(ChannelHandlerContext ctx, QpackAttributes qpackAttributes,
119                                               QpackDecoder qpackDecoder, Http3RequestStreamCodecState decodeState) {
120         if (!qpackAttributes.dynamicTableDisabled() && !decodeState.terminated()) {
121             final long streamId = ((QuicStreamChannel) ctx.channel()).streamId();
122             if (qpackAttributes.decoderStreamAvailable()) {
123                 qpackDecoder.streamAbandoned(qpackAttributes.decoderStream(), streamId);
124             } else {
125                 qpackAttributes.whenDecoderStreamAvailable(future -> {
126                     if (future.isSuccess()) {
127                         qpackDecoder.streamAbandoned(qpackAttributes.decoderStream(), streamId);
128                     }
129                 });
130             }
131         }
132     }
133 
134     private static void headerUnexpected(ChannelHandlerContext ctx, Http3RequestStreamFrame frame, String msg) {
135         // We should close the stream.
136         // See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1
137         ReferenceCountUtil.release(frame);
138         failStream(ctx, new Http3Exception(H3_MESSAGE_ERROR, msg));
139     }
140 
141     private static void failStream(ChannelHandlerContext ctx, Http3Exception cause) {
142         ctx.fireExceptionCaught(cause);
143         Http3CodecUtils.streamError(ctx, cause.errorCode());
144     }
145 
146     // See https://tools.ietf.org/html/draft-ietf-quic-http-34#section-4.1.3
147     private static long verifyContentLength(int length, long expectedLength, long seenLength, boolean end,
148                                             boolean clientHeadRequest) throws Http3Exception {
149         seenLength += length;
150         if (expectedLength != -1 && (seenLength > expectedLength ||
151                 (!clientHeadRequest && end && seenLength != expectedLength))) {
152             throw new Http3Exception(
153                     H3_MESSAGE_ERROR, "Expected content-length " + expectedLength +
154                     " != " + seenLength + ".");
155         }
156         return seenLength;
157     }
158 }