working decoder that can handle D3 and D4 examples from draft
This commit is contained in:
parent
fdf73adf39
commit
d7fbd89a75
|
@ -26,6 +26,7 @@ import java.util.Map;
|
|||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.hpack.HpackDecoder.Listener;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.util.ArrayQueue;
|
||||
import org.eclipse.jetty.util.ArrayTrie;
|
||||
|
@ -232,16 +233,6 @@ public class HpackContext
|
|||
}
|
||||
}
|
||||
|
||||
public void unuseReferenceSet()
|
||||
{
|
||||
Entry entry = _refSet._refSetNext;
|
||||
while(entry!=_refSet)
|
||||
{
|
||||
entry._used=false;
|
||||
entry=entry._refSetNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void removedUnusedReferences(ByteBuffer buffer)
|
||||
{
|
||||
|
@ -250,7 +241,9 @@ public class HpackContext
|
|||
{
|
||||
Entry next = entry._refSetNext;
|
||||
|
||||
if (!entry.isUsed())
|
||||
if (entry.isUsed())
|
||||
entry._used=false;
|
||||
else
|
||||
{
|
||||
// encode the reference to remove it
|
||||
buffer.put((byte)0x80);
|
||||
|
@ -259,7 +252,20 @@ public class HpackContext
|
|||
}
|
||||
entry=next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void emitUnusedReferences(Listener listener)
|
||||
{
|
||||
Entry entry = _refSet._refSetNext;
|
||||
while(entry!=_refSet)
|
||||
{
|
||||
if (entry.isUsed())
|
||||
entry._used=false;
|
||||
else
|
||||
listener.emit(entry.getHttpField());
|
||||
|
||||
entry=entry._refSetNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -517,4 +523,5 @@ public class HpackContext
|
|||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,9 @@ package org.eclipse.jetty.hpack;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.hpack.HpackContext.Entry;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -36,14 +38,136 @@ public class HpackDecoder
|
|||
void endHeaders();
|
||||
}
|
||||
|
||||
public HpackDecoder(Listener listenr)
|
||||
private final HpackContext _context;
|
||||
private final Listener _listener;
|
||||
|
||||
public HpackDecoder(Listener listener)
|
||||
{
|
||||
|
||||
this(listener,4096);
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buffer)
|
||||
public HpackDecoder(Listener listener,int maxHeaderTableSize)
|
||||
{
|
||||
|
||||
_listener=listener;
|
||||
_context=new HpackContext(maxHeaderTableSize);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void decode(ByteBuffer buffer)
|
||||
{
|
||||
while(buffer.hasRemaining())
|
||||
{
|
||||
byte b = buffer.get();
|
||||
if (b<0)
|
||||
{
|
||||
// indexed
|
||||
int index = NBitInteger.decode(buffer,7);
|
||||
Entry entry=_context.get(index);
|
||||
if (entry.isInReferenceSet())
|
||||
_context.get(index).removeFromRefSet();
|
||||
else if (entry.isStatic())
|
||||
{
|
||||
// emit field
|
||||
_listener.emit(entry.getHttpField());
|
||||
|
||||
// copy and add to reference set if there is room
|
||||
Entry new_entry = _context.add(entry.getHttpField());
|
||||
if (new_entry!=null)
|
||||
_context.addToRefSet(new_entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// emit
|
||||
_listener.emit(entry.getHttpField());
|
||||
// add to reference set
|
||||
_context.addToRefSet(entry);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// look at the first nibble in detail
|
||||
int f=(b&0xF0)>>4;
|
||||
String name;
|
||||
HttpHeader header;
|
||||
String value;
|
||||
|
||||
if (f<=1 || f>=4)
|
||||
{
|
||||
// literal
|
||||
boolean indexed=f>=4;
|
||||
int bits=indexed?6:4;
|
||||
|
||||
// decode the name
|
||||
int name_index=NBitInteger.decode(buffer,bits);
|
||||
if (name_index>0)
|
||||
{
|
||||
Entry name_entry=_context.get(name_index);
|
||||
name=name_entry.getHttpField().getName();
|
||||
header=name_entry.getHttpField().getHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean huffman = (buffer.get()&0x80)==0x80;
|
||||
int length = NBitInteger.decode(buffer,7);
|
||||
if (huffman)
|
||||
name=Huffman.decode(buffer,length);
|
||||
else
|
||||
name=toASCIIString(buffer,length);
|
||||
header=HttpHeader.CACHE.get(name);
|
||||
}
|
||||
|
||||
// decode the value
|
||||
boolean huffman = (buffer.get()&0x80)==0x80;
|
||||
int length = NBitInteger.decode(buffer,7);
|
||||
if (huffman)
|
||||
value=Huffman.decode(buffer,length);
|
||||
else
|
||||
value=toASCIIString(buffer,length);
|
||||
|
||||
// Make the new field
|
||||
HttpField field = new HttpField(header,name,value);
|
||||
|
||||
// emit the field
|
||||
_listener.emit(field);
|
||||
|
||||
// if indexed
|
||||
if (indexed)
|
||||
{
|
||||
// add to header table
|
||||
Entry new_entry=_context.add(field);
|
||||
// and to ref set if there was room in header table
|
||||
if (new_entry!=null)
|
||||
_context.addToRefSet(new_entry);
|
||||
}
|
||||
}
|
||||
else if (f==2)
|
||||
{
|
||||
// change table size
|
||||
int size = NBitInteger.decode(buffer,4);
|
||||
_context.resize(size);
|
||||
}
|
||||
else if (f==3)
|
||||
{
|
||||
// clear reference set
|
||||
_context.clearReferenceSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_context.emitUnusedReferences(_listener);
|
||||
_listener.endHeaders();
|
||||
}
|
||||
|
||||
public static String toASCIIString(ByteBuffer buffer,int length)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
int start=buffer.arrayOffset()+buffer.position();
|
||||
int end=start+length;
|
||||
buffer.position(end);
|
||||
byte[] array=buffer.array();
|
||||
for (int i=start;i<end;i++)
|
||||
builder.append((char)(0x7f&array[i]));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,9 +89,6 @@ public class HpackEncoder
|
|||
|
||||
public void encode(ByteBuffer buffer, HttpFields fields)
|
||||
{
|
||||
// Clear the used bits
|
||||
_context.unuseReferenceSet();
|
||||
|
||||
// Add all the known fields
|
||||
for (HttpField field : fields)
|
||||
{
|
||||
|
|
|
@ -340,18 +340,22 @@ public class Huffman
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static public String decode(ByteBuffer buffer)
|
||||
{
|
||||
return decode(buffer,buffer.remaining());
|
||||
}
|
||||
|
||||
static public String decode(ByteBuffer buffer,int length)
|
||||
{
|
||||
StringBuilder out = new StringBuilder(buffer.remaining()*2);
|
||||
StringBuilder out = new StringBuilder(length*2);
|
||||
int node = 0;
|
||||
int current = 0;
|
||||
int bits = 0;
|
||||
|
||||
byte[] array = buffer.array();
|
||||
int start=buffer.arrayOffset()+buffer.position();
|
||||
int end=start+buffer.remaining();
|
||||
buffer.position(buffer.limit());
|
||||
int end=start+length;
|
||||
buffer.position(end);
|
||||
|
||||
for (int i=start; i<end; i++)
|
||||
{
|
||||
|
|
|
@ -107,13 +107,13 @@ public class NBitInteger
|
|||
}
|
||||
}
|
||||
|
||||
public static int decode(ByteBuffer buf, int n)
|
||||
public static int decode(ByteBuffer buffer, int n)
|
||||
{
|
||||
if (n==8)
|
||||
{
|
||||
int nbits = 0xFF;
|
||||
|
||||
int i=buf.get()&0xff;
|
||||
int i=buffer.get()&0xff;
|
||||
|
||||
if (i == nbits)
|
||||
{
|
||||
|
@ -121,7 +121,7 @@ public class NBitInteger
|
|||
int b;
|
||||
do
|
||||
{
|
||||
b = 0xff&buf.get();
|
||||
b = 0xff&buffer.get();
|
||||
i = i + (b&127) * m;
|
||||
m = m*128;
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ public class NBitInteger
|
|||
|
||||
int nbits = 0xFF >>> (8 - n);
|
||||
|
||||
int i=buf.get(buf.position()-1)&nbits;
|
||||
int i=buffer.get(buffer.position()-1)&nbits;
|
||||
|
||||
if (i == nbits)
|
||||
{
|
||||
|
@ -140,7 +140,7 @@ public class NBitInteger
|
|||
int b;
|
||||
do
|
||||
{
|
||||
b = 0xff&buf.get();
|
||||
b = 0xff&buffer.get();
|
||||
i = i + (b&127) * m;
|
||||
m = m*128;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.hpack;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.hpack.HpackDecoder.Listener;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
public class HpackDecoderTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testDecodeD_3()
|
||||
{
|
||||
final HttpFields fields = new HttpFields();
|
||||
Listener listener = new Listener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void endHeaders()
|
||||
{
|
||||
System.err.println("===");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emit(HttpField field)
|
||||
{
|
||||
System.err.println(field);
|
||||
fields.add(field);
|
||||
}
|
||||
};
|
||||
|
||||
HpackDecoder decoder = new HpackDecoder(listener);
|
||||
|
||||
|
||||
// First request
|
||||
String encoded="828786440f7777772e6578616d706c652e636f6d";
|
||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
|
||||
decoder.decode(buffer);
|
||||
|
||||
assertEquals(4,fields.size());
|
||||
assertEquals("GET",fields.get(":method"));
|
||||
assertEquals("http",fields.get(":scheme"));
|
||||
assertEquals("/",fields.get(":path"));
|
||||
assertEquals("www.example.com",fields.get(":authority"));
|
||||
|
||||
|
||||
// Second request
|
||||
fields.clear();
|
||||
encoded="5c086e6f2d6361636865";
|
||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
|
||||
decoder.decode(buffer);
|
||||
|
||||
assertEquals(5,fields.size());
|
||||
assertEquals("GET",fields.get(":method"));
|
||||
assertEquals("http",fields.get(":scheme"));
|
||||
assertEquals("/",fields.get(":path"));
|
||||
assertEquals("www.example.com",fields.get(":authority"));
|
||||
assertEquals("no-cache",fields.get("cache-control"));
|
||||
|
||||
// Third request
|
||||
fields.clear();
|
||||
encoded="30858c8b84400a637573746f6d2d6b65790c637573746f6d2d76616c7565";
|
||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
|
||||
decoder.decode(buffer);
|
||||
|
||||
assertEquals(5,fields.size());
|
||||
assertEquals("GET",fields.get(":method"));
|
||||
assertEquals("https",fields.get(":scheme"));
|
||||
assertEquals("/index.html",fields.get(":path"));
|
||||
assertEquals("www.example.com",fields.get(":authority"));
|
||||
assertEquals("custom-value",fields.get("custom-key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeD_4()
|
||||
{
|
||||
final HttpFields fields = new HttpFields();
|
||||
Listener listener = new Listener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void endHeaders()
|
||||
{
|
||||
System.err.println("===");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emit(HttpField field)
|
||||
{
|
||||
System.err.println(field);
|
||||
fields.add(field);
|
||||
}
|
||||
};
|
||||
|
||||
HpackDecoder decoder = new HpackDecoder(listener);
|
||||
|
||||
|
||||
// First request
|
||||
String encoded="828786448ce7cf9bebe89b6fb16fa9b6ff";
|
||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
|
||||
decoder.decode(buffer);
|
||||
|
||||
assertEquals(4,fields.size());
|
||||
assertEquals("GET",fields.get(":method"));
|
||||
assertEquals("http",fields.get(":scheme"));
|
||||
assertEquals("/",fields.get(":path"));
|
||||
assertEquals("www.example.com",fields.get(":authority"));
|
||||
|
||||
|
||||
// Second request
|
||||
fields.clear();
|
||||
encoded="5c86b9b9949556bf";
|
||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
|
||||
decoder.decode(buffer);
|
||||
|
||||
assertEquals(5,fields.size());
|
||||
assertEquals("GET",fields.get(":method"));
|
||||
assertEquals("http",fields.get(":scheme"));
|
||||
assertEquals("/",fields.get(":path"));
|
||||
assertEquals("www.example.com",fields.get(":authority"));
|
||||
assertEquals("no-cache",fields.get("cache-control"));
|
||||
|
||||
// Third request
|
||||
fields.clear();
|
||||
encoded="30858c8b844088571c5cdb737b2faf89571c5cdb73724d9c57";
|
||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
|
||||
decoder.decode(buffer);
|
||||
|
||||
assertEquals(5,fields.size());
|
||||
assertEquals("GET",fields.get(":method"));
|
||||
assertEquals("https",fields.get(":scheme"));
|
||||
assertEquals("/index.html",fields.get(":path"));
|
||||
assertEquals("www.example.com",fields.get(":authority"));
|
||||
assertEquals("custom-value",fields.get("custom-key"));
|
||||
}
|
||||
|
||||
}
|
|
@ -518,7 +518,7 @@ public class BufferUtil
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Convert a partial buffer to an ISO-8859-1 String
|
||||
/** Convert a partial buffer to a String
|
||||
* @param buffer The buffer to convert in flush mode. The buffer is unchanged
|
||||
* @param charset The {@link Charset} to use to convert the bytes
|
||||
* @return The buffer as a string.
|
||||
|
|
Loading…
Reference in New Issue