fixed hpack literal encoding bug
This commit is contained in:
parent
ad034f4d54
commit
ab5461d73e
|
@ -30,6 +30,7 @@ import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
import org.eclipse.jetty.util.ArrayQueue;
|
import org.eclipse.jetty.util.ArrayQueue;
|
||||||
|
import org.eclipse.jetty.util.ArrayTernaryTrie;
|
||||||
import org.eclipse.jetty.util.ArrayTrie;
|
import org.eclipse.jetty.util.ArrayTrie;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.Trie;
|
import org.eclipse.jetty.util.Trie;
|
||||||
|
@ -104,7 +105,7 @@ public class HpackContext
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
|
private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
|
||||||
private static final Trie<Entry> __staticNameMap = new ArrayTrie<>(24);
|
private static final Trie<Entry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
|
||||||
|
|
||||||
private static final Entry[] __staticTable=new Entry[STATIC_TABLE.length];
|
private static final Entry[] __staticTable=new Entry[STATIC_TABLE.length];
|
||||||
static
|
static
|
||||||
|
@ -145,10 +146,13 @@ public class HpackContext
|
||||||
|
|
||||||
if (entry._field.getValue()!=null)
|
if (entry._field.getValue()!=null)
|
||||||
__staticFieldMap.put(entry._field,entry);
|
__staticFieldMap.put(entry._field,entry);
|
||||||
|
|
||||||
if (!added.contains(entry._field.getName()))
|
if (!added.contains(entry._field.getName()))
|
||||||
{
|
{
|
||||||
added.add(entry._field.getName());
|
added.add(entry._field.getName());
|
||||||
__staticNameMap.put(entry._field.getName(),entry);
|
__staticNameMap.put(entry._field.getName(),entry);
|
||||||
|
if (__staticNameMap.get(entry._field.getName())==null)
|
||||||
|
throw new IllegalStateException("name trie too small");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool.Lease;
|
import org.eclipse.jetty.io.ByteBufferPool.Lease;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
|
||||||
public class HpackEncoder
|
public class HpackEncoder
|
||||||
{
|
{
|
||||||
|
@ -130,6 +132,13 @@ public class HpackEncoder
|
||||||
|
|
||||||
private void encode(ByteBuffer buffer, HttpField field)
|
private void encode(ByteBuffer buffer, HttpField field)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
System.err.println("encode "+field);
|
||||||
|
int p=buffer.position();
|
||||||
|
try{
|
||||||
|
*/
|
||||||
|
|
||||||
// TODO currently we do not check if there is enough space, so we will always
|
// TODO currently we do not check if there is enough space, so we will always
|
||||||
// return true or fail nastily.
|
// return true or fail nastily.
|
||||||
|
|
||||||
|
@ -142,6 +151,7 @@ public class HpackEncoder
|
||||||
if (entry.isInReferenceSet())
|
if (entry.isInReferenceSet())
|
||||||
{
|
{
|
||||||
entry.used();
|
entry.used();
|
||||||
|
// System.err.println("In Reference Set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,9 +173,12 @@ public class HpackEncoder
|
||||||
// Add the value
|
// Add the value
|
||||||
buffer.put(entry.getStaticHuffmanValue());
|
buffer.put(entry.getStaticHuffmanValue());
|
||||||
|
|
||||||
|
// System.err.println("Literal without Indexing, indexed name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// System.err.println("Indexed from Header Set");
|
||||||
|
|
||||||
// So we can add the entry to the reference Set and emit the index;
|
// So we can add the entry to the reference Set and emit the index;
|
||||||
_context.addToRefSet(entry);
|
_context.addToRefSet(entry);
|
||||||
int index=_context.index(entry);
|
int index=_context.index(entry);
|
||||||
|
@ -200,7 +213,7 @@ public class HpackEncoder
|
||||||
{
|
{
|
||||||
reference=true;
|
reference=true;
|
||||||
never_index=false;
|
never_index=false;
|
||||||
huffman=__DO_NOT_HUFFMAN.contains(header);
|
huffman=!__DO_NOT_HUFFMAN.contains(header);
|
||||||
name_bits = 6;
|
name_bits = 6;
|
||||||
mask=(byte)0x40;
|
mask=(byte)0x40;
|
||||||
}
|
}
|
||||||
|
@ -208,17 +221,19 @@ public class HpackEncoder
|
||||||
{
|
{
|
||||||
reference=false;
|
reference=false;
|
||||||
never_index=__NEVER_INDEX.contains(header);
|
never_index=__NEVER_INDEX.contains(header);
|
||||||
huffman=__DO_NOT_HUFFMAN.contains(header);
|
huffman=!__DO_NOT_HUFFMAN.contains(header);
|
||||||
name_bits = 4;
|
name_bits = 4;
|
||||||
mask=never_index?(byte)0x01:(byte)0x00;
|
mask=never_index?(byte)0x01:(byte)0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Add the mask bits
|
// Add the mask bits
|
||||||
buffer.put(mask);
|
buffer.put(mask);
|
||||||
|
|
||||||
// Look for a name Index
|
// Look for a name Index
|
||||||
Entry name_entry = _context.get(field.getName());
|
Entry name_entry = _context.get(field.getName());
|
||||||
|
|
||||||
|
// System.err.printf("Literal huff=%b refed=%b neverIdx=%b nameIdx=%b b=%d m=0x%02x%n",huffman,reference,never_index,name_entry!=null,name_bits,mask);
|
||||||
|
|
||||||
if (name_entry!=null)
|
if (name_entry!=null)
|
||||||
NBitInteger.encode(buffer,name_bits,_context.index(name_entry));
|
NBitInteger.encode(buffer,name_bits,_context.index(name_entry));
|
||||||
else
|
else
|
||||||
|
@ -241,7 +256,7 @@ public class HpackEncoder
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// add literal assuming iso_8859_1
|
// add literal assuming iso_8859_1
|
||||||
buffer.put((byte)0x80);
|
buffer.put((byte)0x00);
|
||||||
NBitInteger.encode(buffer,7,value.length());
|
NBitInteger.encode(buffer,7,value.length());
|
||||||
for (int i=0;i<value.length();i++)
|
for (int i=0;i<value.length();i++)
|
||||||
{
|
{
|
||||||
|
@ -262,6 +277,15 @@ public class HpackEncoder
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
int e=buffer.position();
|
||||||
|
System.err.println("'"+TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p)+"'");
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.eclipse.jetty.http2.hpack;
|
package org.eclipse.jetty.http2.hpack;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -67,6 +68,37 @@ public class MetaData implements Iterable<HttpField>
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (!(o instanceof MetaData))
|
||||||
|
return false;
|
||||||
|
MetaData m = (MetaData)o;
|
||||||
|
|
||||||
|
List<HttpField> lm=m.getFields();
|
||||||
|
int s=0;
|
||||||
|
for (HttpField field: this)
|
||||||
|
{
|
||||||
|
s++;
|
||||||
|
if (!lm.contains(field))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s!=lm.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
for (HttpField field: this)
|
||||||
|
out.append(field).append('\n');
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------------------- */
|
/* -------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------- */
|
/* -------------------------------------------------------- */
|
||||||
|
@ -132,6 +164,27 @@ public class MetaData implements Iterable<HttpField>
|
||||||
{
|
{
|
||||||
return _path;
|
return _path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (!(o instanceof Request))
|
||||||
|
return false;
|
||||||
|
Request r = (Request)o;
|
||||||
|
if (!_method.equals(r._method) ||
|
||||||
|
!_scheme.equals(r._scheme) ||
|
||||||
|
!_authority.equals(r._authority) ||
|
||||||
|
!_path.equals(r._path))
|
||||||
|
return false;
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return _method+" "+_scheme+"://"+_authority+_path+" HTTP/2\n"+super.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------- */
|
/* -------------------------------------------------------- */
|
||||||
|
@ -169,5 +222,21 @@ public class MetaData implements Iterable<HttpField>
|
||||||
{
|
{
|
||||||
return _status;
|
return _status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (!(o instanceof Response))
|
||||||
|
return false;
|
||||||
|
Response r = (Response)o;
|
||||||
|
if (_status!=r._status)
|
||||||
|
return false;
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "HTTP/2 "+_status+"\n"+super.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -686,4 +686,19 @@ public class HpackContextTest
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNameInsensitivity()
|
||||||
|
{
|
||||||
|
HpackContext ctx = new HpackContext(4096);
|
||||||
|
assertEquals("content-length",ctx.get("content-length").getHttpField().getName());
|
||||||
|
assertEquals("content-length",ctx.get("Content-Length").getHttpField().getName());
|
||||||
|
|
||||||
|
ctx.add(new HttpField("Wibble","Wobble"));
|
||||||
|
assertEquals("Wibble",ctx.get("wibble").getHttpField().getName());
|
||||||
|
assertEquals("Wibble",ctx.get("Wibble").getHttpField().getName());
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,5 +270,4 @@ public class HpackEncoderTest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,16 +19,67 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http2.hpack;
|
package org.eclipse.jetty.http2.hpack;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.eclipse.jetty.http2.hpack.MetaData.Response;
|
||||||
|
import org.eclipse.jetty.http2.hpack.MetaData.Request;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
||||||
|
|
||||||
public class HpackTest
|
public class HpackTest
|
||||||
{
|
{
|
||||||
@Test
|
@Test
|
||||||
public void encodeDecodeTest()
|
public void encodeDecodeResponseTest()
|
||||||
{
|
{
|
||||||
HttpFields fields = new HttpFields();
|
HpackEncoder encoder = new HpackEncoder();
|
||||||
|
HpackDecoder decoder = new HpackDecoder();
|
||||||
|
ByteBuffer buffer = BufferUtil.allocate(16*1024);
|
||||||
|
|
||||||
|
HttpFields fields0 = new HttpFields();
|
||||||
|
fields0.add(HttpHeader.CONTENT_TYPE,"text/html");
|
||||||
|
fields0.add(HttpHeader.CONTENT_LENGTH,"1024");
|
||||||
|
fields0.add(HttpHeader.SERVER,"jetty");
|
||||||
|
fields0.add(HttpHeader.SET_COOKIE,"abcdefghijklmnopqrstuvwxyz");
|
||||||
|
fields0.add("custom-key","custom-value");
|
||||||
|
Response original0 = new Response(200,fields0);
|
||||||
|
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
encoder.encode(buffer,original0);
|
||||||
|
BufferUtil.flipToFlush(buffer,0);
|
||||||
|
Response decoded0 = (Response)decoder.decode(buffer);
|
||||||
|
|
||||||
|
System.err.println(decoded0);
|
||||||
|
Assert.assertEquals(original0,decoded0);
|
||||||
|
|
||||||
|
// Same again?
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
encoder.encode(buffer,original0);
|
||||||
|
BufferUtil.flipToFlush(buffer,0);
|
||||||
|
Response decoded0b = (Response)decoder.decode(buffer);
|
||||||
|
|
||||||
|
System.err.println(decoded0b);
|
||||||
|
Assert.assertEquals(original0,decoded0b);
|
||||||
|
|
||||||
|
|
||||||
|
HttpFields fields1 = new HttpFields();
|
||||||
|
fields1.add(HttpHeader.CONTENT_TYPE,"text/plain");
|
||||||
|
fields1.add(HttpHeader.CONTENT_LENGTH,"1234");
|
||||||
|
fields1.add(HttpHeader.SERVER,"jetty");
|
||||||
|
fields1.add("custom-key","other-value");
|
||||||
|
Response original1 = new Response(200,fields1);
|
||||||
|
|
||||||
|
// Same again?
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
encoder.encode(buffer,original1);
|
||||||
|
BufferUtil.flipToFlush(buffer,0);
|
||||||
|
Response decoded1 = (Response)decoder.decode(buffer);
|
||||||
|
|
||||||
|
System.err.println(decoded1);
|
||||||
|
Assert.assertEquals(original1,decoded1);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue