Remove WEB_TIMING feature flag
[WebKit-https.git] / Source / WebCore / platform / network / soup / SoupNetworkSession.cpp
1 /*
2  * Copyright (C) 2014 Igalia S.L.
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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #if USE(SOUP)
29
30 #include "SoupNetworkSession.h"
31
32 #include "AuthenticationChallenge.h"
33 #include "FileSystem.h"
34 #include "GUniquePtrSoup.h"
35 #include "Logging.h"
36 #include "ResourceHandle.h"
37 #include "SoupNetworkProxySettings.h"
38 #include <glib/gstdio.h>
39 #include <libsoup/soup.h>
40 #include <pal/crypto/CryptoDigest.h>
41 #include <wtf/HashSet.h>
42 #include <wtf/NeverDestroyed.h>
43 #include <wtf/text/Base64.h>
44 #include <wtf/text/CString.h>
45
46 namespace WebCore {
47
48 static bool gIgnoreTLSErrors;
49 static CString gInitialAcceptLanguages;
50 static SoupNetworkProxySettings gProxySettings;
51 static GType gCustomProtocolRequestType;
52
53 #if !LOG_DISABLED
54 inline static void soupLogPrinter(SoupLogger*, SoupLoggerLogLevel, char direction, const char* data, gpointer)
55 {
56     LOG(Network, "%c %s", direction, data);
57 }
58 #endif
59
60 class HostTLSCertificateSet {
61 public:
62     void add(GTlsCertificate* certificate)
63     {
64         String certificateHash = computeCertificateHash(certificate);
65         if (!certificateHash.isEmpty())
66             m_certificates.add(certificateHash);
67     }
68
69     bool contains(GTlsCertificate* certificate) const
70     {
71         return m_certificates.contains(computeCertificateHash(certificate));
72     }
73
74 private:
75     static String computeCertificateHash(GTlsCertificate* certificate)
76     {
77         GRefPtr<GByteArray> certificateData;
78         g_object_get(G_OBJECT(certificate), "certificate", &certificateData.outPtr(), nullptr);
79         if (!certificateData)
80             return String();
81
82         auto digest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
83         digest->addBytes(certificateData->data, certificateData->len);
84
85         auto hash = digest->computeHash();
86         return base64Encode(reinterpret_cast<const char*>(hash.data()), hash.size());
87     }
88
89     HashSet<String> m_certificates;
90 };
91
92 static HashMap<String, HostTLSCertificateSet, ASCIICaseInsensitiveHash>& clientCertificates()
93 {
94     static NeverDestroyed<HashMap<String, HostTLSCertificateSet, ASCIICaseInsensitiveHash>> certificates;
95     return certificates;
96 }
97
98 static void authenticateCallback(SoupSession*, SoupMessage* soupMessage, SoupAuth* soupAuth, gboolean retrying)
99 {
100     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(G_OBJECT(soupMessage), "handle"));
101     if (!handle)
102         return;
103     handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(soupMessage, soupAuth, retrying, handle.get()));
104 }
105
106 #if !SOUP_CHECK_VERSION(2, 49, 91)
107 static void requestStartedCallback(SoupSession*, SoupMessage* soupMessage, SoupSocket*, gpointer)
108 {
109     RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(G_OBJECT(soupMessage), "handle"));
110     if (!handle)
111         return;
112     handle->didStartRequest();
113 }
114 #endif
115
116 SoupNetworkSession::SoupNetworkSession(SoupCookieJar* cookieJar)
117     : m_soupSession(adoptGRef(soup_session_async_new()))
118 {
119     // Values taken from http://www.browserscope.org/ following
120     // the rule "Do What Every Other Modern Browser Is Doing". They seem
121     // to significantly improve page loading time compared to soup's
122     // default values.
123     static const int maxConnections = 17;
124     static const int maxConnectionsPerHost = 6;
125
126     GRefPtr<SoupCookieJar> jar = cookieJar;
127     if (!jar) {
128         jar = adoptGRef(soup_cookie_jar_new());
129         soup_cookie_jar_set_accept_policy(jar.get(), SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY);
130     }
131
132     g_object_set(m_soupSession.get(),
133         SOUP_SESSION_MAX_CONNS, maxConnections,
134         SOUP_SESSION_MAX_CONNS_PER_HOST, maxConnectionsPerHost,
135         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
136         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_SNIFFER,
137         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
138         SOUP_SESSION_ADD_FEATURE, jar.get(),
139         SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
140         SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
141         SOUP_SESSION_SSL_STRICT, FALSE,
142         nullptr);
143
144     setupCustomProtocols();
145
146     if (!gInitialAcceptLanguages.isNull())
147         setAcceptLanguages(gInitialAcceptLanguages);
148
149 #if SOUP_CHECK_VERSION(2, 53, 92)
150     if (soup_auth_negotiate_supported()) {
151         g_object_set(m_soupSession.get(),
152             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NEGOTIATE,
153             nullptr);
154     }
155 #endif
156
157     if (gProxySettings.mode != SoupNetworkProxySettings::Mode::Default)
158         setupProxy();
159     setupLogger();
160
161     g_signal_connect(m_soupSession.get(), "authenticate", G_CALLBACK(authenticateCallback), nullptr);
162 #if !SOUP_CHECK_VERSION(2, 49, 91)
163     g_signal_connect(m_soupSession.get(), "request-started", G_CALLBACK(requestStartedCallback), nullptr);
164 #endif
165 }
166
167 SoupNetworkSession::~SoupNetworkSession()
168 {
169 }
170
171 void SoupNetworkSession::setupLogger()
172 {
173 #if !LOG_DISABLED
174     if (LogNetwork.state != WTFLogChannelOn || soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_LOGGER))
175         return;
176
177     GRefPtr<SoupLogger> logger = adoptGRef(soup_logger_new(SOUP_LOGGER_LOG_BODY, -1));
178     soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(logger.get()));
179     soup_logger_set_printer(logger.get(), soupLogPrinter, nullptr, nullptr);
180 #endif
181 }
182
183 void SoupNetworkSession::setCookieJar(SoupCookieJar* jar)
184 {
185     if (SoupCookieJar* currentJar = cookieJar())
186         soup_session_remove_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(currentJar));
187     soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(jar));
188 }
189
190 SoupCookieJar* SoupNetworkSession::cookieJar() const
191 {
192     return SOUP_COOKIE_JAR(soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_COOKIE_JAR));
193 }
194
195 static inline bool stringIsNumeric(const char* str)
196 {
197     while (*str) {
198         if (!g_ascii_isdigit(*str))
199             return false;
200         str++;
201     }
202     return true;
203 }
204
205 // Old versions of WebKit created this cache.
206 void SoupNetworkSession::clearOldSoupCache(const String& cacheDirectory)
207 {
208     CString cachePath = fileSystemRepresentation(cacheDirectory);
209     GUniquePtr<char> cacheFile(g_build_filename(cachePath.data(), "soup.cache2", nullptr));
210     if (!g_file_test(cacheFile.get(), G_FILE_TEST_IS_REGULAR))
211         return;
212
213     GUniquePtr<GDir> dir(g_dir_open(cachePath.data(), 0, nullptr));
214     if (!dir)
215         return;
216
217     while (const char* name = g_dir_read_name(dir.get())) {
218         if (!g_str_has_prefix(name, "soup.cache") && !stringIsNumeric(name))
219             continue;
220
221         GUniquePtr<gchar> filename(g_build_filename(cachePath.data(), name, nullptr));
222         if (g_file_test(filename.get(), G_FILE_TEST_IS_REGULAR))
223             g_unlink(filename.get());
224     }
225 }
226
227 void SoupNetworkSession::setupProxy()
228 {
229     GRefPtr<GProxyResolver> resolver;
230     switch (gProxySettings.mode) {
231     case SoupNetworkProxySettings::Mode::Default: {
232         GRefPtr<GProxyResolver> currentResolver;
233         g_object_get(m_soupSession.get(), SOUP_SESSION_PROXY_RESOLVER, &currentResolver.outPtr(), nullptr);
234         GProxyResolver* defaultResolver = g_proxy_resolver_get_default();
235         if (currentResolver.get() == defaultResolver)
236             return;
237         resolver = defaultResolver;
238         break;
239     }
240     case SoupNetworkProxySettings::Mode::NoProxy:
241         // Do nothing in this case, resolver is nullptr so that when set it will disable proxies.
242         break;
243     case SoupNetworkProxySettings::Mode::Custom:
244         resolver = adoptGRef(g_simple_proxy_resolver_new(nullptr, nullptr));
245         if (!gProxySettings.defaultProxyURL.isNull())
246             g_simple_proxy_resolver_set_default_proxy(G_SIMPLE_PROXY_RESOLVER(resolver.get()), gProxySettings.defaultProxyURL.data());
247         if (gProxySettings.ignoreHosts)
248             g_simple_proxy_resolver_set_ignore_hosts(G_SIMPLE_PROXY_RESOLVER(resolver.get()), gProxySettings.ignoreHosts.get());
249         for (const auto& iter : gProxySettings.proxyMap)
250             g_simple_proxy_resolver_set_uri_proxy(G_SIMPLE_PROXY_RESOLVER(resolver.get()), iter.key.data(), iter.value.data());
251         break;
252     }
253
254     g_object_set(m_soupSession.get(), SOUP_SESSION_PROXY_RESOLVER, resolver.get(), nullptr);
255     soup_session_abort(m_soupSession.get());
256 }
257
258 void SoupNetworkSession::setProxySettings(const SoupNetworkProxySettings& settings)
259 {
260     gProxySettings = settings;
261 }
262
263 void SoupNetworkSession::setInitialAcceptLanguages(const CString& languages)
264 {
265     gInitialAcceptLanguages = languages;
266 }
267
268 void SoupNetworkSession::setAcceptLanguages(const CString& languages)
269 {
270     g_object_set(m_soupSession.get(), "accept-language", languages.data(), nullptr);
271 }
272
273 void SoupNetworkSession::setCustomProtocolRequestType(GType requestType)
274 {
275     ASSERT(g_type_is_a(requestType, SOUP_TYPE_REQUEST));
276     gCustomProtocolRequestType = requestType;
277 }
278
279 void SoupNetworkSession::setupCustomProtocols()
280 {
281     if (!g_type_is_a(gCustomProtocolRequestType, SOUP_TYPE_REQUEST))
282         return;
283
284     auto* requestClass = static_cast<SoupRequestClass*>(g_type_class_peek(gCustomProtocolRequestType));
285     if (!requestClass || !requestClass->schemes)
286         return;
287
288     soup_session_add_feature_by_type(m_soupSession.get(), gCustomProtocolRequestType);
289 }
290
291 void SoupNetworkSession::setShouldIgnoreTLSErrors(bool ignoreTLSErrors)
292 {
293     gIgnoreTLSErrors = ignoreTLSErrors;
294 }
295
296 void SoupNetworkSession::checkTLSErrors(SoupRequest* soupRequest, SoupMessage* message, WTF::Function<void (const ResourceError&)>&& completionHandler)
297 {
298     if (gIgnoreTLSErrors) {
299         completionHandler({ });
300         return;
301     }
302
303     GTlsCertificate* certificate = nullptr;
304     GTlsCertificateFlags tlsErrors = static_cast<GTlsCertificateFlags>(0);
305     soup_message_get_https_status(message, &certificate, &tlsErrors);
306     if (!tlsErrors) {
307         completionHandler({ });
308         return;
309     }
310
311     URL url(soup_request_get_uri(soupRequest));
312     auto it = clientCertificates().find(url.host());
313     if (it != clientCertificates().end() && it->value.contains(certificate)) {
314         completionHandler({ });
315         return;
316     }
317
318     completionHandler(ResourceError::tlsError(soupRequest, tlsErrors, certificate));
319 }
320
321 void SoupNetworkSession::allowSpecificHTTPSCertificateForHost(const CertificateInfo& certificateInfo, const String& host)
322 {
323     clientCertificates().add(host, HostTLSCertificateSet()).iterator->value.add(certificateInfo.certificate());
324 }
325
326 } // namespace WebCore
327
328 #endif