// Parse the string without "Set-Cookie" according to Firefox grammar (loosely RFC 2109 compliant) // see netwerk/cookie/src/nsCookieService.cpp comment for it Cookie* CookieParser::parseOneCookie(const String& cookie, unsigned start, unsigned end, double curTime) { Cookie* res = new Cookie(curTime); if (!res) { LOG_ERROR("Out of memory"); return 0; } // Parse [NAME "="] VALUE unsigned tokenEnd = start; // Token end contains the position of the '=' or the end of a token unsigned pairEnd = start; // Pair end contains always the position of the ';' // find the *first* ';' and the '=' (if they exist) // FIXME : should handle quoted string while (pairEnd < end && cookie[pairEnd] != ';') { if (tokenEnd == start && cookie[pairEnd] == '=') tokenEnd = pairEnd; pairEnd++; } unsigned tokenStart = start; if (tokenEnd != start) { // There is a '=' so parse the NAME unsigned nameEnd = tokenEnd; // Remove lightweight spaces. while (nameEnd && isLightweightSpace(cookie[nameEnd])) nameEnd--; while (tokenStart < nameEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; if (nameEnd == tokenStart) { LOG_ERROR("Empty name. Rejecting the cookie"); delete res; return 0; } String name = cookie.substring(tokenStart, nameEnd - start); res->setName(name); } // Now parse the VALUE tokenStart = tokenEnd + 1; // Skip lightweight spaces in our token while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; tokenEnd = pairEnd; while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd])) tokenEnd--; String value; if (tokenEnd == tokenStart) { // Firefox accepts empty value so we will do the same value = String(); } else value = cookie.substring(tokenStart, tokenEnd - tokenStart); res->setValue(value); while (pairEnd < end) { // Switch to the next pair as pairEnd is on the ';' and fast-forward any lightweight spaces. pairEnd++; while (pairEnd < end && isLightweightSpace(cookie[pairEnd])) pairEnd++; tokenStart = pairEnd; tokenEnd = tokenStart; // initiliasize token end to catch first '=' while (pairEnd < end && cookie[pairEnd] != ';') { if (tokenEnd == tokenStart && cookie[pairEnd] == '=') tokenEnd = pairEnd; pairEnd++; } // FIXME : should we skip lightweight spaces here ? unsigned length = tokenEnd - tokenStart; unsigned tokenStartSvg = tokenStart; String parsedValue; if (tokenStart != tokenEnd) { // There is an equal sign so remove lightweight spaces in VALUE tokenStart = tokenEnd + 1; while (tokenStart < pairEnd && isLightweightSpace(cookie[tokenStart])) tokenStart++; tokenEnd = pairEnd; while (tokenEnd > tokenStart && isLightweightSpace(cookie[tokenEnd])) tokenEnd--; parsedValue = cookie.substring(tokenStart, tokenEnd - tokenStart); } else { // If the parsedValue is empty, initialise it in case we need it parsedValue = String(); // Handle a token without value. length = pairEnd - tokenStart; } // Detect which "cookie-av" is parsed // Look at the first char then parse the whole for performance issue switch (cookie[tokenStartSvg]) { case 'P': case 'p' : { if (length >= 4 && cookie.find("ath", tokenStartSvg + 1, false)) res->setPath(parsedValue); else { LOG_ERROR("Invalid cookie %s (path)", cookie.ascii().data()); delete res; return 0; } break; } case 'D': case 'd' : { if (length >= 6 && cookie.find("omain", tokenStartSvg + 1, false)) { // If the domain does not start with a dot, add one for security checks String realDomain = parsedValue[0] == '.' ? parsedValue : "." + parsedValue; res->setDomain(realDomain); } else { LOG_ERROR("Invalid cookie %s (domain)", cookie.ascii().data()); delete res; return 0; } break; } case 'E' : case 'e' : { if (length >= 7 && cookie.find("xpires", tokenStartSvg + 1, false)) res->setExpiry(parsedValue); else { LOG_ERROR("Invalid cookie %s (expires)", cookie.ascii().data()); delete res; return 0; } break; } case 'M' : case 'm' : { if (length >= 7 && cookie.find("ax-age", tokenStartSvg + 1, false)) res->setMaxAge(parsedValue); else { LOG_ERROR("Invalid cookie %s (max-age)", cookie.ascii().data()); delete res; return 0; } break; } case 'C' : case 'c' : { if (length >= 7 && cookie.find("omment", tokenStartSvg + 1, false)) // We do not have room for the comment part (and so do Mozilla) so just log the comment. LOG(Network, "Comment %s for Cookie : %s\n", parsedValue.ascii().data(), cookie.ascii().data()); else { LOG_ERROR("Invalid cookie %s (comment)", cookie.ascii().data()); delete res; return 0; } break; } case 'V' : case 'v' : { if (length >= 7 && cookie.find("ersion", tokenStartSvg + 1, false)) { if (parsedValue.toInt() != 1) { LOG_ERROR("Cookie version %d not supported (only support version=1)", parsedValue.toInt()); delete res; return 0; } } else { LOG_ERROR("Invalid cookie %s (version)", cookie.ascii().data()); delete res; return 0; } break; } case 'S' : case 's' : { // Secure is a standalone token ("Secure;") if (length >= 6 && cookie.find("ecure", tokenStartSvg + 1, false)) res->setSecureFlag(true); else { LOG_ERROR("Invalid cookie %s (secure)", cookie.ascii().data()); delete res; return 0; } break; } case 'H': case 'h': { // HttpOnly is a standalone token ("HttpOnly;") if (length >= 8 && cookie.find("ttpOnly", tokenStartSvg + 1, false)) res->setIsHttpOnly(true); else { LOG_ERROR("Invalid cookie %s (HttpOnly)", cookie.ascii().data()); delete res; return 0; } break; } default : { // If length == 0, we should be at the end of the cookie (case : ";\r") so ignore it if (length) { LOG_ERROR("Invalid token for cookie %s", cookie.ascii().data()); delete res; return 0; } } } } // Check if the cookie is valid with respect to the size limit/ if (!res->isUnderSizeLimit()) { LOG_ERROR("Cookie %s is above the 4kb in length : REJECTED", cookie.ascii().data()); delete res; return 0; } // If some pair was not provided, during parsing then apply some default value // the rest has been done in the constructor. // If no domain was provided, set it to the host if (!res->domain() || !res->domain().length()) res->setDomain("." + m_defaultCookieURL.host()); // If no path was provided, set it to the host's path if (!res->path() || !res->path().length()) res->setPath(m_defaultCookieURL.path()); return res; }
void Cookies::clear(Cookie & cookie) { cookie.setValue("deleted"); cookie.setExpires(QDateTime::fromTime_t(0)); m_cookies[cookie.getName()] = cookie; }
void CookieJar::addCookieHeader( const std::string &domain_in, const std::string &path_in, const std::string &request) { // printf("addCookieHeader: %s \nFrom: %s\n",request.c_str(), domain_in.c_str()); std::string path(path_in); std::string domain(domain_in); bool secure(false); string lowCaseRequest(request); transform(lowCaseRequest.begin(), lowCaseRequest.end(), lowCaseRequest.begin(), ::tolower); ParameterSet paramSet(request); ParameterSet lowCaseParamSet(lowCaseRequest); if ( lowCaseParamSet.hasParameter(PATH_STR) ) { lowCaseParamSet.parameter(PATH_STR, path); } else { // sort out basename if (path.length() and path[path.length()-1] != '/') { path = nds::File::dirname(path.c_str()); } } if ( lowCaseParamSet.hasParameter(DOMAIN_STR) ) { lowCaseParamSet.parameter(DOMAIN_STR, domain); // reject domains that do not start with a dot /*if (domain[0] != '.') { printf("Reject cookie for %s\n", domain.c_str()); return; }*/ } if ( lowCaseParamSet.hasParameter(SECURE_STR) ) { secure = true; } int expires = -1; if (lowCaseParamSet.hasParameter(EXPIRES_STR)) { std::string expval; const KeyValueMap & keyValueMap(paramSet.keyValueMap()); for (KeyValueMap::const_iterator it(keyValueMap.begin()); it != keyValueMap.end(); ++it) { string name = it->first; string lowCaseName(name); transform(lowCaseName.begin(), lowCaseName.end(), lowCaseName.begin(), ::tolower); if (lowCaseName == EXPIRES_STR) { expires = DateUtils::parseDate(it->second.c_str()); break; } } } const KeyValueMap & keyValueMap(paramSet.keyValueMap()); for (KeyValueMap::const_iterator it(keyValueMap.begin()); it != keyValueMap.end(); ++it) { string name = it->first; string lowCaseName(name); transform(lowCaseName.begin(), lowCaseName.end(), lowCaseName.begin(), ::tolower); if (lowCaseName == PATH_STR or lowCaseName == SECURE_STR or lowCaseName == HTTP_ONLY or lowCaseName == EXPIRES_STR or lowCaseName == DOMAIN_STR) { continue; } Cookie * existingCookie = hasCookieForDomain(domain, name); const string &value(it->second); if (existingCookie != 0) { // replace with new value existingCookie->setValue(value); existingCookie->setExpires(expires); existingCookie->setSaved(false); } else { Cookie * cookie(new Cookie(name, value, domain, path, expires, secure)); m_cookies.push_back(cookie); } } }