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 "CookieJarSoup.h" 34#include "GUniquePtrSoup.h" 35#include "Logging.h" 36#include "ResourceHandle.h" 37#include <libsoup/soup.h> 38#include <wtf/text/CString.h> 39#include <wtf/text/StringBuilder.h> 40 41#if PLATFORM(EFL) 42#include "ProxyResolverSoup.h" 43#endif 44 45namespace WebCore { 46 47#if !LOG_DISABLED 48inline static void soupLogPrinter(SoupLogger*, SoupLoggerLogLevel, char direction, const char* data, gpointer) 49{ 50 LOG(Network, "%c %s", direction, data); 51} 52#endif 53 54SoupNetworkSession& SoupNetworkSession::defaultSession() 55{ 56 static SoupNetworkSession networkSession(soupCookieJar()); 57 return networkSession; 58} 59 60std::unique_ptr<SoupNetworkSession> SoupNetworkSession::createPrivateBrowsingSession() 61{ 62 return std::unique_ptr<SoupNetworkSession>(new SoupNetworkSession(soupCookieJar())); 63} 64 65std::unique_ptr<SoupNetworkSession> SoupNetworkSession::createTestingSession() 66{ 67 GRefPtr<SoupCookieJar> cookieJar = adoptGRef(createPrivateBrowsingCookieJar()); 68 return std::unique_ptr<SoupNetworkSession>(new SoupNetworkSession(cookieJar.get())); 69} 70 71std::unique_ptr<SoupNetworkSession> SoupNetworkSession::createForSoupSession(SoupSession* soupSession) 72{ 73 return std::unique_ptr<SoupNetworkSession>(new SoupNetworkSession(soupSession)); 74} 75 76static void authenticateCallback(SoupSession* session, SoupMessage* soupMessage, SoupAuth* soupAuth, gboolean retrying) 77{ 78 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(G_OBJECT(soupMessage), "handle")); 79 if (!handle) 80 return; 81 handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(session, soupMessage, soupAuth, retrying, handle.get())); 82} 83 84#if ENABLE(WEB_TIMING) 85static void requestStartedCallback(SoupSession*, SoupMessage* soupMessage, SoupSocket*, gpointer) 86{ 87 RefPtr<ResourceHandle> handle = static_cast<ResourceHandle*>(g_object_get_data(G_OBJECT(soupMessage), "handle")); 88 if (!handle) 89 return; 90 handle->didStartRequest(); 91} 92#endif 93 94SoupNetworkSession::SoupNetworkSession(SoupCookieJar* cookieJar) 95 : m_soupSession(adoptGRef(soup_session_async_new())) 96{ 97 // Values taken from http://www.browserscope.org/ following 98 // the rule "Do What Every Other Modern Browser Is Doing". They seem 99 // to significantly improve page loading time compared to soup's 100 // default values. 101 static const int maxConnections = 35; 102 static const int maxConnectionsPerHost = 6; 103 104 g_object_set(m_soupSession.get(), 105 SOUP_SESSION_MAX_CONNS, maxConnections, 106 SOUP_SESSION_MAX_CONNS_PER_HOST, maxConnectionsPerHost, 107 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER, 108 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_SNIFFER, 109 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_DEFAULT, 110 SOUP_SESSION_ADD_FEATURE, cookieJar, 111 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, 112 nullptr); 113 114 setupLogger(); 115 116 g_signal_connect(m_soupSession.get(), "authenticate", G_CALLBACK(authenticateCallback), nullptr); 117#if ENABLE(WEB_TIMING) 118 g_signal_connect(m_soupSession.get(), "request-started", G_CALLBACK(requestStartedCallback), nullptr); 119#endif 120} 121 122SoupNetworkSession::SoupNetworkSession(SoupSession* soupSession) 123 : m_soupSession(soupSession) 124{ 125 setupLogger(); 126} 127 128SoupNetworkSession::~SoupNetworkSession() 129{ 130} 131 132void SoupNetworkSession::setupLogger() 133{ 134#if !LOG_DISABLED 135 if (LogNetwork.state != WTFLogChannelOn || soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_LOGGER)) 136 return; 137 138 GRefPtr<SoupLogger> logger = adoptGRef(soup_logger_new(SOUP_LOGGER_LOG_BODY, -1)); 139 soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(logger.get())); 140 soup_logger_set_printer(logger.get(), soupLogPrinter, nullptr, nullptr); 141#endif 142} 143 144void SoupNetworkSession::setCookieJar(SoupCookieJar* jar) 145{ 146 if (SoupCookieJar* currentJar = cookieJar()) 147 soup_session_remove_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(currentJar)); 148 soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(jar)); 149} 150 151SoupCookieJar* SoupNetworkSession::cookieJar() const 152{ 153 return SOUP_COOKIE_JAR(soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_COOKIE_JAR)); 154} 155 156void SoupNetworkSession::setCache(SoupCache* cache) 157{ 158 ASSERT(!soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_CACHE)); 159 soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(cache)); 160} 161 162SoupCache* SoupNetworkSession::cache() const 163{ 164 SoupSessionFeature* soupCache = soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_CACHE); 165 return soupCache ? SOUP_CACHE(soupCache) : nullptr; 166} 167 168void SoupNetworkSession::setSSLPolicy(SSLPolicy flags) 169{ 170 g_object_set(m_soupSession.get(), 171 SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, flags & SSLUseSystemCAFile ? TRUE : FALSE, 172 SOUP_SESSION_SSL_STRICT, flags & SSLStrict ? TRUE : FALSE, 173 nullptr); 174} 175 176SoupNetworkSession::SSLPolicy SoupNetworkSession::sslPolicy() const 177{ 178 gboolean useSystemCAFile, strict; 179 g_object_get(m_soupSession.get(), 180 SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, &useSystemCAFile, 181 SOUP_SESSION_SSL_STRICT, &strict, 182 nullptr); 183 184 SSLPolicy flags = 0; 185 if (useSystemCAFile) 186 flags |= SSLUseSystemCAFile; 187 if (strict) 188 flags |= SSLStrict; 189 return flags; 190} 191 192void SoupNetworkSession::setHTTPProxy(const char* httpProxy, const char* httpProxyExceptions) 193{ 194#if PLATFORM(EFL) 195 // Only for EFL because GTK port uses the default resolver, which uses GIO's proxy resolver. 196 if (!httpProxy) { 197 soup_session_remove_feature_by_type(m_soupSession.get(), SOUP_TYPE_PROXY_URI_RESOLVER); 198 return; 199 } 200 201 GRefPtr<SoupProxyURIResolver> resolver = adoptGRef(soupProxyResolverWkNew(httpProxy, httpProxyExceptions)); 202 soup_session_add_feature(m_soupSession.get(), SOUP_SESSION_FEATURE(resolver.get())); 203#else 204 UNUSED_PARAM(httpProxy); 205 UNUSED_PARAM(httpProxyExceptions); 206#endif 207} 208 209char* SoupNetworkSession::httpProxy() const 210{ 211#if PLATFORM(EFL) 212 SoupSessionFeature* soupResolver = soup_session_get_feature(m_soupSession.get(), SOUP_TYPE_PROXY_URI_RESOLVER); 213 if (!soupResolver) 214 return nullptr; 215 216 GUniqueOutPtr<SoupURI> uri; 217 g_object_get(soupResolver, SOUP_PROXY_RESOLVER_WK_PROXY_URI, &uri.outPtr(), nullptr); 218 219 return uri ? soup_uri_to_string(uri.get(), FALSE) : nullptr; 220#else 221 return nullptr; 222#endif 223} 224 225void SoupNetworkSession::setupHTTPProxyFromEnvironment() 226{ 227#if PLATFORM(EFL) 228 const char* httpProxy = getenv("http_proxy"); 229 if (!httpProxy) 230 return; 231 232 setHTTPProxy(httpProxy, getenv("no_proxy")); 233#endif 234} 235 236static CString buildAcceptLanguages(const Vector<String>& languages) 237{ 238 size_t languagesCount = languages.size(); 239 240 // Ignore "C" locale. 241 size_t cLocalePosition = languages.find("c"); 242 if (cLocalePosition != notFound) 243 languagesCount--; 244 245 // Fallback to "en" if the list is empty. 246 if (!languagesCount) 247 return "en"; 248 249 // Calculate deltas for the quality values. 250 int delta; 251 if (languagesCount < 10) 252 delta = 10; 253 else if (languagesCount < 20) 254 delta = 5; 255 else 256 delta = 1; 257 258 // Set quality values for each language. 259 StringBuilder builder; 260 for (size_t i = 0; i < languages.size(); ++i) { 261 if (i == cLocalePosition) 262 continue; 263 264 if (i) 265 builder.appendLiteral(", "); 266 267 builder.append(languages[i]); 268 269 int quality = 100 - i * delta; 270 if (quality > 0 && quality < 100) { 271 char buffer[8]; 272 g_ascii_formatd(buffer, 8, "%.2f", quality / 100.0); 273 builder.append(String::format(";q=%s", buffer)); 274 } 275 } 276 277 return builder.toString().utf8(); 278} 279 280void SoupNetworkSession::setAcceptLanguages(const Vector<String>& languages) 281{ 282 g_object_set(m_soupSession.get(), "accept-language", buildAcceptLanguages(languages).data(), nullptr); 283} 284 285} // namespace WebCore 286 287#endif 288