RFC6265 cookies support
Motivation: Currently Netty supports a weird implementation of RFC 2965. First, this RFC has been deprecated by RFC 6265 and nobody on the internet use this format. Then, there's a confusion between client side and server side encoding and decoding. Typically, clients should only send name=value pairs. This PR introduces RFC 6265 support, but keeps on supporting RFC 2965 in the sense that old unused fields are simply ignored, and Cookie fields won't be populated. Deprecated fields are comment, commentUrl, version, discard and ports. It also provides a mechanism for safe server-client-server roundtrip, as User-Agents are not supposed to interpret cookie values but return them as-is (e.g. if Set-Cookie contained a quoted value, it should be sent back in the Cookie header in quoted form too). Also, there are performance gains to be obtained by not allocating the attribute name Strings, as we only want to match them to find which POJO field to populate. Modifications: - New RFC6265ClientCookieEncoder/Decoder and RFC6265ServerCookieEncoder/Decoder pairs that live alongside old CookieEncoder/Decoder pair to not break backward compatibility. - New Cookie.rawValue field, used for lossless server-client-server roundtrip. Result: RFC 6265 support. Clean separation of client and server side. Decoder performance gain: Benchmark Mode Samples Score Error Units parseOldClientDecoder thrpt 20 2070169,228 ± 105044,970 ops/s parseRFC6265ClientDecoder thrpt 20 2954015,476 ± 126670,633 ops/s This commit closes #3221 and #1406.
This commit is contained in:
parent
279187ba5e
commit
c298230128
@ -56,6 +56,21 @@ public interface Cookie extends Comparable<Cookie> {
|
|||||||
*/
|
*/
|
||||||
void setValue(String value);
|
void setValue(String value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw value of this {@link Cookie},
|
||||||
|
* as it was set in original Set-Cookie header.
|
||||||
|
*
|
||||||
|
* @return The raw value of this {@link Cookie}
|
||||||
|
*/
|
||||||
|
String rawValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the raw value of this {@link Cookie}.
|
||||||
|
*
|
||||||
|
* @param value The raw value to set
|
||||||
|
*/
|
||||||
|
void setRawValue(String rawValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #domain()} instead.
|
* @deprecated Use {@link #domain()} instead.
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +24,14 @@ final class CookieEncoderUtil {
|
|||||||
return InternalThreadLocalMap.get().stringBuilder();
|
return InternalThreadLocalMap.get().stringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param buf a buffer where some cookies were maybe encoded
|
||||||
|
* @return the buffer String without the trailing separator, or null if no cookie was appended.
|
||||||
|
*/
|
||||||
|
static String stripTrailingSeparatorOrNull(StringBuilder buf) {
|
||||||
|
return buf.length() == 0 ? null : stripTrailingSeparator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
static String stripTrailingSeparator(StringBuilder buf) {
|
static String stripTrailingSeparator(StringBuilder buf) {
|
||||||
if (buf.length() > 0) {
|
if (buf.length() > 0) {
|
||||||
buf.setLength(buf.length() - 2);
|
buf.setLength(buf.length() - 2);
|
||||||
|
@ -28,6 +28,7 @@ public class DefaultCookie implements Cookie {
|
|||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private String value;
|
private String value;
|
||||||
|
private String rawValue;
|
||||||
private String domain;
|
private String domain;
|
||||||
private String path;
|
private String path;
|
||||||
private String comment;
|
private String comment;
|
||||||
@ -107,6 +108,19 @@ public class DefaultCookie implements Cookie {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String rawValue() {
|
||||||
|
return rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRawValue(String rawValue) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException("rawValue");
|
||||||
|
}
|
||||||
|
this.rawValue = rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public String getDomain() {
|
public String getDomain() {
|
||||||
|
@ -29,8 +29,8 @@ import java.util.TimeZone;
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with
|
* <li>Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with
|
||||||
* valid generation</li>
|
* valid generation</li>
|
||||||
* <li>Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification</li>
|
* <li>Sunday, 06-Nov-94 08:49:37 GMT: obsolete specification</li>
|
||||||
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
|
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
final class HttpHeaderDateFormat extends SimpleDateFormat {
|
final class HttpHeaderDateFormat extends SimpleDateFormat {
|
||||||
|
@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.CookieEncoderUtil.stringBuilder;
|
||||||
|
|
||||||
|
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 raw value in {@link Cookie#setRawValue(String)} so it can be
|
||||||
|
* eventually sent back to the Origin server as is.
|
||||||
|
*
|
||||||
|
* @see Rfc6265ClientCookieEncoder
|
||||||
|
*/
|
||||||
|
public final class Rfc6265ClientCookieDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
|
||||||
|
*
|
||||||
|
* @return the decoded {@link Cookie}
|
||||||
|
*/
|
||||||
|
public static Cookie decode(String header) {
|
||||||
|
|
||||||
|
if (header == null) {
|
||||||
|
throw new NullPointerException("header");
|
||||||
|
}
|
||||||
|
|
||||||
|
final int headerLen = header.length();
|
||||||
|
|
||||||
|
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 newNameStart = i;
|
||||||
|
int newNameEnd = i;
|
||||||
|
String value, rawValue;
|
||||||
|
boolean first = true;
|
||||||
|
|
||||||
|
if (i == headerLen) {
|
||||||
|
value = rawValue = null;
|
||||||
|
} else {
|
||||||
|
keyValLoop: for (;;) {
|
||||||
|
|
||||||
|
char curChar = header.charAt(i);
|
||||||
|
if (curChar == ';') {
|
||||||
|
// NAME; (no value till ';')
|
||||||
|
newNameEnd = i;
|
||||||
|
value = rawValue = null;
|
||||||
|
first = false;
|
||||||
|
break keyValLoop;
|
||||||
|
} else if (curChar == '=') {
|
||||||
|
// NAME=VALUE
|
||||||
|
newNameEnd = i;
|
||||||
|
i++;
|
||||||
|
if (i == headerLen) {
|
||||||
|
// NAME= (empty value, i.e. nothing after '=')
|
||||||
|
value = rawValue = "";
|
||||||
|
first = false;
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newValueStart = i;
|
||||||
|
char c = header.charAt(i);
|
||||||
|
if (c == '"') {
|
||||||
|
// NAME="VALUE"
|
||||||
|
StringBuilder newValueBuf = stringBuilder();
|
||||||
|
|
||||||
|
int rawValueStart = i;
|
||||||
|
int rawValueEnd = i;
|
||||||
|
|
||||||
|
final char q = c;
|
||||||
|
boolean hadBackslash = false;
|
||||||
|
i++;
|
||||||
|
for (;;) {
|
||||||
|
if (i == headerLen) {
|
||||||
|
value = newValueBuf.toString();
|
||||||
|
// only need to compute raw value for cookie
|
||||||
|
// value which is in first position
|
||||||
|
rawValue = first ? header.substring(rawValueStart, rawValueEnd) : null;
|
||||||
|
first = false;
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
if (hadBackslash) {
|
||||||
|
hadBackslash = false;
|
||||||
|
c = header.charAt(i++);
|
||||||
|
rawValueEnd = i;
|
||||||
|
if (c == '\\' || c == '"') {
|
||||||
|
newValueBuf.setCharAt(newValueBuf.length() - 1, c);
|
||||||
|
} else {
|
||||||
|
// Do not escape last backslash.
|
||||||
|
newValueBuf.append(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = header.charAt(i++);
|
||||||
|
rawValueEnd = i;
|
||||||
|
if (c == q) {
|
||||||
|
value = newValueBuf.toString();
|
||||||
|
// only need to compute raw value for
|
||||||
|
// cookie value which is in first
|
||||||
|
// position
|
||||||
|
rawValue = first ? header.substring(rawValueStart, rawValueEnd) : null;
|
||||||
|
first = false;
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
newValueBuf.append(c);
|
||||||
|
if (c == '\\') {
|
||||||
|
hadBackslash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// NAME=VALUE;
|
||||||
|
int semiPos = header.indexOf(';', i);
|
||||||
|
if (semiPos > 0) {
|
||||||
|
value = rawValue = header.substring(newValueStart, semiPos);
|
||||||
|
i = semiPos;
|
||||||
|
} else {
|
||||||
|
value = rawValue = header.substring(newValueStart);
|
||||||
|
i = headerLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break keyValLoop;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == headerLen) {
|
||||||
|
// NAME (no value till the end of string)
|
||||||
|
newNameEnd = i;
|
||||||
|
first = false;
|
||||||
|
value = rawValue = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookieBuilder == null) {
|
||||||
|
cookieBuilder = new CookieBuilder(header, newNameStart, newNameEnd, value, rawValue);
|
||||||
|
} else {
|
||||||
|
cookieBuilder.appendAttribute(header, newNameStart, newNameEnd, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieBuilder.cookie();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CookieBuilder {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String value;
|
||||||
|
private final String rawValue;
|
||||||
|
private String domain;
|
||||||
|
private String path;
|
||||||
|
private long maxAge = Long.MIN_VALUE;
|
||||||
|
private String expires;
|
||||||
|
private boolean secure;
|
||||||
|
private boolean httpOnly;
|
||||||
|
|
||||||
|
public CookieBuilder(String header, int keyStart, int keyEnd,
|
||||||
|
String value, String rawValue) {
|
||||||
|
name = header.substring(keyStart, keyEnd);
|
||||||
|
this.value = value;
|
||||||
|
this.rawValue = rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if (name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultCookie cookie = new DefaultCookie(name, value);
|
||||||
|
cookie.setValue(value);
|
||||||
|
cookie.setRawValue(rawValue);
|
||||||
|
cookie.setDomain(domain);
|
||||||
|
cookie.setPath(path);
|
||||||
|
cookie.setMaxAge(mergeMaxAgeAndExpire(maxAge, expires));
|
||||||
|
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, "Path", 0, 4)) {
|
||||||
|
path = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse6(String header, int nameStart, String value) {
|
||||||
|
if (header.regionMatches(true, nameStart, "Domain", 0, 5)) {
|
||||||
|
domain = value.isEmpty() ? null : value;
|
||||||
|
} else if (header.regionMatches(true, nameStart, "Secure", 0, 5)) {
|
||||||
|
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, "Expires", 0, 7)) {
|
||||||
|
setExpire(value);
|
||||||
|
} else if (header.regionMatches(true, nameStart, "Max-Age", 0, 7)) {
|
||||||
|
setMaxAge(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse8(String header, int nameStart, String value) {
|
||||||
|
|
||||||
|
if (header.regionMatches(true, nameStart, "HttpOnly", 0, 8)) {
|
||||||
|
httpOnly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rfc6265ClientCookieDecoder() {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie encoder to be used client side,
|
||||||
|
* so only name=value pairs are sent.
|
||||||
|
*
|
||||||
|
* 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 Rfc6265ClientCookieEncoder}.encode("JSESSIONID", "1234"));
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see Rfc6265ClientCookieDecoder
|
||||||
|
*/
|
||||||
|
public final class Rfc6265ClientCookieEncoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 static 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 static String encode(Cookie cookie) {
|
||||||
|
if (cookie == null) {
|
||||||
|
throw new NullPointerException("cookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder buf = 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 static String encode(Cookie... cookies) {
|
||||||
|
if (cookies == null) {
|
||||||
|
throw new NullPointerException("cookies");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookies.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder buf = stringBuilder();
|
||||||
|
for (Cookie c : cookies) {
|
||||||
|
if (c == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(buf, c);
|
||||||
|
}
|
||||||
|
return stripTrailingSeparatorOrNull(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 static String encode(Iterable<Cookie> cookies) {
|
||||||
|
if (cookies == null) {
|
||||||
|
throw new NullPointerException("cookies");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cookies.iterator().hasNext()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder buf = stringBuilder();
|
||||||
|
for (Cookie c : cookies) {
|
||||||
|
if (c == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(buf, c);
|
||||||
|
}
|
||||||
|
return stripTrailingSeparatorOrNull(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void encode(StringBuilder buf, Cookie c) {
|
||||||
|
// rawValue > value > ""
|
||||||
|
String value = c.rawValue() != null ? c.rawValue()
|
||||||
|
: c.value() != null ? c.value() : "";
|
||||||
|
addUnquoted(buf, c.name(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rfc6265ClientCookieEncoder() {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
|
||||||
|
|
||||||
|
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 Rfc6265ServerCookieEncoder
|
||||||
|
*/
|
||||||
|
public final class Rfc6265ServerCookieDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
|
||||||
|
*
|
||||||
|
* @return the decoded {@link Cookie}
|
||||||
|
*/
|
||||||
|
public static Set<Cookie> decode(String header) {
|
||||||
|
|
||||||
|
if (header == null) {
|
||||||
|
throw new NullPointerException("header");
|
||||||
|
}
|
||||||
|
|
||||||
|
final int headerLen = header.length();
|
||||||
|
|
||||||
|
if (headerLen == 0) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Cookie> cookies = new TreeSet<Cookie>();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
boolean rfc2965Style = false;
|
||||||
|
if (header.regionMatches(true, 0, "$Version", 0, 8)) {
|
||||||
|
// RFC 2965 style cookie, move to after version value
|
||||||
|
i = header.indexOf(';') + 1;
|
||||||
|
rfc2965Style = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop: for (;;) {
|
||||||
|
|
||||||
|
// Skip spaces and separators.
|
||||||
|
for (;;) {
|
||||||
|
if (i == headerLen) {
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
char c = header.charAt(i);
|
||||||
|
if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
|
||||||
|
|| c == '\r' || c == ' ' || c == ',' || c == ';') {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newNameStart = i;
|
||||||
|
int newNameEnd = i;
|
||||||
|
String value;
|
||||||
|
|
||||||
|
if (i == headerLen) {
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
keyValLoop: for (;;) {
|
||||||
|
|
||||||
|
char curChar = header.charAt(i);
|
||||||
|
if (curChar == ';') {
|
||||||
|
// NAME; (no value till ';')
|
||||||
|
newNameEnd = i;
|
||||||
|
value = null;
|
||||||
|
break keyValLoop;
|
||||||
|
} else if (curChar == '=') {
|
||||||
|
// NAME=VALUE
|
||||||
|
newNameEnd = i;
|
||||||
|
i++;
|
||||||
|
if (i == headerLen) {
|
||||||
|
// NAME= (empty value, i.e. nothing after '=')
|
||||||
|
value = "";
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newValueStart = i;
|
||||||
|
char c = header.charAt(i);
|
||||||
|
if (c == '"') {
|
||||||
|
// NAME="VALUE"
|
||||||
|
StringBuilder newValueBuf = stringBuilder();
|
||||||
|
|
||||||
|
final char q = c;
|
||||||
|
boolean hadBackslash = false;
|
||||||
|
i++;
|
||||||
|
for (;;) {
|
||||||
|
if (i == headerLen) {
|
||||||
|
value = newValueBuf.toString();
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
if (hadBackslash) {
|
||||||
|
hadBackslash = false;
|
||||||
|
c = header.charAt(i++);
|
||||||
|
if (c == '\\' || c == '"') {
|
||||||
|
// Escape last backslash.
|
||||||
|
newValueBuf.setCharAt(newValueBuf.length() - 1, c);
|
||||||
|
} else {
|
||||||
|
// Do not escape last backslash.
|
||||||
|
newValueBuf.append(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = header.charAt(i++);
|
||||||
|
if (c == q) {
|
||||||
|
value = newValueBuf.toString();
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
newValueBuf.append(c);
|
||||||
|
if (c == '\\') {
|
||||||
|
hadBackslash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// NAME=VALUE;
|
||||||
|
int semiPos = header.indexOf(';', i);
|
||||||
|
if (semiPos > 0) {
|
||||||
|
value = header.substring(newValueStart, semiPos);
|
||||||
|
i = semiPos;
|
||||||
|
} else {
|
||||||
|
value = header.substring(newValueStart);
|
||||||
|
i = headerLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break keyValLoop;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == headerLen) {
|
||||||
|
// NAME (no value till the end of string)
|
||||||
|
newNameEnd = headerLen;
|
||||||
|
value = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rfc2965Style || (!header.regionMatches(newNameStart, "$Path", 0, "$Path".length()) &&
|
||||||
|
!header.regionMatches(newNameStart, "$Domain", 0, "$Domain".length()) &&
|
||||||
|
!header.regionMatches(newNameStart, "$Port", 0, "$Port".length()))) {
|
||||||
|
|
||||||
|
// skip obsolete RFC2965 fields
|
||||||
|
String name = header.substring(newNameStart, newNameEnd);
|
||||||
|
cookies.add(new DefaultCookie(name, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rfc6265ServerCookieDecoder() {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.CookieEncoderUtil.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie encoder to be used 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 Rfc6265ServerCookieEncoder}.encode("JSESSIONID", "1234"));
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see Rfc6265ServerCookieDecoder
|
||||||
|
*/
|
||||||
|
public final class Rfc6265ServerCookieEncoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 static 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 static String encode(Cookie cookie) {
|
||||||
|
if (cookie == null) {
|
||||||
|
throw new NullPointerException("cookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder buf = stringBuilder();
|
||||||
|
|
||||||
|
addUnquoted(buf, cookie.name(), cookie.value());
|
||||||
|
|
||||||
|
if (cookie.maxAge() != Long.MIN_VALUE) {
|
||||||
|
add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie.path() != null) {
|
||||||
|
addUnquoted(buf, CookieHeaderNames.PATH, cookie.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie.domain() != null) {
|
||||||
|
addUnquoted(buf, CookieHeaderNames.DOMAIN, cookie.domain());
|
||||||
|
}
|
||||||
|
if (cookie.isSecure()) {
|
||||||
|
buf.append(CookieHeaderNames.SECURE);
|
||||||
|
buf.append((char) HttpConstants.SEMICOLON);
|
||||||
|
buf.append((char) HttpConstants.SP);
|
||||||
|
}
|
||||||
|
if (cookie.isHttpOnly()) {
|
||||||
|
buf.append(CookieHeaderNames.HTTPONLY);
|
||||||
|
buf.append((char) HttpConstants.SEMICOLON);
|
||||||
|
buf.append((char) HttpConstants.SP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stripTrailingSeparator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch encodes cookies into Set-Cookie header values.
|
||||||
|
*
|
||||||
|
* @param cookies a bunch of cookies
|
||||||
|
* @return the corresponding bunch of Set-Cookie headers
|
||||||
|
*/
|
||||||
|
public static List<String> encode(Cookie... cookies) {
|
||||||
|
if (cookies == null) {
|
||||||
|
throw new NullPointerException("cookies");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookies.length == 0) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> encoded = new ArrayList<String>(cookies.length);
|
||||||
|
for (Cookie c : cookies) {
|
||||||
|
if (c == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
encoded.add(encode(c));
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch encodes cookies into Set-Cookie header values.
|
||||||
|
*
|
||||||
|
* @param cookies a bunch of cookies
|
||||||
|
* @return the corresponding bunch of Set-Cookie headers
|
||||||
|
*/
|
||||||
|
public static List<String> encode(Collection<Cookie> cookies) {
|
||||||
|
if (cookies == null) {
|
||||||
|
throw new NullPointerException("cookies");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookies.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> encoded = new ArrayList<String>(cookies.size());
|
||||||
|
for (Cookie c : cookies) {
|
||||||
|
if (c == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
encoded.add(encode(c));
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch encodes cookies into Set-Cookie header values.
|
||||||
|
*
|
||||||
|
* @param cookies a bunch of cookies
|
||||||
|
* @return the corresponding bunch of Set-Cookie headers
|
||||||
|
*/
|
||||||
|
public static List<String> encode(Iterable<Cookie> cookies) {
|
||||||
|
if (cookies == null) {
|
||||||
|
throw new NullPointerException("cookies");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cookies.iterator().hasNext()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> encoded = new ArrayList<String>();
|
||||||
|
for (Cookie c : cookies) {
|
||||||
|
if (c == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
encoded.add(encode(c));
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rfc6265ServerCookieEncoder() {
|
||||||
|
// Unused
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import 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 java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class Rfc6265ClientCookieDecoderTest {
|
||||||
|
@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 = Rfc6265ClientCookieDecoder.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 = Rfc6265ClientCookieDecoder.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 = Rfc6265ClientCookieDecoder.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 = Rfc6265ClientCookieDecoder.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 = Rfc6265ClientCookieDecoder.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 = Rfc6265ClientCookieDecoder.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\",");
|
||||||
|
sources.add("c=\"\\\"1\\\"2\\\"\",");
|
||||||
|
sources.add("d=\"1\\\"2\\\"3\",");
|
||||||
|
sources.add("e=\"\\\"\\\"\",");
|
||||||
|
sources.add("f=\"1\\\"\\\"2\",");
|
||||||
|
sources.add("g=\"\\\\\",");
|
||||||
|
sources.add("h=\"';,\\x\"");
|
||||||
|
|
||||||
|
Collection<Cookie> cookies = new ArrayList<Cookie>();
|
||||||
|
for (String source : sources) {
|
||||||
|
cookies.add(Rfc6265ClientCookieDecoder.decode(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie c;
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("a", c.name());
|
||||||
|
assertEquals("", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("b", c.name());
|
||||||
|
assertEquals("1", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("c", c.name());
|
||||||
|
assertEquals("\"1\"2\"", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("d", c.name());
|
||||||
|
assertEquals("1\"2\"3", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("e", c.name());
|
||||||
|
assertEquals("\"\"", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("f", c.name());
|
||||||
|
assertEquals("1\"\"2", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("g", c.name());
|
||||||
|
assertEquals("\\", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("h", c.name());
|
||||||
|
assertEquals("';,\\x", c.value());
|
||||||
|
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingGoogleAnalyticsCookie() {
|
||||||
|
String source = "ARPT=LWUKQPSWRTUN04CKKJI; "
|
||||||
|
+ "kw-2E343B92-B097-442c-BFA5-BE371E0325A2=unfinished furniture; "
|
||||||
|
+ "__utma=48461872.1094088325.1258140131.1258140131.1258140131.1; "
|
||||||
|
+ "__utmb=48461872.13.10.1258140131; __utmc=48461872; "
|
||||||
|
+ "__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|"
|
||||||
|
+ "utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
|
||||||
|
Cookie cookie = Rfc6265ClientCookieDecoder.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 = Rfc6265ClientCookieDecoder.decode(source);
|
||||||
|
|
||||||
|
assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingValueWithComma() {
|
||||||
|
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 = Rfc6265ClientCookieDecoder.decode(source);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"timeZoneName=(GMT+04:00) Moscow, St. Petersburg, Volgograd&promocode=®ion=BE",
|
||||||
|
cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingWeirdNames1() {
|
||||||
|
String src = "path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com";
|
||||||
|
Cookie cookie = Rfc6265ClientCookieDecoder.decode(src);
|
||||||
|
assertEquals("path", cookie.name());
|
||||||
|
assertEquals("", cookie.value());
|
||||||
|
assertEquals("/", cookie.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingWeirdNames2() {
|
||||||
|
String src = "HTTPOnly=";
|
||||||
|
Cookie cookie = Rfc6265ClientCookieDecoder.decode(src);
|
||||||
|
assertEquals("HTTPOnly", cookie.name());
|
||||||
|
assertEquals("", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingValuesWithCommasAndEquals() {
|
||||||
|
String src = "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E";
|
||||||
|
Cookie cookie = Rfc6265ClientCookieDecoder.decode(src);
|
||||||
|
assertEquals("A", cookie.name());
|
||||||
|
assertEquals("v=1&lg=en-US,it-IT,it&intl=it&np=1", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = Rfc6265ClientCookieDecoder.decode("bh=\"" + longValue
|
||||||
|
+ "\";");
|
||||||
|
assertEquals("bh", cookie.name());
|
||||||
|
assertEquals(longValue, cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIgnoreEmptyDomain() {
|
||||||
|
String emptyDomain = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/";
|
||||||
|
Cookie cookie = Rfc6265ClientCookieDecoder.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 io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class Rfc6265ClientCookieEncoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodingMultipleClientCookies() {
|
||||||
|
String c1 = "myCookie=myValue; ";
|
||||||
|
String c2 = "myCookie2=myValue2; ";
|
||||||
|
String c3 = "myCookie3=myValue3";
|
||||||
|
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||||
|
cookie.setVersion(1);
|
||||||
|
cookie.setComment("this is a Comment");
|
||||||
|
cookie.setCommentUrl("http://aurl.com");
|
||||||
|
cookie.setDomain(".adomainsomewhere");
|
||||||
|
cookie.setDiscard(true);
|
||||||
|
cookie.setMaxAge(50);
|
||||||
|
cookie.setPath("/apathsomewhere");
|
||||||
|
cookie.setPorts(80, 8080);
|
||||||
|
cookie.setSecure(true);
|
||||||
|
Cookie cookie2 = new DefaultCookie("myCookie2", "myValue2");
|
||||||
|
cookie2.setVersion(1);
|
||||||
|
cookie2.setComment("this is another Comment");
|
||||||
|
cookie2.setCommentUrl("http://anotherurl.com");
|
||||||
|
cookie2.setDomain(".anotherdomainsomewhere");
|
||||||
|
cookie2.setDiscard(false);
|
||||||
|
cookie2.setPath("/anotherpathsomewhere");
|
||||||
|
cookie2.setSecure(false);
|
||||||
|
Cookie cookie3 = new DefaultCookie("myCookie3", "myValue3");
|
||||||
|
cookie3.setVersion(1);
|
||||||
|
String encodedCookie = Rfc6265ClientCookieEncoder.encode(cookie, cookie2, cookie3);
|
||||||
|
assertEquals(c1 + c2 + c3, encodedCookie);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class Rfc6265ServerCookieDecoderTest {
|
||||||
|
@Test
|
||||||
|
public void testDecodingSingleCookie() {
|
||||||
|
String cookieString = "myCookie=myValue";
|
||||||
|
cookieString = cookieString.replace("XXX",
|
||||||
|
HttpHeaderDateFormat.get().format(new Date(System.currentTimeMillis() + 50000)));
|
||||||
|
|
||||||
|
Set<Cookie> cookies = Rfc6265ServerCookieDecoder.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 = Rfc6265ServerCookieDecoder.decode(c1 + c2 + c3);
|
||||||
|
assertEquals(3, cookies.size());
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie cookie = it.next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
cookie = it.next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue2", cookie.value());
|
||||||
|
cookie = it.next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue3", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingQuotedCookie() {
|
||||||
|
String source =
|
||||||
|
"a=\"\";" +
|
||||||
|
"b=\"1\";" +
|
||||||
|
"c=\"\\\"1\\\"2\\\"\";" +
|
||||||
|
"d=\"1\\\"2\\\"3\";" +
|
||||||
|
"e=\"\\\"\\\"\";" +
|
||||||
|
"f=\"1\\\"\\\"2\";" +
|
||||||
|
"g=\"\\\\\";" +
|
||||||
|
"h=\"';,\\x\"";
|
||||||
|
|
||||||
|
Set<Cookie> cookies = Rfc6265ServerCookieDecoder.decode(source);
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie c;
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("a", c.name());
|
||||||
|
assertEquals("", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("b", c.name());
|
||||||
|
assertEquals("1", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("c", c.name());
|
||||||
|
assertEquals("\"1\"2\"", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("d", c.name());
|
||||||
|
assertEquals("1\"2\"3", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("e", c.name());
|
||||||
|
assertEquals("\"\"", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("f", c.name());
|
||||||
|
assertEquals("1\"\"2", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("g", c.name());
|
||||||
|
assertEquals("\\", c.value());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("h", c.name());
|
||||||
|
assertEquals("';,\\x", c.value());
|
||||||
|
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingGoogleAnalyticsCookie() {
|
||||||
|
String source =
|
||||||
|
"ARPT=LWUKQPSWRTUN04CKKJI; " +
|
||||||
|
"kw-2E343B92-B097-442c-BFA5-BE371E0325A2=unfinished furniture; " +
|
||||||
|
"__utma=48461872.1094088325.1258140131.1258140131.1258140131.1; " +
|
||||||
|
"__utmb=48461872.13.10.1258140131; __utmc=48461872; " +
|
||||||
|
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
|
||||||
|
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
|
||||||
|
Set<Cookie> cookies = Rfc6265ServerCookieDecoder.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 = Rfc6265ServerCookieDecoder.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 = Rfc6265ServerCookieDecoder.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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class Rfc6265ServerCookieEncoderTest {
|
||||||
|
@Test
|
||||||
|
public void testEncodingSingleCookieV0() {
|
||||||
|
String result = "myCookie=myValue; Max-Age=50; Path=/apathsomewhere; Domain=.adomainsomewhere; Secure";
|
||||||
|
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||||
|
cookie.setDomain(".adomainsomewhere");
|
||||||
|
cookie.setMaxAge(50);
|
||||||
|
cookie.setPath("/apathsomewhere");
|
||||||
|
cookie.setSecure(true);
|
||||||
|
|
||||||
|
String encodedCookie = Rfc6265ServerCookieEncoder.encode(cookie);
|
||||||
|
assertEquals(result, encodedCookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodingWithNoCookies() {
|
||||||
|
String encodedCookie1 = Rfc6265ClientCookieEncoder.encode();
|
||||||
|
List<String> encodedCookie2 = Rfc6265ServerCookieEncoder.encode();
|
||||||
|
assertNull(encodedCookie1);
|
||||||
|
assertNotNull(encodedCookie2);
|
||||||
|
assertTrue(encodedCookie2.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user