1/*
2 * Copyright (C) 2012 Intel Corporation. All rights reserved.
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''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if USE(SOUP)
29
30#include "ProxyResolverSoup.h"
31
32#include <libsoup/soup.h>
33#include <string.h>
34#include <wtf/Vector.h>
35#include <wtf/text/CString.h>
36#include <wtf/text/WTFString.h>
37
38static const char defaultNoProxyValue[] = "localhost,127.0.0.1";
39
40typedef struct {
41    SoupURI* proxyURI;
42    CString noProxy;
43    Vector<String> proxyExceptions;
44} SoupProxyResolverWkPrivate;
45
46#define SOUP_PROXY_RESOLVER_WK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), SOUP_TYPE_PROXY_RESOLVER_WK, SoupProxyResolverWkPrivate))
47
48static void soup_proxy_resolver_wk_interface_init(SoupProxyURIResolverInterface* proxyResolverInterface);
49
50G_DEFINE_TYPE_EXTENDED(SoupProxyResolverWk, soup_proxy_resolver_wk, G_TYPE_OBJECT, 0,
51                       G_IMPLEMENT_INTERFACE(SOUP_TYPE_SESSION_FEATURE, 0)
52                       G_IMPLEMENT_INTERFACE(SOUP_TYPE_PROXY_URI_RESOLVER, soup_proxy_resolver_wk_interface_init))
53
54enum {
55    PROP_0,
56    PROP_PROXY_URI,
57    PROP_NO_PROXY,
58    LAST_PROP
59};
60
61static void soup_proxy_resolver_wk_init(SoupProxyResolverWk*)
62{
63}
64
65static void soupProxyResolverWkFinalize(GObject* object)
66{
67    SoupProxyResolverWkPrivate* priv = SOUP_PROXY_RESOLVER_WK_GET_PRIVATE(object);
68
69    g_clear_pointer(&priv->proxyURI, soup_uri_free);
70
71    G_OBJECT_CLASS(soup_proxy_resolver_wk_parent_class)->finalize(object);
72}
73
74static void soupProxyResolverWkSetProperty(GObject* object, unsigned propID, const GValue* value, GParamSpec* pspec)
75{
76    SoupProxyResolverWkPrivate* priv = SOUP_PROXY_RESOLVER_WK_GET_PRIVATE(object);
77
78    switch (propID) {
79    case PROP_PROXY_URI: {
80        SoupURI* uri = static_cast<SoupURI*>(g_value_get_boxed(value));
81        if (priv->proxyURI)
82            soup_uri_free(priv->proxyURI);
83
84        priv->proxyURI = uri ? soup_uri_copy(uri) : 0;
85        break;
86    }
87    case PROP_NO_PROXY:
88        priv->noProxy = g_value_get_string(value);
89        priv->proxyExceptions.clear();
90        String::fromUTF8(priv->noProxy.data()).replaceWithLiteral(' ', "").split(',', priv->proxyExceptions);
91        break;
92    default:
93        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
94        break;
95    }
96}
97
98static void soupProxyResolverWkGetProperty(GObject* object, unsigned propID, GValue* value, GParamSpec* pspec)
99{
100    SoupProxyResolverWkPrivate* priv = SOUP_PROXY_RESOLVER_WK_GET_PRIVATE(object);
101
102    switch (propID) {
103    case PROP_PROXY_URI:
104        g_value_set_boxed(value, priv->proxyURI);
105        break;
106    case PROP_NO_PROXY:
107        g_value_set_string(value, priv->noProxy.data());
108        break;
109    default:
110        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
111        break;
112    }
113}
114
115static bool shouldBypassProxy(SoupProxyResolverWkPrivate* priv, SoupURI* uri)
116{
117    const size_t exceptionCount = priv->proxyExceptions.size();
118    for (size_t i = 0; i < exceptionCount; ++i) {
119        if (String::fromUTF8(uri->host).endsWith(priv->proxyExceptions[i], false))
120            return true;
121    }
122
123    return false;
124}
125
126typedef struct {
127    SoupProxyURIResolver* proxyResolver;
128    SoupURI* uri;
129    SoupProxyURIResolverCallback callback;
130    void* userData;
131} SoupWkAsyncData;
132
133static gboolean idle_return_proxy_uri(void* data)
134{
135    SoupWkAsyncData* ssad = static_cast<SoupWkAsyncData*>(data);
136    SoupProxyResolverWkPrivate* priv = SOUP_PROXY_RESOLVER_WK_GET_PRIVATE(ssad->proxyResolver);
137
138    SoupURI* proxyURI = 0;
139    if (!shouldBypassProxy(priv, ssad->uri))
140        proxyURI = priv->proxyURI;
141
142    ssad->callback(ssad->proxyResolver, SOUP_STATUS_OK, proxyURI, ssad->userData);
143    g_object_unref(ssad->proxyResolver);
144    soup_uri_free(ssad->uri);
145    g_slice_free(SoupWkAsyncData, ssad);
146
147    return false;
148}
149
150static void soupProxyResolverWkGetProxyURIAsync(SoupProxyURIResolver* proxyResolver, SoupURI* uri, GMainContext* asyncContext, GCancellable*, SoupProxyURIResolverCallback callback, void* userData)
151{
152    SoupWkAsyncData* ssad;
153
154    ssad = g_slice_new0(SoupWkAsyncData);
155    ssad->proxyResolver = SOUP_PROXY_URI_RESOLVER(g_object_ref(proxyResolver));
156    ssad->uri = soup_uri_copy(uri);
157    ssad->callback = callback;
158    ssad->userData = userData;
159    soup_add_completion(asyncContext, idle_return_proxy_uri, ssad);
160}
161
162static unsigned soupProxyResolverWkGetProxyURISync(SoupProxyURIResolver* proxyResolver, SoupURI* uri, GCancellable*, SoupURI** proxyURI)
163{
164    SoupProxyResolverWkPrivate* priv = SOUP_PROXY_RESOLVER_WK_GET_PRIVATE(proxyResolver);
165
166    if (!shouldBypassProxy(priv, uri))
167        *proxyURI = soup_uri_copy(priv->proxyURI);
168
169    return SOUP_STATUS_OK;
170}
171
172static void soup_proxy_resolver_wk_class_init(SoupProxyResolverWkClass* wkClass)
173{
174    GObjectClass* object_class = G_OBJECT_CLASS(wkClass);
175
176    g_type_class_add_private(wkClass, sizeof(SoupProxyResolverWkPrivate));
177
178    object_class->set_property = soupProxyResolverWkSetProperty;
179    object_class->get_property = soupProxyResolverWkGetProperty;
180    object_class->finalize = soupProxyResolverWkFinalize;
181
182    g_object_class_install_property(object_class, PROP_PROXY_URI,
183                                    g_param_spec_boxed(SOUP_PROXY_RESOLVER_WK_PROXY_URI,
184                                                       "Proxy URI",
185                                                       "The HTTP Proxy to use",
186                                                       SOUP_TYPE_URI,
187                                                       static_cast<GParamFlags>(G_PARAM_READWRITE)));
188
189    g_object_class_install_property(object_class, PROP_NO_PROXY,
190                                    g_param_spec_string(SOUP_PROXY_RESOLVER_WK_NO_PROXY,
191                                                       "Proxy exceptions",
192                                                       "Comma-separated proxy exceptions",
193                                                       defaultNoProxyValue,
194                                                       static_cast<GParamFlags>(G_PARAM_READWRITE)));
195}
196
197static void soup_proxy_resolver_wk_interface_init(SoupProxyURIResolverInterface* proxy_uri_resolver_interface)
198{
199    proxy_uri_resolver_interface->get_proxy_uri_async = soupProxyResolverWkGetProxyURIAsync;
200    proxy_uri_resolver_interface->get_proxy_uri_sync = soupProxyResolverWkGetProxyURISync;
201}
202
203SoupProxyURIResolver* soupProxyResolverWkNew(const char* httpProxy, const char* noProxy)
204{
205    SoupURI* proxyURI = soup_uri_new(httpProxy);
206    SoupProxyURIResolver* resolver = SOUP_PROXY_URI_RESOLVER(g_object_new(SOUP_TYPE_PROXY_RESOLVER_WK,
207                                                                          SOUP_PROXY_RESOLVER_WK_PROXY_URI, proxyURI,
208                                                                          SOUP_PROXY_RESOLVER_WK_NO_PROXY, noProxy ? noProxy : defaultNoProxyValue,
209                                                                          0));
210    soup_uri_free(proxyURI);
211
212    return resolver;
213}
214
215#endif
216