diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java index b09accbb18e..af3e12e678e 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -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()); } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java index 75551f76ede..decab4d73bd 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java @@ -163,17 +163,26 @@ public class MetaData implements Iterable 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) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java index f9a048d8471..922388be2a1 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java @@ -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; + } + } } } } diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index 28cb913c34f..74b8b87deb9 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -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); diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java new file mode 100644 index 00000000000..78516c73bd2 --- /dev/null +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java @@ -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. + *

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. + *

+ */ + public static class StreamException extends HpackException + { + StreamException(String messageFormat, Object... args) + { + super(messageFormat,args); + } + } + + /** + * A Session HPACK Exception. + *

Session exceptions are fatal for the stream and the HPACK + * state is unable to decode/encode further.

+ */ + 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); + } + } +} diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java index b9e72e7352f..3a01e8a593a 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java @@ -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); } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index d6cb341aca9..1e74d100fd7 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -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 + + */ + } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java index 31c1ee21d8f..2f3f461de31 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackTest.java @@ -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); diff --git a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties b/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties index e40e8e43ce1..d33a7c32778 100644 --- a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties +++ b/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties @@ -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 diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ deleted file mode 100644 index def6847d14c..00000000000 --- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css~ +++ /dev/null @@ -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; }