Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ public final class HPackDecoder {
private final CharsetDecoder charsetDecoder;
private CharBuffer tmpBuf;
private int maxTableSize;
private int maxListSize;

HPackDecoder(final InboundDynamicTable dynamicTable, final CharsetDecoder charsetDecoder) {
this.dynamicTable = dynamicTable != null ? dynamicTable : new InboundDynamicTable();
this.contentBuf = new ByteArrayBuffer(256);
this.charsetDecoder = charsetDecoder;
this.maxTableSize = dynamicTable != null ? dynamicTable.getMaxSize() : Integer.MAX_VALUE;
this.maxListSize = Integer.MAX_VALUE;
}

HPackDecoder(final InboundDynamicTable dynamicTable, final Charset charset) {
Expand Down Expand Up @@ -221,7 +224,7 @@ int decodeString(final ByteBuffer src, final StringBuilder buf) throws HPackExce
return binaryLen;
}

Header decodeLiteralHeader(
HPackHeader decodeLiteralHeader(
final ByteBuffer src,
final HPackRepresentation representation) throws HPackException, CharacterCodingException {

Expand All @@ -248,20 +251,25 @@ Header decodeLiteralHeader(
if (representation == HPackRepresentation.WITH_INDEXING) {
this.dynamicTable.add(header);
}
return new BasicHeader(header.getName(), header.getValue(), header.isSensitive());
return header;
}

Header decodeIndexedHeader(final ByteBuffer src) throws HPackException, CharacterCodingException {
HPackHeader decodeIndexedHeader(final ByteBuffer src) throws HPackException {

final int index = decodeInt(src, 7);
final Header existing = this.dynamicTable.getHeader(index);
final HPackHeader existing = this.dynamicTable.getHeader(index);
if (existing == null) {
throw new HPackException("Invalid header index");
}
return existing;
}

public Header decodeHeader(final ByteBuffer src) throws HPackException {
final HPackHeader header = decodeHPackHeader(src);
return header != null ? new BasicHeader(header.getName(), header.getValue(), header.isSensitive()) : null;
}

HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException {
try {
while (src.hasRemaining()) {
final int b = peekByte(src);
Expand All @@ -287,14 +295,22 @@ public Header decodeHeader(final ByteBuffer src) throws HPackException {
}

public List<Header> decodeHeaders(final ByteBuffer src) throws HPackException {
final boolean enforceSizeLimit = maxListSize < Integer.MAX_VALUE;
int listSize = 0;

final List<Header> list = new ArrayList<>();
while (src.hasRemaining()) {
final Header header = decodeHeader(src);
final HPackHeader header = decodeHPackHeader(src);
if (header == null) {
break;
}
list.add(header);
if (enforceSizeLimit) {
listSize += header.getTotalSize();
if (listSize >= maxListSize) {
throw new HeaderListConstraintException("Maximum header list size exceeded");
}
}
list.add(new BasicHeader(header.getName(), header.getValue(), header.isSensitive()));
}
return list;
}
Expand All @@ -309,4 +325,13 @@ public void setMaxTableSize(final int maxTableSize) {
this.dynamicTable.setMaxSize(maxTableSize);
}

public int getMaxListSize() {
return maxListSize;
}

public void setMaxListSize(final int maxListSize) {
Args.notNegative(maxListSize, "Max list size");
this.maxListSize = maxListSize;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/

package org.apache.hc.core5.http2.hpack;

/**
* Signals a header list constraint violation.
*
* @since 5.0
*/
public class HeaderListConstraintException extends HPackException {

/**
* Creates a HeaderListConstraintException with the specified detail message.
*
* @param message The exception detail message
*/
public HeaderListConstraintException(final String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ enum SettingsHandshake { READY, TRANSMITTED, ACKED }

this.hPackDecoder.setMaxTableSize(H2Config.INIT.getHeaderTableSize());
this.hPackEncoder.setMaxTableSize(H2Config.INIT.getHeaderTableSize());
this.hPackDecoder.setMaxListSize(H2Config.INIT.getMaxHeaderListSize());

this.lowMark = H2Config.INIT.getInitialWindowSize() / 2;
this.streamListener = streamListener;
Expand Down Expand Up @@ -777,6 +778,8 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
stream.localReset(ex);
} catch (final HttpStreamResetException ex) {
stream.localReset(ex, ex.getCause() != null ? H2Error.INTERNAL_ERROR : H2Error.CANCEL);
} catch (final HttpException ex) {
stream.handle(ex);
}

if (stream.isTerminated()) {
Expand Down Expand Up @@ -1043,6 +1046,10 @@ private void consumePushPromiseFrame(final RawFrame frame, final ByteBuffer payl
}
}

List<Header> decodeHeaders(final ByteBuffer payload) throws HttpException {
return hPackDecoder.decodeHeaders(payload);
}

private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) throws HttpException, IOException {
final int streamId = stream.getId();
if (!frame.isFlagSet(FrameFlag.END_HEADERS)) {
Expand All @@ -1055,7 +1062,7 @@ private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) thr
payload.get();
}
if (continuation == null) {
final List<Header> headers = hPackDecoder.decodeHeaders(payload);
final List<Header> headers = decodeHeaders(payload);
if (stream.isRemoteInitiated() && streamId > processedRemoteStreamId) {
processedRemoteStreamId = streamId;
}
Expand Down Expand Up @@ -1085,7 +1092,7 @@ private void consumeContinuationFrame(final RawFrame frame, final H2Stream strea
final ByteBuffer payload = frame.getPayload();
continuation.copyPayload(payload);
if (frame.isFlagSet(FrameFlag.END_HEADERS)) {
final List<Header> headers = hPackDecoder.decodeHeaders(continuation.getContent());
final List<Header> headers = decodeHeaders(continuation.getContent());
if (stream.isRemoteInitiated() && streamId > processedRemoteStreamId) {
processedRemoteStreamId = streamId;
}
Expand Down Expand Up @@ -1208,6 +1215,7 @@ private void applyRemoteSettings(final H2Config config) throws H2ConnectionExcep

private void applyLocalSettings() throws H2ConnectionException {
hPackDecoder.setMaxTableSize(localConfig.getHeaderTableSize());
hPackDecoder.setMaxListSize(localConfig.getMaxHeaderListSize());

final int delta = localConfig.getInitialWindowSize() - initInputWinSize;
initInputWinSize = localConfig.getInitialWindowSize();
Expand Down Expand Up @@ -1529,7 +1537,7 @@ public String toString() {

}

private static class H2Stream {
static class H2Stream {

private final H2StreamChannelImpl channel;
private final H2StreamHandler handler;
Expand Down Expand Up @@ -1642,6 +1650,10 @@ void localReset(final H2StreamResetException ex) throws IOException {
localReset(ex, ex.getCode());
}

void handle(final HttpException ex) throws IOException, HttpException {
handler.handle(ex, channel.isRemoteClosed());
}

HandlerFactory<AsyncPushConsumer> getPushHandlerFactory() {
return handler.getPushHandlerFactory();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ public void consumeData(final ByteBuffer src, final boolean endStream) throws Ht
}
}

@Override
public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
throw ex;
}

@Override
public void failed(final Exception cause) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ public void failed(final Exception cause) {
}
}

@Override
public void handle(final HttpException ex, final boolean endStream) throws HttpException {
throw ex;
}

@Override
public void releaseResources() {
if (done.compareAndSet(false, true)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ interface H2StreamHandler extends ResourceHolder {

void failed(Exception cause);

void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@
import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter;
import org.apache.hc.core5.util.Asserts;

public class ServerH2StreamHandler implements H2StreamHandler {
class ServerH2StreamHandler implements H2StreamHandler {

private final H2StreamChannel outputChannel;
private final DataStreamChannel dataChannel;
private final ResponseChannel responseChannel;
private final HttpProcessor httpProcessor;
private final BasicHttpConnectionMetrics connMetrics;
private final HandlerFactory<AsyncServerExchangeHandler> exchangeHandlerFactory;
Expand Down Expand Up @@ -111,6 +112,27 @@ public void endStream() throws IOException {
responseState = MessageState.COMPLETE;
}

};
this.responseChannel = new ResponseChannel() {

@Override
public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException {
commitInformation(response);
}

@Override
public void sendResponse(
final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException {
ServerSupport.validateResponse(response, responseEntityDetails);
commitResponse(response, responseEntityDetails);
}

@Override
public void pushPromise(
final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException {
commitPromise(promise, pushProducer);
}

};
this.httpProcessor = httpProcessor;
this.connMetrics = connMetrics;
Expand Down Expand Up @@ -154,9 +176,8 @@ private void commitResponse(

final List<Header> responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response);

Asserts.notNull(receivedRequest, "Received request");
final String method = receivedRequest.getMethod();
final boolean endStream = responseEntityDetails == null || Method.HEAD.isSame(method);
final boolean endStream = responseEntityDetails == null ||
(receivedRequest != null && Method.HEAD.isSame(receivedRequest.getMethod()));
outputChannel.submit(responseHeaders, endStream);
connMetrics.incrementResponseCount();
if (responseEntityDetails == null) {
Expand Down Expand Up @@ -212,28 +233,6 @@ public void consumeHeader(final List<Header> headers, final boolean endStream) t
context.setProtocolVersion(HttpVersion.HTTP_2);
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);

final ResponseChannel responseChannel = new ResponseChannel() {

@Override
public void sendInformation(final HttpResponse response, final HttpContext httpContext) throws HttpException, IOException {
commitInformation(response);
}

@Override
public void sendResponse(
final HttpResponse response, final EntityDetails responseEntityDetails, final HttpContext httpContext) throws HttpException, IOException {
ServerSupport.validateResponse(response, responseEntityDetails);
commitResponse(response, responseEntityDetails);
}

@Override
public void pushPromise(
final HttpRequest promise, final AsyncPushProducer pushProducer, final HttpContext httpContext) throws HttpException, IOException {
commitPromise(promise, pushProducer);
}

};

try {
httpProcessor.process(request, requestEntityDetails, context);
connMetrics.incrementRequestCount();
Expand Down Expand Up @@ -295,6 +294,31 @@ public void produceOutput() throws HttpException, IOException {
}
}

@Override
public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
if (done.get()) {
throw ex;
}
switch (requestState) {
case HEADERS:
requestState = endStream ? MessageState.COMPLETE : MessageState.BODY;
if (!responseCommitted.get()) {
final AsyncResponseProducer responseProducer = new BasicResponseProducer(
ServerSupport.toStatusCode(ex),
ServerSupport.toErrorMessage(ex));
exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer);
exchangeHandler.handleRequest(null, null, responseChannel, context);
} else {
throw ex;
}
break;
case BODY:
responseState = MessageState.COMPLETE;
default:
throw ex;
}
}

@Override
public void failed(final Exception cause) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@
package org.apache.hc.core5.http2.impl.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;

import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException;
import org.apache.hc.core5.http.config.CharCodingConfig;
import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
import org.apache.hc.core5.http.nio.AsyncPushConsumer;
Expand All @@ -43,6 +48,7 @@
import org.apache.hc.core5.http2.frame.DefaultFrameFactory;
import org.apache.hc.core5.http2.frame.FrameFactory;
import org.apache.hc.core5.http2.frame.StreamIdGenerator;
import org.apache.hc.core5.http2.hpack.HeaderListConstraintException;
import org.apache.hc.core5.reactor.ProtocolIOSession;
import org.apache.hc.core5.util.Args;

Expand Down Expand Up @@ -113,6 +119,15 @@ H2StreamHandler createLocallyInitiatedStream(
throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Illegal attempt to execute a request");
}

@Override
List<Header> decodeHeaders(final ByteBuffer payload) throws HttpException {
try {
return super.decodeHeaders(payload);
} catch (final HeaderListConstraintException ex) {
throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex);
}
}

@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
Expand Down
Loading