Skip to content

Commit

Permalink
backport the jetty-http Huffman encoders/decoders from 10.0.x (#10546)
Browse files Browse the repository at this point in the history
* backport the jetty-http Huffman encoders/decoders from 10.0.x
* fix some hpack tests after changes

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
  • Loading branch information
lachlan-roberts and joakime authored Sep 20, 2023
1 parent b9d3213 commit c7a4b05
Show file tree
Hide file tree
Showing 24 changed files with 1,541 additions and 634 deletions.
44 changes: 44 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 @@ -188,5 +188,49 @@ 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.
* <pre>
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
* VCHAR = %x21-7E
* </pre>
* @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 < ' ');
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//

package org.eclipse.jetty.http.compression;

public class EncodingException extends Exception
{
public EncodingException(String message)
{
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,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
Expand Down Expand Up @@ -291,7 +293,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 @@ -307,9 +309,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 @@ -352,200 +354,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

0 comments on commit c7a4b05

Please sign in to comment.