Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / platform / network / soup / SoupNetworkSession.cpp
index c827b46..6004d17 100644 (file)
  *    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 COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
  * 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
  */
 
 #include "config.h"
+
+#if USE(SOUP)
+
 #include "SoupNetworkSession.h"
 
 #include "AuthenticationChallenge.h"
-#include "CookieJarSoup.h"
-#include "GOwnPtrSoup.h"
+#include "FileSystem.h"
 #include "Logging.h"
-#include "ResourceHandle.h"
+#include "SoupNetworkProxySettings.h"
+#include <glib/gstdio.h>
 #include <libsoup/soup.h>
+#include <pal/crypto/CryptoDigest.h>
+#include <wtf/HashSet.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/glib/GUniquePtrSoup.h>
+#include <wtf/text/Base64.h>
 #include <wtf/text/CString.h>
-#include <wtf/text/StringBuilder.h>
-
-#if PLATFORM(EFL)
-#include "ProxyResolverSoup.h"
-#endif
 
 namespace WebCore {
 
-#if !LOG_DISABLED
-inline static void soupLogPrinter(SoupLogger*, SoupLoggerLogLevel, char direction, const char* data, gpointer)
-{
-    LOG(Network, "%c %s", direction, data);
-}
-#endif
+static bool gIgnoreTLSErrors;
+static GType gCustomProtocolRequestType;
 
-SoupNetworkSession& SoupNetworkSession::defaultSession()
+static CString& initialAcceptLanguages()
 {
-    static NeverDestroyed<SoupNetworkSession> networkSession(soupCookieJar());
-    return networkSession;
+    static NeverDestroyed<CString> storage;
+    return storage.get();
 }
 
-std::unique_ptr<SoupNetworkSession> SoupNetworkSession::createPrivateBrowsingSession()
+static SoupNetworkProxySettings& proxySettings()
 {
-    return std::unique_ptr<SoupNetworkSession>(new SoupNetworkSession(soupCookieJar()));
+    static NeverDestroyed<SoupNetworkProxySettings> settings;
+    return settings.get();
 }
 
-std::unique_ptr<SoupNetworkSession> SoupNetworkSession::createTestingSession()
+#if !LOG_DISABLED
+inline static void soupLogPrinter(SoupLogger*, SoupLoggerLogLevel, char direction, const char* data, gpointer)
 {
-    GRefPtr<SoupCookieJar> cookieJar = adoptGRef(createPrivateBrowsingCookieJar());
-    return std::unique_ptr<SoupNetworkSession>(new SoupNetworkSession(cookieJar.get()));
+    LOG(Network, "%c %s", direction, data);
 }
+#endif
 
-std::unique_ptr<SoupNetworkSession> SoupNetworkSession::createForSoupSession(SoupSession* soupSession)
-{
-    return std::unique_ptr<SoupNetworkSession>(new SoupNetworkSession(soupSession));
-}
+class HostTLSCertificateSet {
+public:
+    void add(GTlsCertificate* certificate)
+    {
+        String certificateHash = computeCertificateHash(certificate);
+        if (!certificateHash.isEmpty())
+            m_certificates.add(certificateHash);
+    }
 
-static void authenticateCallback(SoupSession* session, SoupMessage* soupMessage, SoupAuth* soupAuth, gboolean retrying)
-{
-    RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(G_OBJECT(soupMessage), "handle"));
-    if (!handle)
-        return;
-    handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(session, soupMessage, soupAuth, retrying, handle.get()));
-}
+    bool contains(GTlsCertificate* certificate) const
+    {
+        return m_certificates.contains(computeCertificateHash(certificate));
+    }
+
+private:
+    static String computeCertificateHash(GTlsCertificate* certificate)
+    {
+        GRefPtr<GByteArray> certificateData;
+        g_object_get(G_OBJECT(certificate), "certificate", &certificateData.outPtr(), nullptr);
+        if (!certificateData)
+            return String();
+
+        auto digest = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
+        digest->addBytes(certificateData->data, certificateData->len);
 
-#if ENABLE(WEB_TIMING)
-static void requestStartedCallback(SoupSession*, SoupMessage* soupMessage, SoupSocket*, gpointer)
+        auto hash = digest->computeHash();
+        return base64Encode(reinterpret_cast<const char*>(hash.data()), hash.size());
+    }
+
+    HashSet<String> m_certificates;
+};
+
+static HashMap<String, HostTLSCertificateSet, ASCIICaseInsensitiveHash>& clientCertificates()
 {
-    RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(G_OBJECT(soupMessage), "handle"));
-    if (!handle)
-        return;
-    handle->didStartRequest();
+    static NeverDestroyed<HashMap<String, HostTLSCertificateSet, ASCIICaseInsensitiveHash>> certificates;
+    return certificates;
 }
-#endif
 
-SoupNetworkSession::SoupNetworkSession(SoupCookieJar* cookieJar)
+SoupNetworkSession::SoupNetworkSession(PAL::SessionID sessionID, SoupCookieJar* cookieJar)
     : m_soupSession(adoptGRef(soup_session_async_new()))
 {
     // Values taken from http://www.browserscope.org/ following
     // the rule "Do What Every Other Modern Browser Is Doing". They seem
     // to significantly improve page loading time compared to soup's
     // default values.
-    static const int maxConnections = 35;
+    static const int maxConnections = 17;
     static const int maxConnectionsPerHost = 6;
 
+    GRefPtr<SoupCookieJar> jar = cookieJar;
+    if (!jar) {
+        jar = adoptGRef(soup_cookie_jar_new());
+        soup_cookie_jar_set_accept_policy(jar.get(), SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY);
+    }
+
     g_object_set(m_soupSession.get(),
         SOUP_SESSION_MAX_CONNS, maxConnections,
         SOUP_SESSION_MAX_CONNS_PER_HOST, maxConnectionsPerHost,
         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_SNIFFER,
         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
-        SOUP_SESSION_ADD_FEATURE, cookieJar,
+        SOUP_SESSION_ADD_FEATURE, jar.get(),
         SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+        SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+        SOUP_SESSION_SSL_STRICT, TRUE,
         nullptr);
 
-    setupLogger();
+    setupCustomProtocols();
+
+    if (!initialAcceptLanguages().isNull())
+        setAcceptLanguages(initialAcceptLanguages());
 
-    g_signal_connect(m_soupSession.get(), "authenticate", G_CALLBACK(authenticateCallback), nullptr);
-#if ENABLE(WEB_TIMING)
-    g_signal_connect(m_soupSession.get(), "request-started", G_CALLBACK(requestStartedCallback), nullptr);
+#if SOUP_CHECK_VERSION(2, 53, 92)
+    if (soup_auth_negotiate_supported() && !sessionID.isEphemeral()) {
+        g_object_set(m_soupSession.get(),
+            SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NEGOTIATE,
+            nullptr);
+    }
+#else
+    UNUSED_PARAM(sessionID);
 #endif
-}
 
-SoupNetworkSession::SoupNetworkSession(SoupSession* soupSession)
-    : m_soupSession(soupSession)
-{
+    if (proxySettings().mode != SoupNetworkProxySettings::Mode::Default)
+        setupProxy();
     setupLogger();
 }
 
-SoupNetworkSession::~SoupNetworkSession()
-{
-}
+SoupNetworkSession::~SoupNetworkSession() = default;
 
 void SoupNetworkSession::setupLogger()
 {
@@ -150,134 +178,127 @@ SoupCookieJar* SoupNetworkSession::cookieJar() const
     return SOUP_COOKIE_JAR(soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_COOKIE_JAR));
 }
 
-void SoupNetworkSession::setCache(SoupCache* cache)
+static inline bool stringIsNumeric(const char* str)
 {
-    ASSERT(!soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_CACHE));
-    soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(cache));
+    while (*str) {
+        if (!g_ascii_isdigit(*str))
+            return false;
+        str++;
+    }
+    return true;
 }
 
-SoupCache* SoupNetworkSession::cache() const
+// Old versions of WebKit created this cache.
+void SoupNetworkSession::clearOldSoupCache(const String& cacheDirectory)
 {
-    SoupSessionFeature* soupCache = soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_CACHE);
-    return soupCache ? SOUP_CACHE(soupCache) : nullptr;
-}
+    CString cachePath = FileSystem::fileSystemRepresentation(cacheDirectory);
+    GUniquePtr<char> cacheFile(g_build_filename(cachePath.data(), "soup.cache2", nullptr));
+    if (!g_file_test(cacheFile.get(), G_FILE_TEST_IS_REGULAR))
+        return;
 
-void SoupNetworkSession::setSSLPolicy(SSLPolicy flags)
-{
-    g_object_set(m_soupSession.get(),
-        SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, flags & SSLUseSystemCAFile ? TRUE : FALSE,
-        SOUP_SESSION_SSL_STRICT, flags & SSLStrict ? TRUE : FALSE,
-        nullptr);
-}
+    GUniquePtr<GDir> dir(g_dir_open(cachePath.data(), 0, nullptr));
+    if (!dir)
+        return;
 
-SoupNetworkSession::SSLPolicy SoupNetworkSession::sslPolicy() const
-{
-    gboolean useSystemCAFile, strict;
-    g_object_get(m_soupSession.get(),
-        SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, &useSystemCAFile,
-        SOUP_SESSION_SSL_STRICT, &strict,
-        nullptr);
+    while (const char* name = g_dir_read_name(dir.get())) {
+        if (!g_str_has_prefix(name, "soup.cache") && !stringIsNumeric(name))
+            continue;
 
-    SSLPolicy flags = 0;
-    if (useSystemCAFile)
-        flags |= SSLUseSystemCAFile;
-    if (strict)
-        flags |= SSLStrict;
-    return flags;
+        GUniquePtr<gchar> filename(g_build_filename(cachePath.data(), name, nullptr));
+        if (g_file_test(filename.get(), G_FILE_TEST_IS_REGULAR))
+            g_unlink(filename.get());
+    }
 }
 
-void SoupNetworkSession::setHTTPProxy(const char* httpProxy, const char* httpProxyExceptions)
+void SoupNetworkSession::setupProxy()
 {
-#if PLATFORM(EFL)
-    // Only for EFL because GTK port uses the default resolver, which uses GIO's proxy resolver.
-    if (!httpProxy) {
-        soup_session_remove_feature_by_type(m_soupSession.get(), SOUP_TYPE_PROXY_URI_RESOLVER);
-        return;
+    GRefPtr<GProxyResolver> resolver;
+    switch (proxySettings().mode) {
+    case SoupNetworkProxySettings::Mode::Default: {
+        GRefPtr<GProxyResolver> currentResolver;
+        g_object_get(m_soupSession.get(), SOUP_SESSION_PROXY_RESOLVER, &currentResolver.outPtr(), nullptr);
+        GProxyResolver* defaultResolver = g_proxy_resolver_get_default();
+        if (currentResolver.get() == defaultResolver)
+            return;
+        resolver = defaultResolver;
+        break;
+    }
+    case SoupNetworkProxySettings::Mode::NoProxy:
+        // Do nothing in this case, resolver is nullptr so that when set it will disable proxies.
+        break;
+    case SoupNetworkProxySettings::Mode::Custom:
+        resolver = adoptGRef(g_simple_proxy_resolver_new(nullptr, nullptr));
+        if (!proxySettings().defaultProxyURL.isNull())
+            g_simple_proxy_resolver_set_default_proxy(G_SIMPLE_PROXY_RESOLVER(resolver.get()), proxySettings().defaultProxyURL.data());
+        if (proxySettings().ignoreHosts)
+            g_simple_proxy_resolver_set_ignore_hosts(G_SIMPLE_PROXY_RESOLVER(resolver.get()), proxySettings().ignoreHosts.get());
+        for (const auto& iter : proxySettings().proxyMap)
+            g_simple_proxy_resolver_set_uri_proxy(G_SIMPLE_PROXY_RESOLVER(resolver.get()), iter.key.data(), iter.value.data());
+        break;
     }
 
-    GRefPtr<SoupProxyURIResolver> resolver = adoptGRef(soupProxyResolverWkNew(httpProxy, httpProxyExceptions));
-    soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(resolver.get()));
-#else
-    UNUSED_PARAM(httpProxy);
-    UNUSED_PARAM(httpProxyExceptions);
-#endif
+    g_object_set(m_soupSession.get(), SOUP_SESSION_PROXY_RESOLVER, resolver.get(), nullptr);
+    soup_session_abort(m_soupSession.get());
 }
 
-char* SoupNetworkSession::httpProxy() const
+void SoupNetworkSession::setProxySettings(const SoupNetworkProxySettings& settings)
 {
-#if PLATFORM(EFL)
-    SoupSessionFeature* soupResolver = soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_PROXY_URI_RESOLVER);
-    if (!soupResolver)
-        return nullptr;
+    proxySettings() = settings;
+}
 
-    GOwnPtr<SoupURI> uri;
-    g_object_get(soupResolver, SOUP_PROXY_RESOLVER_WK_PROXY_URI, &uri.outPtr(), nullptr);
+void SoupNetworkSession::setInitialAcceptLanguages(const CString& languages)
+{
+    initialAcceptLanguages() = languages;
+}
 
-    return uri ? soup_uri_to_string(uri.get(), FALSE) : nullptr;
-#else
-    return nullptr;
-#endif
+void SoupNetworkSession::setAcceptLanguages(const CString& languages)
+{
+    g_object_set(m_soupSession.get(), "accept-language", languages.data(), nullptr);
+}
+
+void SoupNetworkSession::setCustomProtocolRequestType(GType requestType)
+{
+    ASSERT(g_type_is_a(requestType, SOUP_TYPE_REQUEST));
+    gCustomProtocolRequestType = requestType;
 }
 
-void SoupNetworkSession::setupHTTPProxyFromEnvironment()
+void SoupNetworkSession::setupCustomProtocols()
 {
-#if PLATFORM(EFL)
-    const char* httpProxy = getenv("http_proxy");
-    if (!httpProxy)
+    if (!g_type_is_a(gCustomProtocolRequestType, SOUP_TYPE_REQUEST))
         return;
 
-    setHTTPProxy(httpProxy, getenv("no_proxy"));
-#endif
+    auto* requestClass = static_cast<SoupRequestClass*>(g_type_class_peek(gCustomProtocolRequestType));
+    if (!requestClass || !requestClass->schemes)
+        return;
+
+    soup_session_add_feature_by_type(m_soupSession.get(), gCustomProtocolRequestType);
 }
 
-static CString buildAcceptLanguages(const Vector<String>& languages)
+void SoupNetworkSession::setShouldIgnoreTLSErrors(bool ignoreTLSErrors)
 {
-    size_t languagesCount = languages.size();
-
-    // Ignore "C" locale.
-    size_t cLocalePosition = languages.find("c");
-    if (cLocalePosition != notFound)
-        languagesCount--;
-
-    // Fallback to "en" if the list is empty.
-    if (!languagesCount)
-        return "en";
-
-    // Calculate deltas for the quality values.
-    int delta;
-    if (languagesCount < 10)
-        delta = 10;
-    else if (languagesCount < 20)
-        delta = 5;
-    else
-        delta = 1;
-
-    // Set quality values for each language.
-    StringBuilder builder;
-    for (size_t i = 0; i < languages.size(); ++i) {
-        if (i == cLocalePosition)
-            continue;
+    gIgnoreTLSErrors = ignoreTLSErrors;
+}
 
-        if (i)
-            builder.appendLiteral(", ");
+std::optional<ResourceError> SoupNetworkSession::checkTLSErrors(const URL& requestURL, GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors)
+{
+    if (gIgnoreTLSErrors)
+        return std::nullopt;
 
-        builder.append(languages[i]);
+    if (!tlsErrors)
+        return std::nullopt;
 
-        int quality = 100 - i * delta;
-        if (quality > 0 && quality < 100) {
-            char buffer[8];
-            g_ascii_formatd(buffer, 8, "%.2f", quality / 100.0);
-            builder.append(String::format(";q=%s", buffer));
-        }
-    }
+    auto it = clientCertificates().find(requestURL.host().toString());
+    if (it != clientCertificates().end() && it->value.contains(certificate))
+        return std::nullopt;
 
-    return builder.toString().utf8();
+    return ResourceError::tlsError(requestURL, tlsErrors, certificate);
 }
 
-void SoupNetworkSession::setAcceptLanguages(const Vector<String>& languages)
+void SoupNetworkSession::allowSpecificHTTPSCertificateForHost(const CertificateInfo& certificateInfo, const String& host)
 {
-    g_object_set(m_soupSession.get(), "accept-language", buildAcceptLanguages(languages).data(), nullptr);
+    clientCertificates().add(host, HostTLSCertificateSet()).iterator->value.add(certificateInfo.certificate());
 }
 
 } // namespace WebCore
 
+#endif