955e5d150eac87008fbd4456686bef54138661f4
[WebKit-https.git] / Source / WebCore / platform / network / curl / CookieUtil.cpp
1 /*
2  * Copyright (C) 2018 Sony Interactive Entertainment Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "config.h"
26 #include "CookieUtil.h"
27
28 #if USE(CURL)
29
30 #include "Cookie.h"
31
32 #include <wtf/DateMath.h>
33 #include <wtf/Optional.h>
34 #include <wtf/text/WTFString.h>
35
36 /* This is the maximum line length we accept for a cookie line. RFC 2109
37    section 6.3 says:
38
39    "at least 4096 bytes per cookie (as measured by the size of the characters
40    that comprise the cookie non-terminal in the syntax description of the
41    Set-Cookie header)"
42 */
43
44 #define MAX_COOKIE_LINE 5000
45 #define MAX_COOKIE_LINE_TXT "4999"
46
47 #define MAX_NAME 1024
48 #define MAX_NAME_TXT "1023"
49
50 namespace WebCore {
51
52 namespace CookieUtil {
53
54 bool isIPAddress(const String& hostname)
55 {
56     // Assuming that hosts ending in a digit are IP Addresses
57     return !hostname.isEmpty() && isASCIIDigit(hostname[hostname.length() - 1]);
58 }
59
60 bool domainMatch(const String& cookieDomain, const String& host)
61 {
62     size_t index = host.find(cookieDomain);
63
64     bool tailMatch = (index != WTF::notFound && index + cookieDomain.length() == host.length());
65
66     // Check if host equals cookie domain.
67     if (tailMatch && !index)
68         return true;
69
70     // Check if host is a subdomain of the domain in the cookie.
71     // Curl uses a '.' in front of domains to indicate it's valid on subdomains.
72     if (tailMatch && index > 0 && host[index] == '.')
73         return true;
74
75     // Check the special case where host equals the cookie domain, except for a leading '.' in the cookie domain.
76     // E.g. cookie domain is .apple.com and host is apple.com.
77     if (cookieDomain[0] == '.' && cookieDomain.find(host) == 1)
78         return true;
79
80     return false;
81 }
82
83 static std::optional<double> parseExpires(const char* expires)
84 {
85     double tmp = WTF::parseDateFromNullTerminatedCharacters(expires);
86     if (isnan(tmp))
87         return { };
88
89     return std::optional<double> {tmp / WTF::msPerSecond};
90 }
91
92 static void parseCookieAttributes(const String& attribute, const String& domain, bool& hasMaxAge, Cookie& result)
93 {
94     size_t assignmentPosition = attribute.find('=');
95
96     String attributeName;
97     String attributeValue;
98
99     if (assignmentPosition != notFound) {
100         attributeName = attribute.substring(0, assignmentPosition).stripWhiteSpace();
101         attributeValue = attribute.substring(assignmentPosition + 1).stripWhiteSpace();
102     } else
103         attributeName = attribute.stripWhiteSpace();
104
105     if (equalIgnoringASCIICase(attributeName, "httponly"))
106         result.httpOnly = true;
107     else if (equalIgnoringASCIICase(attributeName, "secure"))
108         result.secure = true;
109     else if (equalIgnoringASCIICase(attributeName, "domain")) {
110         if (attributeValue.isEmpty())
111             return;
112
113         // Enforce a dot character prefix to hostnames which are not ip addresses and not single value hostnames such as localhost
114         if (!isIPAddress(attributeValue) && !attributeValue.startsWith('.') && attributeValue.find('.') != notFound)
115             attributeValue = "." + attributeValue;
116
117         // Make sure the host can set a cookie for the domain
118         // FIXME: firefox and chrome both ignore cookies with no valid domain set
119         // we currently ignore the invalid domains and default to the hostname as the domain
120         if (domainMatch(attributeValue, domain))
121             result.domain = attributeValue;
122
123     } else if (equalIgnoringASCIICase(attributeName, "max-age")) {
124         bool ok;
125         time_t expiryTime = time(0) + attributeValue.toInt64(&ok);
126         if (ok) {
127             result.expires = (double)expiryTime;
128             result.session = false;
129
130             // If there is a max-age attribute as well as an expires attribute
131             // the rightmost max-age attribute takes precedence.
132             hasMaxAge = true;
133         }
134     } else if (equalIgnoringASCIICase(attributeName, "expires") && !hasMaxAge) {
135         if (auto expiryTime = parseExpires(attributeValue.utf8().data())) {
136             result.expires = expiryTime.value();
137             result.session = false;
138         }
139     } else if (equalIgnoringASCIICase(attributeName, "path")) {
140         if (!attributeValue.isEmpty() && attributeValue.startsWith('/'))
141             result.path = attributeValue;
142     }
143 }
144
145 bool parseCookieHeader(const String& cookieLine, const String& domain, Cookie& result)
146 {
147     if (cookieLine.length() >= MAX_COOKIE_LINE)
148         return false;
149
150     // This Algorithm is based on the algorithm defined in RFC 6265 5.2 https://tools.ietf.org/html/rfc6265#section-5.2/
151
152     size_t separatorPosition = cookieLine.find(';');
153
154     String cookiePair = separatorPosition == notFound ? cookieLine : cookieLine.substring(0, separatorPosition);
155
156     String cookieName;
157     String cookieValue;
158     size_t assignmentPosition = cookieLine.find('=');
159
160     // RFC6265 says to ignore cookies pairs with empty names or no assignment character
161     // but browsers seem to treat this type of cookie string as the cookie value
162     if (assignmentPosition == notFound)
163         cookieValue = cookiePair;
164     else {
165         cookieName = cookiePair.substring(0, assignmentPosition);
166         cookieValue = cookiePair.substring(assignmentPosition + 1);
167     }
168
169     result.name = cookieName.stripWhiteSpace();
170     result.value = cookieValue.stripWhiteSpace();
171
172     bool hasMaxAge = false;
173     result.session = true;
174
175     Vector<String> cookieAttributes;
176     cookieLine.split(';', true, cookieAttributes);
177     for (auto attribute : cookieAttributes)
178         parseCookieAttributes(attribute, domain, hasMaxAge, result);
179
180     return true;
181 }
182
183 } // namespace CookieUtil
184
185 } // namespace WebCore
186
187 #endif