1 #include <glib.h>
2 #include <curl/curl.h>
3 #include "urlgfe.h"
4 #include "download_backend.h"
5 #include "utility.h"
6 
7 typedef struct _DownloadBackendCurl DownloadBackendCurl;
8 
9 struct _DownloadBackendCurl
10 {
11 	CURL*   curl;
12 	gdouble last_completed;
13 	glong   resume_from;
14 
15 	DownloadThread* thread;
16 
17 	char    error_string[CURL_ERROR_SIZE + 1];
18 };
19 
20 
21 static int curl_progress_func (DownloadBackendCurl* backend,
22                                double dl_total,
23                                double dl_now,
24                                double ul_total,
25                                double ul_now);
26 
url_post_encode(const char * pre_url,int len)27 char* url_post_encode (const char* pre_url, int len)
28 {
29 	GString* gstring;
30 	UrlPart  urlp;
31 
32 	if (len==0)
33 		len = strlen (pre_url);
34 
35 	url_part (&urlp, pre_url);
36 	if (urlp.path_beg) {
37 		gstring = g_string_sized_new (256);
38 		g_string_append_len (gstring, pre_url, urlp.path_beg - pre_url);
39 		pre_url = urlp.path_beg;
40 		for (; len; len--, pre_url++) {
41 			if ( *pre_url == '\n' )
42 				continue;
43 			else if ( *pre_url & 0x80 || *pre_url== ' ')
44 				g_string_append_printf (gstring, "%c%X", '%', *pre_url);
45 			else
46 				g_string_append_c (gstring, *pre_url);
47 		}
48 		return g_string_free (gstring, FALSE);
49 	}
50 	else
51 		return g_strndup (pre_url, len);
52 }
53 
download_backend_global_init()54 void download_backend_global_init ()
55 {
56 	curl_global_init (CURL_GLOBAL_ALL);
57 }
58 
download_backend_global_clean()59 void download_backend_global_clean ()
60 {
61 	curl_global_cleanup ();
62 }
63 
download_backend_new(gpointer data)64 gpointer download_backend_new (gpointer data)
65 {
66 	DownloadBackendCurl* backend;
67 
68 	backend = g_malloc (sizeof (DownloadBackendCurl));
69 	backend->curl = curl_easy_init ();
70 	backend->thread = data;
71 
72 	return backend;
73 }
74 
download_backend_destroy(gpointer data)75 void download_backend_destroy (gpointer data)
76 {
77 	DownloadBackendCurl* backend = data;
78 
79 	curl_easy_cleanup (backend->curl);
80 
81 	g_free (backend);
82 }
83 
download_backend_run(gpointer backend,DownloadThread * dt)84 gint download_backend_run (gpointer backend, DownloadThread* dt)
85 {
86 	DownloadBackendCurl* dbc = backend;
87 	GString* filename = dt->out_filename;
88 	FILE*    file;
89 	DownloadSetting* setting = DOWNLOAD_SETTING (dt->download_data);
90 
91 	CURL*    curl = dbc->curl;
92 	CURLcode curl_code;
93 	struct   curl_slist* slist = NULL;
94 //	long     curl_proxy_type;
95 
96 	// string data used by libcurl
97 	// besure curl session finish when you release these.
98 	gchar*   encoded_url = NULL;
99 	gchar*   referrer = NULL;
100 	gchar*   proxy_user_pwd = NULL;
101 
102 	dbc->last_completed = 0.0;
103 
104 	// check file
105 	file = fopen (filename->str, (dt->resumable)? "ab" : "wb");
106 	if (file==NULL) {
107 		download_thread_job_set_message (dt, _("File open failure."));
108 		return DOWNLOAD_BACKEND_ERROR;
109 	}
110 
111 	if (dt->resumable) {
112 		fseek (file, 0, SEEK_END);
113 		dbc->resume_from = ftell (file);
114 	} else {
115 		dbc->resume_from = 0;
116 	}
117 
118 	// test code
119 //	curl_easy_setopt (curl, CURLOPT_UNRESTRICTED_AUTH, 1);
120 	curl_easy_setopt (curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)");
121 //	curl_easy_setopt (curl, CURLOPT_USERAGENT, "Internet Explorer");
122 	curl_easy_setopt (curl, CURLOPT_COOKIEFILE, "");
123 
124 	// set proxy ----------------------------------------------------
125 	// clear proxy setting
126 	curl_easy_setopt (curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
127 	curl_easy_setopt (curl, CURLOPT_PROXY, NULL);
128 	curl_easy_setopt (curl, CURLOPT_PROXYUSERPWD, NULL);
129 
130 	if (PROXY_SETTING (setting)->type != PROXY_TYPE_NONE) {
131 		if (PROXY_SETTING (setting)->type == PROXY_TYPE_HTTP)
132 			curl_easy_setopt (curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
133 		else
134 			curl_easy_setopt (curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
135 		curl_easy_setopt (curl, CURLOPT_PROXY,
136 		                  PROXY_SETTING (setting)->address);
137 		curl_easy_setopt (curl, CURLOPT_PROXYPORT,
138 		                  (long)PROXY_SETTING (setting)->port);
139 
140 		if (PROXY_SETTING (setting)->authentication) {
141 			// set username and password
142 			proxy_user_pwd = g_strdup_printf ("%s:%s",
143 			                       STR_N2E (PROXY_SETTING (setting)->username),
144 			                       STR_N2E (PROXY_SETTING (setting)->password));
145 			curl_easy_setopt (curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd);
146 		}
147 		else if (PROXY_SETTING (setting)->type == PROXY_TYPE_SOCKS5) {
148 			// Avoid crash when using socks5 without authentication.
149 			curl_easy_setopt (curl, CURLOPT_PROXYUSERPWD, ":");
150 		}
151 	}
152 
153 	// set redirection
154 	if (setting->n_redirect) {
155 		curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
156 		curl_easy_setopt (curl, CURLOPT_MAXREDIRS, setting->n_redirect);
157 	} else {
158 		curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 0);
159 	}
160 
161 	// set referrer
162 //	referrer = g_strdup_printf ("Referrer: %s", STR_N2E (setting->referer));
163 //	slist = curl_slist_append (slist, referrer);
164 //	curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist);
165 	curl_easy_setopt (curl, CURLOPT_REFERER, STR_N2E (setting->referer));
166 
167 	// set url
168 	encoded_url = url_post_encode (STR_N2E (setting->url), 0);
169 	curl_easy_setopt (curl, CURLOPT_URL, encoded_url);
170 
171 	// set download data
172 	curl_easy_setopt (curl, CURLOPT_FILE, file);
173 	curl_easy_setopt (curl, CURLOPT_NOPROGRESS, FALSE);
174 	curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func);
175 	curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, dbc);
176 	curl_easy_setopt (curl, CURLOPT_RESUME_FROM, dbc->resume_from);
177 	curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, dbc->error_string);
178 
179 	// perform curl
180 	curl_code = curl_easy_perform (curl);
181 	fclose (file);
182 	curl_slist_free_all (slist);
183 
184 	// free string that used by libcurl
185 	g_free (referrer);
186 	g_free (proxy_user_pwd);
187 	g_free (encoded_url);
188 
189 //	dt->time_state.
190 //	set_job_speed (NULL);
191 
192 	if (curl_code == CURLE_OK) {
193 		Download* dld = dt->download_data;
194 
195 		if (dld->completed + dbc->resume_from == dld->total + dbc->resume_from &&
196 		    dld->completed + dbc->resume_from != 0 )
197 		{
198 			return DOWNLOAD_BACKEND_OK;
199 		}
200 		else {
201 			unlink (filename->str);
202 			download_thread_job_set_message (dt, _("File length error. Try to increase redirection limit."));
203 			return DOWNLOAD_BACKEND_ERROR;
204 		}
205 	}
206 
207 	printf ("Curl error %.2u: %s\n", curl_code, dbc->error_string);
208 	download_thread_job_set_message (dt, dbc->error_string);
209 
210 	// check curl return code
211 	switch (curl_code) {
212 		// can resume
213 		case CURLE_PARTIAL_FILE:
214 		case CURLE_GOT_NOTHING:
215 			dt->resumable = TRUE;
216 			break;
217 		// can't resume
218 		case CURLE_HTTP_RANGE_ERROR:
219 			dt->resumable = FALSE;
220 			dbc->resume_from = 0;
221 			break;
222 		case CURLE_ABORTED_BY_CALLBACK:
223 			return DOWNLOAD_BACKEND_ABORT;
224 //		case CURLE_UNSUPPORTED_PROTOCOL:
225 //		case CURLE_FAILED_INIT:
226 //		case CURLE_URL_MALFORMAT:
227 //		case CURLE_URL_MALFORMAT_USER:
228 //		case CURLE_COULDNT_RESOLVE_PROXY:
229 //		case CURLE_COULDNT_RESOLVE_HOST:
230 //		case CURLE_COULDNT_CONNECT:
231 //		case CURLE_FTP_WEIRD_SERVER_REPLY:
232 //		case CURLE_FTP_ACCESS_DENIED:
233 //		case CURLE_FTP_USER_PASSWORD_INCORRECT:
234 		default:
235 			unlink (filename->str);
236 			return DOWNLOAD_BACKEND_ERROR;
237 	}
238 
239 	return DOWNLOAD_BACKEND_CAN_RETRY;
240 }
241 
curl_progress_func(DownloadBackendCurl * dbc,double dl_total,double dl_now,double ul_total,double ul_now)242 static int curl_progress_func (DownloadBackendCurl* dbc,
243                                double dl_total,
244                                double dl_now,
245                                double ul_total,
246                                double ul_now)
247 {
248 	DownloadThread* dt = dbc->thread;
249 	Download* download_data = dt->download_data;
250 	gdouble timer = download_thread_get_timer (dt);
251 	gdouble speed;
252 
253 	if (dl_now) {
254 		download_thread_job_set_info_valid (dt, TRUE);
255 		speed = (dl_now - dbc->last_completed) / timer;
256 		download_data->speed = (download_data->speed + speed) /2;
257 		download_data->completed = dl_now + dbc->resume_from;
258 		download_data->total     = dl_total + dbc->resume_from;
259 		dbc->last_completed = dl_now;
260 		// curl pass dl_total=0 sometimes.
261 		if (download_data->total < download_data->completed)
262 			download_data->total = download_data->completed;
263 	}
264 
265 	// avoid div zero
266 	if (download_data->total)
267 		download_data->percent = (download_data->completed / download_data->total)*100;
268 
269 	download_thread_job_redraw (dt);
270 	// return true when abort.
271 	return (dt->thread_state >= DOWNLOAD_THREAD_SKIP);
272 }
273 
274 
275