1/*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2009, 2012 Igalia S.L.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "DNSResolveQueueSoup.h"
29
30#if USE(SOUP)
31
32#include "NetworkStorageSession.h"
33#include "SoupNetworkSession.h"
34#include <libsoup/soup.h>
35#include <wtf/CompletionHandler.h>
36#include <wtf/Function.h>
37#include <wtf/MainThread.h>
38#include <wtf/NeverDestroyed.h>
39#include <wtf/glib/GUniquePtr.h>
40#include <wtf/text/CString.h>
41
42namespace WebCore {
43
44// Initially true to ensure prefetch stays disabled until we have proxy settings.
45static bool isUsingHttpProxy = true;
46static bool isUsingHttpsProxy = true;
47
48static bool didResolveProxy(char** uris)
49{
50 // We have a list of possible proxies to use for the URI. If the first item in the list is
51 // direct:// (the usual case), then the user prefers not to use a proxy. This is similar to
52 // resolving hostnames: there could be many possibilities returned in order of preference, and
53 // if we're trying to connect we should attempt each one in order, but here we are not trying
54 // to connect, merely to decide whether a proxy "should" be used.
55 return uris && *uris && strcmp(*uris, "direct://");
56}
57
58static void didResolveProxy(GProxyResolver* resolver, GAsyncResult* result, bool* isUsingProxyType, bool* isUsingProxy)
59{
60 GUniqueOutPtr<GError> error;
61 GUniquePtr<char*> uris(g_proxy_resolver_lookup_finish(resolver, result, &error.outPtr()));
62 if (error) {
63 WTFLogAlways("Error determining system proxy settings: %s", error->message);
64 return;
65 }
66
67 *isUsingProxyType = didResolveProxy(uris.get());
68 *isUsingProxy = isUsingHttpProxy || isUsingHttpsProxy;
69}
70
71static void proxyResolvedForHttpUriCallback(GObject* source, GAsyncResult* result, void* userData)
72{
73 didResolveProxy(G_PROXY_RESOLVER(source), result, &isUsingHttpProxy, static_cast<bool*>(userData));
74}
75
76static void proxyResolvedForHttpsUriCallback(GObject* source, GAsyncResult* result, void* userData)
77{
78 didResolveProxy(G_PROXY_RESOLVER(source), result, &isUsingHttpsProxy, static_cast<bool*>(userData));
79}
80
81Function<NetworkStorageSession&()>& globalDefaultNetworkStorageSessionAccessor()
82{
83 static NeverDestroyed<Function<NetworkStorageSession&()>> accessor;
84 return accessor.get();
85}
86
87void DNSResolveQueueSoup::setGlobalDefaultNetworkStorageSessionAccessor(Function<NetworkStorageSession&()>&& accessor)
88{
89 globalDefaultNetworkStorageSessionAccessor() = WTFMove(accessor);
90}
91
92void DNSResolveQueueSoup::updateIsUsingProxy()
93{
94 GRefPtr<GProxyResolver> resolver;
95 g_object_get(globalDefaultNetworkStorageSessionAccessor()().soupNetworkSession().soupSession(), "proxy-resolver", &resolver.outPtr(), nullptr);
96 ASSERT(resolver);
97
98 g_proxy_resolver_lookup_async(resolver.get(), "http://example.com/", nullptr, proxyResolvedForHttpUriCallback, &m_isUsingProxy);
99 g_proxy_resolver_lookup_async(resolver.get(), "https://example.com/", nullptr, proxyResolvedForHttpsUriCallback, &m_isUsingProxy);
100}
101
102static void resolvedCallback(SoupAddress*, guint, void*)
103{
104 DNSResolveQueue::singleton().decrementRequestCount();
105}
106
107static void resolvedWithObserverCallback(SoupAddress* address, guint status, void* data)
108{
109 ASSERT(data);
110 auto* resolveQueue = static_cast<DNSResolveQueueSoup*>(data);
111
112 uint64_t identifier = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(address), "identifier"));
113
114 auto completionAndCancelHandlers = resolveQueue->takeCompletionAndCancelHandlers(identifier);
115
116 if (!completionAndCancelHandlers)
117 return;
118
119 auto completionHandler = WTFMove(completionAndCancelHandlers.get()->first);
120
121 if (status != SOUP_STATUS_OK) {
122 DNSError error = DNSError::Unknown;
123
124 switch (status) {
125 case SOUP_STATUS_CANT_RESOLVE:
126 error = DNSError::CannotResolve;
127 break;
128 case SOUP_STATUS_CANCELLED:
129 error = DNSError::Cancelled;
130 break;
131 case SOUP_STATUS_OK:
132 default:
133 ASSERT_NOT_REACHED();
134 };
135
136 completionHandler(makeUnexpected(error));
137 return;
138 }
139
140 if (!soup_address_is_resolved(address)) {
141 completionHandler(makeUnexpected(DNSError::Unknown));
142 return;
143 }
144
145 Vector<WebCore::IPAddress> addresses;
146 addresses.reserveInitialCapacity(1);
147 int len;
148 auto* ipAddress = reinterpret_cast<const struct sockaddr_in*>(soup_address_get_sockaddr(address, &len));
149 for (unsigned i = 0; i < sizeof(*ipAddress) / len; i++)
150 addresses.uncheckedAppend(WebCore::IPAddress(ipAddress[i]));
151
152 completionHandler(addresses);
153}
154
155std::unique_ptr<DNSResolveQueueSoup::CompletionAndCancelHandlers> DNSResolveQueueSoup::takeCompletionAndCancelHandlers(uint64_t identifier)
156{
157 ASSERT(isMainThread());
158
159 auto completionAndCancelHandlers = m_completionAndCancelHandlers.take(identifier);
160
161 if (!completionAndCancelHandlers)
162 return nullptr;
163
164 return completionAndCancelHandlers;
165}
166
167void DNSResolveQueueSoup::removeCancelAndCompletionHandler(uint64_t identifier)
168{
169 ASSERT(isMainThread());
170
171 m_completionAndCancelHandlers.remove(identifier);
172}
173
174void DNSResolveQueueSoup::platformResolve(const String& hostname)
175{
176 ASSERT(isMainThread());
177
178 soup_session_prefetch_dns(globalDefaultNetworkStorageSessionAccessor()().soupNetworkSession().soupSession(), hostname.utf8().data(), nullptr, resolvedCallback, nullptr);
179}
180
181void DNSResolveQueueSoup::resolve(const String& hostname, uint64_t identifier, DNSCompletionHandler&& completionHandler)
182{
183 ASSERT(isMainThread());
184
185 auto address = adoptGRef(soup_address_new(hostname.utf8().data(), 0));
186 auto cancellable = adoptGRef(g_cancellable_new());
187 soup_address_resolve_async(address.get(), soup_session_get_async_context(WebCore::globalDefaultNetworkStorageSessionAccessor()().soupNetworkSession().soupSession()), cancellable.get(), resolvedWithObserverCallback, this);
188
189 g_object_set_data(G_OBJECT(address.get()), "identifier", GUINT_TO_POINTER(identifier));
190
191 m_completionAndCancelHandlers.add(identifier, std::make_unique<DNSResolveQueueSoup::CompletionAndCancelHandlers>(WTFMove(completionHandler), WTFMove(cancellable)));
192}
193
194void DNSResolveQueueSoup::stopResolve(uint64_t identifier)
195{
196 ASSERT(isMainThread());
197
198 if (auto completionAndCancelHandler = m_completionAndCancelHandlers.take(identifier)) {
199 g_cancellable_cancel(completionAndCancelHandler.get()->second.get());
200 completionAndCancelHandler.get()->first(makeUnexpected(DNSError::Cancelled));
201 }
202}
203
204}
205
206#endif
207