#include "config.h"
#include "CookieParser.h"
#include "Logging.h"
#include "ParsedCookie.h"
#include <network/DomainTools.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
#define LOG_ERROR_AND_RETURN(format, ...) \
{ \
LOG_ERROR(format, ## __VA_ARGS__); \
return 0; \
}
static inline bool isCookieHeaderSeparator(UChar c)
{
return (c == '\r' || c =='\n');
}
static inline bool isLightweightSpace(UChar c)
{
return (c == ' ' || c == '\t');
}
CookieParser::CookieParser(const KURL& defaultCookieURL)
: m_defaultCookieURL(defaultCookieURL)
{
m_defaultCookieHost = defaultCookieURL.host();
m_defaultDomainIsIPAddress = false;
BlackBerry::Platform::String hostDomainCanonical = BlackBerry::Platform::getCanonicalIPFormat(m_defaultCookieHost);
if (!hostDomainCanonical.empty()) {
m_defaultCookieHost = hostDomainCanonical;
m_defaultDomainIsIPAddress = true;
}
}
CookieParser::~CookieParser()
{
}
Vector<RefPtr<ParsedCookie> > CookieParser::parse(const String& cookies)
{
unsigned cookieStart, cookieEnd = 0;
double curTime = currentTime();
Vector<RefPtr<ParsedCookie>, 4> parsedCookies;
unsigned cookiesLength = cookies.length();
if (!cookiesLength) return parsedCookies;
while (cookieEnd <= cookiesLength) {
cookieStart = cookieEnd;
while (cookieEnd <= cookiesLength && !isCookieHeaderSeparator(cookies[cookieEnd]))
cookieEnd++;
if (cookieStart == cookieEnd) {
++cookieEnd;
continue;
}
if (cookieEnd < cookiesLength && isCookieHeaderSeparator(cookies[cookieEnd]))
++cookieEnd;
RefPtr<ParsedCookie> cookie = parseOneCookie(cookies, cookieStart, cookieEnd - 1, curTime);
if (cookie)
parsedCookies.append(cookie);
}
return parsedCookies;
}
PassRefPtr<ParsedCookie> CookieParser::parseOneCookie(const String& cookie)
{
return parseOneCookie(cookie, 0, cookie.length() - 1, currentTime());
}
PassRefPtr<ParsedCookie> CookieParser::parseOneCookie(const String& cookie, unsigned start, unsigned end, double curTime)
{
RefPtr<ParsedCookie> res = ParsedCookie::create(curTime);
if (!res)
LOG_ERROR_AND_RETURN("Out of memory");
res->setProtocol(m_defaultCookieURL.protocol());
unsigned tokenEnd = start; unsigned pairEnd = start;
bool foundEqual = false;
while (pairEnd < end && cookie[pairEnd] != ';') {
if (cookie[pairEnd] == '=') {
if (tokenEnd == start) {
tokenEnd = pairEnd;
foundEqual = true;
}
} else if (cookie[pairEnd] == '"') {
size_t secondQuotePosition = cookie.find('"', pairEnd + 1);
if (secondQuotePosition != notFound && secondQuotePosition <= end) {
pairEnd = secondQuotePosition + 1;
continue;
}
}
pairEnd++;
}
unsigned tokenStart = start;
bool hasName = false; if (tokenEnd != start) {
unsigned nameEnd = tokenEnd;
nameEnd--;
while (nameEnd && isLightweightSpace(cookie[nameEnd]))
nameEnd--;
while (tokenStart < nameEnd && isLightweightSpace(cookie[tokenStart]))
tokenStart++;
if (nameEnd + 1 <= tokenStart)
LOG_ERROR_AND_RETURN("Empty name. Rejecting the cookie");
String name = cookie.substring(tokenStart, nameEnd + 1 - start);
res->setName(name);
hasName = true;
}
tokenStart = tokenEnd + 1;
if (!hasName)
--tokenStart;
while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart]))
tokenStart++;
tokenEnd = pairEnd;
while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1]))
tokenEnd--;
String value;
if (tokenEnd == tokenStart) {
value = String();
} else
value = cookie.substring(tokenStart, tokenEnd - tokenStart);
if (hasName)
res->setValue(value);
else if (foundEqual)
return 0;
else
res->setName(value);
while (pairEnd < end) {
pairEnd++;
while (pairEnd < end && isLightweightSpace(cookie[pairEnd]))
pairEnd++;
tokenStart = pairEnd;
tokenEnd = tokenStart;
while (pairEnd < end && cookie[pairEnd] != ';') {
if (tokenEnd == tokenStart && cookie[pairEnd] == '=')
tokenEnd = pairEnd;
pairEnd++;
}
unsigned length = tokenEnd - tokenStart;
unsigned tokenStartSvg = tokenStart;
String parsedValue;
if (tokenStart != tokenEnd) {
tokenStart = tokenEnd + 1;
while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart]))
tokenStart++;
tokenEnd = pairEnd;
while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd - 1]))
tokenEnd--;
parsedValue = cookie.substring(tokenStart, tokenEnd - tokenStart);
} else {
parsedValue = String();
length = pairEnd - tokenStart;
}
switch (cookie[tokenStartSvg]) {
case 'P':
case 'p' : {
if (length >= 4 && ((cookie.find("ath", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) {
res->setPath(decodeURLEscapeSequences(parsedValue));
#if 0
if (!m_defaultCookieURL.path().startsWith(res->path()))
LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (path): it does not math the URL", cookie.ascii().data());
#endif
} else
LOG_ERROR("Invalid cookie attribute %s (path)", cookie.ascii().data());
break;
}
case 'D':
case 'd' : {
if (length >= 6 && ((cookie.find("omain", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) {
if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"')
parsedValue = parsedValue.substring(1, parsedValue.length() - 2);
size_t dotPosition = parsedValue.find(".", 1);
if (dotPosition == notFound || dotPosition == parsedValue.length())
LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): it does not contain an embedded dot", cookie.ascii().data());
StringBuilder parsedValueBuilder;
if (parsedValue[0] != '.')
parsedValueBuilder.appendLiteral(".");
parsedValueBuilder.append(parsedValue);
String realDomain = parsedValueBuilder.toString();
StringBuilder defaultHostBuilder;
defaultHostBuilder.appendLiteral(".");
defaultHostBuilder.append(m_defaultCookieHost);
String defaultHost = defaultHostBuilder.toString();
bool isIPAddress = false;
if (m_defaultDomainIsIPAddress) {
String realDomainCanonical = BlackBerry::Platform::getCanonicalIPFormat(realDomain);
if (realDomainCanonical.isEmpty() || realDomainCanonical != defaultHost)
LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): domain is IP but does not match host's IP", cookie.ascii().data());
realDomain = realDomainCanonical;
isIPAddress = true;
} else {
if (!defaultHost.endsWith(realDomain, false))
LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): it does not domain match the host", cookie.ascii().data());
if (BlackBerry::Platform::isTopLevelDomain(realDomain))
LOG_ERROR_AND_RETURN("Invalid cookie attribute %s (domain): it did not pass the top level domain check", cookie.ascii().data());
}
res->setDomain(realDomain, isIPAddress);
} else
LOG_ERROR("Invalid cookie attribute %s (domain)", cookie.ascii().data());
break;
}
case 'E' :
case 'e' : {
if (length >= 7 && ((cookie.find("xpires", tokenStartSvg + 1, false) - tokenStartSvg) == 1))
res->setExpiry(parsedValue);
else
LOG_ERROR("Invalid cookie attribute %s (expires)", cookie.ascii().data());
break;
}
case 'M' :
case 'm' : {
if (length >= 7 && ((cookie.find("ax-age", tokenStartSvg + 1, false) - tokenStartSvg) == 1))
res->setMaxAge(parsedValue);
else
LOG_ERROR("Invalid cookie attribute %s (max-age)", cookie.ascii().data());
break;
}
case 'C' :
case 'c' : {
if (length >= 7 && ((cookie.find("omment", tokenStartSvg + 1, false) - tokenStartSvg) == 1))
LOG(Network, "Comment %s for ParsedCookie : %s\n", parsedValue.ascii().data(), cookie.ascii().data());
else
LOG_ERROR("Invalid cookie attribute %s (comment)", cookie.ascii().data());
break;
}
case 'V' :
case 'v' : {
if (length >= 7 && ((cookie.find("ersion", tokenStartSvg + 1, false) - tokenStartSvg) == 1)) {
if (parsedValue.length() > 1 && parsedValue[0] == '"' && parsedValue[parsedValue.length() - 1] == '"')
parsedValue = parsedValue.substring(1, parsedValue.length() - 2);
if (parsedValue.toInt() != 1)
LOG_ERROR_AND_RETURN("ParsedCookie version %d not supported (only support version=1)", parsedValue.toInt());
} else
LOG_ERROR("Invalid cookie attribute %s (version)", cookie.ascii().data());
break;
}
case 'S' :
case 's' : {
if (length >= 6 && ((cookie.find("ecure", tokenStartSvg + 1, false) - tokenStartSvg) == 1))
res->setSecureFlag(true);
else
LOG_ERROR("Invalid cookie attribute %s (secure)", cookie.ascii().data());
break;
}
case 'H':
case 'h': {
if (length >= 8 && ((cookie.find("ttpOnly", tokenStartSvg + 1, false) - tokenStartSvg) == 1))
res->setIsHttpOnly(true);
else
LOG_ERROR("Invalid cookie attribute %s (HttpOnly)", cookie.ascii().data());
break;
}
default : {
if (length)
LOG_ERROR("Invalid token for cookie %s", cookie.ascii().data());
}
}
}
if (!res->isUnderSizeLimit())
LOG_ERROR_AND_RETURN("ParsedCookie %s is above the 4kb in length : REJECTED", cookie.ascii().data());
if (!res->domain())
res->setDomain(m_defaultCookieHost, m_defaultDomainIsIPAddress);
if (!res->path() || !res->path().length() || !res->path().startsWith("/", false)) {
String path = m_defaultCookieURL.string().substring(m_defaultCookieURL.pathStart(), m_defaultCookieURL.pathAfterLastSlash() - m_defaultCookieURL.pathStart() - 1);
if (path.isEmpty())
path = "/";
res->setPath(decodeURLEscapeSequences(path));
}
return res;
}
}