Merged branch 'jetty-9.4.x-2695-hpack-compliance' into 'jetty-9.4.x-2679-h2spec_compliance'.
This commit is contained in:
commit
0f4a1744f0
|
@ -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());
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
Loading…
Reference in New Issue