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:
Stephane Landelle 2015-01-19 15:06:35 +01:00 committed by Trustin Lee
parent 1b8e8ce6f1
commit 5692c1e945
12 changed files with 1470 additions and 2 deletions

View File

@ -44,6 +44,21 @@ public interface Cookie extends Comparable<Cookie> {
*/
void setValue(String value);
/**
* Returns the raw value of this {@link Cookie},
* as it was set in original Set-Cookie header.
*
* @return The raw value of this {@link Cookie}
*/
String rawValue();
/**
* Sets the raw value of this {@link Cookie}.
*
* @param rawValue The raw value to set
*/
void setRawValue(String rawValue);
/**
* Returns the domain of this {@link Cookie}.
*

View File

@ -24,6 +24,14 @@ final class CookieEncoderUtil {
return InternalThreadLocalMap.get().stringBuilder();
}
/**
* @param buf a buffer where some cookies were maybe encoded
* @return the buffer String without the trailing separator, or null if no cookie was appended.
*/
static String stripTrailingSeparatorOrNull(StringBuilder buf) {
return buf.length() == 0 ? null : stripTrailingSeparator(buf);
}
static String stripTrailingSeparator(StringBuilder buf) {
if (buf.length() > 0) {
buf.setLength(buf.length() - 2);

View File

@ -28,6 +28,7 @@ public class DefaultCookie implements Cookie {
private final String name;
private String value;
private String rawValue;
private String domain;
private String path;
private String comment;
@ -95,6 +96,19 @@ public class DefaultCookie implements Cookie {
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
public String domain() {
return domain;

View File

@ -29,8 +29,8 @@ import java.util.TimeZone;
* <ul>
* <li>Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with
* valid generation</li>
* <li>Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification</li>
* <li>Sun Nov 6 08:49:37 1994: 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>
* </ul>
*/
final class HttpHeaderDateFormat extends SimpleDateFormat {

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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=&region=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=&region=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());
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}