package io.craft.atom.protocol.http;
import static io.craft.atom.protocol.http.HttpConstants.EQUAL_SIGN;
import static io.craft.atom.protocol.http.HttpConstants.SEMICOLON;
import static io.craft.atom.protocol.http.HttpConstants.SP;
import io.craft.atom.protocol.AbstractProtocolCodec;
import io.craft.atom.protocol.ProtocolDecoder;
import io.craft.atom.protocol.ProtocolException;
import io.craft.atom.protocol.http.model.HttpCookie;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import lombok.ToString;
/**
* A {@link ProtocolDecoder} which decodes cookie string bytes into {@code Cookie} object, default charset is utf-8.
* <br>
* Only accept complete cookie bytes to decode, because this implementation is stateless and thread safe.
*
* @see HttpCookie
* @author mindwind
* @version 1.0, Mar 25, 2013
*/
@ToString(callSuper = true)
public class HttpCookieDecoder extends AbstractProtocolCodec implements ProtocolDecoder<HttpCookie> {
private static final int START = 0;
private static final int NAME = 1;
private static final int VALUE = 2;
private static final int ATTRIBUTE_START = 3;
private static final int ATTRIBUTE_NAME = 4;
private static final int DOMAIN_ATTRIBUTE_VALUE = 5;
private static final int PATH_ATTRIBUTE_VALUE = 6;
private static final int EXPIRES_ATTRIBUTE_VALUE = 7;
private static final int MAX_AGE_ATTRIBUTE_VALUE = 8;
private static final int EXTENSION_ATTRIBUTE_VALUE = 9;
private static final int END = -1;
private boolean setCookie = false;
// ~ ----------------------------------------------------------------------------------------------------------
public HttpCookieDecoder() {}
public HttpCookieDecoder(Charset charset) {
this.charset = charset;
}
public HttpCookieDecoder(Charset charset, boolean setCookie) {
this.charset = charset;
this.setCookie = setCookie;
}
// ~ ----------------------------------------------------------------------------------------------------------
@Override
public void reset() {}
@Override
public List<HttpCookie> decode(byte[] bytes) throws ProtocolException {
try {
if (setCookie) {
return decode4setCookie(bytes);
} else {
return decode4cookie(bytes);
}
} catch (Exception e) {
if (e instanceof ProtocolException) {
throw (ProtocolException) e;
}
throw new ProtocolException(e);
}
}
// Cookie: SID=31d4d96e407aad42; lang=en-US
// Cookie: test=test123
private List<HttpCookie> decode4cookie(byte[] bytes) throws ProtocolException {
List<HttpCookie> cookies = new ArrayList<HttpCookie>();
HttpCookie cookie = null;
int searchIndex = 0;
int stateIndex = 0;
int state = START;
int len = bytes.length;
int i = 0;
while (searchIndex < len) {
switch (state) {
case START:
// skip all OWS(Optional White Space)
for (; searchIndex < len && bytes[searchIndex] == SP; searchIndex++);
stateIndex = searchIndex;
state = NAME;
cookie = new HttpCookie();
break;
case NAME:
for (; searchIndex < len && bytes[searchIndex] != EQUAL_SIGN; searchIndex++, i++);
String name = new String(bytes, stateIndex, i, charset);
cookie.setName(name);
stateIndex = ++searchIndex;
i = 0;
state = VALUE;
break;
case VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String value = new String(bytes, stateIndex, i, charset);
cookie.setValue(value);
cookies.add(cookie);
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = START;
}
break;
case END:
// nothing to do
break;
}
}
return cookies;
}
// Set-Cookie: SID=31d4d96e407aad42; Domain=example.com; Path=/; HttpOnly; Secure; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=86400
// Set-Cookie: SID=31d4d96e407aad42; Domain=example.com; Path=/; HttpOnly; Secure; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Max-Age=86400; test=extensionTest
private List<HttpCookie> decode4setCookie(byte[] bytes) throws ProtocolException {
List<HttpCookie> cookies = new ArrayList<HttpCookie>();
HttpCookie cookie = null;
int searchIndex = 0;
int stateIndex = 0;
int state = START;
int len = bytes.length;
int i = 0;
String extentionName = "";
while (searchIndex < len) {
switch (state) {
case START:
// skip all OWS(Optional White Space)
for (; searchIndex < len && bytes[searchIndex] == SP; searchIndex++);
stateIndex = searchIndex;
state = NAME;
cookie = new HttpCookie();
break;
case NAME:
for (; searchIndex < len && bytes[searchIndex] != EQUAL_SIGN; searchIndex++, i++);
String name = new String(bytes, stateIndex, i, charset);
cookie.setName(name);
stateIndex = ++searchIndex;
i = 0;
state = VALUE;
break;
case VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String value = new String(bytes, stateIndex, i, charset);
cookie.setValue(value);
cookies.add(cookie);
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
break;
case ATTRIBUTE_START:
for (; searchIndex < len && bytes[searchIndex] == SP; searchIndex++);
stateIndex = searchIndex;
state = ATTRIBUTE_NAME;
break;
case ATTRIBUTE_NAME:
for (; searchIndex < len && bytes[searchIndex] != EQUAL_SIGN && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String an = new String(bytes, stateIndex, i, charset);
if (HttpCookie.DOMAIN.equalsIgnoreCase(an)) {
state = DOMAIN_ATTRIBUTE_VALUE;
} else if (HttpCookie.PATH.equalsIgnoreCase(an)) {
state = PATH_ATTRIBUTE_VALUE;
} else if (HttpCookie.HTTP_ONLY.equalsIgnoreCase(an)) {
cookie.setHttpOnly(true);
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
} else if (HttpCookie.SECURE.equalsIgnoreCase(an)) {
cookie.setSecure(true);
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
} else if (HttpCookie.EXPIRES.equalsIgnoreCase(an)) {
state = EXPIRES_ATTRIBUTE_VALUE;
} else if (HttpCookie.MAX_AGE.equalsIgnoreCase(an)) {
state = MAX_AGE_ATTRIBUTE_VALUE;
} else {
extentionName = an;
state = EXTENSION_ATTRIBUTE_VALUE;
}
stateIndex = ++searchIndex;
i = 0;
break;
case DOMAIN_ATTRIBUTE_VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String domain = new String(bytes, stateIndex, i, charset);
cookie.setDomain(domain);
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
break;
case PATH_ATTRIBUTE_VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String path = new String(bytes, stateIndex, i, charset);
cookie.setPath(path);
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
break;
case EXPIRES_ATTRIBUTE_VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String expires = new String(bytes, stateIndex, i, charset);
cookie.setExpires(HttpDates.parse(expires));
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
break;
case MAX_AGE_ATTRIBUTE_VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String maxAge = new String(bytes, stateIndex, i, charset);
cookie.setMaxAge(Integer.parseInt(maxAge));
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
break;
case EXTENSION_ATTRIBUTE_VALUE:
for (; searchIndex < len && bytes[searchIndex] != SEMICOLON; searchIndex++, i++);
String extentionValue = new String(bytes, stateIndex, i, charset);
cookie.addExtensionAttribute(extentionName, extentionValue);
stateIndex = ++searchIndex;
i = 0;
if (searchIndex >= len) {
state = END;
} else {
state = ATTRIBUTE_START;
}
break;
case END:
// nothing to do
break;
}
}
return cookies;
}
}