Fix #1551 Moved CookieCutter to jetty-http

This commit is contained in:
Greg Wilkins 2017-07-19 16:19:46 +02:00
parent 2443ca34f3
commit 1b1b250947
7 changed files with 467 additions and 146 deletions

View File

@ -16,122 +16,44 @@
// ========================================================================
//
package org.eclipse.jetty.server;
import java.util.ArrayList;
package org.eclipse.jetty.http;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Cookie parser
* <p>Optimized stateful cookie parser. Cookies fields are added with the
* {@link #addCookieField(String)} method and parsed on the next subsequent
* call to {@link #getCookies()}.
* If the added fields are identical to those last added (as strings), then the
* cookies are not re parsed.
*
*/
public class CookieCutter
public abstract class CookieCutter
{
private static final Logger LOG = Log.getLogger(CookieCutter.class);
protected static final Logger LOG = Log.getLogger(CookieCutter.class);
private final CookieCompliance _compliance;
private Cookie[] _cookies;
private Cookie[] _lastCookies;
private final List<String> _fieldList = new ArrayList<>();
int _fields;
public CookieCutter()
{
this(CookieCompliance.RFC6265);
}
public CookieCutter(CookieCompliance compliance)
{
protected final CookieCompliance _compliance;
protected CookieCutter(CookieCompliance compliance)
{
_compliance = compliance;
}
public Cookie[] getCookies()
{
if (_cookies!=null)
return _cookies;
if (_lastCookies!=null && _fields==_fieldList.size())
_cookies=_lastCookies;
else
parseFields();
_lastCookies=_cookies;
return _cookies;
}
public void setCookies(Cookie[] cookies)
{
_cookies=cookies;
_lastCookies=null;
_fieldList.clear();
_fields=0;
}
public void reset()
{
_cookies=null;
_fields=0;
}
public void addCookieField(String f)
{
if (f==null)
return;
f=f.trim();
if (f.length()==0)
return;
if (_fieldList.size()>_fields)
{
if (f.equals(_fieldList.get(_fields)))
{
_fields++;
return;
}
while (_fieldList.size()>_fields)
_fieldList.remove(_fields);
}
_cookies=null;
_lastCookies=null;
_fieldList.add(_fields++,f);
}
protected void parseFields()
{
_lastCookies=null;
_cookies=null;
List<Cookie> cookies = new ArrayList<>();
int version = 0;
// delete excess fields
while (_fieldList.size()>_fields)
_fieldList.remove(_fields);
protected void parseFields(List<String> rawFields)
{
StringBuilder unquoted=null;
// For each cookie field
for (String hdr : _fieldList)
for (String hdr : rawFields)
{
// Parse the header
String name = null;
String value = null;
Cookie cookie = null;
String cookieName = null;
String cookieValue = null;
String cookiePath = null;
String cookieDomain = null;
String cookieComment = null;
int cookieVersion = 0;
boolean invalue=false;
boolean inQuoted=false;
@ -142,9 +64,9 @@ public class CookieCutter
for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
{
char c = hdr.charAt(i);
// System.err.printf("i=%d c=%s v=%b q=%b e=%b u=%s s=%d e=%d%n" ,i,""+c,invalue,inQuoted,escaped,unquoted,tokenstart,tokenend);
// Handle quoted values for name or value
if (inQuoted)
{
@ -154,7 +76,7 @@ public class CookieCutter
unquoted.append(c);
continue;
}
switch (c)
{
case '"':
@ -171,7 +93,7 @@ public class CookieCutter
tokenend=-1;
}
break;
case '\\':
if (i==last)
{
@ -184,7 +106,7 @@ public class CookieCutter
escaped=true;
}
continue;
default:
if (i==last)
{
@ -211,7 +133,7 @@ public class CookieCutter
case ' ':
case '\t':
break;
case ';':
if (quoted)
{
@ -223,7 +145,7 @@ public class CookieCutter
value = hdr.substring(tokenstart, tokenend+1);
else
value = "";
tokenstart = -1;
invalue=false;
break;
@ -334,10 +256,10 @@ public class CookieCutter
else
value = "";
}
// If after processing the current character we have a value and a name, then it is a cookie
if (name!=null && value!=null)
{
{
try
{
if (name.startsWith("$"))
@ -345,34 +267,36 @@ public class CookieCutter
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
if (_compliance==CookieCompliance.RFC6265)
{
// Ignore
// Ignore
}
else if ("$path".equals(lowercaseName))
{
if (cookie!=null)
cookie.setPath(value);
cookiePath = value;
}
else if ("$domain".equals(lowercaseName))
{
if (cookie!=null)
cookie.setDomain(value);
cookieDomain = value;
}
else if ("$port".equals(lowercaseName))
{
if (cookie!=null)
cookie.setComment("$port="+value);
cookieComment = (cookieComment==null?"$port=":", $port=")+value;
}
else if ("$version".equals(lowercaseName))
{
version = Integer.parseInt(value);
cookieVersion = Integer.parseInt(value);
}
}
else
{
cookie = new Cookie(name, value);
if (version > 0)
cookie.setVersion(version);
cookies.add(cookie);
if (cookieName!=null)
{
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
cookieDomain = null;
cookiePath = null;
cookieComment = null;
}
cookieName = name;
cookieValue = value;
}
}
catch (Exception e)
@ -384,10 +308,13 @@ public class CookieCutter
value = null;
}
}
}
_cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
_lastCookies=_cookies;
if (cookieName!=null)
addCookie(cookieName,cookieValue,cookieDomain,cookiePath,cookieVersion,cookieComment);
}
}
protected abstract void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment);
}

View File

@ -16,27 +16,29 @@
// ========================================================================
//
package org.eclipse.jetty.server;
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.http.CookieCompliance;
import org.junit.Ignore;
import org.junit.Test;
public class CookieCutterTest
{
private Cookie[] parseCookieHeaders(CookieCompliance compliance,String... headers)
{
CookieCutter cutter = new CookieCutter(compliance);
TestCutter cutter = new TestCutter(compliance);
for (String header : headers)
{
cutter.addCookieField(header);
cutter.parseFields(header);
}
return cutter.getCookies();
return cutter.cookies.toArray(new Cookie[cutter.cookies.size()]);
}
private void assertCookie(String prefix, Cookie cookie,
@ -207,4 +209,81 @@ public class CookieCutterTest
assertThat("Cookies.length", cookies.length, is(0));
}
static class Cookie
{
String name;
String value;
String domain;
String path;
int version;
String comment;
public Cookie(String name, String value, String domain, String path, int version, String comment)
{
this.name = name;
this.value = value;
this.domain = domain;
this.path = path;
this.version = version;
this.comment = comment;
}
public String getName()
{
return name;
}
public String getValue()
{
return value;
}
public String getDomain()
{
return domain;
}
public String getPath()
{
return path;
}
public int getVersion()
{
return version;
}
public String getComment()
{
return comment;
}
}
class TestCutter extends CookieCutter
{
List<Cookie> cookies = new ArrayList<>();
protected TestCutter()
{
this(CookieCompliance.RFC6265);
}
public TestCutter(CookieCompliance compliance)
{
super(compliance);
}
@Override
protected void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment)
{
cookies.add(new Cookie(cookieName,cookieValue,cookieDomain,cookiePath,cookieVersion,cookieComment));
}
public void parseFields(String... fields)
{
super.parseFields(Arrays.asList(fields));
}
};
}

View File

@ -16,16 +16,17 @@
// ========================================================================
//
package org.eclipse.jetty.server;
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.CookieCutter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@ -165,17 +166,40 @@ public class CookieCutter_LenientTest
@Test
public void testLenientBehavior()
{
CookieCutter cutter = new CookieCutter();
cutter.addCookieField(rawHeader);
Cookie[] cookies = cutter.getCookies();
TestCutter cutter = new TestCutter();
cutter.parseField(rawHeader);
if (expectedName==null)
assertThat("Cookies.length", cookies.length, is(0));
assertThat("Cookies.length", cutter.names.size(), is(0));
else
{
assertThat("Cookies.length", cookies.length, is(1));
assertThat("Cookie.name", cookies[0].getName(), is(expectedName));
assertThat("Cookie.value", cookies[0].getValue(), is(expectedValue));
assertThat("Cookies.length", cutter.names.size(), is(1));
assertThat("Cookie.name", cutter.names.get(0), is(expectedName));
assertThat("Cookie.value", cutter.values.get(0), is(expectedValue));
}
}
class TestCutter extends CookieCutter
{
List<String> names = new ArrayList<>();
List<String> values = new ArrayList<>();
protected TestCutter()
{
super(CookieCompliance.RFC6265);
}
@Override
protected void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment)
{
names.add(cookieName);
values.add(cookieValue);
}
public void parseField(String field)
{
super.parseFields(Collections.singletonList(field));
}
};
}

View File

@ -0,0 +1,132 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.server;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.CookieCutter;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Cookie parser
* <p>Optimized stateful cookie parser. Cookies fields are added with the
* {@link #addCookieField(String)} method and parsed on the next subsequent
* call to {@link #getCookies()}.
* If the added fields are identical to those last added (as strings), then the
* cookies are not re parsed.
*
*/
public class Cookies extends CookieCutter
{
protected static final Logger LOG = Log.getLogger(Cookies.class);
protected final List<String> _rawFields = new ArrayList<>();
protected final List<Cookie> _cookieList = new ArrayList<>();
private int _addedFields;
private boolean _parsed = false;
private Cookie[] _cookies;
private boolean _set = false;
public Cookies()
{
this(CookieCompliance.RFC6265);
}
public Cookies(CookieCompliance compliance)
{
super(compliance);
}
public void addCookieField(String rawField)
{
if (rawField==null)
return;
rawField=rawField.trim();
if (rawField.length()==0)
return;
if (_rawFields.size() > _addedFields)
{
if (rawField.equals(_rawFields.get(_addedFields)))
{
_addedFields++;
return;
}
while (_rawFields.size() > _addedFields)
_rawFields.remove(_addedFields);
}
_rawFields.add(_addedFields++, rawField);
_parsed = false;
}
public Cookie[] getCookies()
{
while (_rawFields.size() > _addedFields)
{
_rawFields.remove(_addedFields);
_parsed = false;
}
if (_parsed)
return _cookies;
parseFields(_rawFields);
_cookies = (Cookie[])_cookieList.toArray(new Cookie[_cookieList.size()]);
_cookieList.clear();
_parsed = true;
return _cookies;
}
public void setCookies(Cookie[] cookies)
{
_cookies = cookies;
_set = true;
}
public void reset()
{
if (_set)
_cookies = null;
_set = false;
_addedFields = 0;
}
@Override
protected void addCookie(String name, String value, String domain, String path, int version, String comment)
{
Cookie cookie = new Cookie(name,value);
if (domain!=null)
cookie.setDomain(domain);
if (path!=null)
cookie.setPath(path);
if (version>0)
cookie.setVersion(version);
if (comment!=null)
cookie.setComment(comment);
_cookieList.add(cookie);
}
}

View File

@ -190,7 +190,7 @@ public class PushBuilderImpl implements PushBuilder
if (_request.isRequestedSessionIdFromURL())
param="jsessionid="+_sessionId;
// TODO else
// _fields.add("Cookie","JSESSIONID="+_sessionId);
// _rawFields.add("Cookie","JSESSIONID="+_sessionId);
}
HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),path,param,query,null);

View File

@ -190,7 +190,7 @@ public class Request implements HttpServletRequest
private Authentication _authentication;
private String _characterEncoding;
private ContextHandler.Context _context;
private CookieCutter _cookies;
private Cookies _cookies;
private DispatcherType _dispatcherType;
private int _inputState = __NONE;
private MultiMap<String> _queryParameters;
@ -755,7 +755,7 @@ public class Request implements HttpServletRequest
for (String c : metadata.getFields().getValuesList(HttpHeader.COOKIE))
{
if (_cookies == null)
_cookies = new CookieCutter(getHttpChannel().getHttpConfiguration().getCookieCompliance());
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getCookieCompliance());
_cookies.addCookieField(c);
}
@ -2030,7 +2030,7 @@ public class Request implements HttpServletRequest
public void setCookies(Cookie[] cookies)
{
if (_cookies == null)
_cookies = new CookieCutter(getHttpChannel().getHttpConfiguration().getCookieCompliance());
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getCookieCompliance());
_cookies.setCookies(cookies);
}

View File

@ -0,0 +1,159 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.server;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.http.CookieCompliance;
import org.hamcrest.Matchers;
import org.junit.Ignore;
import org.junit.Test;
public class CookiesTest
{
@Test
public void testEmpty()
{
Cookies cutter = new Cookies();
assertThat(cutter.getCookies().length,is(0));
cutter.reset();
assertThat(cutter.getCookies().length,is(0));
}
@Test
public void testCacheHit()
{
Cookies cutter = new Cookies();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesX = cutter.getCookies();
assertThat(cookiesX.length,is(4));
assertThat(cookiesX[0].getName(),is("nameA0"));
assertThat(cookiesX[3].getValue(),is("B1"));
cutter.reset();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesY = cutter.getCookies();
assertThat(cookiesY.length,is(4));
assertThat(cookiesY[0].getName(),is("nameA0"));
assertThat(cookiesY[3].getValue(),is("B1"));
assertThat(cookiesX, Matchers.sameInstance(cookiesY));
}
@Test
public void testCacheMiss()
{
Cookies cutter = new Cookies();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesX = cutter.getCookies();
assertThat(cookiesX.length,is(4));
assertThat(cookiesX[0].getName(),is("nameA0"));
assertThat(cookiesX[3].getValue(),is("B1"));
cutter.reset();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameC0=C0; nameC1=C1");
Cookie[] cookiesY = cutter.getCookies();
assertThat(cookiesY.length,is(4));
assertThat(cookiesY[0].getName(),is("nameA0"));
assertThat(cookiesY[3].getValue(),is("C1"));
assertThat(cookiesX, Matchers.not(Matchers.sameInstance(cookiesY)));
}
@Test
public void testCacheUnder()
{
Cookies cutter = new Cookies();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesX = cutter.getCookies();
assertThat(cookiesX.length,is(4));
assertThat(cookiesX[0].getName(),is("nameA0"));
assertThat(cookiesX[3].getValue(),is("B1"));
cutter.reset();
cutter.addCookieField("nameA0=A0; nameA1=A1");
Cookie[] cookiesY = cutter.getCookies();
assertThat(cookiesY.length,is(2));
assertThat(cookiesY[0].getName(),is("nameA0"));
assertThat(cookiesY[1].getValue(),is("A1"));
assertThat(cookiesX, Matchers.not(Matchers.sameInstance(cookiesY)));
}
@Test
public void testCacheOver()
{
Cookies cutter = new Cookies();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesX = cutter.getCookies();
assertThat(cookiesX.length, is(4));
assertThat(cookiesX[0].getName(), is("nameA0"));
assertThat(cookiesX[3].getValue(), is("B1"));
cutter.reset();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
cutter.addCookieField("nameC0=C0; nameC1=C1");
Cookie[] cookiesY = cutter.getCookies();
assertThat(cookiesY.length, is(6));
assertThat(cookiesY[0].getName(), is("nameA0"));
assertThat(cookiesY[5].getValue(), is("C1"));
assertThat(cookiesX, Matchers.not(Matchers.sameInstance(cookiesY)));
}
@Test
public void testCacheReset()
{
Cookies cutter = new Cookies();
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesX = cutter.getCookies();
assertThat(cookiesX.length,is(4));
assertThat(cookiesX[0].getName(),is("nameA0"));
assertThat(cookiesX[3].getValue(),is("B1"));
cutter.reset();
assertThat(cutter.getCookies().length,is(0));
cutter.addCookieField("nameA0=A0; nameA1=A1");
cutter.addCookieField("nameB0=B0; nameB1=B1");
Cookie[] cookiesY = cutter.getCookies();
assertThat(cookiesY.length,is(4));
assertThat(cookiesY[0].getName(),is("nameA0"));
assertThat(cookiesY[3].getValue(),is("B1"));
assertThat(cookiesX, Matchers.not(Matchers.sameInstance(cookiesY)));
}
}