HpackDecoder { 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 (buffer.remaining() > builder.getMaxSize()) throw new HpackException.SessionException("431 Request Header Fields too large"); boolean emitted = false; while (buffer.hasRemaining()) { if (LOG.isDebugEnabled() && buffer.hasArray()) { int l = Math.min(buffer.remaining(), 32); LOG.debug("decode {}{}", TypeUtils.toHexString(buffer.array(), buffer.arrayOffset() + buffer.position(), l), l < buffer.remaining() ? "..." : ""); } byte b = buffer.get(); if (b < 0) { int index = NBitInteger.decode(buffer, 7); Entry entry = context.get(index); if (entry == null) throw new HpackException.SessionException("Unknown index %d", index); if (entry.isStatic()) { if (LOG.isDebugEnabled()) LOG.debug("decode IdxStatic {}", entry); emitted = true; builder.emit(entry.getHttpField()); } else { if (LOG.isDebugEnabled()) LOG.debug("decode Idx {}", entry); emitted = true; builder.emit(entry.getHttpField()); } } else { byte f = (byte) ((b & 0xF0) >> 4); String name; HttpHeader header; String value; boolean indexed; int nameIndex; switch (f) { case 2: case 3: int size = NBitInteger.decode(buffer, 5); if (LOG.isDebugEnabled()) LOG.debug("decode resize=" + size); if (size > localMaxDynamicTableSize) throw new IllegalArgumentException(); if (emitted) throw new HpackException.CompressionException("Dynamic table resize after fields"); context.resize(size); continue; case 0: case 1: indexed = false; nameIndex = NBitInteger.decode(buffer, 4); break; case 4: case 5: case 6: case 7: indexed = true; nameIndex = NBitInteger.decode(buffer, 6); break; default: throw new IllegalStateException(); } boolean huffmanName = false; if (nameIndex > 0) { Entry nameEntry = context.get(nameIndex); name = nameEntry.getHttpField().getName(); header = nameEntry.getHttpField().getHeader(); } else { huffmanName = (buffer.get() & 0x80) == 0x80; int length = NBitInteger.decode(buffer, 7); builder.checkSize(length, huffmanName); if (huffmanName) name = Huffman.decode(buffer, length); else name = toASCIIString(buffer, length); check: for (int i = name.length(); i-- > 0; ) { char c = name.charAt(i); if (c > 0xff) { builder.streamException("Illegal header name %s", name); break; } HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c]; switch (token.getType()) { case ALPHA: if (c >= 'A' && c <= 'Z') { builder.streamException("Uppercase header name %s", name); break check; } break; case COLON: case TCHAR: case DIGIT: break; default: builder.streamException("Illegal header name %s", name); break check; } } header = HttpHeader.CACHE.get(name); } boolean huffmanValue = (buffer.get() & 0x80) == 0x80; int length = NBitInteger.decode(buffer, 7); builder.checkSize(length, huffmanValue); if (huffmanValue) value = Huffman.decode(buffer, length); else value = toASCIIString(buffer, length); HttpField field; if (header == null) { field = new HttpField(null, name, value); } else { switch (header) { case C_STATUS: if (indexed) field = new HttpField.IntValueHttpField(header, name, value); else field = new HttpField(header, name, value); break; case C_AUTHORITY: field = new AuthorityHttpField(value); break; case CONTENT_LENGTH: if ("0".equals(value)) field = CONTENT_LENGTH_0; else field = new HttpField.LongValueHttpField(header, name, value); break; default: field = new HttpField(header, name, value); break; } } if (LOG.isDebugEnabled()) { LOG.debug("decoded '{}' by {}/{}/{}", field, nameIndex > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"), huffmanValue ? "HuffVal" : "LitVal", indexed ? "Idx" : ""); } emitted = true; builder.emit(field); if (indexed) context.add(field); } } return builder.build(); } HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize); HpackContext getHpackContext(); void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize); MetaData decode(ByteBuffer buffer); static String toASCIIString(ByteBuffer buffer, int length); @Override String toString(); static final Logger LOG; static final HttpField.LongValueHttpField CONTENT_LENGTH_0; }
@Test void testDecodeD_3() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "828684410f7777772e6578616d706c652e636f6d"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); MetaData.Request request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); assertFalse(request.iterator().hasNext()); encoded = "828684be58086e6f2d6361636865"; buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); Iterator<HttpField> iterator = request.iterator(); assertTrue(iterator.hasNext()); assertEquals(new HttpField("cache-control", "no-cache"), iterator.next()); assertFalse(iterator.hasNext()); encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565"; buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTPS.getValue(), request.getURI().getScheme()); assertEquals("/index.html", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); iterator = request.iterator(); assertTrue(iterator.hasNext()); assertEquals(new HttpField("custom-key", "custom-value"), iterator.next()); assertFalse(iterator.hasNext()); }
@Test void testDecodeD_4() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); MetaData.Request request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); assertFalse(request.iterator().hasNext()); encoded = "828684be5886a8eb10649cbf"; buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); Iterator<HttpField> iterator = request.iterator(); assertTrue(iterator.hasNext()); assertEquals(new HttpField("cache-control", "no-cache"), iterator.next()); assertFalse(iterator.hasNext()); }
@Test void testDecodeWithArrayOffset() throws Exception { String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d"; byte[] bytes = TypeUtils.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, array, 1, bytes.length); ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); MetaData.Request request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); assertEquals(1, request.getFields().size()); HttpField field = request.iterator().next(); assertEquals(HttpHeader.AUTHORIZATION, field.getHeader()); assertEquals(value, field.getValue()); }
@Test void testDecodeHuffmanWithArrayOffset() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84"; byte[] bytes = TypeUtils.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, array, 1, bytes.length); ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); MetaData.Request request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("www.example.com", request.getURI().getHost()); assertFalse(request.iterator().hasNext()); }
@Test void testNghttpx() throws Exception { String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); MetaData.Response response = (MetaData.Response) decoder.decode(buffer); assertEquals(200, response.getStatus()); assertEquals(6, response.getFields().size()); assertTrue(response.getFields().contains(new HttpField(HttpHeader.DATE, "Fri, 15 Jul 2016 02:36:20 GMT"))); assertTrue(response.getFields().contains(new HttpField(HttpHeader.CONTENT_TYPE, "text/html"))); assertTrue(response.getFields().contains(new HttpField(HttpHeader.CONTENT_ENCODING, ""))); assertTrue(response.getFields().contains(new HttpField(HttpHeader.CONTENT_LENGTH, "42"))); assertTrue(response.getFields().contains(new HttpField(HttpHeader.SERVER, "nghttpx nghttp2/1.12.0"))); assertTrue(response.getFields().contains(new HttpField(HttpHeader.VIA, "1.1 nghttpx"))); }
@Test void testBadResize() throws Exception { String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); try { decoder.decode(buffer); fail(); } catch (CompressionException e) { assertTrue(e.getMessage().contains("Dynamic table resize after fields")); } }
@Test void testUnknownIndex() throws Exception { String encoded = "BE"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(128, 8192); try { decoder.decode(buffer); fail(); } catch (HpackException.SessionException e) { assertTrue(e.getMessage().startsWith("Unknown index")); } }
@Test void testHuffmanEncodedStandard() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "83" + "49509F"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); MetaData.Request request = (MetaData.Request) decoder.decode(buffer); assertEquals("GET", request.getMethod()); assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); assertEquals("/", request.getURI().getPath()); assertEquals("test", request.getURI().getHost()); assertFalse(request.iterator().hasNext()); }
@Test void testHuffmanEncodedExtraPadding() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "84" + "49509FFF"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); try { decoder.decode(buffer); fail(); } catch (CompressionException ex) { assertTrue(ex.getMessage().contains("Bad termination")); } }
@Test void testHuffmanEncodedZeroPadding() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "83" + "495090"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); try { decoder.decode(buffer); fail(); } catch (CompressionException ex) { assertTrue(ex.getMessage().contains("Incorrect padding")); } }
@Test void testHuffmanEncodedWithEOS() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "87" + "497FFFFFFF427F"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); try { decoder.decode(buffer); fail(); } catch (CompressionException ex) { assertTrue(ex.getMessage().contains("EOS in content")); } }
@Test void testHuffmanEncodedOneIncompleteOctet() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "81" + "FE"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); try { decoder.decode(buffer); fail(); } catch (CompressionException ex) { assertTrue(ex.getMessage().contains("Bad termination")); } }
@Test void testHuffmanEncodedTwoIncompleteOctet() throws Exception { HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "82" + "FFFE"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); try { decoder.decode(buffer); fail(); } catch (CompressionException ex) { assertTrue(ex.getMessage().contains("Bad termination")); } }