1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 #include "wocky-test-stream.h"
6 #include "wocky-test-helper.h"
7 
8 #include <string.h>
9 
10 #include <wocky/wocky.h>
11 
12 typedef enum
13 {
14   DOMAIN_NONE = 0,
15   DOMAIN_G_IO_ERROR
16 } HttpErrorDomain;
17 
18 typedef struct
19 {
20   const gchar *path;
21   const gchar *reply;
22   HttpErrorDomain domain;
23   gint code;
24   const gchar *username;
25   const gchar *password;
26 } HttpTestCase;
27 
28 typedef struct
29 {
30   GMainLoop *mainloop;
31   GMainLoop *thread_mainloop;
32   GCancellable *cancellable;
33   GCancellable *thread_cancellable;
34   GThread *thread;
35   GSocketListener *listener;
36   guint16 port;
37   const HttpTestCase *test_case;
38 } HttpTestData;
39 
40 static HttpTestCase test_cases[] = {
41     { "/http-proxy/close-by-peer",
42       "", DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_FAILED },
43     { "/http-proxy/bad-reply",
44       "BAD REPLY", DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_FAILED },
45     { "/http-proxy/very-short-reply",
46       "HTTP/1.\r\n\r\n", DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_FAILED },
47     { "/http-proxy/short-reply",
48       "HTTP/1.0\r\n\r\n", DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_FAILED },
49     { "/http-proxy/http-404",
50       "HTTP/1.0 404 Not Found\r\n"
51         "Content-Type: text/html; charset=UTF-8\r\n"
52         "Content-Length: 27\r\n"
53         "\r\n"
54         "<html><h1>Hello</h1></html>",
55       DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_FAILED },
56     { "/http-proxy/need-authentication",
57       "HTTP/1.0 407 Proxy Authentication Required\r\n"
58         "Content-Type: text/html; charset=UTF-8\r\n"
59         "Content-Length: 27\r\n"
60         "\r\n"
61         "<html><h1>Hello</h1></html>",
62       DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH },
63     { "/http-proxy/success",
64       "HTTP/1.0 200 OK\r\n"
65         "\r\n",
66       DOMAIN_NONE, 0 },
67     { "/http-proxy/authentication-failed",
68       "HTTP/1.0 407 Proxy Authentication Required\r\n"
69         "Content-Type: text/html; charset=UTF-8\r\n"
70         "Content-Length: 27\r\n"
71         "\r\n"
72         "<html><h1>Hello</h1></html>",
73       DOMAIN_G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED,
74       "username", "bad-password" },
75     { "/http-proxy/authenticated",
76       "HTTP/1.0 200 OK\r\n"
77         "\r\n",
78       DOMAIN_NONE, 0,
79       "Aladdin", "open sesame"},
80 };
81 
82 static HttpTestData *
tearup(const HttpTestCase * test_case)83 tearup (const HttpTestCase *test_case)
84 {
85   HttpTestData *data;
86 
87   data = g_new0 (HttpTestData, 1);
88 
89   data->mainloop = g_main_loop_new (NULL, FALSE);
90 
91   data->cancellable = g_cancellable_new ();
92   data->thread_cancellable = g_cancellable_new ();
93 
94   data->listener = g_socket_listener_new ();
95   data->port = g_socket_listener_add_any_inet_port (data->listener, NULL, NULL);
96   g_assert_cmpuint (data->port, !=, 0);
97 
98   data->test_case = test_case;
99 
100   return data;
101 }
102 
103 static void
run_in_thread(HttpTestData * data,GThreadFunc func)104 run_in_thread (HttpTestData *data,
105     GThreadFunc func)
106 {
107   data->thread = g_thread_new ("server_thread", func, data);
108   g_assert (data->thread != NULL);
109 }
110 
111 static gboolean
tear_down_idle_cb(gpointer user_data)112 tear_down_idle_cb (gpointer user_data)
113 {
114   HttpTestData *data = user_data;
115   g_main_loop_quit (data->mainloop);
116   return FALSE;
117 }
118 
119 static void
teardown(HttpTestData * data)120 teardown (HttpTestData *data)
121 {
122   if (!g_cancellable_is_cancelled (data->cancellable))
123     g_cancellable_cancel (data->cancellable);
124 
125   if (!g_cancellable_is_cancelled (data->thread_cancellable))
126     g_cancellable_cancel (data->thread_cancellable);
127 
128   if (data->thread)
129     g_thread_join (data->thread);
130 
131   if (g_main_loop_is_running (data->mainloop))
132     g_main_loop_quit (data->mainloop);
133 
134   g_idle_add_full (G_PRIORITY_LOW, tear_down_idle_cb, data, NULL);
135 
136   g_main_loop_run (data->mainloop);
137 
138   g_object_unref (data->cancellable);
139   g_object_unref (data->thread_cancellable);
140   g_object_unref (data->listener);
141   g_main_loop_unref (data->mainloop);
142 
143   g_free (data);
144 }
145 
146 static gboolean
str_has_prefix_case(const gchar * str,const gchar * prefix)147 str_has_prefix_case (const gchar *str,
148     const gchar *prefix)
149 {
150   return g_ascii_strncasecmp (prefix, str, strlen (prefix)) == 0;
151 }
152 
153 static void
test_http_proxy_instantiation(void)154 test_http_proxy_instantiation (void)
155 {
156   GProxy *proxy;
157 
158   proxy = g_proxy_get_default_for_protocol ("http");
159   g_assert (G_IS_PROXY (proxy));
160   g_object_unref (proxy);
161 }
162 
163 static gpointer
server_thread(gpointer user_data)164 server_thread (gpointer user_data)
165 {
166   HttpTestData *data = user_data;
167   GSocketConnection *conn;
168   GDataInputStream *data_in;
169   GOutputStream *out;
170   gchar *buffer;
171   gint has_host = 0;
172   gint has_user_agent = 0;
173   gint has_cred = 0;
174 
175   conn = g_socket_listener_accept (data->listener, NULL,
176       data->thread_cancellable, NULL);
177   g_assert (conn != NULL);
178 
179   data_in = g_data_input_stream_new (
180       g_io_stream_get_input_stream (G_IO_STREAM (conn)));
181   g_data_input_stream_set_newline_type (data_in,
182       G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
183 
184   buffer = g_data_input_stream_read_line (data_in, NULL,
185       data->thread_cancellable, NULL);
186   g_assert_cmpstr (buffer, ==, "CONNECT to:443 HTTP/1.0");
187 
188   do {
189       g_free (buffer);
190       buffer = g_data_input_stream_read_line (data_in, NULL,
191           data->thread_cancellable, NULL);
192       g_assert (buffer != NULL);
193 
194       if (str_has_prefix_case (buffer, "Host:"))
195         {
196           has_host++;
197           g_assert_cmpstr (buffer, ==, "Host: to:443");
198         }
199       else if (str_has_prefix_case (buffer, "User-Agent:"))
200         has_user_agent++;
201       else if (str_has_prefix_case (buffer, "Proxy-Authorization:"))
202         {
203           gchar *cred;
204           gchar *base64_cred;
205           const gchar *received_cred;
206 
207           has_cred++;
208 
209           g_assert (data->test_case->username != NULL);
210           g_assert (data->test_case->password != NULL);
211 
212           cred = g_strdup_printf ("%s:%s",
213               data->test_case->username, data->test_case->password);
214           base64_cred = g_base64_encode ((guchar *) cred, strlen (cred));
215           g_free (cred);
216 
217           received_cred = buffer + 20;
218           while (*received_cred == ' ')
219             received_cred++;
220 
221           g_assert (str_has_prefix_case (received_cred, "Basic"));
222           received_cred += 5;
223           while (*received_cred == ' ')
224             received_cred++;
225 
226           g_assert_cmpstr (base64_cred, ==, received_cred);
227           g_free (base64_cred);
228         }
229   } while (buffer[0] != '\0');
230 
231   g_assert_cmpuint (has_host, ==, 1);
232   g_assert_cmpuint (has_user_agent, ==, 1);
233 
234   if (data->test_case->username != NULL)
235     g_assert_cmpuint (has_cred, ==, 1);
236   else
237     g_assert_cmpuint (has_cred, ==, 0);
238 
239   g_free (buffer);
240 
241   out = g_io_stream_get_output_stream (G_IO_STREAM (conn));
242   g_assert (g_output_stream_write_all (out,
243         data->test_case->reply, strlen (data->test_case->reply),
244         NULL, data->thread_cancellable, NULL));
245   g_object_unref (data_in);
246   g_object_unref (conn);
247 
248   return NULL;
249 }
250 
251 static GQuark
get_error_domain(HttpErrorDomain id)252 get_error_domain (HttpErrorDomain id)
253 {
254   GQuark domain = 0;
255 
256   switch (id)
257     {
258     case DOMAIN_G_IO_ERROR:
259       domain = G_IO_ERROR;
260       break;
261     default:
262       g_assert_not_reached ();
263     }
264 
265   return domain;
266 }
267 
268 static GSocketAddress *
create_proxy_address(HttpTestData * data)269 create_proxy_address (HttpTestData *data)
270 {
271   GSocketAddress *proxy_address;
272   GInetAddress *inet_address;
273 
274   inet_address = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
275   proxy_address = g_proxy_address_new (inet_address, data->port, "http",
276       "to", 443, data->test_case->username, data->test_case->password);
277   g_object_unref (inet_address);
278 
279   return proxy_address;
280 }
281 
282 static void
check_result(const HttpTestCase * test_case,GSocketConnection * connection,GError * error)283 check_result (const HttpTestCase *test_case,
284     GSocketConnection *connection,
285     GError *error)
286 {
287   if (test_case->domain != DOMAIN_NONE)
288     {
289       g_assert_error (error, get_error_domain (test_case->domain),
290           test_case->code);
291       g_error_free (error);
292     }
293   else
294     {
295       g_assert_no_error (error);
296       g_object_unref (connection);
297     }
298 }
299 
300 static void
test_http_proxy_with_data(gconstpointer user_data)301 test_http_proxy_with_data (gconstpointer user_data)
302 {
303   const HttpTestCase *test_case = user_data;
304   HttpTestData *data;
305   GSocketClient *client;
306   GSocketAddress *proxy_address;
307   GSocketConnection *connection;
308   GError *error = NULL;
309 
310   data = tearup (test_case);
311 
312   run_in_thread (data, server_thread);
313 
314   client = g_socket_client_new ();
315   proxy_address = create_proxy_address (data);
316   connection = g_socket_client_connect (client,
317       G_SOCKET_CONNECTABLE (proxy_address), data->cancellable, &error);
318 
319   g_object_unref (proxy_address);
320   g_object_unref (client);
321 
322   check_result (test_case, connection, error);
323 
324   teardown (data);
325 }
326 
327 static void
connect_cb(GObject * source,GAsyncResult * result,gpointer user_data)328 connect_cb (GObject *source,
329     GAsyncResult *result,
330     gpointer user_data)
331 {
332   HttpTestData *data = user_data;
333   GSocketConnection *connection;
334   GError *error = NULL;
335 
336   connection = g_socket_client_connect_finish (G_SOCKET_CLIENT (source),
337       result, &error);
338 
339   check_result (data->test_case, connection, error);
340 
341   g_main_loop_quit (data->mainloop);
342 }
343 
344 static void
test_http_proxy_with_data_async(gconstpointer user_data)345 test_http_proxy_with_data_async (gconstpointer user_data)
346 {
347   const HttpTestCase *test_case = user_data;
348   HttpTestData *data;
349   GSocketClient *client;
350   GSocketAddress *proxy_address;
351 
352   data = tearup (test_case);
353 
354   run_in_thread (data, server_thread);
355 
356   client = g_socket_client_new ();
357   proxy_address = create_proxy_address (data);
358   g_socket_client_connect_async (client, G_SOCKET_CONNECTABLE (proxy_address),
359       data->cancellable, connect_cb, data);
360 
361   g_object_unref (client);
362   g_object_unref (proxy_address);
363 
364   g_main_loop_run (data->mainloop);
365 
366   teardown (data);
367 }
368 
main(int argc,char ** argv)369 int main (int argc,
370     char **argv)
371 {
372   int result;
373   guint i;
374 
375   test_init (argc, argv);
376 
377   g_test_add_func ("/http-proxy/instantiation",
378       test_http_proxy_instantiation);
379 
380   for (i = 0; i < G_N_ELEMENTS (test_cases); i++)
381     {
382       gchar *async_path;
383 
384       g_test_add_data_func (test_cases[i].path,
385           test_cases + i, test_http_proxy_with_data);
386 
387       async_path = g_strdup_printf ("%s-async", test_cases[i].path);
388       g_test_add_data_func (async_path,
389           test_cases + i, test_http_proxy_with_data_async);
390       g_free (async_path);
391     }
392 
393   result = g_test_run ();
394   test_deinit ();
395   return result;
396 }
397