dload.c 22.5 KB
Newer Older
Dan McGee's avatar
Dan McGee committed
1
/*
2
 *  dload.c
Dan McGee's avatar
Dan McGee committed
3
 *
Allan McRae's avatar
Allan McRae committed
4
 *  Copyright (c) 2006-2019 Pacman Development Team <pacman-dev@archlinux.org>
5
 *  Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
Dan McGee's avatar
Dan McGee committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
26
#include <sys/socket.h> /* setsockopt, SO_KEEPALIVE */
27
28
29
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
30
#include <signal.h>
31

32
33
34
35
36
37
38
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h> /* IPPROTO_TCP */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h> /* TCP_KEEPINTVL, TCP_KEEPIDLE */
#endif

39
40
41
42
#ifdef HAVE_LIBCURL
#include <curl/curl.h>
#endif

Dan McGee's avatar
Dan McGee committed
43
44
45
46
47
48
49
50
/* libalpm */
#include "dload.h"
#include "alpm_list.h"
#include "alpm.h"
#include "log.h"
#include "util.h"
#include "handle.h"

51
#ifdef HAVE_LIBCURL
52
static const char *get_filename(const char *url)
53
{
54
55
	char *filename = strrchr(url, '/');
	if(filename != NULL) {
56
		return filename + 1;
57
	}
58
59
60

	/* no slash found, it's a filename */
	return url;
61
}
62

63
64
static char *get_fullpath(const char *path, const char *filename,
		const char *suffix)
65
{
66
67
68
	char *filepath;
	/* len = localpath len + filename len + suffix len + null */
	size_t len = strlen(path) + strlen(filename) + strlen(suffix) + 1;
69
	MALLOC(filepath, len, return NULL);
70
	snprintf(filepath, len, "%s%s%s", path, filename, suffix);
71

72
	return filepath;
73
74
}

75
76
77
78
79
80
81
82
83
static CURL *get_libcurl_handle(alpm_handle_t *handle)
{
	if(!handle->curl) {
		curl_global_init(CURL_GLOBAL_SSL);
		handle->curl = curl_easy_init();
	}
	return handle->curl;
}

84
85
86
87
88
enum {
	ABORT_SIGINT = 1,
	ABORT_OVER_MAXFILESIZE
};

89
static int dload_interrupted;
90
static void inthandler(int UNUSED signum)
91
{
92
	dload_interrupted = ABORT_SIGINT;
93
}
94

95
96
static int dload_progress_cb(void *file, curl_off_t dltotal, curl_off_t dlnow,
		curl_off_t UNUSED ultotal, curl_off_t UNUSED ulnow)
97
{
98
	struct dload_payload *payload = (struct dload_payload *)file;
99
	off_t current_size, total_size;
100

101
102
103
104
105
	/* avoid displaying progress bar for redirects with a body */
	if(payload->respcode >= 300) {
		return 0;
	}

106
107
108
109
110
	/* SIGINT sent, abort by alerting curl */
	if(dload_interrupted) {
		return 1;
	}

111
	current_size = payload->initial_size + dlnow;
112
113
114

	/* is our filesize still under any set limit? */
	if(payload->max_size && current_size > payload->max_size) {
115
		dload_interrupted = ABORT_OVER_MAXFILESIZE;
116
117
118
		return 1;
	}

119
	/* none of what follows matters if the front end has no callback */
120
	if(payload->handle->dlcb == NULL) {
121
122
123
		return 0;
	}

124
	total_size = payload->initial_size + dltotal;
125

126
	if(dltotal == 0 || payload->prevprogress == total_size) {
127
		return 0;
128
129
	}

130
	/* initialize the progress bar here to avoid displaying it when
131
132
133
134
135
136
137
	 * a repo is up to date and nothing gets downloaded.
	 * payload->handle->dlcb will receive the remote_name
	 * and the following arguments:
	 * 0, -1: download initialized
	 * 0, 0: non-download event
	 * x {x>0}, x: download complete
	 * x {x>0, x<y}, y {y > 0}: download progress, expected total is known */
138
	if(!payload->cb_initialized) {
139
		payload->handle->dlcb(payload->remote_name, 0, -1);
140
141
142
		payload->cb_initialized = 1;
	}
	if(payload->prevprogress == current_size) {
143
144
		payload->handle->dlcb(payload->remote_name, 0, 0);
	} else {
145
146
	/* do NOT include initial_size since it wasn't part of the package's
	 * download_size (nor included in the total download size callback) */
147
		payload->handle->dlcb(payload->remote_name, dlnow, dltotal);
148
	}
149

150
	payload->prevprogress = current_size;
151

152
	return 0;
153
154
}

155
static int curl_gethost(const char *url, char *buffer, size_t buf_len)
156
{
157
	size_t hostlen;
158
	char *p, *q;
159
160

	if(strncmp(url, "file://", 7) == 0) {
161
162
		p = _("disk");
		hostlen = strlen(p);
163
164
165
	} else {
		p = strstr(url, "//");
		if(!p) {
166
			return 1;
167
168
169
		}
		p += 2; /* jump over the found // */
		hostlen = strcspn(p, "/");
170

Dave Reisner's avatar
Dave Reisner committed
171
172
173
174
175
176
177
178
179
		/* there might be a user:pass@ on the URL. hide it. avoid using memrchr()
		 * for portability concerns. */
		q = p + hostlen;
		while(--q > p) {
			if(*q == '@') {
				break;
			}
		}
		if(*q == '@' && p != q) {
180
181
182
			hostlen -= q - p + 1;
			p = q + 1;
		}
183
	}
184

185
186
187
	if(hostlen > buf_len - 1) {
		/* buffer overflow imminent */
		return 1;
188
	}
189
190
	memcpy(buffer, p, hostlen);
	buffer[hostlen] = '\0';
191

192
	return 0;
193
194
}

Dan McGee's avatar
Dan McGee committed
195
static int utimes_long(const char *path, long seconds)
196
{
Dan McGee's avatar
Dan McGee committed
197
	if(seconds != -1) {
198
199
		struct timeval tv[2];
		memset(&tv, 0, sizeof(tv));
Dan McGee's avatar
Dan McGee committed
200
		tv[0].tv_sec = tv[1].tv_sec = seconds;
201
202
203
204
205
		return utimes(path, tv);
	}
	return 0;
}

206
207
208
209
210
211
212
213
/* prefix to avoid possible future clash with getumask(3) */
static mode_t _getumask(void)
{
	mode_t mask = umask(0);
	umask(mask);
	return mask;
}

214
static size_t dload_parseheader_cb(void *ptr, size_t size, size_t nmemb, void *user)
Dave Reisner's avatar
Dave Reisner committed
215
216
217
218
219
{
	size_t realsize = size * nmemb;
	const char *fptr, *endptr = NULL;
	const char * const cd_header = "Content-Disposition:";
	const char * const fn_key = "filename=";
220
	struct dload_payload *payload = (struct dload_payload *)user;
221
	long respcode;
Dave Reisner's avatar
Dave Reisner committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237

	if(_alpm_raw_ncmp(cd_header, ptr, strlen(cd_header)) == 0) {
		if((fptr = strstr(ptr, fn_key))) {
			fptr += strlen(fn_key);

			/* find the end of the field, which is either a semi-colon, or the end of
			 * the data. As per curl_easy_setopt(3), we cannot count on headers being
			 * null terminated, so we look for the closing \r\n */
			endptr = fptr + strcspn(fptr, ";\r\n") - 1;

			/* remove quotes */
			if(*fptr == '"' && *endptr == '"') {
				fptr++;
				endptr--;
			}

238
			STRNDUP(payload->content_disp_name, fptr, endptr - fptr + 1,
239
					RET_ERR(payload->handle, ALPM_ERR_MEMORY, realsize));
Dave Reisner's avatar
Dave Reisner committed
240
241
		}
	}
242

243
244
245
246
247
	curl_easy_getinfo(payload->handle->curl, CURLINFO_RESPONSE_CODE, &respcode);
	if(payload->respcode != respcode) {
		payload->respcode = respcode;
	}

Dave Reisner's avatar
Dave Reisner committed
248
249
250
	return realsize;
}

251
static void curl_set_handle_opts(struct dload_payload *payload,
252
		CURL *curl, char *error_buffer)
253
254
255
256
257
258
{
	alpm_handle_t *handle = payload->handle;
	const char *useragent = getenv("HTTP_USER_AGENT");
	struct stat st;

	/* the curl_easy handle is initialized with the alpm handle, so we only need
Dave Reisner's avatar
Dave Reisner committed
259
	 * to reset the handle's parameters for each time it's used. */
260
261
262
263
	curl_easy_reset(curl);
	curl_easy_setopt(curl, CURLOPT_URL, payload->fileurl);
	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer);
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
264
	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L);
265
266
267
	curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
268
269
	curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, dload_progress_cb);
	curl_easy_setopt(curl, CURLOPT_XFERINFODATA, (void *)payload);
270
271
272
273
	if(!handle->disable_dl_timeout) {
		curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
		curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 10L);
	}
274
	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, dload_parseheader_cb);
275
	curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)payload);
276
	curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
277
278
279
	curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
	curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 60L);
	curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L);
280
	curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
281

Dave Reisner's avatar
Dave Reisner committed
282
283
	_alpm_log(handle, ALPM_LOG_DEBUG, "url: %s\n", payload->fileurl);

284
	if(payload->max_size) {
285
286
		_alpm_log(handle, ALPM_LOG_DEBUG, "maxsize: %jd\n",
				(intmax_t)payload->max_size);
287
		curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE,
288
				(curl_off_t)payload->max_size);
289
290
291
	}

	if(useragent != NULL) {
292
		curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
293
294
295
296
297
	}

	if(!payload->allow_resume && !payload->force && payload->destfile_name &&
			stat(payload->destfile_name, &st) == 0) {
		/* start from scratch, but only download if our local is out of date. */
298
299
		curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
		curl_easy_setopt(curl, CURLOPT_TIMEVALUE, (long)st.st_mtime);
Dave Reisner's avatar
Dave Reisner committed
300
		_alpm_log(handle, ALPM_LOG_DEBUG,
301
				"using time condition: %ld\n", (long)st.st_mtime);
302
303
304
	} else if(stat(payload->tempfile_name, &st) == 0 && payload->allow_resume) {
		/* a previous partial download exists, resume from end of file. */
		payload->tempfile_openmode = "ab";
305
		curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)st.st_size);
Dave Reisner's avatar
Dave Reisner committed
306
		_alpm_log(handle, ALPM_LOG_DEBUG,
307
308
				"tempfile found, attempting continuation from %jd bytes\n",
				(intmax_t)st.st_size);
309
		payload->initial_size = st.st_size;
310
311
312
	}
}

313
static void mask_signal(int signum, void (*handler)(int),
314
		struct sigaction *origaction)
315
{
316
	struct sigaction newaction;
317
318
319
320
321

	newaction.sa_handler = handler;
	sigemptyset(&newaction.sa_mask);
	newaction.sa_flags = 0;

322
323
	sigaction(signum, NULL, origaction);
	sigaction(signum, &newaction, NULL);
324
325
}

326
static void unmask_signal(int signum, struct sigaction *sa)
327
{
328
	sigaction(signum, sa, NULL);
329
330
}

331
332
333
334
static FILE *create_tempfile(struct dload_payload *payload, const char *localpath)
{
	int fd;
	FILE *fp;
335
336
	char *randpath;
	size_t len;
337
338

	/* create a random filename, which is opened with O_EXCL */
339
340
341
	len = strlen(localpath) + 14 + 1;
	MALLOC(randpath, len, RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
	snprintf(randpath, len, "%salpmtmp.XXXXXX", localpath);
342
	if((fd = mkstemp(randpath)) == -1 ||
343
			fchmod(fd, ~(_getumask()) & 0666) ||
344
345
			!(fp = fdopen(fd, payload->tempfile_openmode))) {
		unlink(randpath);
346
		close(fd);
347
		_alpm_log(payload->handle, ALPM_LOG_ERROR,
348
				_("failed to create temporary file for download\n"));
349
		free(randpath);
350
351
352
		return NULL;
	}
	/* fp now points to our alpmtmp.XXXXXX */
353
354
355
356
	free(payload->tempfile_name);
	payload->tempfile_name = randpath;
	free(payload->remote_name);
	STRDUP(payload->remote_name, strrchr(randpath, '/') + 1,
357
			fclose(fp); RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
358
359
360
361

	return fp;
}

362
363
364
/* RFC1123 states applications should support this length */
#define HOSTNAME_SIZE 256

365
static int curl_download_internal(struct dload_payload *payload,
366
		const char *localpath, char **final_file, const char **final_url)
367
{
368
	int ret = -1;
369
	FILE *localf = NULL;
370
	char *effective_url;
371
	char hostname[HOSTNAME_SIZE];
372
	char error_buffer[CURL_ERROR_SIZE] = {0};
373
	struct stat st;
374
	long timecond, remote_time = -1;
375
	double remote_size, bytes_dl;
376
	struct sigaction orig_sig_pipe, orig_sig_int;
377
378
	/* shortcut to our handle within the payload */
	alpm_handle_t *handle = payload->handle;
379
	CURL *curl = get_libcurl_handle(handle);
Ivy Foster's avatar
Ivy Foster committed
380
	handle->pm_errno = ALPM_ERR_OK;
381

382
383
384
385
386
	/* make sure these are NULL */
	FREE(payload->tempfile_name);
	FREE(payload->destfile_name);
	FREE(payload->content_disp_name);

387
	payload->tempfile_openmode = "wb";
388
	if(!payload->remote_name) {
389
390
		STRDUP(payload->remote_name, get_filename(payload->fileurl),
				RET_ERR(handle, ALPM_ERR_MEMORY, -1));
391
	}
392
	if(curl_gethost(payload->fileurl, hostname, sizeof(hostname)) != 0) {
393
		_alpm_log(handle, ALPM_LOG_ERROR, _("url '%s' is invalid\n"), payload->fileurl);
394
		RET_ERR(handle, ALPM_ERR_SERVER_BAD_URL, -1);
395
396
	}

397
398
	if(payload->remote_name && strlen(payload->remote_name) > 0 &&
			strcmp(payload->remote_name, ".sig") != 0) {
399
400
401
		payload->destfile_name = get_fullpath(localpath, payload->remote_name, "");
		payload->tempfile_name = get_fullpath(localpath, payload->remote_name, ".part");
		if(!payload->destfile_name || !payload->tempfile_name) {
Dave Reisner's avatar
Dave Reisner committed
402
403
404
			goto cleanup;
		}
	} else {
405
406
		/* URL doesn't contain a filename, so make a tempfile. We can't support
		 * resuming this kind of download; partial transfers will be destroyed */
407
		payload->unlink_on_fail = 1;
Dave Reisner's avatar
Dave Reisner committed
408

409
410
		localf = create_tempfile(payload, localpath);
		if(localf == NULL) {
Dave Reisner's avatar
Dave Reisner committed
411
412
			goto cleanup;
		}
413
	}
414

415
	curl_set_handle_opts(payload, curl, error_buffer);
416

417
418
419
420
421
422
	if(payload->max_size == payload->initial_size) {
		/* .part file is complete */
		ret = 0;
		goto cleanup;
	}

423
	if(localf == NULL) {
424
		localf = fopen(payload->tempfile_name, payload->tempfile_openmode);
Dave Reisner's avatar
Dave Reisner committed
425
		if(localf == NULL) {
426
427
428
429
			handle->pm_errno = ALPM_ERR_RETRIEVE;
			_alpm_log(handle, ALPM_LOG_ERROR,
					_("could not open file %s: %s\n"),
					payload->tempfile_name, strerror(errno));
Dave Reisner's avatar
Dave Reisner committed
430
431
			goto cleanup;
		}
432
433
	}

Dave Reisner's avatar
Dave Reisner committed
434
435
436
437
	_alpm_log(handle, ALPM_LOG_DEBUG,
			"opened tempfile for download: %s (%s)\n", payload->tempfile_name,
			payload->tempfile_openmode);

438
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, localf);
439

440
441
	/* Ignore any SIGPIPE signals. With libcurl, these shouldn't be happening,
	 * but better safe than sorry. Store the old signal handler first. */
442
	mask_signal(SIGPIPE, SIG_IGN, &orig_sig_pipe);
443
	dload_interrupted = 0;
444
	mask_signal(SIGINT, &inthandler, &orig_sig_int);
445
446

	/* perform transfer */
447
	payload->curlerr = curl_easy_perform(curl);
448
449
	_alpm_log(handle, ALPM_LOG_DEBUG, "curl returned error %d from transfer\n",
			payload->curlerr);
450

451
	/* disconnect relationships from the curl handle for things that might go out
452
	 * of scope, but could still be touched on connection teardown. This really
Allan McRae's avatar
Allan McRae committed
453
	 * only applies to FTP transfers. */
454
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
455
	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, (char *)NULL);
456

457
	/* was it a success? */
458
	switch(payload->curlerr) {
459
		case CURLE_OK:
460
			/* get http/ftp response code */
461
462
			_alpm_log(handle, ALPM_LOG_DEBUG, "response code: %ld\n", payload->respcode);
			if(payload->respcode >= 400) {
463
				payload->unlink_on_fail = 1;
464
				if(!payload->errors_ok) {
465
					handle->pm_errno = ALPM_ERR_RETRIEVE;
466
467
468
469
470
471
472
					/* non-translated message is same as libcurl */
					snprintf(error_buffer, sizeof(error_buffer),
							"The requested URL returned error: %ld", payload->respcode);
					_alpm_log(handle, ALPM_LOG_ERROR,
							_("failed retrieving file '%s' from %s : %s\n"),
							payload->remote_name, hostname, error_buffer);
				}
473
474
				goto cleanup;
			}
475
476
			break;
		case CURLE_ABORTED_BY_CALLBACK:
477
478
			/* handle the interrupt accordingly */
			if(dload_interrupted == ABORT_OVER_MAXFILESIZE) {
479
				payload->curlerr = CURLE_FILESIZE_EXCEEDED;
480
				payload->unlink_on_fail = 1;
481
482
				handle->pm_errno = ALPM_ERR_LIBCURL;
				_alpm_log(handle, ALPM_LOG_ERROR,
483
484
						_("failed retrieving file '%s' from %s : expected download size exceeded\n"),
						payload->remote_name, hostname);
485
			}
486
			goto cleanup;
487
488
489
490
491
492
493
		case CURLE_COULDNT_RESOLVE_HOST:
			payload->unlink_on_fail = 1;
			handle->pm_errno = ALPM_ERR_SERVER_BAD_URL;
			_alpm_log(handle, ALPM_LOG_ERROR,
					_("failed retrieving file '%s' from %s : %s\n"),
					payload->remote_name, hostname, error_buffer);
			goto cleanup;
494
		default:
495
			/* delete zero length downloads */
Dan McGee's avatar
Dan McGee committed
496
			if(fstat(fileno(localf), &st) == 0 && st.st_size == 0) {
497
498
				payload->unlink_on_fail = 1;
			}
499
500
			if(!payload->errors_ok) {
				handle->pm_errno = ALPM_ERR_LIBCURL;
501
502
				_alpm_log(handle, ALPM_LOG_ERROR,
						_("failed retrieving file '%s' from %s : %s\n"),
503
						payload->remote_name, hostname, error_buffer);
504
			} else {
505
506
				_alpm_log(handle, ALPM_LOG_DEBUG,
						"failed retrieving file '%s' from %s : %s\n",
507
						payload->remote_name, hostname, error_buffer);
508
509
			}
			goto cleanup;
510
511
	}

512
	/* retrieve info about the state of the transfer */
513
514
515
516
517
	curl_easy_getinfo(curl, CURLINFO_FILETIME, &remote_time);
	curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &remote_size);
	curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &bytes_dl);
	curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &timecond);
	curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
518

519
520
521
522
	if(final_url != NULL) {
		*final_url = effective_url;
	}

523
524
	/* time condition was met and we didn't download anything. we need to
	 * clean up the 0 byte .part file that's left behind. */
525
	if(timecond == 1 && DOUBLE_EQ(bytes_dl, 0)) {
Dave Reisner's avatar
Dave Reisner committed
526
		_alpm_log(handle, ALPM_LOG_DEBUG, "file met time condition\n");
527
		ret = 1;
528
		unlink(payload->tempfile_name);
529
530
531
532
533
534
		goto cleanup;
	}

	/* remote_size isn't necessarily the full size of the file, just what the
	 * server reported as remaining to download. compare it to what curl reported
	 * as actually being transferred during curl_easy_perform() */
535
536
	if(!DOUBLE_EQ(remote_size, -1) && !DOUBLE_EQ(bytes_dl, -1) &&
			!DOUBLE_EQ(bytes_dl, remote_size)) {
537
		handle->pm_errno = ALPM_ERR_RETRIEVE;
538
		_alpm_log(handle, ALPM_LOG_ERROR, _("%s appears to be truncated: %jd/%jd bytes\n"),
539
				payload->remote_name, (intmax_t)bytes_dl, (intmax_t)remote_size);
540
541
542
		goto cleanup;
	}

543
	if(payload->trust_remote_name) {
544
545
546
		if(payload->content_disp_name) {
			/* content-disposition header has a better name for our file */
			free(payload->destfile_name);
547
548
			payload->destfile_name = get_fullpath(localpath,
				get_filename(payload->content_disp_name), "");
549
550
551
552
553
554
555
556
557
558
559
560
561
562
		} else {
			const char *effective_filename = strrchr(effective_url, '/');
			if(effective_filename && strlen(effective_filename) > 2) {
				effective_filename++;

				/* if destfile was never set, we wrote to a tempfile. even if destfile is
				 * set, we may have followed some redirects and the effective url may
				 * have a better suggestion as to what to name our file. in either case,
				 * refactor destfile to this newly derived name. */
				if(!payload->destfile_name || strcmp(effective_filename,
							strrchr(payload->destfile_name, '/') + 1) != 0) {
					free(payload->destfile_name);
					payload->destfile_name = get_fullpath(localpath, effective_filename, "");
				}
Dave Reisner's avatar
Dave Reisner committed
563
564
565
566
			}
		}
	}

567
568
569
570
571
	ret = 0;

cleanup:
	if(localf != NULL) {
		fclose(localf);
572
		utimes_long(payload->tempfile_name, remote_time);
573
574
	}

575
	if(ret == 0) {
576
577
578
579
		const char *realname = payload->tempfile_name;
		if(payload->destfile_name) {
			realname = payload->destfile_name;
			if(rename(payload->tempfile_name, payload->destfile_name)) {
580
				_alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
581
						payload->tempfile_name, payload->destfile_name, strerror(errno));
582
583
				ret = -1;
			}
Dave Reisner's avatar
Dave Reisner committed
584
585
586
		}
		if(ret != -1 && final_file) {
			STRDUP(*final_file, strrchr(realname, '/') + 1,
587
					RET_ERR(handle, ALPM_ERR_MEMORY, -1));
Dave Reisner's avatar
Dave Reisner committed
588
589
590
		}
	}

591
592
593
	if((ret == -1 || dload_interrupted) && payload->unlink_on_fail &&
			payload->tempfile_name) {
		unlink(payload->tempfile_name);
594
595
	}

596
	/* restore the old signal handlers */
597
598
	unmask_signal(SIGINT, &orig_sig_int);
	unmask_signal(SIGPIPE, &orig_sig_pipe);
599
	/* if we were interrupted, trip the old handler */
600
	if(dload_interrupted == ABORT_SIGINT) {
601
602
603
		raise(SIGINT);
	}

604
	return ret;
605
606
607
}
#endif

Kerrick Staley's avatar
Kerrick Staley committed
608
609
/** Download a file given by a URL to a local directory.
 * Does not overwrite an existing file if the download fails.
610
 * @param payload the payload context
Kerrick Staley's avatar
Kerrick Staley committed
611
 * @param localpath the directory to save the file in
Dave Reisner's avatar
Dave Reisner committed
612
 * @param final_file the real name of the downloaded file (may be NULL)
Kerrick Staley's avatar
Kerrick Staley committed
613
614
 * @return 0 on success, -1 on error (pm_errno is set accordingly if errors_ok == 0)
 */
615
int _alpm_download(struct dload_payload *payload, const char *localpath,
616
		char **final_file, const char **final_url)
617
{
618
619
	alpm_handle_t *handle = payload->handle;

620
	if(handle->fetchcb == NULL) {
Dave Reisner's avatar
Dave Reisner committed
621
#ifdef HAVE_LIBCURL
622
		return curl_download_internal(payload, localpath, final_file, final_url);
623
#else
624
625
626
		/* work around unused warnings when building without libcurl */
		(void)final_file;
		(void)final_url;
627
		RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
628
#endif
629
	} else {
630
631
		int ret = handle->fetchcb(payload->fileurl, localpath, payload->force);
		if(ret == -1 && !payload->errors_ok) {
632
			RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
633
		}
634
		return ret;
635
636
	}
}
637

638
639
static char *filecache_find_url(alpm_handle_t *handle, const char *url)
{
640
	const char *filebase = strrchr(url, '/');
641

642
	if(filebase == NULL) {
643
644
645
		return NULL;
	}

646
	filebase++;
647
	if(*filebase == '\0') {
648
649
650
		return NULL;
	}

651
	return _alpm_filecache_find(handle, filebase);
652
653
}

654
/** Fetch a remote pkg. */
655
char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
Dan McGee's avatar
Dan McGee committed
656
{
657
	char *filepath;
658
659
	const char *cachedir, *final_pkg_url = NULL;
	char *final_file = NULL;
660
	struct dload_payload payload;
661
	int ret = 0;
Dan McGee's avatar
Dan McGee committed
662

663
	CHECK_HANDLE(handle, return NULL);
664
	ASSERT(url, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, NULL));
665

Dan McGee's avatar
Dan McGee committed
666
	/* find a valid cache dir to download to */
667
	cachedir = _alpm_filecache_setup(handle);
Dan McGee's avatar
Dan McGee committed
668

669
	memset(&payload, 0, sizeof(struct dload_payload));
670
671
672
673
674
675
676

	/* attempt to find the file in our pkgcache */
	filepath = filecache_find_url(handle, url);
	if(filepath == NULL) {
		STRDUP(payload.fileurl, url, RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
		payload.allow_resume = 1;
		payload.handle = handle;
677
		payload.trust_remote_name = 1;
678
679

		/* download the file */
680
		ret = _alpm_download(&payload, cachedir, &final_file, &final_pkg_url);
681
682
683
684
685
686
687
		_alpm_dload_payload_reset(&payload);
		if(ret == -1) {
			_alpm_log(handle, ALPM_LOG_WARNING, _("failed to download %s\n"), url);
			free(final_file);
			return NULL;
		}
		_alpm_log(handle, ALPM_LOG_DEBUG, "successfully downloaded %s\n", url);
Dan McGee's avatar
Dan McGee committed
688
689
	}

690
	/* attempt to download the signature */
691
	if(ret == 0 && final_pkg_url && (handle->siglevel & ALPM_SIG_PACKAGE)) {
692
		char *sig_filepath, *sig_final_file = NULL;
693
694
		size_t len;

695
		len = strlen(final_pkg_url) + 5;
696
		MALLOC(payload.fileurl, len, free(final_file); RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
697
		snprintf(payload.fileurl, len, "%s.sig", final_pkg_url);
698
699
700
701

		sig_filepath = filecache_find_url(handle, payload.fileurl);
		if(sig_filepath == NULL) {
			payload.handle = handle;
702
			payload.trust_remote_name = 1;
703
704
705
706
707
708
			payload.force = 1;
			payload.errors_ok = (handle->siglevel & ALPM_SIG_PACKAGE_OPTIONAL);

			/* set hard upper limit of 16KiB */
			payload.max_size = 16 * 1024;

709
			ret = _alpm_download(&payload, cachedir, &sig_final_file, NULL);
710
711
712
713
714
715
716
717
718
719
			if(ret == -1 && !payload.errors_ok) {
				_alpm_log(handle, ALPM_LOG_WARNING,
						_("failed to download %s\n"), payload.fileurl);
				/* Warn now, but don't return NULL. We will fail later during package
				 * load time. */
			} else if(ret == 0) {
				_alpm_log(handle, ALPM_LOG_DEBUG,
						"successfully downloaded %s\n", payload.fileurl);
			}
			FREE(sig_final_file);
720
		}
721
		free(sig_filepath);
722
		_alpm_dload_payload_reset(&payload);
723
724
	}

Dan McGee's avatar
Dan McGee committed
725
	/* we should be able to find the file the second time around */
726
727
728
	if(filepath == NULL) {
		filepath = _alpm_filecache_find(handle, final_file);
	}
729
	free(final_file);
Dave Reisner's avatar
Dave Reisner committed
730

731
	return filepath;
Dan McGee's avatar
Dan McGee committed
732
733
}

734
735
void _alpm_dload_payload_reset(struct dload_payload *payload)
{
736
	ASSERT(payload, return);
737

738
	FREE(payload->remote_name);
739
740
	FREE(payload->tempfile_name);
	FREE(payload->destfile_name);
741
742
	FREE(payload->content_disp_name);
	FREE(payload->fileurl);
743
	memset(payload, '\0', sizeof(*payload));
744
745
}

746
747
748
749
750
751
752
753
void _alpm_dload_payload_reset_for_retry(struct dload_payload *payload)
{
	ASSERT(payload, return);

	FREE(payload->fileurl);
	payload->initial_size += payload->prevprogress;
	payload->prevprogress = 0;
	payload->unlink_on_fail = 0;
754
	payload->cb_initialized = 0;
755
}