Merged branch 'jetty-9.4.x-2695-hpack-compliance' into 'jetty-9.4.x-2679-h2spec_compliance'.

This commit is contained in:
Simone Bordet 2018-07-19 11:17:49 +02:00
commit 0f4a1744f0
10 changed files with 586 additions and 102 deletions

View File

@ -164,7 +164,8 @@ public class HttpURI
_host=host;
_port=port;
parse(State.PATH,pathQuery,0,pathQuery.length());
if (pathQuery!=null)
parse(State.PATH,pathQuery,0,pathQuery.length());
}

View File

@ -163,17 +163,26 @@ public class MetaData implements Iterable<HttpField>
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
{
this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields);
this(method, new HttpURI(scheme == null ? null : scheme.asString(),
hostPort==null?null:hostPort.getHost(),
hostPort==null?-1:hostPort.getPort(),
uri), version, fields);
}
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength);
this(method, new HttpURI(scheme==null?null:scheme.asString(),
hostPort==null?null:hostPort.getHost(),
hostPort==null?-1:hostPort.getPort(),
uri), version, fields, contentLength);
}
public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(method, new HttpURI(scheme, hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength);
this(method, new HttpURI(scheme,
hostPort==null?null:hostPort.getHost(),
hostPort==null?-1:hostPort.getPort(),
uri), version, fields, contentLength);
}
public Request(Request request)

View File

@ -20,8 +20,11 @@ package org.eclipse.jetty.http2.parser;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackDecoder;
import org.eclipse.jetty.http2.hpack.HpackException.SessionException;
import org.eclipse.jetty.http2.hpack.HpackException.StreamException;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -72,17 +75,48 @@ public class HeaderBlockParser
toDecode = buffer;
}
MetaData result = hpackDecoder.decode(toDecode);
buffer.limit(limit);
if (blockBuffer != null)
try
{
byteBufferPool.release(blockBuffer);
blockBuffer = null;
MetaData metadata = hpackDecoder.decode(toDecode);
if (metadata instanceof MetaData.Request)
{
// TODO this must be an initial HEADERs frame received by the server OR
// TODO a push promise received by the client.
// TODO this must not be a trailers frame
}
else if (metadata instanceof MetaData.Response)
{
// TODO this must be an initial HEADERs frame received by the client
// TODO this must not be a trailers frame
}
else
{
// TODO this must be a trailers frame
}
return metadata;
}
catch(StreamException ex)
{
// TODO reset the stream
throw new BadMessageException("TODO");
}
catch(SessionException ex)
{
// TODO reset the session
throw new BadMessageException("TODO");
}
finally
{
buffer.limit(limit);
return result;
if (blockBuffer != null)
{
byteBufferPool.release(blockBuffer);
blockBuffer = null;
}
}
}
}
}

View File

@ -21,12 +21,12 @@ package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -66,14 +66,14 @@ public class HpackDecoder
_localMaxDynamicTableSize=localMaxdynamciTableSize;
}
public MetaData decode(ByteBuffer buffer)
public MetaData decode(ByteBuffer buffer) throws HpackException.SessionException, HpackException.StreamException
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("CtxTbl[%x] decoding %d octets",_context.hashCode(),buffer.remaining()));
// If the buffer is big, don't even think about decoding it
if (buffer.remaining()>_builder.getMaxSize())
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
throw new HpackException.SessionException("431 Request Header Fields too large");
while(buffer.hasRemaining())
{
@ -92,10 +92,9 @@ public class HpackDecoder
int index = NBitInteger.decode(buffer,7);
Entry entry=_context.get(index);
if (entry==null)
{
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown index "+index);
}
else if (entry.isStatic())
throw new HpackException.SessionException("Unknown index %d",index);
if (entry.isStatic())
{
if (LOG.isDebugEnabled())
LOG.debug("decode IdxStatic {}",entry);
@ -178,7 +177,8 @@ public class HpackDecoder
char c=name.charAt(i);
if (c>='A'&&c<='Z')
{
throw new BadMessageException(400,"Uppercase header name");
_builder.streamException("Uppercase header name %s",name);
break;
}
}
header=HttpHeader.CACHE.get(name);

View File

@ -0,0 +1,65 @@
//
// ========================================================================
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
@SuppressWarnings("serial")
public abstract class HpackException extends Exception
{
HpackException(String messageFormat, Object... args)
{
super(String.format(messageFormat, args));
}
/**
* A Stream HPACK exception.
* <p>Stream exceptions are not fatal to the connection and the
* hpack state is complete and able to continue handling other
* decoding/encoding for the session.
* </p>
*/
public static class StreamException extends HpackException
{
StreamException(String messageFormat, Object... args)
{
super(messageFormat,args);
}
}
/**
* A Session HPACK Exception.
* <p>Session exceptions are fatal for the stream and the HPACK
* state is unable to decode/encode further. </p>
*/
public static class SessionException extends HpackException
{
SessionException(String messageFormat, Object... args)
{
super(messageFormat,args);
}
}
public static class CompressionException extends SessionException
{
public CompressionException(String messageFormat, Object... args)
{
super(messageFormat,args);
}
}
}

View File

@ -20,27 +20,29 @@
package org.eclipse.jetty.http2.hpack;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackException.SessionException;
public class MetaDataBuilder
{
private final int _maxSize;
private int _size;
private int _status;
private int _status=-1;
private String _method;
private HttpScheme _scheme;
private HostPortHttpField _authority;
private String _path;
private long _contentLength=Long.MIN_VALUE;
private HttpFields _fields = new HttpFields(10);
private HpackException.StreamException _streamException;
private boolean _request;
private boolean _response;
/**
* @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters.
@ -66,7 +68,7 @@ public class MetaDataBuilder
return _size;
}
public void emit(HttpField field)
public void emit(HttpField field) throws HpackException.SessionException
{
HttpHeader header = field.getHeader();
String name = field.getName();
@ -74,7 +76,7 @@ public class MetaDataBuilder
int field_size = name.length() + (value == null ? 0 : value.length());
_size+=field_size+32;
if (_size>_maxSize)
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header size "+_size+">"+_maxSize);
throw new HpackException.SessionException("Header Size %d > %d",_size,_maxSize);
if (field instanceof StaticTableHttpField)
{
@ -82,15 +84,21 @@ public class MetaDataBuilder
switch(header)
{
case C_STATUS:
_status=(Integer)staticField.getStaticValue();
if(checkHeader(header, _status))
_status = (Integer)staticField.getStaticValue();
_response = true;
break;
case C_METHOD:
_method=value;
if(checkPseudoHeader(header, _method))
_method = value;
_request = true;
break;
case C_SCHEME:
_scheme = (HttpScheme)staticField.getStaticValue();
if(checkPseudoHeader(header, _scheme))
_scheme = (HttpScheme)staticField.getStaticValue();
_request = true;
break;
default:
@ -102,23 +110,32 @@ public class MetaDataBuilder
switch(header)
{
case C_STATUS:
_status=field.getIntValue();
if(checkHeader(header, _status))
_status = field.getIntValue();
_response = true;
break;
case C_METHOD:
_method=value;
if(checkPseudoHeader(header, _method))
_method = value;
_request = true;
break;
case C_SCHEME:
if (value != null)
if(checkPseudoHeader(header, _scheme) && value != null)
_scheme = HttpScheme.CACHE.get(value);
_request = true;
break;
case C_AUTHORITY:
if (field instanceof HostPortHttpField)
_authority = (HostPortHttpField)field;
else if (value != null)
_authority = new AuthorityHttpField(value);
if(checkPseudoHeader(header, _authority))
{
if (field instanceof HostPortHttpField)
_authority = (HostPortHttpField)field;
else if (value != null)
_authority = new AuthorityHttpField(value);
}
_request = true;
break;
case HOST:
@ -134,54 +151,111 @@ public class MetaDataBuilder
break;
case C_PATH:
_path = value;
if(checkPseudoHeader(header, _path))
_path = value;
_request = true;
break;
case CONTENT_LENGTH:
_contentLength = field.getLongValue();
_fields.add(field);
break;
case TE:
if ("trailers".equalsIgnoreCase(value))
_fields.add(field);
else
streamException("Unsupported TE value %s", value);
break;
case CONNECTION:
// TODO should other connection specific fields be listed here?
streamException("Connection specific field %s", header);
break;
default:
if (name.charAt(0)!=':')
default:
if (name.charAt(0)==':')
streamException("Unknown pseudo header %s", name);
else
_fields.add(field);
break;
}
}
else
{
if (name.charAt(0)!=':')
if (name.charAt(0)==':')
streamException("Unknown pseudo header %s",name);
else
_fields.add(field);
}
}
public MetaData build()
void streamException(String messageFormat, Object... args)
{
HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args);
if (_streamException==null)
_streamException = stream;
else
_streamException.addSuppressed(stream);
}
private boolean checkHeader(HttpHeader header, int value)
{
if (_fields.size()>0)
{
streamException("Pseudo header %s after fields", header.asString());
return false;
}
if (value==-1)
return true;
streamException("Duplicate pseudo header %s", header.asString());
return false;
}
private boolean checkPseudoHeader(HttpHeader header, Object value)
{
if (_fields.size()>0)
{
streamException("Pseudo header %s after fields", header.asString());
return false;
}
if (value==null)
return true;
streamException("Duplicate pseudo header %s", header.asString());
return false;
}
public MetaData build() throws HpackException.StreamException
{
if (_streamException!=null)
throw _streamException;
if (_request && _response)
throw new HpackException.StreamException("Request and Response headers");
HttpFields fields = _fields;
try
{
HttpFields fields = _fields;
_fields = new HttpFields(Math.max(10,fields.size()+5));
if (_method!=null)
if (_request)
return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength);
if (_status!=0)
if (_response)
return new MetaData.Response(HttpVersion.HTTP_2,_status,fields,_contentLength);
if (_path!=null)
fields.put(HttpHeader.C_PATH,_path);
if (_authority!=null)
fields.put(HttpHeader.HOST,_authority.getValue());
return new MetaData(HttpVersion.HTTP_2,fields,_contentLength);
}
finally
{
_status=0;
_fields = new HttpFields(Math.max(10,fields.size()+5));
_request=false;
_response=false;
_status=-1;
_method=null;
_scheme=null;
_authority=null;
_path=null;
_size=0;
_contentLength=Long.MIN_VALUE;
_contentLength=Long.MIN_VALUE;
}
}
@ -189,13 +263,14 @@ public class MetaDataBuilder
* Check that the max size will not be exceeded.
* @param length the length
* @param huffman the huffman name
* @throws SessionException
*/
public void checkSize(int length, boolean huffman)
public void checkSize(int length, boolean huffman) throws SessionException
{
// Apply a huffman fudge factor
if (huffman)
length=(length*4)/3;
if ((_size+length)>_maxSize)
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header size "+(_size+length)+">"+_maxSize);
throw new HpackException.SessionException("Header too large %d > %d", _size+length, _maxSize);
}
}

View File

@ -19,35 +19,29 @@
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackException.StreamException;
import org.eclipse.jetty.util.TypeUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class HpackDecoderTest
{
@Test
public void testDecodeD_3()
public void testDecodeD_3() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096,8192);
@ -95,7 +89,7 @@ public class HpackDecoderTest
}
@Test
public void testDecodeD_4()
public void testDecodeD_4() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096,8192);
@ -128,7 +122,7 @@ public class HpackDecoderTest
}
@Test
public void testDecodeWithArrayOffset()
public void testDecodeWithArrayOffset() throws Exception
{
String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
@ -152,7 +146,7 @@ public class HpackDecoderTest
}
@Test
public void testDecodeHuffmanWithArrayOffset()
public void testDecodeHuffmanWithArrayOffset() throws Exception
{
HpackDecoder decoder = new HpackDecoder(4096,8192);
@ -172,7 +166,7 @@ public class HpackDecoderTest
}
@Test
public void testNghttpx()
public void testNghttpx() throws Exception
{
// Response encoded by nghttpx
String encoded="886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3";
@ -204,7 +198,7 @@ public class HpackDecoderTest
}
@Test
public void testTooBigToIndex()
public void testTooBigToIndex() throws Exception
{
String encoded = "44FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -213,11 +207,11 @@ public class HpackDecoderTest
MetaData metaData = decoder.decode(buffer);
assertThat(decoder.getHpackContext().getDynamicTableSize(),is(0));
assertThat(metaData.getFields().get(HttpHeader.C_PATH),Matchers.startsWith("This is a very large field"));
assertThat(((MetaData.Request)metaData).getURI().toString(),Matchers.startsWith("This is a very large field"));
}
@Test
public void testUnknownIndex()
public void testUnknownIndex() throws Exception
{
String encoded = "BE";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -228,11 +222,325 @@ public class HpackDecoderTest
decoder.decode(buffer);
Assert.fail();
}
catch (BadMessageException e)
catch (HpackException.SessionException e)
{
assertThat(e.getCode(),equalTo(HttpStatus.BAD_REQUEST_400));
assertThat(e.getReason(),Matchers.startsWith("Unknown index"));
assertThat(e.getMessage(),Matchers.startsWith("Unknown index"));
}
}
/* 8.1.2.1. Pseudo-Header Fields */
@Test()
public void test8_1_2_1_PsuedoHeaderFields() throws Exception
{
// 1:Sends a HEADERS frame that contains a unknown pseudo-header field
MetaDataBuilder mdb = new MetaDataBuilder(4096);
mdb.emit(new HttpField(":unknown","value"));
try
{
mdb.build();
Assert.fail();
}
catch(StreamException ex)
{
Assert.assertThat(ex.getMessage(),Matchers.containsString("Unknown pseudo header"));
}
// 2: Sends a HEADERS frame that contains the pseudo-header field defined for response
mdb = new MetaDataBuilder(4096);
mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http"));
mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET"));
mdb.emit(new HttpField(HttpHeader.C_PATH,"/path"));
mdb.emit(new HttpField(HttpHeader.C_STATUS,"100"));
try
{
mdb.build();
Assert.fail();
}
catch(StreamException ex)
{
Assert.assertThat(ex.getMessage(),Matchers.containsString("Request and Response headers"));
}
// 3: Sends a HEADERS frame that contains a pseudo-header field as trailers
// 4: Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field
mdb = new MetaDataBuilder(4096);
mdb.emit(new HttpField(HttpHeader.C_SCHEME,"http"));
mdb.emit(new HttpField(HttpHeader.C_METHOD,"GET"));
mdb.emit(new HttpField(HttpHeader.C_PATH,"/path"));
mdb.emit(new HttpField("Accept","No Compromise"));
mdb.emit(new HttpField(HttpHeader.C_AUTHORITY,"localhost"));
try
{
mdb.build();
Assert.fail();
}
catch(StreamException ex)
{
Assert.assertThat(ex.getMessage(),Matchers.containsString("Pseudo header :authority after fields"));
}
}
/*
*
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
3: Sends a HEADERS frame that contains a pseudo-header field as trailers
× 4: Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
*/
/*
8.1.2.2. Connection-Specific Header Fields
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:33, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 1: Sends a HEADERS frame that contains the connection-specific header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:44, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 2: Sends a HEADERS frame that contains the TE header field with any value other than "trailers"
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
8.1.2.3. Request Pseudo-Header Fields
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:16, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:23, flags:0x04, stream_id:1)
[recv] DATA Frame (length:50, flags:0x01, stream_id:1)
[recv] RST_STREAM Frame (length:4, flags:0x00, stream_id:1)
[recv] Timeout
× 1: Sends a HEADERS frame with empty ":path" pseudo-header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:50, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:13, flags:0x05, stream_id:1)
[recv] Timeout
× 2: Sends a HEADERS frame that omits ":method" pseudo-header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: Timeout
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:14, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:100, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 3: Sends a HEADERS frame that omits ":scheme" pseudo-header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:14, flags:0x05, stream_id:1)
[recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0)
4: Sends a HEADERS frame that omits ":path" pseudo-header field
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:16, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 5: Sends a HEADERS frame with duplicated ":method" pseudo-header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:16, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 6: Sends a HEADERS frame with duplicated ":scheme" pseudo-header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:18, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:79, flags:0x05, stream_id:1)
[recv] Timeout
× 7: Sends a HEADERS frame with duplicated ":method" pseudo-header field
-> The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: HEADERS Frame (length:79, flags:0x05, stream_id:1)
8.1.2.6. Malformed Requests and Responses
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:18, flags:0x04, stream_id:1)
[send] DATA Frame (length:4, flags:0x01, stream_id:1)
[recv] HEADERS Frame (length:100, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 1: Sends a HEADERS frame with the "content-length" header field which does not equal the DATA frame payload length
-> The endpoint MUST treat this as a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:18, flags:0x04, stream_id:1)
[send] DATA Frame (length:4, flags:0x00, stream_id:1)
[send] DATA Frame (length:4, flags:0x01, stream_id:1)
[recv] HEADERS Frame (length:100, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 2: Sends a HEADERS frame with the "content-length" header field which does not equal the sum of the multiple DATA frames payload length
-> The endpoint MUST treat this as a stream error of type PROTOCOL_ERROR.
Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR)
RST_STREAM Frame (Error Code: PROTOCOL_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
8.2. Server Push
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] PUSH_PROMISE Frame (length:19, flags:0x04, stream_id:1)
[recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0)
1: Sends a PUSH_PROMISE frame
HPACK: Header Compression for HTTP/2
2. Compression Process Overview
2.3. Indexing Tables
2.3.3. Index Address Space
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:16, flags:0x05, stream_id:1)
[recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0)
[recv] Connection closed
1: Sends a header field representation with invalid index
4. Dynamic Table Management
4.2. Maximum Table Size
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:16, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 1: Sends a dynamic table size update at the end of header block
-> The endpoint MUST treat this as a decoding error.
Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
5. Primitive Type Representations
5.2. String Literal Representation
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:27, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits
-> The endpoint MUST treat this as a decoding error.
Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:26, flags:0x05, stream_id:1)
[recv] HEADERS Frame (length:101, flags:0x04, stream_id:1)
[recv] DATA Frame (length:687, flags:0x01, stream_id:1)
[recv] Timeout
× 2: Sends a Huffman-encoded string literal representation padded by zero
-> The endpoint MUST treat this as a decoding error.
Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR)
Connection closed
Actual: DATA Frame (length:687, flags:0x01, stream_id:1)
[send] SETTINGS Frame (length:6, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0)
[send] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0)
[recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0)
[send] HEADERS Frame (length:28, flags:0x05, stream_id:1)
[recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0)
[recv] Connection closed
3: Sends a Huffman-encoded string literal representation containing the EOS symbol
*/
}

View File

@ -18,15 +18,18 @@
package org.eclipse.jetty.http2.hpack;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MetaData.Response;
@ -35,10 +38,6 @@ import org.eclipse.jetty.util.BufferUtil;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class HpackTest
{
final static HttpField ServerJetty = new PreEncodedHttpField(HttpHeader.SERVER,"jetty");
@ -46,7 +45,7 @@ public class HpackTest
final static HttpField Date = new PreEncodedHttpField(HttpHeader.DATE,DateGenerator.formatDate(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())));
@Test
public void encodeDecodeResponseTest()
public void encodeDecodeResponseTest() throws Exception
{
HpackEncoder encoder = new HpackEncoder();
HpackDecoder decoder = new HpackDecoder(4096,8192);
@ -99,7 +98,7 @@ public class HpackTest
}
@Test
public void encodeDecodeTooLargeTest()
public void encodeDecodeTooLargeTest() throws Exception
{
HpackEncoder encoder = new HpackEncoder();
HpackDecoder decoder = new HpackDecoder(4096,164);
@ -131,14 +130,14 @@ public class HpackTest
decoder.decode(buffer);
Assert.fail();
}
catch(BadMessageException e)
catch(HpackException.SessionException e)
{
assertEquals(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,e.getCode());
assertThat(e.getMessage(),containsString("Header too large"));
}
}
@Test
public void evictReferencedFieldTest()
public void evictReferencedFieldTest() throws Exception
{
HpackEncoder encoder = new HpackEncoder(200,200);
HpackDecoder decoder = new HpackDecoder(200,1024);

View File

@ -1,3 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.http2.LEVEL=INFO
org.eclipse.jetty.http2.hpack.LEVEL=INFO
org.eclipse.jetty.http2.hpack.LEVEL=DEBUG

View File

@ -1,7 +0,0 @@
body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em}
h3 {font-size:100%; letter-spacing: 0.1em;}
span.pass { color: green; }
span.fail { color:red; }