| /* |
| * Copyright (C) 2018 Sony Interactive Entertainment Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "CookieUtil.h" |
| |
| #if USE(CURL) |
| |
| #include "Cookie.h" |
| |
| #include <wtf/DateMath.h> |
| #include <wtf/Optional.h> |
| #include <wtf/text/WTFString.h> |
| |
| /* This is the maximum line length we accept for a cookie line. RFC 2109 |
| section 6.3 says: |
| |
| "at least 4096 bytes per cookie (as measured by the size of the characters |
| that comprise the cookie non-terminal in the syntax description of the |
| Set-Cookie header)" |
| */ |
| |
| #define MAX_COOKIE_LINE 5000 |
| #define MAX_COOKIE_LINE_TXT "4999" |
| |
| #define MAX_NAME 1024 |
| #define MAX_NAME_TXT "1023" |
| |
| namespace WebCore { |
| |
| namespace CookieUtil { |
| |
| bool isIPAddress(const String& hostname) |
| { |
| return URL::hostIsIPAddress(hostname); |
| } |
| |
| bool domainMatch(const String& cookieDomain, const String& host) |
| { |
| size_t index = host.find(cookieDomain); |
| |
| bool tailMatch = (index != WTF::notFound && index + cookieDomain.length() == host.length()); |
| |
| // Check if host equals cookie domain. |
| if (tailMatch && !index) |
| return true; |
| |
| // Check if host is a subdomain of the domain in the cookie. |
| // Curl uses a '.' in front of domains to indicate it's valid on subdomains. |
| if (tailMatch && index > 0 && host[index] == '.') |
| return true; |
| |
| // Check the special case where host equals the cookie domain, except for a leading '.' in the cookie domain. |
| // E.g. cookie domain is .apple.com and host is apple.com. |
| if (cookieDomain[0] == '.' && cookieDomain.find(host) == 1) |
| return true; |
| |
| return false; |
| } |
| |
| static std::optional<double> parseExpires(const char* expires) |
| { |
| double tmp = WTF::parseDateFromNullTerminatedCharacters(expires); |
| if (isnan(tmp)) |
| return { }; |
| |
| return std::optional<double> {tmp / WTF::msPerSecond}; |
| } |
| |
| static void parseCookieAttributes(const String& attribute, const String& domain, bool& hasMaxAge, Cookie& result) |
| { |
| size_t assignmentPosition = attribute.find('='); |
| |
| String attributeName; |
| String attributeValue; |
| |
| if (assignmentPosition != notFound) { |
| attributeName = attribute.substring(0, assignmentPosition).stripWhiteSpace(); |
| attributeValue = attribute.substring(assignmentPosition + 1).stripWhiteSpace(); |
| } else |
| attributeName = attribute.stripWhiteSpace(); |
| |
| if (equalIgnoringASCIICase(attributeName, "httponly")) |
| result.httpOnly = true; |
| else if (equalIgnoringASCIICase(attributeName, "secure")) |
| result.secure = true; |
| else if (equalIgnoringASCIICase(attributeName, "domain")) { |
| if (attributeValue.isEmpty()) |
| return; |
| |
| // Enforce a dot character prefix to hostnames which are not ip addresses and not single value hostnames such as localhost |
| if (!isIPAddress(attributeValue) && !attributeValue.startsWith('.') && attributeValue.find('.') != notFound) |
| attributeValue = "." + attributeValue; |
| |
| // Make sure the host can set a cookie for the domain |
| // FIXME: firefox and chrome both ignore cookies with no valid domain set |
| // we currently ignore the invalid domains and default to the hostname as the domain |
| if (domainMatch(attributeValue, domain)) |
| result.domain = attributeValue; |
| |
| } else if (equalIgnoringASCIICase(attributeName, "max-age")) { |
| bool ok; |
| time_t expiryTime = time(0) + attributeValue.toInt64(&ok); |
| if (ok) { |
| result.expires = (double)expiryTime; |
| result.session = false; |
| |
| // If there is a max-age attribute as well as an expires attribute |
| // the rightmost max-age attribute takes precedence. |
| hasMaxAge = true; |
| } |
| } else if (equalIgnoringASCIICase(attributeName, "expires") && !hasMaxAge) { |
| if (auto expiryTime = parseExpires(attributeValue.utf8().data())) { |
| result.expires = expiryTime.value(); |
| result.session = false; |
| } |
| } else if (equalIgnoringASCIICase(attributeName, "path")) { |
| if (!attributeValue.isEmpty() && attributeValue.startsWith('/')) |
| result.path = attributeValue; |
| } |
| } |
| |
| bool parseCookieHeader(const String& cookieLine, const String& domain, Cookie& result) |
| { |
| if (cookieLine.length() >= MAX_COOKIE_LINE) |
| return false; |
| |
| // This Algorithm is based on the algorithm defined in RFC 6265 5.2 https://tools.ietf.org/html/rfc6265#section-5.2/ |
| |
| size_t separatorPosition = cookieLine.find(';'); |
| |
| String cookiePair = separatorPosition == notFound ? cookieLine : cookieLine.substring(0, separatorPosition); |
| |
| String cookieName; |
| String cookieValue; |
| size_t assignmentPosition = cookieLine.find('='); |
| |
| // RFC6265 says to ignore cookies pairs with empty names or no assignment character |
| // but browsers seem to treat this type of cookie string as the cookie value |
| if (assignmentPosition == notFound) |
| cookieValue = cookiePair; |
| else { |
| cookieName = cookiePair.substring(0, assignmentPosition); |
| cookieValue = cookiePair.substring(assignmentPosition + 1); |
| } |
| |
| result.name = cookieName.stripWhiteSpace(); |
| result.value = cookieValue.stripWhiteSpace(); |
| |
| bool hasMaxAge = false; |
| result.session = true; |
| |
| Vector<String> cookieAttributes; |
| cookieLine.split(';', true, cookieAttributes); |
| for (auto attribute : cookieAttributes) |
| parseCookieAttributes(attribute, domain, hasMaxAge, result); |
| |
| return true; |
| } |
| |
| } // namespace CookieUtil |
| |
| } // namespace WebCore |
| |
| #endif |