Fix/jetty 10 9334 review cookie cutter (#9339)
Cookie cleanup + New Cookie parser with clearer focus on RFC6265. + Better compliance modes for RFC2965 + Introduced CookieParser interface so that old and new parsers can coexist and be selected by compliance mode. --------- Signed-off-by: Simone Bordet <simone.bordet@gmail.com> Signed-off-by: Greg Wilkins <gregw@webtide.com> Co-authored-by: Greg Wilkins <gregw@webtide.com> Co-authored-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
c2a9768d25
commit
7b8c2c1bf0
|
@ -25,8 +25,10 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static java.util.EnumSet.allOf;
|
||||
import static java.util.EnumSet.complementOf;
|
||||
import static java.util.EnumSet.copyOf;
|
||||
import static java.util.EnumSet.noneOf;
|
||||
import static java.util.EnumSet.of;
|
||||
|
||||
/**
|
||||
* The compliance mode for Cookie handling.
|
||||
|
@ -38,14 +40,61 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
public enum Violation implements ComplianceViolation
|
||||
{
|
||||
/**
|
||||
* Allow a comma as part of a cookie value
|
||||
* A comma was found in a cookie value.
|
||||
*
|
||||
* @deprecated Use SPECIAL_CHARS_IN_QUOTES
|
||||
*/
|
||||
COMMA_NOT_VALID_OCTET("https://tools.ietf.org/html/rfc6265#section-4.1.1", "Comma not valid as cookie-octet or separator"),
|
||||
@Deprecated
|
||||
COMMA_NOT_VALID_OCTET("https://tools.ietf.org/html/rfc6265#section-4.2.1", "Comma not valid as cookie-octet or separator"),
|
||||
|
||||
/**
|
||||
* Allow cookies to have $ prefixed reserved parameters
|
||||
* A comma was found as separator between cookies.
|
||||
*/
|
||||
RESERVED_NAMES_NOT_DOLLAR_PREFIXED("https://tools.ietf.org/html/rfc6265#section-4.1.1", "Reserved names no longer use '$' prefix");
|
||||
COMMA_SEPARATOR("https://www.rfc-editor.org/rfc/rfc2965.html", "Comma cookie separator"),
|
||||
|
||||
/**
|
||||
* @deprecated no replacement because was mistakenly considered a violation
|
||||
*/
|
||||
@Deprecated
|
||||
RESERVED_NAMES_NOT_DOLLAR_PREFIXED("https://tools.ietf.org/html/rfc6265#section-4.2.1", "Reserved name no longer use '$' prefix"),
|
||||
|
||||
/**
|
||||
* Special characters were found in a quoted cookie value.
|
||||
*/
|
||||
SPECIAL_CHARS_IN_QUOTES("https://www.rfc-editor.org/rfc/rfc6265#section-4.2.1", "Special character cannot be quoted"),
|
||||
|
||||
/**
|
||||
* A backslash was found in a quoted cookie value.
|
||||
*/
|
||||
ESCAPE_IN_QUOTES("https://www.rfc-editor.org/rfc/rfc2616#section-2.2", "Escaped character in quotes"),
|
||||
|
||||
/**
|
||||
* Quotes are not balanced or are embedded in value.
|
||||
*/
|
||||
BAD_QUOTES("https://www.rfc-editor.org/rfc/rfc2616#section-2.2", "Bad quotes"),
|
||||
|
||||
/**
|
||||
* An invalid cookie was found, without a more specific violation.
|
||||
* When this violation is not allowed, an exception is thrown.
|
||||
*/
|
||||
INVALID_COOKIES("https://tools.ietf.org/html/rfc6265", "Invalid cookie"),
|
||||
|
||||
/**
|
||||
* A cookie attribute was found.
|
||||
* The attribute value is retained only if {@link #ATTRIBUTE_VALUES} is allowed.
|
||||
*/
|
||||
ATTRIBUTES("https://www.rfc-editor.org/rfc/rfc6265#section-4.2.1", "Cookie attribute"),
|
||||
|
||||
/**
|
||||
* A cookie attribute value was found and its value is retained.
|
||||
* Allowing {@code ATTRIBUTE_VALUE} implies allowing {@link #ATTRIBUTES}.
|
||||
*/
|
||||
ATTRIBUTE_VALUES("https://www.rfc-editor.org/rfc/rfc6265#section-4.2.1", "Cookie attribute value"),
|
||||
|
||||
/**
|
||||
* Whitespace was found around the cookie name and/or around the cookie value.
|
||||
*/
|
||||
OPTIONAL_WHITE_SPACE("https://www.rfc-editor.org/rfc/rfc6265#section-5.2", "White space around name/value");
|
||||
|
||||
private final String url;
|
||||
private final String description;
|
||||
|
@ -75,17 +124,58 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A CookieCompliance mode that enforces <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> compliance,
|
||||
* but allows:</p>
|
||||
* <ul>
|
||||
* <li>{@link Violation#ATTRIBUTES}</li>
|
||||
* <li>{@link Violation#INVALID_COOKIES}</li>
|
||||
* <li>{@link Violation#OPTIONAL_WHITE_SPACE}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final CookieCompliance RFC6265 = new CookieCompliance("RFC6265", of(
|
||||
Violation.ATTRIBUTES, Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE)
|
||||
);
|
||||
|
||||
/**
|
||||
* A CookieCompliance mode that enforces <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> compliance.
|
||||
*/
|
||||
public static final CookieCompliance RFC6265 = new CookieCompliance("RFC6265", noneOf(Violation.class));
|
||||
public static final CookieCompliance RFC6265_STRICT = new CookieCompliance("RFC6265_STRICT", noneOf(Violation.class));
|
||||
|
||||
/**
|
||||
* <p>A CookieCompliance mode that enforces <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> compliance,
|
||||
* but allows:</p>
|
||||
* <ul>
|
||||
* <li>{@link Violation#BAD_QUOTES}</li>
|
||||
* <li>{@link Violation#ESCAPE_IN_QUOTES}</li>
|
||||
* <li>{@link Violation#INVALID_COOKIES}</li>
|
||||
* <li>{@link Violation#OPTIONAL_WHITE_SPACE}</li>
|
||||
* <li>{@link Violation#SPECIAL_CHARS_IN_QUOTES}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final CookieCompliance RFC6265_LEGACY = new CookieCompliance("RFC6265_LEGACY", of(
|
||||
Violation.BAD_QUOTES, Violation.ESCAPE_IN_QUOTES, Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE, Violation.SPECIAL_CHARS_IN_QUOTES)
|
||||
);
|
||||
|
||||
/**
|
||||
* A CookieCompliance mode that allows <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a> compliance.
|
||||
*/
|
||||
public static final CookieCompliance RFC2965 = new CookieCompliance("RFC2965", allOf(Violation.class));
|
||||
public static final CookieCompliance RFC2965_LEGACY = new CookieCompliance("RFC2965_LEGACY", allOf(Violation.class));
|
||||
|
||||
private static final List<CookieCompliance> KNOWN_MODES = Arrays.asList(RFC6265, RFC2965);
|
||||
/**
|
||||
* A CookieCompliance mode that allows <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a> compliance
|
||||
* but does <b>not</b> allow:
|
||||
* <ul>
|
||||
* <li>{@link Violation#BAD_QUOTES}</li>
|
||||
* <li>{@link Violation#COMMA_NOT_VALID_OCTET}</li>
|
||||
* <li>{@link Violation#RESERVED_NAMES_NOT_DOLLAR_PREFIXED}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final CookieCompliance RFC2965 = new CookieCompliance("RFC2965", complementOf(of(
|
||||
Violation.BAD_QUOTES, Violation.COMMA_NOT_VALID_OCTET, Violation.RESERVED_NAMES_NOT_DOLLAR_PREFIXED)
|
||||
));
|
||||
|
||||
private static final List<CookieCompliance> KNOWN_MODES = Arrays.asList(RFC6265, RFC6265_STRICT, RFC6265_LEGACY, RFC2965, RFC2965_LEGACY);
|
||||
private static final AtomicInteger __custom = new AtomicInteger();
|
||||
|
||||
public static CookieCompliance valueOf(String name)
|
||||
|
@ -166,7 +256,7 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
private final String _name;
|
||||
private final Set<Violation> _violations;
|
||||
|
||||
private CookieCompliance(String name, Set<Violation> violations)
|
||||
public CookieCompliance(String name, Set<Violation> violations)
|
||||
{
|
||||
_name = name;
|
||||
_violations = unmodifiableSet(copyOf(Objects.requireNonNull(violations)));
|
||||
|
@ -195,4 +285,9 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
{
|
||||
return _violations;
|
||||
}
|
||||
|
||||
public boolean compliesWith(CookieCompliance mode)
|
||||
{
|
||||
return this == mode || getAllowed().containsAll(mode.getAllowed());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,32 +13,47 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.BAD_QUOTES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.COMMA_NOT_VALID_OCTET;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.ESCAPE_IN_QUOTES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.INVALID_COOKIES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.RESERVED_NAMES_NOT_DOLLAR_PREFIXED;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.SPECIAL_CHARS_IN_QUOTES;
|
||||
|
||||
/**
|
||||
* Cookie parser
|
||||
*/
|
||||
public abstract class CookieCutter
|
||||
@Deprecated
|
||||
public class CookieCutter implements CookieParser
|
||||
{
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(CookieCutter.class);
|
||||
|
||||
protected final CookieCompliance _complianceMode;
|
||||
private final CookieParser.Handler _handler;
|
||||
private final CookieCompliance _complianceMode;
|
||||
private final ComplianceViolation.Listener _complianceListener;
|
||||
|
||||
protected CookieCutter(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||
public CookieCutter(CookieParser.Handler handler, CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||
{
|
||||
_handler = handler;
|
||||
_complianceMode = compliance;
|
||||
_complianceListener = complianceListener;
|
||||
}
|
||||
|
||||
protected void parseFields(List<String> rawFields)
|
||||
@Override
|
||||
public void parseField(String field)
|
||||
{
|
||||
parseFields(Collections.singletonList(field));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseFields(List<String> rawFields)
|
||||
{
|
||||
StringBuilder unquoted = null;
|
||||
|
||||
|
@ -93,19 +108,33 @@ public abstract class CookieCutter
|
|||
break;
|
||||
|
||||
case '\\':
|
||||
if (_complianceMode.allows(ESCAPE_IN_QUOTES))
|
||||
reportComplianceViolation(ESCAPE_IN_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
escaped = true;
|
||||
continue;
|
||||
|
||||
case 0:
|
||||
// unterminated quote, let's ignore quotes
|
||||
if (_complianceMode.allows(BAD_QUOTES))
|
||||
reportComplianceViolation(BAD_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
unquoted.setLength(0);
|
||||
inQuoted = false;
|
||||
i--;
|
||||
continue;
|
||||
|
||||
default:
|
||||
if (isRFC6265RejectedCharacter(c))
|
||||
{
|
||||
if (_complianceMode.allows(SPECIAL_CHARS_IN_QUOTES))
|
||||
reportComplianceViolation(SPECIAL_CHARS_IN_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
}
|
||||
unquoted.append(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -128,6 +157,10 @@ public abstract class CookieCutter
|
|||
if (quoted)
|
||||
{
|
||||
// must have been a bad internal quote. let's fix as best we can
|
||||
if (_complianceMode.allows(BAD_QUOTES))
|
||||
reportComplianceViolation(BAD_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
unquoted.append(hdr, tokenstart, i--);
|
||||
inQuoted = true;
|
||||
quoted = false;
|
||||
|
@ -157,7 +190,7 @@ public abstract class CookieCutter
|
|||
|
||||
try
|
||||
{
|
||||
if (name.startsWith("$"))
|
||||
if (name != null && name.startsWith("$"))
|
||||
{
|
||||
if (RESERVED_NAMES_NOT_DOLLAR_PREFIXED.isAllowedBy(_complianceMode))
|
||||
{
|
||||
|
@ -187,11 +220,18 @@ public abstract class CookieCutter
|
|||
// This is a new cookie, so add the completed last cookie if we have one
|
||||
if (cookieName != null)
|
||||
{
|
||||
if (!reject)
|
||||
if (reject)
|
||||
{
|
||||
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
|
||||
reject = false;
|
||||
if (_complianceMode.allows(INVALID_COOKIES))
|
||||
reportComplianceViolation(INVALID_COOKIES, hdr);
|
||||
else
|
||||
throw new IllegalArgumentException("Bad Cookie");
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.addCookie(cookieName, cookieValue, cookieVersion, cookieDomain, cookiePath, cookieComment);
|
||||
}
|
||||
reject = false;
|
||||
cookieDomain = null;
|
||||
cookiePath = null;
|
||||
cookieComment = null;
|
||||
|
@ -227,24 +267,27 @@ public abstract class CookieCutter
|
|||
if (quoted)
|
||||
{
|
||||
// must have been a bad internal quote. let's fix as best we can
|
||||
if (_complianceMode.allows(BAD_QUOTES))
|
||||
reportComplianceViolation(BAD_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
unquoted.append(hdr, tokenstart, i--);
|
||||
inQuoted = true;
|
||||
quoted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_complianceMode == CookieCompliance.RFC6265)
|
||||
if (isRFC6265RejectedCharacter(c))
|
||||
{
|
||||
if (isRFC6265RejectedCharacter(inQuoted, c))
|
||||
{
|
||||
if (c < 128 && _complianceMode.allows(SPECIAL_CHARS_IN_QUOTES))
|
||||
reportComplianceViolation(SPECIAL_CHARS_IN_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenstart < 0)
|
||||
tokenstart = i;
|
||||
tokenend = i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -287,68 +330,69 @@ public abstract class CookieCutter
|
|||
if (quoted)
|
||||
{
|
||||
// must have been a bad internal quote. let's fix as best we can
|
||||
if (_complianceMode.allows(BAD_QUOTES))
|
||||
reportComplianceViolation(BAD_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
unquoted.append(hdr, tokenstart, i--);
|
||||
inQuoted = true;
|
||||
quoted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_complianceMode == CookieCompliance.RFC6265)
|
||||
if (isRFC6265RejectedCharacter(c))
|
||||
{
|
||||
if (isRFC6265RejectedCharacter(inQuoted, c))
|
||||
{
|
||||
if (_complianceMode.allows(SPECIAL_CHARS_IN_QUOTES))
|
||||
reportComplianceViolation(SPECIAL_CHARS_IN_QUOTES, hdr);
|
||||
else
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenstart < 0)
|
||||
tokenstart = i;
|
||||
tokenend = i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieName != null && !reject)
|
||||
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
|
||||
if (cookieName != null)
|
||||
{
|
||||
if (reject)
|
||||
{
|
||||
if (_complianceMode.allows(INVALID_COOKIES))
|
||||
reportComplianceViolation(INVALID_COOKIES, hdr);
|
||||
else
|
||||
throw new IllegalArgumentException("Bad Cookie");
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.addCookie(cookieName, cookieValue, cookieVersion, cookieDomain, cookiePath, cookieComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void reportComplianceViolation(CookieCompliance.Violation violation, String reason)
|
||||
{
|
||||
if (_complianceListener != null)
|
||||
{
|
||||
_complianceListener.onComplianceViolation(_complianceMode, violation, reason);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment);
|
||||
|
||||
protected boolean isRFC6265RejectedCharacter(boolean inQuoted, char c)
|
||||
protected boolean isRFC6265RejectedCharacter(char c)
|
||||
{
|
||||
if (inQuoted)
|
||||
{
|
||||
// We only reject if a Control Character is encountered
|
||||
if (Character.isISOControl(c))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* From RFC6265 - Section 4.1.1 - Syntax
|
||||
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
* ; US-ASCII characters excluding CTLs,
|
||||
* ; whitespace DQUOTE, comma, semicolon,
|
||||
* ; and backslash
|
||||
*/
|
||||
return Character.isISOControl(c) || // control characters
|
||||
c > 127 || // 8-bit characters
|
||||
c == ',' || // comma
|
||||
c == ';'; // semicolon
|
||||
}
|
||||
|
||||
return false;
|
||||
/* From RFC6265 - Section 4.1.1 - Syntax
|
||||
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
* ; US-ASCII characters excluding CTLs,
|
||||
* ; whitespace DQUOTE, comma, semicolon,
|
||||
* ; and backslash
|
||||
*/
|
||||
return Character.isISOControl(c) || // control characters
|
||||
c > 127 || // 8-bit characters
|
||||
c == ' ' || // whitespace
|
||||
c == '"' || // DQUOTE
|
||||
c == ',' || // comma
|
||||
c == ';' || // semicolon
|
||||
c == '\\'; // backslash
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.BAD_QUOTES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.Listener;
|
||||
|
||||
/**
|
||||
* <p>Cookie parser.</p>
|
||||
* <p>An interface for variations of a cookie parser.</p>
|
||||
*/
|
||||
public interface CookieParser
|
||||
{
|
||||
/**
|
||||
* <p>A factory method to create a new parser suitable for the compliance mode.</p>
|
||||
* @param compliance The compliance mode to use for parsing.
|
||||
* @param complianceListener A listener for compliance violations or null.
|
||||
* @return A CookieParser instance.
|
||||
*/
|
||||
static CookieParser newParser(Handler handler, CookieCompliance compliance, Listener complianceListener)
|
||||
{
|
||||
// The RFC6265CookieParser is primarily a RFC6265 parser, but it can handle most
|
||||
// defined "violations" so that it effectively becomes a RFC2965 parser. However, it
|
||||
// cannot forgive bad quotes. Thus, we use the legacy CookieCutter parser only if
|
||||
// the compliance mode requires BAD QUOTES.
|
||||
if (compliance.allows(BAD_QUOTES))
|
||||
return new CookieCutter(handler, compliance, complianceListener);
|
||||
return new RFC6265CookieParser(handler, compliance, complianceListener);
|
||||
}
|
||||
|
||||
void parseField(String field);
|
||||
|
||||
default void parseFields(List<String> rawFields)
|
||||
{
|
||||
// For each cookie field
|
||||
for (String field : rawFields)
|
||||
parseField(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler of parsed cookies.
|
||||
*/
|
||||
interface Handler
|
||||
{
|
||||
void addCookie(String name, String value, int version, String domain, String path, String comment);
|
||||
}
|
||||
}
|
|
@ -281,11 +281,9 @@ public class HttpCookie
|
|||
|
||||
public String getSetCookie(CookieCompliance compliance)
|
||||
{
|
||||
if (compliance == CookieCompliance.RFC6265)
|
||||
if (CookieCompliance.RFC6265.compliesWith(compliance))
|
||||
return getRFC6265SetCookie();
|
||||
if (compliance == CookieCompliance.RFC2965)
|
||||
return getRFC2965SetCookie();
|
||||
throw new IllegalStateException();
|
||||
return getRFC2965SetCookie();
|
||||
}
|
||||
|
||||
public String getRFC2965SetCookie()
|
||||
|
|
|
@ -441,8 +441,7 @@ public class HttpParser
|
|||
private HttpTokens.Token next(ByteBuffer buffer)
|
||||
{
|
||||
byte ch = buffer.get();
|
||||
|
||||
HttpTokens.Token t = HttpTokens.TOKENS[0xff & ch];
|
||||
HttpTokens.Token t = HttpTokens.getToken(ch);
|
||||
|
||||
switch (t.getType())
|
||||
{
|
||||
|
|
|
@ -47,6 +47,16 @@ public class HttpTokens
|
|||
OTEXT // Obsolete text
|
||||
}
|
||||
|
||||
public static Token getToken(byte b)
|
||||
{
|
||||
return TOKENS[0xFF & b];
|
||||
}
|
||||
|
||||
public static Token getToken(char c)
|
||||
{
|
||||
return c <= 0xFF ? TOKENS[c] : null;
|
||||
}
|
||||
|
||||
public static class Token
|
||||
{
|
||||
private final Type _type;
|
||||
|
@ -54,6 +64,10 @@ public class HttpTokens
|
|||
private final char _c;
|
||||
private final int _x;
|
||||
|
||||
private final boolean _rfc2616Token;
|
||||
|
||||
private final boolean _rfc6265CookieOctet;
|
||||
|
||||
private Token(byte b, Type type)
|
||||
{
|
||||
_type = type;
|
||||
|
@ -61,6 +75,30 @@ public class HttpTokens
|
|||
_c = (char)(0xff & b);
|
||||
char lc = (_c >= 'A' & _c <= 'Z') ? ((char)(_c - 'A' + 'a')) : _c;
|
||||
_x = (_type == Type.DIGIT || _type == Type.ALPHA && lc >= 'a' && lc <= 'f') ? TypeUtil.convertHexDigit(b) : -1;
|
||||
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// separators = "(" | ")" | "<" | ">" | "@"
|
||||
// | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "="
|
||||
// | "{" | "}" | SP | HT
|
||||
// CTL = <any US-ASCII control character
|
||||
// (octets 0 - 31) and DEL (127)>
|
||||
_rfc2616Token = b >= 32 && b < 127 &&
|
||||
b != '(' && b != ')' && b != '<' && b != '>' && b != '@' &&
|
||||
b != ',' && b != ';' && b != ':' && b != '\\' && b != '"' &&
|
||||
b != '/' && b != '[' && b != ']' && b != '?' && b != '=' &&
|
||||
b != '{' && b != '}' && b != ' ';
|
||||
|
||||
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
// ; US-ASCII characters excluding CTLs,
|
||||
// ; whitespace DQUOTE, comma, semicolon,
|
||||
// ; and backslash
|
||||
_rfc6265CookieOctet =
|
||||
b == 0x21 ||
|
||||
b >= 0x23 && b <= 0x2B ||
|
||||
b >= 0x2D && b <= 0x3A ||
|
||||
b >= 0x3C && b <= 0x5B ||
|
||||
b >= 0x5D && b <= 0x7E;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
|
@ -83,6 +121,16 @@ public class HttpTokens
|
|||
return _x >= 0;
|
||||
}
|
||||
|
||||
public boolean isRfc2616Token()
|
||||
{
|
||||
return _rfc2616Token;
|
||||
}
|
||||
|
||||
public boolean isRfc6265CookieOctet()
|
||||
{
|
||||
return _rfc6265CookieOctet;
|
||||
}
|
||||
|
||||
public int getHexDigit()
|
||||
{
|
||||
return _x;
|
||||
|
|
|
@ -0,0 +1,421 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.ATTRIBUTES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.ATTRIBUTE_VALUES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.COMMA_NOT_VALID_OCTET;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.COMMA_SEPARATOR;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.ESCAPE_IN_QUOTES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.INVALID_COOKIES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.OPTIONAL_WHITE_SPACE;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.SPECIAL_CHARS_IN_QUOTES;
|
||||
|
||||
/**
|
||||
* Cookie parser
|
||||
*/
|
||||
public class RFC6265CookieParser implements CookieParser
|
||||
{
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(RFC6265CookieParser.class);
|
||||
|
||||
private final CookieParser.Handler _handler;
|
||||
private final CookieCompliance _complianceMode;
|
||||
private final ComplianceViolation.Listener _complianceListener;
|
||||
|
||||
protected RFC6265CookieParser(CookieParser.Handler handler, CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||
{
|
||||
_handler = handler;
|
||||
_complianceMode = compliance;
|
||||
_complianceListener = complianceListener;
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
START,
|
||||
IN_NAME,
|
||||
AFTER_NAME,
|
||||
VALUE,
|
||||
IN_VALUE,
|
||||
IN_QUOTED_VALUE,
|
||||
ESCAPED_VALUE,
|
||||
AFTER_QUOTED_VALUE,
|
||||
END,
|
||||
INVALID_COOKIE
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseField(String field)
|
||||
{
|
||||
State state = State.START;
|
||||
|
||||
String attributeName = null;
|
||||
String value = null;
|
||||
String cookieName = null;
|
||||
String cookieValue = null;
|
||||
String cookiePath = null;
|
||||
String cookieDomain = null;
|
||||
String cookieComment = null;
|
||||
int cookieVersion = 0;
|
||||
boolean cookieInvalid = false;
|
||||
|
||||
int length = field.length();
|
||||
StringBuilder string = new StringBuilder();
|
||||
for (int i = 0; i <= length; i++)
|
||||
{
|
||||
char c = i == length ? ';' : field.charAt(i);
|
||||
HttpTokens.Token token = HttpTokens.getToken(c);
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
if (!_complianceMode.allows(INVALID_COOKIES))
|
||||
throw new IllegalArgumentException("Invalid Cookie character");
|
||||
state = State.INVALID_COOKIE;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case START:
|
||||
if (c == ' ' || c == '\t' || c == ';')
|
||||
continue;
|
||||
|
||||
string.setLength(0);
|
||||
|
||||
if (token.isRfc2616Token())
|
||||
{
|
||||
if (!StringUtil.isBlank(cookieName) && c != '$')
|
||||
{
|
||||
_handler.addCookie(cookieName, cookieValue, cookieVersion, cookieDomain, cookiePath, cookieComment);
|
||||
cookieName = null;
|
||||
cookieValue = null;
|
||||
cookieDomain = null;
|
||||
cookiePath = null;
|
||||
cookieComment = null;
|
||||
}
|
||||
|
||||
string.append(c);
|
||||
state = State.IN_NAME;
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie name");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case IN_NAME:
|
||||
if (c == '=')
|
||||
{
|
||||
if (string.charAt(0) == '$')
|
||||
attributeName = string.toString();
|
||||
else
|
||||
cookieName = string.toString();
|
||||
state = State.VALUE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c == ' ' || c == '\t') && _complianceMode.allows(OPTIONAL_WHITE_SPACE))
|
||||
{
|
||||
reportComplianceViolation(OPTIONAL_WHITE_SPACE, field);
|
||||
if (string.charAt(0) == '$')
|
||||
attributeName = string.toString();
|
||||
else
|
||||
cookieName = string.toString();
|
||||
state = State.AFTER_NAME;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.isRfc2616Token())
|
||||
{
|
||||
string.append(c);
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = c == ';' ? State.START : State.INVALID_COOKIE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie name");
|
||||
}
|
||||
break;
|
||||
|
||||
case AFTER_NAME:
|
||||
if (c == '=')
|
||||
{
|
||||
state = State.VALUE;
|
||||
continue;
|
||||
}
|
||||
if (c == ';' || c == ',')
|
||||
{
|
||||
state = State.START;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie");
|
||||
}
|
||||
break;
|
||||
|
||||
case VALUE:
|
||||
if (c == ' ' && _complianceMode.allows(OPTIONAL_WHITE_SPACE))
|
||||
{
|
||||
reportComplianceViolation(OPTIONAL_WHITE_SPACE, field);
|
||||
continue;
|
||||
}
|
||||
|
||||
string.setLength(0);
|
||||
if (c == '"')
|
||||
{
|
||||
state = State.IN_QUOTED_VALUE;
|
||||
}
|
||||
else if (c == ';')
|
||||
{
|
||||
value = "";
|
||||
i--;
|
||||
state = State.END;
|
||||
}
|
||||
else if (token.isRfc6265CookieOctet())
|
||||
{
|
||||
string.append(c);
|
||||
state = State.IN_VALUE;
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie value");
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_VALUE:
|
||||
if (c == ';' || c == ',' || c == ' ' || c == '\t')
|
||||
{
|
||||
value = string.toString();
|
||||
i--;
|
||||
state = State.END;
|
||||
}
|
||||
else if (token.isRfc6265CookieOctet())
|
||||
{
|
||||
string.append(c);
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie value");
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_QUOTED_VALUE:
|
||||
if (c == '"')
|
||||
{
|
||||
value = string.toString();
|
||||
state = State.AFTER_QUOTED_VALUE;
|
||||
}
|
||||
else if (c == '\\' && _complianceMode.allows(ESCAPE_IN_QUOTES))
|
||||
{
|
||||
state = State.ESCAPED_VALUE;
|
||||
}
|
||||
else if (token.isRfc6265CookieOctet())
|
||||
{
|
||||
string.append(c);
|
||||
}
|
||||
else if (_complianceMode.allows(SPECIAL_CHARS_IN_QUOTES))
|
||||
{
|
||||
reportComplianceViolation(SPECIAL_CHARS_IN_QUOTES, field);
|
||||
string.append(c);
|
||||
}
|
||||
else if (c == ',' && _complianceMode.allows(COMMA_NOT_VALID_OCTET))
|
||||
{
|
||||
reportComplianceViolation(COMMA_NOT_VALID_OCTET, field);
|
||||
string.append(c);
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
string.append(c);
|
||||
if (!cookieInvalid)
|
||||
{
|
||||
cookieInvalid = true;
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
}
|
||||
// Try to find the closing double quote by staying in the current state.
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie quoted value");
|
||||
}
|
||||
break;
|
||||
|
||||
case ESCAPED_VALUE:
|
||||
string.append(c);
|
||||
state = State.IN_QUOTED_VALUE;
|
||||
break;
|
||||
|
||||
case AFTER_QUOTED_VALUE:
|
||||
if (c == ';' || c == ',' || c == ' ' || c == '\t')
|
||||
{
|
||||
i--;
|
||||
state = cookieInvalid ? State.INVALID_COOKIE : State.END;
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Bad Cookie quoted value");
|
||||
}
|
||||
break;
|
||||
|
||||
case END:
|
||||
if (c == ';')
|
||||
{
|
||||
state = State.START;
|
||||
}
|
||||
else if (c == ',')
|
||||
{
|
||||
if (_complianceMode.allows(COMMA_SEPARATOR))
|
||||
{
|
||||
reportComplianceViolation(COMMA_SEPARATOR, field);
|
||||
state = State.START;
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Comma cookie separator");
|
||||
}
|
||||
}
|
||||
else if ((c == ' ' || c == '\t') && _complianceMode.allows(OPTIONAL_WHITE_SPACE))
|
||||
{
|
||||
reportComplianceViolation(OPTIONAL_WHITE_SPACE, field);
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean knownAttribute = true;
|
||||
if (StringUtil.isBlank(attributeName))
|
||||
{
|
||||
cookieValue = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have an attribute.
|
||||
if (_complianceMode.allows(ATTRIBUTE_VALUES))
|
||||
{
|
||||
reportComplianceViolation(ATTRIBUTES, field);
|
||||
switch (attributeName.toLowerCase(Locale.ENGLISH))
|
||||
{
|
||||
case "$path":
|
||||
cookiePath = value;
|
||||
break;
|
||||
case "$domain":
|
||||
cookieDomain = value;
|
||||
break;
|
||||
case "$port":
|
||||
cookieComment = "$port=" + value;
|
||||
break;
|
||||
case "$version":
|
||||
cookieVersion = Integer.parseInt(value);
|
||||
break;
|
||||
default:
|
||||
knownAttribute = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (_complianceMode.allows(ATTRIBUTES))
|
||||
{
|
||||
reportComplianceViolation(ATTRIBUTES, field);
|
||||
}
|
||||
else if (_complianceMode.allows(INVALID_COOKIES))
|
||||
{
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid Cookie with attributes");
|
||||
}
|
||||
attributeName = null;
|
||||
}
|
||||
value = null;
|
||||
|
||||
if (!knownAttribute)
|
||||
{
|
||||
if (!_complianceMode.allows(INVALID_COOKIES))
|
||||
throw new IllegalArgumentException("Invalid Cookie attribute");
|
||||
reportComplianceViolation(INVALID_COOKIES, field);
|
||||
state = State.INVALID_COOKIE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state == State.END)
|
||||
throw new IllegalStateException("Invalid cookie");
|
||||
break;
|
||||
|
||||
case INVALID_COOKIE:
|
||||
attributeName = null;
|
||||
value = null;
|
||||
cookieName = null;
|
||||
cookieValue = null;
|
||||
cookiePath = null;
|
||||
cookieDomain = null;
|
||||
cookieComment = null;
|
||||
cookieInvalid = false;
|
||||
if (c == ';')
|
||||
state = State.START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cookieInvalid && !StringUtil.isBlank(cookieName))
|
||||
_handler.addCookie(cookieName, cookieValue, cookieVersion, cookieDomain, cookiePath, cookieComment);
|
||||
}
|
||||
|
||||
protected void reportComplianceViolation(CookieCompliance.Violation violation, String reason)
|
||||
{
|
||||
if (_complianceListener != null)
|
||||
_complianceListener.onComplianceViolation(_complianceMode, violation, reason);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -64,6 +63,8 @@ public class CookieCutterLenientTest
|
|||
|
||||
// lenient with spaces and EOF
|
||||
Arguments.of("abc=", "abc", ""),
|
||||
Arguments.of("abc= ", "abc", ""),
|
||||
Arguments.of("abc= x", "abc", "x"),
|
||||
Arguments.of("abc = ", "abc", ""),
|
||||
Arguments.of("abc = ;", "abc", ""),
|
||||
Arguments.of("abc = ; ", "abc", ""),
|
||||
|
@ -173,28 +174,27 @@ public class CookieCutterLenientTest
|
|||
}
|
||||
}
|
||||
|
||||
class TestCutter extends CookieCutter
|
||||
static class TestCutter implements CookieParser.Handler
|
||||
{
|
||||
CookieCutter cutter;
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
protected TestCutter()
|
||||
{
|
||||
super(CookieCompliance.RFC6265, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment)
|
||||
{
|
||||
names.add(cookieName);
|
||||
values.add(cookieValue);
|
||||
cutter = new CookieCutter(this, CookieCompliance.RFC6265_LEGACY, null);
|
||||
}
|
||||
|
||||
public void parseField(String field)
|
||||
{
|
||||
super.parseFields(Collections.singletonList(field));
|
||||
cutter.parseField(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCookie(String cookieName, String cookieValue, int cookieVersion, String cookieDomain, String cookiePath, String cookieComment)
|
||||
{
|
||||
names.add(cookieName);
|
||||
values.add(cookieValue);
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public class CookieCutterTest
|
|||
{
|
||||
String rawCookie = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
|
@ -70,7 +70,7 @@ public class CookieCutterTest
|
|||
"Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " +
|
||||
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
|
@ -88,7 +88,7 @@ public class CookieCutterTest
|
|||
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; " +
|
||||
"Shipping=\"FedEx\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(3));
|
||||
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
|
@ -106,7 +106,7 @@ public class CookieCutterTest
|
|||
"Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " +
|
||||
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "Part_Number", "Riding_Rocket_0023", 1, "/acme/ammo");
|
||||
|
@ -123,7 +123,7 @@ public class CookieCutterTest
|
|||
"session_id=\"1234\"; " +
|
||||
"session_id=\"1111\"; $Domain=\".cracker.edu\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
|
||||
|
@ -139,13 +139,13 @@ public class CookieCutterTest
|
|||
String rawCookie = "$Version=\"1\"; session_id=\"1234\", " +
|
||||
"$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
|
||||
Cookie[] cookies /* = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 1, null);
|
||||
|
||||
cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
cookies */ = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "session_id", "1234\", $Version=\"1", 0, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 0, null);
|
||||
|
@ -159,7 +159,7 @@ public class CookieCutterTest
|
|||
{
|
||||
String rawCookie = "SID=31d4d96e407aad42";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "SID", "31d4d96e407aad42", 0, null);
|
||||
|
@ -173,7 +173,7 @@ public class CookieCutterTest
|
|||
{
|
||||
String rawCookie = "SID=31d4d96e407aad42; lang=en-US";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "SID", "31d4d96e407aad42", 0, null);
|
||||
|
@ -188,7 +188,7 @@ public class CookieCutterTest
|
|||
{
|
||||
String rawCookie = "key=value";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "key", "value", 0, null);
|
||||
|
@ -202,7 +202,7 @@ public class CookieCutterTest
|
|||
{
|
||||
String rawCookie = "$key=value";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(0));
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ public class CookieCutterTest
|
|||
|
||||
// The first cookie "testcookie" should be ignored, per RFC6265, as it's missing the "=" sign.
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "server.id", "abcd", 0, null);
|
||||
|
@ -228,7 +228,7 @@ public class CookieCutterTest
|
|||
Arrays.fill(excessive, ';');
|
||||
String rawCookie = "foo=bar; " + excessive + "; xyz=pdq";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "foo", "bar", 0, null);
|
||||
|
@ -285,24 +285,25 @@ public class CookieCutterTest
|
|||
}
|
||||
}
|
||||
|
||||
class TestCutter extends CookieCutter
|
||||
class TestCutter implements CookieParser.Handler
|
||||
{
|
||||
List<Cookie> cookies = new ArrayList<>();
|
||||
CookieCutter cutter;
|
||||
|
||||
public TestCutter(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||
{
|
||||
super(compliance, complianceListener);
|
||||
cutter = new CookieCutter(this, compliance, complianceListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment)
|
||||
public void addCookie(String cookieName, String cookieValue, int cookieVersion, String cookieDomain, String cookiePath, String cookieComment)
|
||||
{
|
||||
cookies.add(new Cookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment));
|
||||
}
|
||||
|
||||
public void parseFields(String... fields)
|
||||
{
|
||||
super.parseFields(Arrays.asList(fields));
|
||||
cutter.parseFields(Arrays.asList(fields));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
public class CookieParserTest
|
||||
{
|
||||
@Test
|
||||
public void testNewCookieParser()
|
||||
{
|
||||
assertThat(CookieParser.newParser(null, CookieCompliance.RFC2965_LEGACY, null), instanceOf(CookieCutter.class));
|
||||
assertThat(CookieParser.newParser(null, CookieCompliance.RFC6265_LEGACY, null), instanceOf(CookieCutter.class));
|
||||
assertThat(CookieParser.newParser(null, new CookieCompliance("custom", EnumSet.of(CookieCompliance.Violation.COMMA_SEPARATOR, CookieCompliance.Violation.BAD_QUOTES)), null), instanceOf(CookieCutter.class));
|
||||
|
||||
assertThat(CookieParser.newParser(null, CookieCompliance.RFC2965, null), instanceOf(RFC6265CookieParser.class));
|
||||
assertThat(CookieParser.newParser(null, CookieCompliance.RFC6265, null), instanceOf(RFC6265CookieParser.class));
|
||||
assertThat(CookieParser.newParser(null, CookieCompliance.RFC6265_STRICT, null), instanceOf(RFC6265CookieParser.class));
|
||||
assertThat(CookieParser.newParser(null, new CookieCompliance("custom", EnumSet.of(CookieCompliance.Violation.COMMA_SEPARATOR)), null), instanceOf(RFC6265CookieParser.class));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class RFC6265CookieParserTest
|
||||
{
|
||||
/**
|
||||
* Example from RFC2109 and RFC2965
|
||||
*/
|
||||
@Test
|
||||
public void testRFC2965Single()
|
||||
{
|
||||
String rawCookie = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"";
|
||||
|
||||
// Test with RFC 2965.
|
||||
TestCookieParser parser = new TestCookieParser(CookieCompliance.RFC2965_LEGACY);
|
||||
List<Cookie> cookies = parser.parseFields(rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.size(), is(1));
|
||||
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
// There are 2 attributes, so 2 violations.
|
||||
assertThat(parser.violations.size(), is(2));
|
||||
|
||||
// Same test with RFC 6265.
|
||||
parser = new TestCookieParser(CookieCompliance.RFC6265);
|
||||
cookies = parser.parseFields(rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.size(), is(1));
|
||||
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 0, null);
|
||||
// There are 2 attributes, so 2 violations.
|
||||
assertThat(parser.violations.size(), is(2));
|
||||
|
||||
// Same test with RFC 6265 strict should throw.
|
||||
TestCookieParser strictParser = new TestCookieParser(CookieCompliance.RFC6265_STRICT);
|
||||
assertThrows(IllegalArgumentException.class, () -> strictParser.parseFields(rawCookie));
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC2109 and RFC2965
|
||||
*/
|
||||
@Test
|
||||
public void testRFC2965Double()
|
||||
{
|
||||
String rawCookie = "$Version=\"1\"; " +
|
||||
"Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " +
|
||||
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 1, "/acme");
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC2109 and RFC2965
|
||||
*/
|
||||
@Test
|
||||
public void testRFCTriple()
|
||||
{
|
||||
String rawCookie = "$Version=\"1\"; " +
|
||||
"Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " +
|
||||
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; " +
|
||||
"Shipping=\"FedEx\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(3));
|
||||
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 1, "/acme");
|
||||
assertCookie("Cookies[2]", cookies[2], "Shipping", "FedEx", 1, "/acme");
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC2109 and RFC2965
|
||||
*/
|
||||
@Test
|
||||
public void testRFCPathExample()
|
||||
{
|
||||
String rawCookie = "$Version=\"1\"; " +
|
||||
"Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " +
|
||||
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "Part_Number", "Riding_Rocket_0023", 1, "/acme/ammo");
|
||||
assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 1, "/acme");
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC2109
|
||||
*/
|
||||
@Test
|
||||
public void testRFC2109CookieSpoofingExample()
|
||||
{
|
||||
String rawCookie = "$Version=\"1\"; " +
|
||||
"session_id=\"1234\"; " +
|
||||
"session_id=\"1111\"; $Domain=\".cracker.edu\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 1, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC2965
|
||||
*/
|
||||
@Test
|
||||
public void testRFC2965CookieSpoofingExample()
|
||||
{
|
||||
String rawCookie = "$Version=\"1\"; session_id=\"1234\", " +
|
||||
"$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 1, null);
|
||||
|
||||
cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[1]", cookies[0], "session_id", "1111", 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC6265
|
||||
*/
|
||||
@Test
|
||||
public void testRFC6265SidExample()
|
||||
{
|
||||
String rawCookie = "SID=31d4d96e407aad42";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "SID", "31d4d96e407aad42", 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example from RFC6265
|
||||
*/
|
||||
@Test
|
||||
public void testRFC6265SidLangExample()
|
||||
{
|
||||
String rawCookie = "SID=31d4d96e407aad42; lang=en-US";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "SID", "31d4d96e407aad42", 0, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "lang", "en-US", 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic name=value, following RFC6265 rules
|
||||
*/
|
||||
@Test
|
||||
public void testKeyValue()
|
||||
{
|
||||
String rawCookie = "key=value";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "key", "value", 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic name=value, following RFC6265 rules
|
||||
*/
|
||||
@Test
|
||||
public void testDollarName()
|
||||
{
|
||||
String rawCookie = "$key=value";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleCookies()
|
||||
{
|
||||
String rawCookie = "testcookie; server.id=abcd; server.detail=cfg";
|
||||
|
||||
// The first cookie "testcookie" should be ignored, per RFC6265, as it's missing the "=" sign.
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "server.id", "abcd", 0, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "server.detail", "cfg", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExcessiveSemicolons()
|
||||
{
|
||||
char[] excessive = new char[65535];
|
||||
Arrays.fill(excessive, ';');
|
||||
String rawCookie = "foo=bar; " + new String(excessive) + "; xyz=pdq";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "foo", "bar", 0, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "xyz", "pdq", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRFC2965QuotedEscape()
|
||||
{
|
||||
String rawCookie = "A=\"double\\\"quote\"";
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "A", "double\"quote", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRFC2965QuotedSpecial()
|
||||
{
|
||||
String rawCookie = "A=\", ;\"";
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "A", ", ;", 0, null);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// $X; N=V
|
||||
// $X=Y; N=V
|
||||
|
||||
public static List<Param> parameters()
|
||||
{
|
||||
return Arrays.asList(
|
||||
new Param("A=1; B=2; C=3", "A=1", "B=2", "C=3"),
|
||||
new Param("A=\"1\"; B=2; C=3", "A=1", "B=2", "C=3"),
|
||||
new Param("A=1 ; B=2; C=3", "A=1", "B=2", "C=3"),
|
||||
new Param("A=\"1; B=2\"; C=3", "C=3"),
|
||||
new Param("A=\"1; B=2; C=3"),
|
||||
new Param("A=\"1 B=2\"; C=3", "C=3"),
|
||||
new Param("A=\"\"1; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=\"\" ; B=2; C=3", "A=", "B=2", "C=3"),
|
||||
new Param("A=1\"\"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=1\"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=1\"1; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A= 1; B=2; C=3", "A=1", "B=2", "C=3"),
|
||||
new Param("A=\" 1\"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=\"1 \"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=1,; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=\"1,\"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=\\1; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=\"\\1\"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=1\u0007; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("A=\"1\u0007\"; B=2; C=3", "B=2", "C=3"),
|
||||
new Param("€"),
|
||||
new Param("@={}"),
|
||||
new Param("$X=Y; N=V", "N=V"),
|
||||
new Param("N=V; $X=Y", "N=V")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
public void testRFC6265Cookie(Param param)
|
||||
{
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, param.input);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(param.expected.size()));
|
||||
for (int i = 0; i < cookies.length; i++)
|
||||
{
|
||||
Cookie cookie = cookies[i];
|
||||
assertThat(cookie.getName() + "=" + cookie.getValue(), is(param.expected.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private Cookie[] parseCookieHeaders(CookieCompliance compliance, String... headers)
|
||||
{
|
||||
TestCookieParser cutter = new TestCookieParser(compliance);
|
||||
for (String header : headers)
|
||||
{
|
||||
cutter.parseFields(header);
|
||||
}
|
||||
return cutter.cookies.toArray(Cookie[]::new);
|
||||
}
|
||||
|
||||
private void assertCookie(String prefix, Cookie cookie,
|
||||
String expectedName,
|
||||
String expectedValue,
|
||||
int expectedVersion,
|
||||
String expectedPath)
|
||||
{
|
||||
assertThat(prefix + ".name", cookie.getName(), is(expectedName));
|
||||
assertThat(prefix + ".value", cookie.getValue(), is(expectedValue));
|
||||
assertThat(prefix + ".version", cookie.getVersion(), is(expectedVersion));
|
||||
assertThat(prefix + ".path", cookie.getPath(), is(expectedPath));
|
||||
}
|
||||
|
||||
private static class TestCookieParser implements ComplianceViolation.Listener, CookieParser.Handler
|
||||
{
|
||||
private final CookieParser parser;
|
||||
private final List<Cookie> cookies = new ArrayList<>();
|
||||
private final List<CookieCompliance.Violation> violations = new ArrayList<>();
|
||||
|
||||
private TestCookieParser(CookieCompliance compliance)
|
||||
{
|
||||
parser = new RFC6265CookieParser(this, compliance, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String details)
|
||||
{
|
||||
violations.add((CookieCompliance.Violation)violation);
|
||||
}
|
||||
|
||||
private List<Cookie> parseFields(String... fields)
|
||||
{
|
||||
parser.parseFields(Arrays.asList(fields));
|
||||
return cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCookie(String cookieName, String cookieValue, int cookieVersion, String cookieDomain, String cookiePath, String cookieComment)
|
||||
{
|
||||
cookies.add(new Cookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Param
|
||||
{
|
||||
private final String input;
|
||||
private final List<String> expected;
|
||||
|
||||
public Param(String input, String... expected)
|
||||
{
|
||||
this.input = input;
|
||||
this.expected = Arrays.asList(expected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return input + " -> " + expected.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ import javax.servlet.http.Cookie;
|
|||
|
||||
import org.eclipse.jetty.http.ComplianceViolation;
|
||||
import org.eclipse.jetty.http.CookieCompliance;
|
||||
import org.eclipse.jetty.http.CookieCutter;
|
||||
import org.eclipse.jetty.http.CookieParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
|
|||
* If the added fields are identical to those last added (as strings), then the
|
||||
* cookies are not re parsed.
|
||||
*/
|
||||
public class Cookies extends CookieCutter
|
||||
public class Cookies implements CookieParser.Handler
|
||||
{
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(Cookies.class);
|
||||
protected final List<String> _rawFields = new ArrayList<>();
|
||||
|
@ -41,6 +41,8 @@ public class Cookies extends CookieCutter
|
|||
private Cookie[] _cookies;
|
||||
private boolean _set = false;
|
||||
|
||||
private final CookieParser _parser;
|
||||
|
||||
public Cookies()
|
||||
{
|
||||
this(CookieCompliance.RFC6265, null);
|
||||
|
@ -48,7 +50,7 @@ public class Cookies extends CookieCutter
|
|||
|
||||
public Cookies(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||
{
|
||||
super(compliance, complianceListener);
|
||||
_parser = CookieParser.newParser(this, compliance, complianceListener);
|
||||
}
|
||||
|
||||
public void addCookieField(String rawField)
|
||||
|
@ -93,7 +95,7 @@ public class Cookies extends CookieCutter
|
|||
if (_parsed)
|
||||
return _cookies;
|
||||
|
||||
parseFields(_rawFields);
|
||||
_parser.parseFields(_rawFields);
|
||||
_cookies = (Cookie[])_cookieList.toArray(new Cookie[_cookieList.size()]);
|
||||
_cookieList.clear();
|
||||
_parsed = true;
|
||||
|
@ -115,7 +117,7 @@ public class Cookies extends CookieCutter
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void addCookie(String name, String value, String domain, String path, int version, String comment)
|
||||
public void addCookie(String name, String value, int version, String domain, String path, String comment)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -154,6 +154,7 @@ public class RequestTest
|
|||
http.getHttpConfiguration().setResponseHeaderSize(512);
|
||||
http.getHttpConfiguration().setOutputBufferSize(2048);
|
||||
http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
|
||||
http.getHttpConfiguration().setRequestCookieCompliance(CookieCompliance.RFC6265_LEGACY);
|
||||
_connector = new LocalConnector(_server, http);
|
||||
_server.addConnector(_connector);
|
||||
_connector.setIdleTimeout(500);
|
||||
|
|
Loading…
Reference in New Issue