Validate cookie name and value characters Motivation:

RFC6265 specifies which characters are allowed in a cookie name and value.

Netty is currently too lax, which can used for HttpOnly escaping.

Modification:

In ServerCookieDecoder: discard cookie key-value pairs that contain invalid characters.
In ClientCookieEncoder: throw an exception when trying to encode cookies with invalid characters.

Result:

The problem described in the motivation section is fixed.
This commit is contained in:
Stephane Landelle 2015-04-14 13:57:32 +02:00 committed by Norman Maurer
parent c85b770ac4
commit 97d871a755
31 changed files with 1863 additions and 1290 deletions

View File

@ -15,8 +15,6 @@
*/
package io.netty.handler.codec.http;
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie encoder to be used client side,
* so only name=value pairs are sent.
@ -34,6 +32,7 @@ import static io.netty.handler.codec.http.CookieEncoderUtil.*;
*
* @see ClientCookieDecoder
*/
@Deprecated
public final class ClientCookieEncoder {
/**
@ -43,8 +42,9 @@ public final class ClientCookieEncoder {
* @param value the cookie value
* @return a Rfc6265 style Cookie header value
*/
@Deprecated
public static String encode(String name, String value) {
return encode(new DefaultCookie(name, value));
return io.netty.handler.codec.http.cookie.ClientCookieEncoder.LAX.encode(name, value);
}
/**
@ -53,14 +53,9 @@ public final class ClientCookieEncoder {
* @param specified the cookie
* @return a Rfc6265 style Cookie header value
*/
@Deprecated
public static String encode(Cookie cookie) {
if (cookie == null) {
throw new NullPointerException("cookie");
}
StringBuilder buf = stringBuilder();
encode(buf, cookie);
return stripTrailingSeparator(buf);
return io.netty.handler.codec.http.cookie.ClientCookieEncoder.LAX.encode(cookie);
}
/**
@ -69,24 +64,9 @@ public final class ClientCookieEncoder {
* @param cookies some cookies
* @return a Rfc6265 style Cookie header value, null if no cookies are passed.
*/
@Deprecated
public static String encode(Cookie... cookies) {
if (cookies == null) {
throw new NullPointerException("cookies");
}
if (cookies.length == 0) {
return null;
}
StringBuilder buf = stringBuilder();
for (Cookie c : cookies) {
if (c == null) {
break;
}
encode(buf, c);
}
return stripTrailingSeparatorOrNull(buf);
return io.netty.handler.codec.http.cookie.ClientCookieEncoder.LAX.encode(cookies);
}
/**
@ -95,31 +75,9 @@ public final class ClientCookieEncoder {
* @param cookies some cookies
* @return a Rfc6265 style Cookie header value, null if no cookies are passed.
*/
@Deprecated
public static String encode(Iterable<Cookie> cookies) {
if (cookies == null) {
throw new NullPointerException("cookies");
}
if (!cookies.iterator().hasNext()) {
return null;
}
StringBuilder buf = stringBuilder();
for (Cookie c : cookies) {
if (c == null) {
break;
}
encode(buf, c);
}
return stripTrailingSeparatorOrNull(buf);
}
private static void encode(StringBuilder buf, Cookie c) {
// rawValue > value > ""
String value = c.rawValue() != null ? c.rawValue()
: c.value() != null ? c.value() : "";
addUnquoted(buf, c.name(), value);
return io.netty.handler.codec.http.cookie.ClientCookieEncoder.LAX.encode(cookies);
}
private ClientCookieEncoder() {

View File

@ -20,8 +20,10 @@ import java.util.Set;
/**
* An interface defining an
* <a href="http://en.wikipedia.org/wiki/HTTP_cookie">HTTP cookie</a>.
* @deprecated Use {@link io.netty.handler.codec.http.cookie.Cookie} instead.
*/
public interface Cookie extends Comparable<Cookie> {
@Deprecated
public interface Cookie extends io.netty.handler.codec.http.cookie.Cookie {
/**
* @deprecated Use {@link #name()} instead.
@ -29,88 +31,24 @@ public interface Cookie extends Comparable<Cookie> {
@Deprecated
String getName();
/**
* Returns the name of this {@link Cookie}.
*
* @return The name of this {@link Cookie}
*/
String name();
/**
* @deprecated Use {@link #value()} instead.
*/
@Deprecated
String getValue();
/**
* Returns the value of this {@link Cookie}.
*
* @return The value of this {@link Cookie}
*/
String value();
/**
* Sets the value of this {@link Cookie}.
*
* @param value The value to set
*/
void setValue(String value);
/**
* Returns the raw value of this {@link Cookie},
* as it was set in original Set-Cookie header.
*
* @return The raw value of this {@link Cookie}
*/
String rawValue();
/**
* Sets the raw value of this {@link Cookie}.
*
* @param rawValue The raw value to set
*/
void setRawValue(String rawValue);
/**
* @deprecated Use {@link #domain()} instead.
*/
@Deprecated
String getDomain();
/**
* Returns the domain of this {@link Cookie}.
*
* @return The domain of this {@link Cookie}
*/
String domain();
/**
* Sets the domain of this {@link Cookie}.
*
* @param domain The domain to use
*/
void setDomain(String domain);
/**
* @deprecated Use {@link #path()} instead.
*/
@Deprecated
String getPath();
/**
* Returns the path of this {@link Cookie}.
*
* @return The {@link Cookie}'s path
*/
String path();
/**
* Sets the path of this {@link Cookie}.
*
* @param path The path to use for this {@link Cookie}
*/
void setPath(String path);
/**
* @deprecated Use {@link #comment()} instead.
*/
@ -121,14 +59,20 @@ public interface Cookie extends Comparable<Cookie> {
* Returns the comment of this {@link Cookie}.
*
* @return The comment of this {@link Cookie}
*
* @deprecated Not part of RFC6265
*/
@Deprecated
String comment();
/**
* Sets the comment of this {@link Cookie}.
*
* @param comment The comment to use
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setComment(String comment);
/**
@ -141,7 +85,10 @@ public interface Cookie extends Comparable<Cookie> {
* Returns the maximum age of this {@link Cookie} in seconds or {@link Long#MIN_VALUE} if unspecified
*
* @return The maximum age of this {@link Cookie}
*
* @deprecated Not part of RFC6265
*/
@Deprecated
long maxAge();
/**
@ -152,7 +99,10 @@ public interface Cookie extends Comparable<Cookie> {
* browser is closed.
*
* @param maxAge The maximum age of this {@link Cookie} in seconds
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setMaxAge(long maxAge);
/**
@ -165,51 +115,22 @@ public interface Cookie extends Comparable<Cookie> {
* Returns the version of this {@link Cookie}.
*
* @return The version of this {@link Cookie}
*
* @deprecated Not part of RFC6265
*/
@Deprecated
int version();
/**
* Sets the version of this {@link Cookie}.
*
* @param version The new version to use
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setVersion(int version);
/**
* Checks to see if this {@link Cookie} is secure
*
* @return True if this {@link Cookie} is secure, otherwise false
*/
boolean isSecure();
/**
* Sets the security getStatus of this {@link Cookie}
*
* @param secure True if this {@link Cookie} is to be secure, otherwise false
*/
void setSecure(boolean secure);
/**
* Checks to see if this {@link Cookie} can only be accessed via HTTP.
* If this returns true, the {@link Cookie} cannot be accessed through
* client side script - But only if the browser supports it.
* For more information, please look <a href="http://www.owasp.org/index.php/HTTPOnly">here</a>
*
* @return True if this {@link Cookie} is HTTP-only or false if it isn't
*/
boolean isHttpOnly();
/**
* Determines if this {@link Cookie} is HTTP only.
* If set to true, this {@link Cookie} cannot be accessed by a client
* side script. However, this works only if the browser supports it.
* For for information, please look
* <a href="http://www.owasp.org/index.php/HTTPOnly">here</a>.
*
* @param httpOnly True if the {@link Cookie} is HTTP only, otherwise false.
*/
void setHttpOnly(boolean httpOnly);
/**
* @deprecated Use {@link #commentUrl()} instead.
*/
@ -220,14 +141,20 @@ public interface Cookie extends Comparable<Cookie> {
* Returns the comment URL of this {@link Cookie}.
*
* @return The comment URL of this {@link Cookie}
*
* @deprecated Not part of RFC6265
*/
@Deprecated
String commentUrl();
/**
* Sets the comment URL of this {@link Cookie}.
*
* @param commentUrl The comment URL to use
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setCommentUrl(String commentUrl);
/**
@ -235,7 +162,10 @@ public interface Cookie extends Comparable<Cookie> {
* at the end of the current session.
*
* @return True if this {@link Cookie} is to be discarded, otherwise false
*
* @deprecated Not part of RFC6265
*/
@Deprecated
boolean isDiscard();
/**
@ -244,7 +174,10 @@ public interface Cookie extends Comparable<Cookie> {
* at the end of the current session
*
* @param discard True if the {@link Cookie} is to be discarded
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setDiscard(boolean discard);
/**
@ -257,14 +190,20 @@ public interface Cookie extends Comparable<Cookie> {
* Returns the ports that this {@link Cookie} can be accessed on.
*
* @return The {@link Set} of ports that this {@link Cookie} can use
*
* @deprecated Not part of RFC6265
*/
@Deprecated
Set<Integer> ports();
/**
* Sets the ports that this {@link Cookie} can be accessed on.
*
* @param ports The ports that this {@link Cookie} can be accessed on
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setPorts(int... ports);
/**
@ -272,6 +211,9 @@ public interface Cookie extends Comparable<Cookie> {
*
* @param ports The {@link Iterable} collection of ports that this
* {@link Cookie} can be accessed on.
*
* @deprecated Not part of RFC6265
*/
@Deprecated
void setPorts(Iterable<Integer> ports);
}

View File

@ -15,7 +15,13 @@
*/
package io.netty.handler.codec.http;
import static io.netty.handler.codec.http.CookieUtil.firstInvalidCookieNameOctet;
import static io.netty.handler.codec.http.CookieUtil.firstInvalidCookieValueOctet;
import static io.netty.handler.codec.http.CookieUtil.unwrapValue;
import io.netty.handler.codec.http.cookie.CookieHeaderNames;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.text.ParseException;
import java.util.ArrayList;
@ -25,7 +31,8 @@ import java.util.Set;
import java.util.TreeSet;
/**
* @deprecated Use {@link ClientCookieDecoder} or {@link ServerCookieDecoder} instead.
* @deprecated Use {@link io.netty.handler.codec.http.cookie.ClientCookieDecoder}
* or {@link io.netty.handler.codec.http.cookie.ServerCookieDecoder} instead.
*
* Decodes an HTTP header value into {@link Cookie}s. This decoder can decode
* the HTTP cookie version 0, 1, and 2.
@ -36,20 +43,46 @@ import java.util.TreeSet;
* Set&lt;{@link Cookie}&gt; cookies = {@link CookieDecoder}.decode(value);
* </pre>
*
* @see ClientCookieEncoder
* @see ServerCookieEncoder
* @see io.netty.handler.codec.http.cookie.ClientCookieDecoder
* @see io.netty.handler.codec.http.cookie.ServerCookieDecoder
*/
@Deprecated
public final class CookieDecoder {
private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
private static final CookieDecoder STRICT = new CookieDecoder(true);
private static final CookieDecoder LAX = new CookieDecoder(false);
private static final String COMMENT = "Comment";
private static final String COMMENTURL = "CommentURL";
private static final String DISCARD = "Discard";
private static final String PORT = "Port";
private static final String VERSION = "Version";
private static final char COMMA = ',';
private final boolean strict;
public static Set<Cookie> decode(String header) {
return decode(header, true);
}
public static Set<Cookie> decode(String header, boolean strict) {
return (strict ? STRICT : LAX).doDecode(header);
}
/**
* Decodes the specified HTTP header value into {@link Cookie}s.
*
* @return the decoded {@link Cookie}s
*/
public static Set<Cookie> decode(String header) {
private Set<Cookie> doDecode(String header) {
List<String> names = new ArrayList<String>(8);
List<String> values = new ArrayList<String>(8);
extractKeyValuePairs(header, names, values);
@ -63,7 +96,7 @@ public final class CookieDecoder {
// $Version is the only attribute that can appear before the actual
// cookie name-value pair.
if (names.get(0).equalsIgnoreCase(CookieHeaderNames.VERSION)) {
if (names.get(0).equalsIgnoreCase(VERSION)) {
try {
version = Integer.parseInt(values.get(0));
} catch (NumberFormatException e) {
@ -87,7 +120,11 @@ public final class CookieDecoder {
value = "";
}
Cookie c = new DefaultCookie(name, value);
Cookie c = initCookie(name, value);
if (c == null) {
break;
}
boolean discard = false;
boolean secure = false;
@ -103,15 +140,15 @@ public final class CookieDecoder {
name = names.get(j);
value = values.get(j);
if (CookieHeaderNames.DISCARD.equalsIgnoreCase(name)) {
if (DISCARD.equalsIgnoreCase(name)) {
discard = true;
} else if (CookieHeaderNames.SECURE.equalsIgnoreCase(name)) {
secure = true;
} else if (CookieHeaderNames.HTTPONLY.equalsIgnoreCase(name)) {
httpOnly = true;
} else if (CookieHeaderNames.COMMENT.equalsIgnoreCase(name)) {
} else if (COMMENT.equalsIgnoreCase(name)) {
comment = value;
} else if (CookieHeaderNames.COMMENTURL.equalsIgnoreCase(name)) {
} else if (COMMENTURL.equalsIgnoreCase(name)) {
commentURL = value;
} else if (CookieHeaderNames.DOMAIN.equalsIgnoreCase(name)) {
domain = value;
@ -129,9 +166,9 @@ public final class CookieDecoder {
}
} else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) {
maxAge = Integer.parseInt(value);
} else if (CookieHeaderNames.VERSION.equalsIgnoreCase(name)) {
} else if (VERSION.equalsIgnoreCase(name)) {
version = Integer.parseInt(value);
} else if (CookieHeaderNames.PORT.equalsIgnoreCase(name)) {
} else if (PORT.equalsIgnoreCase(name)) {
String[] portList = StringUtil.split(value, COMMA);
for (String s1: portList) {
try {
@ -168,7 +205,6 @@ public final class CookieDecoder {
private static void extractKeyValuePairs(
final String header, final List<String> names, final List<String> values) {
final int headerLen = header.length();
loop: for (int i = 0;;) {
@ -290,7 +326,49 @@ public final class CookieDecoder {
}
}
private CookieDecoder() {
// Unused
private CookieDecoder(boolean strict) {
this.strict = strict;
}
private DefaultCookie initCookie(String name, String value) {
if (name == null || name.length() == 0) {
logger.debug("Skipping cookie with null name");
return null;
}
if (value == null) {
logger.debug("Skipping cookie with null value");
return null;
}
CharSequence unwrappedValue = unwrapValue(value);
if (unwrappedValue == null) {
logger.debug("Skipping cookie because starting quotes are not properly balanced in '{}'",
unwrappedValue);
return null;
}
int invalidOctetPos;
if (strict && (invalidOctetPos = firstInvalidCookieNameOctet(name)) >= 0) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping cookie because name '{}' contains invalid char '{}'",
name, name.charAt(invalidOctetPos));
}
return null;
}
final boolean wrap = unwrappedValue.length() != value.length();
if (strict && (invalidOctetPos = firstInvalidCookieValueOctet(unwrappedValue)) >= 0) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping cookie because value '{}' contains invalid char '{}'",
unwrappedValue, unwrappedValue.charAt(invalidOctetPos));
}
return null;
}
DefaultCookie cookie = new DefaultCookie(name, unwrappedValue.toString());
cookie.setWrap(wrap);
return cookie;
}
}

View File

@ -1,96 +0,0 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.util.internal.InternalThreadLocalMap;
final class CookieEncoderUtil {
static StringBuilder stringBuilder() {
return InternalThreadLocalMap.get().stringBuilder();
}
/**
* @param buf a buffer where some cookies were maybe encoded
* @return the buffer String without the trailing separator, or null if no cookie was appended.
*/
static String stripTrailingSeparatorOrNull(StringBuilder buf) {
return buf.length() == 0 ? null : stripTrailingSeparator(buf);
}
static String stripTrailingSeparator(StringBuilder buf) {
if (buf.length() > 0) {
buf.setLength(buf.length() - 2);
}
return buf.toString();
}
static void add(StringBuilder sb, String name, String val) {
if (val == null) {
addQuoted(sb, name, "");
return;
}
for (int i = 0; i < val.length(); i ++) {
char c = val.charAt(i);
switch (c) {
case '\t': case ' ': case '"': case '(': case ')': case ',':
case '/': case ':': case ';': case '<': case '=': case '>':
case '?': case '@': case '[': case '\\': case ']':
case '{': case '}':
addQuoted(sb, name, val);
return;
}
}
addUnquoted(sb, name, val);
}
static void addUnquoted(StringBuilder sb, String name, String val) {
sb.append(name);
sb.append((char) HttpConstants.EQUALS);
sb.append(val);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
static void addQuoted(StringBuilder sb, String name, String val) {
if (val == null) {
val = "";
}
sb.append(name);
sb.append((char) HttpConstants.EQUALS);
sb.append((char) HttpConstants.DOUBLE_QUOTE);
sb.append(val.replace("\\", "\\\\").replace("\"", "\\\""));
sb.append((char) HttpConstants.DOUBLE_QUOTE);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
static void add(StringBuilder sb, String name, long val) {
sb.append(name);
sb.append((char) HttpConstants.EQUALS);
sb.append(val);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
private CookieEncoderUtil() {
// Unused
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import java.util.BitSet;
/**
* @deprecated Duplicate of package private ${@link io.netty.handler.codec.http.cookie.CookieUtil}
*/
@Deprecated
final class CookieUtil {
private static final BitSet VALID_COOKIE_VALUE_OCTETS = validCookieValueOctets();
private static final BitSet VALID_COOKIE_NAME_OCTETS = validCookieNameOctets(VALID_COOKIE_VALUE_OCTETS);
// US-ASCII characters excluding CTLs, whitespace, DQUOTE, comma, semicolon, and backslash
private static BitSet validCookieValueOctets() {
BitSet bits = new BitSet(8);
for (int i = 35; i < 127; i++) {
// US-ASCII characters excluding CTLs (%x00-1F / %x7F)
bits.set(i);
}
bits.set('"', false); // exclude DQUOTE = %x22
bits.set(',', false); // exclude comma = %x2C
bits.set(';', false); // exclude semicolon = %x3B
bits.set('\\', false); // exclude backslash = %x5C
return bits;
}
// token = 1*<any CHAR except CTLs or separators>
// separators = "(" | ")" | "<" | ">" | "@"
// | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "="
// | "{" | "}" | SP | HT
private static BitSet validCookieNameOctets(BitSet validCookieValueOctets) {
BitSet bits = new BitSet(8);
bits.or(validCookieValueOctets);
bits.set('(', false);
bits.set(')', false);
bits.set('<', false);
bits.set('>', false);
bits.set('@', false);
bits.set(':', false);
bits.set('/', false);
bits.set('[', false);
bits.set(']', false);
bits.set('?', false);
bits.set('=', false);
bits.set('{', false);
bits.set('}', false);
bits.set(' ', false);
bits.set('\t', false);
return bits;
}
static int firstInvalidCookieNameOctet(CharSequence cs) {
return firstInvalidOctet(cs, VALID_COOKIE_NAME_OCTETS);
}
static int firstInvalidCookieValueOctet(CharSequence cs) {
return firstInvalidOctet(cs, VALID_COOKIE_VALUE_OCTETS);
}
static int firstInvalidOctet(CharSequence cs, BitSet bits) {
for (int i = 0; i < cs.length(); i++) {
char c = cs.charAt(i);
if (!bits.get(c)) {
return i;
}
}
return -1;
}
static CharSequence unwrapValue(CharSequence cs) {
final int len = cs.length();
if (len > 0 && cs.charAt(0) == '"') {
if (len >= 2 && cs.charAt(len - 1) == '"') {
// properly balanced
return len == 2 ? "" : cs.subSequence(1, len - 1);
} else {
return null;
}
}
return cs;
}
private CookieUtil() {
// Unused
}
}

View File

@ -19,63 +19,26 @@ import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
/**
* The default {@link Cookie} implementation.
*
* @deprecated Use {@link io.netty.handler.codec.http.cookie.DefaultCookie} instead.
*/
public class DefaultCookie implements Cookie {
@Deprecated
public class DefaultCookie extends io.netty.handler.codec.http.cookie.DefaultCookie implements Cookie {
private final String name;
private String value;
private String rawValue;
private String domain;
private String path;
private String comment;
private String commentUrl;
private boolean discard;
private Set<Integer> ports = Collections.emptySet();
private Set<Integer> unmodifiablePorts = ports;
private long maxAge = Long.MIN_VALUE;
private int version;
private boolean secure;
private boolean httpOnly;
/**
* Creates a new cookie with the specified name and value.
*/
public DefaultCookie(String name, String value) {
if (name == null) {
throw new NullPointerException("name");
}
name = name.trim();
if (name.isEmpty()) {
throw new IllegalArgumentException("empty name");
}
for (int i = 0; i < name.length(); i ++) {
char c = name.charAt(i);
if (c > 127) {
throw new IllegalArgumentException(
"name contains non-ascii character: " + name);
}
// Check prohibited characters.
switch (c) {
case '\t': case '\n': case 0x0b: case '\f': case '\r':
case ' ': case ',': case ';': case '=':
throw new IllegalArgumentException(
"name contains one of the following prohibited characters: " +
"=,; \\t\\r\\n\\v\\f: " + name);
}
}
if (name.charAt(0) == '$') {
throw new IllegalArgumentException("name starting with '$' not allowed: " + name);
}
this.name = name;
setValue(value);
super(name, value);
}
@Override
@ -84,75 +47,24 @@ public class DefaultCookie implements Cookie {
return name();
}
@Override
public String name() {
return name;
}
@Override
@Deprecated
public String getValue() {
return value();
}
@Override
public String value() {
return value;
}
@Override
public void setValue(String value) {
if (value == null) {
throw new NullPointerException("value");
}
this.value = value;
}
@Override
public String rawValue() {
return rawValue;
}
@Override
public void setRawValue(String rawValue) {
if (value == null) {
throw new NullPointerException("rawValue");
}
this.rawValue = rawValue;
}
@Override
@Deprecated
public String getDomain() {
return domain();
}
@Override
public String domain() {
return domain;
}
@Override
public void setDomain(String domain) {
this.domain = validateValue("domain", domain);
}
@Override
@Deprecated
public String getPath() {
return path();
}
@Override
public String path() {
return path;
}
@Override
public void setPath(String path) {
this.path = validateValue("path", path);
}
@Override
@Deprecated
public String getComment() {
@ -160,11 +72,13 @@ public class DefaultCookie implements Cookie {
}
@Override
@Deprecated
public String comment() {
return comment;
}
@Override
@Deprecated
public void setComment(String comment) {
this.comment = validateValue("comment", comment);
}
@ -176,21 +90,25 @@ public class DefaultCookie implements Cookie {
}
@Override
@Deprecated
public String commentUrl() {
return commentUrl;
}
@Override
@Deprecated
public void setCommentUrl(String commentUrl) {
this.commentUrl = validateValue("commentUrl", commentUrl);
}
@Override
@Deprecated
public boolean isDiscard() {
return discard;
}
@Override
@Deprecated
public void setDiscard(boolean discard) {
this.discard = discard;
}
@ -202,6 +120,7 @@ public class DefaultCookie implements Cookie {
}
@Override
@Deprecated
public Set<Integer> ports() {
if (unmodifiablePorts == null) {
unmodifiablePorts = Collections.unmodifiableSet(ports);
@ -210,6 +129,7 @@ public class DefaultCookie implements Cookie {
}
@Override
@Deprecated
public void setPorts(int... ports) {
if (ports == null) {
throw new NullPointerException("ports");
@ -232,6 +152,7 @@ public class DefaultCookie implements Cookie {
}
@Override
@Deprecated
public void setPorts(Iterable<Integer> ports) {
Set<Integer> newPorts = new TreeSet<Integer>();
for (int p: ports) {
@ -254,16 +175,6 @@ public class DefaultCookie implements Cookie {
return maxAge();
}
@Override
public long maxAge() {
return maxAge;
}
@Override
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
@Override
@Deprecated
public int getVersion() {
@ -271,158 +182,14 @@ public class DefaultCookie implements Cookie {
}
@Override
@Deprecated
public int version() {
return version;
}
@Override
@Deprecated
public void setVersion(int version) {
this.version = version;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public void setSecure(boolean secure) {
this.secure = secure;
}
@Override
public boolean isHttpOnly() {
return httpOnly;
}
@Override
public void setHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
}
@Override
public int hashCode() {
return name().hashCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Cookie)) {
return false;
}
Cookie that = (Cookie) o;
if (!name().equalsIgnoreCase(that.name())) {
return false;
}
if (path() == null) {
if (that.path() != null) {
return false;
}
} else if (that.path() == null) {
return false;
} else if (!path().equals(that.path())) {
return false;
}
if (domain() == null) {
if (that.domain() != null) {
return false;
}
} else if (that.domain() == null) {
return false;
} else {
return domain().equalsIgnoreCase(that.domain());
}
return true;
}
@Override
public int compareTo(Cookie c) {
int v;
v = name().compareToIgnoreCase(c.name());
if (v != 0) {
return v;
}
if (path() == null) {
if (c.path() != null) {
return -1;
}
} else if (c.path() == null) {
return 1;
} else {
v = path().compareTo(c.path());
if (v != 0) {
return v;
}
}
if (domain() == null) {
if (c.domain() != null) {
return -1;
}
} else if (c.domain() == null) {
return 1;
} else {
v = domain().compareToIgnoreCase(c.domain());
return v;
}
return 0;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder()
.append(name())
.append('=')
.append(value());
if (domain() != null) {
buf.append(", domain=")
.append(domain());
}
if (path() != null) {
buf.append(", path=")
.append(path());
}
if (comment() != null) {
buf.append(", comment=")
.append(comment());
}
if (maxAge() >= 0) {
buf.append(", maxAge=")
.append(maxAge())
.append('s');
}
if (isSecure()) {
buf.append(", secure");
}
if (isHttpOnly()) {
buf.append(", HTTPOnly");
}
return buf.toString();
}
private static String validateValue(String name, String value) {
if (value == null) {
return null;
}
value = value.trim();
if (value.isEmpty()) {
return null;
}
for (int i = 0; i < value.length(); i ++) {
char c = value.charAt(i);
switch (c) {
case '\r': case '\n': case '\f': case 0x0b: case ';':
throw new IllegalArgumentException(
name + " contains one of the following prohibited characters: " +
";\\r\\n\\f\\v (" + value + ')');
}
}
return value;
}
}

View File

@ -33,7 +33,7 @@ import java.util.TimeZone;
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
* </ul>
*/
final class HttpHeaderDateFormat extends SimpleDateFormat {
public final class HttpHeaderDateFormat extends SimpleDateFormat {
private static final long serialVersionUID = -925286159755905325L;
private final SimpleDateFormat format1 = new HttpHeaderDateFormatObsolete1();
@ -47,7 +47,7 @@ final class HttpHeaderDateFormat extends SimpleDateFormat {
}
};
static HttpHeaderDateFormat get() {
public static HttpHeaderDateFormat get() {
return dateFormatThreadLocal.get();
}

View File

@ -15,22 +15,25 @@
*/
package io.netty.handler.codec.http;
/**
* An HTTP request.
*
* <h3>Accessing Query Parameters and Cookie</h3>
* <p>
* Unlike the Servlet API, a query string is constructed and decomposed by
* {@link QueryStringEncoder} and {@link QueryStringDecoder}. {@link Cookie}
* support is also provided separately via {@link ClientCookieDecoder}, {@link ServerCookieDecoder},
* {@link ClientCookieEncoder}, and {@link @ServerCookieEncoder}.
* {@link QueryStringEncoder} and {@link QueryStringDecoder}.
*
* {@link io.netty.handler.codec.http.cookie.Cookie} support is also provided
* separately via {@link io.netty.handler.codec.http.cookie.ServerCookieDecoder},
* {@link io.netty.handler.codec.http.cookie.ClientCookieDecoder},
* {@link io.netty.handler.codec.http.cookie.ServerCookieEncoder},
* and {@link @io.netty.handler.codec.http.cookie.ClientCookieEncoder}.
*
* @see HttpResponse
* @see ClientCookieEncoder
* @see ServerCookieEncoder
* @see ClientCookieDecoder
* @see ServerCookieDecoder
* @see io.netty.handler.codec.http.cookie.ServerCookieDecoder
* @see io.netty.handler.codec.http.cookie.ClientCookieDecoder
* @see io.netty.handler.codec.http.cookie.ServerCookieEncoder
* @see io.netty.handler.codec.http.cookie.ClientCookieEncoder
*/
public interface HttpRequest extends HttpMessage {

View File

@ -15,20 +15,22 @@
*/
package io.netty.handler.codec.http;
/**
* An HTTP response.
*
* <h3>Accessing Cookies</h3>
* <p>
* Unlike the Servlet API, {@link Cookie} support is provided separately via {@link ClientCookieDecoder},
* {@link ServerCookieDecoder}, {@link ClientCookieEncoder}, and {@link @ServerCookieEncoder}.
* Unlike the Servlet API, {@link io.netty.handler.codec.http.cookie.Cookie} support is provided
* separately via {@link io.netty.handler.codec.http.cookie.ServerCookieDecoder},
* {@link io.netty.handler.codec.http.cookie.ClientCookieDecoder},
* {@link io.netty.handler.codec.http.cookie.ServerCookieEncoder},
* and {@link @io.netty.handler.codec.http.cookie.ClientCookieEncoder}.
*
* @see HttpRequest
* @see ClientCookieEncoder
* @see ServerCookieEncoder
* @see ClientCookieDecoder
* @see ServerCookieDecoder
* @see io.netty.handler.codec.http.cookie.ServerCookieDecoder
* @see io.netty.handler.codec.http.cookie.ClientCookieDecoder
* @see io.netty.handler.codec.http.cookie.ServerCookieEncoder
* @see io.netty.handler.codec.http.cookie.ClientCookieEncoder
*/
public interface HttpResponse extends HttpMessage {

View File

@ -1,182 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used server side.
*
* Only name and value fields are expected, so old fields are not populated (path, domain, etc).
*
* Old <a href="http://tools.ietf.org/html/rfc2965">RFC2965</a> cookies are still supported,
* old fields will simply be ignored.
*
* @see ServerCookieEncoder
*/
public final class ServerCookieDecoder {
/**
* Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
*
* @return the decoded {@link Cookie}
*/
public static Set<Cookie> decode(String header) {
if (header == null) {
throw new NullPointerException("header");
}
final int headerLen = header.length();
if (headerLen == 0) {
return Collections.emptySet();
}
Set<Cookie> cookies = new TreeSet<Cookie>();
int i = 0;
boolean rfc2965Style = false;
if (header.regionMatches(true, 0, "$Version", 0, 8)) {
// RFC 2965 style cookie, move to after version value
i = header.indexOf(';') + 1;
rfc2965Style = true;
}
loop: for (;;) {
// Skip spaces and separators.
for (;;) {
if (i == headerLen) {
break loop;
}
char c = header.charAt(i);
if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
|| c == '\r' || c == ' ' || c == ',' || c == ';') {
i++;
continue;
}
break;
}
int newNameStart = i;
int newNameEnd = i;
String value;
if (i == headerLen) {
value = null;
} else {
keyValLoop: for (;;) {
char curChar = header.charAt(i);
if (curChar == ';') {
// NAME; (no value till ';')
newNameEnd = i;
value = null;
break keyValLoop;
} else if (curChar == '=') {
// NAME=VALUE
newNameEnd = i;
i++;
if (i == headerLen) {
// NAME= (empty value, i.e. nothing after '=')
value = "";
break keyValLoop;
}
int newValueStart = i;
char c = header.charAt(i);
if (c == '"') {
// NAME="VALUE"
StringBuilder newValueBuf = stringBuilder();
final char q = c;
boolean hadBackslash = false;
i++;
for (;;) {
if (i == headerLen) {
value = newValueBuf.toString();
break keyValLoop;
}
if (hadBackslash) {
hadBackslash = false;
c = header.charAt(i++);
if (c == '\\' || c == '"') {
// Escape last backslash.
newValueBuf.setCharAt(newValueBuf.length() - 1, c);
} else {
// Do not escape last backslash.
newValueBuf.append(c);
}
} else {
c = header.charAt(i++);
if (c == q) {
value = newValueBuf.toString();
break keyValLoop;
}
newValueBuf.append(c);
if (c == '\\') {
hadBackslash = true;
}
}
}
} else {
// NAME=VALUE;
int semiPos = header.indexOf(';', i);
if (semiPos > 0) {
value = header.substring(newValueStart, semiPos);
i = semiPos;
} else {
value = header.substring(newValueStart);
i = headerLen;
}
}
break keyValLoop;
} else {
i++;
}
if (i == headerLen) {
// NAME (no value till the end of string)
newNameEnd = headerLen;
value = null;
break;
}
}
}
if (!rfc2965Style || (!header.regionMatches(newNameStart, "$Path", 0, "$Path".length()) &&
!header.regionMatches(newNameStart, "$Domain", 0, "$Domain".length()) &&
!header.regionMatches(newNameStart, "$Port", 0, "$Port".length()))) {
// skip obsolete RFC2965 fields
String name = header.substring(newNameStart, newNameEnd);
cookies.add(new DefaultCookie(name, value));
}
}
return cookies;
}
private ServerCookieDecoder() {
// unused
}
}

View File

@ -15,12 +15,7 @@
*/
package io.netty.handler.codec.http;
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
@ -38,7 +33,10 @@ import java.util.List;
* </pre>
*
* @see ServerCookieDecoder
*
* @deprecated Use {@link io.netty.handler.codec.http.cookie.ServerCookieEncoder} instead
*/
@Deprecated
public final class ServerCookieEncoder {
/**
@ -48,8 +46,9 @@ public final class ServerCookieEncoder {
* @param value the cookie value
* @return a single Set-Cookie header value
*/
@Deprecated
public static String encode(String name, String value) {
return encode(new DefaultCookie(name, value));
return io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(name, value);
}
/**
@ -58,40 +57,9 @@ public final class ServerCookieEncoder {
* @param cookie the cookie
* @return a single Set-Cookie header value
*/
@Deprecated
public static String encode(Cookie cookie) {
if (cookie == null) {
throw new NullPointerException("cookie");
}
StringBuilder buf = stringBuilder();
addUnquoted(buf, cookie.name(), cookie.value());
if (cookie.maxAge() != Long.MIN_VALUE) {
add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis());
addUnquoted(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires));
}
if (cookie.path() != null) {
addUnquoted(buf, CookieHeaderNames.PATH, cookie.path());
}
if (cookie.domain() != null) {
addUnquoted(buf, CookieHeaderNames.DOMAIN, cookie.domain());
}
if (cookie.isSecure()) {
buf.append(CookieHeaderNames.SECURE);
buf.append((char) HttpConstants.SEMICOLON);
buf.append((char) HttpConstants.SP);
}
if (cookie.isHttpOnly()) {
buf.append(CookieHeaderNames.HTTPONLY);
buf.append((char) HttpConstants.SEMICOLON);
buf.append((char) HttpConstants.SP);
}
return stripTrailingSeparator(buf);
return io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(cookie);
}
/**
@ -100,23 +68,9 @@ public final class ServerCookieEncoder {
* @param cookies a bunch of cookies
* @return the corresponding bunch of Set-Cookie headers
*/
@Deprecated
public static List<String> encode(Cookie... cookies) {
if (cookies == null) {
throw new NullPointerException("cookies");
}
if (cookies.length == 0) {
return Collections.emptyList();
}
List<String> encoded = new ArrayList<String>(cookies.length);
for (Cookie c : cookies) {
if (c == null) {
break;
}
encoded.add(encode(c));
}
return encoded;
return io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(cookies);
}
/**
@ -125,23 +79,9 @@ public final class ServerCookieEncoder {
* @param cookies a bunch of cookies
* @return the corresponding bunch of Set-Cookie headers
*/
@Deprecated
public static List<String> encode(Collection<Cookie> cookies) {
if (cookies == null) {
throw new NullPointerException("cookies");
}
if (cookies.isEmpty()) {
return Collections.emptyList();
}
List<String> encoded = new ArrayList<String>(cookies.size());
for (Cookie c : cookies) {
if (c == null) {
break;
}
encoded.add(encode(c));
}
return encoded;
return io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(cookies);
}
/**
@ -150,23 +90,9 @@ public final class ServerCookieEncoder {
* @param cookies a bunch of cookies
* @return the corresponding bunch of Set-Cookie headers
*/
@Deprecated
public static List<String> encode(Iterable<Cookie> cookies) {
if (cookies == null) {
throw new NullPointerException("cookies");
}
if (!cookies.iterator().hasNext()) {
return Collections.emptyList();
}
List<String> encoded = new ArrayList<String>();
for (Cookie c : cookies) {
if (c == null) {
break;
}
encoded.add(encode(c));
}
return encoded;
return io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(cookies);
}
private ServerCookieEncoder() {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 The Netty Project
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -13,35 +13,47 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
package io.netty.handler.codec.http.cookie;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import java.text.ParsePosition;
import java.util.Date;
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used client side.
*
* It will store the raw value in {@link Cookie#setRawValue(String)} so it can be
* It will store the way the raw value was wrapped in {@link Cookie#setWrap(boolean)} so it can be
* eventually sent back to the Origin server as is.
*
* @see ClientCookieEncoder
*/
public final class ClientCookieDecoder {
public final class ClientCookieDecoder extends CookieDecoder {
/**
* Strict encoder that validates that name and value chars are in the valid scope
* defined in RFC6265
*/
public static final ClientCookieDecoder STRICT = new ClientCookieDecoder(true);
/**
* Lax instance that doesn't validate name and value
*/
public static final ClientCookieDecoder LAX = new ClientCookieDecoder(false);
private ClientCookieDecoder(boolean strict) {
super(strict);
}
/**
* Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
*
* @return the decoded {@link Cookie}
*/
public static Cookie decode(String header) {
if (header == null) {
throw new NullPointerException("header");
}
final int headerLen = header.length();
public Cookie decode(String header) {
final int headerLen = checkNotNull(header, "header").length();
if (headerLen == 0) {
return null;
@ -70,89 +82,35 @@ public final class ClientCookieDecoder {
break;
}
int newNameStart = i;
int newNameEnd = i;
String value, rawValue;
int nameBegin = i;
int nameEnd = i;
int valueBegin = -1;
int valueEnd = -1;
if (i == headerLen) {
value = rawValue = null;
} else {
if (i != headerLen) {
keyValLoop: for (;;) {
char curChar = header.charAt(i);
if (curChar == ';') {
// NAME; (no value till ';')
newNameEnd = i;
value = rawValue = null;
nameEnd = i;
valueBegin = valueEnd = -1;
break keyValLoop;
} else if (curChar == '=') {
// NAME=VALUE
newNameEnd = i;
nameEnd = i;
i++;
if (i == headerLen) {
// NAME= (empty value, i.e. nothing after '=')
value = rawValue = "";
valueBegin = valueEnd = 0;
break keyValLoop;
}
int newValueStart = i;
char c = header.charAt(i);
if (c == '"') {
// NAME="VALUE"
StringBuilder newValueBuf = stringBuilder();
int rawValueStart = i;
int rawValueEnd = i;
final char q = c;
boolean hadBackslash = false;
i++;
for (;;) {
if (i == headerLen) {
value = newValueBuf.toString();
// only need to compute raw value for cookie
// value which is in first position
rawValue = header.substring(rawValueStart, rawValueEnd);
break keyValLoop;
}
if (hadBackslash) {
hadBackslash = false;
c = header.charAt(i++);
rawValueEnd = i;
if (c == '\\' || c == '"') {
newValueBuf.setCharAt(newValueBuf.length() - 1, c);
} else {
// Do not escape last backslash.
newValueBuf.append(c);
}
} else {
c = header.charAt(i++);
rawValueEnd = i;
if (c == q) {
value = newValueBuf.toString();
// only need to compute raw value for
// cookie value which is in first
// position
rawValue = header.substring(rawValueStart, rawValueEnd);
break keyValLoop;
}
newValueBuf.append(c);
if (c == '\\') {
hadBackslash = true;
}
}
}
} else {
// NAME=VALUE;
int semiPos = header.indexOf(';', i);
if (semiPos > 0) {
value = rawValue = header.substring(newValueStart, semiPos);
i = semiPos;
} else {
value = rawValue = header.substring(newValueStart);
i = headerLen;
}
}
valueBegin = i;
// NAME=VALUE;
int semiPos = header.indexOf(';', i);
valueEnd = i = semiPos > 0 ? semiPos : headerLen;
break keyValLoop;
} else {
i++;
@ -160,17 +118,31 @@ public final class ClientCookieDecoder {
if (i == headerLen) {
// NAME (no value till the end of string)
newNameEnd = i;
value = rawValue = null;
nameEnd = headerLen;
valueBegin = valueEnd = -1;
break;
}
}
}
if (valueEnd > 0 && header.charAt(valueEnd - 1) == ',') {
// old multiple cookies separator, skipping it
valueEnd--;
}
if (cookieBuilder == null) {
cookieBuilder = new CookieBuilder(header, newNameStart, newNameEnd, value, rawValue);
// cookie name-value pair
DefaultCookie cookie = initCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
if (cookie == null) {
return null;
}
cookieBuilder = new CookieBuilder(cookie);
} else {
cookieBuilder.appendAttribute(header, newNameStart, newNameEnd, value);
// cookie attribute
String attrValue = valueBegin == -1 ? null : header.substring(valueBegin, valueEnd);
cookieBuilder.appendAttribute(header, nameBegin, nameEnd, attrValue);
}
}
return cookieBuilder.cookie();
@ -178,9 +150,7 @@ public final class ClientCookieDecoder {
private static class CookieBuilder {
private final String name;
private final String value;
private final String rawValue;
private final DefaultCookie cookie;
private String domain;
private String path;
private long maxAge = Long.MIN_VALUE;
@ -188,11 +158,8 @@ public final class ClientCookieDecoder {
private boolean secure;
private boolean httpOnly;
public CookieBuilder(String header, int keyStart, int keyEnd,
String value, String rawValue) {
name = header.substring(keyStart, keyEnd);
this.value = value;
this.rawValue = rawValue;
public CookieBuilder(DefaultCookie cookie) {
this.cookie = cookie;
}
private long mergeMaxAgeAndExpire(long maxAge, String expires) {
@ -210,13 +177,6 @@ public final class ClientCookieDecoder {
}
public Cookie cookie() {
if (name == null) {
return null;
}
DefaultCookie cookie = new DefaultCookie(name, value);
cookie.setValue(value);
cookie.setRawValue(rawValue);
cookie.setDomain(domain);
cookie.setPath(path);
cookie.setMaxAge(mergeMaxAgeAndExpire(maxAge, expires));
@ -245,7 +205,6 @@ public final class ClientCookieDecoder {
private void setCookieAttribute(String header, int keyStart,
int keyEnd, String value) {
int length = keyEnd - keyStart;
if (length == 4) {
@ -260,15 +219,15 @@ public final class ClientCookieDecoder {
}
private void parse4(String header, int nameStart, String value) {
if (header.regionMatches(true, nameStart, "Path", 0, 4)) {
if (header.regionMatches(true, nameStart, CookieHeaderNames.PATH, 0, 4)) {
path = value;
}
}
private void parse6(String header, int nameStart, String value) {
if (header.regionMatches(true, nameStart, "Domain", 0, 5)) {
domain = value.isEmpty() ? null : value;
} else if (header.regionMatches(true, nameStart, "Secure", 0, 5)) {
if (header.regionMatches(true, nameStart, CookieHeaderNames.DOMAIN, 0, 5)) {
domain = value.length() > 0 ? value.toString() : null;
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.SECURE, 0, 5)) {
secure = true;
}
}
@ -286,22 +245,17 @@ public final class ClientCookieDecoder {
}
private void parse7(String header, int nameStart, String value) {
if (header.regionMatches(true, nameStart, "Expires", 0, 7)) {
if (header.regionMatches(true, nameStart, CookieHeaderNames.EXPIRES, 0, 7)) {
setExpire(value);
} else if (header.regionMatches(true, nameStart, "Max-Age", 0, 7)) {
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.MAX_AGE, 0, 7)) {
setMaxAge(value);
}
}
private void parse8(String header, int nameStart, String value) {
if (header.regionMatches(true, nameStart, "HttpOnly", 0, 8)) {
if (header.regionMatches(true, nameStart, CookieHeaderNames.HTTPONLY, 0, 8)) {
httpOnly = true;
}
}
}
private ClientCookieDecoder() {
// unused
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.handler.codec.http.cookie.CookieUtil.*;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.util.Iterator;
import io.netty.handler.codec.http.HttpRequest;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie encoder to be used client side,
* so only name=value pairs are sent.
*
* User-Agents are not supposed to interpret cookies, so, if present, {@link Cookie#rawValue()} will be used.
* Otherwise, {@link Cookie#value()} will be used unquoted.
*
* Note that multiple cookies are supposed to be sent at once in a single "Cookie" header.
*
* <pre>
* // Example
* {@link HttpRequest} req = ...;
* res.setHeader("Cookie", {@link ClientCookieEncoder}.encode("JSESSIONID", "1234"));
* </pre>
*
* @see ClientCookieDecoder
*/
public final class ClientCookieEncoder extends CookieEncoder {
/**
* Strict encoder that validates that name and value chars are in the valid scope
* defined in RFC6265
*/
public static final ClientCookieEncoder STRICT = new ClientCookieEncoder(true);
/**
* Lax instance that doesn't validate name and value
*/
public static final ClientCookieEncoder LAX = new ClientCookieEncoder(false);
private ClientCookieEncoder(boolean strict) {
super(strict);
}
/**
* Encodes the specified cookie into a Cookie header value.
*
* @param name the cookie name
* @param value the cookie value
* @return a Rfc6265 style Cookie header value
*/
public String encode(String name, String value) {
return encode(new DefaultCookie(name, value));
}
/**
* Encodes the specified cookie into a Cookie header value.
*
* @param specified the cookie
* @return a Rfc6265 style Cookie header value
*/
public String encode(Cookie cookie) {
StringBuilder buf = stringBuilder();
encode(buf, checkNotNull(cookie, "cookie"));
return stripTrailingSeparator(buf);
}
/**
* Encodes the specified cookies into a single Cookie header value.
*
* @param cookies some cookies
* @return a Rfc6265 style Cookie header value, null if no cookies are passed.
*/
public String encode(Cookie... cookies) {
if (checkNotNull(cookies, "cookies").length == 0) {
return null;
}
StringBuilder buf = stringBuilder();
for (Cookie c : cookies) {
if (c == null) {
break;
}
encode(buf, c);
}
return stripTrailingSeparatorOrNull(buf);
}
/**
* Encodes the specified cookies into a single Cookie header value.
*
* @param cookies some cookies
* @return a Rfc6265 style Cookie header value, null if no cookies are passed.
*/
public String encode(Iterable<? extends Cookie> cookies) {
Iterator<? extends Cookie> cookiesIt = checkNotNull(cookies, "cookies").iterator();
if (!cookiesIt.hasNext()) {
return null;
}
StringBuilder buf = stringBuilder();
while (cookiesIt.hasNext()) {
Cookie c = cookiesIt.next();
if (c == null) {
break;
}
encode(buf, c);
}
return stripTrailingSeparatorOrNull(buf);
}
private void encode(StringBuilder buf, Cookie c) {
final String name = c.name();
final String value = c.value() != null ? c.value() : "";
validateCookie(name, value);
if (c.wrap()) {
addQuoted(buf, name, value);
} else {
add(buf, name, value);
}
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
/**
* An interface defining an
* <a href="http://en.wikipedia.org/wiki/HTTP_cookie">HTTP cookie</a>.
*/
public interface Cookie extends Comparable<Cookie> {
/**
* Returns the name of this {@link Cookie}.
*
* @return The name of this {@link Cookie}
*/
String name();
/**
* Returns the value of this {@link Cookie}.
*
* @return The value of this {@link Cookie}
*/
String value();
/**
* Sets the value of this {@link Cookie}.
*
* @param value The value to set
*/
void setValue(String value);
/**
* Returns true if the raw value of this {@link Cookie},
* was wrapped with double quotes in original Set-Cookie header.
*
* @return If the value of this {@link Cookie} is to be wrapped
*/
boolean wrap();
/**
* Sets true if the value of this {@link Cookie}
* is to be wrapped with double quotes.
*
* @param wrap true if wrap
*/
void setWrap(boolean wrap);
/**
* Returns the domain of this {@link Cookie}.
*
* @return The domain of this {@link Cookie}
*/
String domain();
/**
* Sets the domain of this {@link Cookie}.
*
* @param domain The domain to use
*/
void setDomain(String domain);
/**
* Returns the path of this {@link Cookie}.
*
* @return The {@link Cookie}'s path
*/
String path();
/**
* Sets the path of this {@link Cookie}.
*
* @param path The path to use for this {@link Cookie}
*/
void setPath(String path);
/**
* Returns the maximum age of this {@link Cookie} in seconds or {@link Long#MIN_VALUE} if unspecified
*
* @return The maximum age of this {@link Cookie}
*/
long maxAge();
/**
* Sets the maximum age of this {@link Cookie} in seconds.
* If an age of {@code 0} is specified, this {@link Cookie} will be
* automatically removed by browser because it will expire immediately.
* If {@link Long#MIN_VALUE} is specified, this {@link Cookie} will be removed when the
* browser is closed.
*
* @param maxAge The maximum age of this {@link Cookie} in seconds
*/
void setMaxAge(long maxAge);
/**
* Checks to see if this {@link Cookie} is secure
*
* @return True if this {@link Cookie} is secure, otherwise false
*/
boolean isSecure();
/**
* Sets the security getStatus of this {@link Cookie}
*
* @param secure True if this {@link Cookie} is to be secure, otherwise false
*/
void setSecure(boolean secure);
/**
* Checks to see if this {@link Cookie} can only be accessed via HTTP.
* If this returns true, the {@link Cookie} cannot be accessed through
* client side script - But only if the browser supports it.
* For more information, please look <a href="http://www.owasp.org/index.php/HTTPOnly">here</a>
*
* @return True if this {@link Cookie} is HTTP-only or false if it isn't
*/
boolean isHttpOnly();
/**
* Determines if this {@link Cookie} is HTTP only.
* If set to true, this {@link Cookie} cannot be accessed by a client
* side script. However, this works only if the browser supports it.
* For for information, please look
* <a href="http://www.owasp.org/index.php/HTTPOnly">here</a>.
*
* @param httpOnly True if the {@link Cookie} is HTTP only, otherwise false.
*/
void setHttpOnly(boolean httpOnly);
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieNameOctet;
import static io.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieValueOctet;
import static io.netty.handler.codec.http.cookie.CookieUtil.unwrapValue;
import java.nio.CharBuffer;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* Parent of Client and Server side cookie decoders
*/
public abstract class CookieDecoder {
private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
private final boolean strict;
protected CookieDecoder(boolean strict) {
this.strict = strict;
}
protected DefaultCookie initCookie(String header, int nameBegin, int nameEnd, int valueBegin, int valueEnd) {
if (nameBegin == -1 || nameBegin == nameEnd) {
logger.debug("Skipping cookie with null name");
return null;
}
if (valueBegin == -1) {
logger.debug("Skipping cookie with null value");
return null;
}
CharSequence wrappedValue = CharBuffer.wrap(header, valueBegin, valueEnd);
CharSequence unwrappedValue = unwrapValue(wrappedValue);
if (unwrappedValue == null) {
logger.debug("Skipping cookie because starting quotes are not properly balanced in '{}'",
wrappedValue);
return null;
}
final String name = header.substring(nameBegin, nameEnd);
int invalidOctetPos;
if (strict && (invalidOctetPos = firstInvalidCookieNameOctet(name)) >= 0) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping cookie because name '{}' contains invalid char '{}'",
name, name.charAt(invalidOctetPos));
}
return null;
}
final boolean wrap = unwrappedValue.length() != valueEnd - valueBegin;
if (strict && (invalidOctetPos = firstInvalidCookieValueOctet(unwrappedValue)) >= 0) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping cookie because value '{}' contains invalid char '{}'",
unwrappedValue, unwrappedValue.charAt(invalidOctetPos));
}
return null;
}
DefaultCookie cookie = new DefaultCookie(name, unwrappedValue.toString());
cookie.setWrap(wrap);
return cookie;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieNameOctet;
import static io.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieValueOctet;
import static io.netty.handler.codec.http.cookie.CookieUtil.unwrapValue;
/**
* Parent of Client and Server side cookie encoders
*/
public abstract class CookieEncoder {
private final boolean strict;
protected CookieEncoder(boolean strict) {
this.strict = strict;
}
protected void validateCookie(String name, String value) {
if (strict) {
int pos;
if ((pos = firstInvalidCookieNameOctet(name)) >= 0) {
throw new IllegalArgumentException("Cookie name contains an invalid char: " + name.charAt(pos));
}
CharSequence unwrappedValue = unwrapValue(value);
if (unwrappedValue == null) {
throw new IllegalArgumentException("Cookie value wrapping quotes are not balanced: " + value);
}
if ((pos = firstInvalidCookieValueOctet(unwrappedValue)) >= 0) {
throw new IllegalArgumentException("Cookie value contains an invalid char: " + value.charAt(pos));
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -13,30 +13,20 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
package io.netty.handler.codec.http.cookie;
final class CookieHeaderNames {
static final String PATH = "Path";
public final class CookieHeaderNames {
public static final String PATH = "Path";
static final String EXPIRES = "Expires";
public static final String EXPIRES = "Expires";
static final String MAX_AGE = "Max-Age";
public static final String MAX_AGE = "Max-Age";
static final String DOMAIN = "Domain";
public static final String DOMAIN = "Domain";
static final String SECURE = "Secure";
public static final String SECURE = "Secure";
static final String HTTPONLY = "HTTPOnly";
static final String COMMENT = "Comment";
static final String COMMENTURL = "CommentURL";
static final String DISCARD = "Discard";
static final String PORT = "Port";
static final String VERSION = "Version";
public static final String HTTPONLY = "HTTPOnly";
private CookieHeaderNames() {
// Unused.

View File

@ -0,0 +1,158 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.util.internal.InternalThreadLocalMap;
import java.util.BitSet;
final class CookieUtil {
private static final BitSet VALID_COOKIE_VALUE_OCTETS = validCookieValueOctets();
private static final BitSet VALID_COOKIE_NAME_OCTETS = validCookieNameOctets(VALID_COOKIE_VALUE_OCTETS);
// US-ASCII characters excluding CTLs, whitespace, DQUOTE, comma, semicolon, and backslash
private static BitSet validCookieValueOctets() {
BitSet bits = new BitSet(8);
for (int i = 35; i < 127; i++) {
// US-ASCII characters excluding CTLs (%x00-1F / %x7F)
bits.set(i);
}
bits.set('"', false); // exclude DQUOTE = %x22
bits.set(',', false); // exclude comma = %x2C
bits.set(';', false); // exclude semicolon = %x3B
bits.set('\\', false); // exclude backslash = %x5C
return bits;
}
// token = 1*<any CHAR except CTLs or separators>
// separators = "(" | ")" | "<" | ">" | "@"
// | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "="
// | "{" | "}" | SP | HT
private static BitSet validCookieNameOctets(BitSet validCookieValueOctets) {
BitSet bits = new BitSet(8);
bits.or(validCookieValueOctets);
bits.set('(', false);
bits.set(')', false);
bits.set('<', false);
bits.set('>', false);
bits.set('@', false);
bits.set(':', false);
bits.set('/', false);
bits.set('[', false);
bits.set(']', false);
bits.set('?', false);
bits.set('=', false);
bits.set('{', false);
bits.set('}', false);
bits.set(' ', false);
bits.set('\t', false);
return bits;
}
static StringBuilder stringBuilder() {
return InternalThreadLocalMap.get().stringBuilder();
}
/**
* @param buf a buffer where some cookies were maybe encoded
* @return the buffer String without the trailing separator, or null if no cookie was appended.
*/
static String stripTrailingSeparatorOrNull(StringBuilder buf) {
return buf.length() == 0 ? null : stripTrailingSeparator(buf);
}
static String stripTrailingSeparator(StringBuilder buf) {
if (buf.length() > 0) {
buf.setLength(buf.length() - 2);
}
return buf.toString();
}
static void add(StringBuilder sb, String name, long val) {
sb.append(name);
sb.append((char) HttpConstants.EQUALS);
sb.append(val);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
static void add(StringBuilder sb, String name, String val) {
sb.append(name);
sb.append((char) HttpConstants.EQUALS);
sb.append(val);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
static void add(StringBuilder sb, String name) {
sb.append(name);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
static void addQuoted(StringBuilder sb, String name, String val) {
if (val == null) {
val = "";
}
sb.append(name);
sb.append((char) HttpConstants.EQUALS);
sb.append((char) HttpConstants.DOUBLE_QUOTE);
sb.append(val);
sb.append((char) HttpConstants.DOUBLE_QUOTE);
sb.append((char) HttpConstants.SEMICOLON);
sb.append((char) HttpConstants.SP);
}
static int firstInvalidCookieNameOctet(CharSequence cs) {
return firstInvalidOctet(cs, VALID_COOKIE_NAME_OCTETS);
}
static int firstInvalidCookieValueOctet(CharSequence cs) {
return firstInvalidOctet(cs, VALID_COOKIE_VALUE_OCTETS);
}
static int firstInvalidOctet(CharSequence cs, BitSet bits) {
for (int i = 0; i < cs.length(); i++) {
char c = cs.charAt(i);
if (!bits.get(c)) {
return i;
}
}
return -1;
}
static CharSequence unwrapValue(CharSequence cs) {
final int len = cs.length();
if (len > 0 && cs.charAt(0) == '"') {
if (len >= 2 && cs.charAt(len - 1) == '"') {
// properly balanced
return len == 2 ? "" : cs.subSequence(1, len - 1);
} else {
return null;
}
}
return cs;
}
private CookieUtil() {
// Unused
}
}

View File

@ -0,0 +1,268 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
* The default {@link Cookie} implementation.
*/
public class DefaultCookie implements Cookie {
private final String name;
private String value;
private boolean wrap;
private String domain;
private String path;
private long maxAge = Long.MIN_VALUE;
private boolean secure;
private boolean httpOnly;
/**
* Creates a new cookie with the specified name and value.
*/
public DefaultCookie(String name, String value) {
name = checkNotNull(name, "name").trim();
if (name.isEmpty()) {
throw new IllegalArgumentException("empty name");
}
for (int i = 0; i < name.length(); i ++) {
char c = name.charAt(i);
if (c > 127) {
throw new IllegalArgumentException(
"name contains non-ascii character: " + name);
}
// Check prohibited characters.
switch (c) {
case '\t': case '\n': case 0x0b: case '\f': case '\r':
case ' ': case ',': case ';': case '=':
throw new IllegalArgumentException(
"name contains one of the following prohibited characters: " +
"=,; \\t\\r\\n\\v\\f: " + name);
}
}
if (name.charAt(0) == '$') {
throw new IllegalArgumentException("name starting with '$' not allowed: " + name);
}
this.name = name;
setValue(value);
}
@Override
public String name() {
return name;
}
@Override
public String value() {
return value;
}
@Override
public void setValue(String value) {
this.value = checkNotNull(value, "value");
}
@Override
public boolean wrap() {
return wrap;
}
@Override
public void setWrap(boolean wrap) {
this.wrap = wrap;
}
@Override
public String domain() {
return domain;
}
@Override
public void setDomain(String domain) {
this.domain = validateValue("domain", domain);
}
@Override
public String path() {
return path;
}
@Override
public void setPath(String path) {
this.path = validateValue("path", path);
}
@Override
public long maxAge() {
return maxAge;
}
@Override
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public void setSecure(boolean secure) {
this.secure = secure;
}
@Override
public boolean isHttpOnly() {
return httpOnly;
}
@Override
public void setHttpOnly(boolean httpOnly) {
this.httpOnly = httpOnly;
}
@Override
public int hashCode() {
return name().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Cookie)) {
return false;
}
Cookie that = (Cookie) o;
if (!name().equalsIgnoreCase(that.name())) {
return false;
}
if (path() == null) {
if (that.path() != null) {
return false;
}
} else if (that.path() == null) {
return false;
} else if (!path().equals(that.path())) {
return false;
}
if (domain() == null) {
if (that.domain() != null) {
return false;
}
} else if (that.domain() == null) {
return false;
} else {
return domain().equalsIgnoreCase(that.domain());
}
return true;
}
@Override
public int compareTo(Cookie c) {
int v = name().compareToIgnoreCase(c.name());
if (v != 0) {
return v;
}
if (path() == null) {
if (c.path() != null) {
return -1;
}
} else if (c.path() == null) {
return 1;
} else {
v = path().compareTo(c.path());
if (v != 0) {
return v;
}
}
if (domain() == null) {
if (c.domain() != null) {
return -1;
}
} else if (c.domain() == null) {
return 1;
} else {
v = domain().compareToIgnoreCase(c.domain());
return v;
}
return 0;
}
@Override
public String toString() {
StringBuilder buf = stringBuilder()
.append(name())
.append('=')
.append(value());
if (domain() != null) {
buf.append(", domain=")
.append(domain());
}
if (path() != null) {
buf.append(", path=")
.append(path());
}
if (maxAge() >= 0) {
buf.append(", maxAge=")
.append(maxAge())
.append('s');
}
if (isSecure()) {
buf.append(", secure");
}
if (isHttpOnly()) {
buf.append(", HTTPOnly");
}
return buf.toString();
}
protected String validateValue(String name, String value) {
if (value == null) {
return null;
}
value = value.trim();
if (value.isEmpty()) {
return null;
}
for (int i = 0; i < value.length(); i ++) {
char c = value.charAt(i);
switch (c) {
case '\r': case '\n': case '\f': case 0x0b: case ';':
throw new IllegalArgumentException(
name + " contains one of the following prohibited characters: " +
";\\r\\n\\f\\v (" + value + ')');
}
}
return value;
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used server side.
*
* Only name and value fields are expected, so old fields are not populated (path, domain, etc).
*
* Old <a href="http://tools.ietf.org/html/rfc2965">RFC2965</a> cookies are still supported,
* old fields will simply be ignored.
*
* @see ServerCookieEncoder
*/
public final class ServerCookieDecoder extends CookieDecoder {
private static final String RFC2965_VERSION = "$Version";
private static final String RFC2965_PATH = "$" + CookieHeaderNames.PATH;
private static final String RFC2965_DOMAIN = "$" + CookieHeaderNames.DOMAIN;
private static final String RFC2965_PORT = "$Port";
/**
* Strict encoder that validates that name and value chars are in the valid scope
* defined in RFC6265
*/
public static final ServerCookieDecoder STRICT = new ServerCookieDecoder(true);
/**
* Lax instance that doesn't validate name and value
*/
public static final ServerCookieDecoder LAX = new ServerCookieDecoder(false);
private ServerCookieDecoder(boolean strict) {
super(strict);
}
/**
* Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
*
* @return the decoded {@link Cookie}
*/
public Set<Cookie> decode(String header) {
final int headerLen = checkNotNull(header, "header").length();
if (headerLen == 0) {
return Collections.emptySet();
}
Set<Cookie> cookies = new TreeSet<Cookie>();
int i = 0;
boolean rfc2965Style = false;
if (header.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) {
// RFC 2965 style cookie, move to after version value
i = header.indexOf(';') + 1;
rfc2965Style = true;
}
loop: for (;;) {
// Skip spaces and separators.
for (;;) {
if (i == headerLen) {
break loop;
}
char c = header.charAt(i);
if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
|| c == '\r' || c == ' ' || c == ',' || c == ';') {
i++;
continue;
}
break;
}
int nameBegin = i;
int nameEnd = i;
int valueBegin = -1;
int valueEnd = -1;
if (i != headerLen) {
keyValLoop: for (;;) {
char curChar = header.charAt(i);
if (curChar == ';') {
// NAME; (no value till ';')
nameEnd = i;
valueBegin = valueEnd = -1;
break keyValLoop;
} else if (curChar == '=') {
// NAME=VALUE
nameEnd = i;
i++;
if (i == headerLen) {
// NAME= (empty value, i.e. nothing after '=')
valueBegin = valueEnd = 0;
break keyValLoop;
}
valueBegin = i;
// NAME=VALUE;
int semiPos = header.indexOf(';', i);
valueEnd = i = semiPos > 0 ? semiPos : headerLen;
break keyValLoop;
} else {
i++;
}
if (i == headerLen) {
// NAME (no value till the end of string)
nameEnd = headerLen;
valueBegin = valueEnd = -1;
break;
}
}
}
if (rfc2965Style && (header.regionMatches(nameBegin, RFC2965_PATH, 0, RFC2965_PATH.length()) ||
header.regionMatches(nameBegin, RFC2965_DOMAIN, 0, RFC2965_DOMAIN.length()) ||
header.regionMatches(nameBegin, RFC2965_PORT, 0, RFC2965_PORT.length()))) {
// skip obsolete RFC2965 fields
continue;
}
DefaultCookie cookie = initCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
if (cookie != null) {
cookies.add(cookie);
}
}
return cookies;
}
}

View File

@ -0,0 +1,179 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import static io.netty.handler.codec.http.cookie.CookieUtil.*;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import io.netty.handler.codec.http.HttpRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie encoder to be used server side,
* so some fields are sent (Version is typically ignored).
*
* As Netty's Cookie merges Expires and MaxAge into one single field, only Max-Age field is sent.
*
* Note that multiple cookies are supposed to be sent at once in a single "Set-Cookie" header.
*
* <pre>
* // Example
* {@link HttpRequest} req = ...;
* res.setHeader("Cookie", {@link ServerCookieEncoder}.encode("JSESSIONID", "1234"));
* </pre>
*
* @see ServerCookieDecoder
*/
public final class ServerCookieEncoder extends CookieEncoder {
/**
* Strict encoder that validates that name and value chars are in the valid scope
* defined in RFC6265
*/
public static final ServerCookieEncoder STRICT = new ServerCookieEncoder(true);
/**
* Lax instance that doesn't validate name and value
*/
public static final ServerCookieEncoder LAX = new ServerCookieEncoder(false);
private ServerCookieEncoder(boolean strict) {
super(strict);
}
/**
* Encodes the specified cookie name-value pair into a Set-Cookie header value.
*
* @param name the cookie name
* @param value the cookie value
* @return a single Set-Cookie header value
*/
public String encode(String name, String value) {
return encode(new DefaultCookie(name, value));
}
/**
* Encodes the specified cookie into a Set-Cookie header value.
*
* @param cookie the cookie
* @return a single Set-Cookie header value
*/
public String encode(Cookie cookie) {
final String name = checkNotNull(cookie, "cookie").name();
final String value = cookie.value() != null ? cookie.value() : "";
validateCookie(name, value);
StringBuilder buf = stringBuilder();
if (cookie.wrap()) {
addQuoted(buf, name, value);
} else {
add(buf, name, value);
}
if (cookie.maxAge() != Long.MIN_VALUE) {
add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis());
add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires));
}
if (cookie.path() != null) {
add(buf, CookieHeaderNames.PATH, cookie.path());
}
if (cookie.domain() != null) {
add(buf, CookieHeaderNames.DOMAIN, cookie.domain());
}
if (cookie.isSecure()) {
add(buf, CookieHeaderNames.SECURE);
}
if (cookie.isHttpOnly()) {
add(buf, CookieHeaderNames.HTTPONLY);
}
return stripTrailingSeparator(buf);
}
/**
* Batch encodes cookies into Set-Cookie header values.
*
* @param cookies a bunch of cookies
* @return the corresponding bunch of Set-Cookie headers
*/
public List<String> encode(Cookie... cookies) {
if (checkNotNull(cookies, "cookies").length == 0) {
return Collections.emptyList();
}
List<String> encoded = new ArrayList<String>(cookies.length);
for (Cookie c : cookies) {
if (c == null) {
break;
}
encoded.add(encode(c));
}
return encoded;
}
/**
* Batch encodes cookies into Set-Cookie header values.
*
* @param cookies a bunch of cookies
* @return the corresponding bunch of Set-Cookie headers
*/
public List<String> encode(Collection<? extends Cookie> cookies) {
if (checkNotNull(cookies, "cookies").isEmpty()) {
return Collections.emptyList();
}
List<String> encoded = new ArrayList<String>(cookies.size());
for (Cookie c : cookies) {
if (c == null) {
break;
}
encoded.add(encode(c));
}
return encoded;
}
/**
* Batch encodes cookies into Set-Cookie header values.
*
* @param cookies a bunch of cookies
* @return the corresponding bunch of Set-Cookie headers
*/
public List<String> encode(Iterable<? extends Cookie> cookies) {
if (!checkNotNull(cookies, "cookies").iterator().hasNext()) {
return Collections.emptyList();
}
List<String> encoded = new ArrayList<String>();
for (Cookie c : cookies) {
if (c == null) {
break;
}
encoded.add(encode(c));
}
return encoded;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* This package contains Cookie related classes.
*/
package io.netty.handler.codec.http.cookie;

View File

@ -1,228 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import org.junit.Test;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import static org.junit.Assert.*;
public class ServerCookieDecoderTest {
@Test
public void testDecodingSingleCookie() {
String cookieString = "myCookie=myValue";
cookieString = cookieString.replace("XXX",
HttpHeaderDateFormat.get().format(new Date(System.currentTimeMillis() + 50000)));
Set<Cookie> cookies = ServerCookieDecoder.decode(cookieString);
assertEquals(1, cookies.size());
Cookie cookie = cookies.iterator().next();
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
}
@Test
public void testDecodingMultipleCookies() {
String c1 = "myCookie=myValue;";
String c2 = "myCookie2=myValue2;";
String c3 = "myCookie3=myValue3;";
Set<Cookie> cookies = ServerCookieDecoder.decode(c1 + c2 + c3);
assertEquals(3, cookies.size());
Iterator<Cookie> it = cookies.iterator();
Cookie cookie = it.next();
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
cookie = it.next();
assertNotNull(cookie);
assertEquals("myValue2", cookie.value());
cookie = it.next();
assertNotNull(cookie);
assertEquals("myValue3", cookie.value());
}
@Test
public void testDecodingQuotedCookie() {
String source =
"a=\"\";" +
"b=\"1\";" +
"c=\"\\\"1\\\"2\\\"\";" +
"d=\"1\\\"2\\\"3\";" +
"e=\"\\\"\\\"\";" +
"f=\"1\\\"\\\"2\";" +
"g=\"\\\\\";" +
"h=\"';,\\x\"";
Set<Cookie> cookies = ServerCookieDecoder.decode(source);
Iterator<Cookie> it = cookies.iterator();
Cookie c;
c = it.next();
assertEquals("a", c.name());
assertEquals("", c.value());
c = it.next();
assertEquals("b", c.name());
assertEquals("1", c.value());
c = it.next();
assertEquals("c", c.name());
assertEquals("\"1\"2\"", c.value());
c = it.next();
assertEquals("d", c.name());
assertEquals("1\"2\"3", c.value());
c = it.next();
assertEquals("e", c.name());
assertEquals("\"\"", c.value());
c = it.next();
assertEquals("f", c.name());
assertEquals("1\"\"2", c.value());
c = it.next();
assertEquals("g", c.name());
assertEquals("\\", c.value());
c = it.next();
assertEquals("h", c.name());
assertEquals("';,\\x", c.value());
assertFalse(it.hasNext());
}
@Test
public void testDecodingGoogleAnalyticsCookie() {
String source =
"ARPT=LWUKQPSWRTUN04CKKJI; " +
"kw-2E343B92-B097-442c-BFA5-BE371E0325A2=unfinished furniture; " +
"__utma=48461872.1094088325.1258140131.1258140131.1258140131.1; " +
"__utmb=48461872.13.10.1258140131; __utmc=48461872; " +
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
Set<Cookie> cookies = ServerCookieDecoder.decode(source);
Iterator<Cookie> it = cookies.iterator();
Cookie c;
c = it.next();
assertEquals("__utma", c.name());
assertEquals("48461872.1094088325.1258140131.1258140131.1258140131.1", c.value());
c = it.next();
assertEquals("__utmb", c.name());
assertEquals("48461872.13.10.1258140131", c.value());
c = it.next();
assertEquals("__utmc", c.name());
assertEquals("48461872", c.value());
c = it.next();
assertEquals("__utmz", c.name());
assertEquals("48461872.1258140131.1.1.utmcsr=overstock.com|" +
"utmccn=(referral)|utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html",
c.value());
c = it.next();
assertEquals("ARPT", c.name());
assertEquals("LWUKQPSWRTUN04CKKJI", c.value());
c = it.next();
assertEquals("kw-2E343B92-B097-442c-BFA5-BE371E0325A2", c.name());
assertEquals("unfinished furniture", c.value());
assertFalse(it.hasNext());
}
@Test
public void testDecodingLongValue() {
String longValue =
"b!!!$Q!!$ha!!<NC=MN(F!!%#4!!<NC=MN(F!!2!d!!!!#=IvZB!!2,F!!!!'=KqtH!!2-9!!!!" +
"'=IvZM!!3f:!!!!$=HbQW!!3g'!!!!%=J^wI!!3g-!!!!%=J^wI!!3g1!!!!$=HbQW!!3g2!!!!" +
"$=HbQW!!3g5!!!!%=J^wI!!3g9!!!!$=HbQW!!3gT!!!!$=HbQW!!3gX!!!!#=J^wI!!3gY!!!!" +
"#=J^wI!!3gh!!!!$=HbQW!!3gj!!!!$=HbQW!!3gr!!!!$=HbQW!!3gx!!!!#=J^wI!!3h!!!!!" +
"$=HbQW!!3h$!!!!#=J^wI!!3h'!!!!$=HbQW!!3h,!!!!$=HbQW!!3h0!!!!%=J^wI!!3h1!!!!" +
"#=J^wI!!3h2!!!!$=HbQW!!3h4!!!!$=HbQW!!3h7!!!!$=HbQW!!3h8!!!!%=J^wI!!3h:!!!!" +
"#=J^wI!!3h@!!!!%=J^wI!!3hB!!!!$=HbQW!!3hC!!!!$=HbQW!!3hL!!!!$=HbQW!!3hQ!!!!" +
"$=HbQW!!3hS!!!!%=J^wI!!3hU!!!!$=HbQW!!3h[!!!!$=HbQW!!3h^!!!!$=HbQW!!3hd!!!!" +
"%=J^wI!!3he!!!!%=J^wI!!3hf!!!!%=J^wI!!3hg!!!!$=HbQW!!3hh!!!!%=J^wI!!3hi!!!!" +
"%=J^wI!!3hv!!!!$=HbQW!!3i/!!!!#=J^wI!!3i2!!!!#=J^wI!!3i3!!!!%=J^wI!!3i4!!!!" +
"$=HbQW!!3i7!!!!$=HbQW!!3i8!!!!$=HbQW!!3i9!!!!%=J^wI!!3i=!!!!#=J^wI!!3i>!!!!" +
"%=J^wI!!3iD!!!!$=HbQW!!3iF!!!!#=J^wI!!3iH!!!!%=J^wI!!3iM!!!!%=J^wI!!3iS!!!!" +
"#=J^wI!!3iU!!!!%=J^wI!!3iZ!!!!#=J^wI!!3i]!!!!%=J^wI!!3ig!!!!%=J^wI!!3ij!!!!" +
"%=J^wI!!3ik!!!!#=J^wI!!3il!!!!$=HbQW!!3in!!!!%=J^wI!!3ip!!!!$=HbQW!!3iq!!!!" +
"$=HbQW!!3it!!!!%=J^wI!!3ix!!!!#=J^wI!!3j!!!!!$=HbQW!!3j%!!!!$=HbQW!!3j'!!!!" +
"%=J^wI!!3j(!!!!%=J^wI!!9mJ!!!!'=KqtH!!=SE!!<NC=MN(F!!?VS!!<NC=MN(F!!Zw`!!!!" +
"%=KqtH!!j+C!!<NC=MN(F!!j+M!!<NC=MN(F!!j+a!!<NC=MN(F!!j,.!!<NC=MN(F!!n>M!!!!" +
"'=KqtH!!s1X!!!!$=MMyc!!s1_!!!!#=MN#O!!ypn!!!!'=KqtH!!ypr!!!!'=KqtH!#%h!!!!!" +
"%=KqtH!#%o!!!!!'=KqtH!#)H6!!<NC=MN(F!#*%'!!!!%=KqtH!#+k(!!!!'=KqtH!#-E!!!!!" +
"'=KqtH!#1)w!!!!'=KqtH!#1)y!!!!'=KqtH!#1*M!!!!#=KqtH!#1*p!!!!'=KqtH!#14Q!!<N" +
"C=MN(F!#14S!!<NC=MN(F!#16I!!<NC=MN(F!#16N!!<NC=MN(F!#16X!!<NC=MN(F!#16k!!<N" +
"C=MN(F!#17@!!<NC=MN(F!#17A!!<NC=MN(F!#1Cq!!!!'=KqtH!#7),!!!!#=KqtH!#7)b!!!!" +
"#=KqtH!#7Ww!!!!'=KqtH!#?cQ!!!!'=KqtH!#His!!!!'=KqtH!#Jrh!!!!'=KqtH!#O@M!!<N" +
"C=MN(F!#O@O!!<NC=MN(F!#OC6!!<NC=MN(F!#Os.!!!!#=KqtH!#YOW!!!!#=H/Li!#Zat!!!!" +
"'=KqtH!#ZbI!!!!%=KqtH!#Zbc!!!!'=KqtH!#Zbs!!!!%=KqtH!#Zby!!!!'=KqtH!#Zce!!!!" +
"'=KqtH!#Zdc!!!!%=KqtH!#Zea!!!!'=KqtH!#ZhI!!!!#=KqtH!#ZiD!!!!'=KqtH!#Zis!!!!" +
"'=KqtH!#Zj0!!!!#=KqtH!#Zj1!!!!'=KqtH!#Zj[!!!!'=KqtH!#Zj]!!!!'=KqtH!#Zj^!!!!" +
"'=KqtH!#Zjb!!!!'=KqtH!#Zk!!!!!'=KqtH!#Zk6!!!!#=KqtH!#Zk9!!!!%=KqtH!#Zk<!!!!" +
"'=KqtH!#Zl>!!!!'=KqtH!#]9R!!!!$=H/Lt!#]I6!!!!#=KqtH!#]Z#!!!!%=KqtH!#^*N!!!!" +
"#=KqtH!#^:m!!!!#=KqtH!#_*_!!!!%=J^wI!#`-7!!!!#=KqtH!#`T>!!!!'=KqtH!#`T?!!!!" +
"'=KqtH!#`TA!!!!'=KqtH!#`TB!!!!'=KqtH!#`TG!!!!'=KqtH!#`TP!!!!#=KqtH!#`U,!!!!" +
"'=KqtH!#`U/!!!!'=KqtH!#`U0!!!!#=KqtH!#`U9!!!!'=KqtH!#aEQ!!!!%=KqtH!#b<)!!!!" +
"'=KqtH!#c9-!!!!%=KqtH!#dxC!!!!%=KqtH!#dxE!!!!%=KqtH!#ev$!!!!'=KqtH!#fBi!!!!" +
"#=KqtH!#fBj!!!!'=KqtH!#fG)!!!!'=KqtH!#fG+!!!!'=KqtH!#g<d!!!!'=KqtH!#g<e!!!!" +
"'=KqtH!#g=J!!!!'=KqtH!#gat!!!!#=KqtH!#s`D!!!!#=J_#p!#sg?!!!!#=J_#p!#t<a!!!!" +
"#=KqtH!#t<c!!!!#=KqtH!#trY!!!!$=JiYj!#vA$!!!!'=KqtH!#xs_!!!!'=KqtH!$$rO!!!!" +
"#=KqtH!$$rP!!!!#=KqtH!$(!%!!!!'=KqtH!$)]o!!!!%=KqtH!$,@)!!!!'=KqtH!$,k]!!!!" +
"'=KqtH!$1]+!!!!%=KqtH!$3IO!!!!%=KqtH!$3J#!!!!'=KqtH!$3J.!!!!'=KqtH!$3J:!!!!" +
"#=KqtH!$3JH!!!!#=KqtH!$3JI!!!!#=KqtH!$3JK!!!!%=KqtH!$3JL!!!!'=KqtH!$3JS!!!!" +
"'=KqtH!$8+M!!!!#=KqtH!$99d!!!!%=KqtH!$:Lw!!!!#=LK+x!$:N@!!!!#=KqtG!$:NC!!!!" +
"#=KqtG!$:hW!!!!'=KqtH!$:i[!!!!'=KqtH!$:ih!!!!'=KqtH!$:it!!!!'=KqtH!$:kO!!!!" +
"'=KqtH!$>*B!!!!'=KqtH!$>hD!!!!+=J^x0!$?lW!!!!'=KqtH!$?ll!!!!'=KqtH!$?lm!!!!" +
"%=KqtH!$?mi!!!!'=KqtH!$?mx!!!!'=KqtH!$D7]!!!!#=J_#p!$D@T!!!!#=J_#p!$V<g!!!!" +
"'=KqtH";
Set<Cookie> cookies = ServerCookieDecoder.decode("bh=\"" + longValue + "\";");
assertEquals(1, cookies.size());
Cookie c = cookies.iterator().next();
assertEquals("bh", c.name());
assertEquals(longValue, c.value());
}
@Test
public void testDecodingOldRFC2965Cookies() {
String source = "$Version=\"1\"; " +
"Part_Number1=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " +
"Part_Number2=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
Set<Cookie> cookies = ServerCookieDecoder.decode(source);
Iterator<Cookie> it = cookies.iterator();
Cookie c;
c = it.next();
assertEquals("Part_Number1", c.name());
assertEquals("Riding_Rocket_0023", c.value());
c = it.next();
assertEquals("Part_Number2", c.name());
assertEquals("Rocket_Launcher_0001", c.value());
assertFalse(it.hasNext());
}
}

View File

@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
package io.netty.handler.codec.http.cookie;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -22,6 +22,10 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
@ -29,8 +33,6 @@ import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
import org.junit.Test;
public class ClientCookieDecoderTest {
@Test
public void testDecodingSingleCookieV0() {
@ -38,7 +40,7 @@ public class ClientCookieDecoderTest {
cookieString = cookieString.replace("XXX", HttpHeaderDateFormat.get()
.format(new Date(System.currentTimeMillis() + 50000)));
Cookie cookie = ClientCookieDecoder.decode(cookieString);
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
assertEquals(".adomainsomewhere", cookie.domain());
@ -63,7 +65,7 @@ public class ClientCookieDecoderTest {
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;" +
"domain=.adomainsomewhere;secure;comment=this is a comment;version=0;" +
"commentURL=http://aurl.com;port=\"80,8080\";discard;";
Cookie cookie = ClientCookieDecoder.decode(cookieString);
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
assertEquals(".adomainsomewhere", cookie.domain());
@ -76,7 +78,7 @@ public class ClientCookieDecoderTest {
public void testDecodingSingleCookieV1() {
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;domain=.adomainsomewhere"
+ ";secure;comment=this is a comment;version=1;";
Cookie cookie = ClientCookieDecoder.decode(cookieString);
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertEquals("myValue", cookie.value());
assertNotNull(cookie);
assertEquals(".adomainsomewhere", cookie.domain());
@ -90,7 +92,7 @@ public class ClientCookieDecoderTest {
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;"
+ "domain=.adomainsomewhere;secure;comment=this is a comment;version=1;"
+ "commentURL=http://aurl.com;port='80,8080';discard;";
Cookie cookie = ClientCookieDecoder.decode(cookieString);
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
assertEquals(".adomainsomewhere", cookie.domain());
@ -104,7 +106,7 @@ public class ClientCookieDecoderTest {
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;"
+ "domain=.adomainsomewhere;secure;comment=this is a comment;version=2;"
+ "commentURL=http://aurl.com;port=\"80,8080\";discard;";
Cookie cookie = ClientCookieDecoder.decode(cookieString);
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
assertEquals(".adomainsomewhere", cookie.domain());
@ -119,7 +121,7 @@ public class ClientCookieDecoderTest {
+ "domain=.adomainsomewhere;secure;comment=this is a comment;version=2;"
+ "commentURL=\"http://aurl.com\";port='80,8080';discard;";
Cookie cookie = ClientCookieDecoder.decode(c1);
Cookie cookie = ClientCookieDecoder.STRICT.decode(c1);
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
assertEquals(".adomainsomewhere", cookie.domain());
@ -133,16 +135,10 @@ public class ClientCookieDecoderTest {
Collection<String> sources = new ArrayList<String>();
sources.add("a=\"\",");
sources.add("b=\"1\",");
sources.add("c=\"\\\"1\\\"2\\\"\",");
sources.add("d=\"1\\\"2\\\"3\",");
sources.add("e=\"\\\"\\\"\",");
sources.add("f=\"1\\\"\\\"2\",");
sources.add("g=\"\\\\\",");
sources.add("h=\"';,\\x\"");
Collection<Cookie> cookies = new ArrayList<Cookie>();
for (String source : sources) {
cookies.add(ClientCookieDecoder.decode(source));
cookies.add(ClientCookieDecoder.STRICT.decode(source));
}
Iterator<Cookie> it = cookies.iterator();
@ -156,30 +152,6 @@ public class ClientCookieDecoderTest {
assertEquals("b", c.name());
assertEquals("1", c.value());
c = it.next();
assertEquals("c", c.name());
assertEquals("\"1\"2\"", c.value());
c = it.next();
assertEquals("d", c.name());
assertEquals("1\"2\"3", c.value());
c = it.next();
assertEquals("e", c.name());
assertEquals("\"\"", c.value());
c = it.next();
assertEquals("f", c.name());
assertEquals("1\"\"2", c.value());
c = it.next();
assertEquals("g", c.name());
assertEquals("\\", c.value());
c = it.next();
assertEquals("h", c.name());
assertEquals("';,\\x", c.value());
assertFalse(it.hasNext());
}
@ -191,7 +163,7 @@ public class ClientCookieDecoderTest {
+ "__utmb=48461872.13.10.1258140131; __utmc=48461872; "
+ "__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|"
+ "utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
Cookie cookie = ClientCookieDecoder.decode(source);
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
assertEquals("ARPT", cookie.name());
assertEquals("LWUKQPSWRTUN04CKKJI", cookie.value());
@ -206,27 +178,25 @@ public class ClientCookieDecoderTest {
String source = "Format=EU; expires=Fri, 31-Dec-9999 23:59:59 GMT; path=/";
Cookie cookie = ClientCookieDecoder.decode(source);
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2);
}
@Test
public void testDecodingValueWithComma() {
public void testDecodingValueWithCommaFails() {
String source = "UserCookie=timeZoneName=(GMT+04:00) Moscow, St. Petersburg, Volgograd&promocode=&region=BE;"
+ " expires=Sat, 01-Dec-2012 10:53:31 GMT; path=/";
Cookie cookie = ClientCookieDecoder.decode(source);
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
assertEquals(
"timeZoneName=(GMT+04:00) Moscow, St. Petersburg, Volgograd&promocode=&region=BE",
cookie.value());
assertNull(cookie);
}
@Test
public void testDecodingWeirdNames1() {
String src = "path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com";
Cookie cookie = ClientCookieDecoder.decode(src);
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
assertEquals("path", cookie.name());
assertEquals("", cookie.value());
assertEquals("/", cookie.path());
@ -235,67 +205,67 @@ public class ClientCookieDecoderTest {
@Test
public void testDecodingWeirdNames2() {
String src = "HTTPOnly=";
Cookie cookie = ClientCookieDecoder.decode(src);
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
assertEquals("HTTPOnly", cookie.name());
assertEquals("", cookie.value());
}
@Test
public void testDecodingValuesWithCommasAndEquals() {
public void testDecodingValuesWithCommasAndEqualsFails() {
String src = "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E";
Cookie cookie = ClientCookieDecoder.decode(src);
assertEquals("A", cookie.name());
assertEquals("v=1&lg=en-US,it-IT,it&intl=it&np=1", cookie.value());
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
assertNull(cookie);
}
@Test
public void testDecodingLongValue() {
String longValue = "b!!!$Q!!$ha!!<NC=MN(F!!%#4!!<NC=MN(F!!2!d!!!!#=IvZB!!2,F!!!!'=KqtH!!2-9!!!!"
+ "'=IvZM!!3f:!!!!$=HbQW!!3g'!!!!%=J^wI!!3g-!!!!%=J^wI!!3g1!!!!$=HbQW!!3g2!!!!"
+ "$=HbQW!!3g5!!!!%=J^wI!!3g9!!!!$=HbQW!!3gT!!!!$=HbQW!!3gX!!!!#=J^wI!!3gY!!!!"
+ "#=J^wI!!3gh!!!!$=HbQW!!3gj!!!!$=HbQW!!3gr!!!!$=HbQW!!3gx!!!!#=J^wI!!3h!!!!!"
+ "$=HbQW!!3h$!!!!#=J^wI!!3h'!!!!$=HbQW!!3h,!!!!$=HbQW!!3h0!!!!%=J^wI!!3h1!!!!"
+ "#=J^wI!!3h2!!!!$=HbQW!!3h4!!!!$=HbQW!!3h7!!!!$=HbQW!!3h8!!!!%=J^wI!!3h:!!!!"
+ "#=J^wI!!3h@!!!!%=J^wI!!3hB!!!!$=HbQW!!3hC!!!!$=HbQW!!3hL!!!!$=HbQW!!3hQ!!!!"
+ "$=HbQW!!3hS!!!!%=J^wI!!3hU!!!!$=HbQW!!3h[!!!!$=HbQW!!3h^!!!!$=HbQW!!3hd!!!!"
+ "%=J^wI!!3he!!!!%=J^wI!!3hf!!!!%=J^wI!!3hg!!!!$=HbQW!!3hh!!!!%=J^wI!!3hi!!!!"
+ "%=J^wI!!3hv!!!!$=HbQW!!3i/!!!!#=J^wI!!3i2!!!!#=J^wI!!3i3!!!!%=J^wI!!3i4!!!!"
+ "$=HbQW!!3i7!!!!$=HbQW!!3i8!!!!$=HbQW!!3i9!!!!%=J^wI!!3i=!!!!#=J^wI!!3i>!!!!"
+ "%=J^wI!!3iD!!!!$=HbQW!!3iF!!!!#=J^wI!!3iH!!!!%=J^wI!!3iM!!!!%=J^wI!!3iS!!!!"
+ "#=J^wI!!3iU!!!!%=J^wI!!3iZ!!!!#=J^wI!!3i]!!!!%=J^wI!!3ig!!!!%=J^wI!!3ij!!!!"
+ "%=J^wI!!3ik!!!!#=J^wI!!3il!!!!$=HbQW!!3in!!!!%=J^wI!!3ip!!!!$=HbQW!!3iq!!!!"
+ "$=HbQW!!3it!!!!%=J^wI!!3ix!!!!#=J^wI!!3j!!!!!$=HbQW!!3j%!!!!$=HbQW!!3j'!!!!"
+ "%=J^wI!!3j(!!!!%=J^wI!!9mJ!!!!'=KqtH!!=SE!!<NC=MN(F!!?VS!!<NC=MN(F!!Zw`!!!!"
+ "%=KqtH!!j+C!!<NC=MN(F!!j+M!!<NC=MN(F!!j+a!!<NC=MN(F!!j,.!!<NC=MN(F!!n>M!!!!"
+ "'=KqtH!!s1X!!!!$=MMyc!!s1_!!!!#=MN#O!!ypn!!!!'=KqtH!!ypr!!!!'=KqtH!#%h!!!!!"
+ "%=KqtH!#%o!!!!!'=KqtH!#)H6!!<NC=MN(F!#*%'!!!!%=KqtH!#+k(!!!!'=KqtH!#-E!!!!!"
+ "'=KqtH!#1)w!!!!'=KqtH!#1)y!!!!'=KqtH!#1*M!!!!#=KqtH!#1*p!!!!'=KqtH!#14Q!!<N"
+ "C=MN(F!#14S!!<NC=MN(F!#16I!!<NC=MN(F!#16N!!<NC=MN(F!#16X!!<NC=MN(F!#16k!!<N"
+ "C=MN(F!#17@!!<NC=MN(F!#17A!!<NC=MN(F!#1Cq!!!!'=KqtH!#7),!!!!#=KqtH!#7)b!!!!"
+ "#=KqtH!#7Ww!!!!'=KqtH!#?cQ!!!!'=KqtH!#His!!!!'=KqtH!#Jrh!!!!'=KqtH!#O@M!!<N"
+ "C=MN(F!#O@O!!<NC=MN(F!#OC6!!<NC=MN(F!#Os.!!!!#=KqtH!#YOW!!!!#=H/Li!#Zat!!!!"
+ "'=KqtH!#ZbI!!!!%=KqtH!#Zbc!!!!'=KqtH!#Zbs!!!!%=KqtH!#Zby!!!!'=KqtH!#Zce!!!!"
+ "'=KqtH!#Zdc!!!!%=KqtH!#Zea!!!!'=KqtH!#ZhI!!!!#=KqtH!#ZiD!!!!'=KqtH!#Zis!!!!"
+ "'=KqtH!#Zj0!!!!#=KqtH!#Zj1!!!!'=KqtH!#Zj[!!!!'=KqtH!#Zj]!!!!'=KqtH!#Zj^!!!!"
+ "'=KqtH!#Zjb!!!!'=KqtH!#Zk!!!!!'=KqtH!#Zk6!!!!#=KqtH!#Zk9!!!!%=KqtH!#Zk<!!!!"
+ "'=KqtH!#Zl>!!!!'=KqtH!#]9R!!!!$=H/Lt!#]I6!!!!#=KqtH!#]Z#!!!!%=KqtH!#^*N!!!!"
+ "#=KqtH!#^:m!!!!#=KqtH!#_*_!!!!%=J^wI!#`-7!!!!#=KqtH!#`T>!!!!'=KqtH!#`T?!!!!"
+ "'=KqtH!#`TA!!!!'=KqtH!#`TB!!!!'=KqtH!#`TG!!!!'=KqtH!#`TP!!!!#=KqtH!#`U,!!!!"
+ "'=KqtH!#`U/!!!!'=KqtH!#`U0!!!!#=KqtH!#`U9!!!!'=KqtH!#aEQ!!!!%=KqtH!#b<)!!!!"
+ "'=KqtH!#c9-!!!!%=KqtH!#dxC!!!!%=KqtH!#dxE!!!!%=KqtH!#ev$!!!!'=KqtH!#fBi!!!!"
+ "#=KqtH!#fBj!!!!'=KqtH!#fG)!!!!'=KqtH!#fG+!!!!'=KqtH!#g<d!!!!'=KqtH!#g<e!!!!"
+ "'=KqtH!#g=J!!!!'=KqtH!#gat!!!!#=KqtH!#s`D!!!!#=J_#p!#sg?!!!!#=J_#p!#t<a!!!!"
+ "#=KqtH!#t<c!!!!#=KqtH!#trY!!!!$=JiYj!#vA$!!!!'=KqtH!#xs_!!!!'=KqtH!$$rO!!!!"
+ "#=KqtH!$$rP!!!!#=KqtH!$(!%!!!!'=KqtH!$)]o!!!!%=KqtH!$,@)!!!!'=KqtH!$,k]!!!!"
+ "'=KqtH!$1]+!!!!%=KqtH!$3IO!!!!%=KqtH!$3J#!!!!'=KqtH!$3J.!!!!'=KqtH!$3J:!!!!"
+ "#=KqtH!$3JH!!!!#=KqtH!$3JI!!!!#=KqtH!$3JK!!!!%=KqtH!$3JL!!!!'=KqtH!$3JS!!!!"
+ "'=KqtH!$8+M!!!!#=KqtH!$99d!!!!%=KqtH!$:Lw!!!!#=LK+x!$:N@!!!!#=KqtG!$:NC!!!!"
+ "#=KqtG!$:hW!!!!'=KqtH!$:i[!!!!'=KqtH!$:ih!!!!'=KqtH!$:it!!!!'=KqtH!$:kO!!!!"
+ "'=KqtH!$>*B!!!!'=KqtH!$>hD!!!!+=J^x0!$?lW!!!!'=KqtH!$?ll!!!!'=KqtH!$?lm!!!!"
+ "%=KqtH!$?mi!!!!'=KqtH!$?mx!!!!'=KqtH!$D7]!!!!#=J_#p!$D@T!!!!#=J_#p!$V<g!!!!"
+ "'=KqtH";
String longValue =
"b___$Q__$ha__<NC=MN(F__%#4__<NC=MN(F__2_d____#=IvZB__2_F____'=KqtH__2-9____" +
"'=IvZM__3f:____$=HbQW__3g'____%=J^wI__3g-____%=J^wI__3g1____$=HbQW__3g2____" +
"$=HbQW__3g5____%=J^wI__3g9____$=HbQW__3gT____$=HbQW__3gX____#=J^wI__3gY____" +
"#=J^wI__3gh____$=HbQW__3gj____$=HbQW__3gr____$=HbQW__3gx____#=J^wI__3h_____" +
"$=HbQW__3h$____#=J^wI__3h'____$=HbQW__3h_____$=HbQW__3h0____%=J^wI__3h1____" +
"#=J^wI__3h2____$=HbQW__3h4____$=HbQW__3h7____$=HbQW__3h8____%=J^wI__3h:____" +
"#=J^wI__3h@____%=J^wI__3hB____$=HbQW__3hC____$=HbQW__3hL____$=HbQW__3hQ____" +
"$=HbQW__3hS____%=J^wI__3hU____$=HbQW__3h[____$=HbQW__3h^____$=HbQW__3hd____" +
"%=J^wI__3he____%=J^wI__3hf____%=J^wI__3hg____$=HbQW__3hh____%=J^wI__3hi____" +
"%=J^wI__3hv____$=HbQW__3i/____#=J^wI__3i2____#=J^wI__3i3____%=J^wI__3i4____" +
"$=HbQW__3i7____$=HbQW__3i8____$=HbQW__3i9____%=J^wI__3i=____#=J^wI__3i>____" +
"%=J^wI__3iD____$=HbQW__3iF____#=J^wI__3iH____%=J^wI__3iM____%=J^wI__3iS____" +
"#=J^wI__3iU____%=J^wI__3iZ____#=J^wI__3i]____%=J^wI__3ig____%=J^wI__3ij____" +
"%=J^wI__3ik____#=J^wI__3il____$=HbQW__3in____%=J^wI__3ip____$=HbQW__3iq____" +
"$=HbQW__3it____%=J^wI__3ix____#=J^wI__3j_____$=HbQW__3j%____$=HbQW__3j'____" +
"%=J^wI__3j(____%=J^wI__9mJ____'=KqtH__=SE__<NC=MN(F__?VS__<NC=MN(F__Zw`____" +
"%=KqtH__j+C__<NC=MN(F__j+M__<NC=MN(F__j+a__<NC=MN(F__j_.__<NC=MN(F__n>M____" +
"'=KqtH__s1X____$=MMyc__s1_____#=MN#O__ypn____'=KqtH__ypr____'=KqtH_#%h_____" +
"%=KqtH_#%o_____'=KqtH_#)H6__<NC=MN(F_#*%'____%=KqtH_#+k(____'=KqtH_#-E_____" +
"'=KqtH_#1)w____'=KqtH_#1)y____'=KqtH_#1*M____#=KqtH_#1*p____'=KqtH_#14Q__<N" +
"C=MN(F_#14S__<NC=MN(F_#16I__<NC=MN(F_#16N__<NC=MN(F_#16X__<NC=MN(F_#16k__<N" +
"C=MN(F_#17@__<NC=MN(F_#17A__<NC=MN(F_#1Cq____'=KqtH_#7)_____#=KqtH_#7)b____" +
"#=KqtH_#7Ww____'=KqtH_#?cQ____'=KqtH_#His____'=KqtH_#Jrh____'=KqtH_#O@M__<N" +
"C=MN(F_#O@O__<NC=MN(F_#OC6__<NC=MN(F_#Os.____#=KqtH_#YOW____#=H/Li_#Zat____" +
"'=KqtH_#ZbI____%=KqtH_#Zbc____'=KqtH_#Zbs____%=KqtH_#Zby____'=KqtH_#Zce____" +
"'=KqtH_#Zdc____%=KqtH_#Zea____'=KqtH_#ZhI____#=KqtH_#ZiD____'=KqtH_#Zis____" +
"'=KqtH_#Zj0____#=KqtH_#Zj1____'=KqtH_#Zj[____'=KqtH_#Zj]____'=KqtH_#Zj^____" +
"'=KqtH_#Zjb____'=KqtH_#Zk_____'=KqtH_#Zk6____#=KqtH_#Zk9____%=KqtH_#Zk<____" +
"'=KqtH_#Zl>____'=KqtH_#]9R____$=H/Lt_#]I6____#=KqtH_#]Z#____%=KqtH_#^*N____" +
"#=KqtH_#^:m____#=KqtH_#_*_____%=J^wI_#`-7____#=KqtH_#`T>____'=KqtH_#`T?____" +
"'=KqtH_#`TA____'=KqtH_#`TB____'=KqtH_#`TG____'=KqtH_#`TP____#=KqtH_#`U_____" +
"'=KqtH_#`U/____'=KqtH_#`U0____#=KqtH_#`U9____'=KqtH_#aEQ____%=KqtH_#b<)____" +
"'=KqtH_#c9-____%=KqtH_#dxC____%=KqtH_#dxE____%=KqtH_#ev$____'=KqtH_#fBi____" +
"#=KqtH_#fBj____'=KqtH_#fG)____'=KqtH_#fG+____'=KqtH_#g<d____'=KqtH_#g<e____" +
"'=KqtH_#g=J____'=KqtH_#gat____#=KqtH_#s`D____#=J_#p_#sg?____#=J_#p_#t<a____" +
"#=KqtH_#t<c____#=KqtH_#trY____$=JiYj_#vA$____'=KqtH_#xs_____'=KqtH_$$rO____" +
"#=KqtH_$$rP____#=KqtH_$(_%____'=KqtH_$)]o____%=KqtH_$_@)____'=KqtH_$_k]____" +
"'=KqtH_$1]+____%=KqtH_$3IO____%=KqtH_$3J#____'=KqtH_$3J.____'=KqtH_$3J:____" +
"#=KqtH_$3JH____#=KqtH_$3JI____#=KqtH_$3JK____%=KqtH_$3JL____'=KqtH_$3JS____" +
"'=KqtH_$8+M____#=KqtH_$99d____%=KqtH_$:Lw____#=LK+x_$:N@____#=KqtG_$:NC____" +
"#=KqtG_$:hW____'=KqtH_$:i[____'=KqtH_$:ih____'=KqtH_$:it____'=KqtH_$:kO____" +
"'=KqtH_$>*B____'=KqtH_$>hD____+=J^x0_$?lW____'=KqtH_$?ll____'=KqtH_$?lm____" +
"%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V<g____" +
"'=KqtH";
Cookie cookie = ClientCookieDecoder.decode("bh=\"" + longValue
Cookie cookie = ClientCookieDecoder.STRICT.decode("bh=\"" + longValue
+ "\";");
assertEquals("bh", cookie.name());
assertEquals(longValue, cookie.value());
@ -304,7 +274,7 @@ public class ClientCookieDecoderTest {
@Test
public void testIgnoreEmptyDomain() {
String emptyDomain = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/";
Cookie cookie = ClientCookieDecoder.decode(emptyDomain);
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyDomain);
assertNull(cookie.domain());
}
}

View File

@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
package io.netty.handler.codec.http.cookie;
import static org.junit.Assert.*;
@ -27,26 +27,26 @@ public class ClientCookieEncoderTest {
String c2 = "myCookie2=myValue2; ";
String c3 = "myCookie3=myValue3";
Cookie cookie = new DefaultCookie("myCookie", "myValue");
cookie.setVersion(1);
cookie.setComment("this is a Comment");
cookie.setCommentUrl("http://aurl.com");
cookie.setDomain(".adomainsomewhere");
cookie.setDiscard(true);
cookie.setMaxAge(50);
cookie.setPath("/apathsomewhere");
cookie.setPorts(80, 8080);
cookie.setSecure(true);
Cookie cookie2 = new DefaultCookie("myCookie2", "myValue2");
cookie2.setVersion(1);
cookie2.setComment("this is another Comment");
cookie2.setCommentUrl("http://anotherurl.com");
cookie2.setDomain(".anotherdomainsomewhere");
cookie2.setDiscard(false);
cookie2.setPath("/anotherpathsomewhere");
cookie2.setSecure(false);
Cookie cookie3 = new DefaultCookie("myCookie3", "myValue3");
cookie3.setVersion(1);
String encodedCookie = ClientCookieEncoder.encode(cookie, cookie2, cookie3);
String encodedCookie = ClientCookieEncoder.STRICT.encode(cookie, cookie2, cookie3);
assertEquals(c1 + c2 + c3, encodedCookie);
}
@Test
public void testWrappedCookieValue() {
ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "\"foo\""));
}
@Test(expected = IllegalArgumentException.class)
public void testRejectCookieValueWithSemicolon() {
ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar"));
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.cookie;
import org.junit.Test;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import static org.junit.Assert.*;
public class ServerCookieDecoderTest {
@Test
public void testDecodingSingleCookie() {
String cookieString = "myCookie=myValue";
cookieString = cookieString.replace("XXX",
HttpHeaderDateFormat.get().format(new Date(System.currentTimeMillis() + 50000)));
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
assertEquals(1, cookies.size());
Cookie cookie = cookies.iterator().next();
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
}
@Test
public void testDecodingMultipleCookies() {
String c1 = "myCookie=myValue;";
String c2 = "myCookie2=myValue2;";
String c3 = "myCookie3=myValue3;";
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(c1 + c2 + c3);
assertEquals(3, cookies.size());
Iterator<Cookie> it = cookies.iterator();
Cookie cookie = it.next();
assertNotNull(cookie);
assertEquals("myValue", cookie.value());
cookie = it.next();
assertNotNull(cookie);
assertEquals("myValue2", cookie.value());
cookie = it.next();
assertNotNull(cookie);
assertEquals("myValue3", cookie.value());
}
@Test
public void testDecodingGoogleAnalyticsCookie() {
String source =
"ARPT=LWUKQPSWRTUN04CKKJI; " +
"kw-2E343B92-B097-442c-BFA5-BE371E0325A2=unfinished_furniture; " +
"__utma=48461872.1094088325.1258140131.1258140131.1258140131.1; " +
"__utmb=48461872.13.10.1258140131; __utmc=48461872; " +
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance/clearance/32/dept.html";
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(source);
Iterator<Cookie> it = cookies.iterator();
Cookie c;
c = it.next();
assertEquals("__utma", c.name());
assertEquals("48461872.1094088325.1258140131.1258140131.1258140131.1", c.value());
c = it.next();
assertEquals("__utmb", c.name());
assertEquals("48461872.13.10.1258140131", c.value());
c = it.next();
assertEquals("__utmc", c.name());
assertEquals("48461872", c.value());
c = it.next();
assertEquals("__utmz", c.name());
assertEquals("48461872.1258140131.1.1.utmcsr=overstock.com|" +
"utmccn=(referral)|utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance/clearance/32/dept.html",
c.value());
c = it.next();
assertEquals("ARPT", c.name());
assertEquals("LWUKQPSWRTUN04CKKJI", c.value());
c = it.next();
assertEquals("kw-2E343B92-B097-442c-BFA5-BE371E0325A2", c.name());
assertEquals("unfinished_furniture", c.value());
assertFalse(it.hasNext());
}
@Test
public void testDecodingLongValue() {
String longValue =
"b___$Q__$ha__<NC=MN(F__%#4__<NC=MN(F__2_d____#=IvZB__2_F____'=KqtH__2-9____" +
"'=IvZM__3f:____$=HbQW__3g'____%=J^wI__3g-____%=J^wI__3g1____$=HbQW__3g2____" +
"$=HbQW__3g5____%=J^wI__3g9____$=HbQW__3gT____$=HbQW__3gX____#=J^wI__3gY____" +
"#=J^wI__3gh____$=HbQW__3gj____$=HbQW__3gr____$=HbQW__3gx____#=J^wI__3h_____" +
"$=HbQW__3h$____#=J^wI__3h'____$=HbQW__3h_____$=HbQW__3h0____%=J^wI__3h1____" +
"#=J^wI__3h2____$=HbQW__3h4____$=HbQW__3h7____$=HbQW__3h8____%=J^wI__3h:____" +
"#=J^wI__3h@____%=J^wI__3hB____$=HbQW__3hC____$=HbQW__3hL____$=HbQW__3hQ____" +
"$=HbQW__3hS____%=J^wI__3hU____$=HbQW__3h[____$=HbQW__3h^____$=HbQW__3hd____" +
"%=J^wI__3he____%=J^wI__3hf____%=J^wI__3hg____$=HbQW__3hh____%=J^wI__3hi____" +
"%=J^wI__3hv____$=HbQW__3i/____#=J^wI__3i2____#=J^wI__3i3____%=J^wI__3i4____" +
"$=HbQW__3i7____$=HbQW__3i8____$=HbQW__3i9____%=J^wI__3i=____#=J^wI__3i>____" +
"%=J^wI__3iD____$=HbQW__3iF____#=J^wI__3iH____%=J^wI__3iM____%=J^wI__3iS____" +
"#=J^wI__3iU____%=J^wI__3iZ____#=J^wI__3i]____%=J^wI__3ig____%=J^wI__3ij____" +
"%=J^wI__3ik____#=J^wI__3il____$=HbQW__3in____%=J^wI__3ip____$=HbQW__3iq____" +
"$=HbQW__3it____%=J^wI__3ix____#=J^wI__3j_____$=HbQW__3j%____$=HbQW__3j'____" +
"%=J^wI__3j(____%=J^wI__9mJ____'=KqtH__=SE__<NC=MN(F__?VS__<NC=MN(F__Zw`____" +
"%=KqtH__j+C__<NC=MN(F__j+M__<NC=MN(F__j+a__<NC=MN(F__j_.__<NC=MN(F__n>M____" +
"'=KqtH__s1X____$=MMyc__s1_____#=MN#O__ypn____'=KqtH__ypr____'=KqtH_#%h_____" +
"%=KqtH_#%o_____'=KqtH_#)H6__<NC=MN(F_#*%'____%=KqtH_#+k(____'=KqtH_#-E_____" +
"'=KqtH_#1)w____'=KqtH_#1)y____'=KqtH_#1*M____#=KqtH_#1*p____'=KqtH_#14Q__<N" +
"C=MN(F_#14S__<NC=MN(F_#16I__<NC=MN(F_#16N__<NC=MN(F_#16X__<NC=MN(F_#16k__<N" +
"C=MN(F_#17@__<NC=MN(F_#17A__<NC=MN(F_#1Cq____'=KqtH_#7)_____#=KqtH_#7)b____" +
"#=KqtH_#7Ww____'=KqtH_#?cQ____'=KqtH_#His____'=KqtH_#Jrh____'=KqtH_#O@M__<N" +
"C=MN(F_#O@O__<NC=MN(F_#OC6__<NC=MN(F_#Os.____#=KqtH_#YOW____#=H/Li_#Zat____" +
"'=KqtH_#ZbI____%=KqtH_#Zbc____'=KqtH_#Zbs____%=KqtH_#Zby____'=KqtH_#Zce____" +
"'=KqtH_#Zdc____%=KqtH_#Zea____'=KqtH_#ZhI____#=KqtH_#ZiD____'=KqtH_#Zis____" +
"'=KqtH_#Zj0____#=KqtH_#Zj1____'=KqtH_#Zj[____'=KqtH_#Zj]____'=KqtH_#Zj^____" +
"'=KqtH_#Zjb____'=KqtH_#Zk_____'=KqtH_#Zk6____#=KqtH_#Zk9____%=KqtH_#Zk<____" +
"'=KqtH_#Zl>____'=KqtH_#]9R____$=H/Lt_#]I6____#=KqtH_#]Z#____%=KqtH_#^*N____" +
"#=KqtH_#^:m____#=KqtH_#_*_____%=J^wI_#`-7____#=KqtH_#`T>____'=KqtH_#`T?____" +
"'=KqtH_#`TA____'=KqtH_#`TB____'=KqtH_#`TG____'=KqtH_#`TP____#=KqtH_#`U_____" +
"'=KqtH_#`U/____'=KqtH_#`U0____#=KqtH_#`U9____'=KqtH_#aEQ____%=KqtH_#b<)____" +
"'=KqtH_#c9-____%=KqtH_#dxC____%=KqtH_#dxE____%=KqtH_#ev$____'=KqtH_#fBi____" +
"#=KqtH_#fBj____'=KqtH_#fG)____'=KqtH_#fG+____'=KqtH_#g<d____'=KqtH_#g<e____" +
"'=KqtH_#g=J____'=KqtH_#gat____#=KqtH_#s`D____#=J_#p_#sg?____#=J_#p_#t<a____" +
"#=KqtH_#t<c____#=KqtH_#trY____$=JiYj_#vA$____'=KqtH_#xs_____'=KqtH_$$rO____" +
"#=KqtH_$$rP____#=KqtH_$(_%____'=KqtH_$)]o____%=KqtH_$_@)____'=KqtH_$_k]____" +
"'=KqtH_$1]+____%=KqtH_$3IO____%=KqtH_$3J#____'=KqtH_$3J.____'=KqtH_$3J:____" +
"#=KqtH_$3JH____#=KqtH_$3JI____#=KqtH_$3JK____%=KqtH_$3JL____'=KqtH_$3JS____" +
"'=KqtH_$8+M____#=KqtH_$99d____%=KqtH_$:Lw____#=LK+x_$:N@____#=KqtG_$:NC____" +
"#=KqtG_$:hW____'=KqtH_$:i[____'=KqtH_$:ih____'=KqtH_$:it____'=KqtH_$:kO____" +
"'=KqtH_$>*B____'=KqtH_$>hD____+=J^x0_$?lW____'=KqtH_$?ll____'=KqtH_$?lm____" +
"%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V<g____" +
"'=KqtH";
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode("bh=\"" + longValue + "\";");
assertEquals(1, cookies.size());
Cookie c = cookies.iterator().next();
assertEquals("bh", c.name());
assertEquals(longValue, c.value());
}
@Test
public void testDecodingOldRFC2965Cookies() {
String source = "$Version=\"1\"; " +
"Part_Number1=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " +
"Part_Number2=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(source);
Iterator<Cookie> it = cookies.iterator();
Cookie c;
c = it.next();
assertEquals("Part_Number1", c.name());
assertEquals("Riding_Rocket_0023", c.value());
c = it.next();
assertEquals("Part_Number2", c.name());
assertEquals("Rocket_Launcher_0001", c.value());
assertFalse(it.hasNext());
}
@Test
public void testRejectCookieValueWithSemicolon() {
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode("name=\"foo;bar\";");
assertTrue(cookies.isEmpty());
}
}

View File

@ -13,10 +13,12 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
package io.netty.handler.codec.http.cookie;
import org.junit.Test;
import io.netty.handler.codec.http.HttpHeaderDateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
@ -40,7 +42,7 @@ public class ServerCookieEncoderTest {
cookie.setPath("/apathsomewhere");
cookie.setSecure(true);
String encodedCookie = ServerCookieEncoder.encode(cookie);
String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie);
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
assertTrue(matcher.find());
@ -52,8 +54,8 @@ public class ServerCookieEncoderTest {
@Test
public void testEncodingWithNoCookies() {
String encodedCookie1 = ClientCookieEncoder.encode();
List<String> encodedCookie2 = ServerCookieEncoder.encode();
String encodedCookie1 = ClientCookieEncoder.STRICT.encode();
List<String> encodedCookie2 = ServerCookieEncoder.STRICT.encode();
assertNull(encodedCookie1);
assertNotNull(encodedCookie2);
assertTrue(encodedCookie2.isEmpty());

View File

@ -20,14 +20,14 @@ import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.ClientCookieEncoder;
import io.netty.handler.codec.http.DefaultCookie;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@ -91,7 +91,7 @@ public final class HttpSnoopClient {
// Set some example cookies.
request.headers().set(
HttpHeaderNames.COOKIE,
ClientCookieEncoder.encode(
ClientCookieEncoder.STRICT.encode(
new DefaultCookie("my-cookie", "foo"),
new DefaultCookie("another-cookie", "bar")));

View File

@ -21,7 +21,6 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
@ -33,8 +32,9 @@ import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.ServerCookieDecoder;
import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.util.CharsetUtil;
import java.util.List;
@ -165,17 +165,17 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
// Encode the cookie.
String cookieString = request.headers().get(HttpHeaderNames.COOKIE);
if (cookieString != null) {
Set<Cookie> cookies = ServerCookieDecoder.decode(cookieString);
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
if (!cookies.isEmpty()) {
// Reset the cookies if necessary.
for (Cookie cookie: cookies) {
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.encode(cookie));
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
}
}
} else {
// Browser sent no cookie. Add some.
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.encode("key1", "value1"));
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.encode("key2", "value2"));
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode("key1", "value1"));
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode("key2", "value2"));
}
// Write the response.

View File

@ -21,8 +21,6 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.ClientCookieEncoder;
import io.netty.handler.codec.http.DefaultCookie;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
@ -31,6 +29,8 @@ import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringEncoder;
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
@ -181,7 +181,7 @@ public final class HttpUploadClient {
// headers.set("Keep-Alive","300");
headers.set(
HttpHeaderNames.COOKIE, ClientCookieEncoder.encode(
HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode(
new DefaultCookie("my-cookie", "foo"),
new DefaultCookie("another-cookie", "bar"))
);

View File

@ -21,7 +21,6 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
@ -35,8 +34,9 @@ import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.ServerCookieDecoder;
import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskAttribute;
@ -125,7 +125,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
if (value == null) {
cookies = Collections.emptySet();
} else {
cookies = ServerCookieDecoder.decode(value);
cookies = ServerCookieDecoder.STRICT.decode(value);
}
for (Cookie cookie : cookies) {
responseContent.append("COOKIE: " + cookie + "\r\n");
@ -308,12 +308,12 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
if (value == null) {
cookies = Collections.emptySet();
} else {
cookies = ServerCookieDecoder.decode(value);
cookies = ServerCookieDecoder.STRICT.decode(value);
}
if (!cookies.isEmpty()) {
// Reset the cookies if necessary.
for (Cookie cookie : cookies) {
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.encode(cookie));
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
}
}
// Write the response.