package org.apache.http.impl.cookie;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.http.FormattedHeader;
import org.apache.http.Header;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.cookie.ClientCookie;
import org.apache.http.cookie.CommonCookieAttributeHandler;
import org.apache.http.cookie.Cookie;
import org.apache.http.cookie.CookieAttributeHandler;
import org.apache.http.cookie.CookieOrigin;
import org.apache.http.cookie.CookiePriorityComparator;
import org.apache.http.cookie.CookieSpec;
import org.apache.http.cookie.MalformedCookieException;
import org.apache.http.cookie.SM;
import org.apache.http.message.BufferedHeader;
import org.apache.http.message.ParserCursor;
import org.apache.http.message.TokenParser;
import org.apache.http.util.Args;
import org.apache.http.util.CharArrayBuffer;
@Contract(threading = ThreadingBehavior.SAFE)
public class RFC6265CookieSpec implements CookieSpec {
private final static char PARAM_DELIMITER = ';';
private final static char COMMA_CHAR = ',';
private final static char EQUAL_CHAR = '=';
private final static char DQUOTE_CHAR = '"';
private final static char ESCAPE_CHAR = '\\';
private static final BitSet TOKEN_DELIMS = TokenParser.INIT_BITSET(EQUAL_CHAR, PARAM_DELIMITER);
private static final BitSet VALUE_DELIMS = TokenParser.INIT_BITSET(PARAM_DELIMITER);
private static final BitSet SPECIAL_CHARS = TokenParser.INIT_BITSET(' ',
DQUOTE_CHAR, COMMA_CHAR, PARAM_DELIMITER, ESCAPE_CHAR);
private final CookieAttributeHandler[] attribHandlers;
private final Map<String, CookieAttributeHandler> attribHandlerMap;
private final TokenParser tokenParser;
protected RFC6265CookieSpec(final CommonCookieAttributeHandler... handlers) {
super();
this.attribHandlers = handlers.clone();
this.attribHandlerMap = new ConcurrentHashMap<String, CookieAttributeHandler>(handlers.length);
for (final CommonCookieAttributeHandler handler: handlers) {
this.attribHandlerMap.put(handler.getAttributeName().toLowerCase(Locale.ROOT), handler);
}
this.tokenParser = TokenParser.INSTANCE;
}
static String getDefaultPath(final CookieOrigin origin) {
String defaultPath = origin.getPath();
int lastSlashIndex = defaultPath.lastIndexOf('/');
if (lastSlashIndex >= 0) {
if (lastSlashIndex == 0) {
lastSlashIndex = 1;
}
defaultPath = defaultPath.substring(0, lastSlashIndex);
}
return defaultPath;
}
static String getDefaultDomain(final CookieOrigin origin) {
return origin.getHost();
}
@Override
public final List<Cookie> (final Header header, final CookieOrigin origin) throws MalformedCookieException {
Args.notNull(header, "Header");
Args.notNull(origin, "Cookie origin");
if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) {
throw new MalformedCookieException("Unrecognized cookie header: '" + header.toString() + "'");
}
final CharArrayBuffer buffer;
final ParserCursor cursor;
if (header instanceof FormattedHeader) {
buffer = ((FormattedHeader) header).getBuffer();
cursor = new ParserCursor(((FormattedHeader) header).getValuePos(), buffer.length());
} else {
final String s = header.getValue();
if (s == null) {
throw new MalformedCookieException("Header value is null");
}
buffer = new CharArrayBuffer(s.length());
buffer.append(s);
cursor = new ParserCursor(0, buffer.length());
}
final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
if (name.isEmpty()) {
return Collections.emptyList();
}
if (cursor.atEnd()) {
return Collections.emptyList();
}
final int valueDelim = buffer.charAt(cursor.getPos());
cursor.updatePos(cursor.getPos() + 1);
if (valueDelim != '=') {
throw new MalformedCookieException("Cookie value is invalid: '" + header.toString() + "'");
}
final String value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
if (!cursor.atEnd()) {
cursor.updatePos(cursor.getPos() + 1);
}
final BasicClientCookie cookie = new BasicClientCookie(name, value);
cookie.setPath(getDefaultPath(origin));
cookie.setDomain(getDefaultDomain(origin));
cookie.setCreationDate(new Date());
final Map<String, String> attribMap = new LinkedHashMap<String, String>();
while (!cursor.atEnd()) {
final String paramName = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS)
.toLowerCase(Locale.ROOT);
String paramValue = null;
if (!cursor.atEnd()) {
final int paramDelim = buffer.charAt(cursor.getPos());
cursor.updatePos(cursor.getPos() + 1);
if (paramDelim == EQUAL_CHAR) {
paramValue = tokenParser.parseToken(buffer, cursor, VALUE_DELIMS);
if (!cursor.atEnd()) {
cursor.updatePos(cursor.getPos() + 1);
}
}
}
cookie.setAttribute(paramName, paramValue);
attribMap.put(paramName, paramValue);
}
if (attribMap.containsKey(ClientCookie.MAX_AGE_ATTR)) {
attribMap.remove(ClientCookie.EXPIRES_ATTR);
}
for (final Map.Entry<String, String> entry: attribMap.entrySet()) {
final String paramName = entry.getKey();
final String paramValue = entry.getValue();
final CookieAttributeHandler handler = this.attribHandlerMap.get(paramName);
if (handler != null) {
handler.parse(cookie, paramValue);
}
}
return Collections.<Cookie>singletonList(cookie);
}
@Override
public final void validate(final Cookie cookie, final CookieOrigin origin)
throws MalformedCookieException {
Args.notNull(cookie, "Cookie");
Args.notNull(origin, "Cookie origin");
for (final CookieAttributeHandler handler: this.attribHandlers) {
handler.validate(cookie, origin);
}
}
@Override
public final boolean match(final Cookie cookie, final CookieOrigin origin) {
Args.notNull(cookie, "Cookie");
Args.notNull(origin, "Cookie origin");
for (final CookieAttributeHandler handler: this.attribHandlers) {
if (!handler.match(cookie, origin)) {
return false;
}
}
return true;
}
@Override
public List<Header> formatCookies(final List<Cookie> cookies) {
Args.notEmpty(cookies, "List of cookies");
final List<? extends Cookie> sortedCookies;
if (cookies.size() > 1) {
sortedCookies = new ArrayList<Cookie>(cookies);
Collections.sort(sortedCookies, CookiePriorityComparator.INSTANCE);
} else {
sortedCookies = cookies;
}
final CharArrayBuffer buffer = new CharArrayBuffer(20 * sortedCookies.size());
buffer.append(SM.COOKIE);
buffer.append(": ");
for (int n = 0; n < sortedCookies.size(); n++) {
final Cookie cookie = sortedCookies.get(n);
if (n > 0) {
buffer.append(PARAM_DELIMITER);
buffer.append(' ');
}
buffer.append(cookie.getName());
final String s = cookie.getValue();
if (s != null) {
buffer.append(EQUAL_CHAR);
if (containsSpecialChar(s)) {
buffer.append(DQUOTE_CHAR);
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
if (ch == DQUOTE_CHAR || ch == ESCAPE_CHAR) {
buffer.append(ESCAPE_CHAR);
}
buffer.append(ch);
}
buffer.append(DQUOTE_CHAR);
} else {
buffer.append(s);
}
}
}
final List<Header> headers = new ArrayList<Header>(1);
headers.add(new BufferedHeader(buffer));
return headers;
}
boolean containsSpecialChar(final CharSequence s) {
return containsChars(s, SPECIAL_CHARS);
}
boolean containsChars(final CharSequence s, final BitSet chars) {
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
if (chars.get(ch)) {
return true;
}
}
return false;
}
@Override
public final int getVersion() {
return 0;
}
@Override
public final Header () {
return null;
}
}