1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2012 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <libsoup/soup.h>
26 #include "gmpc_easy_download.h"
29 #define LOG_DOMAIN "EasyDownload"
33 #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
34 #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
35 #define ORIG_NAME 0x08 /* bit 3 set: original file name present */
36 #define COMMENT 0x10 /* bit 4 set: file comment present */
37 static char gz_magic
[2] = { 0x1f, 0x8b };
39 static int skip_gzip_header(const char *src
, gsize size
)
42 if (size
< 10 || memcmp(src
, gz_magic
, 2))
44 if (src
[2] != Z_DEFLATED
)
46 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
,
47 "unsupported compression method (%d).\n", (int)src
[3]);
51 /* skip past header, mtime and xos */
53 if (src
[3] & EXTRA_FIELD
)
54 idx
+= src
[idx
] + (src
[idx
+ 1] << 8) + 2;
55 if (src
[3] & ORIG_NAME
)
67 if (src
[3] & HEAD_CRC
)
72 static int read_cb(void *z
, char *buffer
, int size
)
78 zs
->next_out
= (void *)buffer
;
80 r
= inflate(zs
, Z_SYNC_FLUSH
);
81 if (r
== Z_OK
|| r
== Z_STREAM_END
|| r
== Z_NEED_DICT
|| r
== Z_BUF_ERROR
)
83 return size
- zs
->avail_out
;
86 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, "failed unzipping stream: %i\n", r
);
90 static int close_cb(void *z
)
98 static SoupSession
*soup_session
= NULL
;
100 static void gmpc_easy_download_set_proxy(SoupSession
* session
)
105 if (cfg_get_single_value_as_int_with_default(config
, "Network Settings", "Use Proxy", FALSE
))
107 char *value
= cfg_get_single_value_as_string(config
, "Network Settings", "Proxy Address");
108 char *username
= NULL
;
109 char *password
= NULL
;
110 gint port
= cfg_get_single_value_as_int_with_default(config
, "Network Settings", "Proxy Port", 8080);
111 if (cfg_get_single_value_as_int_with_default(config
, "Network Settings", "Use authentication", FALSE
))
113 password
= cfg_get_single_value_as_string(config
, "Network Settings", "Proxy authentication password");
114 username
= cfg_get_single_value_as_string(config
, "Network Settings", "Proxy authentication username");
120 if (username
&& username
[0] != '\0' && password
&& password
[0] != '\0')
122 gchar
*usere
= gmpc_easy_download_uri_escape(username
);
123 gchar
*passe
= gmpc_easy_download_uri_escape(password
);
124 ppath
= g_strdup_printf("http://%s:%s@%s:%i", usere
, passe
, value
, port
);
127 } else if (username
&& username
[0] != '\0')
129 gchar
*usere
= gmpc_easy_download_uri_escape(username
);
130 ppath
= g_strdup_printf("http://%s@%s:%i", usere
, value
, port
);
134 ppath
= g_strdup_printf("http://%s:%i", value
, port
);
136 uri
= soup_uri_new(ppath
);
137 g_object_set(G_OBJECT(session
), SOUP_SESSION_PROXY_URI
, uri
, NULL
);
146 g_object_set(G_OBJECT(session
), SOUP_SESSION_PROXY_URI
, NULL
, NULL
);
154 void proxy_pref_use_proxy_toggled(GtkWidget
* toggle_button
);
155 void proxy_pref_http_address_changed(GtkWidget
* entry
);
156 void proxy_pref_http_port_changed(GtkWidget
* entry
);
157 void proxy_pref_use_auth_toggled(GtkWidget
* toggle_button
);
158 void proxy_pref_auth_username_changed(GtkWidget
* entry
);
159 void proxy_pref_auth_password_changed(GtkWidget
* entry
);
161 static GtkBuilder
*proxy_pref_xml
= NULL
;
162 static void proxy_pref_destroy(GtkWidget
* container
)
164 GObject
*temp
= gtk_builder_get_object(proxy_pref_xml
, "frame_proxy_settings");
165 gtk_container_remove(GTK_CONTAINER(container
), GTK_WIDGET(temp
));
166 g_object_unref(proxy_pref_xml
);
167 proxy_pref_xml
= NULL
;
170 void proxy_pref_use_proxy_toggled(GtkWidget
* toggle_button
)
172 cfg_set_single_value_as_int(config
, "Network Settings", "Use Proxy",
173 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button
)));
174 gmpc_easy_download_set_proxy(soup_session
);
177 void proxy_pref_http_address_changed(GtkWidget
* entry
)
179 cfg_set_single_value_as_string(config
, "Network Settings", "Proxy Address",
180 (char *)gtk_entry_get_text(GTK_ENTRY(entry
)));
181 gmpc_easy_download_set_proxy(soup_session
);
184 void proxy_pref_http_port_changed(GtkWidget
* entry
)
186 cfg_set_single_value_as_int(config
, "Network Settings", "Proxy Port",
187 gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(entry
)));
188 gmpc_easy_download_set_proxy(soup_session
);
191 void proxy_pref_use_auth_toggled(GtkWidget
* toggle_button
)
193 cfg_set_single_value_as_int(config
, "Network Settings", "Use authentication",
194 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle_button
)));
195 gmpc_easy_download_set_proxy(soup_session
);
198 void proxy_pref_auth_username_changed(GtkWidget
* entry
)
200 cfg_set_single_value_as_string(config
, "Network Settings", "Proxy authentication username",
201 (char *)gtk_entry_get_text(GTK_ENTRY(entry
)));
202 gmpc_easy_download_set_proxy(soup_session
);
205 void proxy_pref_auth_password_changed(GtkWidget
* entry
)
207 cfg_set_single_value_as_string(config
, "Network Settings", "Proxy authentication password",
208 gtk_entry_get_text(GTK_ENTRY(entry
)));
209 gmpc_easy_download_set_proxy(soup_session
);
212 static void proxy_pref_construct(GtkWidget
* container
)
214 GObject
*temp
= NULL
;
216 GError
*error
= NULL
;
217 gchar
*path
= gmpc_get_full_glade_path("preferences-proxy.ui");
218 proxy_pref_xml
= gtk_builder_new();
219 gtk_builder_add_from_file(proxy_pref_xml
, path
, &error
);
223 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, "Failed to load %s: '%s'", path
, error
->message
);
232 temp
= gtk_builder_get_object(proxy_pref_xml
, "checkbutton_use_proxy");
233 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(temp
),
234 cfg_get_single_value_as_int_with_default(config
, "Network Settings", "Use Proxy",
238 temp
= gtk_builder_get_object(proxy_pref_xml
, "entry_http_hostname");
239 string
= cfg_get_single_value_as_string(config
, "Network Settings", "Proxy Address");
242 gtk_entry_set_text(GTK_ENTRY(temp
), string
);
247 temp
= gtk_builder_get_object(proxy_pref_xml
, "spinbutton_http_port");
248 gtk_spin_button_set_value(GTK_SPIN_BUTTON(temp
),
249 cfg_get_single_value_as_int_with_default(config
, "Network Settings", "Proxy Port", 8080));
252 temp
= gtk_builder_get_object(proxy_pref_xml
, "checkbutton_use_auth");
253 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(temp
),
254 cfg_get_single_value_as_int_with_default(config
, "Network Settings",
255 "Use authentication", FALSE
));
258 temp
= gtk_builder_get_object(proxy_pref_xml
, "entry_auth_username");
259 string
= cfg_get_single_value_as_string(config
, "Network Settings", "Proxy authentication username");
262 gtk_entry_set_text(GTK_ENTRY(temp
), string
);
267 temp
= gtk_builder_get_object(proxy_pref_xml
, "entry_auth_password");
268 string
= cfg_get_single_value_as_string(config
, "Network Settings", "Proxy authentication password");
271 gtk_entry_set_text(GTK_ENTRY(temp
), string
);
275 /* signal autoconnect */
276 gtk_builder_connect_signals(proxy_pref_xml
, NULL
);
278 temp
= gtk_builder_get_object(proxy_pref_xml
, "frame_proxy_settings");
279 gtk_container_add(GTK_CONTAINER(container
), GTK_WIDGET(temp
));
280 gtk_widget_show_all(container
);
283 gmpcPrefPlugin proxyplug_pref
= {
284 .construct
= proxy_pref_construct
,
285 .destroy
= proxy_pref_destroy
288 gmpcPlugin proxyplug
= {
292 .plugin_type
= GMPC_INTERNALL
,
293 .pref
= &proxyplug_pref
297 * LIBSOUP BASED ASYNC DOWNLOADER
300 static void gmpc_easy_async_free_handler_real(GEADAsyncHandler
* handle
);
305 GEADAsyncCallback callback
;
314 guint old_status_code
;
317 static guint uid
= 0;
318 static void gmpc_easy_async_headers_update(SoupMessage
* msg
, gpointer data
)
320 _GEADAsyncHandler
*d
= data
;
321 const gchar
*encoding
= soup_message_headers_get(msg
->response_headers
, "Content-Encoding");
322 goffset size
= soup_message_headers_get_content_length(msg
->response_headers
);
323 g_log("EasyDownload", G_LOG_LEVEL_DEBUG
,
324 "Expected download length: %u",(guint
)size
);
327 if (strcmp(encoding
, "gzip") == 0)
330 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Url is gzipped");
331 } else if (strcmp(encoding
, "deflate") == 0)
334 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
, "Url is enflated");
338 /* If a second set comes in, close that */
346 * Don't record data from redirection, in a while it _will_ be redirected,
349 if (d
->old_status_code
!= 0 && d
->old_status_code
!= msg
->status_code
)
351 g_log("EasyDownload", G_LOG_LEVEL_DEBUG
,
352 "Cleaning out the previous block of data: status_code: %i(old) ->%i(new)",
353 d
->old_status_code
, msg
->status_code
);
362 d
->old_status_code
= msg
->status_code
;
365 static void gmpc_easy_async_status_update(SoupMessage
* msg
, SoupBuffer
* buffer
, gpointer data
)
367 _GEADAsyncHandler
*d
= data
;
368 /* don't store error data, not used anyway */
369 if (!SOUP_STATUS_IS_SUCCESSFUL(msg
->status_code
))
371 g_log(LOG_DOMAIN
, G_LOG_LEVEL_DEBUG
,
372 "Error mesg status code: %i\n", msg
->status_code
);
375 if (d
->is_gzip
|| d
->is_deflate
)
380 d
->z
= g_malloc0(sizeof(*d
->z
));
381 data_start
= (d
->is_gzip
== 1) ? skip_gzip_header(buffer
->data
, buffer
->length
) : 0;
382 d
->z
->next_in
= (void *)((buffer
->data
) + data_start
);
383 d
->z
->avail_in
= buffer
->length
- data_start
;
387 if (inflateInit2(d
->z
, -MAX_WBITS
) == Z_OK
)
392 d
->data
= g_realloc(d
->data
, d
->length
+ 12 * 1024 + 1);
393 res
= read_cb(d
->z
, &(d
->data
[d
->length
]), 12 * 1024);
398 d
->data
[d
->length
] = '\0';
402 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, "Failure during unzipping 1: %s", d
->uri
);
403 soup_session_cancel_message(soup_session
, d
->msg
, SOUP_STATUS_MALFORMED
);
408 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, "Failure during inflateInit2: %s", d
->uri
);
409 soup_session_cancel_message(soup_session
, d
->msg
, SOUP_STATUS_MALFORMED
);
414 d
->z
->next_in
= (void *)((buffer
->data
));
415 d
->z
->avail_in
= buffer
->length
;
418 d
->data
= g_realloc(d
->data
, d
->length
+ 12 * 1024 + 1);
419 res
= read_cb(d
->z
, &(d
->data
[d
->length
]), 12 * 1024);
424 d
->data
[d
->length
] = '\0';
428 g_log(LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, "Failure during unzipping 2: %s", d
->uri
);
429 soup_session_cancel_message(soup_session
, d
->msg
, SOUP_STATUS_MALFORMED
);
435 d
->data
= g_realloc(d
->data
, d
->length
+ buffer
->length
+ 1);
436 g_memmove(&(d
->data
[d
->length
]), buffer
->data
, (size_t) buffer
->length
);
437 d
->length
+= buffer
->length
;
438 d
->data
[d
->length
] = '\0';
440 d
->callback((GEADAsyncHandler
*) d
, GEAD_PROGRESS
, d
->userdata
);
443 static void gmpc_easy_async_callback(SoupSession
* session
, SoupMessage
* msg
, gpointer data
)
445 _GEADAsyncHandler
*d
= data
;
446 if (SOUP_STATUS_IS_SUCCESSFUL(msg
->status_code
))
448 d
->callback((GEADAsyncHandler
*) d
, GEAD_DONE
, d
->userdata
);
449 } else if (msg
->status_code
== SOUP_STATUS_CANCELLED
)
451 d
->callback((GEADAsyncHandler
*) d
, GEAD_CANCELLED
, d
->userdata
);
454 d
->callback((GEADAsyncHandler
*) d
, GEAD_FAILED
, d
->userdata
);
456 gmpc_easy_async_free_handler_real((GEADAsyncHandler
*) d
);
459 static void gmpc_easy_async_free_handler_real(GEADAsyncHandler
* handle
)
461 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
470 * Get the total size of the download, if available.
472 goffset
gmpc_easy_handler_get_content_size(const GEADAsyncHandler
* handle
)
474 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
475 return soup_message_headers_get_content_length(d
->msg
->response_headers
);
478 const char *gmpc_easy_handler_get_uri(const GEADAsyncHandler
* handle
)
480 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
484 const char *gmpc_easy_handler_get_data(const GEADAsyncHandler
* handle
, goffset
* length
)
486 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
492 const guchar
*gmpc_easy_handler_get_data_vala_wrap(const GEADAsyncHandler
* handle
, gint
* length
)
494 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
496 *length
= (gint
) d
->length
;
497 return (guchar
*) d
->data
;
499 const char *gmpc_easy_handler_get_data_as_string(const GEADAsyncHandler
* handle
)
501 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
502 g_assert(d
->data
[d
->length
] == '\0');
503 return (gchar
*) d
->data
;
505 void gmpc_easy_handler_set_user_data(const GEADAsyncHandler
* handle
, gpointer user_data
)
507 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
508 d
->extra_data
= user_data
;
511 gpointer
gmpc_easy_handler_get_user_data(const GEADAsyncHandler
* handle
)
513 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
514 return d
->extra_data
;
517 void gmpc_easy_async_cancel(const GEADAsyncHandler
* handle
)
519 _GEADAsyncHandler
*d
= (_GEADAsyncHandler
*) handle
;
520 soup_session_cancel_message(soup_session
, d
->msg
, SOUP_STATUS_CANCELLED
);
523 GEADAsyncHandler
*gmpc_easy_async_downloader(const gchar
* uri
, GEADAsyncCallback callback
, gpointer user_data
)
527 g_log(LOG_DOMAIN
,G_LOG_LEVEL_WARNING
, "No download uri specified.");
530 return gmpc_easy_async_downloader_with_headers(uri
, callback
, user_data
, NULL
);
533 GEADAsyncHandler
*gmpc_easy_async_downloader_with_headers(const gchar
* uri
, GEADAsyncCallback callback
,
534 gpointer user_data
, ...)
537 _GEADAsyncHandler
*d
;
540 if (soup_session
== NULL
)
542 soup_session
= soup_session_async_new();
543 gmpc_easy_download_set_proxy(soup_session
);
544 g_object_set(soup_session
, "timeout", 5, NULL
);
545 /* Set user agent, to get around wikipedia ban. */
546 g_object_set(soup_session
, "user-agent", "gmpc ",NULL
);
549 msg
= soup_message_new("GET", uri
);
553 soup_message_headers_append(msg
->request_headers
, "Accept-Encoding", "deflate,gzip");
554 va_start(ap
, user_data
);
555 va_entry
= va_arg(ap
, typeof(va_entry
));
558 char *value
= va_arg(ap
, typeof(value
));
559 soup_message_headers_append(msg
->request_headers
, va_entry
, value
);
560 va_entry
= va_arg(ap
, typeof(va_entry
));
564 d
= g_malloc0(sizeof(*d
));
571 d
->uri
= g_strdup(uri
);
572 d
->callback
= callback
;
573 d
->userdata
= user_data
;
574 d
->extra_data
= NULL
;
575 d
->old_status_code
= 0;
576 soup_message_body_set_accumulate(msg
->response_body
, FALSE
);
577 g_signal_connect_after(msg
, "got-chunk", G_CALLBACK(gmpc_easy_async_status_update
), d
);
578 g_signal_connect_after(msg
, "got-headers", G_CALLBACK(gmpc_easy_async_headers_update
), d
);
579 soup_session_queue_message(soup_session
, msg
, gmpc_easy_async_callback
, d
);
581 return (GEADAsyncHandler
*) d
;
584 void gmpc_easy_async_quit(void)
588 soup_session_abort(soup_session
);
589 g_object_unref(soup_session
);
594 char *gmpc_easy_download_uri_escape(const char *part
)
596 return soup_uri_encode(part
, "&+");
599 /**************************************************************/
605 GEADAsyncCallbackVala callback
;
608 static void temp_callback(const GEADAsyncHandler
*handle
, GEADStatus status
, gpointer user_data
)
610 valaf
*f
= (valaf
*)user_data
;
611 f
->callback(handle
, status
, f
->b
, f
->a
);
612 if(status
!= GEAD_PROGRESS
) g_free(f
);
614 GEADAsyncHandler
* gmpc_easy_async_downloader_vala(const char *path
, gpointer user_data2
, GEADAsyncCallbackVala callback
,
618 valaf
*f
= g_malloc0(sizeof(*f
));
621 f
->callback
= callback
;
622 return gmpc_easy_async_downloader(path
, temp_callback
, f
);
626 /* vim: set noexpandtab ts=4 sw=4 sts=4 tw=120: */