Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #9554 - move common hpack/qpack code to jetty-http (jetty-10) #9634

Merged
merged 13 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jetty-http/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down
55 changes: 55 additions & 0 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,5 +231,60 @@ else if (b >= 0x80) // OBS
}
}
}

/**
* 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.
*
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
* @param c the character to test.
* @return the original character or the replacement character ' ' or '?'.
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
*/
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 (c >= 256 || c < ' ')
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
return '?';
}

lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
return c;
}

/**
* 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.
*
* @param i the character to test.
* @return the original character or the replacement character ' ' or '?'.
*/
public static int sanitizeFieldVchar(int i)
{
if (i > Character.MAX_VALUE)
return '?';
return sanitizeFieldVchar((char)i);
}

/**
* 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 < ' ');
}
}

23 changes: 23 additions & 0 deletions jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.util.Collections;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Supplier;

public class MetaData implements Iterable<HttpField>
Expand Down Expand Up @@ -100,6 +101,28 @@ public Iterator<HttpField> iterator()
return _fields.iterator();
}

@Override
public int hashCode()
{
return Objects.hash(_httpVersion, _contentLength, _fields, _trailerSupplier);
}

@Override
public boolean equals(Object obj)
{
if (!(obj instanceof MetaData))
return false;

MetaData other = (MetaData)obj;
if (!Objects.equals(_httpVersion, other._httpVersion))
return false;
if (!Objects.equals(_contentLength, other._contentLength))
return false;
if (!Objects.equals(_fields, other._fields))
return false;
return _trailerSupplier == null && other._trailerSupplier == null;
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
}
gregw marked this conversation as resolved.
Show resolved Hide resolved

@Override
public String toString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// ========================================================================
//

package org.eclipse.jetty.http3.qpack.internal.util;
package org.eclipse.jetty.http.compression;

public class EncodingException extends Exception
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
}

// Appendix C: Huffman Codes
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C
Expand Down Expand Up @@ -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;
Expand All @@ -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];
Expand Down Expand Up @@ -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));
}
}
}
Loading