diff --git a/jetty-http/src/main/java/module-info.java b/jetty-http/src/main/java/module-info.java
index 396d16bab3c..59164fc20be 100644
--- a/jetty-http/src/main/java/module-info.java
+++ b/jetty-http/src/main/java/module-info.java
@@ -18,6 +18,7 @@ module org.eclipse.jetty.http
requires transitive org.eclipse.jetty.io;
exports org.eclipse.jetty.http;
+ exports org.eclipse.jetty.http.compression;
exports org.eclipse.jetty.http.pathmap;
uses org.eclipse.jetty.http.HttpFieldPreEncoder;
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
index 305c8b59fce..725722fd259 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
@@ -231,5 +231,49 @@ public class HttpTokens
}
}
}
+
+ /**
+ * This is used when decoding to not decode illegal characters based on RFC9110.
+ * CR, LF, or NUL are replaced with ' ', all other control and multibyte characters
+ * are replaced with '?'. If this is given a legal character the same value will be returned.
+ *
+ * field-vchar = VCHAR / obs-text
+ * obs-text = %x80-FF
+ * VCHAR = %x21-7E
+ *
+ * @param c the character to test.
+ * @return the original character or the replacement character ' ' or '?',
+ * the return value is guaranteed to be a valid ISO-8859-1 character.
+ */
+ public static char sanitizeFieldVchar(char c)
+ {
+ switch (c)
+ {
+ // A recipient of CR, LF, or NUL within a field value MUST either reject the message
+ // or replace each of those characters with SP before further processing
+ case '\r':
+ case '\n':
+ case 0x00:
+ return ' ';
+
+ default:
+ if (isIllegalFieldVchar(c))
+ return '?';
+ }
+ return c;
+ }
+
+ /**
+ * Checks whether this is an invalid VCHAR based on RFC9110.
+ * If this not a valid ISO-8859-1 character or a control character
+ * we say that it is illegal.
+ *
+ * @param c the character to test.
+ * @return true if this is invalid VCHAR.
+ */
+ public static boolean isIllegalFieldVchar(char c)
+ {
+ return (c >= 256 || c < ' ');
+ }
}
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/EncodingException.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/EncodingException.java
similarity index 92%
rename from jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/EncodingException.java
rename to jetty-http/src/main/java/org/eclipse/jetty/http/compression/EncodingException.java
index d8cc91a7de7..22dde443355 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/EncodingException.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/EncodingException.java
@@ -11,7 +11,7 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack.internal.util;
+package org.eclipse.jetty.http.compression;
public class EncodingException extends Exception
{
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/Huffman.java
similarity index 82%
rename from jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java
rename to jetty-http/src/main/java/org/eclipse/jetty/http/compression/Huffman.java
index 8b880b10ebe..bf530959175 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/Huffman.java
@@ -11,14 +11,16 @@
// ========================================================================
//
-package org.eclipse.jetty.http2.hpack;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.Utf8StringBuilder;
+package org.eclipse.jetty.http.compression;
+/**
+ * This class contains the Huffman Codes defined in RFC7541.
+ */
public class Huffman
{
+ private Huffman()
+ {
+ }
// Appendix C: Huffman Codes
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C
@@ -286,7 +288,7 @@ public class Huffman
static final int[][] LCCODES = new int[CODES.length][];
static final char EOS = 256;
- // Huffman decode tree stored in a flattened char array for good
+ // Huffman decode tree stored in a flattened char array for good
// locality of reference.
static final char[] tree;
static final char[] rowsym;
@@ -302,9 +304,9 @@ public class Huffman
}
int r = 0;
- for (int i = 0; i < CODES.length; i++)
+ for (int[] ints : CODES)
{
- r += (CODES[i][1] + 7) / 8;
+ r += (ints[1] + 7) / 8;
}
tree = new char[r * 256];
rowsym = new char[r];
@@ -347,200 +349,4 @@ public class Huffman
}
}
}
-
- public static String decode(ByteBuffer buffer) throws HpackException.CompressionException
- {
- return decode(buffer, buffer.remaining());
- }
-
- public static String decode(ByteBuffer buffer, int length) throws HpackException.CompressionException
- {
- Utf8StringBuilder utf8 = new Utf8StringBuilder(length * 2);
- int node = 0;
- int current = 0;
- int bits = 0;
-
- for (int i = 0; i < length; i++)
- {
- int b = buffer.get() & 0xFF;
- current = (current << 8) | b;
- bits += 8;
- while (bits >= 8)
- {
- int c = (current >>> (bits - 8)) & 0xFF;
- node = tree[node * 256 + c];
- if (rowbits[node] != 0)
- {
- if (rowsym[node] == EOS)
- throw new HpackException.CompressionException("EOS in content");
-
- // terminal node
- utf8.append((byte)(0xFF & rowsym[node]));
- bits -= rowbits[node];
- node = 0;
- }
- else
- {
- // non-terminal node
- bits -= 8;
- }
- }
- }
-
- while (bits > 0)
- {
- int c = (current << (8 - bits)) & 0xFF;
- int lastNode = node;
- node = tree[node * 256 + c];
-
- if (rowbits[node] == 0 || rowbits[node] > bits)
- {
- int requiredPadding = 0;
- for (int i = 0; i < bits; i++)
- {
- requiredPadding = (requiredPadding << 1) | 1;
- }
-
- if ((c >> (8 - bits)) != requiredPadding)
- throw new HpackException.CompressionException("Incorrect padding");
-
- node = lastNode;
- break;
- }
-
- utf8.append((byte)(0xFF & rowsym[node]));
- bits -= rowbits[node];
- node = 0;
- }
-
- if (node != 0)
- throw new HpackException.CompressionException("Bad termination");
-
- return utf8.toString();
- }
-
- public static int octetsNeeded(String s)
- {
- return octetsNeeded(CODES, s);
- }
-
- public static int octetsNeeded(byte[] b)
- {
- return octetsNeeded(CODES, b);
- }
-
- public static void encode(ByteBuffer buffer, String s)
- {
- encode(CODES, buffer, s);
- }
-
- public static void encode(ByteBuffer buffer, byte[] b)
- {
- encode(CODES, buffer, b);
- }
-
- public static int octetsNeededLC(String s)
- {
- return octetsNeeded(LCCODES, s);
- }
-
- public static void encodeLC(ByteBuffer buffer, String s)
- {
- encode(LCCODES, buffer, s);
- }
-
- private static int octetsNeeded(final int[][] table, String s)
- {
- int needed = 0;
- int len = s.length();
- for (int i = 0; i < len; i++)
- {
- char c = s.charAt(i);
- if (c >= 128 || c < ' ')
- return -1;
- needed += table[c][1];
- }
-
- return (needed + 7) / 8;
- }
-
- private static int octetsNeeded(final int[][] table, byte[] b)
- {
- int needed = 0;
- int len = b.length;
- for (int i = 0; i < len; i++)
- {
- int c = 0xFF & b[i];
- needed += table[c][1];
- }
- return (needed + 7) / 8;
- }
-
- /**
- * @param table The table to encode by
- * @param buffer The buffer to encode to
- * @param s The string to encode
- */
- private static void encode(final int[][] table, ByteBuffer buffer, String s)
- {
- long current = 0;
- int n = 0;
- int len = s.length();
- for (int i = 0; i < len; i++)
- {
- char c = s.charAt(i);
- if (c >= 128 || c < ' ')
- throw new IllegalArgumentException();
- int code = table[c][0];
- int bits = table[c][1];
-
- current <<= bits;
- current |= code;
- n += bits;
-
- while (n >= 8)
- {
- n -= 8;
- buffer.put((byte)(current >> n));
- }
- }
-
- if (n > 0)
- {
- current <<= (8 - n);
- current |= (0xFF >>> n);
- buffer.put((byte)(current));
- }
- }
-
- private static void encode(final int[][] table, ByteBuffer buffer, byte[] b)
- {
- long current = 0;
- int n = 0;
-
- int len = b.length;
- for (int i = 0; i < len; i++)
- {
- int c = 0xFF & b[i];
- int code = table[c][0];
- int bits = table[c][1];
-
- current <<= bits;
- current |= code;
- n += bits;
-
- while (n >= 8)
- {
- n -= 8;
- buffer.put((byte)(current >> n));
- }
- }
-
- if (n > 0)
- {
- current <<= (8 - n);
- current |= (0xFF >>> n);
- buffer.put((byte)(current));
- }
- }
}
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/HuffmanDecoder.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java
similarity index 62%
rename from jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/HuffmanDecoder.java
rename to jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java
index 31e832ccfa2..983e10a9847 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/HuffmanDecoder.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java
@@ -11,26 +11,34 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack.internal.util;
+package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer;
-import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.http.HttpTokens;
+import org.eclipse.jetty.util.CharsetStringBuilder;
+import static org.eclipse.jetty.http.compression.Huffman.rowbits;
+import static org.eclipse.jetty.http.compression.Huffman.rowsym;
+
+/**
+ * Used to decoded Huffman encoded strings.
+ *
+ * Characters which are illegal field-vchar values are replaced with
+ * either ' ' or '?' as described in RFC9110
+ */
public class HuffmanDecoder
{
- static final char EOS = HuffmanEncoder.EOS;
- static final char[] tree = HuffmanEncoder.tree;
- static final char[] rowsym = HuffmanEncoder.rowsym;
- static final byte[] rowbits = HuffmanEncoder.rowbits;
-
- private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
+ private final CharsetStringBuilder.Iso88591StringBuilder _builder = new CharsetStringBuilder.Iso88591StringBuilder();
private int _length = 0;
private int _count = 0;
private int _node = 0;
private int _current = 0;
private int _bits = 0;
+ /**
+ * @param length in bytes of the huffman data.
+ */
public void setLength(int length)
{
if (_count != 0)
@@ -38,6 +46,11 @@ public class HuffmanDecoder
_length = length;
}
+ /**
+ * @param buffer the buffer containing the Huffman encoded bytes.
+ * @return the decoded String.
+ * @throws EncodingException if the huffman encoding is invalid.
+ */
public String decode(ByteBuffer buffer) throws EncodingException
{
for (; _count < _length; _count++)
@@ -50,18 +63,20 @@ public class HuffmanDecoder
_bits += 8;
while (_bits >= 8)
{
- int c = (_current >>> (_bits - 8)) & 0xFF;
- _node = tree[_node * 256 + c];
+ int i = (_current >>> (_bits - 8)) & 0xFF;
+ _node = Huffman.tree[_node * 256 + i];
if (rowbits[_node] != 0)
{
- if (rowsym[_node] == EOS)
+ if (rowsym[_node] == Huffman.EOS)
{
reset();
throw new EncodingException("eos_in_content");
}
// terminal node
- _utf8.append((byte)(0xFF & rowsym[_node]));
+ char c = rowsym[_node];
+ c = HttpTokens.sanitizeFieldVchar(c);
+ _builder.append((byte)c);
_bits -= rowbits[_node];
_node = 0;
}
@@ -75,26 +90,28 @@ public class HuffmanDecoder
while (_bits > 0)
{
- int c = (_current << (8 - _bits)) & 0xFF;
+ int i = (_current << (8 - _bits)) & 0xFF;
int lastNode = _node;
- _node = tree[_node * 256 + c];
+ _node = Huffman.tree[_node * 256 + i];
if (rowbits[_node] == 0 || rowbits[_node] > _bits)
{
int requiredPadding = 0;
- for (int i = 0; i < _bits; i++)
+ for (int j = 0; j < _bits; j++)
{
requiredPadding = (requiredPadding << 1) | 1;
}
- if ((c >> (8 - _bits)) != requiredPadding)
+ if ((i >> (8 - _bits)) != requiredPadding)
throw new EncodingException("incorrect_padding");
_node = lastNode;
break;
}
- _utf8.append((byte)(0xFF & rowsym[_node]));
+ char c = rowsym[_node];
+ c = HttpTokens.sanitizeFieldVchar(c);
+ _builder.append((byte)c);
_bits -= rowbits[_node];
_node = 0;
}
@@ -105,14 +122,14 @@ public class HuffmanDecoder
throw new EncodingException("bad_termination");
}
- String value = _utf8.toString();
+ String value = _builder.build();
reset();
return value;
}
public void reset()
{
- _utf8.reset();
+ _builder.reset();
_count = 0;
_current = 0;
_node = 0;
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java
new file mode 100644
index 00000000000..965f90ad017
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java
@@ -0,0 +1,137 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http.compression;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpTokens;
+
+import static org.eclipse.jetty.http.compression.Huffman.CODES;
+import static org.eclipse.jetty.http.compression.Huffman.LCCODES;
+
+/**
+ * Used to encode strings Huffman encoding.
+ *
+ * Characters are encoded with ISO-8859-1, if any multi-byte characters or
+ * control characters are present the encoder will throw {@link EncodingException}.
+ */
+public class HuffmanEncoder
+{
+ private HuffmanEncoder()
+ {
+ }
+
+ /**
+ * @param s the string to encode.
+ * @return the number of octets needed to encode the string, or -1 if it cannot be encoded.
+ */
+ public static int octetsNeeded(String s)
+ {
+ return octetsNeeded(CODES, s);
+ }
+
+ /**
+ * @param b the byte array to encode.
+ * @return the number of octets needed to encode the bytes, or -1 if it cannot be encoded.
+ */
+ public static int octetsNeeded(byte[] b)
+ {
+ int needed = 0;
+ for (byte value : b)
+ {
+ int c = 0xFF & value;
+ needed += CODES[c][1];
+ }
+ return (needed + 7) / 8;
+ }
+
+ /**
+ * @param buffer the buffer to encode into.
+ * @param s the string to encode.
+ */
+ public static void encode(ByteBuffer buffer, String s)
+ {
+ encode(CODES, buffer, s);
+ }
+
+ /**
+ * @param s the string to encode in lowercase.
+ * @return the number of octets needed to encode the string, or -1 if it cannot be encoded.
+ */
+ public static int octetsNeededLowerCase(String s)
+ {
+ return octetsNeeded(LCCODES, s);
+ }
+
+ /**
+ * @param buffer the buffer to encode into in lowercase.
+ * @param s the string to encode.
+ */
+ public static void encodeLowerCase(ByteBuffer buffer, String s)
+ {
+ encode(LCCODES, buffer, s);
+ }
+
+ private static int octetsNeeded(final int[][] table, String s)
+ {
+ int needed = 0;
+ int len = s.length();
+ for (int i = 0; i < len; i++)
+ {
+ char c = s.charAt(i);
+ if (HttpTokens.isIllegalFieldVchar(c))
+ return -1;
+ needed += table[c][1];
+ }
+
+ return (needed + 7) / 8;
+ }
+
+ /**
+ * @param table The table to encode by
+ * @param buffer The buffer to encode to
+ * @param s The string to encode
+ */
+ private static void encode(final int[][] table, ByteBuffer buffer, String s)
+ {
+ long current = 0;
+ int n = 0;
+ int len = s.length();
+ for (int i = 0; i < len; i++)
+ {
+ char c = s.charAt(i);
+ if (HttpTokens.isIllegalFieldVchar(c))
+ throw new IllegalArgumentException();
+ int code = table[c][0];
+ int bits = table[c][1];
+
+ current <<= bits;
+ current |= code;
+ n += bits;
+
+ while (n >= 8)
+ {
+ n -= 8;
+ buffer.put((byte)(current >> n));
+ }
+ }
+
+ if (n > 0)
+ {
+ current <<= (8 - n);
+ current |= (0xFF >>> n);
+ buffer.put((byte)(current));
+ }
+ }
+}
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitIntegerParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java
similarity index 59%
rename from jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitIntegerParser.java
rename to jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java
index 1088bbc9255..e1c0dbb17bb 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitIntegerParser.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java
@@ -11,17 +11,25 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack.internal.util;
+package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer;
-public class NBitIntegerParser
+/**
+ * Used to decode integers as described in RFC7541.
+ */
+public class NBitIntegerDecoder
{
private int _prefix;
private long _total;
private long _multiplier;
private boolean _started;
+ /**
+ * Set the prefix length in of the integer representation in bits.
+ * A prefix of 6 means the integer representation starts after the first 2 bits.
+ * @param prefix the number of bits in the integer prefix.
+ */
public void setPrefix(int prefix)
{
if (_started)
@@ -29,11 +37,27 @@ public class NBitIntegerParser
_prefix = prefix;
}
+ /**
+ * Decode an integer from the buffer. If the buffer does not contain the complete integer representation
+ * a value of -1 is returned to indicate that more data is needed to complete parsing.
+ * This should be only after the prefix has been set with {@link #setPrefix(int)}.
+ * @param buffer the buffer containing the encoded integer.
+ * @return the decoded integer or -1 to indicate that more data is needed.
+ * @throws ArithmeticException if the value overflows a int.
+ */
public int decodeInt(ByteBuffer buffer)
{
return Math.toIntExact(decodeLong(buffer));
}
+ /**
+ * Decode a long from the buffer. If the buffer does not contain the complete integer representation
+ * a value of -1 is returned to indicate that more data is needed to complete parsing.
+ * This should be only after the prefix has been set with {@link #setPrefix(int)}.
+ * @param buffer the buffer containing the encoded integer.
+ * @return the decoded long or -1 to indicate that more data is needed.
+ * @throws ArithmeticException if the value overflows a long.
+ */
public long decodeLong(ByteBuffer buffer)
{
if (!_started)
@@ -71,6 +95,9 @@ public class NBitIntegerParser
}
}
+ /**
+ * Reset the internal state of the parser.
+ */
public void reset()
{
_prefix = 0;
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitIntegerEncoder.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java
similarity index 83%
rename from jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitIntegerEncoder.java
rename to jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java
index a23d5426647..0750d6584f8 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitIntegerEncoder.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java
@@ -11,13 +11,25 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack.internal.util;
+package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer;
+/**
+ * Used to encode integers as described in RFC7541.
+ */
public class NBitIntegerEncoder
{
- public static int octectsNeeded(int n, long i)
+ private NBitIntegerEncoder()
+ {
+ }
+
+ /**
+ * @param n the prefix used to encode this long.
+ * @param i the integer to encode.
+ * @return the number of octets it would take to encode the long.
+ */
+ public static int octetsNeeded(int n, long i)
{
if (n == 8)
{
@@ -43,6 +55,12 @@ public class NBitIntegerEncoder
return (log + 6) / 7;
}
+ /**
+ *
+ * @param buf the buffer to encode into.
+ * @param n the prefix used to encode this long.
+ * @param i the long to encode into the buffer.
+ */
public static void encode(ByteBuffer buf, int n, long i)
{
if (n == 8)
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitStringParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java
similarity index 53%
rename from jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitStringParser.java
rename to jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java
index 1b5e2a89835..25367abe377 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/NBitStringParser.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java
@@ -11,15 +11,27 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack.internal.util;
+package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer;
-public class NBitStringParser
+import org.eclipse.jetty.util.CharsetStringBuilder;
+
+/**
+ * Used to decode string literals as described in RFC7541.
+ *
+ * The string literal representation consists of a single bit to indicate whether huffman encoding is used,
+ * followed by the string byte length encoded with the n-bit integer representation also from RFC7541, and
+ * the bytes of the string are directly after this.
+ *
+ * Characters which are illegal field-vchar values are replaced with
+ * either ' ' or '?' as described in RFC9110
+ */
+public class NBitStringDecoder
{
- private final NBitIntegerParser _integerParser;
+ private final NBitIntegerDecoder _integerDecoder;
private final HuffmanDecoder _huffmanBuilder;
- private final StringBuilder _stringBuilder;
+ private final CharsetStringBuilder.Iso88591StringBuilder _builder;
private boolean _huffman;
private int _count;
private int _length;
@@ -34,13 +46,18 @@ public class NBitStringParser
VALUE
}
- public NBitStringParser()
+ public NBitStringDecoder()
{
- _integerParser = new NBitIntegerParser();
+ _integerDecoder = new NBitIntegerDecoder();
_huffmanBuilder = new HuffmanDecoder();
- _stringBuilder = new StringBuilder();
+ _builder = new CharsetStringBuilder.Iso88591StringBuilder();
}
+ /**
+ * Set the prefix length in of the string representation in bits.
+ * A prefix of 6 means the string representation starts after the first 2 bits.
+ * @param prefix the number of bits in the string prefix.
+ */
public void setPrefix(int prefix)
{
if (_state != State.PARSING)
@@ -48,6 +65,15 @@ public class NBitStringParser
_prefix = prefix;
}
+ /**
+ * Decode a string from the buffer. If the buffer does not contain the complete string representation
+ * then a value of null is returned to indicate that more data is needed to complete parsing.
+ * This should be only after the prefix has been set with {@link #setPrefix(int)}.
+ * @param buffer the buffer containing the encoded string.
+ * @return the decoded string or null to indicate that more data is needed.
+ * @throws ArithmeticException if the string length value overflows a int.
+ * @throws EncodingException if the string encoding is invalid.
+ */
public String decode(ByteBuffer buffer) throws EncodingException
{
while (true)
@@ -58,11 +84,11 @@ public class NBitStringParser
byte firstByte = buffer.get(buffer.position());
_huffman = ((0x80 >>> (8 - _prefix)) & firstByte) != 0;
_state = State.LENGTH;
- _integerParser.setPrefix(_prefix - 1);
+ _integerDecoder.setPrefix(_prefix - 1);
continue;
case LENGTH:
- _length = _integerParser.decodeInt(buffer);
+ _length = _integerDecoder.decodeInt(buffer);
if (_length < 0)
return null;
_state = State.VALUE;
@@ -70,7 +96,7 @@ public class NBitStringParser
continue;
case VALUE:
- String value = _huffman ? _huffmanBuilder.decode(buffer) : asciiStringDecode(buffer);
+ String value = _huffman ? _huffmanBuilder.decode(buffer) : stringDecode(buffer);
if (value != null)
reset();
return value;
@@ -81,23 +107,24 @@ public class NBitStringParser
}
}
- private String asciiStringDecode(ByteBuffer buffer)
+ private String stringDecode(ByteBuffer buffer)
{
for (; _count < _length; _count++)
{
if (!buffer.hasRemaining())
return null;
- _stringBuilder.append((char)(0x7F & buffer.get()));
+ _builder.append(buffer.get());
}
- return _stringBuilder.toString();
+
+ return _builder.build();
}
public void reset()
{
_state = State.PARSING;
- _integerParser.reset();
+ _integerDecoder.reset();
_huffmanBuilder.reset();
- _stringBuilder.setLength(0);
+ _builder.reset();
_prefix = 0;
_count = 0;
_length = 0;
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java
new file mode 100644
index 00000000000..42dd39981e9
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java
@@ -0,0 +1,164 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.stream.Stream;
+
+import org.eclipse.jetty.http.compression.EncodingException;
+import org.eclipse.jetty.http.compression.HuffmanDecoder;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class HuffmanTest
+{
+ public static String decode(ByteBuffer buffer, int length) throws EncodingException
+ {
+ HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
+ huffmanDecoder.setLength(length);
+ String decoded = huffmanDecoder.decode(buffer);
+ if (decoded == null)
+ throw new EncodingException("invalid string encoding");
+
+ huffmanDecoder.reset();
+ return decoded;
+ }
+
+ public static Stream data()
+ {
+ return Stream.of(
+ new String[][]{
+ {"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"},
+ {"D.4.2", "a8eb10649cbf", "no-cache"},
+ {"D.6.1k", "6402", "302"},
+ {"D.6.1v", "aec3771a4b", "private"},
+ {"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"},
+ {"D.6.2te", "640cff", "303"},
+ }).map(Arguments::of);
+ }
+
+ @ParameterizedTest(name = "[{index}] spec={0}")
+ @MethodSource("data")
+ public void testDecode(String specSection, String hex, String expected) throws Exception
+ {
+ byte[] encoded = TypeUtil.fromHexString(hex);
+ HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
+ huffmanDecoder.setLength(encoded.length);
+ String decoded = huffmanDecoder.decode(ByteBuffer.wrap(encoded));
+ assertEquals(expected, decoded, specSection);
+ }
+
+ @ParameterizedTest(name = "[{index}] spec={0}")
+ @MethodSource("data")
+ public void testEncode(String specSection, String hex, String expected)
+ {
+ ByteBuffer buf = BufferUtil.allocate(1024);
+ int pos = BufferUtil.flipToFill(buf);
+ HuffmanEncoder.encode(buf, expected);
+ BufferUtil.flipToFlush(buf, pos);
+ String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
+ assertEquals(hex, encoded, specSection);
+ assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
+ }
+
+ public static Stream testDecode8859OnlyArguments()
+ {
+ return Stream.of(
+ // These are valid characters for ISO-8859-1.
+ Arguments.of("FfFe6f", (char)128),
+ Arguments.of("FfFfFbBf", (char)255),
+
+ // RFC9110 specifies these to be replaced as ' ' during decoding.
+ Arguments.of("FfC7", ' '), // (char)0
+ Arguments.of("FfFfFfF7", ' '), // '\r'
+ Arguments.of("FfFfFfF3", ' '), // '\n'
+
+ // We replace control chars with the default replacement character of '?'.
+ Arguments.of("FfFfFfBf", '?') // (char)(' ' - 1)
+ );
+ }
+
+ @ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
+ @MethodSource("testDecode8859OnlyArguments")
+ public void testDecode8859Only(String hexString, char expected) throws Exception
+ {
+ ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(hexString));
+ String decoded = decode(buffer, buffer.remaining());
+ assertThat(decoded, equalTo("" + expected));
+ }
+
+ public static Stream testEncode8859OnlyArguments()
+ {
+ return Stream.of(
+ Arguments.of((char)128, (char)128),
+ Arguments.of((char)255, (char)255),
+ Arguments.of((char)0, null),
+ Arguments.of('\r', null),
+ Arguments.of('\n', null),
+ Arguments.of((char)456, null),
+ Arguments.of((char)256, null),
+ Arguments.of((char)-1, null),
+ Arguments.of((char)(' ' - 1), null)
+ );
+ }
+
+ @ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
+ @MethodSource("testEncode8859OnlyArguments")
+ public void testEncode8859Only(char value, Character expectedValue) throws Exception
+ {
+ String s = "value = '" + value + "'";
+
+ // If expected is null we should not be able to encode.
+ if (expectedValue == null)
+ {
+ assertThat(HuffmanEncoder.octetsNeeded(s), equalTo(-1));
+ assertThrows(Throwable.class, () -> encode(s));
+ return;
+ }
+
+ String expected = "value = '" + expectedValue + "'";
+ assertThat(HuffmanEncoder.octetsNeeded(s), greaterThan(0));
+ ByteBuffer buffer = encode(s);
+ String decode = decode(buffer);
+ System.err.println("decoded: " + decode);
+ assertThat(decode, equalTo(expected));
+ }
+
+ private ByteBuffer encode(String s)
+ {
+ ByteBuffer buffer = BufferUtil.allocate(32);
+ BufferUtil.clearToFill(buffer);
+ HuffmanEncoder.encode(buffer, s);
+ BufferUtil.flipToFlush(buffer, 0);
+ return buffer;
+ }
+
+ private String decode(ByteBuffer buffer) throws Exception
+ {
+ return decode(buffer, buffer.remaining());
+ }
+}
diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/NBitIntegerParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/NBitIntegerParserTest.java
similarity index 81%
rename from jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/NBitIntegerParserTest.java
rename to jetty-http/src/test/java/org/eclipse/jetty/http/NBitIntegerParserTest.java
index 249bf785a25..bd1969373eb 100644
--- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/NBitIntegerParserTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/NBitIntegerParserTest.java
@@ -11,11 +11,11 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack;
+package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.Test;
@@ -29,7 +29,7 @@ public class NBitIntegerParserTest
@Test
public void testParsingOverByteBoundary()
{
- NBitIntegerParser parser = new NBitIntegerParser();
+ NBitIntegerDecoder decoder = new NBitIntegerDecoder();
String encoded = "FFBA09";
byte[] bytes = TypeUtil.fromHexString(encoded);
@@ -37,11 +37,11 @@ public class NBitIntegerParserTest
ByteBuffer buffer1 = BufferUtil.toBuffer(bytes, 0, 2);
ByteBuffer buffer2 = BufferUtil.toBuffer(bytes, 2, 1);
- parser.setPrefix(7);
- int value = parser.decodeInt(buffer1);
+ decoder.setPrefix(7);
+ int value = decoder.decodeInt(buffer1);
assertThat(value, is(-1));
- value = parser.decodeInt(buffer2);
+ value = decoder.decodeInt(buffer2);
assertThat(value, is(1337));
}
}
diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/NBitIntegerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/NBitIntegerTest.java
similarity index 80%
rename from jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/NBitIntegerTest.java
rename to jetty-http/src/test/java/org/eclipse/jetty/http/NBitIntegerTest.java
index 99f84265566..c390db1869c 100644
--- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/NBitIntegerTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/NBitIntegerTest.java
@@ -11,12 +11,12 @@
// ========================================================================
//
-package org.eclipse.jetty.http3.qpack;
+package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.Test;
@@ -26,22 +26,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@SuppressWarnings("PointlessArithmeticExpression")
public class NBitIntegerTest
{
- private final NBitIntegerParser _parser = new NBitIntegerParser();
+ private final NBitIntegerDecoder _decoder = new NBitIntegerDecoder();
@Test
public void testOctetsNeeded()
{
- assertEquals(0, NBitIntegerEncoder.octectsNeeded(5, 10));
- assertEquals(2, NBitIntegerEncoder.octectsNeeded(5, 1337));
- assertEquals(1, NBitIntegerEncoder.octectsNeeded(8, 42));
- assertEquals(3, NBitIntegerEncoder.octectsNeeded(8, 1337));
+ assertEquals(0, NBitIntegerEncoder.octetsNeeded(5, 10));
+ assertEquals(2, NBitIntegerEncoder.octetsNeeded(5, 1337));
+ assertEquals(1, NBitIntegerEncoder.octetsNeeded(8, 42));
+ assertEquals(3, NBitIntegerEncoder.octetsNeeded(8, 1337));
- assertEquals(0, NBitIntegerEncoder.octectsNeeded(6, 62));
- assertEquals(1, NBitIntegerEncoder.octectsNeeded(6, 63));
- assertEquals(1, NBitIntegerEncoder.octectsNeeded(6, 64));
- assertEquals(2, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
- assertEquals(3, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
- assertEquals(4, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
+ assertEquals(0, NBitIntegerEncoder.octetsNeeded(6, 62));
+ assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 63));
+ assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 64));
+ assertEquals(2, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
+ assertEquals(3, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
+ assertEquals(4, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
}
@Test
@@ -87,7 +87,7 @@ public class NBitIntegerTest
String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
assertEquals(expected, r);
- assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octectsNeeded(n, i));
+ assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octetsNeeded(n, i));
}
@Test
@@ -125,8 +125,8 @@ public class NBitIntegerTest
public void testDecode(int n, int expected, String encoded)
{
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
- _parser.setPrefix(n);
- assertEquals(expected, _parser.decodeInt(buf));
+ _decoder.setPrefix(n);
+ assertEquals(expected, _decoder.decodeInt(buf));
}
@Test
@@ -149,8 +149,8 @@ public class NBitIntegerTest
{
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("77EaFF"));
buf.position(1);
- _parser.setPrefix(5);
- assertEquals(10, _parser.decodeInt(buf));
+ _decoder.setPrefix(5);
+ assertEquals(10, _decoder.decodeInt(buf));
}
@Test
@@ -173,8 +173,8 @@ public class NBitIntegerTest
{
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("881f9a0aff"));
buf.position(1);
- _parser.setPrefix(5);
- assertEquals(1337, _parser.decodeInt(buf));
+ _decoder.setPrefix(5);
+ assertEquals(1337, _decoder.decodeInt(buf));
}
@Test
@@ -197,7 +197,7 @@ public class NBitIntegerTest
{
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("882aFf"));
buf.position(1);
- _parser.setPrefix(8);
- assertEquals(42, _parser.decodeInt(buf));
+ _decoder.setPrefix(8);
+ assertEquals(42, _decoder.decodeInt(buf));
}
}
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java
index eef7a229156..3dc58f56a72 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/AuthorityHttpField.java
@@ -16,9 +16,6 @@ package org.eclipse.jetty.http2.hpack;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpHeader;
-/**
- *
- */
public class AuthorityHttpField extends HostPortHttpField
{
public static final String AUTHORITY = HpackContext.STATIC_TABLE[1][0];
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java
index 2917b7b4d0b..6a774a51f07 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java
@@ -24,6 +24,8 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
@@ -457,19 +459,19 @@ public class HpackContext
String value = field.getValue();
if (value != null && value.length() > 0)
{
- int huffmanLen = Huffman.octetsNeeded(value);
+ int huffmanLen = HuffmanEncoder.octetsNeeded(value);
if (huffmanLen < 0)
throw new IllegalStateException("bad value");
- int lenLen = NBitInteger.octectsNeeded(7, huffmanLen);
+ int lenLen = NBitIntegerEncoder.octetsNeeded(7, huffmanLen);
_huffmanValue = new byte[1 + lenLen + huffmanLen];
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
// Indicate Huffman
buffer.put((byte)0x80);
// Add huffman length
- NBitInteger.encode(buffer, 7, huffmanLen);
+ NBitIntegerEncoder.encode(buffer, 7, huffmanLen);
// Encode value
- Huffman.encode(buffer, value);
+ HuffmanEncoder.encode(buffer, value);
}
else
_huffmanValue = 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 4367eca1d31..577caabbb47 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
@@ -19,8 +19,12 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http.compression.EncodingException;
+import org.eclipse.jetty.http.compression.HuffmanDecoder;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.CharsetStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,6 +40,8 @@ public class HpackDecoder
private final HpackContext _context;
private final MetaDataBuilder _builder;
+ private final HuffmanDecoder _huffmanDecoder;
+ private final NBitIntegerDecoder _integerDecoder;
private int _localMaxDynamicTableSize;
/**
@@ -47,6 +53,8 @@ public class HpackDecoder
_context = new HpackContext(localMaxDynamicTableSize);
_localMaxDynamicTableSize = localMaxDynamicTableSize;
_builder = new MetaDataBuilder(maxHeaderSize);
+ _huffmanDecoder = new HuffmanDecoder();
+ _integerDecoder = new NBitIntegerDecoder();
}
public HpackContext getHpackContext()
@@ -64,7 +72,8 @@ public class HpackDecoder
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 the buffer is big, don't even think about decoding it.
+ // Huffman may double the size, but it will only be a temporary allocation until detected in MetaDataBuilder.emit().
if (buffer.remaining() > _builder.getMaxSize())
throw new HpackException.SessionException("431 Request Header Fields too large");
@@ -79,7 +88,7 @@ public class HpackDecoder
if (b < 0)
{
// 7.1 indexed if the high bit is set
- int index = NBitInteger.decode(buffer, 7);
+ int index = integerDecode(buffer, 7);
Entry entry = _context.get(index);
if (entry == null)
throw new HpackException.SessionException("Unknown index %d", index);
@@ -120,7 +129,7 @@ public class HpackDecoder
case 2: // 7.3
case 3: // 7.3
// change table size
- int size = NBitInteger.decode(buffer, 5);
+ int size = integerDecode(buffer, 5);
if (LOG.isDebugEnabled())
LOG.debug("decode resize={}", size);
if (size > _localMaxDynamicTableSize)
@@ -133,7 +142,7 @@ public class HpackDecoder
case 0: // 7.2.2
case 1: // 7.2.3
indexed = false;
- nameIndex = NBitInteger.decode(buffer, 4);
+ nameIndex = integerDecode(buffer, 4);
break;
case 4: // 7.2.1
@@ -141,7 +150,7 @@ public class HpackDecoder
case 6: // 7.2.1
case 7: // 7.2.1
indexed = true;
- nameIndex = NBitInteger.decode(buffer, 6);
+ nameIndex = integerDecode(buffer, 6);
break;
default:
@@ -160,12 +169,11 @@ public class HpackDecoder
else
{
huffmanName = (buffer.get() & 0x80) == 0x80;
- int length = NBitInteger.decode(buffer, 7);
- _builder.checkSize(length, huffmanName);
+ int length = integerDecode(buffer, 7);
if (huffmanName)
- name = Huffman.decode(buffer, length);
+ name = huffmanDecode(buffer, length);
else
- name = toASCIIString(buffer, length);
+ name = toISO88591String(buffer, length);
check:
for (int i = name.length(); i-- > 0; )
{
@@ -201,12 +209,11 @@ public class HpackDecoder
// decode the value
boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
- int length = NBitInteger.decode(buffer, 7);
- _builder.checkSize(length, huffmanValue);
+ int length = integerDecode(buffer, 7);
if (huffmanValue)
- value = Huffman.decode(buffer, length);
+ value = huffmanDecode(buffer, length);
else
- value = toASCIIString(buffer, length);
+ value = toISO88591String(buffer, length);
// Make the new field
HttpField field;
@@ -267,14 +274,61 @@ public class HpackDecoder
return _builder.build();
}
- public static String toASCIIString(ByteBuffer buffer, int length)
+ private int integerDecode(ByteBuffer buffer, int prefix) throws HpackException.CompressionException
{
- StringBuilder builder = new StringBuilder(length);
+ try
+ {
+ if (prefix != 8)
+ buffer.position(buffer.position() - 1);
+
+ _integerDecoder.setPrefix(prefix);
+ int decodedInt = _integerDecoder.decodeInt(buffer);
+ if (decodedInt < 0)
+ throw new EncodingException("invalid integer encoding");
+ return decodedInt;
+ }
+ catch (EncodingException e)
+ {
+ HpackException.CompressionException compressionException = new HpackException.CompressionException(e.getMessage());
+ compressionException.initCause(e);
+ throw compressionException;
+ }
+ finally
+ {
+ _integerDecoder.reset();
+ }
+ }
+
+ private String huffmanDecode(ByteBuffer buffer, int length) throws HpackException.CompressionException
+ {
+ try
+ {
+ _huffmanDecoder.setLength(length);
+ String decoded = _huffmanDecoder.decode(buffer);
+ if (decoded == null)
+ throw new HpackException.CompressionException("invalid string encoding");
+ return decoded;
+ }
+ catch (EncodingException e)
+ {
+ HpackException.CompressionException compressionException = new HpackException.CompressionException(e.getMessage());
+ compressionException.initCause(e);
+ throw compressionException;
+ }
+ finally
+ {
+ _huffmanDecoder.reset();
+ }
+ }
+
+ public static String toISO88591String(ByteBuffer buffer, int length)
+ {
+ CharsetStringBuilder.Iso88591StringBuilder builder = new CharsetStringBuilder.Iso88591StringBuilder();
for (int i = 0; i < length; ++i)
{
- builder.append((char)(0x7F & buffer.get()));
+ builder.append(HttpTokens.sanitizeFieldVchar((char)buffer.get()));
}
- return builder.toString();
+ return builder.build();
}
@Override
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java
index 9d89b040e6d..e84bdb4e367 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java
@@ -14,7 +14,6 @@
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
@@ -26,9 +25,12 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry;
import org.eclipse.jetty.util.BufferUtil;
@@ -286,7 +288,7 @@ public class HpackEncoder
if (maxDynamicTableSize > _remoteMaxDynamicTableSize)
throw new IllegalArgumentException();
buffer.put((byte)0x20);
- NBitInteger.encode(buffer, 5, maxDynamicTableSize);
+ NBitIntegerEncoder.encode(buffer, 5, maxDynamicTableSize);
_context.resize(maxDynamicTableSize);
}
@@ -315,9 +317,9 @@ public class HpackEncoder
{
int index = _context.index(entry);
buffer.put((byte)0x80);
- NBitInteger.encode(buffer, 7, index);
+ NBitIntegerEncoder.encode(buffer, 7, index);
if (_debug)
- encoding = "IdxField" + (entry.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(7, index));
+ encoding = "IdxField" + (entry.isStatic() ? "S" : "") + (1 + NBitIntegerEncoder.octetsNeeded(7, index));
}
}
else
@@ -391,19 +393,19 @@ public class HpackEncoder
if (_debug)
encoding = "Lit" +
- ((name == null) ? "HuffN" : ("IdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(4, _context.index(name))))) +
+ ((name == null) ? "HuffN" : ("IdxN" + (name.isStatic() ? "S" : "") + (1 + NBitIntegerEncoder.octetsNeeded(4, _context.index(name))))) +
(huffman ? "HuffV" : "LitV") +
(neverIndex ? "!!Idx" : "!Idx");
}
else if (fieldSize >= _context.getMaxDynamicTableSize() || header == HttpHeader.CONTENT_LENGTH && !"0".equals(field.getValue()))
{
- // The field is too large or a non zero content length, so do not index.
+ // The field is too large or a non-zero content length, so do not index.
indexed = false;
encodeName(buffer, (byte)0x00, 4, header.asString(), name);
encodeValue(buffer, true, field.getValue());
if (_debug)
encoding = "Lit" +
- ((name == null) ? "HuffN" : "IdxNS" + (1 + NBitInteger.octectsNeeded(4, _context.index(name)))) +
+ ((name == null) ? "HuffN" : "IdxNS" + (1 + NBitIntegerEncoder.octetsNeeded(4, _context.index(name)))) +
"HuffV!Idx";
}
else
@@ -414,7 +416,7 @@ public class HpackEncoder
encodeName(buffer, (byte)0x40, 6, header.asString(), name);
encodeValue(buffer, huffman, field.getValue());
if (_debug)
- encoding = ((name == null) ? "LitHuffN" : ("LitIdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(6, _context.index(name))))) +
+ encoding = ((name == null) ? "LitHuffN" : ("LitIdxN" + (name.isStatic() ? "S" : "") + (1 + NBitIntegerEncoder.octetsNeeded(6, _context.index(name))))) +
(huffman ? "HuffVIdx" : "LitVIdx");
}
}
@@ -439,12 +441,12 @@ public class HpackEncoder
// leave name index bits as 0
// Encode the name always with lowercase huffman
buffer.put((byte)0x80);
- NBitInteger.encode(buffer, 7, Huffman.octetsNeededLC(name));
- Huffman.encodeLC(buffer, name);
+ NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLowerCase(name));
+ HuffmanEncoder.encodeLowerCase(buffer, name);
}
else
{
- NBitInteger.encode(buffer, bits, _context.index(entry));
+ NBitIntegerEncoder.encode(buffer, bits, _context.index(entry));
}
}
@@ -454,38 +456,19 @@ public class HpackEncoder
{
// huffman literal value
buffer.put((byte)0x80);
-
- int needed = Huffman.octetsNeeded(value);
- if (needed >= 0)
- {
- NBitInteger.encode(buffer, 7, needed);
- Huffman.encode(buffer, value);
- }
- else
- {
- // Not iso_8859_1
- byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
- NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(bytes));
- Huffman.encode(buffer, bytes);
- }
+ int needed = HuffmanEncoder.octetsNeeded(value);
+ NBitIntegerEncoder.encode(buffer, 7, needed);
+ HuffmanEncoder.encode(buffer, value);
}
else
{
// add literal assuming iso_8859_1
buffer.put((byte)0x00).mark();
- NBitInteger.encode(buffer, 7, value.length());
+ NBitIntegerEncoder.encode(buffer, 7, value.length());
for (int i = 0; i < value.length(); i++)
{
char c = value.charAt(i);
- if (c < ' ' || c > 127)
- {
- // Not iso_8859_1, so re-encode as UTF-8
- buffer.reset();
- byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
- NBitInteger.encode(buffer, 7, bytes.length);
- buffer.put(bytes, 0, bytes.length);
- return;
- }
+ c = HttpTokens.sanitizeFieldVchar(c);
buffer.put((byte)c);
}
}
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
index 663bb24d3d6..60faded01aa 100644
--- 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
@@ -13,7 +13,6 @@
package org.eclipse.jetty.http2.hpack;
-@SuppressWarnings("serial")
public abstract class HpackException extends Exception
{
HpackException(String messageFormat, Object... args)
@@ -30,7 +29,7 @@ public abstract class HpackException extends Exception
*/
public static class StreamException extends HpackException
{
- StreamException(String messageFormat, Object... args)
+ public StreamException(String messageFormat, Object... args)
{
super(messageFormat, args);
}
@@ -43,7 +42,7 @@ public abstract class HpackException extends Exception
*/
public static class SessionException extends HpackException
{
- SessionException(String messageFormat, Object... args)
+ public SessionException(String messageFormat, Object... args)
{
super(messageFormat, args);
}
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java
index ae6b7e2d2b4..d3e5f7fddde 100644
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java
+++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java
@@ -18,6 +18,8 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpFieldPreEncoder;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.BufferUtil;
/**
@@ -67,12 +69,12 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder
int nameIdx = HpackContext.staticIndex(header);
if (nameIdx > 0)
- NBitInteger.encode(buffer, bits, nameIdx);
+ NBitIntegerEncoder.encode(buffer, bits, nameIdx);
else
{
buffer.put((byte)0x80);
- NBitInteger.encode(buffer, 7, Huffman.octetsNeededLC(name));
- Huffman.encodeLC(buffer, name);
+ NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLowerCase(name));
+ HuffmanEncoder.encodeLowerCase(buffer, name);
}
HpackEncoder.encodeValue(buffer, huffman, value);
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 83314f0e268..b00be5c232e 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
@@ -34,7 +34,7 @@ public class MetaDataBuilder
private HostPortHttpField _authority;
private String _path;
private String _protocol;
- private long _contentLength = Long.MIN_VALUE;
+ private long _contentLength = -1;
private HpackException.StreamException _streamException;
private boolean _request;
private boolean _response;
@@ -67,17 +67,17 @@ public class MetaDataBuilder
return _size;
}
- public void emit(HttpField field) throws HpackException.SessionException
+ public void emit(HttpField field) throws SessionException
{
HttpHeader header = field.getHeader();
String name = field.getName();
if (name == null || name.length() == 0)
- throw new HpackException.SessionException("Header size 0");
+ throw new SessionException("Header size 0");
String value = field.getValue();
int fieldSize = name.length() + (value == null ? 0 : value.length());
_size += fieldSize + 32;
if (_size > _maxSize)
- throw new HpackException.SessionException("Header size %d > %d", _size, _maxSize);
+ throw new SessionException("Header size %d > %d", _size, _maxSize);
if (field instanceof StaticTableHttpField)
{
@@ -196,7 +196,7 @@ public class MetaDataBuilder
}
}
- protected void streamException(String messageFormat, Object... args)
+ public void streamException(String messageFormat, Object... args)
{
HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args);
if (_streamException == null)
@@ -277,23 +277,7 @@ public class MetaDataBuilder
_path = null;
_protocol = null;
_size = 0;
- _contentLength = Long.MIN_VALUE;
+ _contentLength = -1;
}
}
-
- /**
- * Check that the max size will not be exceeded.
- *
- * @param length the length
- * @param huffman the huffman name
- * @throws SessionException in case of size errors
- */
- 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 HpackException.SessionException("Header too large %d > %d", _size + length, _maxSize);
- }
}
diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/NBitInteger.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/NBitInteger.java
deleted file mode 100644
index 93a9fd4c688..00000000000
--- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/NBitInteger.java
+++ /dev/null
@@ -1,146 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.http2.hpack;
-
-import java.nio.ByteBuffer;
-
-public class NBitInteger
-{
- public static int octectsNeeded(int n, int i)
- {
- if (n == 8)
- {
- int nbits = 0xFF;
- i = i - nbits;
- if (i < 0)
- return 1;
- if (i == 0)
- return 2;
- int lz = Integer.numberOfLeadingZeros(i);
- int log = 32 - lz;
- return 1 + (log + 6) / 7;
- }
-
- int nbits = 0xFF >>> (8 - n);
- i = i - nbits;
- if (i < 0)
- return 0;
- if (i == 0)
- return 1;
- int lz = Integer.numberOfLeadingZeros(i);
- int log = 32 - lz;
- return (log + 6) / 7;
- }
-
- public static void encode(ByteBuffer buf, int n, int i)
- {
- if (n == 8)
- {
- if (i < 0xFF)
- {
- buf.put((byte)i);
- }
- else
- {
- buf.put((byte)0xFF);
-
- int length = i - 0xFF;
- while (true)
- {
- if ((length & ~0x7F) == 0)
- {
- buf.put((byte)length);
- return;
- }
- else
- {
- buf.put((byte)((length & 0x7F) | 0x80));
- length >>>= 7;
- }
- }
- }
- }
- else
- {
- int p = buf.position() - 1;
- int bits = 0xFF >>> (8 - n);
-
- if (i < bits)
- {
- buf.put(p, (byte)((buf.get(p) & ~bits) | i));
- }
- else
- {
- buf.put(p, (byte)(buf.get(p) | bits));
-
- int length = i - bits;
- while (true)
- {
- if ((length & ~0x7F) == 0)
- {
- buf.put((byte)length);
- return;
- }
- else
- {
- buf.put((byte)((length & 0x7F) | 0x80));
- length >>>= 7;
- }
- }
- }
- }
- }
-
- public static int decode(ByteBuffer buffer, int n)
- {
- if (n == 8)
- {
- int nbits = 0xFF;
-
- int i = buffer.get() & 0xff;
-
- if (i == nbits)
- {
- int m = 1;
- int b;
- do
- {
- b = 0xff & buffer.get();
- i = i + (b & 127) * m;
- m = m * 128;
- }
- while ((b & 128) == 128);
- }
- return i;
- }
-
- int nbits = 0xFF >>> (8 - n);
-
- int i = buffer.get(buffer.position() - 1) & nbits;
-
- if (i == nbits)
- {
- int m = 1;
- int b;
- do
- {
- b = 0xff & buffer.get();
- i = i + (b & 127) * m;
- m = m * 128;
- }
- while ((b & 128) == 128);
- }
- return i;
- }
-}
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java
index 96f7c6666e7..e2c5eddd110 100644
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java
+++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java
@@ -16,6 +16,9 @@ package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.compression.EncodingException;
+import org.eclipse.jetty.http.compression.HuffmanDecoder;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
@@ -32,6 +35,32 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class HpackContextTest
{
+ public static String decode(ByteBuffer buffer, int length) throws EncodingException
+ {
+ HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
+ huffmanDecoder.setLength(length);
+ String decoded = huffmanDecoder.decode(buffer);
+ if (decoded == null)
+ throw new EncodingException("invalid string encoding");
+
+ huffmanDecoder.reset();
+ return decoded;
+ }
+
+ public static int decodeInt(ByteBuffer buffer, int prefix) throws EncodingException
+ {
+ // This is a fix for HPACK as it already takes the first byte of the encoded integer.
+ if (prefix != 8)
+ buffer.position(buffer.position() - 1);
+
+ NBitIntegerDecoder decoder = new NBitIntegerDecoder();
+ decoder.setPrefix(prefix);
+ int decodedInt = decoder.decodeInt(buffer);
+ if (decodedInt < 0)
+ throw new EncodingException("invalid integer encoding");
+ decoder.reset();
+ return decodedInt;
+ }
@Test
public void testStaticName()
@@ -423,10 +452,10 @@ public class HpackContextTest
int huff = 0xff & buffer.get();
assertTrue((0x80 & huff) == 0x80);
- int len = NBitInteger.decode(buffer, 7);
+ int len = decodeInt(buffer, 7);
assertEquals(len, buffer.remaining());
- String value = Huffman.decode(buffer);
+ String value = decode(buffer, buffer.remaining());
assertEquals(entry.getHttpField().getValue(), value);
}
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 9f9a62081d2..3a0de9189eb 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
@@ -465,7 +465,7 @@ public class HpackDecoderTest
String encoded = "82868441" + "84" + "49509FFF";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
- assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
+ assertThat(ex.getMessage(), Matchers.containsString("bad_termination"));
}
/* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */
@@ -478,7 +478,7 @@ public class HpackDecoderTest
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
- assertThat(ex.getMessage(), Matchers.containsString("Incorrect padding"));
+ assertThat(ex.getMessage(), Matchers.containsString("incorrect_padding"));
}
/* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */
@@ -491,7 +491,7 @@ public class HpackDecoderTest
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
- assertThat(ex.getMessage(), Matchers.containsString("EOS in content"));
+ assertThat(ex.getMessage(), Matchers.containsString("eos_in_content"));
}
@Test
@@ -503,7 +503,7 @@ public class HpackDecoderTest
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
- assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
+ assertThat(ex.getMessage(), Matchers.containsString("bad_termination"));
}
@Test
@@ -515,7 +515,7 @@ public class HpackDecoderTest
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer));
- assertThat(ex.getMessage(), Matchers.containsString("Bad termination"));
+ assertThat(ex.getMessage(), Matchers.containsString("bad_termination"));
}
@Test
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 576d310937d..8073a331c6a 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
@@ -129,31 +129,32 @@ public class HpackTest
}
catch (HpackException.SessionException e)
{
- assertThat(e.getMessage(), containsString("Header too large"));
+ assertThat(e.getMessage(), containsString("Header size 198 > 164"));
}
}
@Test
- public void encodeDecodeNonAscii() throws Exception
+ public void encodeNonAscii() throws Exception
{
HpackEncoder encoder = new HpackEncoder();
- HpackDecoder decoder = new HpackDecoder(4096, 8192);
ByteBuffer buffer = BufferUtil.allocate(16 * 1024);
HttpFields fields0 = HttpFields.build()
- // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
+ // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
.add("Cookie", "[\uD842\uDF9F]")
.add("custom-key", "[\uD842\uDF9F]");
Response original0 = new MetaData.Response(HttpVersion.HTTP_2, 200, fields0);
- BufferUtil.clearToFill(buffer);
- encoder.encode(buffer, original0);
- BufferUtil.flipToFlush(buffer, 0);
- Response decoded0 = (Response)decoder.decode(buffer);
+ HpackException.SessionException throwable = assertThrows(HpackException.SessionException.class, () ->
+ {
+ BufferUtil.clearToFill(buffer);
+ encoder.encode(buffer, original0);
+ BufferUtil.flipToFlush(buffer, 0);
+ });
- assertMetaDataSame(original0, decoded0);
+ assertThat(throwable.getMessage(), containsString("Could not hpack encode"));
}
-
+
@Test
public void evictReferencedFieldTest() throws Exception
{
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java
deleted file mode 100644
index 72cc6fea983..00000000000
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.http2.hpack;
-
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-import java.util.Locale;
-import java.util.stream.Stream;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.hamcrest.Matchers;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-public class HuffmanTest
-{
- public static Stream data()
- {
- return Stream.of(
- new String[][]{
- {"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"},
- {"D.4.2", "a8eb10649cbf", "no-cache"},
- {"D.6.1k", "6402", "302"},
- {"D.6.1v", "aec3771a4b", "private"},
- {"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
- {"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"},
- {"D.6.2te", "640cff", "303"},
- }).map(Arguments::of);
- }
-
- @ParameterizedTest(name = "[{index}] spec={0}")
- @MethodSource("data")
- public void testDecode(String specSection, String hex, String expected) throws Exception
- {
- byte[] encoded = TypeUtil.fromHexString(hex);
- String decoded = Huffman.decode(ByteBuffer.wrap(encoded));
- assertEquals(expected, decoded, specSection);
- }
-
- @ParameterizedTest(name = "[{index}] spec={0}")
- @MethodSource("data")
- public void testEncode(String specSection, String hex, String expected)
- {
- ByteBuffer buf = BufferUtil.allocate(1024);
- int pos = BufferUtil.flipToFill(buf);
- Huffman.encode(buf, expected);
- BufferUtil.flipToFlush(buf, pos);
- String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
- assertEquals(hex, encoded, specSection);
- assertEquals(hex.length() / 2, Huffman.octetsNeeded(expected));
- }
-
- @ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
- @ValueSource(chars = {(char)128, (char)0, (char)-1, ' ' - 1})
- public void testEncode8859Only(char bad)
- {
- String s = "bad '" + bad + "'";
-
- assertThat(Huffman.octetsNeeded(s), Matchers.is(-1));
-
- assertThrows(BufferOverflowException.class,
- () -> Huffman.encode(BufferUtil.allocate(32), s));
- }
-}
diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java
deleted file mode 100644
index 22b5acb2ce6..00000000000
--- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.http2.hpack;
-
-import java.nio.ByteBuffer;
-
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class NBitIntegerTest
-{
-
- @Test
- public void testOctetsNeeded()
- {
- assertEquals(0, NBitInteger.octectsNeeded(5, 10));
- assertEquals(2, NBitInteger.octectsNeeded(5, 1337));
- assertEquals(1, NBitInteger.octectsNeeded(8, 42));
- assertEquals(3, NBitInteger.octectsNeeded(8, 1337));
-
- assertEquals(0, NBitInteger.octectsNeeded(6, 62));
- assertEquals(1, NBitInteger.octectsNeeded(6, 63));
- assertEquals(1, NBitInteger.octectsNeeded(6, 64));
- assertEquals(2, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
- assertEquals(3, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
- assertEquals(4, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
- }
-
- @Test
- public void testEncode()
- {
- testEncode(6, 0, "00");
- testEncode(6, 1, "01");
- testEncode(6, 62, "3e");
- testEncode(6, 63, "3f00");
- testEncode(6, 63 + 1, "3f01");
- testEncode(6, 63 + 0x7e, "3f7e");
- testEncode(6, 63 + 0x7f, "3f7f");
- testEncode(6, 63 + 0x00 + 0x80 * 0x01, "3f8001");
- testEncode(6, 63 + 0x01 + 0x80 * 0x01, "3f8101");
- testEncode(6, 63 + 0x7f + 0x80 * 0x01, "3fFf01");
- testEncode(6, 63 + 0x00 + 0x80 * 0x02, "3f8002");
- testEncode(6, 63 + 0x01 + 0x80 * 0x02, "3f8102");
- testEncode(6, 63 + 0x7f + 0x80 * 0x7f, "3fFf7f");
- testEncode(6, 63 + 0x00 + 0x80 * 0x80, "3f808001");
- testEncode(6, 63 + 0x7f + 0x80 * 0x80 * 0x7f, "3fFf807f");
- testEncode(6, 63 + 0x00 + 0x80 * 0x80 * 0x80, "3f80808001");
-
- testEncode(8, 0, "00");
- testEncode(8, 1, "01");
- testEncode(8, 128, "80");
- testEncode(8, 254, "Fe");
- testEncode(8, 255, "Ff00");
- testEncode(8, 255 + 1, "Ff01");
- testEncode(8, 255 + 0x7e, "Ff7e");
- testEncode(8, 255 + 0x7f, "Ff7f");
- testEncode(8, 255 + 0x80, "Ff8001");
- testEncode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001");
- }
-
- public void testEncode(int n, int i, String expected)
- {
- ByteBuffer buf = BufferUtil.allocate(16);
- int p = BufferUtil.flipToFill(buf);
- if (n < 8)
- buf.put((byte)0x00);
- NBitInteger.encode(buf, n, i);
- BufferUtil.flipToFlush(buf, p);
- String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
- assertEquals(expected, r);
-
- assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitInteger.octectsNeeded(n, i));
- }
-
- @Test
- public void testDecode()
- {
- testDecode(6, 0, "00");
- testDecode(6, 1, "01");
- testDecode(6, 62, "3e");
- testDecode(6, 63, "3f00");
- testDecode(6, 63 + 1, "3f01");
- testDecode(6, 63 + 0x7e, "3f7e");
- testDecode(6, 63 + 0x7f, "3f7f");
- testDecode(6, 63 + 0x80, "3f8001");
- testDecode(6, 63 + 0x81, "3f8101");
- testDecode(6, 63 + 0x7f + 0x80 * 0x01, "3fFf01");
- testDecode(6, 63 + 0x00 + 0x80 * 0x02, "3f8002");
- testDecode(6, 63 + 0x01 + 0x80 * 0x02, "3f8102");
- testDecode(6, 63 + 0x7f + 0x80 * 0x7f, "3fFf7f");
- testDecode(6, 63 + 0x00 + 0x80 * 0x80, "3f808001");
- testDecode(6, 63 + 0x7f + 0x80 * 0x80 * 0x7f, "3fFf807f");
- testDecode(6, 63 + 0x00 + 0x80 * 0x80 * 0x80, "3f80808001");
-
- testDecode(8, 0, "00");
- testDecode(8, 1, "01");
- testDecode(8, 128, "80");
- testDecode(8, 254, "Fe");
- testDecode(8, 255, "Ff00");
- testDecode(8, 255 + 1, "Ff01");
- testDecode(8, 255 + 0x7e, "Ff7e");
- testDecode(8, 255 + 0x7f, "Ff7f");
- testDecode(8, 255 + 0x80, "Ff8001");
- testDecode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001");
- }
-
- public void testDecode(int n, int expected, String encoded)
- {
- ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
- buf.position(n == 8 ? 0 : 1);
- assertEquals(expected, NBitInteger.decode(buf, n));
- }
-
- @Test
- public void testEncodeExampleD11()
- {
- ByteBuffer buf = BufferUtil.allocate(16);
- int p = BufferUtil.flipToFill(buf);
- buf.put((byte)0x77);
- buf.put((byte)0xFF);
- NBitInteger.encode(buf, 5, 10);
- BufferUtil.flipToFlush(buf, p);
-
- String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
-
- assertEquals("77Ea", r);
- }
-
- @Test
- public void testDecodeExampleD11()
- {
- ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("77EaFF"));
- buf.position(2);
-
- assertEquals(10, NBitInteger.decode(buf, 5));
- }
-
- @Test
- public void testEncodeExampleD12()
- {
- ByteBuffer buf = BufferUtil.allocate(16);
- int p = BufferUtil.flipToFill(buf);
- buf.put((byte)0x88);
- buf.put((byte)0x00);
- NBitInteger.encode(buf, 5, 1337);
- BufferUtil.flipToFlush(buf, p);
-
- String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
-
- assertEquals("881f9a0a", r);
- }
-
- @Test
- public void testDecodeExampleD12()
- {
- ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("881f9a0aff"));
- buf.position(2);
-
- assertEquals(1337, NBitInteger.decode(buf, 5));
- }
-
- @Test
- public void testEncodeExampleD13()
- {
- ByteBuffer buf = BufferUtil.allocate(16);
- int p = BufferUtil.flipToFill(buf);
- buf.put((byte)0x88);
- buf.put((byte)0xFF);
- NBitInteger.encode(buf, 8, 42);
- BufferUtil.flipToFlush(buf, p);
-
- String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
-
- assertEquals("88Ff2a", r);
- }
-
- @Test
- public void testDecodeExampleD13()
- {
- ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("882aFf"));
- buf.position(1);
-
- assertEquals(42, NBitInteger.decode(buf, 8));
- }
-}
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
index 13fd93aac33..d01d59bf6b7 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.instruction.InsertCountIncrementInstruction;
import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction;
@@ -33,7 +34,6 @@ import org.eclipse.jetty.http3.qpack.internal.parser.EncodedFieldSection;
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
import org.eclipse.jetty.http3.qpack.internal.table.StaticTable;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger;
@@ -52,7 +52,7 @@ public class QpackDecoder implements Dumpable
private final QpackContext _context;
private final DecoderInstructionParser _parser;
private final List _encodedFieldSections = new ArrayList<>();
- private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
+ private final NBitIntegerDecoder _integerDecoder = new NBitIntegerDecoder();
private final InstructionHandler _instructionHandler = new InstructionHandler();
private final Map _blockedStreams = new HashMap<>();
private int _maxHeaderSize;
@@ -136,6 +136,7 @@ public class QpackDecoder implements Dumpable
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
// If the buffer is big, don't even think about decoding it
+ // Huffman may double the size, but it will only be a temporary allocation until detected in MetaDataBuilder.emit().
int maxHeaderSize = getMaxHeaderSize();
if (buffer.remaining() > maxHeaderSize)
throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large");
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
index 0c0c4411504..8ce7cc5c32b 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java
@@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.internal.EncodableEntry;
import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.StreamInfo;
@@ -37,7 +38,6 @@ import org.eclipse.jetty.http3.qpack.internal.metadata.Http3Fields;
import org.eclipse.jetty.http3.qpack.internal.parser.EncoderInstructionParser;
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock;
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/EncodableEntry.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/EncodableEntry.java
index c8abe4688ba..4d354b43544 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/EncodableEntry.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/EncodableEntry.java
@@ -14,14 +14,15 @@
package org.eclipse.jetty.http3.qpack.internal;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.PreEncodedHttpField;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
-import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
public abstract class EncodableEntry
{
@@ -95,19 +96,19 @@ public abstract class EncodableEntry
{
// Indexed Field Line with Static Reference.
int relativeIndex = _entry.getIndex();
- return 1 + NBitIntegerEncoder.octectsNeeded(6, relativeIndex);
+ return 1 + NBitIntegerEncoder.octetsNeeded(6, relativeIndex);
}
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
int relativeIndex = base - (_entry.getIndex() + 1);
- return 1 + NBitIntegerEncoder.octectsNeeded(6, relativeIndex);
+ return 1 + NBitIntegerEncoder.octetsNeeded(6, relativeIndex);
}
else
{
// Indexed Field Line with Post-Base Index.
int relativeIndex = _entry.getIndex() - base;
- return 1 + NBitIntegerEncoder.octectsNeeded(4, relativeIndex);
+ return 1 + NBitIntegerEncoder.octetsNeeded(4, relativeIndex);
}
}
@@ -173,7 +174,7 @@ public abstract class EncodableEntry
{
buffer.put((byte)0x00);
NBitIntegerEncoder.encode(buffer, 7, value.length());
- buffer.put(value.getBytes());
+ buffer.put(value.getBytes(StandardCharsets.ISO_8859_1));
}
}
@@ -181,9 +182,26 @@ public abstract class EncodableEntry
public int getRequiredSize(int base)
{
String value = getValue();
- int relativeIndex = _nameEntry.getIndex() - base;
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
- return 1 + NBitIntegerEncoder.octectsNeeded(4, relativeIndex) + 1 + NBitIntegerEncoder.octectsNeeded(7, valueLength) + valueLength;
+
+ int nameOctets;
+ if (_nameEntry.isStatic())
+ {
+ int relativeIndex = _nameEntry.getIndex();
+ nameOctets = NBitIntegerEncoder.octetsNeeded(4, relativeIndex);
+ }
+ else if (_nameEntry.getIndex() < base)
+ {
+ int relativeIndex = base - (_nameEntry.getIndex() + 1);
+ nameOctets = NBitIntegerEncoder.octetsNeeded(4, relativeIndex);
+ }
+ else
+ {
+ int relativeIndex = _nameEntry.getIndex() - base;
+ nameOctets = NBitIntegerEncoder.octetsNeeded(3, relativeIndex);
+ }
+
+ return 1 + nameOctets + 1 + NBitIntegerEncoder.octetsNeeded(7, valueLength) + valueLength;
}
@Override
@@ -229,13 +247,12 @@ public abstract class EncodableEntry
}
else
{
- // TODO: What charset should we be using? (this applies to the instruction generators as well).
buffer.put((byte)(0x20 | allowIntermediary));
NBitIntegerEncoder.encode(buffer, 3, name.length());
- buffer.put(name.getBytes());
+ buffer.put(name.getBytes(StandardCharsets.ISO_8859_1));
buffer.put((byte)0x00);
NBitIntegerEncoder.encode(buffer, 7, value.length());
- buffer.put(value.getBytes());
+ buffer.put(value.getBytes(StandardCharsets.ISO_8859_1));
}
}
@@ -246,7 +263,7 @@ public abstract class EncodableEntry
String value = getValue();
int nameLength = _huffman ? HuffmanEncoder.octetsNeeded(name) : name.length();
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
- return 2 + NBitIntegerEncoder.octectsNeeded(3, nameLength) + nameLength + NBitIntegerEncoder.octectsNeeded(7, valueLength) + valueLength;
+ return 2 + NBitIntegerEncoder.octetsNeeded(3, nameLength) + nameLength + NBitIntegerEncoder.octetsNeeded(7, valueLength) + valueLength;
}
@Override
@@ -268,7 +285,6 @@ public abstract class EncodableEntry
}
}
- // TODO: pass in the HTTP version to avoid hard coding HTTP3?
private static class PreEncodedEntry extends EncodableEntry
{
private final PreEncodedHttpField _httpField;
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/DuplicateInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/DuplicateInstruction.java
index 286a57a7c26..8ec1cb827e8 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/DuplicateInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/DuplicateInstruction.java
@@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -37,7 +37,7 @@ public class DuplicateInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
- int size = NBitIntegerEncoder.octectsNeeded(5, _index) + 1;
+ int size = NBitIntegerEncoder.octetsNeeded(5, _index) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00);
NBitIntegerEncoder.encode(buffer, 5, _index);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/IndexedNameEntryInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/IndexedNameEntryInstruction.java
index fccb7824bd4..555703192b1 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/IndexedNameEntryInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/IndexedNameEntryInstruction.java
@@ -14,10 +14,11 @@
package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -54,7 +55,7 @@ public class IndexedNameEntryInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
- int size = NBitIntegerEncoder.octectsNeeded(6, _index) + (_huffman ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2;
+ int size = NBitIntegerEncoder.octetsNeeded(6, _index) + (_huffman ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2;
ByteBuffer buffer = lease.acquire(size, false);
// First bit indicates the instruction, second bit is whether it is a dynamic table reference or not.
@@ -72,7 +73,7 @@ public class IndexedNameEntryInstruction implements Instruction
{
buffer.put((byte)(0x00));
NBitIntegerEncoder.encode(buffer, 7, _value.length());
- buffer.put(_value.getBytes());
+ buffer.put(_value.getBytes(StandardCharsets.ISO_8859_1));
}
BufferUtil.flipToFlush(buffer, 0);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java
index a41818282b5..5d883a8572e 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java
@@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -37,7 +37,7 @@ public class InsertCountIncrementInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
- int size = NBitIntegerEncoder.octectsNeeded(6, _increment) + 1;
+ int size = NBitIntegerEncoder.octetsNeeded(6, _increment) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00);
NBitIntegerEncoder.encode(buffer, 6, _increment);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/LiteralNameEntryInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/LiteralNameEntryInstruction.java
index dfa5ba4909c..79947c0a43f 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/LiteralNameEntryInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/LiteralNameEntryInstruction.java
@@ -14,11 +14,12 @@
package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -69,7 +70,7 @@ public class LiteralNameEntryInstruction implements Instruction
{
buffer.put((byte)(0x40));
NBitIntegerEncoder.encode(buffer, 5, _name.length());
- buffer.put(_name.getBytes());
+ buffer.put(_name.getBytes(StandardCharsets.ISO_8859_1));
}
if (_huffmanValue)
@@ -81,8 +82,8 @@ public class LiteralNameEntryInstruction implements Instruction
else
{
buffer.put((byte)(0x00));
- NBitIntegerEncoder.encode(buffer, 5, _value.length());
- buffer.put(_value.getBytes());
+ NBitIntegerEncoder.encode(buffer, 7, _value.length());
+ buffer.put(_value.getBytes(StandardCharsets.ISO_8859_1));
}
BufferUtil.flipToFlush(buffer, 0);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java
index cedb01f1fad..efb107f32c6 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java
@@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -37,7 +37,7 @@ public class SectionAcknowledgmentInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
- int size = NBitIntegerEncoder.octectsNeeded(7, _streamId) + 1;
+ int size = NBitIntegerEncoder.octetsNeeded(7, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x80);
NBitIntegerEncoder.encode(buffer, 7, _streamId);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SetCapacityInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SetCapacityInstruction.java
index a91511f8e8d..24eb0f44611 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SetCapacityInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SetCapacityInstruction.java
@@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -37,7 +37,7 @@ public class SetCapacityInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
- int size = NBitIntegerEncoder.octectsNeeded(5, _capacity) + 1;
+ int size = NBitIntegerEncoder.octetsNeeded(5, _capacity) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x20);
NBitIntegerEncoder.encode(buffer, 5, _capacity);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/StreamCancellationInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/StreamCancellationInstruction.java
index 10f976e6049..5dcf89f2bfd 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/StreamCancellationInstruction.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/StreamCancellationInstruction.java
@@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@@ -32,7 +32,7 @@ public class StreamCancellationInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
- int size = NBitIntegerEncoder.octectsNeeded(6, _streamId) + 1;
+ int size = NBitIntegerEncoder.octetsNeeded(6, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x40);
NBitIntegerEncoder.encode(buffer, 6, _streamId);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/DecoderInstructionParser.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/DecoderInstructionParser.java
index 45c02fdd1a3..dc3a227fa81 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/DecoderInstructionParser.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/DecoderInstructionParser.java
@@ -15,10 +15,10 @@ package org.eclipse.jetty.http3.qpack.internal.parser;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.EncodingException;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
+import org.eclipse.jetty.http.compression.NBitStringDecoder;
import org.eclipse.jetty.http3.qpack.QpackException;
-import org.eclipse.jetty.http3.qpack.internal.util.EncodingException;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitStringParser;
/**
* Parses a stream of unframed instructions for the Decoder. These instructions are sent from the remote Encoder.
@@ -26,8 +26,8 @@ import org.eclipse.jetty.http3.qpack.internal.util.NBitStringParser;
public class DecoderInstructionParser
{
private final Handler _handler;
- private final NBitStringParser _stringParser;
- private final NBitIntegerParser _integerParser;
+ private final NBitStringDecoder _stringDecoder;
+ private final NBitIntegerDecoder _integerDecoder;
private State _state = State.PARSING;
private Operation _operation = Operation.NONE;
@@ -66,8 +66,8 @@ public class DecoderInstructionParser
public DecoderInstructionParser(Handler handler)
{
_handler = handler;
- _stringParser = new NBitStringParser();
- _integerParser = new NBitIntegerParser();
+ _stringDecoder = new NBitStringDecoder();
+ _integerDecoder = new NBitIntegerDecoder();
}
public void parse(ByteBuffer buffer) throws QpackException, EncodingException
@@ -92,13 +92,13 @@ public class DecoderInstructionParser
else if ((firstByte & 0x20) != 0)
{
_state = State.SET_CAPACITY;
- _integerParser.setPrefix(5);
+ _integerDecoder.setPrefix(5);
parseSetDynamicTableCapacity(buffer);
}
else
{
_state = State.DUPLICATE;
- _integerParser.setPrefix(5);
+ _integerDecoder.setPrefix(5);
parseDuplicate(buffer);
}
break;
@@ -134,20 +134,20 @@ public class DecoderInstructionParser
byte firstByte = buffer.get(buffer.position());
_referenceDynamicTable = (firstByte & 0x40) == 0;
_operation = Operation.INDEX;
- _integerParser.setPrefix(6);
+ _integerDecoder.setPrefix(6);
continue;
case INDEX:
- _index = _integerParser.decodeInt(buffer);
+ _index = _integerDecoder.decodeInt(buffer);
if (_index < 0)
return;
_operation = Operation.VALUE;
- _stringParser.setPrefix(8);
+ _stringDecoder.setPrefix(8);
continue;
case VALUE:
- String value = _stringParser.decode(buffer);
+ String value = _stringDecoder.decode(buffer);
if (value == null)
return;
@@ -171,20 +171,20 @@ public class DecoderInstructionParser
{
case NONE:
_operation = Operation.NAME;
- _stringParser.setPrefix(6);
+ _stringDecoder.setPrefix(6);
continue;
case NAME:
- _name = _stringParser.decode(buffer);
+ _name = _stringDecoder.decode(buffer);
if (_name == null)
return;
_operation = Operation.VALUE;
- _stringParser.setPrefix(8);
+ _stringDecoder.setPrefix(8);
continue;
case VALUE:
- String value = _stringParser.decode(buffer);
+ String value = _stringDecoder.decode(buffer);
if (value == null)
return;
@@ -201,7 +201,7 @@ public class DecoderInstructionParser
private void parseDuplicate(ByteBuffer buffer) throws QpackException
{
- int index = _integerParser.decodeInt(buffer);
+ int index = _integerDecoder.decodeInt(buffer);
if (index >= 0)
{
reset();
@@ -211,7 +211,7 @@ public class DecoderInstructionParser
private void parseSetDynamicTableCapacity(ByteBuffer buffer) throws QpackException
{
- int capacity = _integerParser.decodeInt(buffer);
+ int capacity = _integerDecoder.decodeInt(buffer);
if (capacity >= 0)
{
reset();
@@ -221,8 +221,8 @@ public class DecoderInstructionParser
public void reset()
{
- _stringParser.reset();
- _integerParser.reset();
+ _stringDecoder.reset();
+ _integerDecoder.reset();
_state = State.PARSING;
_operation = Operation.NONE;
_referenceDynamicTable = false;
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java
index 50023cc9ebb..ddcb939dacf 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncodedFieldSection.java
@@ -19,13 +19,13 @@ import java.util.List;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http.compression.EncodingException;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
+import org.eclipse.jetty.http.compression.NBitStringDecoder;
import org.eclipse.jetty.http3.qpack.QpackDecoder;
import org.eclipse.jetty.http3.qpack.QpackException;
import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.metadata.MetaDataBuilder;
-import org.eclipse.jetty.http3.qpack.internal.util.EncodingException;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitStringParser;
import org.eclipse.jetty.util.BufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,8 +36,8 @@ public class EncodedFieldSection
{
private static final Logger LOG = LoggerFactory.getLogger(EncodedFieldSection.class);
- private final NBitIntegerParser _integerParser = new NBitIntegerParser();
- private final NBitStringParser _stringParser = new NBitStringParser();
+ private final NBitIntegerDecoder _integerDecoder = new NBitIntegerDecoder();
+ private final NBitStringDecoder _stringDecoder = new NBitStringDecoder();
private final List _encodedFields = new ArrayList<>();
private final long _streamId;
@@ -111,8 +111,8 @@ public class EncodedFieldSection
{
byte firstByte = buffer.get(buffer.position());
boolean dynamicTable = (firstByte & 0x40) == 0;
- _integerParser.setPrefix(6);
- int index = _integerParser.decodeInt(buffer);
+ _integerDecoder.setPrefix(6);
+ int index = _integerDecoder.decodeInt(buffer);
if (index < 0)
throw new EncodingException("invalid_index");
return new IndexedField(dynamicTable, index);
@@ -120,8 +120,8 @@ public class EncodedFieldSection
private EncodedField parseIndexedFieldPostBase(ByteBuffer buffer) throws EncodingException
{
- _integerParser.setPrefix(4);
- int index = _integerParser.decodeInt(buffer);
+ _integerDecoder.setPrefix(4);
+ int index = _integerDecoder.decodeInt(buffer);
if (index < 0)
throw new EncodingException("Invalid Index");
@@ -137,13 +137,13 @@ public class EncodedFieldSection
boolean allowEncoding = (firstByte & 0x20) != 0;
boolean dynamicTable = (firstByte & 0x10) == 0;
- _integerParser.setPrefix(4);
- int nameIndex = _integerParser.decodeInt(buffer);
+ _integerDecoder.setPrefix(4);
+ int nameIndex = _integerDecoder.decodeInt(buffer);
if (nameIndex < 0)
throw new EncodingException("invalid_name_index");
- _stringParser.setPrefix(8);
- String value = _stringParser.decode(buffer);
+ _stringDecoder.setPrefix(8);
+ String value = _stringDecoder.decode(buffer);
if (value == null)
throw new EncodingException("incomplete_value");
@@ -155,13 +155,13 @@ public class EncodedFieldSection
byte firstByte = buffer.get(buffer.position());
boolean allowEncoding = (firstByte & 0x08) != 0;
- _integerParser.setPrefix(3);
- int nameIndex = _integerParser.decodeInt(buffer);
+ _integerDecoder.setPrefix(3);
+ int nameIndex = _integerDecoder.decodeInt(buffer);
if (nameIndex < 0)
throw new EncodingException("invalid_index");
- _stringParser.setPrefix(8);
- String value = _stringParser.decode(buffer);
+ _stringDecoder.setPrefix(8);
+ String value = _stringDecoder.decode(buffer);
if (value == null)
throw new EncodingException("invalid_value");
@@ -173,13 +173,13 @@ public class EncodedFieldSection
byte firstByte = buffer.get(buffer.position());
boolean allowEncoding = (firstByte & 0x10) != 0;
- _stringParser.setPrefix(4);
- String name = _stringParser.decode(buffer);
+ _stringDecoder.setPrefix(4);
+ String name = _stringDecoder.decode(buffer);
if (name == null)
throw new EncodingException("invalid_name");
- _stringParser.setPrefix(8);
- String value = _stringParser.decode(buffer);
+ _stringDecoder.setPrefix(8);
+ String value = _stringDecoder.decode(buffer);
if (value == null)
throw new EncodingException("invalid_value");
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncoderInstructionParser.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncoderInstructionParser.java
index 48a17eb19bd..87f56cfc86d 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncoderInstructionParser.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/parser/EncoderInstructionParser.java
@@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.parser;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
import org.eclipse.jetty.http3.qpack.QpackException;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
/**
* Parses a stream of unframed instructions for the Encoder. These instructions are sent from the remote Decoder.
@@ -28,7 +28,7 @@ public class EncoderInstructionParser
private static final int INSERT_COUNT_INCREMENT_PREFIX = 6;
private final Handler _handler;
- private final NBitIntegerParser _integerParser;
+ private final NBitIntegerDecoder _integerDecoder;
private State _state = State.IDLE;
private enum State
@@ -51,7 +51,7 @@ public class EncoderInstructionParser
public EncoderInstructionParser(Handler handler)
{
_handler = handler;
- _integerParser = new NBitIntegerParser();
+ _integerDecoder = new NBitIntegerDecoder();
}
public void parse(ByteBuffer buffer) throws QpackException
@@ -67,19 +67,19 @@ public class EncoderInstructionParser
if ((firstByte & 0x80) != 0)
{
_state = State.SECTION_ACKNOWLEDGEMENT;
- _integerParser.setPrefix(SECTION_ACKNOWLEDGEMENT_PREFIX);
+ _integerDecoder.setPrefix(SECTION_ACKNOWLEDGEMENT_PREFIX);
parseSectionAcknowledgment(buffer);
}
else if ((firstByte & 0x40) != 0)
{
_state = State.STREAM_CANCELLATION;
- _integerParser.setPrefix(STREAM_CANCELLATION_PREFIX);
+ _integerDecoder.setPrefix(STREAM_CANCELLATION_PREFIX);
parseStreamCancellation(buffer);
}
else
{
_state = State.INSERT_COUNT_INCREMENT;
- _integerParser.setPrefix(INSERT_COUNT_INCREMENT_PREFIX);
+ _integerDecoder.setPrefix(INSERT_COUNT_INCREMENT_PREFIX);
parseInsertCountIncrement(buffer);
}
break;
@@ -103,7 +103,7 @@ public class EncoderInstructionParser
private void parseSectionAcknowledgment(ByteBuffer buffer) throws QpackException
{
- long streamId = _integerParser.decodeInt(buffer);
+ long streamId = _integerDecoder.decodeInt(buffer);
if (streamId >= 0)
{
reset();
@@ -113,7 +113,7 @@ public class EncoderInstructionParser
private void parseStreamCancellation(ByteBuffer buffer) throws QpackException
{
- long streamId = _integerParser.decodeLong(buffer);
+ long streamId = _integerDecoder.decodeLong(buffer);
if (streamId >= 0)
{
reset();
@@ -123,7 +123,7 @@ public class EncoderInstructionParser
private void parseInsertCountIncrement(ByteBuffer buffer) throws QpackException
{
- int increment = _integerParser.decodeInt(buffer);
+ int increment = _integerDecoder.decodeInt(buffer);
if (increment >= 0)
{
reset();
@@ -134,6 +134,6 @@ public class EncoderInstructionParser
public void reset()
{
_state = State.IDLE;
- _integerParser.reset();
+ _integerDecoder.reset();
}
}
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/table/Entry.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/table/Entry.java
index 7af81578208..9f7f7af4c06 100644
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/table/Entry.java
+++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/table/Entry.java
@@ -17,8 +17,8 @@ import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
-import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
+import org.eclipse.jetty.http.compression.HuffmanEncoder;
+import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.StringUtil;
public class Entry
@@ -119,7 +119,7 @@ public class Entry
int huffmanLen = HuffmanEncoder.octetsNeeded(value);
if (huffmanLen < 0)
throw new IllegalStateException("bad value");
- int lenLen = NBitIntegerEncoder.octectsNeeded(7, huffmanLen);
+ int lenLen = NBitIntegerEncoder.octetsNeeded(7, huffmanLen);
_huffmanValue = new byte[1 + lenLen + huffmanLen];
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/HuffmanEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/HuffmanEncoder.java
deleted file mode 100644
index 2ce68d01706..00000000000
--- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/util/HuffmanEncoder.java
+++ /dev/null
@@ -1,473 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.http3.qpack.internal.util;
-
-import java.nio.ByteBuffer;
-
-public class HuffmanEncoder
-{
-
- // Appendix C: Huffman Codes
- // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C
- static final int[][] CODES =
- {
- /* ( 0) |11111111|11000 */ {0x1ff8, 13},
- /* ( 1) |11111111|11111111|1011000 */ {0x7fffd8, 23},
- /* ( 2) |11111111|11111111|11111110|0010 */ {0xfffffe2, 28},
- /* ( 3) |11111111|11111111|11111110|0011 */ {0xfffffe3, 28},
- /* ( 4) |11111111|11111111|11111110|0100 */ {0xfffffe4, 28},
- /* ( 5) |11111111|11111111|11111110|0101 */ {0xfffffe5, 28},
- /* ( 6) |11111111|11111111|11111110|0110 */ {0xfffffe6, 28},
- /* ( 7) |11111111|11111111|11111110|0111 */ {0xfffffe7, 28},
- /* ( 8) |11111111|11111111|11111110|1000 */ {0xfffffe8, 28},
- /* ( 9) |11111111|11111111|11101010 */ {0xffffea, 24},
- /* ( 10) |11111111|11111111|11111111|111100 */ {0x3ffffffc, 30},
- /* ( 11) |11111111|11111111|11111110|1001 */ {0xfffffe9, 28},
- /* ( 12) |11111111|11111111|11111110|1010 */ {0xfffffea, 28},
- /* ( 13) |11111111|11111111|11111111|111101 */ {0x3ffffffd, 30},
- /* ( 14) |11111111|11111111|11111110|1011 */ {0xfffffeb, 28},
- /* ( 15) |11111111|11111111|11111110|1100 */ {0xfffffec, 28},
- /* ( 16) |11111111|11111111|11111110|1101 */ {0xfffffed, 28},
- /* ( 17) |11111111|11111111|11111110|1110 */ {0xfffffee, 28},
- /* ( 18) |11111111|11111111|11111110|1111 */ {0xfffffef, 28},
- /* ( 19) |11111111|11111111|11111111|0000 */ {0xffffff0, 28},
- /* ( 20) |11111111|11111111|11111111|0001 */ {0xffffff1, 28},
- /* ( 21) |11111111|11111111|11111111|0010 */ {0xffffff2, 28},
- /* ( 22) |11111111|11111111|11111111|111110 */ {0x3ffffffe, 30},
- /* ( 23) |11111111|11111111|11111111|0011 */ {0xffffff3, 28},
- /* ( 24) |11111111|11111111|11111111|0100 */ {0xffffff4, 28},
- /* ( 25) |11111111|11111111|11111111|0101 */ {0xffffff5, 28},
- /* ( 26) |11111111|11111111|11111111|0110 */ {0xffffff6, 28},
- /* ( 27) |11111111|11111111|11111111|0111 */ {0xffffff7, 28},
- /* ( 28) |11111111|11111111|11111111|1000 */ {0xffffff8, 28},
- /* ( 29) |11111111|11111111|11111111|1001 */ {0xffffff9, 28},
- /* ( 30) |11111111|11111111|11111111|1010 */ {0xffffffa, 28},
- /* ( 31) |11111111|11111111|11111111|1011 */ {0xffffffb, 28},
- /*' ' ( 32) |010100 */ {0x14, 6},
- /*'!' ( 33) |11111110|00 */ {0x3f8, 10},
- /*'"' ( 34) |11111110|01 */ {0x3f9, 10},
- /*'#' ( 35) |11111111|1010 */ {0xffa, 12},
- /*'$' ( 36) |11111111|11001 */ {0x1ff9, 13},
- /*'%' ( 37) |010101 */ {0x15, 6},
- /*'&' ( 38) |11111000 */ {0xf8, 8},
- /*''' ( 39) |11111111|010 */ {0x7fa, 11},
- /*'(' ( 40) |11111110|10 */ {0x3fa, 10},
- /*')' ( 41) |11111110|11 */ {0x3fb, 10},
- /*'*' ( 42) |11111001 */ {0xf9, 8},
- /*'+' ( 43) |11111111|011 */ {0x7fb, 11},
- /*',' ( 44) |11111010 */ {0xfa, 8},
- /*'-' ( 45) |010110 */ {0x16, 6},
- /*'.' ( 46) |010111 */ {0x17, 6},
- /*'/' ( 47) |011000 */ {0x18, 6},
- /*'0' ( 48) |00000 */ {0x0, 5},
- /*'1' ( 49) |00001 */ {0x1, 5},
- /*'2' ( 50) |00010 */ {0x2, 5},
- /*'3' ( 51) |011001 */ {0x19, 6},
- /*'4' ( 52) |011010 */ {0x1a, 6},
- /*'5' ( 53) |011011 */ {0x1b, 6},
- /*'6' ( 54) |011100 */ {0x1c, 6},
- /*'7' ( 55) |011101 */ {0x1d, 6},
- /*'8' ( 56) |011110 */ {0x1e, 6},
- /*'9' ( 57) |011111 */ {0x1f, 6},
- /*':' ( 58) |1011100 */ {0x5c, 7},
- /*';' ( 59) |11111011 */ {0xfb, 8},
- /*'<' ( 60) |11111111|1111100 */ {0x7ffc, 15},
- /*'=' ( 61) |100000 */ {0x20, 6},
- /*'>' ( 62) |11111111|1011 */ {0xffb, 12},
- /*'?' ( 63) |11111111|00 */ {0x3fc, 10},
- /*'@' ( 64) |11111111|11010 */ {0x1ffa, 13},
- /*'A' ( 65) |100001 */ {0x21, 6},
- /*'B' ( 66) |1011101 */ {0x5d, 7},
- /*'C' ( 67) |1011110 */ {0x5e, 7},
- /*'D' ( 68) |1011111 */ {0x5f, 7},
- /*'E' ( 69) |1100000 */ {0x60, 7},
- /*'F' ( 70) |1100001 */ {0x61, 7},
- /*'G' ( 71) |1100010 */ {0x62, 7},
- /*'H' ( 72) |1100011 */ {0x63, 7},
- /*'I' ( 73) |1100100 */ {0x64, 7},
- /*'J' ( 74) |1100101 */ {0x65, 7},
- /*'K' ( 75) |1100110 */ {0x66, 7},
- /*'L' ( 76) |1100111 */ {0x67, 7},
- /*'M' ( 77) |1101000 */ {0x68, 7},
- /*'N' ( 78) |1101001 */ {0x69, 7},
- /*'O' ( 79) |1101010 */ {0x6a, 7},
- /*'P' ( 80) |1101011 */ {0x6b, 7},
- /*'Q' ( 81) |1101100 */ {0x6c, 7},
- /*'R' ( 82) |1101101 */ {0x6d, 7},
- /*'S' ( 83) |1101110 */ {0x6e, 7},
- /*'T' ( 84) |1101111 */ {0x6f, 7},
- /*'U' ( 85) |1110000 */ {0x70, 7},
- /*'V' ( 86) |1110001 */ {0x71, 7},
- /*'W' ( 87) |1110010 */ {0x72, 7},
- /*'X' ( 88) |11111100 */ {0xfc, 8},
- /*'Y' ( 89) |1110011 */ {0x73, 7},
- /*'Z' ( 90) |11111101 */ {0xfd, 8},
- /*'[' ( 91) |11111111|11011 */ {0x1ffb, 13},
- /*'\' ( 92) |11111111|11111110|000 */ {0x7fff0, 19},
- /*']' ( 93) |11111111|11100 */ {0x1ffc, 13},
- /*'^' ( 94) |11111111|111100 */ {0x3ffc, 14},
- /*'_' ( 95) |100010 */ {0x22, 6},
- /*'`' ( 96) |11111111|1111101 */ {0x7ffd, 15},
- /*'a' ( 97) |00011 */ {0x3, 5},
- /*'b' ( 98) |100011 */ {0x23, 6},
- /*'c' ( 99) |00100 */ {0x4, 5},
- /*'d' (100) |100100 */ {0x24, 6},
- /*'e' (101) |00101 */ {0x5, 5},
- /*'f' (102) |100101 */ {0x25, 6},
- /*'g' (103) |100110 */ {0x26, 6},
- /*'h' (104) |100111 */ {0x27, 6},
- /*'i' (105) |00110 */ {0x6, 5},
- /*'j' (106) |1110100 */ {0x74, 7},
- /*'k' (107) |1110101 */ {0x75, 7},
- /*'l' (108) |101000 */ {0x28, 6},
- /*'m' (109) |101001 */ {0x29, 6},
- /*'n' (110) |101010 */ {0x2a, 6},
- /*'o' (111) |00111 */ {0x7, 5},
- /*'p' (112) |101011 */ {0x2b, 6},
- /*'q' (113) |1110110 */ {0x76, 7},
- /*'r' (114) |101100 */ {0x2c, 6},
- /*'s' (115) |01000 */ {0x8, 5},
- /*'t' (116) |01001 */ {0x9, 5},
- /*'u' (117) |101101 */ {0x2d, 6},
- /*'v' (118) |1110111 */ {0x77, 7},
- /*'w' (119) |1111000 */ {0x78, 7},
- /*'x' (120) |1111001 */ {0x79, 7},
- /*'y' (121) |1111010 */ {0x7a, 7},
- /*'z' (122) |1111011 */ {0x7b, 7},
- /*'{' (123) |11111111|1111110 */ {0x7ffe, 15},
- /*'|' (124) |11111111|100 */ {0x7fc, 11},
- /*'}' (125) |11111111|111101 */ {0x3ffd, 14},
- /*'~' (126) |11111111|11101 */ {0x1ffd, 13},
- /* (127) |11111111|11111111|11111111|1100 */ {0xffffffc, 28},
- /* (128) |11111111|11111110|0110 */ {0xfffe6, 20},
- /* (129) |11111111|11111111|010010 */ {0x3fffd2, 22},
- /* (130) |11111111|11111110|0111 */ {0xfffe7, 20},
- /* (131) |11111111|11111110|1000 */ {0xfffe8, 20},
- /* (132) |11111111|11111111|010011 */ {0x3fffd3, 22},
- /* (133) |11111111|11111111|010100 */ {0x3fffd4, 22},
- /* (134) |11111111|11111111|010101 */ {0x3fffd5, 22},
- /* (135) |11111111|11111111|1011001 */ {0x7fffd9, 23},
- /* (136) |11111111|11111111|010110 */ {0x3fffd6, 22},
- /* (137) |11111111|11111111|1011010 */ {0x7fffda, 23},
- /* (138) |11111111|11111111|1011011 */ {0x7fffdb, 23},
- /* (139) |11111111|11111111|1011100 */ {0x7fffdc, 23},
- /* (140) |11111111|11111111|1011101 */ {0x7fffdd, 23},
- /* (141) |11111111|11111111|1011110 */ {0x7fffde, 23},
- /* (142) |11111111|11111111|11101011 */ {0xffffeb, 24},
- /* (143) |11111111|11111111|1011111 */ {0x7fffdf, 23},
- /* (144) |11111111|11111111|11101100 */ {0xffffec, 24},
- /* (145) |11111111|11111111|11101101 */ {0xffffed, 24},
- /* (146) |11111111|11111111|010111 */ {0x3fffd7, 22},
- /* (147) |11111111|11111111|1100000 */ {0x7fffe0, 23},
- /* (148) |11111111|11111111|11101110 */ {0xffffee, 24},
- /* (149) |11111111|11111111|1100001 */ {0x7fffe1, 23},
- /* (150) |11111111|11111111|1100010 */ {0x7fffe2, 23},
- /* (151) |11111111|11111111|1100011 */ {0x7fffe3, 23},
- /* (152) |11111111|11111111|1100100 */ {0x7fffe4, 23},
- /* (153) |11111111|11111110|11100 */ {0x1fffdc, 21},
- /* (154) |11111111|11111111|011000 */ {0x3fffd8, 22},
- /* (155) |11111111|11111111|1100101 */ {0x7fffe5, 23},
- /* (156) |11111111|11111111|011001 */ {0x3fffd9, 22},
- /* (157) |11111111|11111111|1100110 */ {0x7fffe6, 23},
- /* (158) |11111111|11111111|1100111 */ {0x7fffe7, 23},
- /* (159) |11111111|11111111|11101111 */ {0xffffef, 24},
- /* (160) |11111111|11111111|011010 */ {0x3fffda, 22},
- /* (161) |11111111|11111110|11101 */ {0x1fffdd, 21},
- /* (162) |11111111|11111110|1001 */ {0xfffe9, 20},
- /* (163) |11111111|11111111|011011 */ {0x3fffdb, 22},
- /* (164) |11111111|11111111|011100 */ {0x3fffdc, 22},
- /* (165) |11111111|11111111|1101000 */ {0x7fffe8, 23},
- /* (166) |11111111|11111111|1101001 */ {0x7fffe9, 23},
- /* (167) |11111111|11111110|11110 */ {0x1fffde, 21},
- /* (168) |11111111|11111111|1101010 */ {0x7fffea, 23},
- /* (169) |11111111|11111111|011101 */ {0x3fffdd, 22},
- /* (170) |11111111|11111111|011110 */ {0x3fffde, 22},
- /* (171) |11111111|11111111|11110000 */ {0xfffff0, 24},
- /* (172) |11111111|11111110|11111 */ {0x1fffdf, 21},
- /* (173) |11111111|11111111|011111 */ {0x3fffdf, 22},
- /* (174) |11111111|11111111|1101011 */ {0x7fffeb, 23},
- /* (175) |11111111|11111111|1101100 */ {0x7fffec, 23},
- /* (176) |11111111|11111111|00000 */ {0x1fffe0, 21},
- /* (177) |11111111|11111111|00001 */ {0x1fffe1, 21},
- /* (178) |11111111|11111111|100000 */ {0x3fffe0, 22},
- /* (179) |11111111|11111111|00010 */ {0x1fffe2, 21},
- /* (180) |11111111|11111111|1101101 */ {0x7fffed, 23},
- /* (181) |11111111|11111111|100001 */ {0x3fffe1, 22},
- /* (182) |11111111|11111111|1101110 */ {0x7fffee, 23},
- /* (183) |11111111|11111111|1101111 */ {0x7fffef, 23},
- /* (184) |11111111|11111110|1010 */ {0xfffea, 20},
- /* (185) |11111111|11111111|100010 */ {0x3fffe2, 22},
- /* (186) |11111111|11111111|100011 */ {0x3fffe3, 22},
- /* (187) |11111111|11111111|100100 */ {0x3fffe4, 22},
- /* (188) |11111111|11111111|1110000 */ {0x7ffff0, 23},
- /* (189) |11111111|11111111|100101 */ {0x3fffe5, 22},
- /* (190) |11111111|11111111|100110 */ {0x3fffe6, 22},
- /* (191) |11111111|11111111|1110001 */ {0x7ffff1, 23},
- /* (192) |11111111|11111111|11111000|00 */ {0x3ffffe0, 26},
- /* (193) |11111111|11111111|11111000|01 */ {0x3ffffe1, 26},
- /* (194) |11111111|11111110|1011 */ {0xfffeb, 20},
- /* (195) |11111111|11111110|001 */ {0x7fff1, 19},
- /* (196) |11111111|11111111|100111 */ {0x3fffe7, 22},
- /* (197) |11111111|11111111|1110010 */ {0x7ffff2, 23},
- /* (198) |11111111|11111111|101000 */ {0x3fffe8, 22},
- /* (199) |11111111|11111111|11110110|0 */ {0x1ffffec, 25},
- /* (200) |11111111|11111111|11111000|10 */ {0x3ffffe2, 26},
- /* (201) |11111111|11111111|11111000|11 */ {0x3ffffe3, 26},
- /* (202) |11111111|11111111|11111001|00 */ {0x3ffffe4, 26},
- /* (203) |11111111|11111111|11111011|110 */ {0x7ffffde, 27},
- /* (204) |11111111|11111111|11111011|111 */ {0x7ffffdf, 27},
- /* (205) |11111111|11111111|11111001|01 */ {0x3ffffe5, 26},
- /* (206) |11111111|11111111|11110001 */ {0xfffff1, 24},
- /* (207) |11111111|11111111|11110110|1 */ {0x1ffffed, 25},
- /* (208) |11111111|11111110|010 */ {0x7fff2, 19},
- /* (209) |11111111|11111111|00011 */ {0x1fffe3, 21},
- /* (210) |11111111|11111111|11111001|10 */ {0x3ffffe6, 26},
- /* (211) |11111111|11111111|11111100|000 */ {0x7ffffe0, 27},
- /* (212) |11111111|11111111|11111100|001 */ {0x7ffffe1, 27},
- /* (213) |11111111|11111111|11111001|11 */ {0x3ffffe7, 26},
- /* (214) |11111111|11111111|11111100|010 */ {0x7ffffe2, 27},
- /* (215) |11111111|11111111|11110010 */ {0xfffff2, 24},
- /* (216) |11111111|11111111|00100 */ {0x1fffe4, 21},
- /* (217) |11111111|11111111|00101 */ {0x1fffe5, 21},
- /* (218) |11111111|11111111|11111010|00 */ {0x3ffffe8, 26},
- /* (219) |11111111|11111111|11111010|01 */ {0x3ffffe9, 26},
- /* (220) |11111111|11111111|11111111|1101 */ {0xffffffd, 28},
- /* (221) |11111111|11111111|11111100|011 */ {0x7ffffe3, 27},
- /* (222) |11111111|11111111|11111100|100 */ {0x7ffffe4, 27},
- /* (223) |11111111|11111111|11111100|101 */ {0x7ffffe5, 27},
- /* (224) |11111111|11111110|1100 */ {0xfffec, 20},
- /* (225) |11111111|11111111|11110011 */ {0xfffff3, 24},
- /* (226) |11111111|11111110|1101 */ {0xfffed, 20},
- /* (227) |11111111|11111111|00110 */ {0x1fffe6, 21},
- /* (228) |11111111|11111111|101001 */ {0x3fffe9, 22},
- /* (229) |11111111|11111111|00111 */ {0x1fffe7, 21},
- /* (230) |11111111|11111111|01000 */ {0x1fffe8, 21},
- /* (231) |11111111|11111111|1110011 */ {0x7ffff3, 23},
- /* (232) |11111111|11111111|101010 */ {0x3fffea, 22},
- /* (233) |11111111|11111111|101011 */ {0x3fffeb, 22},
- /* (234) |11111111|11111111|11110111|0 */ {0x1ffffee, 25},
- /* (235) |11111111|11111111|11110111|1 */ {0x1ffffef, 25},
- /* (236) |11111111|11111111|11110100 */ {0xfffff4, 24},
- /* (237) |11111111|11111111|11110101 */ {0xfffff5, 24},
- /* (238) |11111111|11111111|11111010|10 */ {0x3ffffea, 26},
- /* (239) |11111111|11111111|1110100 */ {0x7ffff4, 23},
- /* (240) |11111111|11111111|11111010|11 */ {0x3ffffeb, 26},
- /* (241) |11111111|11111111|11111100|110 */ {0x7ffffe6, 27},
- /* (242) |11111111|11111111|11111011|00 */ {0x3ffffec, 26},
- /* (243) |11111111|11111111|11111011|01 */ {0x3ffffed, 26},
- /* (244) |11111111|11111111|11111100|111 */ {0x7ffffe7, 27},
- /* (245) |11111111|11111111|11111101|000 */ {0x7ffffe8, 27},
- /* (246) |11111111|11111111|11111101|001 */ {0x7ffffe9, 27},
- /* (247) |11111111|11111111|11111101|010 */ {0x7ffffea, 27},
- /* (248) |11111111|11111111|11111101|011 */ {0x7ffffeb, 27},
- /* (249) |11111111|11111111|11111111|1110 */ {0xffffffe, 28},
- /* (250) |11111111|11111111|11111101|100 */ {0x7ffffec, 27},
- /* (251) |11111111|11111111|11111101|101 */ {0x7ffffed, 27},
- /* (252) |11111111|11111111|11111101|110 */ {0x7ffffee, 27},
- /* (253) |11111111|11111111|11111101|111 */ {0x7ffffef, 27},
- /* (254) |11111111|11111111|11111110|000 */ {0x7fffff0, 27},
- /* (255) |11111111|11111111|11111011|10 */ {0x3ffffee, 26},
- /*EOS (256) |11111111|11111111|11111111|111111 */ {0x3fffffff, 30}
- };
-
- static final int[][] LCCODES = new int[CODES.length][];
- static final char EOS = 256;
-
- // Huffman decode tree stored in a flattened char array for good
- // locality of reference.
- static final char[] tree;
- static final char[] rowsym;
- static final byte[] rowbits;
-
- // Build the Huffman lookup tree and LC TABLE
- static
- {
- System.arraycopy(CODES, 0, LCCODES, 0, CODES.length);
- for (int i = 'A'; i <= 'Z'; i++)
- {
- LCCODES[i] = LCCODES['a' + i - 'A'];
- }
-
- int r = 0;
- for (int i = 0; i < CODES.length; i++)
- {
- r += (CODES[i][1] + 7) / 8;
- }
- tree = new char[r * 256];
- rowsym = new char[r];
- rowbits = new byte[r];
-
- r = 0;
- for (int sym = 0; sym < CODES.length; sym++)
- {
- int code = CODES[sym][0];
- int len = CODES[sym][1];
-
- int current = 0;
-
- while (len > 8)
- {
- len -= 8;
- int i = ((code >>> len) & 0xFF);
-
- int t = current * 256 + i;
- current = tree[t];
- if (current == 0)
- {
- tree[t] = (char)++r;
- current = r;
- }
- }
-
- int terminal = ++r;
- rowsym[r] = (char)sym;
- int b = len & 0x07;
- int terminalBits = b == 0 ? 8 : b;
-
- rowbits[r] = (byte)terminalBits;
- int shift = 8 - len;
- int start = current * 256 + ((code << shift) & 0xFF);
- int end = start + (1 << shift);
- for (int i = start; i < end; i++)
- {
- tree[i] = (char)terminal;
- }
- }
- }
-
- public static int octetsNeeded(String s)
- {
- return octetsNeeded(CODES, s);
- }
-
- public static int octetsNeeded(byte[] b)
- {
- return octetsNeeded(CODES, b);
- }
-
- public static void encode(ByteBuffer buffer, String s)
- {
- encode(CODES, buffer, s);
- }
-
- public static void encode(ByteBuffer buffer, byte[] b)
- {
- encode(CODES, buffer, b);
- }
-
- public static int octetsNeededLC(String s)
- {
- return octetsNeeded(LCCODES, s);
- }
-
- public static void encodeLC(ByteBuffer buffer, String s)
- {
- encode(LCCODES, buffer, s);
- }
-
- private static int octetsNeeded(final int[][] table, String s)
- {
- int needed = 0;
- int len = s.length();
- for (int i = 0; i < len; i++)
- {
- char c = s.charAt(i);
- if (c >= 128 || c < ' ')
- return -1;
- needed += table[c][1];
- }
-
- return (needed + 7) / 8;
- }
-
- private static int octetsNeeded(final int[][] table, byte[] b)
- {
- int needed = 0;
- int len = b.length;
- for (int i = 0; i < len; i++)
- {
- int c = 0xFF & b[i];
- needed += table[c][1];
- }
- return (needed + 7) / 8;
- }
-
- /**
- * @param table The table to encode by
- * @param buffer The buffer to encode to
- * @param s The string to encode
- */
- private static void encode(final int[][] table, ByteBuffer buffer, String s)
- {
- long current = 0;
- int n = 0;
- int len = s.length();
- for (int i = 0; i < len; i++)
- {
- char c = s.charAt(i);
- if (c >= 128 || c < ' ')
- throw new IllegalArgumentException();
- int code = table[c][0];
- int bits = table[c][1];
-
- current <<= bits;
- current |= code;
- n += bits;
-
- while (n >= 8)
- {
- n -= 8;
- buffer.put((byte)(current >> n));
- }
- }
-
- if (n > 0)
- {
- current <<= (8 - n);
- current |= (0xFF >>> n);
- buffer.put((byte)(current));
- }
- }
-
- private static void encode(final int[][] table, ByteBuffer buffer, byte[] b)
- {
- long current = 0;
- int n = 0;
-
- int len = b.length;
- for (int i = 0; i < len; i++)
- {
- int c = 0xFF & b[i];
- int code = table[c][0];
- int bits = table[c][1];
-
- current <<= bits;
- current |= code;
- n += bits;
-
- while (n >= 8)
- {
- n -= 8;
- buffer.put((byte)(current >> n));
- }
- }
-
- if (n > 0)
- {
- current <<= (8 - n);
- current |= (0xFF >>> n);
- buffer.put((byte)(current));
- }
- }
-}
diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/DecoderInstructionParserTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/DecoderInstructionParserTest.java
index 6bcc7ab239e..460a4e92c0e 100644
--- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/DecoderInstructionParserTest.java
+++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/DecoderInstructionParserTest.java
@@ -14,18 +14,26 @@
package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
+import java.util.List;
+import org.eclipse.jetty.http3.qpack.internal.instruction.DuplicateInstruction;
+import org.eclipse.jetty.http3.qpack.internal.instruction.IndexedNameEntryInstruction;
+import org.eclipse.jetty.http3.qpack.internal.instruction.SetCapacityInstruction;
import org.eclipse.jetty.http3.qpack.internal.parser.DecoderInstructionParser;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.NullByteBufferPool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DecoderInstructionParserTest
{
+ private final NullByteBufferPool bufferPool = new NullByteBufferPool();
private DecoderInstructionParser _instructionParser;
private DecoderParserDebugHandler _handler;
@@ -41,6 +49,11 @@ public class DecoderInstructionParserTest
{
// Set Dynamic Table Capacity=220.
ByteBuffer buffer = QpackTestUtil.hexToBuffer("3fbd 01");
+
+ // Assert that our generated value is equal to that of the spec example.
+ ByteBuffer encodedValue = getEncodedValue(new SetCapacityInstruction(220));
+ assertThat(buffer, equalTo(encodedValue));
+
_instructionParser.parse(buffer);
assertThat(_handler.setCapacities.poll(), is(220));
assertTrue(_handler.isEmpty());
@@ -51,6 +64,11 @@ public class DecoderInstructionParserTest
{
// Duplicate (Relative Index = 2).
ByteBuffer buffer = QpackTestUtil.hexToBuffer("02");
+
+ // Assert that our generated value is equal to that of the spec example.
+ ByteBuffer encodedValue = getEncodedValue(new DuplicateInstruction(2));
+ assertThat(buffer, equalTo(encodedValue));
+
_instructionParser.parse(buffer);
assertThat(_handler.duplicates.poll(), is(2));
assertTrue(_handler.isEmpty());
@@ -61,6 +79,11 @@ public class DecoderInstructionParserTest
{
// Insert With Name Reference to Static Table, Index=0 (:authority=www.example.com).
ByteBuffer buffer = QpackTestUtil.hexToBuffer("c00f 7777 772e 6578 616d 706c 652e 636f 6d");
+
+ // Assert that our generated value is equal to that of the spec example.
+ ByteBuffer encodedValue = getEncodedValue(new IndexedNameEntryInstruction(false, 0, false, "www.example.com"));
+ assertThat(buffer, equalTo(encodedValue));
+
_instructionParser.parse(buffer);
DecoderParserDebugHandler.ReferencedEntry entry = _handler.referencedNameEntries.poll();
assertNotNull(entry);
@@ -94,4 +117,13 @@ public class DecoderInstructionParserTest
// There are no other instructions received.
assertTrue(_handler.isEmpty());
}
+
+ private ByteBuffer getEncodedValue(Instruction instruction)
+ {
+ ByteBufferPool.Lease lease = new ByteBufferPool.Lease(bufferPool);
+ instruction.encode(lease);
+ List byteBuffers = lease.getByteBuffers();
+ assertThat(byteBuffers.size(), equalTo(1));
+ return byteBuffers.get(0);
+ }
}
diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/HuffmanTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/HuffmanTest.java
deleted file mode 100644
index 189803ce310..00000000000
--- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/HuffmanTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.http3.qpack;
-
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-import java.util.Locale;
-import java.util.stream.Stream;
-
-import org.eclipse.jetty.http3.qpack.internal.util.HuffmanDecoder;
-import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.TypeUtil;
-import org.hamcrest.Matchers;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-public class HuffmanTest
-{
- public static Stream data()
- {
- return Stream.of(
- new String[][]{
- {"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"},
- {"D.4.2", "a8eb10649cbf", "no-cache"},
- {"D.6.1k", "6402", "302"},
- {"D.6.1v", "aec3771a4b", "private"},
- {"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
- {"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"},
- {"D.6.2te", "640cff", "303"},
- }).map(Arguments::of);
- }
-
- @ParameterizedTest(name = "[{index}] spec={0}")
- @MethodSource("data")
- public void testDecode(String specSection, String hex, String expected) throws Exception
- {
- byte[] encoded = TypeUtil.fromHexString(hex);
- HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
- huffmanDecoder.setLength(encoded.length);
- String decoded = huffmanDecoder.decode(ByteBuffer.wrap(encoded));
- assertEquals(expected, decoded, specSection);
- }
-
- @ParameterizedTest(name = "[{index}] spec={0}")
- @MethodSource("data")
- public void testEncode(String specSection, String hex, String expected)
- {
- ByteBuffer buf = BufferUtil.allocate(1024);
- int pos = BufferUtil.flipToFill(buf);
- HuffmanEncoder.encode(buf, expected);
- BufferUtil.flipToFlush(buf, pos);
- String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
- assertEquals(hex, encoded, specSection);
- assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
- }
-
- @ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
- @ValueSource(chars = {(char)128, (char)0, (char)-1, ' ' - 1})
- public void testEncode8859Only(char bad)
- {
- String s = "bad '" + bad + "'";
-
- assertThat(HuffmanEncoder.octetsNeeded(s), Matchers.is(-1));
-
- assertThrows(BufferOverflowException.class,
- () -> HuffmanEncoder.encode(BufferUtil.allocate(32), s));
- }
-}
diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java
index 5125867646b..04f699e8e1a 100644
--- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java
+++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java
@@ -15,6 +15,7 @@ package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
import java.util.List;
+import java.util.Objects;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
@@ -127,4 +128,15 @@ public class QpackTestUtil
{
return new MetaData(HttpVersion.HTTP_3, fields);
}
+
+ public static boolean compareMetaData(MetaData m1, MetaData m2)
+ {
+ if (!Objects.equals(m1.getHttpVersion(), m2.getHttpVersion()))
+ return false;
+ if (!Objects.equals(m1.getContentLength(), m2.getContentLength()))
+ return false;
+ if (!Objects.equals(m1.getFields(), m2.getFields()))
+ return false;
+ return m1.getTrailerSupplier() == null && m2.getTrailerSupplier() == null;
+ }
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/CharsetStringBuilder.java b/jetty-util/src/main/java/org/eclipse/jetty/util/CharsetStringBuilder.java
new file mode 100644
index 00000000000..341f39fda07
--- /dev/null
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/CharsetStringBuilder.java
@@ -0,0 +1,277 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Build a string from a sequence of bytes.
+ * Implementations of this interface are optimized for processing a mix of calls to already decoded
+ * character based appends (e.g. {@link #append(char)} and calls to undecoded byte methods (e.g. {@link #append(byte)}.
+ * This is particularly useful for decoding % encoded strings that are mostly already decoded but may contain
+ * escaped byte sequences that are not decoded. The standard {@link CharsetDecoder} API is not well suited for this
+ * use-case.
+ * Any coding errors in the string will be reported by a {@link CharacterCodingException} thrown
+ * from the {@link #build()} method.
+ * @see Utf8StringBuilder for UTF-8 decoding with replacement of coding errors and/or fast fail behaviour.
+ * @see CharsetDecoder for decoding arbitrary {@link Charset}s with control over {@link CodingErrorAction}.
+ */
+public interface CharsetStringBuilder
+{
+ void append(byte b);
+
+ void append(char c);
+
+ default void append(byte[] bytes)
+ {
+ append(bytes, 0, bytes.length);
+ }
+
+ default void append(byte[] b, int offset, int length)
+ {
+ int end = offset + length;
+ for (int i = offset; i < end; i++)
+ append(b[i]);
+ }
+
+ default void append(CharSequence chars, int offset, int length)
+ {
+ int end = offset + length;
+ for (int i = offset; i < end; i++)
+ append(chars.charAt(i));
+ }
+
+ default void append(ByteBuffer buf)
+ {
+ int end = buf.position() + buf.remaining();
+ while (buf.position() < end)
+ append(buf.get());
+ }
+
+ /**
+ * Build the completed string and reset the buffer.
+ * @return The decoded built string which must be complete in regard to any multibyte sequences.
+ * @throws CharacterCodingException If the bytes cannot be correctly decoded or a multibyte sequence is incomplete.
+ */
+ String build() throws CharacterCodingException;
+
+ void reset();
+
+ static CharsetStringBuilder forCharset(Charset charset)
+ {
+ Objects.requireNonNull(charset);
+ if (charset == StandardCharsets.ISO_8859_1)
+ return new Iso88591StringBuilder();
+ if (charset == StandardCharsets.US_ASCII)
+ return new UsAsciiStringBuilder();
+
+ // Use a CharsetDecoder that defaults to CodingErrorAction#REPORT
+ return new DecoderStringBuilder(charset.newDecoder());
+ }
+
+ class Iso88591StringBuilder implements CharsetStringBuilder
+ {
+ private final StringBuilder _builder = new StringBuilder();
+
+ @Override
+ public void append(byte b)
+ {
+ _builder.append((char)(0xff & b));
+ }
+
+ @Override
+ public void append(char c)
+ {
+ _builder.append(c);
+ }
+
+ @Override
+ public void append(CharSequence chars, int offset, int length)
+ {
+ _builder.append(chars, offset, length);
+ }
+
+ @Override
+ public String build()
+ {
+ String s = _builder.toString();
+ _builder.setLength(0);
+ return s;
+ }
+
+ @Override
+ public void reset()
+ {
+ _builder.setLength(0);
+ }
+ }
+
+ class UsAsciiStringBuilder implements CharsetStringBuilder
+ {
+ private final StringBuilder _builder = new StringBuilder();
+
+ @Override
+ public void append(byte b)
+ {
+ if (b < 0)
+ throw new IllegalArgumentException();
+ _builder.append((char)b);
+ }
+
+ @Override
+ public void append(char c)
+ {
+ _builder.append(c);
+ }
+
+ @Override
+ public void append(CharSequence chars, int offset, int length)
+ {
+ _builder.append(chars, offset, length);
+ }
+
+ @Override
+ public String build()
+ {
+ String s = _builder.toString();
+ _builder.setLength(0);
+ return s;
+ }
+
+ @Override
+ public void reset()
+ {
+ _builder.setLength(0);
+ }
+ }
+
+ class DecoderStringBuilder implements CharsetStringBuilder
+ {
+ private final CharsetDecoder _decoder;
+ private final StringBuilder _stringBuilder = new StringBuilder(32);
+ private ByteBuffer _buffer = ByteBuffer.allocate(32);
+
+ public DecoderStringBuilder(CharsetDecoder charsetDecoder)
+ {
+ _decoder = charsetDecoder;
+ }
+
+ private void ensureSpace(int needed)
+ {
+ int space = _buffer.remaining();
+ if (space < needed)
+ {
+ int position = _buffer.position();
+ _buffer = ByteBuffer.wrap(Arrays.copyOf(_buffer.array(), _buffer.capacity() + needed - space + 32)).position(position);
+ }
+ }
+
+ @Override
+ public void append(byte b)
+ {
+ ensureSpace(1);
+ _buffer.put(b);
+ }
+
+ @Override
+ public void append(char c)
+ {
+ if (_buffer.position() > 0)
+ {
+ try
+ {
+ // Append any data already in the decoder
+ _stringBuilder.append(_decoder.decode(_buffer.flip()));
+ _buffer.clear();
+ }
+ catch (CharacterCodingException e)
+ {
+ // This will be thrown only if the decoder is configured to REPORT,
+ // otherwise errors will be ignored or replaced and we will not catch here.
+ throw new RuntimeException(e);
+ }
+ }
+ _stringBuilder.append(c);
+ }
+
+ @Override
+ public void append(CharSequence chars, int offset, int length)
+ {
+ if (_buffer.position() > 0)
+ {
+ try
+ {
+ // Append any data already in the decoder
+ _stringBuilder.append(_decoder.decode(_buffer.flip()));
+ _buffer.clear();
+ }
+ catch (CharacterCodingException e)
+ {
+ // This will be thrown only if the decoder is configured to REPORT,
+ // otherwise errors will be ignored or replaced and we will not catch here.
+ throw new RuntimeException(e);
+ }
+ }
+ _stringBuilder.append(chars, offset, offset + length);
+ }
+
+ @Override
+ public void append(byte[] b, int offset, int length)
+ {
+ ensureSpace(length);
+ _buffer.put(b, offset, length);
+ }
+
+ @Override
+ public void append(ByteBuffer buf)
+ {
+ ensureSpace(buf.remaining());
+ _buffer.put(buf);
+ }
+
+ @Override
+ public String build() throws CharacterCodingException
+ {
+ try
+ {
+ if (_buffer.position() > 0)
+ {
+ CharSequence decoded = _decoder.decode(_buffer.flip());
+ _buffer.clear();
+ if (_stringBuilder.length() == 0)
+ return decoded.toString();
+ _stringBuilder.append(decoded);
+ }
+ return _stringBuilder.toString();
+ }
+ finally
+ {
+ _stringBuilder.setLength(0);
+ }
+ }
+
+ @Override
+ public void reset()
+ {
+ _stringBuilder.setLength(0);
+ }
+ }
+}