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:
parent
16bfc4f59b
commit
a46d233f37
|
@ -2,6 +2,7 @@ syntax: glob
|
||||||
build
|
build
|
||||||
|
|
||||||
External Plug-in Libraries
|
External Plug-in Libraries
|
||||||
|
org.eclipse.jetty.http/bin
|
||||||
syntax: regexp
|
syntax: regexp
|
||||||
^adempiere$
|
^adempiere$
|
||||||
^org\.adempiere\.install/lib$
|
^org\.adempiere\.install/lib$
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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: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\.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.feature" failOnError="false"/>
|
||||||
<rm:locator pattern="^org\.idempiere\.hazelcast\.service$" searchPathRef="workspace.project"/>
|
<rm:locator pattern="^org\.idempiere\.hazelcast\.service$" searchPathRef="workspace.project"/>
|
||||||
|
@ -171,4 +173,11 @@
|
||||||
<rm:uri format="${url.zkoss.osgi}"/>
|
<rm:uri format="${url.zkoss.osgi}"/>
|
||||||
</rm:provider>
|
</rm:provider>
|
||||||
</rm:searchPath>
|
</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>
|
</rm:rmap>
|
||||||
|
|
|
@ -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.file.srv=http://downloads.sourceforge.net/project/idempiere/binary.file
|
||||||
|
|
||||||
url.restlet.p2=http://p2.restlet.com/2.3
|
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
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.eclipse.jetty.http.Http1FieldPreEncoder
|
||||||
|
org.eclipse.jetty.http2.hpack.HpackFieldPreEncoder
|
|
@ -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 ("Content"). The Content is dual licensed and is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 ("EPL") 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, "Program" 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 ("Redistributor") 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>
|
|
@ -0,0 +1,4 @@
|
||||||
|
source.. = src/
|
||||||
|
output.. = bin/
|
||||||
|
bin.includes = META-INF/,\
|
||||||
|
.
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 }
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -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
|
@ -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 }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue