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: Backport new RFC6265 compliant Cookie parsers in cookie subpackage. Deprecate old Cookie encoders and decoders that will be dropped in 5.0. Result: The problem described in the motivation section is fixed.
This commit is contained in:
parent
0e06a53890
commit
402edce586
|
@ -26,9 +26,9 @@ import org.jboss.netty.channel.Channels;
|
|||
import org.jboss.netty.channel.ExceptionEvent;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||
import org.jboss.netty.handler.codec.http.Cookie;
|
||||
import org.jboss.netty.handler.codec.http.CookieDecoder;
|
||||
import org.jboss.netty.handler.codec.http.CookieEncoder;
|
||||
import org.jboss.netty.handler.codec.http.cookie.Cookie;
|
||||
import org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder;
|
||||
import org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import org.jboss.netty.handler.codec.http.HttpChunk;
|
||||
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||
|
@ -123,8 +123,7 @@ public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler {
|
|||
if (value == null) {
|
||||
cookies = Collections.emptySet();
|
||||
} else {
|
||||
CookieDecoder decoder = new CookieDecoder();
|
||||
cookies = decoder.decode(value);
|
||||
cookies = ServerCookieDecoder.STRICT.decode(value);
|
||||
}
|
||||
for (Cookie cookie: cookies) {
|
||||
responseContent.append("COOKIE: " + cookie + "\r\n");
|
||||
|
@ -308,17 +307,10 @@ public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler {
|
|||
if (value == null) {
|
||||
cookies = Collections.emptySet();
|
||||
} else {
|
||||
CookieDecoder decoder = new CookieDecoder();
|
||||
cookies = decoder.decode(value);
|
||||
cookies = ServerCookieDecoder.STRICT.decode(value);
|
||||
}
|
||||
if (!cookies.isEmpty()) {
|
||||
// Reset the cookies if necessary.
|
||||
CookieEncoder cookieEncoder = new CookieEncoder(true);
|
||||
for (Cookie cookie: cookies) {
|
||||
cookieEncoder.addCookie(cookie);
|
||||
response.headers().add(HttpHeaders.Names.SET_COOKIE, cookieEncoder.encode());
|
||||
cookieEncoder = new CookieEncoder(true);
|
||||
}
|
||||
response.headers().add(HttpHeaders.Names.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookies));
|
||||
}
|
||||
// Write the response.
|
||||
ChannelFuture future = channel.write(response);
|
||||
|
|
|
@ -18,134 +18,202 @@ package org.jboss.netty.handler.codec.http;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An HTTP <a href="http://en.wikipedia.org/wiki/HTTP_cookie">Cookie</a>.
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie.Cookie {
|
||||
|
||||
/**
|
||||
* Returns the name of this cookie.
|
||||
* @deprecated Use {@link #name()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the value of this cookie.
|
||||
* @deprecated Use {@link #value()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getValue();
|
||||
|
||||
/**
|
||||
* Sets the value of this cookie.
|
||||
*/
|
||||
void setValue(String value);
|
||||
|
||||
/**
|
||||
* Returns the domain of this cookie.
|
||||
* @deprecated Use {@link #domain()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getDomain();
|
||||
|
||||
/**
|
||||
* Sets the domain of this cookie.
|
||||
*/
|
||||
void setDomain(String domain);
|
||||
|
||||
/**
|
||||
* Returns the path of this cookie.
|
||||
* @deprecated Use {@link #path()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getPath();
|
||||
|
||||
/**
|
||||
* Sets the path of this cookie.
|
||||
*/
|
||||
void setPath(String path);
|
||||
|
||||
/**
|
||||
* Returns the comment of this cookie.
|
||||
* @deprecated Use {@link #comment()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getComment();
|
||||
|
||||
/**
|
||||
* Sets the comment of this 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);
|
||||
|
||||
/**
|
||||
* Returns the max age of this cookie in seconds.
|
||||
* @deprecated Use {@link #maxAge()} instead.
|
||||
*/
|
||||
int getMaxAge();
|
||||
@Deprecated
|
||||
long getMaxAge();
|
||||
|
||||
/**
|
||||
* Sets the max age of this cookie in seconds. If {@code 0} is specified,
|
||||
* this cookie will be removed by browser because it will be expired
|
||||
* immediately. If {@link Integer#MIN_VALUE} is specified, this cookie will be removed
|
||||
* when a user terminates browser.
|
||||
* 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
|
||||
*/
|
||||
void setMaxAge(int maxAge);
|
||||
@Deprecated
|
||||
long maxAge();
|
||||
|
||||
/**
|
||||
* Returns the version of this cookie.
|
||||
* 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
|
||||
*
|
||||
* @deprecated Not part of RFC6265
|
||||
*/
|
||||
@Deprecated
|
||||
void setMaxAge(long maxAge);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #version()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
int getVersion();
|
||||
|
||||
/**
|
||||
* Sets the version of this 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);
|
||||
|
||||
/**
|
||||
* Returns the secure flag of this cookie.
|
||||
*/
|
||||
boolean isSecure();
|
||||
|
||||
/**
|
||||
* Sets the secure flag of this cookie.
|
||||
*/
|
||||
void setSecure(boolean secure);
|
||||
|
||||
/**
|
||||
* Returns if this cookie cannot be accessed through client side script.
|
||||
* This flag works only if the browser supports it. For more information,
|
||||
* see <a href="http://www.owasp.org/index.php/HTTPOnly">here</a>.
|
||||
*/
|
||||
boolean isHttpOnly();
|
||||
|
||||
/**
|
||||
* Sets if this cookie cannot be accessed through client side script.
|
||||
* This flag works only if the browser supports it. For more information,
|
||||
* see <a href="http://www.owasp.org/index.php/HTTPOnly">here</a>.
|
||||
*/
|
||||
void setHttpOnly(boolean httpOnly);
|
||||
|
||||
/**
|
||||
* Returns the comment URL of this cookie.
|
||||
* @deprecated Use {@link #commentUrl()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getCommentUrl();
|
||||
|
||||
/**
|
||||
* Sets the comment URL of this 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);
|
||||
|
||||
/**
|
||||
* Returns the discard flag of this cookie.
|
||||
* Checks to see if this {@link Cookie} is to be discarded by the browser
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Sets the discard flag of this cookie.
|
||||
* Sets the discard flag of this {@link Cookie}.
|
||||
* If set to true, this {@link Cookie} will be discarded by the browser
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Returns the ports of this cookie.
|
||||
* @deprecated Use {@link #ports()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
Set<Integer> getPorts();
|
||||
|
||||
/**
|
||||
* Sets the ports of this 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);
|
||||
|
||||
/**
|
||||
* Sets the ports of this cookie.
|
||||
* Sets the ports that this {@link Cookie} can be accessed on.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,13 @@
|
|||
*/
|
||||
package org.jboss.netty.handler.codec.http;
|
||||
|
||||
import static org.jboss.netty.handler.codec.http.CookieUtil.firstInvalidCookieNameOctet;
|
||||
import static org.jboss.netty.handler.codec.http.CookieUtil.firstInvalidCookieValueOctet;
|
||||
import static org.jboss.netty.handler.codec.http.CookieUtil.unwrapValue;
|
||||
import org.jboss.netty.handler.codec.http.cookie.CookieHeaderNames;
|
||||
import org.jboss.netty.util.internal.StringUtil;
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -25,25 +31,57 @@ import java.util.Set;
|
|||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* <pre>
|
||||
* {@link HttpRequest} req = ...;
|
||||
* String value = req.getHeader("Cookie");
|
||||
* Set<{@link Cookie}> cookies = new {@link CookieDecoder}().decode(value);
|
||||
* Set<{@link Cookie}> cookies = {@link CookieDecoder}.decode(value);
|
||||
* </pre>
|
||||
*
|
||||
* @see CookieEncoder
|
||||
*
|
||||
* @apiviz.stereotype utility
|
||||
* @apiviz.has org.jboss.netty.handler.codec.http.Cookie oneway - - decodes
|
||||
* @see io.netty.handler.codec.http.cookie.ClientCookieDecoder
|
||||
* @see io.netty.handler.codec.http.cookie.ServerCookieDecoder
|
||||
*/
|
||||
public class CookieDecoder {
|
||||
@Deprecated
|
||||
public final class CookieDecoder {
|
||||
|
||||
private final InternalLogger logger = InternalLoggerFactory.getInstance(getClass());
|
||||
|
||||
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 String INVALID_VALUE = "<invalid value>";
|
||||
private static final char COMMA = ',';
|
||||
|
||||
private final boolean strict;
|
||||
|
||||
/**
|
||||
* Creates a new decoder.
|
||||
*/
|
||||
public CookieDecoder() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decoder.
|
||||
*
|
||||
* @param strict {@code true} if and only if this encoder is supposed to
|
||||
* validate characters according to RFC6265.
|
||||
*/
|
||||
public CookieDecoder(boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the specified HTTP header value into {@link Cookie}s.
|
||||
*
|
||||
|
@ -63,7 +101,7 @@ public 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 +125,11 @@ public class CookieDecoder {
|
|||
value = "";
|
||||
}
|
||||
|
||||
Cookie c = new DefaultCookie(name, value);
|
||||
Cookie c = initCookie(name, value);
|
||||
|
||||
if (c == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
boolean discard = false;
|
||||
boolean secure = false;
|
||||
|
@ -96,22 +138,22 @@ public class CookieDecoder {
|
|||
String commentURL = null;
|
||||
String domain = null;
|
||||
String path = null;
|
||||
int maxAge = Integer.MIN_VALUE;
|
||||
long maxAge = Long.MIN_VALUE;
|
||||
List<Integer> ports = new ArrayList<Integer>(2);
|
||||
|
||||
for (int j = i + 1; j < names.size(); j++, i++) {
|
||||
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;
|
||||
|
@ -123,17 +165,15 @@ public class CookieDecoder {
|
|||
HttpHeaderDateFormat.get().parse(value).getTime() -
|
||||
System.currentTimeMillis();
|
||||
|
||||
maxAge = (int) (maxAgeMillis / 1000) +
|
||||
(maxAgeMillis % 1000 != 0? 1 : 0);
|
||||
|
||||
maxAge = maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0? 1 : 0);
|
||||
} catch (ParseException e) {
|
||||
// Ignore.
|
||||
}
|
||||
} 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 {
|
||||
|
@ -170,7 +210,6 @@ public 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;;) {
|
||||
|
||||
|
@ -235,8 +274,7 @@ public class CookieDecoder {
|
|||
i ++;
|
||||
for (;;) {
|
||||
if (i == headerLen) {
|
||||
// Missing closing quote.
|
||||
value = INVALID_VALUE;
|
||||
value = newValueBuf.toString();
|
||||
break keyValLoop;
|
||||
}
|
||||
if (hadBackslash) {
|
||||
|
@ -288,10 +326,52 @@ public class CookieDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
if (value != INVALID_VALUE) {
|
||||
names.add(name);
|
||||
values.add(value);
|
||||
}
|
||||
names.add(name);
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
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 '" + name + "' contains invalid char '"
|
||||
+ 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 '" + unwrappedValue
|
||||
+ "' contains invalid char '" + unwrappedValue.charAt(invalidOctetPos) + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
DefaultCookie cookie = new DefaultCookie(name, unwrappedValue.toString());
|
||||
cookie.setWrap(wrap);
|
||||
return cookie;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
package org.jboss.netty.handler.codec.http;
|
||||
|
||||
import java.util.Date;
|
||||
import org.jboss.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
||||
import org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
@ -51,6 +53,7 @@ public class CookieEncoder {
|
|||
|
||||
private final Set<Cookie> cookies = new TreeSet<Cookie>();
|
||||
private final boolean server;
|
||||
private final boolean strict;
|
||||
|
||||
/**
|
||||
* Creates a new encoder.
|
||||
|
@ -60,7 +63,21 @@ public class CookieEncoder {
|
|||
* this encoder is supposed to encode client-side cookies.
|
||||
*/
|
||||
public CookieEncoder(boolean server) {
|
||||
this(server, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new encoder.
|
||||
*
|
||||
* @param server {@code true} if and only if this encoder is supposed to
|
||||
* encode server-side cookies. {@code false} if and only if
|
||||
* this encoder is supposed to encode client-side cookies.
|
||||
* @param strict {@code true} if and only if this encoder is supposed to
|
||||
* validate characters according to RFC6265.
|
||||
*/
|
||||
public CookieEncoder(boolean server, boolean strict) {
|
||||
this.server = server;
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,174 +119,13 @@ public class CookieEncoder {
|
|||
"encode() can encode only one cookie on server mode: " + cookies.size() + " cookies added");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (Cookie cookie: cookies) {
|
||||
add(sb, cookie.getName(), cookie.getValue());
|
||||
|
||||
if (cookie.getMaxAge() != Integer.MIN_VALUE) {
|
||||
if (cookie.getVersion() == 0) {
|
||||
addUnquoted(sb, CookieHeaderNames.EXPIRES,
|
||||
HttpHeaderDateFormat.get().format(
|
||||
new Date(System.currentTimeMillis() +
|
||||
cookie.getMaxAge() * 1000L)));
|
||||
} else {
|
||||
add(sb, CookieHeaderNames.MAX_AGE, cookie.getMaxAge());
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie.getPath() != null) {
|
||||
if (cookie.getVersion() > 0) {
|
||||
add(sb, CookieHeaderNames.PATH, cookie.getPath());
|
||||
} else {
|
||||
addUnquoted(sb, CookieHeaderNames.PATH, cookie.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie.getDomain() != null) {
|
||||
if (cookie.getVersion() > 0) {
|
||||
add(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
|
||||
} else {
|
||||
addUnquoted(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
|
||||
}
|
||||
}
|
||||
if (cookie.isSecure()) {
|
||||
sb.append(CookieHeaderNames.SECURE);
|
||||
sb.append((char) HttpConstants.SEMICOLON);
|
||||
sb.append((char) HttpConstants.SP);
|
||||
}
|
||||
if (cookie.isHttpOnly()) {
|
||||
sb.append(CookieHeaderNames.HTTPONLY);
|
||||
sb.append((char) HttpConstants.SEMICOLON);
|
||||
sb.append((char) HttpConstants.SP);
|
||||
}
|
||||
if (cookie.getVersion() >= 1) {
|
||||
if (cookie.getComment() != null) {
|
||||
add(sb, CookieHeaderNames.COMMENT, cookie.getComment());
|
||||
}
|
||||
|
||||
add(sb, CookieHeaderNames.VERSION, 1);
|
||||
|
||||
if (cookie.getCommentUrl() != null) {
|
||||
addQuoted(sb, CookieHeaderNames.COMMENTURL, cookie.getCommentUrl());
|
||||
}
|
||||
|
||||
if (!cookie.getPorts().isEmpty()) {
|
||||
sb.append(CookieHeaderNames.PORT);
|
||||
sb.append((char) HttpConstants.EQUALS);
|
||||
sb.append((char) HttpConstants.DOUBLE_QUOTE);
|
||||
for (int port: cookie.getPorts()) {
|
||||
sb.append(port);
|
||||
sb.append((char) HttpConstants.COMMA);
|
||||
}
|
||||
sb.setCharAt(sb.length() - 1, (char) HttpConstants.DOUBLE_QUOTE);
|
||||
sb.append((char) HttpConstants.SEMICOLON);
|
||||
sb.append((char) HttpConstants.SP);
|
||||
}
|
||||
if (cookie.isDiscard()) {
|
||||
sb.append(CookieHeaderNames.DISCARD);
|
||||
sb.append((char) HttpConstants.SEMICOLON);
|
||||
sb.append((char) HttpConstants.SP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.setLength(sb.length() - 2);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
Cookie cookie = cookies.isEmpty() ? null : cookies.iterator().next();
|
||||
ServerCookieEncoder encoder = strict ? ServerCookieEncoder.STRICT : ServerCookieEncoder.LAX;
|
||||
return encoder.encode(cookie);
|
||||
}
|
||||
|
||||
private String encodeClientSide() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (Cookie cookie: cookies) {
|
||||
if (cookie.getVersion() >= 1) {
|
||||
add(sb, '$' + CookieHeaderNames.VERSION, 1);
|
||||
}
|
||||
|
||||
add(sb, cookie.getName(), cookie.getValue());
|
||||
|
||||
if (cookie.getPath() != null) {
|
||||
add(sb, '$' + CookieHeaderNames.PATH, cookie.getPath());
|
||||
}
|
||||
|
||||
if (cookie.getDomain() != null) {
|
||||
add(sb, '$' + CookieHeaderNames.DOMAIN, cookie.getDomain());
|
||||
}
|
||||
|
||||
if (cookie.getVersion() >= 1) {
|
||||
if (!cookie.getPorts().isEmpty()) {
|
||||
sb.append('$');
|
||||
sb.append(CookieHeaderNames.PORT);
|
||||
sb.append((char) HttpConstants.EQUALS);
|
||||
sb.append((char) HttpConstants.DOUBLE_QUOTE);
|
||||
for (int port: cookie.getPorts()) {
|
||||
sb.append(port);
|
||||
sb.append((char) HttpConstants.COMMA);
|
||||
}
|
||||
sb.setCharAt(sb.length() - 1, (char) HttpConstants.DOUBLE_QUOTE);
|
||||
sb.append((char) HttpConstants.SEMICOLON);
|
||||
sb.append((char) HttpConstants.SP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.setLength(sb.length() - 2);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
private static void add(StringBuilder sb, String name, int val) {
|
||||
sb.append(name);
|
||||
sb.append((char) HttpConstants.EQUALS);
|
||||
sb.append(val);
|
||||
sb.append((char) HttpConstants.SEMICOLON);
|
||||
sb.append((char) HttpConstants.SP);
|
||||
ClientCookieEncoder encoder = strict ? ClientCookieEncoder.STRICT : ClientCookieEncoder.LAX;
|
||||
return encoder.encode(cookies);
|
||||
}
|
||||
}
|
||||
|
|
104
src/main/java/org/jboss/netty/handler/codec/http/CookieUtil.java
Normal file
104
src/main/java/org/jboss/netty/handler/codec/http/CookieUtil.java
Normal 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 org.jboss.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
|
||||
}
|
||||
}
|
|
@ -19,126 +19,102 @@ 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 org.jboss.netty.handler.codec.http.cookie.DefaultCookie implements Cookie {
|
||||
|
||||
private final String name;
|
||||
private String value;
|
||||
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 int maxAge = Integer.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.length() == 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getName() {
|
||||
return name;
|
||||
return name();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
this.value = value;
|
||||
return value();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = validateValue("domain", domain);
|
||||
return domain();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = validateValue("path", path);
|
||||
return path();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getComment() {
|
||||
return comment();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String comment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setComment(String comment) {
|
||||
this.comment = validateValue("comment", comment);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getCommentUrl() {
|
||||
return commentUrl();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String commentUrl() {
|
||||
return commentUrl;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setCommentUrl(String commentUrl) {
|
||||
this.commentUrl = validateValue("commentUrl", commentUrl);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean isDiscard() {
|
||||
return discard;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setDiscard(boolean discard) {
|
||||
this.discard = discard;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Set<Integer> getPorts() {
|
||||
return ports();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Set<Integer> ports() {
|
||||
if (unmodifiablePorts == null) {
|
||||
unmodifiablePorts = Collections.unmodifiableSet(ports);
|
||||
}
|
||||
return unmodifiablePorts;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setPorts(int... ports) {
|
||||
if (ports == null) {
|
||||
throw new NullPointerException("ports");
|
||||
|
@ -153,20 +129,21 @@ public class DefaultCookie implements Cookie {
|
|||
if (p <= 0 || p > 65535) {
|
||||
throw new IllegalArgumentException("port out of range: " + p);
|
||||
}
|
||||
newPorts.add(p);
|
||||
newPorts.add(Integer.valueOf(p));
|
||||
}
|
||||
this.ports = newPorts;
|
||||
unmodifiablePorts = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setPorts(Iterable<Integer> ports) {
|
||||
Set<Integer> newPorts = new TreeSet<Integer>();
|
||||
for (int p: ports) {
|
||||
if (p <= 0 || p > 65535) {
|
||||
throw new IllegalArgumentException("port out of range: " + p);
|
||||
}
|
||||
newPorts.add(p);
|
||||
newPorts.add(Integer.valueOf(p));
|
||||
}
|
||||
if (newPorts.isEmpty()) {
|
||||
unmodifiablePorts = this.ports = Collections.emptySet();
|
||||
|
@ -176,161 +153,23 @@ public class DefaultCookie implements Cookie {
|
|||
}
|
||||
}
|
||||
|
||||
public int getMaxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
public void setMaxAge(int maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
@Deprecated
|
||||
public long getMaxAge() {
|
||||
return maxAge();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public int getVersion() {
|
||||
return version();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public int version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
public void setSecure(boolean secure) {
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
public boolean isHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
public void setHttpOnly(boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Cookie)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cookie that = (Cookie) o;
|
||||
if (!getName().equalsIgnoreCase(that.getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getPath() == null) {
|
||||
if (that.getPath() != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (that.getPath() == null) {
|
||||
return false;
|
||||
} else if (!getPath().equals(that.getPath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getDomain() == null) {
|
||||
if (that.getDomain() != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (that.getDomain() == null) {
|
||||
return false;
|
||||
} else {
|
||||
return getDomain().equalsIgnoreCase(that.getDomain());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int compareTo(Cookie c) {
|
||||
int v;
|
||||
v = getName().compareToIgnoreCase(c.getName());
|
||||
if (v != 0) {
|
||||
return v;
|
||||
}
|
||||
|
||||
if (getPath() == null) {
|
||||
if (c.getPath() != null) {
|
||||
return -1;
|
||||
}
|
||||
} else if (c.getPath() == null) {
|
||||
return 1;
|
||||
} else {
|
||||
v = getPath().compareTo(c.getPath());
|
||||
if (v != 0) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
if (getDomain() == null) {
|
||||
if (c.getDomain() != null) {
|
||||
return -1;
|
||||
}
|
||||
} else if (c.getDomain() == null) {
|
||||
return 1;
|
||||
} else {
|
||||
v = getDomain().compareToIgnoreCase(c.getDomain());
|
||||
return v;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(getName());
|
||||
buf.append('=');
|
||||
buf.append(getValue());
|
||||
if (getDomain() != null) {
|
||||
buf.append(", domain=");
|
||||
buf.append(getDomain());
|
||||
}
|
||||
if (getPath() != null) {
|
||||
buf.append(", path=");
|
||||
buf.append(getPath());
|
||||
}
|
||||
if (getComment() != null) {
|
||||
buf.append(", comment=");
|
||||
buf.append(getComment());
|
||||
}
|
||||
if (getMaxAge() >= 0) {
|
||||
buf.append(", maxAge=");
|
||||
buf.append(getMaxAge());
|
||||
buf.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.length() == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,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();
|
||||
|
|
|
@ -15,19 +15,25 @@
|
|||
*/
|
||||
package org.jboss.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 CookieEncoder} and
|
||||
* {@link CookieDecoder}.
|
||||
* {@link QueryStringEncoder} and {@link QueryStringDecoder}.
|
||||
*
|
||||
* {@link org.jboss.netty.handler.codec.http.cookie.Cookie} support is also provided
|
||||
* separately via {@link org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder},
|
||||
* {@link org.jboss.netty.handler.codec.http.cookie.ClientCookieDecoder},
|
||||
* {@link org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder},
|
||||
* and {@link org.jboss.netty.handler.codec.http.cookie.ClientCookieEncoder}.
|
||||
*
|
||||
* @see HttpResponse
|
||||
* @see CookieEncoder
|
||||
* @see CookieDecoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ClientCookieDecoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ClientCookieEncoder
|
||||
*/
|
||||
public interface HttpRequest extends HttpMessage {
|
||||
|
||||
|
|
|
@ -15,17 +15,22 @@
|
|||
*/
|
||||
package org.jboss.netty.handler.codec.http;
|
||||
|
||||
|
||||
/**
|
||||
* An HTTP response.
|
||||
*
|
||||
* <h3>Accessing Cookie</h3>
|
||||
* <h3>Accessing Cookies</h3>
|
||||
* <p>
|
||||
* Unlike the Servlet API, {@link Cookie} support is provided separately via
|
||||
* {@link CookieEncoder} and {@link CookieDecoder}.
|
||||
* Unlike the Servlet API, {@link org.jboss.netty.handler.codec.http.cookie.Cookie} support is provided
|
||||
* separately via {@link org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder},
|
||||
* {@link org.jboss.netty.handler.codec.http.cookie.ClientCookieDecoder},
|
||||
* {@link org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder},
|
||||
* and {@link @io.netty.handler.codec.http.cookie.ClientCookieEncoder}.
|
||||
*
|
||||
* @see HttpRequest
|
||||
* @see CookieEncoder
|
||||
* @see CookieDecoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ClientCookieDecoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder
|
||||
* @see org.jboss.netty.handler.codec.http.cookie.ClientCookieEncoder
|
||||
*/
|
||||
public interface HttpResponse extends HttpMessage {
|
||||
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpHeaderDateFormat;
|
||||
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used client side.
|
||||
*
|
||||
* 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 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 Cookie decode(String header) {
|
||||
if (header == null) {
|
||||
throw new NullPointerException("header");
|
||||
}
|
||||
final int headerLen = header.length();
|
||||
|
||||
if (headerLen == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CookieBuilder cookieBuilder = null;
|
||||
|
||||
loop: for (int i = 0;;) {
|
||||
|
||||
// Skip spaces and separators.
|
||||
for (;;) {
|
||||
if (i == headerLen) {
|
||||
break loop;
|
||||
}
|
||||
char c = header.charAt(i);
|
||||
if (c == ',') {
|
||||
// Having multiple cookies in a single Set-Cookie header is
|
||||
// deprecated, modern browsers only parse the first one
|
||||
break loop;
|
||||
|
||||
} else if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
|
||||
|| c == '\r' || 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 (valueEnd > 0 && header.charAt(valueEnd - 1) == ',') {
|
||||
// old multiple cookies separator, skipping it
|
||||
valueEnd--;
|
||||
}
|
||||
|
||||
if (cookieBuilder == null) {
|
||||
// cookie name-value pair
|
||||
DefaultCookie cookie = initCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
|
||||
|
||||
if (cookie == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
cookieBuilder = new CookieBuilder(cookie);
|
||||
} else {
|
||||
// cookie attribute
|
||||
String attrValue = valueBegin == -1 ? null : header.substring(valueBegin, valueEnd);
|
||||
cookieBuilder.appendAttribute(header, nameBegin, nameEnd, attrValue);
|
||||
}
|
||||
}
|
||||
return cookieBuilder.cookie();
|
||||
}
|
||||
|
||||
private static class CookieBuilder {
|
||||
|
||||
private final DefaultCookie cookie;
|
||||
private String domain;
|
||||
private String path;
|
||||
private long maxAge = Long.MIN_VALUE;
|
||||
private String expires;
|
||||
private boolean secure;
|
||||
private boolean httpOnly;
|
||||
|
||||
public CookieBuilder(DefaultCookie cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
|
||||
private long mergeMaxAgeAndExpire(long maxAge, String expires) {
|
||||
// max age has precedence over expires
|
||||
if (maxAge != Long.MIN_VALUE) {
|
||||
return maxAge;
|
||||
} else if (expires != null) {
|
||||
Date expiresDate = HttpHeaderDateFormat.get().parse(expires, new ParsePosition(0));
|
||||
if (expiresDate != null) {
|
||||
long maxAgeMillis = expiresDate.getTime() - System.currentTimeMillis();
|
||||
return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
return Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public Cookie cookie() {
|
||||
cookie.setDomain(domain);
|
||||
cookie.setPath(path);
|
||||
cookie.setMaxAge(mergeMaxAgeAndExpire(maxAge, expires));
|
||||
cookie.setSecure(secure);
|
||||
cookie.setHttpOnly(httpOnly);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and store a key-value pair. First one is considered to be the
|
||||
* cookie name/value. Unknown attribute names are silently discarded.
|
||||
*
|
||||
* @param header
|
||||
* the HTTP header
|
||||
* @param keyStart
|
||||
* where the key starts in the header
|
||||
* @param keyEnd
|
||||
* where the key ends in the header
|
||||
* @param value
|
||||
* the decoded value
|
||||
*/
|
||||
public void appendAttribute(String header, int keyStart, int keyEnd,
|
||||
String value) {
|
||||
setCookieAttribute(header, keyStart, keyEnd, value);
|
||||
}
|
||||
|
||||
private void setCookieAttribute(String header, int keyStart,
|
||||
int keyEnd, String value) {
|
||||
int length = keyEnd - keyStart;
|
||||
|
||||
if (length == 4) {
|
||||
parse4(header, keyStart, value);
|
||||
} else if (length == 6) {
|
||||
parse6(header, keyStart, value);
|
||||
} else if (length == 7) {
|
||||
parse7(header, keyStart, value);
|
||||
} else if (length == 8) {
|
||||
parse8(header, keyStart, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void parse4(String header, int nameStart, String value) {
|
||||
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, CookieHeaderNames.DOMAIN, 0, 5)) {
|
||||
domain = value.length() > 0 ? value.toString() : null;
|
||||
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.SECURE, 0, 5)) {
|
||||
secure = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void setExpire(String value) {
|
||||
expires = value;
|
||||
}
|
||||
|
||||
private void setMaxAge(String value) {
|
||||
try {
|
||||
maxAge = Math.max(Long.valueOf(value), 0L);
|
||||
} catch (NumberFormatException e1) {
|
||||
// ignore failure to parse -> treat as session cookie
|
||||
}
|
||||
}
|
||||
|
||||
private void parse7(String header, int nameStart, String value) {
|
||||
if (header.regionMatches(true, nameStart, CookieHeaderNames.EXPIRES, 0, 7)) {
|
||||
setExpire(value);
|
||||
} 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, CookieHeaderNames.HTTPONLY, 0, 8)) {
|
||||
httpOnly = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.*;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.jboss.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) {
|
||||
if (cookie == null) {
|
||||
throw new NullPointerException("cookie");
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
encode(buf, 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 (cookies == null) {
|
||||
throw new NullPointerException("cookies");
|
||||
}
|
||||
if (cookies.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder buf = new 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) {
|
||||
if (cookies == null) {
|
||||
throw new NullPointerException("cookies");
|
||||
}
|
||||
Iterator<? extends Cookie> cookiesIt = cookies.iterator();
|
||||
if (!cookiesIt.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder buf = new 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 org.jboss.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);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieNameOctet;
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieValueOctet;
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.unwrapValue;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
import org.jboss.netty.logging.InternalLogger;
|
||||
import org.jboss.netty.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) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
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 '" + name + "' contains invalid char '"
|
||||
+ 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 '" + unwrappedValue
|
||||
+ "' contains invalid char '" + unwrappedValue.charAt(invalidOctetPos) + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
DefaultCookie cookie = new DefaultCookie(name, unwrappedValue.toString());
|
||||
cookie.setWrap(wrap);
|
||||
return cookie;
|
||||
}
|
||||
}
|
|
@ -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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieNameOctet;
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.firstInvalidCookieValueOctet;
|
||||
import static org.jboss.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 org.jboss.netty.handler.codec.http;
|
||||
package org.jboss.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.
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpConstants;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
name = name.trim();
|
||||
if (name.length() == 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean wrap() {
|
||||
return wrap;
|
||||
}
|
||||
|
||||
public void setWrap(boolean wrap) {
|
||||
this.wrap = wrap;
|
||||
}
|
||||
|
||||
public String domain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = validateValue("domain", domain);
|
||||
}
|
||||
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = validateValue("path", path);
|
||||
}
|
||||
|
||||
public long maxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
public void setMaxAge(long maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
public void setSecure(boolean secure) {
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
public boolean isHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (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.length() == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
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) {
|
||||
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, 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import static org.jboss.netty.handler.codec.http.cookie.CookieUtil.*;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpHeaderDateFormat;
|
||||
import org.jboss.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) {
|
||||
if (cookie == null) {
|
||||
throw new NullPointerException("cookie");
|
||||
}
|
||||
final String name = cookie.name();
|
||||
final String value = cookie.value() != null ? cookie.value() : "";
|
||||
|
||||
validateCookie(name, value);
|
||||
|
||||
StringBuilder buf = new 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 (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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (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;
|
||||
}
|
||||
}
|
|
@ -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 org.jboss.netty.handler.codec.http.cookie;
|
|
@ -212,7 +212,7 @@ public class CookieDecoderTest {
|
|||
assertNull(c.getCommentUrl());
|
||||
assertNull(c.getDomain());
|
||||
assertTrue(c.getPorts().isEmpty());
|
||||
assertEquals(Integer.MIN_VALUE, c.getMaxAge());
|
||||
assertEquals(Long.MIN_VALUE, c.getMaxAge());
|
||||
|
||||
c = it.next();
|
||||
assertEquals(1, c.getVersion());
|
||||
|
@ -223,7 +223,7 @@ public class CookieDecoderTest {
|
|||
assertNull(c.getCommentUrl());
|
||||
assertNull(c.getDomain());
|
||||
assertTrue(c.getPorts().isEmpty());
|
||||
assertEquals(Integer.MIN_VALUE, c.getMaxAge());
|
||||
assertEquals(Long.MIN_VALUE, c.getMaxAge());
|
||||
|
||||
assertFalse(it.hasNext());
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ public class CookieDecoderTest {
|
|||
assertNull(c.getCommentUrl());
|
||||
assertNull(c.getDomain());
|
||||
assertTrue(c.getPorts().isEmpty());
|
||||
assertEquals(Integer.MIN_VALUE, c.getMaxAge());
|
||||
assertEquals(Long.MIN_VALUE, c.getMaxAge());
|
||||
|
||||
assertTrue(it.hasNext());
|
||||
c = it.next();
|
||||
|
@ -260,7 +260,7 @@ public class CookieDecoderTest {
|
|||
assertNull(c.getComment());
|
||||
assertNull(c.getCommentUrl());
|
||||
assertTrue(c.getPorts().isEmpty());
|
||||
assertEquals(Integer.MIN_VALUE, c.getMaxAge());
|
||||
assertEquals(Long.MIN_VALUE, c.getMaxAge());
|
||||
|
||||
assertFalse(it.hasNext());
|
||||
}
|
||||
|
@ -290,7 +290,8 @@ public class CookieDecoderTest {
|
|||
|
||||
c = it.next();
|
||||
assertEquals("c", c.getName());
|
||||
assertEquals("\"1\"2\"", c.getValue());
|
||||
assertEquals("1\"2", c.getValue());
|
||||
assertTrue(c.wrap());
|
||||
|
||||
c = it.next();
|
||||
assertEquals("d", c.getName());
|
||||
|
@ -298,7 +299,8 @@ public class CookieDecoderTest {
|
|||
|
||||
c = it.next();
|
||||
assertEquals("e", c.getName());
|
||||
assertEquals("\"\"", c.getValue());
|
||||
assertEquals("", c.getValue());
|
||||
assertTrue(c.wrap());
|
||||
|
||||
c = it.next();
|
||||
assertEquals("f", c.getName());
|
||||
|
@ -382,14 +384,6 @@ public class CookieDecoderTest {
|
|||
assertEquals("z=E", c.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMismatchingQuotes() {
|
||||
String src = "aaa='bbb;ccc=ddd";
|
||||
Set<Cookie> cookies = new CookieDecoder().decode(src);
|
||||
assertTrue(cookies.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDecodingLongValue() {
|
||||
String longValue =
|
||||
|
|
|
@ -18,14 +18,19 @@ package org.jboss.netty.handler.codec.http;
|
|||
import static org.junit.Assert.*;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CookieEncoderTest {
|
||||
@Test
|
||||
public void testEncodingSingleCookieV0() {
|
||||
String result = "myCookie=myValue; Expires=XXX; Path=/apathsomewhere; Domain=.adomainsomewhere; Secure";
|
||||
public void testEncodingSingleCookieV0() throws ParseException {
|
||||
int maxAge = 50;
|
||||
String result = "myCookie=myValue; Max-Age=" + maxAge + "; Expires=(.+?); Path=/apathsomewhere; "
|
||||
+ "Domain=.adomainsomewhere; Secure";
|
||||
DateFormat df = HttpHeaderDateFormat.get();
|
||||
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||
CookieEncoder encoder = new CookieEncoder(true);
|
||||
|
@ -34,32 +39,26 @@ public class CookieEncoderTest {
|
|||
cookie.setCommentUrl("http://aurl.com");
|
||||
cookie.setDomain(".adomainsomewhere");
|
||||
cookie.setDiscard(true);
|
||||
cookie.setMaxAge(50);
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setPath("/apathsomewhere");
|
||||
cookie.setPorts(80, 8080);
|
||||
cookie.setSecure(true);
|
||||
|
||||
String encodedCookie = encoder.encode();
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
boolean fail = true;
|
||||
// +/- 10-second tolerance
|
||||
for (int delta = 0; delta <= 20000; delta += 250) {
|
||||
if (encodedCookie.equals(result.replace(
|
||||
"XXX", df.format(new Date(currentTime + 40000 + delta))))) {
|
||||
fail = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fail("Expected: " + result + ", Actual: " + encodedCookie);
|
||||
}
|
||||
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||
assertTrue(matcher.find());
|
||||
Date expiresDate = HttpHeaderDateFormat.get().parse(matcher.group(1));
|
||||
long diff = (expiresDate.getTime() - System.currentTimeMillis()) / 1000;
|
||||
// 2 secs should be fine
|
||||
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodingSingleCookieV1() {
|
||||
String result = "myCookie=myValue; Max-Age=50; Path=\"/apathsomewhere\"; Domain=.adomainsomewhere; Secure; Comment=\"this is a Comment\"; Version=1";
|
||||
public void testEncodingSingleCookieV1() throws ParseException {
|
||||
int maxAge = 50;
|
||||
String result = "myCookie=myValue; Max-Age=" + maxAge + "; Expires=(.+?); Path=/apathsomewhere; "
|
||||
+ "Domain=.adomainsomewhere; Secure";
|
||||
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||
CookieEncoder encoder = new CookieEncoder(true);
|
||||
encoder.addCookie(cookie);
|
||||
|
@ -70,12 +69,20 @@ public class CookieEncoderTest {
|
|||
cookie.setPath("/apathsomewhere");
|
||||
cookie.setSecure(true);
|
||||
String encodedCookie = encoder.encode();
|
||||
assertEquals(result, encodedCookie);
|
||||
|
||||
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||
assertTrue(matcher.find());
|
||||
Date expiresDate = HttpHeaderDateFormat.get().parse(matcher.group(1));
|
||||
long diff = (expiresDate.getTime() - System.currentTimeMillis()) / 1000;
|
||||
// 2 secs should be fine
|
||||
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodingSingleCookieV2() {
|
||||
String result = "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";
|
||||
public void testEncodingSingleCookieV2() throws ParseException {
|
||||
int maxAge = 50;
|
||||
String result = "myCookie=myValue; Max-Age=" + maxAge + "; Expires=(.+?); Path=/apathsomewhere; "
|
||||
+ "Domain=.adomainsomewhere; Secure";
|
||||
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||
CookieEncoder encoder = new CookieEncoder(true);
|
||||
encoder.addCookie(cookie);
|
||||
|
@ -89,7 +96,13 @@ public class CookieEncoderTest {
|
|||
cookie.setPorts(80, 8080);
|
||||
cookie.setSecure(true);
|
||||
String encodedCookie = encoder.encode();
|
||||
assertEquals(result, encodedCookie);
|
||||
|
||||
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||
assertTrue(matcher.find());
|
||||
Date expiresDate = HttpHeaderDateFormat.get().parse(matcher.group(1));
|
||||
long diff = (expiresDate.getTime() - System.currentTimeMillis()) / 1000;
|
||||
// 2 secs should be fine
|
||||
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -107,9 +120,9 @@ public class CookieEncoderTest {
|
|||
|
||||
@Test
|
||||
public void testEncodingMultipleClientCookies() {
|
||||
String c1 = "$Version=1; myCookie=myValue; $Path=\"/apathsomewhere\"; $Domain=.adomainsomewhere; $Port=\"80,8080\"; ";
|
||||
String c2 = "$Version=1; myCookie2=myValue2; $Path=\"/anotherpathsomewhere\"; $Domain=.anotherdomainsomewhere; ";
|
||||
String c3 = "$Version=1; myCookie3=myValue3";
|
||||
String c1 = "myCookie=myValue; ";
|
||||
String c2 = "myCookie2=myValue2; ";
|
||||
String c3 = "myCookie3=myValue3";
|
||||
CookieEncoder encoder = new CookieEncoder(false);
|
||||
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||
cookie.setVersion(1);
|
||||
|
@ -137,16 +150,4 @@ public class CookieEncoderTest {
|
|||
String encodedCookie = encoder.encode();
|
||||
assertEquals(c1 +c2 + c3, encodedCookie);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodingWithNoCookies() {
|
||||
CookieEncoder encoderForServer = new CookieEncoder(true);
|
||||
String encodedCookie1 = encoderForServer.encode();
|
||||
CookieEncoder encoderForClient = new CookieEncoder(false);
|
||||
String encodedCookie2 = encoderForClient.encode();
|
||||
assertNotNull(encodedCookie1);
|
||||
assertNotNull(encodedCookie2);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpHeaderDateFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class ClientCookieDecoderTest {
|
||||
@Test
|
||||
public void testDecodingSingleCookieV0() {
|
||||
String cookieString = "myCookie=myValue;expires=XXX;path=/apathsomewhere;domain=.adomainsomewhere;secure;";
|
||||
cookieString = cookieString.replace("XXX", HttpHeaderDateFormat.get()
|
||||
.format(new Date(System.currentTimeMillis() + 50000)));
|
||||
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||
assertNotNull(cookie);
|
||||
assertEquals("myValue", cookie.value());
|
||||
assertEquals(".adomainsomewhere", cookie.domain());
|
||||
|
||||
boolean fail = true;
|
||||
for (int i = 40; i <= 60; i++) {
|
||||
if (cookie.maxAge() == i) {
|
||||
fail = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fail("expected: 50, actual: " + cookie.maxAge());
|
||||
}
|
||||
|
||||
assertEquals("/apathsomewhere", cookie.path());
|
||||
assertTrue(cookie.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingSingleCookieV0ExtraParamsIgnored() {
|
||||
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.STRICT.decode(cookieString);
|
||||
assertNotNull(cookie);
|
||||
assertEquals("myValue", cookie.value());
|
||||
assertEquals(".adomainsomewhere", cookie.domain());
|
||||
assertEquals(50, cookie.maxAge());
|
||||
assertEquals("/apathsomewhere", cookie.path());
|
||||
assertTrue(cookie.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
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.STRICT.decode(cookieString);
|
||||
assertEquals("myValue", cookie.value());
|
||||
assertNotNull(cookie);
|
||||
assertEquals(".adomainsomewhere", cookie.domain());
|
||||
assertEquals(50, cookie.maxAge());
|
||||
assertEquals("/apathsomewhere", cookie.path());
|
||||
assertTrue(cookie.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingSingleCookieV1ExtraParamsIgnored() {
|
||||
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.STRICT.decode(cookieString);
|
||||
assertNotNull(cookie);
|
||||
assertEquals("myValue", cookie.value());
|
||||
assertEquals(".adomainsomewhere", cookie.domain());
|
||||
assertEquals(50, cookie.maxAge());
|
||||
assertEquals("/apathsomewhere", cookie.path());
|
||||
assertTrue(cookie.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingSingleCookieV2() {
|
||||
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.STRICT.decode(cookieString);
|
||||
assertNotNull(cookie);
|
||||
assertEquals("myValue", cookie.value());
|
||||
assertEquals(".adomainsomewhere", cookie.domain());
|
||||
assertEquals(50, cookie.maxAge());
|
||||
assertEquals("/apathsomewhere", cookie.path());
|
||||
assertTrue(cookie.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingComplexCookie() {
|
||||
String c1 = "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.STRICT.decode(c1);
|
||||
assertNotNull(cookie);
|
||||
assertEquals("myValue", cookie.value());
|
||||
assertEquals(".adomainsomewhere", cookie.domain());
|
||||
assertEquals(50, cookie.maxAge());
|
||||
assertEquals("/apathsomewhere", cookie.path());
|
||||
assertTrue(cookie.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingQuotedCookie() {
|
||||
Collection<String> sources = new ArrayList<String>();
|
||||
sources.add("a=\"\",");
|
||||
sources.add("b=\"1\",");
|
||||
|
||||
Collection<Cookie> cookies = new ArrayList<Cookie>();
|
||||
for (String source : sources) {
|
||||
cookies.add(ClientCookieDecoder.STRICT.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());
|
||||
|
||||
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";
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||
|
||||
assertEquals("ARPT", cookie.name());
|
||||
assertEquals("LWUKQPSWRTUN04CKKJI", cookie.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingLongDates() {
|
||||
Calendar cookieDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
cookieDate.set(9999, Calendar.DECEMBER, 31, 23, 59, 59);
|
||||
long expectedMaxAge = (cookieDate.getTimeInMillis() - System
|
||||
.currentTimeMillis()) / 1000;
|
||||
|
||||
String source = "Format=EU; expires=Fri, 31-Dec-9999 23:59:59 GMT; path=/";
|
||||
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||
|
||||
assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingValueWithCommaFails() {
|
||||
String source = "UserCookie=timeZoneName=(GMT+04:00) Moscow, St. Petersburg, Volgograd&promocode=®ion=BE;"
|
||||
+ " expires=Sat, 01-Dec-2012 10:53:31 GMT; path=/";
|
||||
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||
|
||||
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.STRICT.decode(src);
|
||||
assertEquals("path", cookie.name());
|
||||
assertEquals("", cookie.value());
|
||||
assertEquals("/", cookie.path());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingWeirdNames2() {
|
||||
String src = "HTTPOnly=";
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
||||
assertEquals("HTTPOnly", cookie.name());
|
||||
assertEquals("", cookie.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodingValuesWithCommasAndEqualsFails() {
|
||||
String src = "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E";
|
||||
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";
|
||||
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode("bh=\"" + longValue
|
||||
+ "\";");
|
||||
assertEquals("bh", cookie.name());
|
||||
assertEquals(longValue, cookie.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreEmptyDomain() {
|
||||
String emptyDomain = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/";
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyDomain);
|
||||
assertNull(cookie.domain());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ClientCookieEncoderTest {
|
||||
|
||||
@Test
|
||||
public void testEncodingMultipleClientCookies() {
|
||||
String c1 = "myCookie=myValue; ";
|
||||
String c2 = "myCookie2=myValue2; ";
|
||||
String c3 = "myCookie3=myValue3";
|
||||
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||
cookie.setDomain(".adomainsomewhere");
|
||||
cookie.setMaxAge(50);
|
||||
cookie.setPath("/apathsomewhere");
|
||||
cookie.setSecure(true);
|
||||
Cookie cookie2 = new DefaultCookie("myCookie2", "myValue2");
|
||||
cookie2.setDomain(".anotherdomainsomewhere");
|
||||
cookie2.setPath("/anotherpathsomewhere");
|
||||
cookie2.setSecure(false);
|
||||
Cookie cookie3 = new DefaultCookie("myCookie3", "myValue3");
|
||||
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"));
|
||||
}
|
||||
}
|
|
@ -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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.jboss.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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 org.jboss.netty.handler.codec.http.cookie;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpHeaderDateFormat;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ServerCookieEncoderTest {
|
||||
|
||||
@Test
|
||||
public void testEncodingSingleCookieV0() throws ParseException {
|
||||
|
||||
int maxAge = 50;
|
||||
|
||||
String result =
|
||||
"myCookie=myValue; Max-Age=50; Expires=(.+?); Path=/apathsomewhere; Domain=.adomainsomewhere; Secure";
|
||||
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||
cookie.setDomain(".adomainsomewhere");
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setPath("/apathsomewhere");
|
||||
cookie.setSecure(true);
|
||||
|
||||
String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie);
|
||||
|
||||
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||
assertTrue(matcher.find());
|
||||
Date expiresDate = HttpHeaderDateFormat.get().parse(matcher.group(1));
|
||||
long diff = (expiresDate.getTime() - System.currentTimeMillis()) / 1000;
|
||||
// 2 secs should be fine
|
||||
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodingWithNoCookies() {
|
||||
String encodedCookie1 = ClientCookieEncoder.STRICT.encode();
|
||||
List<String> encodedCookie2 = ServerCookieEncoder.STRICT.encode();
|
||||
assertNull(encodedCookie1);
|
||||
assertNotNull(encodedCookie2);
|
||||
assertTrue(encodedCookie2.isEmpty());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user