Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-websocket-parser
This commit is contained in:
commit
69200f6161
|
@ -1,4 +1,5 @@
|
|||
jetty-10.0.16-SNAPSHOT
|
||||
+ Jan is now using IntelliJ!
|
||||
|
||||
jetty-10.0.15 - 11 April 2023
|
||||
+ 6184 JEP-411 will deprecate/remove the SecurityManager from the JVM
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
|
||||
Jetty's `HttpClient` can be configured to use proxies to connect to destinations.
|
||||
|
||||
Two types of proxies are available out of the box: a HTTP proxy (provided by class `org.eclipse.jetty.client.HttpProxy`) and a SOCKS 4 proxy (provided by class `org.eclipse.jetty.client.Socks4Proxy`).
|
||||
These types of proxies are available out of the box:
|
||||
|
||||
* HTTP proxy (provided by class `org.eclipse.jetty.client.HttpProxy`)
|
||||
* SOCKS 4 proxy (provided by class `org.eclipse.jetty.client.Socks4Proxy`)
|
||||
* xref:pg-client-http-proxy-socks5[SOCKS 5 proxy] (provided by class `org.eclipse.jetty.client.Socks5Proxy`)
|
||||
|
||||
Other implementations may be written by subclassing `ProxyConfiguration.Proxy`.
|
||||
|
||||
The following is a typical configuration:
|
||||
|
@ -30,14 +35,26 @@ You specify the proxy host and proxy port, and optionally also the addresses tha
|
|||
|
||||
Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via HTTP `CONNECT` (for encrypted HTTPS requests).
|
||||
|
||||
Proxying is supported for HTTP/1.1 and HTTP/2.
|
||||
Proxying is supported for any version of the HTTP protocol.
|
||||
|
||||
[[pg-client-http-proxy-socks5]]
|
||||
===== SOCKS5 Proxy Support
|
||||
|
||||
SOCKS 5 (defined in link:https://datatracker.ietf.org/doc/html/rfc1928[RFC 1928]) offers choices for authentication methods and supports IPv6 (things that SOCKS 4 does not support).
|
||||
|
||||
A typical SOCKS 5 proxy configuration with the username/password authentication method is the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=proxySocks5]
|
||||
----
|
||||
|
||||
[[pg-client-http-proxy-authentication]]
|
||||
===== Proxy Authentication Support
|
||||
===== HTTP Proxy Authentication Support
|
||||
|
||||
Jetty's `HttpClient` supports proxy authentication in the same way it supports xref:pg-client-http-authentication[server authentication].
|
||||
Jetty's `HttpClient` supports HTTP proxy authentication in the same way it supports xref:pg-client-http-authentication[server authentication].
|
||||
|
||||
In the example below, the proxy requires `BASIC` authentication, but the server requires `DIGEST` authentication, and therefore:
|
||||
In the example below, the HTTP proxy requires `BASIC` authentication, but the server requires `DIGEST` authentication, and therefore:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
|
|
@ -34,6 +34,8 @@ import org.eclipse.jetty.client.HttpDestination;
|
|||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.RoundRobinConnectionPool;
|
||||
import org.eclipse.jetty.client.Socks5;
|
||||
import org.eclipse.jetty.client.Socks5Proxy;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
|
@ -665,6 +667,30 @@ public class HTTPClientDocs
|
|||
// end::proxy[]
|
||||
}
|
||||
|
||||
public void proxySocks5() throws Exception
|
||||
{
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
|
||||
// tag::proxySocks5[]
|
||||
Socks5Proxy proxy = new Socks5Proxy("proxyHost", 8888);
|
||||
String socks5User = "jetty";
|
||||
String socks5Pass = "secret";
|
||||
var socks5AuthenticationFactory = new Socks5.UsernamePasswordAuthenticationFactory(socks5User, socks5Pass);
|
||||
// Add the authentication method to the proxy.
|
||||
proxy.putAuthenticationFactory(socks5AuthenticationFactory);
|
||||
|
||||
// Do not proxy requests for localhost:8080.
|
||||
proxy.getExcludedAddresses().add("localhost:8080");
|
||||
|
||||
// Add the new proxy to the list of proxies already registered.
|
||||
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
|
||||
proxyConfig.addProxy(proxy);
|
||||
|
||||
ContentResponse response = httpClient.GET("http://domain.com/path");
|
||||
// end::proxySocks5[]
|
||||
}
|
||||
|
||||
public void proxyAuthentication() throws Exception
|
||||
{
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>Helper class for SOCKS5 proxying.</p>
|
||||
*
|
||||
* @see Socks5Proxy
|
||||
*/
|
||||
public class Socks5
|
||||
{
|
||||
/**
|
||||
* The SOCKS protocol version: {@value}.
|
||||
*/
|
||||
public static final byte VERSION = 0x05;
|
||||
/**
|
||||
* The SOCKS5 {@code CONNECT} command used in SOCKS5 connect requests.
|
||||
*/
|
||||
public static final byte COMMAND_CONNECT = 0x01;
|
||||
/**
|
||||
* The reserved byte value: {@value}.
|
||||
*/
|
||||
public static final byte RESERVED = 0x00;
|
||||
/**
|
||||
* The address type for IPv4 used in SOCKS5 connect requests and responses.
|
||||
*/
|
||||
public static final byte ADDRESS_TYPE_IPV4 = 0x01;
|
||||
/**
|
||||
* The address type for domain names used in SOCKS5 connect requests and responses.
|
||||
*/
|
||||
public static final byte ADDRESS_TYPE_DOMAIN = 0x03;
|
||||
/**
|
||||
* The address type for IPv6 used in SOCKS5 connect requests and responses.
|
||||
*/
|
||||
public static final byte ADDRESS_TYPE_IPV6 = 0x04;
|
||||
|
||||
private Socks5()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A SOCKS5 authentication method.</p>
|
||||
* <p>Implementations should send and receive the bytes that
|
||||
* are specific to the particular authentication method.</p>
|
||||
*/
|
||||
public interface Authentication
|
||||
{
|
||||
/**
|
||||
* <p>Performs the authentication send and receive bytes
|
||||
* exchanges specific for this {@link Authentication}.</p>
|
||||
*
|
||||
* @param endPoint the {@link EndPoint} to send to and receive from the SOCKS5 server
|
||||
* @param callback the callback to complete when the authentication is complete
|
||||
*/
|
||||
void authenticate(EndPoint endPoint, Callback callback);
|
||||
|
||||
/**
|
||||
* A factory for {@link Authentication}s.
|
||||
*/
|
||||
interface Factory
|
||||
{
|
||||
/**
|
||||
* @return the authentication method defined by RFC 1928
|
||||
*/
|
||||
byte getMethod();
|
||||
|
||||
/**
|
||||
* @return a new {@link Authentication}
|
||||
*/
|
||||
Authentication newAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The implementation of the {@code NO AUTH} authentication method defined in
|
||||
* <a href="https://datatracker.ietf.org/doc/html/rfc1928">RFC 1928</a>.</p>
|
||||
*/
|
||||
public static class NoAuthenticationFactory implements Authentication.Factory
|
||||
{
|
||||
public static final byte METHOD = 0x00;
|
||||
|
||||
@Override
|
||||
public byte getMethod()
|
||||
{
|
||||
return METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication newAuthentication()
|
||||
{
|
||||
return (endPoint, callback) -> callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The implementation of the {@code USERNAME/PASSWORD} authentication method defined in
|
||||
* <a href="https://datatracker.ietf.org/doc/html/rfc1929">RFC 1929</a>.</p>
|
||||
*/
|
||||
public static class UsernamePasswordAuthenticationFactory implements Authentication.Factory
|
||||
{
|
||||
public static final byte METHOD = 0x02;
|
||||
public static final byte VERSION = 0x01;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsernamePasswordAuthenticationFactory.class);
|
||||
|
||||
private final String userName;
|
||||
private final String password;
|
||||
private final Charset charset;
|
||||
|
||||
public UsernamePasswordAuthenticationFactory(String userName, String password)
|
||||
{
|
||||
this(userName, password, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationFactory(String userName, String password, Charset charset)
|
||||
{
|
||||
this.userName = Objects.requireNonNull(userName);
|
||||
this.password = Objects.requireNonNull(password);
|
||||
this.charset = Objects.requireNonNull(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMethod()
|
||||
{
|
||||
return METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication newAuthentication()
|
||||
{
|
||||
return new UsernamePasswordAuthentication(this);
|
||||
}
|
||||
|
||||
private static class UsernamePasswordAuthentication implements Authentication, Callback
|
||||
{
|
||||
private final ByteBuffer byteBuffer = BufferUtil.allocate(2);
|
||||
private final UsernamePasswordAuthenticationFactory factory;
|
||||
private EndPoint endPoint;
|
||||
private Callback callback;
|
||||
|
||||
private UsernamePasswordAuthentication(UsernamePasswordAuthenticationFactory factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(EndPoint endPoint, Callback callback)
|
||||
{
|
||||
this.endPoint = endPoint;
|
||||
this.callback = callback;
|
||||
|
||||
byte[] userNameBytes = factory.userName.getBytes(factory.charset);
|
||||
byte[] passwordBytes = factory.password.getBytes(factory.charset);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(3 + userNameBytes.length + passwordBytes.length)
|
||||
.put(VERSION)
|
||||
.put((byte)userNameBytes.length)
|
||||
.put(userNameBytes)
|
||||
.put((byte)passwordBytes.length)
|
||||
.put(passwordBytes)
|
||||
.flip();
|
||||
endPoint.write(Callback.from(this::authenticationSent, this::failed), byteBuffer);
|
||||
}
|
||||
|
||||
private void authenticationSent()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Written SOCKS5 username/password authentication request");
|
||||
endPoint.fillInterested(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
try
|
||||
{
|
||||
int filled = endPoint.fill(byteBuffer);
|
||||
if (filled < 0)
|
||||
throw new ClosedChannelException();
|
||||
if (byteBuffer.remaining() < 2)
|
||||
{
|
||||
endPoint.fillInterested(this);
|
||||
return;
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Received SOCKS5 username/password authentication response");
|
||||
byte version = byteBuffer.get();
|
||||
if (version != VERSION)
|
||||
throw new IOException("Unsupported username/password authentication version: " + version);
|
||||
byte status = byteBuffer.get();
|
||||
if (status != 0)
|
||||
throw new IOException("SOCK5 username/password authentication failure");
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SOCKS5 username/password authentication succeeded");
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.client.ProxyConfiguration.Proxy;
|
||||
import org.eclipse.jetty.client.Socks5.NoAuthenticationFactory;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>Client-side proxy configuration for SOCKS5, defined by
|
||||
* <a href="https://datatracker.ietf.org/doc/html/rfc1928">RFC 1928</a>.</p>
|
||||
* <p>Multiple authentication methods are supported via
|
||||
* {@link #putAuthenticationFactory(Socks5.Authentication.Factory)}.
|
||||
* By default only the {@link Socks5.NoAuthenticationFactory NO AUTH}
|
||||
* authentication method is configured.
|
||||
* The {@link Socks5.UsernamePasswordAuthenticationFactory USERNAME/PASSWORD}
|
||||
* is available to applications but must be explicitly configured and
|
||||
* added.</p>
|
||||
*/
|
||||
public class Socks5Proxy extends Proxy
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Socks5Proxy.class);
|
||||
|
||||
private final Map<Byte, Socks5.Authentication.Factory> authentications = new LinkedHashMap<>();
|
||||
|
||||
public Socks5Proxy(String host, int port)
|
||||
{
|
||||
this(new Origin.Address(host, port), false);
|
||||
}
|
||||
|
||||
public Socks5Proxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure, null, null);
|
||||
putAuthenticationFactory(new NoAuthenticationFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Provides this class with the given SOCKS5 authentication method.</p>
|
||||
*
|
||||
* @param authenticationFactory the SOCKS5 authentication factory
|
||||
* @return the previous authentication method of the same type, or {@code null}
|
||||
* if there was none of that type already present
|
||||
*/
|
||||
public Socks5.Authentication.Factory putAuthenticationFactory(Socks5.Authentication.Factory authenticationFactory)
|
||||
{
|
||||
return authentications.put(authenticationFactory.getMethod(), authenticationFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Removes the authentication of the given {@code method}.</p>
|
||||
*
|
||||
* @param method the authentication method to remove
|
||||
*/
|
||||
public Socks5.Authentication.Factory removeAuthenticationFactory(byte method)
|
||||
{
|
||||
return authentications.remove(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
return new Socks5ProxyClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
|
||||
private class Socks5ProxyClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
|
||||
private Socks5ProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
Socks5ProxyConnection connection = new Socks5ProxyConnection(endPoint, executor, connectionFactory, context, authentications);
|
||||
return customize(connection, context);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Socks5ProxyConnection extends AbstractConnection implements org.eclipse.jetty.io.Connection.UpgradeFrom
|
||||
{
|
||||
private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
|
||||
|
||||
// SOCKS5 response max length is 262 bytes.
|
||||
private final ByteBuffer byteBuffer = BufferUtil.allocate(512);
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private final Map<String, Object> context;
|
||||
private final Map<Byte, Socks5.Authentication.Factory> authentications;
|
||||
private State state = State.HANDSHAKE;
|
||||
|
||||
private Socks5ProxyConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context, Map<Byte, Socks5.Authentication.Factory> authentications)
|
||||
{
|
||||
super(endPoint, executor);
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.context = context;
|
||||
this.authentications = Map.copyOf(authentications);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer onUpgradeFrom()
|
||||
{
|
||||
return BufferUtil.copy(byteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
sendHandshake();
|
||||
}
|
||||
|
||||
private void sendHandshake()
|
||||
{
|
||||
try
|
||||
{
|
||||
// +-------------+--------------------+------------------+
|
||||
// | version (1) | num of methods (1) | methods (1..255) |
|
||||
// +-------------+--------------------+------------------+
|
||||
int size = authentications.size();
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(1 + 1 + size)
|
||||
.put(Socks5.VERSION)
|
||||
.put((byte)size);
|
||||
authentications.keySet().forEach(byteBuffer::put);
|
||||
byteBuffer.flip();
|
||||
getEndPoint().write(Callback.from(this::handshakeSent, this::fail), byteBuffer);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void handshakeSent()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Written SOCKS5 handshake request");
|
||||
state = State.HANDSHAKE;
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
private void fail(Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SOCKS5 failure", x);
|
||||
getEndPoint().close(x);
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)this.context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onIdleExpired()
|
||||
{
|
||||
fail(new TimeoutException("Idle timeout expired"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case HANDSHAKE:
|
||||
receiveHandshake();
|
||||
break;
|
||||
case CONNECT:
|
||||
receiveConnect();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveHandshake() throws IOException
|
||||
{
|
||||
// +-------------+------------+
|
||||
// | version (1) | method (1) |
|
||||
// +-------------+------------+
|
||||
int filled = getEndPoint().fill(byteBuffer);
|
||||
if (filled < 0)
|
||||
throw new ClosedChannelException();
|
||||
if (byteBuffer.remaining() < 2)
|
||||
{
|
||||
fillInterested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Received SOCKS5 handshake response {}", BufferUtil.toDetailString(byteBuffer));
|
||||
|
||||
byte version = byteBuffer.get();
|
||||
if (version != Socks5.VERSION)
|
||||
throw new IOException("Unsupported SOCKS5 version: " + version);
|
||||
|
||||
byte method = byteBuffer.get();
|
||||
if (method == -1)
|
||||
throw new IOException("Unacceptable SOCKS5 authentication methods");
|
||||
|
||||
Socks5.Authentication.Factory factory = authentications.get(method);
|
||||
if (factory == null)
|
||||
throw new IOException("Unknown SOCKS5 authentication method: " + method);
|
||||
|
||||
factory.newAuthentication().authenticate(getEndPoint(), Callback.from(this::sendConnect, this::fail));
|
||||
}
|
||||
|
||||
private void sendConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
// +-------------+-------------+--------------+------------------+------------------------+----------+
|
||||
// | version (1) | command (1) | reserved (1) | address type (1) | address bytes (4..255) | port (2) |
|
||||
// +-------------+-------------+--------------+------------------+------------------------+----------+
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Origin.Address address = destination.getOrigin().getAddress();
|
||||
String host = address.getHost();
|
||||
short port = (short)address.getPort();
|
||||
|
||||
ByteBuffer byteBuffer;
|
||||
Matcher matcher = IPv4_PATTERN.matcher(host);
|
||||
if (matcher.matches())
|
||||
{
|
||||
byteBuffer = ByteBuffer.allocate(10)
|
||||
.put(Socks5.VERSION)
|
||||
.put(Socks5.COMMAND_CONNECT)
|
||||
.put(Socks5.RESERVED)
|
||||
.put(Socks5.ADDRESS_TYPE_IPV4);
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
{
|
||||
byteBuffer.put(Byte.parseByte(matcher.group(i)));
|
||||
}
|
||||
byteBuffer.putShort(port)
|
||||
.flip();
|
||||
}
|
||||
else if (URIUtil.isValidHostRegisteredName(host))
|
||||
{
|
||||
byte[] bytes = host.getBytes(StandardCharsets.US_ASCII);
|
||||
if (bytes.length > 255)
|
||||
throw new IOException("Invalid host name: " + host);
|
||||
byteBuffer = ByteBuffer.allocate(7 + bytes.length)
|
||||
.put(Socks5.VERSION)
|
||||
.put(Socks5.COMMAND_CONNECT)
|
||||
.put(Socks5.RESERVED)
|
||||
.put(Socks5.ADDRESS_TYPE_DOMAIN)
|
||||
.put((byte)bytes.length)
|
||||
.put(bytes)
|
||||
.putShort(port)
|
||||
.flip();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume IPv6.
|
||||
byte[] bytes = InetAddress.getByName(host).getAddress();
|
||||
byteBuffer = ByteBuffer.allocate(22)
|
||||
.put(Socks5.VERSION)
|
||||
.put(Socks5.COMMAND_CONNECT)
|
||||
.put(Socks5.RESERVED)
|
||||
.put(Socks5.ADDRESS_TYPE_IPV6)
|
||||
.put(bytes)
|
||||
.putShort(port)
|
||||
.flip();
|
||||
}
|
||||
|
||||
getEndPoint().write(Callback.from(this::connectSent, this::fail), byteBuffer);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectSent()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Written SOCKS5 connect request");
|
||||
state = State.CONNECT;
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
private void receiveConnect() throws IOException
|
||||
{
|
||||
// +-------------+-----------+--------------+------------------+------------------------+----------+
|
||||
// | version (1) | reply (1) | reserved (1) | address type (1) | address bytes (4..255) | port (2) |
|
||||
// +-------------+-----------+--------------+------------------+------------------------+----------+
|
||||
int filled = getEndPoint().fill(byteBuffer);
|
||||
if (filled < 0)
|
||||
throw new ClosedChannelException();
|
||||
if (byteBuffer.remaining() < 5)
|
||||
{
|
||||
fillInterested();
|
||||
return;
|
||||
}
|
||||
byte addressType = byteBuffer.get(3);
|
||||
int length = 6;
|
||||
if (addressType == Socks5.ADDRESS_TYPE_IPV4)
|
||||
length += 4;
|
||||
else if (addressType == Socks5.ADDRESS_TYPE_DOMAIN)
|
||||
length += 1 + (byteBuffer.get(4) & 0xFF);
|
||||
else if (addressType == Socks5.ADDRESS_TYPE_IPV6)
|
||||
length += 16;
|
||||
else
|
||||
throw new IOException("Invalid SOCKS5 address type: " + addressType);
|
||||
if (byteBuffer.remaining() < length)
|
||||
{
|
||||
fillInterested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Received SOCKS5 connect response {}", BufferUtil.toDetailString(byteBuffer));
|
||||
|
||||
// We have all the SOCKS5 bytes.
|
||||
byte version = byteBuffer.get();
|
||||
if (version != Socks5.VERSION)
|
||||
throw new IOException("Unsupported SOCKS5 version: " + version);
|
||||
|
||||
byte status = byteBuffer.get();
|
||||
switch (status)
|
||||
{
|
||||
case 0:
|
||||
// Consume the buffer before upgrading to the tunnel.
|
||||
byteBuffer.position(length);
|
||||
tunnel();
|
||||
break;
|
||||
case 1:
|
||||
throw new IOException("SOCKS5 general failure");
|
||||
case 2:
|
||||
throw new IOException("SOCKS5 connection not allowed");
|
||||
case 3:
|
||||
throw new IOException("SOCKS5 network unreachable");
|
||||
case 4:
|
||||
throw new IOException("SOCKS5 host unreachable");
|
||||
case 5:
|
||||
throw new IOException("SOCKS5 connection refused");
|
||||
case 6:
|
||||
throw new IOException("SOCKS5 timeout expired");
|
||||
case 7:
|
||||
throw new IOException("SOCKS5 unsupported command");
|
||||
case 8:
|
||||
throw new IOException("SOCKS5 unsupported address");
|
||||
default:
|
||||
throw new IOException("SOCKS5 unknown status: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
private void tunnel()
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
// Don't want to do DNS resolution here.
|
||||
InetSocketAddress address = InetSocketAddress.createUnresolved(destination.getHost(), destination.getPort());
|
||||
context.put(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, address);
|
||||
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||
if (destination.isSecure())
|
||||
connectionFactory = destination.newSslClientConnectionFactory(null, connectionFactory);
|
||||
var newConnection = connectionFactory.newConnection(getEndPoint(), context);
|
||||
getEndPoint().upgrade(newConnection);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SOCKS5 tunnel established: {} over {}", this, newConnection);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
HANDSHAKE, CONNECT
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
|
|
|
@ -176,6 +176,36 @@ public class HttpTester
|
|||
return r;
|
||||
}
|
||||
|
||||
public static Request parseRequest(InputStream inputStream) throws IOException
|
||||
{
|
||||
return parseRequest(from(inputStream));
|
||||
}
|
||||
|
||||
public static Request parseRequest(ReadableByteChannel channel) throws IOException
|
||||
{
|
||||
return parseRequest(from(channel));
|
||||
}
|
||||
|
||||
public static Request parseRequest(Input input) throws IOException
|
||||
{
|
||||
Request request;
|
||||
HttpParser parser = input.takeHttpParser();
|
||||
if (parser != null)
|
||||
{
|
||||
request = (Request)parser.getHandler();
|
||||
}
|
||||
else
|
||||
{
|
||||
request = newRequest();
|
||||
parser = new HttpParser(request);
|
||||
}
|
||||
parse(input, parser);
|
||||
if (request.isComplete())
|
||||
return request;
|
||||
input.setHttpParser(parser);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Response parseResponse(String response)
|
||||
{
|
||||
Response r = new Response();
|
||||
|
@ -230,7 +260,7 @@ public class HttpTester
|
|||
else
|
||||
r = (Response)parser.getHandler();
|
||||
|
||||
parseResponse(in, parser, r);
|
||||
parse(in, parser);
|
||||
|
||||
if (r.isComplete())
|
||||
return r;
|
||||
|
@ -246,13 +276,13 @@ public class HttpTester
|
|||
{
|
||||
parser = new HttpParser(response);
|
||||
}
|
||||
parseResponse(in, parser, response);
|
||||
parse(in, parser);
|
||||
|
||||
if (!response.isComplete())
|
||||
in.setHttpParser(parser);
|
||||
}
|
||||
|
||||
private static void parseResponse(Input in, HttpParser parser, Response r) throws IOException
|
||||
private static void parse(Input in, HttpParser parser) throws IOException
|
||||
{
|
||||
ByteBuffer buffer = in.getBuffer();
|
||||
|
||||
|
|
|
@ -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.
|
||||
* <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 < ' ');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http3.qpack.internal.util;
|
||||
package org.eclipse.jetty.http.compression;
|
||||
|
||||
public class EncodingException extends Exception
|
||||
{
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>Used to decoded Huffman encoded strings.</p>
|
||||
*
|
||||
* <p>Characters which are illegal field-vchar values are replaced with
|
||||
* either ' ' or '?' as described in RFC9110</p>
|
||||
*/
|
||||
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;
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>Used to encode strings Huffman encoding.</p>
|
||||
*
|
||||
* <p>Characters are encoded with ISO-8859-1, if any multi-byte characters or
|
||||
* control characters are present the encoder will throw {@link EncodingException}.</p>
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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)
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>Used to decode string literals as described in RFC7541.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>Characters which are illegal field-vchar values are replaced with
|
||||
* either ' ' or '?' as described in RFC9110</p>
|
||||
*/
|
||||
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;
|
|
@ -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<Arguments> 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<Arguments> 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<Arguments> 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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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<Arguments> 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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
||||
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
||||
private final NBitIntegerDecoder _integerDecoder = new NBitIntegerDecoder();
|
||||
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
||||
private final Map<Long, AtomicInteger> _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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<EncodedField> _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");
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ByteBuffer> byteBuffers = lease.getByteBuffers();
|
||||
assertThat(byteBuffers.size(), equalTo(1));
|
||||
return byteBuffers.get(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Arguments> 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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,10 @@ import org.eclipse.jetty.util.NanoTime;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @deprecated no replacement for this deprecated http feature
|
||||
*/
|
||||
@Deprecated
|
||||
public class PushSessionCacheFilter implements Filter
|
||||
{
|
||||
private static final String RESPONSE_ATTR = "PushSessionCacheFilter.response";
|
||||
|
@ -46,6 +50,11 @@ public class PushSessionCacheFilter implements Filter
|
|||
private final ConcurrentMap<String, Target> _cache = new ConcurrentHashMap<>();
|
||||
private long _associateDelay = 5000L;
|
||||
|
||||
public PushSessionCacheFilter()
|
||||
{
|
||||
LOG.warn(PushSessionCacheFilter.class.getSimpleName() + " is an example class not suitable for production.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>Build a string from a sequence of bytes.</p>
|
||||
* <p>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.</p>
|
||||
* <p>Any coding errors in the string will be reported by a {@link CharacterCodingException} thrown
|
||||
* from the {@link #build()} method.</p>
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Build the completed string and reset the buffer.</p>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue