IDEMPIERE-3136:all stuff relate library of idempiere

update jetty to 9.3.10
work-around for:
https://github.com/eclipse/jetty.project/issues/262
https://github.com/eclipse/jetty.project/issues/704
This commit is contained in:
hieplq 2016-07-08 23:54:51 +07:00
parent 16bfc4f59b
commit a46d233f37
59 changed files with 14234 additions and 2 deletions

View File

@ -2,6 +2,7 @@ syntax: glob
build
External Plug-in Libraries
org.eclipse.jetty.http/bin
syntax: regexp
^adempiere$
^org\.adempiere\.install/lib$

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<rm:rmap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bc="http://www.eclipse.org/buckminster/Common-1.0" xmlns:maven="http://www.eclipse.org/buckminster/MavenProvider-1.0" xmlns:rm="http://www.eclipse.org/buckminster/RMap-1.0">
<rm:locator pattern="^org\.eclipse\.jetty\.http$" searchPathRef="workspace.project"/>
<rm:locator pattern="^org\.eclipse\.jetty\.alpn\.api$" searchPathRef="modify-bundle"/>
<rm:locator pattern="^org\.apache\.felix\.webconsole\.plugins\.packageadmin$" searchPathRef="bundles.maven"/>
<rm:locator pattern="^org\.idempiere\.hazelcast\.service$" searchPathRef="workspace.feature" failOnError="false"/>
<rm:locator pattern="^org\.idempiere\.hazelcast\.service$" searchPathRef="workspace.project"/>
@ -171,4 +173,11 @@
<rm:uri format="${url.zkoss.osgi}"/>
</rm:provider>
</rm:searchPath>
<rm:searchPath name="modify-bundle">
<rm:provider componentTypes="osgi.bundle" readerType="p2" source="false" mutable="false">
<rm:property key="buckminster.source" value="false"/>
<rm:property key="buckminster.mutable" value="false"/>
<rm:uri format="${url.modify.bundle}"/>
</rm:provider>
</rm:searchPath>
</rm:rmap>

View File

@ -26,3 +26,5 @@ url.orbit.neon=http://download.eclipse.org/tools/orbit/downloads/drops/R20160520
url.file.srv=http://downloads.sourceforge.net/project/idempiere/binary.file
url.restlet.p2=http://p2.restlet.com/2.3
url.modify.bundle=http://downloads.sourceforge.net/project/idempiere/p2/modifyBundle-1.0.0

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.jetty.http</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,35 @@
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven Bundle Plugin
Built-By: joakim
Build-Jdk: 1.8.0_65
Implementation-Vendor: Eclipse.org - Jetty
Implementation-Version: 9.3.10.v20160621
url: http://www.eclipse.org/jetty
Bnd-LastModified: 1457969504223
Bundle-Classpath: .
Bundle-Copyright: Copyright (c) 2008-2016 Mort Bay Consulting Pty. Ltd.
Bundle-Description: Jetty module for Jetty :: Http Utility
Bundle-DocURL: http://www.eclipse.org/jetty
Bundle-License: http://www.apache.org/licenses/LICENSE-2.0, http://www
.eclipse.org/org/documents/epl-v10.php
Bundle-ManifestVersion: 2
Bundle-Name: Jetty :: Http Utility
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-SymbolicName: org.eclipse.jetty.http
Bundle-Vendor: Eclipse Jetty Project
Bundle-Version: 9.3.10.v20160621
Export-Package: org.eclipse.jetty.http;version="9.3.10";uses:="org.ecl
ipse.jetty.util,org.eclipse.jetty.util.log,org.eclipse.jetty.util.res
ource",org.eclipse.jetty.http.pathmap;version="9.3.10";uses:="org.ecl
ipse.jetty.util.annotation,org.eclipse.jetty.util.component",org.ecli
pse.jetty.http2.hpack;version="9.3.10";uses:="org.eclipse.jetty.http,
org.eclipse.jetty.util.log"
Import-Package: org.eclipse.jetty.util;version="[9.3.10,9.3.11)",org.e
clipse.jetty.util.annotation;version="[9.3.10,9.3.11)",org.eclipse.je
tty.util.component;version="[9.3.10,9.3.11)",org.eclipse.jetty.util.l
og;version="[9.3.10,9.3.11)",org.eclipse.jetty.util.resource;version=
"[9.3.10,9.3.11)"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Tool: Bnd-2.4.1.201501161923

View File

@ -0,0 +1,2 @@
org.eclipse.jetty.http.Http1FieldPreEncoder
org.eclipse.jetty.http2.hpack.HpackFieldPreEncoder

View File

@ -0,0 +1,27 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>About</title>
</head>
<body lang="EN-US">
<h2>About This Content</h2>
<p>19 May, 2009</p>
<h3>License</h3>
<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). The Content is dual licensed and is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 (&quot;EPL&quot;) as well as the Apache Software License Version 2.0. A copy of the EPL is available
at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. A copy of the ASL is available at <a href="http://www.apache.org/licenses/LICENSE-2.0.html">http://www.apache.org/licenses/LICENSE-2.0.html</a>. For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org">http://www.eclipse.org</a>.</p>
<p><b>jetty-util</b> artifact only:<br/><br/>The UnixCrypt.java code implements the one way cryptography used by
Unix systems for simple password protection. Copyright 1996 Aki Yoshida,
modified April 2001 by Iris Van den Broeke, Daniel Deville.
Permission to use, copy, modify and distribute UnixCrypt
for non-commercial or commercial purposes and without fee is
granted provided that the copyright notice appears in all copies.</p>
</body>
</html>

View File

@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.

View File

@ -0,0 +1,71 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
/* ------------------------------------------------------------------------------- */
/**
* <p>Exception thrown to indicate a Bad HTTP Message has either been received
* or attempted to be generated. Typically these are handled with either 400
* or 500 responses.</p>
*/
@SuppressWarnings("serial")
public class BadMessageException extends RuntimeException
{
final int _code;
final String _reason;
public BadMessageException()
{
this(400,null);
}
public BadMessageException(int code)
{
this(code,null);
}
public BadMessageException(String reason)
{
this(400,reason);
}
public BadMessageException(int code, String reason)
{
super(code+": "+reason);
_code=code;
_reason=reason;
}
public BadMessageException(int code, String reason, Throwable cause)
{
super(code+": "+reason, cause);
_code=code;
_reason=reason;
}
public int getCode()
{
return _code;
}
public String getReason()
{
return _reason;
}
}

View File

@ -0,0 +1,173 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.eclipse.jetty.util.StringUtil;
/**
* ThreadLocal Date formatters for HTTP style dates.
*/
public class DateGenerator
{
private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
static
{
__GMT.setID("GMT");
}
static final String[] DAYS =
{ "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static final String[] MONTHS =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
{
@Override
protected DateGenerator initialValue()
{
return new DateGenerator();
}
};
public final static String __01Jan1970=DateGenerator.formatDate(0);
/**
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
* @param date the date in milliseconds
* @return the formatted date
*/
public static String formatDate(long date)
{
return __dateGenerator.get().doFormatDate(date);
}
/**
* Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
* @param buf the buffer to put the formatted date into
* @param date the date in milliseconds
*/
public static void formatCookieDate(StringBuilder buf, long date)
{
__dateGenerator.get().doFormatCookieDate(buf,date);
}
/**
* Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
* @param date the date in milliseconds
* @return the formatted date
*/
public static String formatCookieDate(long date)
{
StringBuilder buf = new StringBuilder(28);
formatCookieDate(buf, date);
return buf.toString();
}
private final StringBuilder buf = new StringBuilder(32);
private final GregorianCalendar gc = new GregorianCalendar(__GMT);
/**
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
* @param date the date in milliseconds
* @return the formatted date
*/
public String doFormatDate(long date)
{
buf.setLength(0);
gc.setTimeInMillis(date);
int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
int month = gc.get(Calendar.MONTH);
int year = gc.get(Calendar.YEAR);
int century = year / 100;
year = year % 100;
int hours = gc.get(Calendar.HOUR_OF_DAY);
int minutes = gc.get(Calendar.MINUTE);
int seconds = gc.get(Calendar.SECOND);
buf.append(DAYS[day_of_week]);
buf.append(',');
buf.append(' ');
StringUtil.append2digits(buf, day_of_month);
buf.append(' ');
buf.append(MONTHS[month]);
buf.append(' ');
StringUtil.append2digits(buf, century);
StringUtil.append2digits(buf, year);
buf.append(' ');
StringUtil.append2digits(buf, hours);
buf.append(':');
StringUtil.append2digits(buf, minutes);
buf.append(':');
StringUtil.append2digits(buf, seconds);
buf.append(" GMT");
return buf.toString();
}
/**
* Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
* @param buf the buffer to format the date into
* @param date the date in milliseconds
*/
public void doFormatCookieDate(StringBuilder buf, long date)
{
gc.setTimeInMillis(date);
int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
int month = gc.get(Calendar.MONTH);
int year = gc.get(Calendar.YEAR);
year = year % 10000;
int epoch = (int) ((date / 1000) % (60 * 60 * 24));
int seconds = epoch % 60;
epoch = epoch / 60;
int minutes = epoch % 60;
int hours = epoch / 60;
buf.append(DAYS[day_of_week]);
buf.append(',');
buf.append(' ');
StringUtil.append2digits(buf, day_of_month);
buf.append('-');
buf.append(MONTHS[month]);
buf.append('-');
StringUtil.append2digits(buf, year/100);
StringUtil.append2digits(buf, year%100);
buf.append(' ');
StringUtil.append2digits(buf, hours);
buf.append(':');
StringUtil.append2digits(buf, minutes);
buf.append(':');
StringUtil.append2digits(buf, seconds);
buf.append(" GMT");
}
}

View File

@ -0,0 +1,109 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* ThreadLocal data parsers for HTTP style dates
*
*/
public class DateParser
{
private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
static
{
__GMT.setID("GMT");
}
final static String __dateReceiveFmt[] =
{
"EEE, dd MMM yyyy HH:mm:ss zzz",
"EEE, dd-MMM-yy HH:mm:ss",
"EEE MMM dd HH:mm:ss yyyy",
"EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
"EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
"EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
"dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
"MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
"EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
"EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
};
public static long parseDate(String date)
{
return __dateParser.get().parse(date);
}
private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
{
@Override
protected DateParser initialValue()
{
return new DateParser();
}
};
final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
private long parse(final String dateVal)
{
for (int i = 0; i < _dateReceive.length; i++)
{
if (_dateReceive[i] == null)
{
_dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
_dateReceive[i].setTimeZone(__GMT);
}
try
{
Date date = (Date) _dateReceive[i].parseObject(dateVal);
return date.getTime();
}
catch (java.lang.Exception e)
{
// LOG.ignore(e);
}
}
if (dateVal.endsWith(" GMT"))
{
final String val = dateVal.substring(0, dateVal.length() - 4);
for (SimpleDateFormat element : _dateReceive)
{
try
{
Date date = (Date) element.parseObject(val);
return date.getTime();
}
catch (java.lang.Exception e)
{
// LOG.ignore(e);
}
}
}
return -1;
}
}

View File

@ -0,0 +1,188 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
public class GzipHttpContent implements HttpContent
{
private final HttpContent _content;
private final HttpContent _contentGz;
public final static String ETAG_GZIP="--gzip";
public final static String ETAG_GZIP_QUOTE="--gzip\"";
public final static PreEncodedHttpField CONTENT_ENCODING_GZIP=new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING,"gzip");
public static String removeGzipFromETag(String etag)
{
if (etag==null)
return null;
int i = etag.indexOf(ETAG_GZIP_QUOTE);
if (i<0)
return etag;
return etag.substring(0,i)+'"';
}
public GzipHttpContent(HttpContent content, HttpContent contentGz)
{
_content=content;
_contentGz=contentGz;
}
@Override
public int hashCode()
{
return _content.hashCode();
}
@Override
public boolean equals(Object obj)
{
return _content.equals(obj);
}
@Override
public Resource getResource()
{
return _content.getResource();
}
@Override
public HttpField getETag()
{
return new HttpField(HttpHeader.ETAG,getETagValue());
}
@Override
public String getETagValue()
{
return _content.getResource().getWeakETag(ETAG_GZIP);
}
@Override
public HttpField getLastModified()
{
return _content.getLastModified();
}
@Override
public String getLastModifiedValue()
{
return _content.getLastModifiedValue();
}
@Override
public HttpField getContentType()
{
return _content.getContentType();
}
@Override
public String getContentTypeValue()
{
return _content.getContentTypeValue();
}
@Override
public HttpField getContentEncoding()
{
return CONTENT_ENCODING_GZIP;
}
@Override
public String getContentEncodingValue()
{
return CONTENT_ENCODING_GZIP.getValue();
}
@Override
public String getCharacterEncoding()
{
return _content.getCharacterEncoding();
}
@Override
public Type getMimeType()
{
return _content.getMimeType();
}
@Override
public void release()
{
_content.release();
}
@Override
public ByteBuffer getIndirectBuffer()
{
return _contentGz.getIndirectBuffer();
}
@Override
public ByteBuffer getDirectBuffer()
{
return _contentGz.getDirectBuffer();
}
@Override
public HttpField getContentLength()
{
return _contentGz.getContentLength();
}
@Override
public long getContentLengthValue()
{
return _contentGz.getContentLengthValue();
}
@Override
public InputStream getInputStream() throws IOException
{
return _contentGz.getInputStream();
}
@Override
public ReadableByteChannel getReadableByteChannel() throws IOException
{
return _contentGz.getReadableByteChannel();
}
@Override
public String toString()
{
return String.format("GzipHttpContent@%x{r=%s|%s,lm=%s|%s,ct=%s}",hashCode(),
_content.getResource(),_contentGz.getResource(),
_content.getResource().lastModified(),_contentGz.getResource().lastModified(),
getContentType());
}
@Override
public HttpContent getGzipContent()
{
return null;
}
}

View File

@ -0,0 +1,106 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import org.eclipse.jetty.util.StringUtil;
/* ------------------------------------------------------------ */
/**
*/
public class HostPortHttpField extends HttpField
{
private final String _host;
private final int _port;
public HostPortHttpField(String authority)
{
this(HttpHeader.HOST,HttpHeader.HOST.asString(),authority);
}
public HostPortHttpField(HttpHeader header, String name, String authority)
{
super(header,name,authority);
if (authority==null || authority.length()==0)
throw new IllegalArgumentException("No Authority");
try
{
if (authority.charAt(0)=='[')
{
// ipv6reference
int close=authority.lastIndexOf(']');
if (close<0)
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad ipv6");
_host=authority.substring(0,close+1);
if (authority.length()>close+1)
{
if (authority.charAt(close+1)!=':')
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad ipv6 port");
_port=StringUtil.toInt(authority,close+2);
}
else
_port=0;
}
else
{
// ipv4address or hostname
int c = authority.lastIndexOf(':');
if (c>=0)
{
_host=authority.substring(0,c);
_port=StringUtil.toInt(authority,c+1);
}
else
{
_host=authority;
_port=0;
}
}
}
catch (BadMessageException bm)
{
throw bm;
}
catch(Exception e)
{
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad HostPort",e);
}
}
/* ------------------------------------------------------------ */
/** Get the host.
* @return the host
*/
public String getHost()
{
return _host;
}
/* ------------------------------------------------------------ */
/** Get the port.
* @return the port
*/
public int getPort()
{
return _port;
}
}

View File

@ -0,0 +1,69 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import java.util.Arrays;
/* ------------------------------------------------------------ */
/**
*/
public class Http1FieldPreEncoder implements HttpFieldPreEncoder
{
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.HttpFieldPreEncoder#getHttpVersion()
*/
@Override
public HttpVersion getHttpVersion()
{
return HttpVersion.HTTP_1_0;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.HttpFieldPreEncoder#getEncodedField(org.eclipse.jetty.http.HttpHeader, java.lang.String, java.lang.String)
*/
@Override
public byte[] getEncodedField(HttpHeader header, String headerString, String value)
{
if (header!=null)
{
int cbl=header.getBytesColonSpace().length;
byte[] bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
System.arraycopy(value.getBytes(ISO_8859_1),0,bytes,cbl,value.length());
bytes[bytes.length-2]=(byte)'\r';
bytes[bytes.length-1]=(byte)'\n';
return bytes;
}
byte[] n=headerString.getBytes(ISO_8859_1);
byte[] v=value.getBytes(ISO_8859_1);
byte[] bytes=Arrays.copyOf(n,n.length+2+v.length+2);
bytes[n.length]=(byte)':';
bytes[n.length]=(byte)' ';
bytes[bytes.length-2]=(byte)'\r';
bytes[bytes.length-1]=(byte)'\n';
return bytes;
}
}

View File

@ -0,0 +1,32 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
/**
* HTTP compliance modes:
* <dl>
* <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd>
* <dt>RFC2616</dt><dd>Wrapped/Continued headers and HTTP/0.9 supported</dd>
* <dt>LEGACY</dt><dd>(aka STRICT) Adherence to Servlet Specification requirement for
* exact case of header names, bypassing the header caches, which are case insensitive,
* otherwise equivalent to RFC2616</dd>
* </dl>
*/
public enum HttpCompliance { LEGACY, RFC2616, RFC7230 }

View File

@ -0,0 +1,80 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
/** HttpContent interface.
* <p>This information represents all the information about a
* static resource that is needed to evaluate conditional headers
* and to serve the content if need be. It can be implemented
* either transiently (values and fields generated on demand) or
* persistently (values and fields pre-generated in anticipation of
* reuse in from a cache).
* </p>
*
*/
public interface HttpContent
{
HttpField getContentType();
String getContentTypeValue();
String getCharacterEncoding();
Type getMimeType();
HttpField getContentEncoding();
String getContentEncodingValue();
HttpField getContentLength();
long getContentLengthValue();
HttpField getLastModified();
String getLastModifiedValue();
HttpField getETag();
String getETagValue();
ByteBuffer getIndirectBuffer();
ByteBuffer getDirectBuffer();
Resource getResource();
InputStream getInputStream() throws IOException;
ReadableByteChannel getReadableByteChannel() throws IOException;
void release();
HttpContent getGzipContent();
public interface Factory
{
/**
* @param path The path within the context to the resource
* @param maxBuffer The maximum buffer to allocated for this request. For cached content, a larger buffer may have
* previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls.
* @return A {@link HttpContent}
* @throws IOException
*/
HttpContent getContent(String path,int maxBuffer) throws IOException;
}
}

View File

@ -0,0 +1,164 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.concurrent.TimeUnit;
public class HttpCookie
{
private final String _name;
private final String _value;
private final String _comment;
private final String _domain;
private final long _maxAge;
private final String _path;
private final boolean _secure;
private final int _version;
private final boolean _httpOnly;
private final long _expiration;
public HttpCookie(String name, String value)
{
this(name, value, -1);
}
public HttpCookie(String name, String value, String domain, String path)
{
this(name, value, domain, path, -1, false, false);
}
public HttpCookie(String name, String value, long maxAge)
{
this(name, value, null, null, maxAge, false, false);
}
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
{
this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
}
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
{
_name = name;
_value = value;
_domain = domain;
_path = path;
_maxAge = maxAge;
_httpOnly = httpOnly;
_secure = secure;
_comment = comment;
_version = version;
_expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
}
/**
* @return the cookie name
*/
public String getName()
{
return _name;
}
/**
* @return the cookie value
*/
public String getValue()
{
return _value;
}
/**
* @return the cookie comment
*/
public String getComment()
{
return _comment;
}
/**
* @return the cookie domain
*/
public String getDomain()
{
return _domain;
}
/**
* @return the cookie max age in seconds
*/
public long getMaxAge()
{
return _maxAge;
}
/**
* @return the cookie path
*/
public String getPath()
{
return _path;
}
/**
* @return whether the cookie is valid for secure domains
*/
public boolean isSecure()
{
return _secure;
}
/**
* @return the cookie version
*/
public int getVersion()
{
return _version;
}
/**
* @return whether the cookie is valid for the http protocol only
*/
public boolean isHttpOnly()
{
return _httpOnly;
}
/**
* @param timeNanos the time to check for cookie expiration, in nanoseconds
* @return whether the cookie is expired by the given time
*/
public boolean isExpired(long timeNanos)
{
return _expiration >= 0 && timeNanos >= _expiration;
}
/**
* @return a string representation of this cookie
*/
public String asString()
{
StringBuilder builder = new StringBuilder();
builder.append(getName()).append("=").append(getValue());
if (getDomain() != null)
builder.append(";$Domain=").append(getDomain());
if (getPath() != null)
builder.append(";$Path=").append(getPath());
return builder.toString();
}
}

View File

@ -0,0 +1,510 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Objects;
import org.eclipse.jetty.util.StringUtil;
/** A HTTP Field
*/
public class HttpField
{
private final static String __zeroquality="q=0";
private final HttpHeader _header;
private final String _name;
private final String _value;
// cached hashcode for case insensitive name
private int hash = 0;
public HttpField(HttpHeader header, String name, String value)
{
_header = header;
_name = name;
_value = value;
}
public HttpField(HttpHeader header, String value)
{
this(header,header.asString(),value);
}
public HttpField(HttpHeader header, HttpHeaderValue value)
{
this(header,header.asString(),value.asString());
}
public HttpField(String name, String value)
{
this(HttpHeader.CACHE.get(name),name,value);
}
public HttpHeader getHeader()
{
return _header;
}
public String getName()
{
return _name;
}
public String getValue()
{
return _value;
}
public int getIntValue()
{
return Integer.valueOf(_value);
}
public long getLongValue()
{
return Long.valueOf(_value);
}
public String[] getValues()
{
ArrayList<String> list = new ArrayList<>();
int state = 0;
int start=0;
int end=0;
StringBuilder builder = new StringBuilder();
for (int i=0;i<_value.length();i++)
{
char c = _value.charAt(i);
switch(state)
{
case 0: // initial white space
switch(c)
{
case '"': // open quote
state=2;
break;
case ',': // ignore leading empty field
break;
case ' ': // more white space
case '\t':
break;
default: // character
start=i;
end=i;
state=1;
}
break;
case 1: // In token
switch(c)
{
case ',': // next field
list.add(_value.substring(start,end+1));
state=0;
break;
case ' ': // more white space
case '\t':
break;
default:
end=i;
}
break;
case 2: // In Quoted
switch(c)
{
case '\\': // next field
state=3;
break;
case '"': // end quote
list.add(builder.toString());
builder.setLength(0);
state=4;
break;
default:
builder.append(c);
}
break;
case 3: // In Quoted Quoted
builder.append(c);
state=2;
break;
case 4: // WS after end quote
switch(c)
{
case ' ': // white space
case '\t': // white space
break;
case ',': // white space
state=0;
break;
default:
throw new IllegalArgumentException("c="+(int)c);
}
break;
}
}
switch(state)
{
case 0:
break;
case 1:
list.add(_value.substring(start,end+1));
break;
case 4:
break;
default:
throw new IllegalArgumentException("state="+state);
}
return list.toArray(new String[list.size()]);
}
/* ------------------------------------------------------------ */
/** Look for a value in a possible multi valued field
* @param search Values to search for (case insensitive)
* @return True iff the value is contained in the field value entirely or
* as an element of a quoted comma separated list. List element parameters (eg qualities) are ignored,
* except if they are q=0, in which case the item itself is ignored.
*/
public boolean contains(String search)
{
if (search==null)
return _value==null;
if (search.length()==0)
return false;
if (_value==null)
return false;
search = StringUtil.asciiToLowerCase(search);
int state=0;
int match=0;
int param=0;
for (int i=0;i<_value.length();i++)
{
char c = _value.charAt(i);
switch(state)
{
case 0: // initial white space
switch(c)
{
case '"': // open quote
match=0;
state=2;
break;
case ',': // ignore leading empty field
break;
case ';': // ignore leading empty field parameter
param=-1;
match=-1;
state=5;
break;
case ' ': // more white space
case '\t':
break;
default: // character
match = Character.toLowerCase(c)==search.charAt(0)?1:-1;
state=1;
break;
}
break;
case 1: // In token
switch(c)
{
case ',': // next field
// Have we matched the token?
if (match==search.length())
return true;
state=0;
break;
case ';':
param=match>=0?0:-1;
state=5; // parameter
break;
default:
if (match>0)
{
if (match<search.length())
match=Character.toLowerCase(c)==search.charAt(match)?(match+1):-1;
else if (c!=' ' && c!= '\t')
match=-1;
}
break;
}
break;
case 2: // In Quoted token
switch(c)
{
case '\\': // quoted character
state=3;
break;
case '"': // end quote
state=4;
break;
default:
if (match>=0)
{
if (match<search.length())
match=Character.toLowerCase(c)==search.charAt(match)?(match+1):-1;
else
match=-1;
}
}
break;
case 3: // In Quoted character in quoted token
if (match>=0)
{
if (match<search.length())
match=Character.toLowerCase(c)==search.charAt(match)?(match+1):-1;
else
match=-1;
}
state=2;
break;
case 4: // WS after end quote
switch(c)
{
case ' ': // white space
case '\t': // white space
break;
case ';':
state=5; // parameter
break;
case ',': // end token
// Have we matched the token?
if (match==search.length())
return true;
state=0;
break;
default:
// This is an illegal token, just ignore
match=-1;
}
break;
case 5: // parameter
switch(c)
{
case ',': // end token
// Have we matched the token and not q=0?
if (param!=__zeroquality.length() && match==search.length())
return true;
param=0;
state=0;
break;
case ' ': // white space
case '\t': // white space
break;
default:
if (param>=0)
{
if (param<__zeroquality.length())
param=Character.toLowerCase(c)==__zeroquality.charAt(param)?(param+1):-1;
else if (c!='0'&&c!='.')
param=-1;
}
}
break;
default:
throw new IllegalStateException();
}
}
return param!=__zeroquality.length() && match==search.length();
}
@Override
public String toString()
{
String v=getValue();
return getName() + ": " + (v==null?"":v);
}
public boolean isSameName(HttpField field)
{
if (field==null)
return false;
if (field==this)
return true;
if (_header!=null && _header==field.getHeader())
return true;
if (_name.equalsIgnoreCase(field.getName()))
return true;
return false;
}
private int nameHashCode()
{
int h = this.hash;
int len = _name.length();
if (h == 0 && len > 0)
{
for (int i = 0; i < len; i++)
{
// simple case insensitive hash
char c = _name.charAt(i);
// assuming us-ascii (per last paragraph on http://tools.ietf.org/html/rfc7230#section-3.2.4)
if ((c >= 'a' && c <= 'z'))
c -= 0x20;
h = 31 * h + c;
}
this.hash = h;
}
return h;
}
@Override
public int hashCode()
{
if (_header==null)
return _value.hashCode() ^ nameHashCode();
return _value.hashCode() ^ _header.hashCode();
}
@Override
public boolean equals(Object o)
{
if (o==this)
return true;
if (!(o instanceof HttpField))
return false;
HttpField field=(HttpField)o;
if (_header!=field.getHeader())
return false;
if (!_name.equalsIgnoreCase(field.getName()))
return false;
if (_value==null && field.getValue()!=null)
return false;
return Objects.equals(_value,field.getValue());
}
public static class IntValueHttpField extends HttpField
{
private final int _int;
public IntValueHttpField(HttpHeader header, String name, String value, int intValue)
{
super(header,name,value);
_int=intValue;
}
public IntValueHttpField(HttpHeader header, String name, String value)
{
this(header,name,value,Integer.valueOf(value));
}
public IntValueHttpField(HttpHeader header, String name, int intValue)
{
this(header,name,Integer.toString(intValue),intValue);
}
public IntValueHttpField(HttpHeader header, int value)
{
this(header,header.asString(),value);
}
@Override
public int getIntValue()
{
return _int;
}
@Override
public long getLongValue()
{
return _int;
}
}
public static class LongValueHttpField extends HttpField
{
private final long _long;
public LongValueHttpField(HttpHeader header, String name, String value, long longValue)
{
super(header,name,value);
_long=longValue;
}
public LongValueHttpField(HttpHeader header, String name, String value)
{
this(header,name,value,Long.valueOf(value));
}
public LongValueHttpField(HttpHeader header, String name, long value)
{
this(header,name,Long.toString(value),value);
}
public LongValueHttpField(HttpHeader header,long value)
{
this(header,header.asString(),value);
}
@Override
public int getIntValue()
{
return (int)_long;
}
@Override
public long getLongValue()
{
return _long;
}
}
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
/* ------------------------------------------------------------ */
/** Interface to pre-encode HttpFields. Used by {@link PreEncodedHttpField}
*/
public interface HttpFieldPreEncoder
{
/* ------------------------------------------------------------ */
/** The major version this encoder is for. Both HTTP/1.0 and HTTP/1.1
* use the same field encoding, so the {@link HttpVersion#HTTP_1_0} should
* be return for all HTTP/1.x encodings.
* @return The major version this encoder is for.
*/
HttpVersion getHttpVersion();
byte[] getEncodedField(HttpHeader header, String headerString, String value);
}

View File

@ -0,0 +1,970 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* HTTP Fields. A collection of HTTP header and or Trailer fields.
*
* <p>This class is not synchronized as it is expected that modifications will only be performed by a
* single thread.
*
* <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
*
*/
public class HttpFields implements Iterable<HttpField>
{
@Deprecated
public static final String __separators = ", \t";
private static final Logger LOG = Log.getLogger(HttpFields.class);
private HttpField[] _fields;
private int _size;
/**
* Initialize an empty HttpFields.
*/
public HttpFields()
{
_fields=new HttpField[20];
}
/**
* Initialize an empty HttpFields.
*
* @param capacity the capacity of the http fields
*/
public HttpFields(int capacity)
{
_fields=new HttpField[capacity];
}
/**
* Initialize HttpFields from copy.
*
* @param fields the fields to copy data from
*/
public HttpFields(HttpFields fields)
{
_fields=Arrays.copyOf(fields._fields,fields._fields.length+10);
_size=fields._size;
}
public int size()
{
return _size;
}
@Override
public Iterator<HttpField> iterator()
{
return new Itr();
}
/**
* Get Collection of header names.
* @return the unique set of field names.
*/
public Set<String> getFieldNamesCollection()
{
final Set<String> set = new HashSet<>(_size);
for (HttpField f : this)
{
if (f!=null)
set.add(f.getName());
}
return set;
}
/**
* Get enumeration of header _names. Returns an enumeration of strings representing the header
* _names for this request.
* @return an enumeration of field names
*/
public Enumeration<String> getFieldNames()
{
return Collections.enumeration(getFieldNamesCollection());
}
/**
* Get a Field by index.
* @param index the field index
* @return A Field value or null if the Field value has not been set
*/
public HttpField getField(int index)
{
if (index>=_size)
throw new NoSuchElementException();
return _fields[index];
}
public HttpField getField(HttpHeader header)
{
for (int i=0;i<_size;i++)
{
HttpField f=_fields[i];
if (f.getHeader()==header)
return f;
}
return null;
}
public HttpField getField(String name)
{
for (int i=0;i<_size;i++)
{
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name))
return f;
}
return null;
}
public boolean contains(HttpField field)
{
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.isSameName(field) && f.contains(field.getValue()))
return true;
}
return false;
}
public boolean contains(HttpHeader header, String value)
{
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.getHeader()==header && f.contains(value))
return true;
}
return false;
}
public boolean contains(String name, String value)
{
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name) && f.contains(value))
return true;
}
return false;
}
public boolean contains(HttpHeader header)
{
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.getHeader()==header)
return true;
}
return false;
}
public boolean containsKey(String name)
{
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name))
return true;
}
return false;
}
@Deprecated
public String getStringField(HttpHeader header)
{
return get(header);
}
public String get(HttpHeader header)
{
for (int i=0;i<_size;i++)
{
HttpField f=_fields[i];
if (f.getHeader()==header)
return f.getValue();
}
return null;
}
@Deprecated
public String getStringField(String name)
{
return get(name);
}
public String get(String header)
{
for (int i=0;i<_size;i++)
{
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(header))
return f.getValue();
}
return null;
}
/**
* Get multiple header of the same name
*
* @return List the values
* @param header the header
*/
public List<String> getValuesList(HttpHeader header)
{
final List<String> list = new ArrayList<>();
for (HttpField f : this)
if (f.getHeader()==header)
list.add(f.getValue());
return list;
}
/**
* Get multiple header of the same name
*
* @return List the header values
* @param name the case-insensitive field name
*/
public List<String> getValuesList(String name)
{
final List<String> list = new ArrayList<>();
for (HttpField f : this)
if (f.getName().equalsIgnoreCase(name))
list.add(f.getValue());
return list;
}
/**
* Get multiple field values of the same name, split
* as a {@link QuotedCSV}
*
* @return List the values with OWS stripped
* @param header The header
* @param keepQuotes True if the fields are kept quoted
*/
public List<String> getCSV(HttpHeader header,boolean keepQuotes)
{
QuotedCSV values = new QuotedCSV(keepQuotes);
for (HttpField f : this)
if (f.getHeader()==header)
values.addValue(f.getValue());
return values.getValues();
}
/**
* Get multiple field values of the same name
* as a {@link QuotedCSV}
*
* @return List the values with OWS stripped
* @param name the case-insensitive field name
* @param keepQuotes True if the fields are kept quoted
*/
public List<String> getCSV(String name,boolean keepQuotes)
{
QuotedCSV values = new QuotedCSV(keepQuotes);
for (HttpField f : this)
if (f.getName().equalsIgnoreCase(name))
values.addValue(f.getValue());
return values.getValues();
}
/**
* Get multiple field values of the same name, split and
* sorted as a {@link QuotedQualityCSV}
*
* @return List the values in quality order with the q param and OWS stripped
* @param header The header
*/
public List<String> getQualityCSV(HttpHeader header)
{
QuotedQualityCSV values = new QuotedQualityCSV();
for (HttpField f : this)
if (f.getHeader()==header)
values.addValue(f.getValue());
return values.getValues();
}
/**
* Get multiple field values of the same name, split and
* sorted as a {@link QuotedQualityCSV}
*
* @return List the values in quality order with the q param and OWS stripped
* @param name the case-insensitive field name
*/
public List<String> getQualityCSV(String name)
{
QuotedQualityCSV values = new QuotedQualityCSV();
for (HttpField f : this)
if (f.getName().equalsIgnoreCase(name))
values.addValue(f.getValue());
return values.getValues();
}
/**
* Get multi headers
*
* @return Enumeration of the values
* @param name the case-insensitive field name
*/
public Enumeration<String> getValues(final String name)
{
for (int i=0;i<_size;i++)
{
final HttpField f = _fields[i];
if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
{
final int first=i;
return new Enumeration<String>()
{
HttpField field=f;
int i = first+1;
@Override
public boolean hasMoreElements()
{
if (field==null)
{
while (i<_size)
{
field=_fields[i++];
if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
return true;
}
field=null;
return false;
}
return true;
}
@Override
public String nextElement() throws NoSuchElementException
{
if (hasMoreElements())
{
String value=field.getValue();
field=null;
return value;
}
throw new NoSuchElementException();
}
};
}
}
List<String> empty=Collections.emptyList();
return Collections.enumeration(empty);
}
/**
* Get multi field values with separator. The multiple values can be represented as separate
* headers of the same name, or by a single header using the separator(s), or a combination of
* both. Separators may be quoted.
*
* @param name the case-insensitive field name
* @param separators String of separators.
* @return Enumeration of the values, or null if no such header.
*/
@Deprecated
public Enumeration<String> getValues(String name, final String separators)
{
final Enumeration<String> e = getValues(name);
if (e == null)
return null;
return new Enumeration<String>()
{
QuotedStringTokenizer tok = null;
@Override
public boolean hasMoreElements()
{
if (tok != null && tok.hasMoreElements()) return true;
while (e.hasMoreElements())
{
String value = e.nextElement();
if (value!=null)
{
tok = new QuotedStringTokenizer(value, separators, false, false);
if (tok.hasMoreElements()) return true;
}
}
tok = null;
return false;
}
@Override
public String nextElement() throws NoSuchElementException
{
if (!hasMoreElements()) throw new NoSuchElementException();
String next = (String) tok.nextElement();
if (next != null) next = next.trim();
return next;
}
};
}
public void put(HttpField field)
{
boolean put=false;
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.isSameName(field))
{
if (put)
{
System.arraycopy(_fields,i+1,_fields,i,--_size-i);
}
else
{
_fields[i]=field;
put=true;
}
}
}
if (!put)
add(field);
}
/**
* Set a field.
*
* @param name the name of the field
* @param value the value of the field. If null the field is cleared.
*/
public void put(String name, String value)
{
if (value == null)
remove(name);
else
put(new HttpField(name, value));
}
public void put(HttpHeader header, HttpHeaderValue value)
{
put(header,value.toString());
}
/**
* Set a field.
*
* @param header the header name of the field
* @param value the value of the field. If null the field is cleared.
*/
public void put(HttpHeader header, String value)
{
if (value == null)
remove(header);
else
put(new HttpField(header, value));
}
/**
* Set a field.
*
* @param name the name of the field
* @param list the List value of the field. If null the field is cleared.
*/
public void put(String name, List<String> list)
{
remove(name);
for (String v : list)
if (v!=null)
add(name,v);
}
/**
* Add to or set a field. If the field is allowed to have multiple values, add will add multiple
* headers of the same name.
*
* @param name the name of the field
* @param value the value of the field.
*/
public void add(String name, String value)
{
if (value == null)
return;
HttpField field = new HttpField(name, value);
add(field);
}
public void add(HttpHeader header, HttpHeaderValue value)
{
add(header,value.toString());
}
/**
* Add to or set a field. If the field is allowed to have multiple values, add will add multiple
* headers of the same name.
*
* @param header the header
* @param value the value of the field.
*/
public void add(HttpHeader header, String value)
{
if (value == null) throw new IllegalArgumentException("null value");
HttpField field = new HttpField(header, value);
add(field);
}
/**
* Remove a field.
*
* @param name the field to remove
* @return the header that was removed
*/
public HttpField remove(HttpHeader name)
{
HttpField removed=null;
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.getHeader()==name)
{
removed=f;
System.arraycopy(_fields,i+1,_fields,i,--_size-i);
}
}
return removed;
}
/**
* Remove a field.
*
* @param name the field to remove
* @return the header that was removed
*/
public HttpField remove(String name)
{
HttpField removed=null;
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name))
{
removed=f;
System.arraycopy(_fields,i+1,_fields,i,--_size-i);
}
}
return removed;
}
/**
* Get a header as an long value. Returns the value of an integer field or -1 if not found. The
* case of the field name is ignored.
*
* @param name the case-insensitive field name
* @return the value of the field as a long
* @exception NumberFormatException If bad long found
*/
public long getLongField(String name) throws NumberFormatException
{
HttpField field = getField(name);
return field==null?-1L:field.getLongValue();
}
/**
* Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
* of the field name is ignored.
*
* @param name the case-insensitive field name
* @return the value of the field as a number of milliseconds since unix epoch
*/
public long getDateField(String name)
{
HttpField field = getField(name);
if (field == null)
return -1;
String val = valueParameters(field.getValue(), null);
if (val == null)
return -1;
final long date = DateParser.parseDate(val);
if (date==-1)
throw new IllegalArgumentException("Cannot convert date: " + val);
return date;
}
/**
* Sets the value of an long field.
*
* @param name the field name
* @param value the field long value
*/
public void putLongField(HttpHeader name, long value)
{
String v = Long.toString(value);
put(name, v);
}
/**
* Sets the value of an long field.
*
* @param name the field name
* @param value the field long value
*/
public void putLongField(String name, long value)
{
String v = Long.toString(value);
put(name, v);
}
/**
* Sets the value of a date field.
*
* @param name the field name
* @param date the field date value
*/
public void putDateField(HttpHeader name, long date)
{
String d=DateGenerator.formatDate(date);
put(name, d);
}
/**
* Sets the value of a date field.
*
* @param name the field name
* @param date the field date value
*/
public void putDateField(String name, long date)
{
String d=DateGenerator.formatDate(date);
put(name, d);
}
/**
* Sets the value of a date field.
*
* @param name the field name
* @param date the field date value
*/
public void addDateField(String name, long date)
{
String d=DateGenerator.formatDate(date);
add(name,d);
}
@Override
public int hashCode()
{
int hash=0;
for (HttpField field:_fields)
hash+=field.hashCode();
return hash;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof HttpFields))
return false;
HttpFields that = (HttpFields)o;
// Order is not important, so we cannot rely on List.equals().
if (size() != that.size())
return false;
loop: for (HttpField fi : this)
{
for (HttpField fa : that)
{
if (fi.equals(fa))
continue loop;
}
return false;
}
return true;
}
@Override
public String toString()
{
try
{
StringBuilder buffer = new StringBuilder();
for (HttpField field : this)
{
if (field != null)
{
String tmp = field.getName();
if (tmp != null) buffer.append(tmp);
buffer.append(": ");
tmp = field.getValue();
if (tmp != null) buffer.append(tmp);
buffer.append("\r\n");
}
}
buffer.append("\r\n");
return buffer.toString();
}
catch (Exception e)
{
LOG.warn(e);
return e.toString();
}
}
public void clear()
{
_size=0;
}
public void add(HttpField field)
{
if (field!=null)
{
if (_size==_fields.length)
_fields=Arrays.copyOf(_fields,_size*2);
_fields[_size++]=field;
}
}
public void addAll(HttpFields fields)
{
for (int i=0;i<fields._size;i++)
add(fields._fields[i]);
}
/**
* Add fields from another HttpFields instance. Single valued fields are replaced, while all
* others are added.
*
* @param fields the fields to add
*/
public void add(HttpFields fields)
{
if (fields == null) return;
Enumeration<String> e = fields.getFieldNames();
while (e.hasMoreElements())
{
String name = e.nextElement();
Enumeration<String> values = fields.getValues(name);
while (values.hasMoreElements())
add(name, values.nextElement());
}
}
/**
* Get field value without parameters. Some field values can have parameters. This method separates the
* value from the parameters and optionally populates a map with the parameters. For example:
*
* <PRE>
*
* FieldName : Value ; param1=val1 ; param2=val2
*
* </PRE>
*
* @param value The Field value, possibly with parameters.
* @return The value.
*/
public static String stripParameters(String value)
{
if (value == null) return null;
int i = value.indexOf(';');
if (i < 0) return value;
return value.substring(0, i).trim();
}
/**
* Get field value parameters. Some field values can have parameters. This method separates the
* value from the parameters and optionally populates a map with the parameters. For example:
*
* <PRE>
*
* FieldName : Value ; param1=val1 ; param2=val2
*
* </PRE>
*
* @param value The Field value, possibly with parameters.
* @param parameters A map to populate with the parameters, or null
* @return The value.
*/
public static String valueParameters(String value, Map<String,String> parameters)
{
if (value == null) return null;
int i = value.indexOf(';');
if (i < 0) return value;
if (parameters == null) return value.substring(0, i).trim();
StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
while (tok1.hasMoreTokens())
{
String token = tok1.nextToken();
StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
if (tok2.hasMoreTokens())
{
String paramName = tok2.nextToken();
String paramVal = null;
if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
parameters.put(paramName, paramVal);
}
}
return value.substring(0, i).trim();
}
@Deprecated
private static final Float __one = new Float("1.0");
@Deprecated
private static final Float __zero = new Float("0.0");
@Deprecated
private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
static
{
__qualities.put("*", __one);
__qualities.put("1.0", __one);
__qualities.put("1", __one);
__qualities.put("0.9", new Float("0.9"));
__qualities.put("0.8", new Float("0.8"));
__qualities.put("0.7", new Float("0.7"));
__qualities.put("0.66", new Float("0.66"));
__qualities.put("0.6", new Float("0.6"));
__qualities.put("0.5", new Float("0.5"));
__qualities.put("0.4", new Float("0.4"));
__qualities.put("0.33", new Float("0.33"));
__qualities.put("0.3", new Float("0.3"));
__qualities.put("0.2", new Float("0.2"));
__qualities.put("0.1", new Float("0.1"));
__qualities.put("0", __zero);
__qualities.put("0.0", __zero);
}
@Deprecated
public static Float getQuality(String value)
{
if (value == null) return __zero;
int qe = value.indexOf(";");
if (qe++ < 0 || qe == value.length()) return __one;
if (value.charAt(qe++) == 'q')
{
qe++;
Float q = __qualities.get(value, qe, value.length() - qe);
if (q != null)
return q;
}
Map<String,String> params = new HashMap<>(4);
valueParameters(value, params);
String qs = params.get("q");
if (qs==null)
qs="*";
Float q = __qualities.get(qs);
if (q == null)
{
try
{
q = new Float(qs);
}
catch (Exception e)
{
q = __one;
}
}
return q;
}
/**
* List values in quality order.
*
* @param e Enumeration of values with quality parameters
* @return values in quality order.
*/
@Deprecated
public static List<String> qualityList(Enumeration<String> e)
{
if (e == null || !e.hasMoreElements())
return Collections.emptyList();
QuotedQualityCSV values = new QuotedQualityCSV();
while(e.hasMoreElements())
values.addValue(e.nextElement());
return values.getValues();
}
private class Itr implements Iterator<HttpField>
{
int _cursor; // index of next element to return
int _last=-1;
public boolean hasNext()
{
return _cursor != _size;
}
public HttpField next()
{
int i = _cursor;
if (i >= _size)
throw new NoSuchElementException();
_cursor = i + 1;
return _fields[_last=i];
}
public void remove()
{
if (_last<0)
throw new IllegalStateException();
System.arraycopy(_fields,_last+1,_fields,_last,--_size-_last);
_cursor=_last;
_last=-1;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,191 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
public enum HttpHeader
{
/* ------------------------------------------------------------ */
/** General Fields.
*/
CONNECTION("Connection"),
CACHE_CONTROL("Cache-Control"),
DATE("Date"),
PRAGMA("Pragma"),
PROXY_CONNECTION ("Proxy-Connection"),
TRAILER("Trailer"),
TRANSFER_ENCODING("Transfer-Encoding"),
UPGRADE("Upgrade"),
VIA("Via"),
WARNING("Warning"),
NEGOTIATE("Negotiate"),
/* ------------------------------------------------------------ */
/** Entity Fields.
*/
ALLOW("Allow"),
CONTENT_ENCODING("Content-Encoding"),
CONTENT_LANGUAGE("Content-Language"),
CONTENT_LENGTH("Content-Length"),
CONTENT_LOCATION("Content-Location"),
CONTENT_MD5("Content-MD5"),
CONTENT_RANGE("Content-Range"),
CONTENT_TYPE("Content-Type"),
EXPIRES("Expires"),
LAST_MODIFIED("Last-Modified"),
/* ------------------------------------------------------------ */
/** Request Fields.
*/
ACCEPT("Accept"),
ACCEPT_CHARSET("Accept-Charset"),
ACCEPT_ENCODING("Accept-Encoding"),
ACCEPT_LANGUAGE("Accept-Language"),
AUTHORIZATION("Authorization"),
EXPECT("Expect"),
FORWARDED("Forwarded"),
FROM("From"),
HOST("Host"),
IF_MATCH("If-Match"),
IF_MODIFIED_SINCE("If-Modified-Since"),
IF_NONE_MATCH("If-None-Match"),
IF_RANGE("If-Range"),
IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
KEEP_ALIVE("Keep-Alive"),
MAX_FORWARDS("Max-Forwards"),
PROXY_AUTHORIZATION("Proxy-Authorization"),
RANGE("Range"),
REQUEST_RANGE("Request-Range"),
REFERER("Referer"),
TE("TE"),
USER_AGENT("User-Agent"),
X_FORWARDED_FOR("X-Forwarded-For"),
X_FORWARDED_PROTO("X-Forwarded-Proto"),
X_FORWARDED_SERVER("X-Forwarded-Server"),
X_FORWARDED_HOST("X-Forwarded-Host"),
/* ------------------------------------------------------------ */
/** Response Fields.
*/
ACCEPT_RANGES("Accept-Ranges"),
AGE("Age"),
ETAG("ETag"),
LOCATION("Location"),
PROXY_AUTHENTICATE("Proxy-Authenticate"),
RETRY_AFTER("Retry-After"),
SERVER("Server"),
SERVLET_ENGINE("Servlet-Engine"),
VARY("Vary"),
WWW_AUTHENTICATE("WWW-Authenticate"),
/* ------------------------------------------------------------ */
/** Other Fields.
*/
COOKIE("Cookie"),
SET_COOKIE("Set-Cookie"),
SET_COOKIE2("Set-Cookie2"),
MIME_VERSION("MIME-Version"),
IDENTITY("identity"),
X_POWERED_BY("X-Powered-By"),
HTTP2_SETTINGS("HTTP2-Settings"),
STRICT_TRANSPORT_SECURITY("Strict-Transport-Security"),
/* ------------------------------------------------------------ */
/** HTTP2 Fields.
*/
C_METHOD(":method"),
C_SCHEME(":scheme"),
C_AUTHORITY(":authority"),
C_PATH(":path"),
C_STATUS(":status"),
UNKNOWN("::UNKNOWN::");
/* ------------------------------------------------------------ */
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(560);
static
{
for (HttpHeader header : HttpHeader.values())
if (header!=UNKNOWN)
if (!CACHE.put(header.toString(),header))
throw new IllegalStateException();
}
private final String _string;
private final byte[] _bytes;
private final byte[] _bytesColonSpace;
private final ByteBuffer _buffer;
/* ------------------------------------------------------------ */
HttpHeader(String s)
{
_string=s;
_bytes=StringUtil.getBytes(s);
_bytesColonSpace=StringUtil.getBytes(s+": ");
_buffer=ByteBuffer.wrap(_bytes);
}
/* ------------------------------------------------------------ */
public ByteBuffer toBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public byte[] getBytes()
{
return _bytes;
}
/* ------------------------------------------------------------ */
public byte[] getBytesColonSpace()
{
return _bytesColonSpace;
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return _string.equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public String asString()
{
return _string;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return _string;
}
}

View File

@ -0,0 +1,104 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Trie;
/**
*
*/
public enum HttpHeaderValue
{
CLOSE("close"),
CHUNKED("chunked"),
GZIP("gzip"),
IDENTITY("identity"),
KEEP_ALIVE("keep-alive"),
CONTINUE("100-continue"),
PROCESSING("102-processing"),
TE("TE"),
BYTES("bytes"),
NO_CACHE("no-cache"),
UPGRADE("Upgrade"),
UNKNOWN("::UNKNOWN::");
/* ------------------------------------------------------------ */
public final static Trie<HttpHeaderValue> CACHE= new ArrayTrie<HttpHeaderValue>();
static
{
for (HttpHeaderValue value : HttpHeaderValue.values())
if (value!=UNKNOWN)
CACHE.put(value.toString(),value);
}
private final String _string;
private final ByteBuffer _buffer;
/* ------------------------------------------------------------ */
HttpHeaderValue(String s)
{
_string=s;
_buffer=BufferUtil.toBuffer(s);
}
/* ------------------------------------------------------------ */
public ByteBuffer toBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return _string.equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public String asString()
{
return _string;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return _string;
}
/* ------------------------------------------------------------ */
private static EnumSet<HttpHeader> __known =
EnumSet.of(HttpHeader.CONNECTION,
HttpHeader.TRANSFER_ENCODING,
HttpHeader.CONTENT_ENCODING);
/* ------------------------------------------------------------ */
public static boolean hasKnownValues(HttpHeader header)
{
if (header==null)
return false;
return __known.contains(header);
}
}

View File

@ -0,0 +1,187 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
/* ------------------------------------------------------------------------------- */
/**
*/
public enum HttpMethod
{
GET,
POST,
HEAD,
PUT,
OPTIONS,
DELETE,
TRACE,
CONNECT,
MOVE,
PROXY,
PRI;
/* ------------------------------------------------------------ */
/**
* Optimized lookup to find a method name and trailing space in a byte array.
* @param bytes Array containing ISO-8859-1 characters
* @param position The first valid index
* @param limit The first non valid index
* @return A HttpMethod if a match or null if no easy match.
*/
public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
{
int length=limit-position;
if (length<4)
return null;
switch(bytes[position])
{
case 'G':
if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
return GET;
break;
case 'P':
if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
return POST;
if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
return PROXY;
if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
return PUT;
if (bytes[position+1]=='R' && bytes[position+2]=='I' && bytes[position+3]==' ')
return PRI;
break;
case 'H':
if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
return HEAD;
break;
case 'O':
if (bytes[position+1]=='P' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
return OPTIONS;
break;
case 'D':
if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
return DELETE;
break;
case 'T':
if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
bytes[position+4]=='E' && bytes[position+5]==' ' )
return TRACE;
break;
case 'C':
if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
return CONNECT;
break;
case 'M':
if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && length>=5 && bytes[position+4]==' ')
return MOVE;
break;
default:
break;
}
return null;
}
/* ------------------------------------------------------------ */
/**
* Optimized lookup to find a method name and trailing space in a byte array.
* @param buffer buffer containing ISO-8859-1 characters, it is not modified.
* @return A HttpMethod if a match or null if no easy match.
*/
public static HttpMethod lookAheadGet(ByteBuffer buffer)
{
if (buffer.hasArray())
return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
int l = buffer.remaining();
if (l>=4)
{
HttpMethod m = CACHE.getBest(buffer,0,l);
if (m!=null)
{
int ml = m.asString().length();
if (l>ml && buffer.get(buffer.position()+ml)==' ')
return m;
}
}
return null;
}
/* ------------------------------------------------------------ */
public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
static
{
for (HttpMethod method : HttpMethod.values())
CACHE.put(method.toString(),method);
}
/* ------------------------------------------------------------ */
private final ByteBuffer _buffer;
private final byte[] _bytes;
/* ------------------------------------------------------------ */
HttpMethod()
{
_bytes=StringUtil.getBytes(toString());
_buffer=ByteBuffer.wrap(_bytes);
}
/* ------------------------------------------------------------ */
public byte[] getBytes()
{
return _bytes;
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return toString().equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public ByteBuffer asBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public String asString()
{
return toString();
}
/* ------------------------------------------------------------ */
/**
* Converts the given String parameter to an HttpMethod
* @param method the String to get the equivalent HttpMethod from
* @return the HttpMethod or null if the parameter method is unknown
*/
public static HttpMethod fromString(String method)
{
return CACHE.get(method);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Trie;
/* ------------------------------------------------------------------------------- */
/**
*/
public enum HttpScheme
{
HTTP("http"),
HTTPS("https"),
WS("ws"),
WSS("wss");
/* ------------------------------------------------------------ */
public final static Trie<HttpScheme> CACHE= new ArrayTrie<HttpScheme>();
static
{
for (HttpScheme version : HttpScheme.values())
CACHE.put(version.asString(),version);
}
private final String _string;
private final ByteBuffer _buffer;
/* ------------------------------------------------------------ */
HttpScheme(String s)
{
_string=s;
_buffer=BufferUtil.toBuffer(s);
}
/* ------------------------------------------------------------ */
public ByteBuffer asByteBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return s!=null && _string.equalsIgnoreCase(s);
}
public String asString()
{
return _string;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return _string;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
/**
* HTTP constants
*/
public interface HttpTokens
{
// Terminal symbols.
static final byte COLON= (byte)':';
static final byte TAB= 0x09;
static final byte LINE_FEED= 0x0A;
static final byte CARRIAGE_RETURN= 0x0D;
static final byte SPACE= 0x20;
static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
static final byte SEMI_COLON= (byte)';';
public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT }
}

View File

@ -0,0 +1,783 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded;
/* ------------------------------------------------------------ */
/** Http URI.
* Parse a HTTP URI from a string or byte array. Given a URI
* <code>http://user@host:port/path/info;param?query#fragment</code>
* this class will split it into the following undecoded optional elements:<ul>
* <li>{@link #getScheme()} - http:</li>
* <li>{@link #getAuthority()} - //name@host:port</li>
* <li>{@link #getHost()} - host</li>
* <li>{@link #getPort()} - port</li>
* <li>{@link #getPath()} - /path/info</li>
* <li>{@link #getParam()} - param</li>
* <li>{@link #getQuery()} - query</li>
* <li>{@link #getFragment()} - fragment</li>
* </ul>
*
* <p>Any parameters will be returned from {@link #getPath()}, but are excluded from the
* return value of {@link #getDecodedPath()}. If there are multiple parameters, the
* {@link #getParam()} method returns only the last one.
*/
public class HttpURI
{
private enum State {
START,
HOST_OR_PATH,
SCHEME_OR_PATH,
HOST,
IPV6,
PORT,
PATH,
PARAM,
QUERY,
FRAGMENT,
ASTERISK};
private String _scheme;
private String _user;
private String _host;
private int _port;
private String _path;
private String _param;
private String _query;
private String _fragment;
String _uri;
String _decodedPath;
/* ------------------------------------------------------------ */
/**
* Construct a normalized URI.
* Port is not set if it is the default port.
* @param scheme the URI scheme
* @param host the URI hose
* @param port the URI port
* @param path the URI path
* @param param the URI param
* @param query the URI query
* @param fragment the URI fragment
* @return the normalized URI
*/
public static HttpURI createHttpURI(String scheme, String host, int port, String path, String param, String query, String fragment)
{
if (port==80 && HttpScheme.HTTP.is(scheme))
port=0;
if (port==443 && HttpScheme.HTTPS.is(scheme))
port=0;
return new HttpURI(scheme,host,port,path,param,query,fragment);
}
/* ------------------------------------------------------------ */
public HttpURI()
{
}
/* ------------------------------------------------------------ */
public HttpURI(String scheme, String host, int port, String path, String param, String query, String fragment)
{
_scheme = scheme;
_host = host;
_port = port;
_path = path;
_param = param;
_query = query;
_fragment = fragment;
}
/* ------------------------------------------------------------ */
public HttpURI(HttpURI uri)
{
this(uri._scheme,uri._host,uri._port,uri._path,uri._param,uri._query,uri._fragment);
}
/* ------------------------------------------------------------ */
public HttpURI(String uri)
{
_port=-1;
parse(State.START,uri,0,uri.length());
}
/* ------------------------------------------------------------ */
public HttpURI(URI uri)
{
_uri=null;
_scheme=uri.getScheme();
_host=uri.getHost();
if (_host==null && uri.getRawSchemeSpecificPart().startsWith("//"))
_host="";
_port=uri.getPort();
_user = uri.getUserInfo();
_path=uri.getRawPath();
_decodedPath = uri.getPath();
if (_decodedPath != null)
{
int p = _decodedPath.lastIndexOf(';');
if (p >= 0)
_param = _decodedPath.substring(p + 1);
}
_query=uri.getRawQuery();
_fragment=uri.getFragment();
_decodedPath=null;
}
/* ------------------------------------------------------------ */
public HttpURI(String scheme, String host, int port, String pathQuery)
{
_uri=null;
_scheme=scheme;
_host=host;
_port=port;
parse(State.PATH,pathQuery,0,pathQuery.length());
}
/* ------------------------------------------------------------ */
public void parse(String uri)
{
clear();
_uri=uri;
parse(State.START,uri,0,uri.length());
}
/* ------------------------------------------------------------ */
/** Parse according to https://tools.ietf.org/html/rfc7230#section-5.3
* @param method
* @param uri
*/
public void parseRequestTarget(String method,String uri)
{
clear();
_uri=uri;
if (HttpMethod.CONNECT.is(method))
_path=uri;
else
parse(uri.startsWith("/")?State.PATH:State.START,uri,0,uri.length());
}
/* ------------------------------------------------------------ */
@Deprecated
public void parseConnect(String uri)
{
clear();
_uri=uri;
_path=uri;
}
/* ------------------------------------------------------------ */
public void parse(String uri, int offset, int length)
{
clear();
int end=offset+length;
_uri=uri.substring(offset,end);
parse(State.START,uri,offset,end);
}
/* ------------------------------------------------------------ */
private void parse(State state, final String uri, final int offset, final int end)
{
boolean encoded=false;
int mark=offset;
int path_mark=0;
for (int i=offset; i<end; i++)
{
char c=uri.charAt(i);
switch (state)
{
case START:
{
switch(c)
{
case '/':
mark = i;
state = State.HOST_OR_PATH;
break;
case ';':
mark=i+1;
state=State.PARAM;
break;
case '?':
// assume empty path (if seen at start)
_path = "";
mark=i+1;
state=State.QUERY;
break;
case '#':
mark=i+1;
state=State.FRAGMENT;
break;
case '*':
_path="*";
state=State.ASTERISK;
break;
default:
mark=i;
if (_scheme==null)
state=State.SCHEME_OR_PATH;
else
{
path_mark=i;
state=State.PATH;
}
}
continue;
}
case SCHEME_OR_PATH:
{
switch (c)
{
case ':':
// must have been a scheme
_scheme=uri.substring(mark,i);
// Start again with scheme set
state=State.START;
break;
case '/':
// must have been in a path and still are
state=State.PATH;
break;
case ';':
// must have been in a path
mark=i+1;
state=State.PARAM;
break;
case '?':
// must have been in a path
_path=uri.substring(mark,i);
mark=i+1;
state=State.QUERY;
break;
case '%':
// must have be in an encoded path
encoded=true;
state=State.PATH;
break;
case '#':
// must have been in a path
_path=uri.substring(mark,i);
state=State.FRAGMENT;
break;
}
continue;
}
case HOST_OR_PATH:
{
switch(c)
{
case '/':
_host="";
mark=i+1;
state=State.HOST;
break;
case '@':
case ';':
case '?':
case '#':
// was a path, look again
i--;
path_mark=mark;
state=State.PATH;
break;
default:
// it is a path
path_mark=mark;
state=State.PATH;
}
continue;
}
case HOST:
{
switch (c)
{
case '/':
_host = uri.substring(mark,i);
path_mark=mark=i;
state=State.PATH;
break;
case ':':
if (i > mark)
_host=uri.substring(mark,i);
mark=i+1;
state=State.PORT;
break;
case '@':
if (_user!=null)
throw new IllegalArgumentException("Bad authority");
_user=uri.substring(mark,i);
mark=i+1;
break;
case '[':
state=State.IPV6;
break;
}
continue;
}
case IPV6:
{
switch (c)
{
case '/':
throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
case ']':
c = uri.charAt(++i);
_host=uri.substring(mark,i);
if (c == ':')
{
mark=i+1;
state=State.PORT;
}
else
{
path_mark=mark=i;
state=State.PATH;
}
break;
}
continue;
}
case PORT:
{
if (c=='@')
{
if (_user!=null)
throw new IllegalArgumentException("Bad authority");
// It wasn't a port, but a password!
_user=_host+":"+uri.substring(mark,i);
mark=i+1;
state=State.HOST;
}
else if (c=='/')
{
_port=TypeUtil.parseInt(uri,mark,i-mark,10);
path_mark=mark=i;
state=State.PATH;
}
continue;
}
case PATH:
{
switch (c)
{
case ';':
mark=i+1;
state=State.PARAM;
break;
case '?':
_path=uri.substring(path_mark,i);
mark=i+1;
state=State.QUERY;
break;
case '#':
_path=uri.substring(path_mark,i);
mark=i+1;
state=State.FRAGMENT;
break;
case '%':
encoded=true;
break;
}
continue;
}
case PARAM:
{
switch (c)
{
case '?':
_path=uri.substring(path_mark,i);
_param=uri.substring(mark,i);
mark=i+1;
state=State.QUERY;
break;
case '#':
_path=uri.substring(path_mark,i);
_param=uri.substring(mark,i);
mark=i+1;
state=State.FRAGMENT;
break;
case '/':
encoded=true;
// ignore internal params
state=State.PATH;
break;
case ';':
// multiple parameters
mark=i+1;
break;
}
continue;
}
case QUERY:
{
if (c=='#')
{
_query=uri.substring(mark,i);
mark=i+1;
state=State.FRAGMENT;
}
continue;
}
case ASTERISK:
{
throw new IllegalArgumentException("only '*'");
}
case FRAGMENT:
{
_fragment=uri.substring(mark,end);
i=end;
}
}
}
switch(state)
{
case START:
break;
case SCHEME_OR_PATH:
_path=uri.substring(mark,end);
break;
case HOST_OR_PATH:
_path=uri.substring(mark,end);
break;
case HOST:
if(end>mark)
_host=uri.substring(mark,end);
break;
case IPV6:
throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
case PORT:
_port=TypeUtil.parseInt(uri,mark,end-mark,10);
break;
case ASTERISK:
break;
case FRAGMENT:
_fragment=uri.substring(mark,end);
break;
case PARAM:
_path=uri.substring(path_mark,end);
_param=uri.substring(mark,end);
break;
case PATH:
_path=uri.substring(path_mark,end);
break;
case QUERY:
_query=uri.substring(mark,end);
break;
}
if (!encoded)
{
if (_param==null)
_decodedPath=_path;
else
_decodedPath=_path.substring(0,_path.length()-_param.length()-1);
}
}
/* ------------------------------------------------------------ */
public String getScheme()
{
return _scheme;
}
/* ------------------------------------------------------------ */
public String getHost()
{
// Return null for empty host to retain compatibility with java.net.URI
if (_host!=null && _host.length()==0)
return null;
return _host;
}
/* ------------------------------------------------------------ */
public int getPort()
{
return _port;
}
/* ------------------------------------------------------------ */
/**
* The parsed Path.
*
* @return the path as parsed on valid URI. null for invalid URI.
*/
public String getPath()
{
return _path;
}
/* ------------------------------------------------------------ */
public String getDecodedPath()
{
if (_decodedPath==null && _path!=null)
_decodedPath=URIUtil.decodePath(_path);
return _decodedPath;
}
/* ------------------------------------------------------------ */
public String getParam()
{
return _param;
}
/* ------------------------------------------------------------ */
public String getQuery()
{
return _query;
}
/* ------------------------------------------------------------ */
public boolean hasQuery()
{
return _query!=null && _query.length()>0;
}
/* ------------------------------------------------------------ */
public String getFragment()
{
return _fragment;
}
/* ------------------------------------------------------------ */
public void decodeQueryTo(MultiMap<String> parameters)
{
if (_query==_fragment)
return;
UrlEncoded.decodeUtf8To(_query,parameters);
}
/* ------------------------------------------------------------ */
public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
{
decodeQueryTo(parameters,Charset.forName(encoding));
}
/* ------------------------------------------------------------ */
public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
{
if (_query==_fragment)
return;
if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
UrlEncoded.decodeUtf8To(_query,parameters);
else
UrlEncoded.decodeTo(_query,parameters,encoding);
}
/* ------------------------------------------------------------ */
public void clear()
{
_uri=null;
_scheme=null;
_host=null;
_port=-1;
_path=null;
_param=null;
_query=null;
_fragment=null;
_decodedPath=null;
}
/* ------------------------------------------------------------ */
public boolean isAbsolute()
{
return _scheme!=null && _scheme.length()>0;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
if (_uri==null)
{
StringBuilder out = new StringBuilder();
if (_scheme!=null)
out.append(_scheme).append(':');
if (_host != null)
{
out.append("//");
if (_user != null)
out.append(_user).append('@');
out.append(_host);
}
if (_port>0)
out.append(':').append(_port);
if (_path!=null)
out.append(_path);
if (_query!=null)
out.append('?').append(_query);
if (_fragment!=null)
out.append('#').append(_fragment);
if (out.length()>0)
_uri=out.toString();
else
_uri="";
}
return _uri;
}
/* ------------------------------------------------------------ */
public boolean equals(Object o)
{
if (o==this)
return true;
if (!(o instanceof HttpURI))
return false;
return toString().equals(o.toString());
}
/* ------------------------------------------------------------ */
public void setScheme(String scheme)
{
_scheme=scheme;
_uri=null;
}
/* ------------------------------------------------------------ */
/**
* @param host the host
* @param port the port
*/
public void setAuthority(String host, int port)
{
_host=host;
_port=port;
_uri=null;
}
/* ------------------------------------------------------------ */
/**
* @param path the path
*/
public void setPath(String path)
{
_uri=null;
_path=path;
_decodedPath=null;
}
/* ------------------------------------------------------------ */
public void setPathQuery(String path)
{
_uri=null;
_path=null;
_decodedPath=null;
_param=null;
_fragment=null;
if (path!=null)
parse(State.PATH,path,0,path.length());
}
/* ------------------------------------------------------------ */
public void setQuery(String query)
{
_query=query;
_uri=null;
}
/* ------------------------------------------------------------ */
public URI toURI() throws URISyntaxException
{
return new URI(_scheme,null,_host,_port,_path,_query==null?null:UrlEncoded.decodeString(_query),_fragment);
}
/* ------------------------------------------------------------ */
public String getPathQuery()
{
if (_query==null)
return _path;
return _path+"?"+_query;
}
/* ------------------------------------------------------------ */
public String getAuthority()
{
if (_port>0)
return _host+":"+_port;
return _host;
}
/* ------------------------------------------------------------ */
public String getUser()
{
return _user;
}
}

View File

@ -0,0 +1,173 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
/* ------------------------------------------------------------------------------- */
public enum HttpVersion
{
HTTP_0_9("HTTP/0.9",9),
HTTP_1_0("HTTP/1.0",10),
HTTP_1_1("HTTP/1.1",11),
HTTP_2("HTTP/2.0",20);
/* ------------------------------------------------------------ */
public final static Trie<HttpVersion> CACHE= new ArrayTrie<HttpVersion>();
static
{
for (HttpVersion version : HttpVersion.values())
CACHE.put(version.toString(),version);
}
/* ------------------------------------------------------------ */
/**
* Optimised lookup to find a Http Version and whitespace in a byte array.
* @param bytes Array containing ISO-8859-1 characters
* @param position The first valid index
* @param limit The first non valid index
* @return A HttpMethod if a match or null if no easy match.
*/
public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
{
int length=limit-position;
if (length<9)
return null;
if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
((bytes[position]=='H' && bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
(bytes[position]=='h' && bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
{
switch(bytes[position+5])
{
case '1':
switch(bytes[position+7])
{
case '0':
return HTTP_1_0;
case '1':
return HTTP_1_1;
}
break;
case '2':
switch(bytes[position+7])
{
case '0':
return HTTP_2;
}
break;
}
}
return null;
}
/* ------------------------------------------------------------ */
/**
* Optimised lookup to find a HTTP Version and trailing white space in a byte array.
* @param buffer buffer containing ISO-8859-1 characters
* @return A HttpVersion if a match or null if no easy match.
*/
public static HttpVersion lookAheadGet(ByteBuffer buffer)
{
if (buffer.hasArray())
return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
return null;
}
private final String _string;
private final byte[] _bytes;
private final ByteBuffer _buffer;
private final int _version;
/* ------------------------------------------------------------ */
HttpVersion(String s,int version)
{
_string=s;
_bytes=StringUtil.getBytes(s);
_buffer=ByteBuffer.wrap(_bytes);
_version=version;
}
/* ------------------------------------------------------------ */
public byte[] toBytes()
{
return _bytes;
}
/* ------------------------------------------------------------ */
public ByteBuffer toBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public int getVersion()
{
return _version;
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return _string.equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public String asString()
{
return _string;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return _string;
}
/**
* Case insensitive fromString() conversion
* @param version the String to convert to enum constant
* @return the enum constant or null if version unknown
*/
public static HttpVersion fromString(String version)
{
return CACHE.get(version);
}
/* ------------------------------------------------------------ */
public static HttpVersion fromVersion(int version)
{
switch(version)
{
case 9: return HttpVersion.HTTP_0_9;
case 10: return HttpVersion.HTTP_1_0;
case 11: return HttpVersion.HTTP_1_1;
case 20: return HttpVersion.HTTP_2;
default: throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,298 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.Collections;
import java.util.Iterator;
public class MetaData implements Iterable<HttpField>
{
private HttpVersion _httpVersion;
private HttpFields _fields;
private long _contentLength;
public MetaData(HttpVersion version, HttpFields fields)
{
this(version, fields, Long.MIN_VALUE);
}
public MetaData(HttpVersion version, HttpFields fields, long contentLength)
{
_httpVersion = version;
_fields = fields;
_contentLength = contentLength;
}
protected void recycle()
{
_httpVersion = null;
if (_fields != null)
_fields.clear();
_contentLength = Long.MIN_VALUE;
}
public boolean isRequest()
{
return false;
}
public boolean isResponse()
{
return false;
}
/**
* @return the HTTP version of this MetaData object
*/
public HttpVersion getVersion()
{
return _httpVersion;
}
/**
* @param httpVersion the HTTP version to set
*/
public void setHttpVersion(HttpVersion httpVersion)
{
_httpVersion = httpVersion;
}
/**
* @return the HTTP fields of this MetaData object
*/
public HttpFields getFields()
{
return _fields;
}
/**
* @return the content length if available, otherwise {@link Long#MIN_VALUE}
*/
public long getContentLength()
{
if (_contentLength == Long.MIN_VALUE)
{
if (_fields != null)
{
HttpField field = _fields.getField(HttpHeader.CONTENT_LENGTH);
_contentLength = field == null ? -1 : field.getLongValue();
}
}
return _contentLength;
}
/**
* @return an iterator over the HTTP fields
* @see #getFields()
*/
public Iterator<HttpField> iterator()
{
HttpFields fields = getFields();
return fields == null ? Collections.<HttpField>emptyIterator() : fields.iterator();
}
@Override
public String toString()
{
StringBuilder out = new StringBuilder();
for (HttpField field : this)
out.append(field).append(System.lineSeparator());
return out.toString();
}
public static class Request extends MetaData
{
private String _method;
private HttpURI _uri;
public Request(HttpFields fields)
{
this(null, null, null, fields);
}
public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields)
{
this(method, uri, version, fields, Long.MIN_VALUE);
}
public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength)
{
super(version, fields, contentLength);
_method = method;
_uri = uri;
}
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
{
this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields);
}
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(method, new HttpURI(scheme == null ? null : scheme.asString(), hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength);
}
public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(method, new HttpURI(scheme, hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength);
}
public Request(Request request)
{
this(request.getMethod(),new HttpURI(request.getURI()), request.getVersion(), new HttpFields(request.getFields()), request.getContentLength());
}
// TODO MetaData should be immuttable!!!
public void recycle()
{
super.recycle();
_method = null;
if (_uri != null)
_uri.clear();
}
@Override
public boolean isRequest()
{
return true;
}
/**
* @return the HTTP method
*/
public String getMethod()
{
return _method;
}
/**
* @param method the HTTP method to set
*/
public void setMethod(String method)
{
_method = method;
}
/**
* @return the HTTP URI
*/
public HttpURI getURI()
{
return _uri;
}
/**
* @return the HTTP URI in string form
*/
public String getURIString()
{
return _uri == null ? null : _uri.toString();
}
/**
* @param uri the HTTP URI to set
*/
public void setURI(HttpURI uri)
{
_uri = uri;
}
@Override
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{u=%s,%s,h=%d}",
getMethod(), getURI(), getVersion(), fields == null ? -1 : fields.size());
}
}
public static class Response extends MetaData
{
private int _status;
private String _reason;
public Response()
{
this(null, 0, null);
}
public Response(HttpVersion version, int status, HttpFields fields)
{
this(version, status, fields, Long.MIN_VALUE);
}
public Response(HttpVersion version, int status, HttpFields fields, long contentLength)
{
super(version, fields, contentLength);
_status = status;
}
public Response(HttpVersion version, int status, String reason, HttpFields fields, long contentLength)
{
super(version, fields, contentLength);
_reason = reason;
_status = status;
}
@Override
public boolean isResponse()
{
return true;
}
/**
* @return the HTTP status
*/
public int getStatus()
{
return _status;
}
/**
* @return the HTTP reason
*/
public String getReason()
{
return _reason;
}
/**
* @param status the HTTP status to set
*/
public void setStatus(int status)
{
_status = status;
}
/**
* @param reason the HTTP reason to set
*/
public void setReason(String reason)
{
_reason = reason;
}
@Override
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{s=%d,h=%d}", getVersion(), getStatus(), fields == null ? -1 : fields.size());
}
}
}

View File

@ -0,0 +1,497 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
*
*/
public class MimeTypes
{
public enum Type
{
FORM_ENCODED("application/x-www-form-urlencoded"),
MESSAGE_HTTP("message/http"),
MULTIPART_BYTERANGES("multipart/byteranges"),
TEXT_HTML("text/html"),
TEXT_PLAIN("text/plain"),
TEXT_XML("text/xml"),
TEXT_JSON("text/json",StandardCharsets.UTF_8),
APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
TEXT_HTML_8859_1("text/html;charset=iso-8859-1",TEXT_HTML),
TEXT_HTML_UTF_8("text/html;charset=utf-8",TEXT_HTML),
TEXT_PLAIN_8859_1("text/plain;charset=iso-8859-1",TEXT_PLAIN),
TEXT_PLAIN_UTF_8("text/plain;charset=utf-8",TEXT_PLAIN),
TEXT_XML_8859_1("text/xml;charset=iso-8859-1",TEXT_XML),
TEXT_XML_UTF_8("text/xml;charset=utf-8",TEXT_XML),
TEXT_JSON_8859_1("text/json;charset=iso-8859-1",TEXT_JSON),
TEXT_JSON_UTF_8("text/json;charset=utf-8",TEXT_JSON),
APPLICATION_JSON_8859_1("text/json;charset=iso-8859-1",APPLICATION_JSON),
APPLICATION_JSON_UTF_8("text/json;charset=utf-8",APPLICATION_JSON);
/* ------------------------------------------------------------ */
private final String _string;
private final Type _base;
private final ByteBuffer _buffer;
private final Charset _charset;
private final String _charsetString;
private final boolean _assumedCharset;
private final HttpField _field;
/* ------------------------------------------------------------ */
Type(String s)
{
_string=s;
_buffer=BufferUtil.toBuffer(s);
_base=this;
_charset=null;
_charsetString=null;
_assumedCharset=false;
_field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
Type(String s,Type base)
{
_string=s;
_buffer=BufferUtil.toBuffer(s);
_base=base;
int i=s.indexOf(";charset=");
_charset=Charset.forName(s.substring(i+9));
_charsetString=_charset.toString().toLowerCase(Locale.ENGLISH);
_assumedCharset=false;
_field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
Type(String s,Charset cs)
{
_string=s;
_base=this;
_buffer=BufferUtil.toBuffer(s);
_charset=cs;
_charsetString=_charset==null?null:_charset.toString().toLowerCase(Locale.ENGLISH);
_assumedCharset=true;
_field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
public ByteBuffer asBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public Charset getCharset()
{
return _charset;
}
/* ------------------------------------------------------------ */
public String getCharsetString()
{
return _charsetString;
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return _string.equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public String asString()
{
return _string;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return _string;
}
/* ------------------------------------------------------------ */
public boolean isCharsetAssumed()
{
return _assumedCharset;
}
/* ------------------------------------------------------------ */
public HttpField getContentTypeField()
{
return _field;
}
/* ------------------------------------------------------------ */
public Type getBaseType()
{
return _base;
}
}
/* ------------------------------------------------------------ */
private static final Logger LOG = Log.getLogger(MimeTypes.class);
public final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
private final static Map<String,String> __encodings = new HashMap<String,String>();
static
{
for (MimeTypes.Type type : MimeTypes.Type.values())
{
CACHE.put(type.toString(),type);
TYPES.put(type.toString(),type.asBuffer());
int charset=type.toString().indexOf(";charset=");
if (charset>0)
{
String alt=type.toString().replace(";charset=","; charset=");
CACHE.put(alt,type);
TYPES.put(alt,type.asBuffer());
}
}
try
{
ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
Enumeration<String> i = mime.getKeys();
while(i.hasMoreElements())
{
String ext = i.nextElement();
String m = mime.getString(ext);
__dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
}
}
catch(MissingResourceException e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
try
{
ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
Enumeration<String> i = encoding.getKeys();
while(i.hasMoreElements())
{
String type = i.nextElement();
__encodings.put(type,encoding.getString(type));
}
}
catch(MissingResourceException e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
}
/* ------------------------------------------------------------ */
private final Map<String,String> _mimeMap=new HashMap<String,String>();
/* ------------------------------------------------------------ */
/** Constructor.
*/
public MimeTypes()
{
}
/* ------------------------------------------------------------ */
public synchronized Map<String,String> getMimeMap()
{
return _mimeMap;
}
/* ------------------------------------------------------------ */
/**
* @param mimeMap A Map of file extension to mime-type.
*/
public void setMimeMap(Map<String,String> mimeMap)
{
_mimeMap.clear();
if (mimeMap!=null)
{
for (Entry<String, String> ext : mimeMap.entrySet())
_mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
}
}
/* ------------------------------------------------------------ */
/** Get the MIME type by filename extension.
* @param filename A file name
* @return MIME type matching the longest dot extension of the
* file name.
*/
public String getMimeByExtension(String filename)
{
String type=null;
if (filename!=null)
{
int i=-1;
while(type==null)
{
i=filename.indexOf(".",i+1);
if (i<0 || i>=filename.length())
break;
String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
if (_mimeMap!=null)
type=_mimeMap.get(ext);
if (type==null)
type=__dftMimeMap.get(ext);
}
}
if (type==null)
{
if (_mimeMap!=null)
type=_mimeMap.get("*");
if (type==null)
type=__dftMimeMap.get("*");
}
return type;
}
/* ------------------------------------------------------------ */
/** Set a mime mapping
* @param extension the extension
* @param type the mime type
*/
public void addMimeMapping(String extension,String type)
{
_mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
}
/* ------------------------------------------------------------ */
public static Set<String> getKnownMimeTypes()
{
return new HashSet<>(__dftMimeMap.values());
}
/* ------------------------------------------------------------ */
private static String normalizeMimeType(String type)
{
MimeTypes.Type t =CACHE.get(type);
if (t!=null)
return t.asString();
return StringUtil.asciiToLowerCase(type);
}
/* ------------------------------------------------------------ */
public static String getCharsetFromContentType(String value)
{
if (value==null)
return null;
int end=value.length();
int state=0;
int start=0;
boolean quote=false;
int i=0;
for (;i<end;i++)
{
char b = value.charAt(i);
if (quote && state!=10)
{
if ('"'==b)
quote=false;
continue;
}
switch(state)
{
case 0:
if ('"'==b)
{
quote=true;
break;
}
if (';'==b)
state=1;
break;
case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
case 2: if ('h'==b) state=3; else state=0;break;
case 3: if ('a'==b) state=4; else state=0;break;
case 4: if ('r'==b) state=5; else state=0;break;
case 5: if ('s'==b) state=6; else state=0;break;
case 6: if ('e'==b) state=7; else state=0;break;
case 7: if ('t'==b) state=8; else state=0;break;
case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
case 9:
if (' '==b)
break;
if ('"'==b)
{
quote=true;
start=i+1;
state=10;
break;
}
start=i;
state=10;
break;
case 10:
if (!quote && (';'==b || ' '==b )||
(quote && '"'==b ))
return StringUtil.normalizeCharset(value,start,i-start);
}
}
if (state==10)
return StringUtil.normalizeCharset(value,start,i-start);
return null;
}
public static String inferCharsetFromContentType(String value)
{
return __encodings.get(value);
}
public static String getContentTypeWithoutCharset(String value)
{
int end=value.length();
int state=0;
int start=0;
boolean quote=false;
int i=0;
StringBuilder builder=null;
for (;i<end;i++)
{
char b = value.charAt(i);
if ('"'==b)
{
if (quote)
{
quote=false;
}
else
{
quote=true;
}
switch(state)
{
case 11:
builder.append(b);break;
case 10:
break;
case 9:
builder=new StringBuilder();
builder.append(value,0,start+1);
state=10;
break;
default:
start=i;
state=0;
}
continue;
}
if (quote)
{
if (builder!=null && state!=10)
builder.append(b);
continue;
}
switch(state)
{
case 0:
if (';'==b)
state=1;
else if (' '!=b)
start=i;
break;
case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
case 2: if ('h'==b) state=3; else state=0;break;
case 3: if ('a'==b) state=4; else state=0;break;
case 4: if ('r'==b) state=5; else state=0;break;
case 5: if ('s'==b) state=6; else state=0;break;
case 6: if ('e'==b) state=7; else state=0;break;
case 7: if ('t'==b) state=8; else state=0;break;
case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
case 9:
if (' '==b)
break;
builder=new StringBuilder();
builder.append(value,0,start+1);
state=10;
break;
case 10:
if (';'==b)
{
builder.append(b);
state=11;
}
break;
case 11:
if (' '!=b)
builder.append(b);
}
}
if (builder==null)
return value;
return builder.toString();
}
}

View File

@ -0,0 +1,641 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.function.Predicate;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.URIUtil;
/**
* URI path map to Object.
* <p>
* This mapping implements the path specification recommended
* in the 2.2 Servlet API.
* </p>
*
* <p>
* Path specifications can be of the following forms:
* </p>
* <pre>
* /foo/bar - an exact path specification.
* /foo/* - a prefix path specification (must end '/*').
* *.ext - a suffix path specification.
* / - the default path specification.
* "" - the / path specification
* </pre>
*
* Matching is performed in the following order
* <ol>
* <li>Exact match.</li>
* <li>Longest prefix match.</li>
* <li>Longest suffix match.</li>
* <li>default.</li>
* </ol>
*
* <p>
* Multiple path specifications can be mapped by providing a list of
* specifications. By default this class uses characters ":," as path
* separators, unless configured differently by calling the static
* method @see PathMap#setPathSpecSeparators(String)
* <p>
* Special characters within paths such as '?<EFBFBD> and ';' are not treated specially
* as it is assumed they would have been either encoded in the original URL or
* stripped from the path.
* <p>
* This class is not synchronized. If concurrent modifications are
* possible then it should be synchronized at a higher level.
*
* @param <O> the Map.Entry value type
* @deprecated replaced with {@link org.eclipse.jetty.http.pathmap.PathMappings} (this class will be removed in Jetty 10)
*/
@Deprecated
public class PathMap<O> extends HashMap<String,O>
{
/* ------------------------------------------------------------ */
private static String __pathSpecSeparators = ":,";
/* ------------------------------------------------------------ */
/** Set the path spec separator.
* Multiple path specification may be included in a single string
* if they are separated by the characters set in this string.
* By default this class uses ":," characters as path separators.
* @param s separators
*/
public static void setPathSpecSeparators(String s)
{
__pathSpecSeparators=s;
}
/* --------------------------------------------------------------- */
Trie<MappedEntry<O>> _prefixMap=new ArrayTernaryTrie<>(false);
Trie<MappedEntry<O>> _suffixMap=new ArrayTernaryTrie<>(false);
final Map<String,MappedEntry<O>> _exactMap=new HashMap<>();
List<MappedEntry<O>> _defaultSingletonList=null;
MappedEntry<O> _prefixDefault=null;
MappedEntry<O> _default=null;
boolean _nodefault=false;
/* --------------------------------------------------------------- */
public PathMap()
{
this(11);
}
/* --------------------------------------------------------------- */
public PathMap(boolean noDefault)
{
this(11, noDefault);
}
/* --------------------------------------------------------------- */
public PathMap(int capacity)
{
this(capacity, false);
}
/* --------------------------------------------------------------- */
private PathMap(int capacity, boolean noDefault)
{
super(capacity);
_nodefault=noDefault;
}
/* --------------------------------------------------------------- */
/**
* Construct from dictionary PathMap.
* @param dictMap the map representing the dictionary to build this PathMap from
*/
public PathMap(Map<String, ? extends O> dictMap)
{
putAll(dictMap);
}
/* --------------------------------------------------------------- */
/** Add a single path match to the PathMap.
* @param pathSpec The path specification, or comma separated list of
* path specifications.
* @param object The object the path maps to
*/
@Override
public O put(String pathSpec, O object)
{
if ("".equals(pathSpec.trim()))
{
MappedEntry<O> entry = new MappedEntry<>("",object);
entry.setMapped("");
_exactMap.put("", entry);
return super.put("", object);
}
StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
O old =null;
while (tok.hasMoreTokens())
{
String spec=tok.nextToken();
if (!spec.startsWith("/") && !spec.startsWith("*."))
throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
old = super.put(spec,object);
// Make entry that was just created.
MappedEntry<O> entry = new MappedEntry<>(spec,object);
if (entry.getKey().equals(spec))
{
if (spec.equals("/*"))
_prefixDefault=entry;
else if (spec.endsWith("/*"))
{
String mapped=spec.substring(0,spec.length()-2);
entry.setMapped(mapped);
while (!_prefixMap.put(mapped,entry))
_prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_prefixMap,1.5);
}
else if (spec.startsWith("*."))
{
String suffix=spec.substring(2);
while(!_suffixMap.put(suffix,entry))
_suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_suffixMap,1.5);
}
else if (spec.equals(URIUtil.SLASH))
{
if (_nodefault)
_exactMap.put(spec,entry);
else
{
_default=entry;
_defaultSingletonList=Collections.singletonList(_default);
}
}
else
{
entry.setMapped(spec);
_exactMap.put(spec,entry);
}
}
}
return old;
}
/* ------------------------------------------------------------ */
/** Get object matched by the path.
* @param path the path.
* @return Best matched object or null.
*/
public O match(String path)
{
MappedEntry<O> entry = getMatch(path);
if (entry!=null)
return entry.getValue();
return null;
}
/* --------------------------------------------------------------- */
/** Get the entry mapped by the best specification.
* @param path the path.
* @return Map.Entry of the best matched or null.
*/
public MappedEntry<O> getMatch(String path)
{
if (path==null)
return null;
int l=path.length();
MappedEntry<O> entry=null;
//special case
if (l == 1 && path.charAt(0)=='/')
{
entry = _exactMap.get("");
if (entry != null)
return entry;
}
// try exact match
entry=_exactMap.get(path);
if (entry!=null)
return entry;
// prefix search
int i=l;
final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
while(i>=0)
{
entry=prefix_map.getBest(path,0,i);
if (entry==null)
break;
String key = entry.getKey();
if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
return entry;
i=key.length()-3;
}
// Prefix Default
if (_prefixDefault!=null)
return _prefixDefault;
// Extension search
i=0;
final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
while ((i=path.indexOf('.',i+1))>0)
{
entry=suffix_map.get(path,i+1,l-i-1);
if (entry!=null)
return entry;
}
// Default
return _default;
}
/* --------------------------------------------------------------- */
/** Get all entries matched by the path.
* Best match first.
* @param path Path to match
* @return List of Map.Entry instances key=pathSpec
*/
public List<? extends Map.Entry<String,O>> getMatches(String path)
{
MappedEntry<O> entry;
List<MappedEntry<O>> entries=new ArrayList<>();
if (path==null)
return entries;
if (path.length()==0)
return _defaultSingletonList;
// try exact match
entry=_exactMap.get(path);
if (entry!=null)
entries.add(entry);
// prefix search
int l=path.length();
int i=l;
final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
while(i>=0)
{
entry=prefix_map.getBest(path,0,i);
if (entry==null)
break;
String key = entry.getKey();
if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
entries.add(entry);
i=key.length()-3;
}
// Prefix Default
if (_prefixDefault!=null)
entries.add(_prefixDefault);
// Extension search
i=0;
final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
while ((i=path.indexOf('.',i+1))>0)
{
entry=suffix_map.get(path,i+1,l-i-1);
if (entry!=null)
entries.add(entry);
}
// root match
if ("/".equals(path))
{
entry=_exactMap.get("");
if (entry!=null)
entries.add(entry);
}
// Default
if (_default!=null)
entries.add(_default);
return entries;
}
/* --------------------------------------------------------------- */
/** Return whether the path matches any entries in the PathMap,
* excluding the default entry
* @param path Path to match
* @return Whether the PathMap contains any entries that match this
*/
public boolean containsMatch(String path)
{
MappedEntry<?> match = getMatch(path);
return match!=null && !match.equals(_default);
}
/* --------------------------------------------------------------- */
@Override
public O remove(Object pathSpec)
{
if (pathSpec!=null)
{
String spec=(String) pathSpec;
if (spec.equals("/*"))
_prefixDefault=null;
else if (spec.endsWith("/*"))
_prefixMap.remove(spec.substring(0,spec.length()-2));
else if (spec.startsWith("*."))
_suffixMap.remove(spec.substring(2));
else if (spec.equals(URIUtil.SLASH))
{
_default=null;
_defaultSingletonList=null;
}
else
_exactMap.remove(spec);
}
return super.remove(pathSpec);
}
/* --------------------------------------------------------------- */
@Override
public void clear()
{
_exactMap.clear();
_prefixMap=new ArrayTernaryTrie<>(false);
_suffixMap=new ArrayTernaryTrie<>(false);
_default=null;
_defaultSingletonList=null;
_prefixDefault=null;
super.clear();
}
/* --------------------------------------------------------------- */
/**
* @param pathSpec the path spec
* @param path the path
* @return true if match.
*/
public static boolean match(String pathSpec, String path)
{
return match(pathSpec, path, false);
}
/* --------------------------------------------------------------- */
/**
* @param pathSpec the path spec
* @param path the path
* @param noDefault true to not handle the default path "/" special, false to allow matcher rules to run
* @return true if match.
*/
public static boolean match(String pathSpec, String path, boolean noDefault)
{
if (pathSpec.length()==0)
return "/".equals(path);
char c = pathSpec.charAt(0);
if (c=='/')
{
if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
return true;
if(isPathWildcardMatch(pathSpec, path))
return true;
}
else if (c=='*')
return path.regionMatches(path.length()-pathSpec.length()+1,
pathSpec,1,pathSpec.length()-1);
return false;
}
/* --------------------------------------------------------------- */
private static boolean isPathWildcardMatch(String pathSpec, String path)
{
// For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
int cpl=pathSpec.length()-2;
if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
{
if (path.length()==cpl || '/'==path.charAt(cpl))
return true;
}
return false;
}
/* --------------------------------------------------------------- */
/** Return the portion of a path that matches a path spec.
* @param pathSpec the path spec
* @param path the path
* @return null if no match at all.
*/
public static String pathMatch(String pathSpec, String path)
{
char c = pathSpec.charAt(0);
if (c=='/')
{
if (pathSpec.length()==1)
return path;
if (pathSpec.equals(path))
return path;
if (isPathWildcardMatch(pathSpec, path))
return path.substring(0,pathSpec.length()-2);
}
else if (c=='*')
{
if (path.regionMatches(path.length()-(pathSpec.length()-1),
pathSpec,1,pathSpec.length()-1))
return path;
}
return null;
}
/* --------------------------------------------------------------- */
/** Return the portion of a path that is after a path spec.
* @param pathSpec the path spec
* @param path the path
* @return The path info string
*/
public static String pathInfo(String pathSpec, String path)
{
if ("".equals(pathSpec))
return path; //servlet 3 spec sec 12.2 will be '/'
char c = pathSpec.charAt(0);
if (c=='/')
{
if (pathSpec.length()==1)
return null;
boolean wildcard = isPathWildcardMatch(pathSpec, path);
// handle the case where pathSpec uses a wildcard and path info is "/*"
if (pathSpec.equals(path) && !wildcard)
return null;
if (wildcard)
{
if (path.length()==pathSpec.length()-2)
return null;
return path.substring(pathSpec.length()-2);
}
}
return null;
}
/* ------------------------------------------------------------ */
/** Relative path.
* @param base The base the path is relative to.
* @param pathSpec The spec of the path segment to ignore.
* @param path the additional path
* @return base plus path with pathspec removed
*/
public static String relativePath(String base,
String pathSpec,
String path )
{
String info=pathInfo(pathSpec,path);
if (info==null)
info=path;
if( info.startsWith( "./"))
info = info.substring( 2);
if( base.endsWith( URIUtil.SLASH))
if( info.startsWith( URIUtil.SLASH))
path = base + info.substring(1);
else
path = base + info;
else
if( info.startsWith( URIUtil.SLASH))
path = base + info;
else
path = base + URIUtil.SLASH + info;
return path;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public static class MappedEntry<O> implements Map.Entry<String,O>
{
private final String key;
private final O value;
private String mapped;
MappedEntry(String key, O value)
{
this.key=key;
this.value=value;
}
@Override
public String getKey()
{
return key;
}
@Override
public O getValue()
{
return value;
}
@Override
public O setValue(O o)
{
throw new UnsupportedOperationException();
}
@Override
public String toString()
{
return key+"="+value;
}
public String getMapped()
{
return mapped;
}
void setMapped(String mapped)
{
this.mapped = mapped;
}
}
public static class PathSet extends AbstractSet<String> implements Predicate<String>
{
private final PathMap<Boolean> _map = new PathMap<>();
@Override
public Iterator<String> iterator()
{
return _map.keySet().iterator();
}
@Override
public int size()
{
return _map.size();
}
@Override
public boolean add(String item)
{
return _map.put(item,Boolean.TRUE)==null;
}
@Override
public boolean remove(Object item)
{
return _map.remove(item)!=null;
}
@Override
public boolean contains(Object o)
{
return _map.containsKey(o);
}
@Override
public boolean test(String s)
{
return _map.containsMatch(s);
}
public boolean containsMatch(String s)
{
return _map.containsMatch(s);
}
}
}

View File

@ -0,0 +1,94 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Pre encoded HttpField.
* <p>A HttpField that will be cached and used many times can be created as
* a {@link PreEncodedHttpField}, which will use the {@link HttpFieldPreEncoder}
* instances discovered by the {@link ServiceLoader} to pre-encode the header
* for each version of HTTP in use. This will save garbage
* and CPU each time the field is encoded into a response.
* </p>
*/
public class PreEncodedHttpField extends HttpField
{
private final static Logger LOG = Log.getLogger(PreEncodedHttpField.class);
private final static HttpFieldPreEncoder[] __encoders;
static
{
List<HttpFieldPreEncoder> encoders = new ArrayList<>();
Iterator<HttpFieldPreEncoder> iter = ServiceLoader.load(HttpFieldPreEncoder.class,PreEncodedHttpField.class.getClassLoader()).iterator();
while (iter.hasNext())
{
try
{
encoders.add(iter.next());
}
catch(Error|RuntimeException e)
{
LOG.debug(e);
}
}
// TODO avoid needing this catch all
if (encoders.size()==0)
encoders.add(new Http1FieldPreEncoder());
LOG.debug("HttpField encoders loaded: {}",encoders);
__encoders = encoders.toArray(new HttpFieldPreEncoder[encoders.size()]);
}
private final byte[][] _encodedField=new byte[2][];
public PreEncodedHttpField(HttpHeader header,String name,String value)
{
super(header,name, value);
for (HttpFieldPreEncoder e:__encoders)
{
_encodedField[e.getHttpVersion()==HttpVersion.HTTP_2?1:0]=e.getEncodedField(header,header.asString(),value);
}
}
public PreEncodedHttpField(HttpHeader header,String value)
{
this(header,header.asString(),value);
}
public PreEncodedHttpField(String name,String value)
{
this(null,name,value);
}
public void putTo(ByteBuffer bufferInFillMode, HttpVersion version)
{
bufferInFillMode.put(_encodedField[version==HttpVersion.HTTP_2?1:0]);
}
}

View File

@ -0,0 +1,229 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/* ------------------------------------------------------------ */
/**
* Implements a quoted comma separated list of values
* in accordance with RFC7230.
* OWS is removed and quoted characters ignored for parsing.
* @see "https://tools.ietf.org/html/rfc7230#section-3.2.6"
* @see "https://tools.ietf.org/html/rfc7230#section-7"
*/
public class QuotedCSV implements Iterable<String>
{
private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
private final List<String> _values = new ArrayList<>();
private final boolean _keepQuotes;
/* ------------------------------------------------------------ */
public QuotedCSV(String... values)
{
this(true,values);
}
/* ------------------------------------------------------------ */
public QuotedCSV(boolean keepQuotes,String... values)
{
_keepQuotes=keepQuotes;
for (String v:values)
addValue(v);
}
/* ------------------------------------------------------------ */
public void addValue(String value)
{
StringBuffer buffer = new StringBuffer();
int l=value.length();
State state=State.VALUE;
boolean quoted=false;
boolean sloshed=false;
int nws_length=0;
int last_length=0;
for (int i=0;i<=l;i++)
{
char c=i==l?0:value.charAt(i);
// Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
if (quoted && c!=0)
{
if (sloshed)
sloshed=false;
else
{
switch(c)
{
case '\\':
sloshed=true;
if (!_keepQuotes)
continue;
break;
case '"':
quoted=false;
if (!_keepQuotes)
continue;
break;
}
}
buffer.append(c);
nws_length=buffer.length();
continue;
}
// Handle common cases
switch(c)
{
case ' ':
case '\t':
if (buffer.length()>last_length) // not leading OWS
buffer.append(c);
continue;
case '"':
quoted=true;
if (_keepQuotes)
buffer.append(c);
nws_length=buffer.length();
continue;
case ';':
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_NAME;
continue;
case ',':
case 0:
if (nws_length>0)
{
buffer.setLength(nws_length); // trim following OWS
_values.add(buffer.toString());
}
buffer.setLength(0);
last_length=0;
nws_length=0;
state=State.VALUE;
continue;
default:
{
switch (state)
{
case VALUE:
{
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_NAME:
{
if (c=='=')
{
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_VALUE;
continue;
}
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_VALUE:
{
buffer.append(c);
nws_length=buffer.length();
continue;
}
}
}
}
}
}
public List<String> getValues()
{
return _values;
}
@Override
public Iterator<String> iterator()
{
return _values.iterator();
}
public static String unquote(String s)
{
// handle trivial cases
int l=s.length();
if (s==null || l==0)
return s;
// Look for any quotes
int i=0;
for (;i<l;i++)
{
char c=s.charAt(i);
if (c=='"')
break;
}
if (i==l)
return s;
boolean quoted=true;
boolean sloshed=false;
StringBuffer buffer = new StringBuffer();
buffer.append(s,0,i);
i++;
for (;i<l;i++)
{
char c=s.charAt(i);
if (quoted)
{
if (sloshed)
{
buffer.append(c);
sloshed=false;
}
else if (c=='"')
quoted=false;
else if (c=='\\')
sloshed=true;
else
buffer.append(c);
}
else if (c=='"')
quoted=true;
else
buffer.append(c);
}
return buffer.toString();
}
}

View File

@ -0,0 +1,252 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/* ------------------------------------------------------------ */
/**
* Implements a quoted comma separated list of quality values
* in accordance with RFC7230 and RFC7231.
* Values are returned sorted in quality order, with OWS and the
* quality parameters removed.
* @see "https://tools.ietf.org/html/rfc7230#section-3.2.6"
* @see "https://tools.ietf.org/html/rfc7230#section-7"
* @see "https://tools.ietf.org/html/rfc7231#section-5.3.1"
*/
public class QuotedQualityCSV implements Iterable<String>
{
private final static Double ZERO=new Double(0.0);
private final static Double ONE=new Double(1.0);
private enum State { VALUE, PARAM_NAME, PARAM_VALUE, Q_VALUE};
private final List<String> _values = new ArrayList<>();
private final List<Double> _quality = new ArrayList<>();
private boolean _sorted = false;
/* ------------------------------------------------------------ */
public QuotedQualityCSV(String... values)
{
for (String v:values)
addValue(v);
}
/* ------------------------------------------------------------ */
public void addValue(String value)
{
StringBuffer buffer = new StringBuffer();
int l=value.length();
State state=State.VALUE;
boolean quoted=false;
boolean sloshed=false;
int nws_length=0;
int last_length=0;
Double q=ONE;
for (int i=0;i<=l;i++)
{
char c=i==l?0:value.charAt(i);
// Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
if (quoted && c!=0)
{
if (sloshed)
sloshed=false;
else
{
switch(c)
{
case '\\':
sloshed=true;
break;
case '"':
quoted=false;
if (state==State.Q_VALUE)
continue;
break;
}
}
buffer.append(c);
nws_length=buffer.length();
continue;
}
// Handle common cases
switch(c)
{
case ' ':
case '\t':
if (buffer.length()>last_length) // not leading OWS
buffer.append(c);
continue;
case '"':
quoted=true;
if (state==State.Q_VALUE)
continue;
buffer.append(c);
nws_length=buffer.length();
continue;
case ';':
if (state==State.Q_VALUE)
{
try
{
q=new Double(buffer.substring(last_length));
}
catch(Exception e)
{
q=ZERO;
}
nws_length=last_length;
}
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_NAME;
continue;
case ',':
case 0:
if (state==State.Q_VALUE)
{
try
{
q=new Double(buffer.substring(last_length));
}
catch(Exception e)
{
q=ZERO;
}
nws_length=last_length;
}
buffer.setLength(nws_length); // trim following OWS
if (q>0.0 && nws_length>0)
{
_values.add(buffer.toString());
_quality.add(q);
_sorted=false;
}
buffer.setLength(0);
last_length=0;
nws_length=0;
q=ONE;
state=State.VALUE;
continue;
default:
{
switch (state)
{
case VALUE:
{
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_NAME:
{
if (c=='=')
{
buffer.setLength(nws_length); // trim following OWS
if (nws_length-last_length==1 && Character.toLowerCase(buffer.charAt(last_length))=='q')
{
buffer.setLength(last_length-1);
nws_length=buffer.length();
last_length=nws_length;
state=State.Q_VALUE;
continue;
}
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_VALUE;
continue;
}
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_VALUE:
case Q_VALUE:
{
buffer.append(c);
nws_length=buffer.length();
continue;
}
}
}
}
}
}
public List<String> getValues()
{
if (!_sorted)
sort();
return _values;
}
@Override
public Iterator<String> iterator()
{
if (!_sorted)
sort();
return _values.iterator();
}
protected void sort()
{
_sorted=true;
Double last = ZERO;
int len = Integer.MIN_VALUE;
for (int i = _values.size(); i-- > 0;)
{
String v = _values.get(i);
Double q = _quality.get(i);
int compare=last.compareTo(q);
if (compare > 0 || (compare==0 && v.length()<len))
{
_values.set(i, _values.get(i + 1));
_values.set(i + 1, v);
_quality.set(i, _quality.get(i + 1));
_quality.set(i + 1, q);
last = ZERO;
len=0;
i = _values.size();
continue;
}
last=q;
len=v.length();
}
}
}

View File

@ -0,0 +1,227 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
/** HttpContent created from a {@link Resource}.
* <p>The HttpContent is used to server static content that is not
* cached. So fields and values are only generated as need be an not
* kept for reuse</p>
*/
public class ResourceHttpContent implements HttpContent
{
final Resource _resource;
final String _contentType;
final int _maxBuffer;
HttpContent _gzip;
String _etag;
/* ------------------------------------------------------------ */
public ResourceHttpContent(final Resource resource, final String contentType)
{
this(resource,contentType,-1,null);
}
/* ------------------------------------------------------------ */
public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer)
{
this(resource,contentType,maxBuffer,null);
}
/* ------------------------------------------------------------ */
public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer, HttpContent gzip)
{
_resource=resource;
_contentType=contentType;
_maxBuffer=maxBuffer;
_gzip=gzip;
}
/* ------------------------------------------------------------ */
@Override
public String getContentTypeValue()
{
return _contentType;
}
/* ------------------------------------------------------------ */
@Override
public HttpField getContentType()
{
return _contentType==null?null:new HttpField(HttpHeader.CONTENT_TYPE,_contentType);
}
/* ------------------------------------------------------------ */
@Override
public HttpField getContentEncoding()
{
return null;
}
/* ------------------------------------------------------------ */
@Override
public String getContentEncodingValue()
{
return null;
}
/* ------------------------------------------------------------ */
@Override
public String getCharacterEncoding()
{
return _contentType==null?null:MimeTypes.getCharsetFromContentType(_contentType);
}
/* ------------------------------------------------------------ */
@Override
public Type getMimeType()
{
return _contentType==null?null:MimeTypes.CACHE.get(MimeTypes.getContentTypeWithoutCharset(_contentType));
}
/* ------------------------------------------------------------ */
@Override
public HttpField getLastModified()
{
long lm = _resource.lastModified();
return lm>=0?new HttpField(HttpHeader.LAST_MODIFIED,DateGenerator.formatDate(lm)):null;
}
/* ------------------------------------------------------------ */
@Override
public String getLastModifiedValue()
{
long lm = _resource.lastModified();
return lm>=0?DateGenerator.formatDate(lm):null;
}
/* ------------------------------------------------------------ */
@Override
public ByteBuffer getDirectBuffer()
{
if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
return null;
try
{
return BufferUtil.toBuffer(_resource,true);
}
catch(IOException e)
{
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
@Override
public HttpField getETag()
{
return new HttpField(HttpHeader.ETAG,getETagValue());
}
/* ------------------------------------------------------------ */
@Override
public String getETagValue()
{
return _resource.getWeakETag();
}
/* ------------------------------------------------------------ */
@Override
public ByteBuffer getIndirectBuffer()
{
if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
return null;
try
{
return BufferUtil.toBuffer(_resource,false);
}
catch(IOException e)
{
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */
@Override
public HttpField getContentLength()
{
long l=_resource.length();
return l==-1?null:new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,_resource.length());
}
/* ------------------------------------------------------------ */
@Override
public long getContentLengthValue()
{
return _resource.length();
}
/* ------------------------------------------------------------ */
@Override
public InputStream getInputStream() throws IOException
{
return _resource.getInputStream();
}
/* ------------------------------------------------------------ */
@Override
public ReadableByteChannel getReadableByteChannel() throws IOException
{
return _resource.getReadableByteChannel();
}
/* ------------------------------------------------------------ */
@Override
public Resource getResource()
{
return _resource;
}
/* ------------------------------------------------------------ */
@Override
public void release()
{
_resource.close();
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s@%x{r=%s,gz=%b}",this.getClass().getSimpleName(),hashCode(),_resource,_gzip!=null);
}
/* ------------------------------------------------------------ */
@Override
public HttpContent getGzipContent()
{
return _gzip==null?null:new GzipHttpContent(this,_gzip);
}
}

View File

@ -0,0 +1,5 @@
text/html=utf-8
text/plain=iso-8859-1
text/xml=utf-8
text/json=utf-8
application/xhtml+xml=utf-8

View File

@ -0,0 +1,189 @@
ai=application/postscript
aif=audio/x-aiff
aifc=audio/x-aiff
aiff=audio/x-aiff
apk=application/vnd.android.package-archive
asc=text/plain
asf=video/x.ms.asf
asx=video/x.ms.asx
au=audio/basic
avi=video/x-msvideo
bcpio=application/x-bcpio
bin=application/octet-stream
bmp=image/bmp
cab=application/x-cabinet
cdf=application/x-netcdf
chm=application/vnd.ms-htmlhelp
class=application/java-vm
cpio=application/x-cpio
cpt=application/mac-compactpro
crt=application/x-x509-ca-cert
csh=application/x-csh
css=text/css
csv=text/csv
dcr=application/x-director
dir=application/x-director
dll=application/x-msdownload
dms=application/octet-stream
doc=application/msword
dtd=application/xml-dtd
dvi=application/x-dvi
dxr=application/x-director
eps=application/postscript
etx=text/x-setext
exe=application/octet-stream
ez=application/andrew-inset
gif=image/gif
gtar=application/x-gtar
gz=application/gzip
gzip=application/gzip
hdf=application/x-hdf
hqx=application/mac-binhex40
htc=text/x-component
htm=text/html
html=text/html
ice=x-conference/x-cooltalk
ico=image/x-icon
ief=image/ief
iges=model/iges
igs=model/iges
jad=text/vnd.sun.j2me.app-descriptor
jar=application/java-archive
java=text/plain
jnlp=application/x-java-jnlp-file
jpe=image/jpeg
jp2=image/jpeg2000
jpeg=image/jpeg
jpg=image/jpeg
js=application/javascript
json=application/json
jsp=text/html
kar=audio/midi
latex=application/x-latex
lha=application/octet-stream
lzh=application/octet-stream
man=application/x-troff-man
mathml=application/mathml+xml
me=application/x-troff-me
mesh=model/mesh
mid=audio/midi
midi=audio/midi
mif=application/vnd.mif
mol=chemical/x-mdl-molfile
mov=video/quicktime
movie=video/x-sgi-movie
mp2=audio/mpeg
mp3=audio/mpeg
mpe=video/mpeg
mpeg=video/mpeg
mpg=video/mpeg
mpga=audio/mpeg
ms=application/x-troff-ms
msh=model/mesh
msi=application/octet-stream
nc=application/x-netcdf
oda=application/oda
odb=application/vnd.oasis.opendocument.database
odc=application/vnd.oasis.opendocument.chart
odf=application/vnd.oasis.opendocument.formula
odg=application/vnd.oasis.opendocument.graphics
odi=application/vnd.oasis.opendocument.image
odm=application/vnd.oasis.opendocument.text-master
odp=application/vnd.oasis.opendocument.presentation
ods=application/vnd.oasis.opendocument.spreadsheet
odt=application/vnd.oasis.opendocument.text
ogg=application/ogg
otc=application/vnd.oasis.opendocument.chart-template
otf=application/vnd.oasis.opendocument.formula-template
otg=application/vnd.oasis.opendocument.graphics-template
oth=application/vnd.oasis.opendocument.text-web
oti=application/vnd.oasis.opendocument.image-template
otp=application/vnd.oasis.opendocument.presentation-template
ots=application/vnd.oasis.opendocument.spreadsheet-template
ott=application/vnd.oasis.opendocument.text-template
pbm=image/x-portable-bitmap
pdb=chemical/x-pdb
pdf=application/pdf
pgm=image/x-portable-graymap
pgn=application/x-chess-pgn
png=image/png
pnm=image/x-portable-anymap
ppm=image/x-portable-pixmap
pps=application/vnd.ms-powerpoint
ppt=application/vnd.ms-powerpoint
ps=application/postscript
qml=text/x-qml
qt=video/quicktime
ra=audio/x-pn-realaudio
rar=application/x-rar-compressed
ram=audio/x-pn-realaudio
ras=image/x-cmu-raster
rdf=application/rdf+xml
rgb=image/x-rgb
rm=audio/x-pn-realaudio
roff=application/x-troff
rpm=application/x-rpm
rtf=application/rtf
rtx=text/richtext
rv=video/vnd.rn-realvideo
ser=application/java-serialized-object
sgm=text/sgml
sgml=text/sgml
sh=application/x-sh
shar=application/x-shar
silo=model/mesh
sit=application/x-stuffit
skd=application/x-koan
skm=application/x-koan
skp=application/x-koan
skt=application/x-koan
smi=application/smil
smil=application/smil
snd=audio/basic
spl=application/x-futuresplash
src=application/x-wais-source
sv4cpio=application/x-sv4cpio
sv4crc=application/x-sv4crc
svg=image/svg+xml
svgz=image/svg+xml
swf=application/x-shockwave-flash
t=application/x-troff
tar=application/x-tar
tar.gz=application/x-gtar
tcl=application/x-tcl
tex=application/x-tex
texi=application/x-texinfo
texinfo=application/x-texinfo
tgz=application/x-gtar
tif=image/tiff
tiff=image/tiff
tr=application/x-troff
tsv=text/tab-separated-values
txt=text/plain
ustar=application/x-ustar
vcd=application/x-cdlink
vrml=model/vrml
vxml=application/voicexml+xml
wav=audio/x-wav
wbmp=image/vnd.wap.wbmp
wml=text/vnd.wap.wml
wmlc=application/vnd.wap.wmlc
wmls=text/vnd.wap.wmlscript
wmlsc=application/vnd.wap.wmlscriptc
wrl=model/vrml
wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
xbm=image/x-xbitmap
xcf=image/xcf
xht=application/xhtml+xml
xhtml=application/xhtml+xml
xls=application/vnd.ms-excel
xml=application/xml
xpm=image/x-xpixmap
xsd=application/xml
xsl=application/xml
xslt=application/xslt+xml
xul=application/vnd.mozilla.xul+xml
xwd=image/x-xwindowdump
xyz=chemical/x-xyz
z=application/compress
zip=application/zip

View File

@ -0,0 +1,23 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
/**
* Jetty Http : Tools for Http processing
*/
package org.eclipse.jetty.http;

View File

@ -0,0 +1,101 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ManagedObject("Mapped Resource")
public class MappedResource<E> implements Comparable<MappedResource<E>>
{
private final PathSpec pathSpec;
private final E resource;
public MappedResource(PathSpec pathSpec, E resource)
{
this.pathSpec = pathSpec;
this.resource = resource;
}
/**
* Comparison is based solely on the pathSpec
*/
@Override
public int compareTo(MappedResource<E> other)
{
return this.pathSpec.compareTo(other.pathSpec);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
MappedResource<?> other = (MappedResource<?>)obj;
if (pathSpec == null)
{
if (other.pathSpec != null)
{
return false;
}
}
else if (!pathSpec.equals(other.pathSpec))
{
return false;
}
return true;
}
@ManagedAttribute(value = "path spec", readonly = true)
public PathSpec getPathSpec()
{
return pathSpec;
}
@ManagedAttribute(value = "resource", readonly = true)
public E getResource()
{
return resource;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((pathSpec == null) ? 0 : pathSpec.hashCode());
return result;
}
@Override
public String toString()
{
return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource);
}
}

View File

@ -0,0 +1,159 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Path Mappings of PathSpec to Resource.
* <p>
* Sorted into search order upon entry into the Set
*
* @param <E> the type of mapping endpoint
*/
@ManagedObject("Path Mappings")
public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
{
private static final Logger LOG = Log.getLogger(PathMappings.class);
private List<MappedResource<E>> mappings = new ArrayList<MappedResource<E>>();
private MappedResource<E> defaultResource = null;
private MappedResource<E> rootResource = null;
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
ContainerLifeCycle.dump(out,indent,mappings);
}
@ManagedAttribute(value = "mappings", readonly = true)
public List<MappedResource<E>> getMappings()
{
return mappings;
}
public void reset()
{
mappings.clear();
}
/**
* Return a list of MappedResource matches for the specified path.
*
* @param path the path to return matches on
* @return the list of mapped resource the path matches on
*/
public List<MappedResource<E>> getMatches(String path)
{
boolean matchRoot = "/".equals(path);
List<MappedResource<E>> ret = new ArrayList<>();
int len = mappings.size();
for (int i = 0; i < len; i++)
{
MappedResource<E> mr = mappings.get(i);
switch (mr.getPathSpec().group)
{
case ROOT:
if (matchRoot)
ret.add(mr);
break;
case DEFAULT:
if (matchRoot || mr.getPathSpec().matches(path))
ret.add(mr);
break;
default:
if (mr.getPathSpec().matches(path))
ret.add(mr);
break;
}
}
return ret;
}
public MappedResource<E> getMatch(String path)
{
if (path.equals("/") && rootResource != null)
{
return rootResource;
}
int len = mappings.size();
for (int i = 0; i < len; i++)
{
MappedResource<E> mr = mappings.get(i);
if (mr.getPathSpec().matches(path))
{
return mr;
}
}
return defaultResource;
}
@Override
public Iterator<MappedResource<E>> iterator()
{
return mappings.iterator();
}
@SuppressWarnings("incomplete-switch")
public void put(PathSpec pathSpec, E resource)
{
MappedResource<E> entry = new MappedResource<>(pathSpec,resource);
switch (pathSpec.group)
{
case DEFAULT:
defaultResource = entry;
break;
case ROOT:
rootResource = entry;
break;
}
// TODO: add warning when replacing an existing pathspec?
mappings.add(entry);
if (LOG.isDebugEnabled())
LOG.debug("Added {} to {}",entry,this);
Collections.sort(mappings);
}
@Override
public String toString()
{
return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size());
}
}

View File

@ -0,0 +1,167 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
/**
* The base PathSpec, what all other path specs are based on
*/
public abstract class PathSpec implements Comparable<PathSpec>
{
protected String pathSpec;
protected PathSpecGroup group;
protected int pathDepth;
protected int specLength;
@Override
public int compareTo(PathSpec other)
{
// Grouping (increasing)
int diff = this.group.ordinal() - other.group.ordinal();
if (diff != 0)
{
return diff;
}
// Spec Length (decreasing)
diff = other.specLength - this.specLength;
if (diff != 0)
{
return diff;
}
// Path Spec Name (alphabetical)
return this.pathSpec.compareTo(other.pathSpec);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
PathSpec other = (PathSpec)obj;
if (pathSpec == null)
{
if (other.pathSpec != null)
{
return false;
}
}
else if (!pathSpec.equals(other.pathSpec))
{
return false;
}
return true;
}
public PathSpecGroup getGroup()
{
return group;
}
/**
* Get the number of path elements that this path spec declares.
* <p>
* This is used to determine longest match logic.
*
* @return the depth of the path segments that this spec declares
*/
public int getPathDepth()
{
return pathDepth;
}
/**
* Return the portion of the path that is after the path spec.
*
* @param path
* the path to match against
* @return the path info portion of the string
*/
public abstract String getPathInfo(String path);
/**
* Return the portion of the path that matches a path spec.
*
* @param path
* the path to match against
* @return the match, or null if no match at all
*/
public abstract String getPathMatch(String path);
/**
* The as-provided path spec.
*
* @return the as-provided path spec
*/
public String getDeclaration()
{
return pathSpec;
}
/**
* Get the relative path.
*
* @param base
* the base the path is relative to
* @param path
* the additional path
* @return the base plus path with pathSpec portion removed
*/
public abstract String getRelativePath(String base, String path);
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
return result;
}
/**
* Test to see if the provided path matches this path spec
*
* @param path
* the path to test
* @return true if the path matches this path spec, false otherwise
*/
public abstract boolean matches(String path);
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append(this.getClass().getSimpleName()).append("[\"");
str.append(pathSpec);
str.append("\",pathDepth=").append(pathDepth);
str.append(",group=").append(group);
str.append("]");
return str.toString();
}
}

View File

@ -0,0 +1,101 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
/**
* Types of path spec groups.
* <p>
* This is used to facilitate proper pathspec search order.
* <p>
* Search Order:
* <ol>
* <li>{@link PathSpecGroup#ordinal()} [increasing]</li>
* <li>{@link PathSpec#specLength} [decreasing]</li>
* <li>{@link PathSpec#pathSpec} [natural sort order]</li>
* </ol>
*/
public enum PathSpecGroup
{
// NOTE: Order of enums determines order of Groups.
/**
* For exactly defined path specs, no glob.
*/
EXACT,
/**
* For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle.
*
* <pre>
* "^/downloads/[^/]*.zip$" - regex spec
* "/a/{var}/c" - uri-template spec
* </pre>
*
* Note: there is no known servlet spec variant of this kind of path spec
*/
MIDDLE_GLOB,
/**
* For path specs that have a hardcoded prefix and a trailing wildcard glob.
* <p>
*
* <pre>
* "/downloads/*" - servlet spec
* "/api/*" - servlet spec
* "^/rest/.*$" - regex spec
* "/bookings/{guest-id}" - uri-template spec
* "/rewards/{vip-level}" - uri-template spec
* </pre>
*/
PREFIX_GLOB,
/**
* For path specs that have a wildcard glob with a hardcoded suffix
*
* <pre>
* "*.do" - servlet spec
* "*.css" - servlet spec
* "^.*\.zip$" - regex spec
* </pre>
*
* Note: there is no known uri-template spec variant of this kind of path spec
*/
SUFFIX_GLOB,
/**
* The root spec for accessing the Root behavior.
*
* <pre>
* "" - servlet spec (Root Servlet)
* null - servlet spec (Root Servlet)
* </pre>
*
* Note: there is no known uri-template spec variant of this kind of path spec
*/
ROOT,
/**
* The default spec for accessing the Default path behavior.
*
* <pre>
* "/" - servlet spec (Default Servlet)
* "/" - uri-template spec (Root Context)
* "^/$" - regex spec (Root Context)
* </pre>
*
* Per Servlet Spec, pathInfo is always null for these specs.
* If nothing above matches, then default will match.
*/
DEFAULT,
}

View File

@ -0,0 +1,222 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
/**
* A Set of PathSpec strings.
* <p>
* Used by {@link org.eclipse.jetty.util.IncludeExclude} logic
*/
public class PathSpecSet implements Set<String>, Predicate<String>
{
private final Set<PathSpec> specs = new TreeSet<>();
@Override
public boolean test(String s)
{
for (PathSpec spec : specs)
{
if (spec.matches(s))
{
return true;
}
}
return false;
}
@Override
public boolean isEmpty()
{
return specs.isEmpty();
}
@Override
public Iterator<String> iterator()
{
return new Iterator<String>()
{
private Iterator<PathSpec> iter = specs.iterator();
@Override
public boolean hasNext()
{
return iter.hasNext();
}
@Override
public String next()
{
PathSpec spec = iter.next();
if (spec == null)
{
return null;
}
return spec.getDeclaration();
}
@Override
public void remove()
{
throw new UnsupportedOperationException("Remove not supported by this Iterator");
}
};
}
@Override
public int size()
{
return specs.size();
}
@Override
public boolean contains(Object o)
{
if (o instanceof PathSpec)
{
return specs.contains(o);
}
if (o instanceof String)
{
return specs.contains(toPathSpec((String)o));
}
return false;
}
private PathSpec asPathSpec(Object o)
{
if (o == null)
{
return null;
}
if (o instanceof PathSpec)
{
return (PathSpec)o;
}
if (o instanceof String)
{
return toPathSpec((String)o);
}
return toPathSpec(o.toString());
}
private PathSpec toPathSpec(String rawSpec)
{
if ((rawSpec == null) || (rawSpec.length() < 1))
{
throw new RuntimeException("Path Spec String must start with '^', '/', or '*.': got [" + rawSpec + "]");
}
if (rawSpec.charAt(0) == '^')
{
return new RegexPathSpec(rawSpec);
}
else
{
return new ServletPathSpec(rawSpec);
}
}
@Override
public Object[] toArray()
{
return toArray(new String[specs.size()]);
}
@Override
public <T> T[] toArray(T[] a)
{
int i = 0;
for (PathSpec spec : specs)
{
a[i++] = (T)spec.getDeclaration();
}
return a;
}
@Override
public boolean add(String e)
{
return specs.add(toPathSpec(e));
}
@Override
public boolean remove(Object o)
{
return specs.remove(asPathSpec(o));
}
@Override
public boolean containsAll(Collection<?> coll)
{
for (Object o : coll)
{
if (!specs.contains(asPathSpec(o)))
return false;
}
return true;
}
@Override
public boolean addAll(Collection<? extends String> coll)
{
boolean ret = false;
for (String s : coll)
{
ret |= add(s);
}
return ret;
}
@Override
public boolean retainAll(Collection<?> coll)
{
List<PathSpec> collSpecs = new ArrayList<>();
for (Object o : coll)
{
collSpecs.add(asPathSpec(o));
}
return specs.retainAll(collSpecs);
}
@Override
public boolean removeAll(Collection<?> coll)
{
List<PathSpec> collSpecs = new ArrayList<>();
for (Object o : coll)
{
collSpecs.add(asPathSpec(o));
}
return specs.removeAll(collSpecs);
}
@Override
public void clear()
{
specs.clear();
}
}

View File

@ -0,0 +1,176 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexPathSpec extends PathSpec
{
protected Pattern pattern;
protected RegexPathSpec()
{
super();
}
public RegexPathSpec(String regex)
{
super.pathSpec = regex;
boolean inGrouping = false;
this.pathDepth = 0;
this.specLength = pathSpec.length();
// build up a simple signature we can use to identify the grouping
StringBuilder signature = new StringBuilder();
for (char c : pathSpec.toCharArray())
{
switch (c)
{
case '[':
inGrouping = true;
break;
case ']':
inGrouping = false;
signature.append('g'); // glob
break;
case '*':
signature.append('g'); // glob
break;
case '/':
if (!inGrouping)
{
this.pathDepth++;
}
break;
default:
if (!inGrouping)
{
if (Character.isLetterOrDigit(c))
{
signature.append('l'); // literal (exact)
}
}
break;
}
}
this.pattern = Pattern.compile(pathSpec);
// Figure out the grouping based on the signature
String sig = signature.toString();
if (Pattern.matches("^l*$",sig))
{
this.group = PathSpecGroup.EXACT;
}
else if (Pattern.matches("^l*g+",sig))
{
this.group = PathSpecGroup.PREFIX_GLOB;
}
else if (Pattern.matches("^g+l+$",sig))
{
this.group = PathSpecGroup.SUFFIX_GLOB;
}
else
{
this.group = PathSpecGroup.MIDDLE_GLOB;
}
}
public Matcher getMatcher(String path)
{
return this.pattern.matcher(path);
}
@Override
public String getPathInfo(String path)
{
// Path Info only valid for PREFIX_GLOB types
if (group == PathSpecGroup.PREFIX_GLOB)
{
Matcher matcher = getMatcher(path);
if (matcher.matches())
{
if (matcher.groupCount() >= 1)
{
String pathInfo = matcher.group(1);
if ("".equals(pathInfo))
{
return "/";
}
else
{
return pathInfo;
}
}
}
}
return null;
}
@Override
public String getPathMatch(String path)
{
Matcher matcher = getMatcher(path);
if (matcher.matches())
{
if (matcher.groupCount() >= 1)
{
int idx = matcher.start(1);
if (idx > 0)
{
if (path.charAt(idx - 1) == '/')
{
idx--;
}
return path.substring(0,idx);
}
}
return path;
}
return null;
}
public Pattern getPattern()
{
return this.pattern;
}
@Override
public String getRelativePath(String base, String path)
{
// TODO Auto-generated method stub
return null;
}
@Override
public boolean matches(final String path)
{
int idx = path.indexOf('?');
if (idx >= 0)
{
// match only non-query part
return getMatcher(path.substring(0,idx)).matches();
}
else
{
// match entire path
return getMatcher(path).matches();
}
}
}

View File

@ -0,0 +1,261 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import org.eclipse.jetty.util.URIUtil;
public class ServletPathSpec extends PathSpec
{
public ServletPathSpec(String servletPathSpec)
{
super();
assertValidServletPathSpec(servletPathSpec);
// The Root Path Spec
if ((servletPathSpec == null) || (servletPathSpec.length() == 0))
{
super.pathSpec = "";
super.pathDepth = -1; // force this to be at the end of the sort order
this.specLength = 1;
this.group = PathSpecGroup.ROOT;
return;
}
// The Default Path Spec
if("/".equals(servletPathSpec))
{
super.pathSpec = "/";
super.pathDepth = -1; // force this to be at the end of the sort order
this.specLength = 1;
this.group = PathSpecGroup.DEFAULT;
return;
}
this.specLength = servletPathSpec.length();
super.pathDepth = 0;
char lastChar = servletPathSpec.charAt(specLength - 1);
// prefix based
if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*'))
{
this.group = PathSpecGroup.PREFIX_GLOB;
}
// suffix based
else if (servletPathSpec.charAt(0) == '*')
{
this.group = PathSpecGroup.SUFFIX_GLOB;
}
else
{
this.group = PathSpecGroup.EXACT;
}
for (int i = 0; i < specLength; i++)
{
int cp = servletPathSpec.codePointAt(i);
if (cp < 128)
{
char c = (char)cp;
switch (c)
{
case '/':
super.pathDepth++;
break;
}
}
}
super.pathSpec = servletPathSpec;
}
private void assertValidServletPathSpec(String servletPathSpec)
{
if ((servletPathSpec == null) || servletPathSpec.equals(""))
{
return; // empty path spec
}
int len = servletPathSpec.length();
// path spec must either start with '/' or '*.'
if (servletPathSpec.charAt(0) == '/')
{
// Prefix Based
if (len == 1)
{
return; // simple '/' path spec
}
int idx = servletPathSpec.indexOf('*');
if (idx < 0)
{
return; // no hit on glob '*'
}
// only allowed to have '*' at the end of the path spec
if (idx != (len - 1))
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches: bad spec \""+ servletPathSpec +"\"");
}
}
else if (servletPathSpec.startsWith("*."))
{
// Suffix Based
int idx = servletPathSpec.indexOf('/');
// cannot have path separator
if (idx >= 0)
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators: bad spec \""+ servletPathSpec +"\"");
}
idx = servletPathSpec.indexOf('*',2);
// only allowed to have 1 glob '*', at the start of the path spec
if (idx >= 1)
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*': bad spec \""+ servletPathSpec +"\"");
}
}
else
{
throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\": bad spec \""+ servletPathSpec +"\"");
}
}
@Override
public String getPathInfo(String path)
{
// Path Info only valid for PREFIX_GLOB types
if (group == PathSpecGroup.PREFIX_GLOB)
{
if (path.length() == (specLength - 2))
{
return null;
}
return path.substring(specLength - 2);
}
return null;
}
@Override
public String getPathMatch(String path)
{
switch (group)
{
case EXACT:
if (pathSpec.equals(path))
{
return path;
}
else
{
return null;
}
case PREFIX_GLOB:
if (isWildcardMatch(path))
{
return path.substring(0,specLength - 2);
}
else
{
return null;
}
case SUFFIX_GLOB:
if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1))
{
return path;
}
else
{
return null;
}
case DEFAULT:
return path;
default:
return null;
}
}
@Override
public String getRelativePath(String base, String path)
{
String info = getPathInfo(path);
if (info == null)
{
info = path;
}
if (info.startsWith("./"))
{
info = info.substring(2);
}
if (base.endsWith(URIUtil.SLASH))
{
if (info.startsWith(URIUtil.SLASH))
{
path = base + info.substring(1);
}
else
{
path = base + info;
}
}
else if (info.startsWith(URIUtil.SLASH))
{
path = base + info;
}
else
{
path = base + URIUtil.SLASH + info;
}
return path;
}
private boolean isWildcardMatch(String path)
{
// For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
int cpl = specLength - 2;
if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl)))
{
if ((path.length() == cpl) || ('/' == path.charAt(cpl)))
{
return true;
}
}
return false;
}
@Override
public boolean matches(String path)
{
switch (group)
{
case EXACT:
return pathSpec.equals(path);
case PREFIX_GLOB:
return isWildcardMatch(path);
case SUFFIX_GLOB:
return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1);
case ROOT:
// Only "/" matches
return ("/".equals(path));
case DEFAULT:
// If we reached this point, then everything matches
return true;
default:
return false;
}
}
}

View File

@ -0,0 +1,341 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* PathSpec for URI Template based declarations
*
* @see <a href="https://tools.ietf.org/html/rfc6570">URI Templates (Level 1)</a>
*/
public class UriTemplatePathSpec extends RegexPathSpec
{
private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class);
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
/** Reserved Symbols in URI Template variable */
private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims
"!$&'()*+,;="; // sub-delims
/** Allowed Symbols in a URI Template variable */
private static final String VARIABLE_SYMBOLS="-._";
private static final Set<String> FORBIDDEN_SEGMENTS;
static
{
FORBIDDEN_SEGMENTS = new HashSet<>();
FORBIDDEN_SEGMENTS.add("/./");
FORBIDDEN_SEGMENTS.add("/../");
FORBIDDEN_SEGMENTS.add("//");
}
private String variables[];
public UriTemplatePathSpec(String rawSpec)
{
super();
Objects.requireNonNull(rawSpec,"Path Param Spec cannot be null");
if ("".equals(rawSpec) || "/".equals(rawSpec))
{
super.pathSpec = "/";
super.pattern = Pattern.compile("^/$");
super.pathDepth = 1;
this.specLength = 1;
this.variables = new String[0];
this.group = PathSpecGroup.EXACT;
return;
}
if (rawSpec.charAt(0) != '/')
{
// path specs must start with '/'
StringBuilder err = new StringBuilder();
err.append("Syntax Error: path spec \"");
err.append(rawSpec);
err.append("\" must start with '/'");
throw new IllegalArgumentException(err.toString());
}
for (String forbidden : FORBIDDEN_SEGMENTS)
{
if (rawSpec.contains(forbidden))
{
StringBuilder err = new StringBuilder();
err.append("Syntax Error: segment ");
err.append(forbidden);
err.append(" is forbidden in path spec: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
}
}
this.pathSpec = rawSpec;
StringBuilder regex = new StringBuilder();
regex.append('^');
List<String> varNames = new ArrayList<>();
// split up into path segments (ignoring the first slash that will always be empty)
String segments[] = rawSpec.substring(1).split("/");
char segmentSignature[] = new char[segments.length];
this.pathDepth = segments.length;
for (int i = 0; i < segments.length; i++)
{
String segment = segments[i];
Matcher mat = VARIABLE_PATTERN.matcher(segment);
if (mat.matches())
{
// entire path segment is a variable.
String variable = mat.group(1);
if (varNames.contains(variable))
{
// duplicate variable names
StringBuilder err = new StringBuilder();
err.append("Syntax Error: variable ");
err.append(variable);
err.append(" is duplicated in path spec: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
}
assertIsValidVariableLiteral(variable);
segmentSignature[i] = 'v'; // variable
// valid variable name
varNames.add(variable);
// build regex
regex.append("/([^/]+)");
}
else if (mat.find(0))
{
// variable exists as partial segment
StringBuilder err = new StringBuilder();
err.append("Syntax Error: variable ");
err.append(mat.group());
err.append(" must exist as entire path segment: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
}
else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
{
// variable is split with a path separator
StringBuilder err = new StringBuilder();
err.append("Syntax Error: invalid path segment /");
err.append(segment);
err.append("/ variable declaration incomplete: ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
}
else if (segment.indexOf('*') >= 0)
{
// glob segment
StringBuilder err = new StringBuilder();
err.append("Syntax Error: path segment /");
err.append(segment);
err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): ");
err.append(rawSpec);
throw new IllegalArgumentException(err.toString());
}
else
{
// valid path segment
segmentSignature[i] = 'e'; // exact
// build regex
regex.append('/');
// escape regex special characters
for (char c : segment.toCharArray())
{
if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
{
regex.append('\\');
}
regex.append(c);
}
}
}
// Handle trailing slash (which is not picked up during split)
if(rawSpec.charAt(rawSpec.length()-1) == '/')
{
regex.append('/');
}
regex.append('$');
this.pattern = Pattern.compile(regex.toString());
int varcount = varNames.size();
this.variables = varNames.toArray(new String[varcount]);
// Convert signature to group
String sig = String.valueOf(segmentSignature);
if (Pattern.matches("^e*$",sig))
{
this.group = PathSpecGroup.EXACT;
}
else if (Pattern.matches("^e*v+",sig))
{
this.group = PathSpecGroup.PREFIX_GLOB;
}
else if (Pattern.matches("^v+e+",sig))
{
this.group = PathSpecGroup.SUFFIX_GLOB;
}
else
{
this.group = PathSpecGroup.MIDDLE_GLOB;
}
}
/**
* Validate variable literal name, per RFC6570, Section 2.1 Literals
* @param variable
* @param pathParamSpec
*/
private void assertIsValidVariableLiteral(String variable)
{
int len = variable.length();
int i = 0;
int codepoint;
boolean valid = (len > 0); // must not be zero length
while (valid && i < len)
{
codepoint = variable.codePointAt(i);
i += Character.charCount(codepoint);
// basic letters, digits, or symbols
if (isValidBasicLiteralCodepoint(codepoint))
{
continue;
}
// The ucschar and iprivate pieces
if (Character.isSupplementaryCodePoint(codepoint))
{
continue;
}
// pct-encoded
if (codepoint == '%')
{
if (i + 2 > len)
{
// invalid percent encoding, missing extra 2 chars
valid = false;
continue;
}
codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
// validate basic literal
if (isValidBasicLiteralCodepoint(codepoint))
{
continue;
}
}
valid = false;
}
if (!valid)
{
// invalid variable name
StringBuilder err = new StringBuilder();
err.append("Syntax Error: variable {");
err.append(variable);
err.append("} an invalid variable name: ");
err.append(pathSpec);
throw new IllegalArgumentException(err.toString());
}
}
private boolean isValidBasicLiteralCodepoint(int codepoint)
{
// basic letters or digits
if((codepoint >= 'a' && codepoint <= 'z') ||
(codepoint >= 'A' && codepoint <= 'Z') ||
(codepoint >= '0' && codepoint <= '9'))
{
return true;
}
// basic allowed symbols
if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
{
return true; // valid simple value
}
// basic reserved symbols
if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
{
LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
return false; // valid simple value
}
return false;
}
public Map<String, String> getPathParams(String path)
{
Matcher matcher = getMatcher(path);
if (matcher.matches())
{
if (group == PathSpecGroup.EXACT)
{
return Collections.emptyMap();
}
Map<String, String> ret = new HashMap<>();
int groupCount = matcher.groupCount();
for (int i = 1; i <= groupCount; i++)
{
ret.put(this.variables[i - 1],matcher.group(i));
}
return ret;
}
return null;
}
public int getVariableCount()
{
return variables.length;
}
public String[] getVariables()
{
return this.variables;
}
}

View File

@ -0,0 +1,43 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpHeader;
/* ------------------------------------------------------------ */
/**
*/
public class AuthorityHttpField extends HostPortHttpField
{
public final static String AUTHORITY = HpackContext.STATIC_TABLE[1][0];
public AuthorityHttpField(String authority)
{
super(HttpHeader.C_AUTHORITY,AUTHORITY,authority);
}
@Override
public String toString()
{
return String.format("%s(preparsed h=%s p=%d)",super.toString(),getHost(),getPort());
}
}

View File

@ -0,0 +1,515 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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.util.ArrayQueue;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** HPACK - Header Compression for HTTP/2
* <p>This class maintains the compression context for a single HTTP/2
* connection. Specifically it holds the static and dynamic Header Field Tables
* and the associated sizes and limits.
* </p>
* <p>It is compliant with draft 11 of the specification</p>
*/
public class HpackContext
{
public static final Logger LOG = Log.getLogger(HpackContext.class);
public static final String[][] STATIC_TABLE =
{
{null,null},
/* 1 */ {":authority",null},
/* 2 */ {":method","GET"},
/* 3 */ {":method","POST"},
/* 4 */ {":path","/"},
/* 5 */ {":path","/index.html"},
/* 6 */ {":scheme","http"},
/* 7 */ {":scheme","https"},
/* 8 */ {":status","200"},
/* 9 */ {":status","204"},
/* 10 */ {":status","206"},
/* 11 */ {":status","304"},
/* 12 */ {":status","400"},
/* 13 */ {":status","404"},
/* 14 */ {":status","500"},
/* 15 */ {"accept-charset",null},
/* 16 */ {"accept-encoding","gzip, deflate"},
/* 17 */ {"accept-language",null},
/* 18 */ {"accept-ranges",null},
/* 19 */ {"accept",null},
/* 20 */ {"access-control-allow-origin",null},
/* 21 */ {"age",null},
/* 22 */ {"allow",null},
/* 23 */ {"authorization",null},
/* 24 */ {"cache-control",null},
/* 25 */ {"content-disposition",null},
/* 26 */ {"content-encoding",null},
/* 27 */ {"content-language",null},
/* 28 */ {"content-length",null},
/* 29 */ {"content-location",null},
/* 30 */ {"content-range",null},
/* 31 */ {"content-type",null},
/* 32 */ {"cookie",null},
/* 33 */ {"date",null},
/* 34 */ {"etag",null},
/* 35 */ {"expect",null},
/* 36 */ {"expires",null},
/* 37 */ {"from",null},
/* 38 */ {"host",null},
/* 39 */ {"if-match",null},
/* 40 */ {"if-modified-since",null},
/* 41 */ {"if-none-match",null},
/* 42 */ {"if-range",null},
/* 43 */ {"if-unmodified-since",null},
/* 44 */ {"last-modified",null},
/* 45 */ {"link",null},
/* 46 */ {"location",null},
/* 47 */ {"max-forwards",null},
/* 48 */ {"proxy-authenticate",null},
/* 49 */ {"proxy-authorization",null},
/* 50 */ {"range",null},
/* 51 */ {"referer",null},
/* 52 */ {"refresh",null},
/* 53 */ {"retry-after",null},
/* 54 */ {"server",null},
/* 55 */ {"set-cookie",null},
/* 56 */ {"strict-transport-security",null},
/* 57 */ {"transfer-encoding",null},
/* 58 */ {"user-agent",null},
/* 59 */ {"vary",null},
/* 60 */ {"via",null},
/* 61 */ {"www-authenticate",null},
};
private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
private static final Trie<StaticEntry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()];
private static final StaticEntry[] __staticTable=new StaticEntry[STATIC_TABLE.length];
static
{
Set<String> added = new HashSet<>();
for (int i=1;i<STATIC_TABLE.length;i++)
{
StaticEntry entry=null;
String name = STATIC_TABLE[i][0];
String value = STATIC_TABLE[i][1];
HttpHeader header = HttpHeader.CACHE.get(name);
if (header!=null && value!=null)
{
switch (header)
{
case C_METHOD:
{
HttpMethod method = HttpMethod.CACHE.get(value);
if (method!=null)
entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,method));
break;
}
case C_SCHEME:
{
HttpScheme scheme = HttpScheme.CACHE.get(value);
if (scheme!=null)
entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,scheme));
break;
}
case C_STATUS:
{
entry=new StaticEntry(i,new StaticTableHttpField(header,name,value,Integer.valueOf(value)));
break;
}
default:
break;
}
}
if (entry==null)
entry=new StaticEntry(i,header==null?new HttpField(STATIC_TABLE[i][0],value):new HttpField(header,name,value));
__staticTable[i]=entry;
if (entry._field.getValue()!=null)
__staticFieldMap.put(entry._field,entry);
if (!added.contains(entry._field.getName()))
{
added.add(entry._field.getName());
__staticNameMap.put(entry._field.getName(),entry);
if (__staticNameMap.get(entry._field.getName())==null)
throw new IllegalStateException("name trie too small");
}
}
for (HttpHeader h : HttpHeader.values())
{
StaticEntry entry = __staticNameMap.get(h.asString());
if (entry!=null)
__staticTableByHeader[h.ordinal()]=entry;
}
}
private int _maxDynamicTableSizeInBytes;
private int _dynamicTableSizeInBytes;
private final DynamicTable _dynamicTable;
private final Map<HttpField,Entry> _fieldMap = new HashMap<>();
private final Map<String,Entry> _nameMap = new HashMap<>();
HpackContext(int maxDynamicTableSize)
{
_maxDynamicTableSizeInBytes=maxDynamicTableSize;
int guesstimateEntries = 10+maxDynamicTableSize/(32+10+10);
_dynamicTable=new DynamicTable(guesstimateEntries,guesstimateEntries+10);
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] created max=%d",hashCode(),maxDynamicTableSize));
}
public void resize(int newMaxDynamicTableSize)
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d",hashCode(),_maxDynamicTableSizeInBytes,newMaxDynamicTableSize));
_maxDynamicTableSizeInBytes=newMaxDynamicTableSize;
int guesstimateEntries = 10+newMaxDynamicTableSize/(32+10+10);
evict();
_dynamicTable.resizeUnsafe(guesstimateEntries);
}
public Entry get(HttpField field)
{
Entry entry = _fieldMap.get(field);
if (entry==null)
entry=__staticFieldMap.get(field);
return entry;
}
public Entry get(String name)
{
Entry entry = __staticNameMap.get(name);
if (entry!=null)
return entry;
return _nameMap.get(StringUtil.asciiToLowerCase(name));
}
public Entry get(int index)
{
if (index<__staticTable.length)
return __staticTable[index];
int d=_dynamicTable.size()-index+__staticTable.length-1;
if (d>=0)
return _dynamicTable.getUnsafe(d);
return null;
}
public Entry get(HttpHeader header)
{
Entry e = __staticTableByHeader[header.ordinal()];
if (e==null)
return get(header.asString());
return e;
}
public static Entry getStatic(HttpHeader header)
{
return __staticTableByHeader[header.ordinal()];
}
public Entry add(HttpField field)
{
int slot=_dynamicTable.getNextSlotUnsafe();
Entry entry=new Entry(slot,field);
int size = entry.getSize();
if (size>_maxDynamicTableSizeInBytes)
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] !added size %d>%d",hashCode(),size,_maxDynamicTableSizeInBytes));
return null;
}
_dynamicTableSizeInBytes+=size;
_dynamicTable.addUnsafe(entry);
_fieldMap.put(field,entry);
_nameMap.put(StringUtil.asciiToLowerCase(field.getName()),entry);
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] added %s",hashCode(),entry));
evict();
return entry;
}
/**
* @return Current dynamic table size in entries
*/
public int size()
{
return _dynamicTable.size();
}
/**
* @return Current Dynamic table size in Octets
*/
public int getDynamicTableSize()
{
return _dynamicTableSizeInBytes;
}
/**
* @return Max Dynamic table size in Octets
*/
public int getMaxDynamicTableSize()
{
return _maxDynamicTableSizeInBytes;
}
public int index(Entry entry)
{
if (entry._slot<0)
return 0;
if (entry.isStatic())
return entry._slot;
return _dynamicTable.index(entry)+__staticTable.length-1;
}
public static int staticIndex(HttpHeader header)
{
if (header==null)
return 0;
Entry entry=__staticNameMap.get(header.asString());
if (entry==null)
return 0;
return entry.getSlot();
}
private void evict()
{
while (_dynamicTableSizeInBytes>_maxDynamicTableSizeInBytes)
{
Entry entry = _dynamicTable.pollUnsafe();
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] evict %s",hashCode(),entry));
_dynamicTableSizeInBytes-=entry.getSize();
entry._slot=-1;
_fieldMap.remove(entry.getHttpField());
String lc=StringUtil.asciiToLowerCase(entry.getHttpField().getName());
if (entry==_nameMap.get(lc))
_nameMap.remove(lc);
}
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes));
}
@Override
public String toString()
{
return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes);
}
/* ------------------------------------------------------------ */
/**
*/
private class DynamicTable extends ArrayQueue<HpackContext.Entry>
{
/* ------------------------------------------------------------ */
/**
* @param initCapacity
* @param growBy
*/
private DynamicTable(int initCapacity, int growBy)
{
super(initCapacity,growBy);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.ArrayQueue#growUnsafe()
*/
@Override
protected void resizeUnsafe(int newCapacity)
{
// Relay on super.growUnsafe to pack all entries 0 to _nextSlot
super.resizeUnsafe(newCapacity);
for (int s=0;s<_nextSlot;s++)
((Entry)_elements[s])._slot=s;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.ArrayQueue#enqueue(java.lang.Object)
*/
@Override
public boolean enqueue(Entry e)
{
return super.enqueue(e);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.ArrayQueue#dequeue()
*/
@Override
public Entry dequeue()
{
return super.dequeue();
}
/* ------------------------------------------------------------ */
/**
* @param entry
* @return
*/
private int index(Entry entry)
{
return entry._slot>=_nextE?_size-entry._slot+_nextE:_nextSlot-entry._slot;
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public static class Entry
{
final HttpField _field;
int _slot;
Entry()
{
_slot=0;
_field=null;
}
Entry(int index,String name, String value)
{
_slot=index;
_field=new HttpField(name,value);
}
Entry(int slot, HttpField field)
{
_slot=slot;
_field=field;
}
public int getSize()
{
return 32+_field.getName().length()+_field.getValue().length();
}
public HttpField getHttpField()
{
return _field;
}
public boolean isStatic()
{
return false;
}
public byte[] getStaticHuffmanValue()
{
return null;
}
public int getSlot()
{
return _slot;
}
public String toString()
{
return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_slot,_field,hashCode());
}
}
public static class StaticEntry extends Entry
{
private final byte[] _huffmanValue;
private final byte _encodedField;
StaticEntry(int index,HttpField field)
{
super(index,field);
String value = field.getValue();
if (value!=null && value.length()>0)
{
int huffmanLen = Huffman.octetsNeeded(value);
int lenLen = NBitInteger.octectsNeeded(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);
// Encode value
Huffman.encode(buffer,value);
}
else
_huffmanValue=null;
_encodedField=(byte)(0x80|index);
}
@Override
public boolean isStatic()
{
return true;
}
@Override
public byte[] getStaticHuffmanValue()
{
return _huffmanValue;
}
public byte getEncodedField()
{
return _encodedField;
}
}
}

View File

@ -0,0 +1,280 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Hpack Decoder
* <p>This is not thread safe and may only be called by 1 thread at a time.</p>
*/
public class HpackDecoder
{
public static final Logger LOG = Log.getLogger(HpackDecoder.class);
public final static HttpField.LongValueHttpField CONTENT_LENGTH_0 =
new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,0L);
private final HpackContext _context;
private final MetaDataBuilder _builder;
private int _localMaxDynamicTableSize;
/**
* @param localMaxDynamicTableSize The maximum allowed size of the local dynamic header field table.
* @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters.
*/
public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize)
{
_context=new HpackContext(localMaxDynamicTableSize);
_localMaxDynamicTableSize=localMaxDynamicTableSize;
_builder = new MetaDataBuilder(maxHeaderSize);
}
public HpackContext getHpackContext()
{
return _context;
}
public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize)
{
_localMaxDynamicTableSize=localMaxdynamciTableSize;
}
public MetaData decode(ByteBuffer buffer)
{
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 (buffer.remaining()>_builder.getMaxSize())
throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
while(buffer.hasRemaining())
{
if (LOG.isDebugEnabled())
{
int l=Math.min(buffer.remaining(),16);
// TODO: not guaranteed the buffer has a backing array !
LOG.debug("decode {}{}",
TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+buffer.position(),l),
l<buffer.remaining()?"...":"");
}
byte b = buffer.get();
if (b<0)
{
// 7.1 indexed if the high bit is set
int index = NBitInteger.decode(buffer,7);
Entry entry=_context.get(index);
if (entry==null)
{
throw new BadMessageException("Unknown index "+index);
}
else if (entry.isStatic())
{
if (LOG.isDebugEnabled())
LOG.debug("decode IdxStatic {}",entry);
// emit field
_builder.emit(entry.getHttpField());
// TODO copy and add to reference set if there is room
// _context.add(entry.getHttpField());
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("decode Idx {}",entry);
// emit
_builder.emit(entry.getHttpField());
}
}
else
{
// look at the first nibble in detail
byte f= (byte)((b&0xF0)>>4);
String name;
HttpHeader header;
String value;
boolean indexed;
int name_index;
switch (f)
{
case 2: // 7.3
case 3: // 7.3
// change table size
int size = NBitInteger.decode(buffer,5);
if (LOG.isDebugEnabled())
LOG.debug("decode resize="+size);
if (size>_localMaxDynamicTableSize)
throw new IllegalArgumentException();
_context.resize(size);
continue;
case 0: // 7.2.2
case 1: // 7.2.3
indexed=false;
name_index=NBitInteger.decode(buffer,4);
break;
case 4: // 7.2.1
case 5: // 7.2.1
case 6: // 7.2.1
case 7: // 7.2.1
indexed=true;
name_index=NBitInteger.decode(buffer,6);
break;
default:
throw new IllegalStateException();
}
boolean huffmanName=false;
// decode the name
if (name_index>0)
{
Entry name_entry=_context.get(name_index);
name=name_entry.getHttpField().getName();
header=name_entry.getHttpField().getHeader();
}
else
{
huffmanName = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
_builder.checkSize(length,huffmanName);
if (huffmanName)
name=Huffman.decode(buffer,length);
else
name=toASCIIString(buffer,length);
for (int i=0;i<name.length();i++)
{
char c=name.charAt(i);
if (c>='A'&&c<='Z')
{
throw new BadMessageException(400,"Uppercase header name");
}
}
header=HttpHeader.CACHE.get(name);
}
// decode the value
boolean huffmanValue = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
_builder.checkSize(length,huffmanValue);
if (huffmanValue)
value=Huffman.decode(buffer,length);
else
value=toASCIIString(buffer,length);
// Make the new field
HttpField field;
if (header==null)
{
// just make a normal field and bypass header name lookup
field = new HttpField(null,name,value);
}
else
{
// might be worthwhile to create a value HttpField if it is indexed
// and/or of a type that may be looked up multiple times.
switch(header)
{
case C_STATUS:
if (indexed)
field = new HttpField.IntValueHttpField(header,name,value);
else
field = new HttpField(header,name,value);
break;
case C_AUTHORITY:
field = new AuthorityHttpField(value);
break;
case CONTENT_LENGTH:
if ("0".equals(value))
field = CONTENT_LENGTH_0;
else
field = new HttpField.LongValueHttpField(header,name,value);
break;
default:
field = new HttpField(header,name,value);
break;
}
}
if (LOG.isDebugEnabled())
{
LOG.debug("decoded '{}' by {}/{}/{}",
field,
name_index > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
huffmanValue ? "HuffVal" : "LitVal",
indexed ? "Idx" : "");
}
// emit the field
_builder.emit(field);
// if indexed
if (indexed)
{
// add to dynamic table
_context.add(field);
}
}
}
return _builder.build();
}
public static String toASCIIString(ByteBuffer buffer,int length)
{
StringBuilder builder = new StringBuilder(length);
int position=buffer.position();
int start=buffer.arrayOffset()+ position;
int end=start+length;
buffer.position(position+length);
byte[] array=buffer.array();
for (int i=start;i<end;i++)
builder.append((char)(0x7f&array[i]));
return builder.toString();
}
@Override
public String toString()
{
return String.format("HpackDecoder@%x{%s}",hashCode(),_context);
}
}

View File

@ -0,0 +1,354 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HpackEncoder
{
public static final Logger LOG = Log.getLogger(HpackEncoder.class);
private final static HttpField[] __status= new HttpField[599];
final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
EnumSet.of(
HttpHeader.AUTHORIZATION,
HttpHeader.CONTENT_MD5,
HttpHeader.PROXY_AUTHENTICATE,
HttpHeader.PROXY_AUTHORIZATION);
final static EnumSet<HttpHeader> __DO_NOT_INDEX =
EnumSet.of(
// HttpHeader.C_PATH, // TODO more data needed
// HttpHeader.DATE, // TODO more data needed
HttpHeader.AUTHORIZATION,
HttpHeader.CONTENT_MD5,
HttpHeader.CONTENT_RANGE,
HttpHeader.ETAG,
HttpHeader.IF_MODIFIED_SINCE,
HttpHeader.IF_UNMODIFIED_SINCE,
HttpHeader.IF_NONE_MATCH,
HttpHeader.IF_RANGE,
HttpHeader.IF_MATCH,
HttpHeader.LOCATION,
HttpHeader.RANGE,
HttpHeader.RETRY_AFTER,
// HttpHeader.EXPIRES,
HttpHeader.LAST_MODIFIED,
HttpHeader.SET_COOKIE,
HttpHeader.SET_COOKIE2);
final static EnumSet<HttpHeader> __NEVER_INDEX =
EnumSet.of(
HttpHeader.AUTHORIZATION,
HttpHeader.SET_COOKIE,
HttpHeader.SET_COOKIE2);
static
{
for (HttpStatus.Code code : HttpStatus.Code.values())
__status[code.getCode()]=new PreEncodedHttpField(HttpHeader.C_STATUS,Integer.toString(code.getCode()));
}
private final HpackContext _context;
private final boolean _debug;
private int _remoteMaxDynamicTableSize;
private int _localMaxDynamicTableSize;
public HpackEncoder()
{
this(4096,4096);
}
public HpackEncoder(int localMaxDynamicTableSize)
{
this(localMaxDynamicTableSize,4096);
}
public HpackEncoder(int localMaxDynamicTableSize,int remoteMaxDynamicTableSize)
{
_context=new HpackContext(remoteMaxDynamicTableSize);
_remoteMaxDynamicTableSize=remoteMaxDynamicTableSize;
_localMaxDynamicTableSize=localMaxDynamicTableSize;
_debug=LOG.isDebugEnabled();
}
public HpackContext getHpackContext()
{
return _context;
}
public void setRemoteMaxDynamicTableSize(int remoteMaxDynamicTableSize)
{
_remoteMaxDynamicTableSize=remoteMaxDynamicTableSize;
}
public void setLocalMaxDynamicTableSize(int localMaxDynamicTableSize)
{
_localMaxDynamicTableSize=localMaxDynamicTableSize;
}
public void encode(ByteBuffer buffer, MetaData metadata)
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("CtxTbl[%x] encoding",_context.hashCode()));
int pos = buffer.position();
// Check the dynamic table sizes!
int maxDynamicTableSize=Math.min(_remoteMaxDynamicTableSize,_localMaxDynamicTableSize);
if (maxDynamicTableSize!=_context.getMaxDynamicTableSize())
encodeMaxDynamicTableSize(buffer,maxDynamicTableSize);
// Add Request/response meta fields
if (metadata.isRequest())
{
MetaData.Request request = (MetaData.Request)metadata;
// TODO optimise these to avoid HttpField creation
String scheme=request.getURI().getScheme();
encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
}
else if (metadata.isResponse())
{
MetaData.Response response = (MetaData.Response)metadata;
int code=response.getStatus();
HttpField status = code<__status.length?__status[code]:null;
if (status==null)
status=new HttpField.IntValueHttpField(HttpHeader.C_STATUS,code);
encode(buffer,status);
}
// Add all the other fields
for (HttpField field : metadata)
encode(buffer,field);
if (LOG.isDebugEnabled())
LOG.debug(String.format("CtxTbl[%x] encoded %d octets",_context.hashCode(), buffer.position() - pos));
}
public void encodeMaxDynamicTableSize(ByteBuffer buffer, int maxDynamicTableSize)
{
if (maxDynamicTableSize>_remoteMaxDynamicTableSize)
throw new IllegalArgumentException();
buffer.put((byte)0x20);
NBitInteger.encode(buffer,5,maxDynamicTableSize);
_context.resize(maxDynamicTableSize);
}
public void encode(ByteBuffer buffer, HttpField field)
{
final int p=_debug?buffer.position():-1;
String encoding=null;
// Is there an entry for the field?
Entry entry = _context.get(field);
if (entry!=null)
{
// Known field entry, so encode it as indexed
if (entry.isStatic())
{
buffer.put(((StaticEntry)entry).getEncodedField());
if (_debug)
encoding="IdxFieldS1";
}
else
{
int index=_context.index(entry);
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index);
if (_debug)
encoding="IdxField"+(entry.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(7,index));
}
}
else
{
// Unknown field entry, so we will have to send literally.
final boolean indexed;
// But do we know it's name?
HttpHeader header = field.getHeader();
// Select encoding strategy
if (header==null)
{
// Select encoding strategy for unknown header names
Entry name = _context.get(field.getName());
if (field instanceof PreEncodedHttpField)
{
int i=buffer.position();
((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
byte b=buffer.get(i);
indexed=b<0||b>=0x40;
if (_debug)
encoding=indexed?"PreEncodedIdx":"PreEncoded";
}
// has the custom header name been seen before?
else if (name==null)
{
// unknown name and value, so let's index this just in case it is
// the first time we have seen a custom name or a custom field.
// unless the name is changing, this is worthwhile
indexed=true;
encodeName(buffer,(byte)0x40,6,field.getName(),null);
encodeValue(buffer,true,field.getValue());
if (_debug)
encoding="LitHuffNHuffVIdx";
}
else
{
// known custom name, but unknown value.
// This is probably a custom field with changing value, so don't index.
indexed=false;
encodeName(buffer,(byte)0x00,4,field.getName(),null);
encodeValue(buffer,true,field.getValue());
if (_debug)
encoding="LitHuffNHuffV!Idx";
}
}
else
{
// Select encoding strategy for known header names
Entry name = _context.get(header);
if (field instanceof PreEncodedHttpField)
{
// Preencoded field
int i=buffer.position();
((PreEncodedHttpField)field).putTo(buffer,HttpVersion.HTTP_2);
byte b=buffer.get(i);
indexed=b<0||b>=0x40;
if (_debug)
encoding=indexed?"PreEncodedIdx":"PreEncoded";
}
else if (__DO_NOT_INDEX.contains(header))
{
// Non indexed field
indexed=false;
boolean never_index=__NEVER_INDEX.contains(header);
boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
encodeName(buffer,never_index?(byte)0x10:(byte)0x00,4,header.asString(),name);
encodeValue(buffer,huffman,field.getValue());
if (_debug)
encoding="Lit"+
((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(4,_context.index(name)))))+
(huffman?"HuffV":"LitV")+
(indexed?"Idx":(never_index?"!!Idx":"!Idx"));
}
else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
{
// Non indexed content length for 2 digits or more
indexed=false;
encodeName(buffer,(byte)0x00,4,header.asString(),name);
encodeValue(buffer,true,field.getValue());
if (_debug)
encoding="LitIdxNS"+(1+NBitInteger.octectsNeeded(4,_context.index(name)))+"HuffV!Idx";
}
else
{
// indexed
indexed=true;
boolean huffman=!__DO_NOT_HUFFMAN.contains(header);
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)))))+
(huffman?"HuffVIdx":"LitVIdx");
}
}
// If we want the field referenced, then we add it to our
// table and reference set.
if (indexed)
_context.add(field);
}
if (_debug)
{
int e=buffer.position();
if (LOG.isDebugEnabled())
LOG.debug("encode {}:'{}' to '{}'",encoding,field,TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p));
}
}
private void encodeName(ByteBuffer buffer, byte mask, int bits, String name, Entry entry)
{
buffer.put(mask);
if (entry==null)
{
// 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);
}
else
{
NBitInteger.encode(buffer,bits,_context.index(entry));
}
}
static void encodeValue(ByteBuffer buffer, boolean huffman, String value)
{
if (huffman)
{
// huffman literal value
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,Huffman.octetsNeeded(value));
Huffman.encode(buffer,value);
}
else
{
// add literal assuming iso_8859_1
buffer.put((byte)0x00);
NBitInteger.encode(buffer,7,value.length());
for (int i=0;i<value.length();i++)
{
char c=value.charAt(i);
if (c<' '|| c>127)
throw new IllegalArgumentException();
buffer.put((byte)c);
}
}
}
}

View File

@ -0,0 +1,97 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
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.util.BufferUtil;
/* ------------------------------------------------------------ */
/**
*/
public class HpackFieldPreEncoder implements HttpFieldPreEncoder
{
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.HttpFieldPreEncoder#getHttpVersion()
*/
@Override
public HttpVersion getHttpVersion()
{
return HttpVersion.HTTP_2;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.HttpFieldPreEncoder#getEncodedField(org.eclipse.jetty.http.HttpHeader, java.lang.String, java.lang.String)
*/
@Override
public byte[] getEncodedField(HttpHeader header, String name, String value)
{
boolean not_indexed=HpackEncoder.__DO_NOT_INDEX.contains(header);
ByteBuffer buffer = BufferUtil.allocate(name.length()+value.length()+10);
BufferUtil.clearToFill(buffer);
boolean huffman;
int bits;
if (not_indexed)
{
// Non indexed field
boolean never_index=HpackEncoder.__NEVER_INDEX.contains(header);
huffman=!HpackEncoder.__DO_NOT_HUFFMAN.contains(header);
buffer.put(never_index?(byte)0x10:(byte)0x00);
bits=4;
}
else if (header==HttpHeader.CONTENT_LENGTH && value.length()>1)
{
// Non indexed content length for 2 digits or more
buffer.put((byte)0x00);
huffman=true;
bits=4;
}
else
{
// indexed
buffer.put((byte)0x40);
huffman=!HpackEncoder.__DO_NOT_HUFFMAN.contains(header);
bits=6;
}
int name_idx=HpackContext.staticIndex(header);
if (name_idx>0)
NBitInteger.encode(buffer,bits,name_idx);
else
{
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(name));
Huffman.encodeLC(buffer,name);
}
HpackEncoder.encodeValue(buffer,huffman,value);
BufferUtil.flipToFlush(buffer,0);
return BufferUtil.toArray(buffer);
}
}

View File

@ -0,0 +1,480 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
public class Huffman
{
// 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][];
// 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 String decode(ByteBuffer buffer)
{
return decode(buffer,buffer.remaining());
}
public static String decode(ByteBuffer buffer,int length)
{
StringBuilder out = new StringBuilder(length*2);
int node = 0;
int current = 0;
int bits = 0;
byte[] array = buffer.array();
int position=buffer.position();
int start=buffer.arrayOffset()+position;
int end=start+length;
buffer.position(position+length);
for (int i=start; i<end; i++)
{
int b = array[i]&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)
{
// terminal node
out.append(rowsym[node]);
bits -= rowbits[node];
node = 0;
}
else
{
// non-terminal node
bits -= 8;
}
}
}
while (bits > 0)
{
int c = (current << (8 - bits)) & 0xFF;
node = tree[node*256+c];
if (rowbits[node]==0 || rowbits[node] > bits)
break;
if (rowbits[node]==0)
throw new IllegalStateException();
out.append(rowsym[node]);
bits -= rowbits[node];
node = 0;
}
return out.toString();
}
public static int octetsNeeded(String s)
{
return octetsNeeded(CODES,s);
}
public static void encode(ByteBuffer buffer,String s)
{
encode(CODES,buffer,s);
}
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<' ')
throw new IllegalArgumentException();
needed += table[c][1];
}
return (needed+7) / 8;
}
private static void encode(final int[][] table,ByteBuffer buffer,String s)
{
long current = 0;
int n = 0;
byte[] array = buffer.array();
int p=buffer.arrayOffset()+buffer.position();
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;
array[p++]=(byte)(current >> n);
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
array[p++]=(byte)current;
}
buffer.position(p-buffer.arrayOffset());
}
}

View File

@ -0,0 +1,184 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
public class MetaDataBuilder
{
private final int _maxSize;
private int _size;
private int _status;
private String _method;
private HttpScheme _scheme;
private HostPortHttpField _authority;
private String _path;
private long _contentLength=Long.MIN_VALUE;
private HttpFields _fields = new HttpFields(10);
/* ------------------------------------------------------------ */
/**
* @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters.
*/
MetaDataBuilder(int maxHeadersSize)
{
_maxSize=maxHeadersSize;
}
/** Get the maxSize.
* @return the maxSize
*/
public int getMaxSize()
{
return _maxSize;
}
/** Get the size.
* @return the current size in bytes
*/
public int getSize()
{
return _size;
}
public void emit(HttpField field)
{
int field_size = field.getName().length()+field.getValue().length();
_size+=field_size;
if (_size>_maxSize)
throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header size "+_size+">"+_maxSize);
if (field instanceof StaticTableHttpField)
{
StaticTableHttpField value = (StaticTableHttpField)field;
switch(field.getHeader())
{
case C_STATUS:
_status=(Integer)value.getStaticValue();
break;
case C_METHOD:
_method=field.getValue();
break;
case C_SCHEME:
_scheme = (HttpScheme)value.getStaticValue();
break;
default:
throw new IllegalArgumentException(field.getName());
}
}
else if (field.getHeader()!=null)
{
switch(field.getHeader())
{
case C_STATUS:
_status=field.getIntValue();
break;
case C_METHOD:
_method=field.getValue();
break;
case C_SCHEME:
_scheme = HttpScheme.CACHE.get(field.getValue());
break;
case C_AUTHORITY:
_authority=(field instanceof HostPortHttpField)?((HostPortHttpField)field):new AuthorityHttpField(field.getValue());
break;
case HOST:
// :authority fields must come first. If we have one, ignore the host header as far as authority goes.
if (_authority==null)
_authority=(field instanceof HostPortHttpField)?((HostPortHttpField)field):new AuthorityHttpField(field.getValue());
_fields.add(field);
break;
case C_PATH:
_path = field.getValue();
break;
case CONTENT_LENGTH:
_contentLength = field.getLongValue();
_fields.add(field);
break;
default:
if (field.getName().charAt(0)!=':')
_fields.add(field);
}
}
else
{
if (field.getName().charAt(0)!=':')
_fields.add(field);
}
}
public MetaData build()
{
try
{
HttpFields fields = _fields;
_fields = new HttpFields(Math.max(10,fields.size()+5));
if (_method!=null)
return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength);
if (_status!=0)
return new MetaData.Response(HttpVersion.HTTP_2,_status,fields,_contentLength);
return new MetaData(HttpVersion.HTTP_2,fields,_contentLength);
}
finally
{
_status=0;
_method=null;
_scheme=null;
_authority=null;
_path=null;
_size=0;
_contentLength=Long.MIN_VALUE;
}
}
/* ------------------------------------------------------------ */
/** Check that the max size will not be exceeded.
* @param length the length
* @param huffman the huffman name
*/
public void checkSize(int length, boolean huffman)
{
// Apply a huffman fudge factor
if (huffman)
length=(length*4)/3;
if ((_size+length)>_maxSize)
throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header size "+(_size+length)+">"+_maxSize);
}
}

View File

@ -0,0 +1,151 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.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;
}
}

View File

@ -0,0 +1,61 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.hpack;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
/* ------------------------------------------------------------ */
public class StaticTableHttpField extends HttpField
{
private final Object _value;
public StaticTableHttpField(HttpHeader header, String name, String valueString, Object value)
{
super(header,name,valueString);
if (value==null)
throw new IllegalArgumentException();
_value=value;
}
public StaticTableHttpField(HttpHeader header,String valueString, Object value)
{
this (header,header.asString(),valueString, value);
}
public StaticTableHttpField(String name, String valueString, Object value)
{
super(name,valueString);
if (value==null)
throw new IllegalArgumentException();
_value=value;
}
public Object getStaticValue()
{
return _value;
}
@Override
public String toString()
{
return super.toString()+"(evaluated)";
}
}