Add a way to check if a host is an IP address
[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     return URL::hostIsIPAddress(hostname);
57 }
58
59 bool domainMatch(const String& cookieDomain, const String& host)
60 {
61     size_t index = host.find(cookieDomain);
62
63     bool tailMatch = (index != WTF::notFound && index + cookieDomain.length() == host.length());
64
65     // Check if host equals cookie domain.
66     if (tailMatch && !index)
67         return true;
68
69     // Check if host is a subdomain of the domain in the cookie.
70     // Curl uses a '.' in front of domains to indicate it's valid on subdomains.
71     if (tailMatch && index > 0 && host[index] == '.')
72         return true;
73
74     // Check the special case where host equals the cookie domain, except for a leading '.' in the cookie domain.
75     // E.g. cookie domain is .apple.com and host is apple.com.
76     if (cookieDomain[0] == '.' && cookieDomain.find(host) == 1)
77         return true;
78
79     return false;
80 }
81
82 static std::optional<double> parseExpires(const char* expires)
83 {
84     double tmp = WTF::parseDateFromNullTerminatedCharacters(expires);
85     if (isnan(tmp))
86         return { };
87
88     return std::optional<double> {tmp / WTF::msPerSecond};
89 }
90
91 static void parseCookieAttributes(const String& attribute, const String& domain, bool& hasMaxAge, Cookie& result)
92 {
93     size_t assignmentPosition = attribute.find('=');
94
95     String attributeName;
96     String attributeValue;
97
98     if (assignmentPosition != notFound) {
99         attributeName = attribute.substring(0, assignmentPosition).stripWhiteSpace();
100         attributeValue = attribute.substring(assignmentPosition + 1).stripWhiteSpace();
101     } else
102         attributeName = attribute.stripWhiteSpace();
103
104     if (equalIgnoringASCIICase(attributeName, "httponly"))
105         result.httpOnly = true;
106     else if (equalIgnoringASCIICase(attributeName, "secure"))
107         result.secure = true;
108     else if (equalIgnoringASCIICase(attributeName, "domain")) {
109         if (attributeValue.isEmpty())
110             return;
111
112         // Enforce a dot character prefix to hostnames which are not ip addresses and not single value hostnames such as localhost
113         if (!isIPAddress(attributeValue) && !attributeValue.startsWith('.') && attributeValue.find('.') != notFound)
114             attributeValue = "." + attributeValue;
115
116         // Make sure the host can set a cookie for the domain
117         // FIXME: firefox and chrome both ignore cookies with no valid domain set
118         // we currently ignore the invalid domains and default to the hostname as the domain
119         if (domainMatch(attributeValue, domain))
120             result.domain = attributeValue;
121
122     } else if (equalIgnoringASCIICase(attributeName, "max-age")) {
123         bool ok;
124         time_t expiryTime = time(0) + attributeValue.toInt64(&ok);
125         if (ok) {
126             result.expires = (double)expiryTime;
127             result.session = false;
128
129             // If there is a max-age attribute as well as an expires attribute
130             // the rightmost max-age attribute takes precedence.
131             hasMaxAge = true;
132         }
133     } else if (equalIgnoringASCIICase(attributeName, "expires") && !hasMaxAge) {
134         if (auto expiryTime = parseExpires(attributeValue.utf8().data())) {
135             result.expires = expiryTime.value();
136             result.session = false;
137         }
138     } else if (equalIgnoringASCIICase(attributeName, "path")) {
139         if (!attributeValue.isEmpty() && attributeValue.startsWith('/'))
140             result.path = attributeValue;
141     }
142 }
143
144 bool parseCookieHeader(const String& cookieLine, const String& domain, Cookie& result)
145 {
146     if (cookieLine.length() >= MAX_COOKIE_LINE)
147         return false;
148
149     // This Algorithm is based on the algorithm defined in RFC 6265 5.2 https://tools.ietf.org/html/rfc6265#section-5.2/
150
151     size_t separatorPosition = cookieLine.find(';');
152
153     String cookiePair = separatorPosition == notFound ? cookieLine : cookieLine.substring(0, separatorPosition);
154
155     String cookieName;
156     String cookieValue;
157     size_t assignmentPosition = cookieLine.find('=');
158
159     // RFC6265 says to ignore cookies pairs with empty names or no assignment character
160     // but browsers seem to treat this type of cookie string as the cookie value
161     if (assignmentPosition == notFound)
162         cookieValue = cookiePair;
163     else {
164         cookieName = cookiePair.substring(0, assignmentPosition);
165         cookieValue = cookiePair.substring(assignmentPosition + 1);
166     }
167
168     result.name = cookieName.stripWhiteSpace();
169     result.value = cookieValue.stripWhiteSpace();
170
171     bool hasMaxAge = false;
172     result.session = true;
173
174     Vector<String> cookieAttributes;
175     cookieLine.split(';', true, cookieAttributes);
176     for (auto attribute : cookieAttributes)
177         parseCookieAttributes(attribute, domain, hasMaxAge, result);
178
179     return true;
180 }
181
182 } // namespace CookieUtil
183
184 } // namespace WebCore
185
186 #endif