dload.c 22.4 KB
Newer Older
Dan McGee's avatar
Dan McGee committed
1
2
3
/*
 *  download.c
 *
Allan McRae's avatar
Allan McRae committed
4
 *  Copyright (c) 2006-2014 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
56
57
	char *filename = strrchr(url, '/');
	if(filename != NULL) {
		filename++;
	}
58
	return filename;
59
}
60

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

70
	return filepath;
71
72
}

73
74
75
76
77
78
79
80
81
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;
}

82
83
84
85
86
enum {
	ABORT_SIGINT = 1,
	ABORT_OVER_MAXFILESIZE
};

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

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

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

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

109
110
111
112
	current_size = payload->initial_size + (off_t)dlnow;

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

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

122
	total_size = payload->initial_size + (off_t)dltotal;
123

124
	if(DOUBLE_EQ(dltotal, 0.0) || payload->prevprogress == total_size) {
125
		return 0;
126
127
	}

128
129
	/* initialize the progress bar here to avoid displaying it when
	 * a repo is up to date and nothing gets downloaded */
130
	if(payload->prevprogress == 0) {
131
		payload->handle->dlcb(payload->remote_name, 0, (off_t)dltotal);
132
133
	}

134
135
136
	/* do NOT include initial_size since it wasn't part of the package's
	 * download_size (nor included in the total download size callback) */
	payload->handle->dlcb(payload->remote_name, (off_t)dlnow, (off_t)dltotal);
137

138
	payload->prevprogress = current_size;
139

140
	return 0;
141
142
}

143
static int curl_gethost(const char *url, char *buffer, size_t buf_len)
144
{
145
	size_t hostlen;
146
	char *p, *q;
147
148

	if(strncmp(url, "file://", 7) == 0) {
149
150
		p = _("disk");
		hostlen = strlen(p);
151
152
153
	} else {
		p = strstr(url, "//");
		if(!p) {
154
			return 1;
155
156
157
		}
		p += 2; /* jump over the found // */
		hostlen = strcspn(p, "/");
158

Dave Reisner's avatar
Dave Reisner committed
159
160
161
162
163
164
165
166
167
		/* 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) {
168
169
170
			hostlen -= q - p + 1;
			p = q + 1;
		}
171
	}
172

173
174
175
	if(hostlen > buf_len - 1) {
		/* buffer overflow imminent */
		return 1;
176
	}
177
178
	memcpy(buffer, p, hostlen);
	buffer[hostlen] = '\0';
179

180
	return 0;
181
182
}

Dan McGee's avatar
Dan McGee committed
183
static int utimes_long(const char *path, long seconds)
184
{
Dan McGee's avatar
Dan McGee committed
185
	if(seconds != -1) {
186
187
		struct timeval tv[2];
		memset(&tv, 0, sizeof(tv));
Dan McGee's avatar
Dan McGee committed
188
		tv[0].tv_sec = tv[1].tv_sec = seconds;
189
190
191
192
193
		return utimes(path, tv);
	}
	return 0;
}

194
195
196
197
198
199
200
201
/* prefix to avoid possible future clash with getumask(3) */
static mode_t _getumask(void)
{
	mode_t mask = umask(0);
	umask(mask);
	return mask;
}

202
static size_t dload_parseheader_cb(void *ptr, size_t size, size_t nmemb, void *user)
Dave Reisner's avatar
Dave Reisner committed
203
204
205
206
207
{
	size_t realsize = size * nmemb;
	const char *fptr, *endptr = NULL;
	const char * const cd_header = "Content-Disposition:";
	const char * const fn_key = "filename=";
208
	struct dload_payload *payload = (struct dload_payload *)user;
209
	long respcode;
Dave Reisner's avatar
Dave Reisner committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

	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--;
			}

226
			STRNDUP(payload->content_disp_name, fptr, endptr - fptr + 1,
227
					RET_ERR(payload->handle, ALPM_ERR_MEMORY, realsize));
Dave Reisner's avatar
Dave Reisner committed
228
229
		}
	}
230

231
232
233
234
235
	curl_easy_getinfo(payload->handle->curl, CURLINFO_RESPONSE_CODE, &respcode);
	if(payload->respcode != respcode) {
		payload->respcode = respcode;
	}

Dave Reisner's avatar
Dave Reisner committed
236
237
238
	return realsize;
}

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
static int dload_sockopt_cb(void *userdata, curl_socket_t curlfd,
		curlsocktype purpose)
{
	alpm_handle_t *handle = userdata;
	int optval = 1;

	/* this whole method is to prevent FTP control connections from going sour
	 * during a long data transfer; crappy firewalls love to drop otherwise idle
	 * connections if there is no traffic. */
	if(purpose != CURLSOCKTYPE_IPCXN) {
		return 0;
	}

	/* don't abort operation if any setsockopt fails, just log to debug */
	if(setsockopt(curlfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&optval,
				sizeof(optval)) < 0) {
		_alpm_log(handle, ALPM_LOG_DEBUG,
				"Failed to set SO_KEEPALIVE on fd %d\n", curlfd);
	}
	else {
#ifdef TCP_KEEPIDLE
		optval = 60;
		if(setsockopt(curlfd, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&optval,
					sizeof(optval)) < 0) {
			_alpm_log(handle, ALPM_LOG_DEBUG,
					"Failed to set TCP_KEEPIDLE on fd %d\n", curlfd);
		}
#endif
#ifdef TCP_KEEPINTVL
		optval = 60;
		if(setsockopt(curlfd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&optval,
					sizeof(optval)) < 0) {
			_alpm_log(handle, ALPM_LOG_DEBUG,
					"Failed to set TCP_KEEPINTVL on fd %d\n", curlfd);
		}
#endif
	}

	return 0;
}

280
static void curl_set_handle_opts(struct dload_payload *payload,
281
		CURL *curl, char *error_buffer)
282
283
284
285
286
287
{
	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
288
	 * to reset the handle's parameters for each time it's used. */
289
290
291
292
293
294
295
	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);
	curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
296
	curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, dload_progress_cb);
297
	curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, (void *)payload);
298
	curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
299
	curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 10L);
300
	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, dload_parseheader_cb);
301
302
	curl_easy_setopt(curl, CURLOPT_WRITEHEADER, (void *)payload);
	curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
303
304
	curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, dload_sockopt_cb);
	curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA, (void *)handle);
305
	curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
306

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

309
	if(payload->max_size) {
310
311
		_alpm_log(handle, ALPM_LOG_DEBUG, "maxsize: %jd\n",
				(intmax_t)payload->max_size);
312
		curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE,
313
				(curl_off_t)payload->max_size);
314
315
316
	}

	if(useragent != NULL) {
317
		curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
318
319
320
321
322
	}

	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. */
323
324
		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
325
326
		_alpm_log(handle, ALPM_LOG_DEBUG,
				"using time condition: %lu\n", (long)st.st_mtime);
327
328
329
	} 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";
330
		curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)st.st_size);
Dave Reisner's avatar
Dave Reisner committed
331
		_alpm_log(handle, ALPM_LOG_DEBUG,
332
333
				"tempfile found, attempting continuation from %jd bytes\n",
				(intmax_t)st.st_size);
334
		payload->initial_size = st.st_size;
335
336
337
	}
}

338
static void mask_signal(int signum, void (*handler)(int),
339
		struct sigaction *origaction)
340
{
341
	struct sigaction newaction;
342
343
344
345
346

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

347
348
	sigaction(signum, NULL, origaction);
	sigaction(signum, &newaction, NULL);
349
350
}

351
static void unmask_signal(int signum, struct sigaction *sa)
352
{
353
	sigaction(signum, sa, NULL);
354
355
}

356
357
358
359
static FILE *create_tempfile(struct dload_payload *payload, const char *localpath)
{
	int fd;
	FILE *fp;
360
361
	char *randpath;
	size_t len;
362
363

	/* create a random filename, which is opened with O_EXCL */
364
365
366
	len = strlen(localpath) + 14 + 1;
	MALLOC(randpath, len, RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
	snprintf(randpath, len, "%salpmtmp.XXXXXX", localpath);
367
	if((fd = mkstemp(randpath)) == -1 ||
368
			fchmod(fd, ~(_getumask()) & 0666) ||
369
370
			!(fp = fdopen(fd, payload->tempfile_openmode))) {
		unlink(randpath);
371
		close(fd);
372
		_alpm_log(payload->handle, ALPM_LOG_ERROR,
373
				_("failed to create temporary file for download\n"));
374
		free(randpath);
375
376
377
		return NULL;
	}
	/* fp now points to our alpmtmp.XXXXXX */
378
379
380
381
382
	free(payload->tempfile_name);
	payload->tempfile_name = randpath;
	free(payload->remote_name);
	STRDUP(payload->remote_name, strrchr(randpath, '/') + 1,
			RET_ERR(payload->handle, ALPM_ERR_MEMORY, NULL));
383
384
385
386

	return fp;
}

387
388
389
/* RFC1123 states applications should support this length */
#define HOSTNAME_SIZE 256

390
static int curl_download_internal(struct dload_payload *payload,
391
		const char *localpath, char **final_file, const char **final_url)
392
{
393
	int ret = -1;
394
	FILE *localf = NULL;
395
	char *effective_url;
396
	char hostname[HOSTNAME_SIZE];
397
	char error_buffer[CURL_ERROR_SIZE] = {0};
398
	struct stat st;
399
	long timecond, remote_time = -1;
400
	double remote_size, bytes_dl;
401
	struct sigaction orig_sig_pipe, orig_sig_int;
402
403
	/* shortcut to our handle within the payload */
	alpm_handle_t *handle = payload->handle;
404
	CURL *curl = get_libcurl_handle(handle);
405
	handle->pm_errno = 0;
406

407
408
409
410
411
	/* make sure these are NULL */
	FREE(payload->tempfile_name);
	FREE(payload->destfile_name);
	FREE(payload->content_disp_name);

412
	payload->tempfile_openmode = "wb";
413
	if(!payload->remote_name) {
414
415
		STRDUP(payload->remote_name, get_filename(payload->fileurl),
				RET_ERR(handle, ALPM_ERR_MEMORY, -1));
416
	}
417
	if(curl_gethost(payload->fileurl, hostname, sizeof(hostname)) != 0) {
418
		_alpm_log(handle, ALPM_LOG_ERROR, _("url '%s' is invalid\n"), payload->fileurl);
419
		RET_ERR(handle, ALPM_ERR_SERVER_BAD_URL, -1);
420
421
	}

422
423
	if(payload->remote_name && strlen(payload->remote_name) > 0 &&
			strcmp(payload->remote_name, ".sig") != 0) {
424
425
426
		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
427
428
429
			goto cleanup;
		}
	} else {
430
431
		/* URL doesn't contain a filename, so make a tempfile. We can't support
		 * resuming this kind of download; partial transfers will be destroyed */
432
		payload->unlink_on_fail = 1;
Dave Reisner's avatar
Dave Reisner committed
433

434
435
		localf = create_tempfile(payload, localpath);
		if(localf == NULL) {
Dave Reisner's avatar
Dave Reisner committed
436
437
			goto cleanup;
		}
438
	}
439

440
	curl_set_handle_opts(payload, curl, error_buffer);
441
442

	if(localf == NULL) {
443
		localf = fopen(payload->tempfile_name, payload->tempfile_openmode);
Dave Reisner's avatar
Dave Reisner committed
444
		if(localf == NULL) {
445
446
447
448
			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
449
450
			goto cleanup;
		}
451
452
	}

Dave Reisner's avatar
Dave Reisner committed
453
454
455
456
	_alpm_log(handle, ALPM_LOG_DEBUG,
			"opened tempfile for download: %s (%s)\n", payload->tempfile_name,
			payload->tempfile_openmode);

457
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, localf);
458

459
460
	/* Ignore any SIGPIPE signals. With libcurl, these shouldn't be happening,
	 * but better safe than sorry. Store the old signal handler first. */
461
462
	mask_signal(SIGPIPE, SIG_IGN, &orig_sig_pipe);
	mask_signal(SIGINT, &inthandler, &orig_sig_int);
463
464

	/* perform transfer */
465
	payload->curlerr = curl_easy_perform(curl);
466
467
	_alpm_log(handle, ALPM_LOG_DEBUG, "curl returned error %d from transfer\n",
			payload->curlerr);
468

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

475
	/* was it a success? */
476
	switch(payload->curlerr) {
477
		case CURLE_OK:
478
			/* get http/ftp response code */
479
480
			_alpm_log(handle, ALPM_LOG_DEBUG, "response code: %ld\n", payload->respcode);
			if(payload->respcode >= 400) {
481
				payload->unlink_on_fail = 1;
482
483
484
485
486
487
488
489
				if(!payload->errors_ok) {
					/* 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);
				}
490
491
				goto cleanup;
			}
492
493
			break;
		case CURLE_ABORTED_BY_CALLBACK:
494
495
			/* handle the interrupt accordingly */
			if(dload_interrupted == ABORT_OVER_MAXFILESIZE) {
496
				payload->curlerr = CURLE_FILESIZE_EXCEEDED;
497
				payload->unlink_on_fail = 1;
498
499
				handle->pm_errno = ALPM_ERR_LIBCURL;
				_alpm_log(handle, ALPM_LOG_ERROR,
500
501
						_("failed retrieving file '%s' from %s : expected download size exceeded\n"),
						payload->remote_name, hostname);
502
			}
503
504
			goto cleanup;
		default:
505
			/* delete zero length downloads */
Dan McGee's avatar
Dan McGee committed
506
			if(fstat(fileno(localf), &st) == 0 && st.st_size == 0) {
507
508
				payload->unlink_on_fail = 1;
			}
509
510
			if(!payload->errors_ok) {
				handle->pm_errno = ALPM_ERR_LIBCURL;
511
512
				_alpm_log(handle, ALPM_LOG_ERROR,
						_("failed retrieving file '%s' from %s : %s\n"),
513
						payload->remote_name, hostname, error_buffer);
514
			} else {
515
516
				_alpm_log(handle, ALPM_LOG_DEBUG,
						"failed retrieving file '%s' from %s : %s\n",
517
						payload->remote_name, hostname, error_buffer);
518
519
			}
			goto cleanup;
520
521
	}

522
	/* retrieve info about the state of the transfer */
523
524
525
526
527
	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);
528

529
530
531
532
	if(final_url != NULL) {
		*final_url = effective_url;
	}

533
534
	/* time condition was met and we didn't download anything. we need to
	 * clean up the 0 byte .part file that's left behind. */
535
	if(timecond == 1 && DOUBLE_EQ(bytes_dl, 0)) {
Dave Reisner's avatar
Dave Reisner committed
536
		_alpm_log(handle, ALPM_LOG_DEBUG, "file met time condition\n");
537
		ret = 1;
538
		unlink(payload->tempfile_name);
539
540
541
542
543
544
		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() */
545
546
	if(!DOUBLE_EQ(remote_size, -1) && !DOUBLE_EQ(bytes_dl, -1) &&
			!DOUBLE_EQ(bytes_dl, remote_size)) {
547
		handle->pm_errno = ALPM_ERR_RETRIEVE;
548
		_alpm_log(handle, ALPM_LOG_ERROR, _("%s appears to be truncated: %jd/%jd bytes\n"),
549
				payload->remote_name, (intmax_t)bytes_dl, (intmax_t)remote_size);
550
551
552
		goto cleanup;
	}

553
	if(payload->trust_remote_name) {
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
		if(payload->content_disp_name) {
			/* content-disposition header has a better name for our file */
			free(payload->destfile_name);
			payload->destfile_name = get_fullpath(localpath, payload->content_disp_name, "");
		} 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
572
573
574
575
			}
		}
	}

576
577
578
579
580
	ret = 0;

cleanup:
	if(localf != NULL) {
		fclose(localf);
581
		utimes_long(payload->tempfile_name, remote_time);
582
583
	}

584
	if(ret == 0) {
585
586
587
588
		const char *realname = payload->tempfile_name;
		if(payload->destfile_name) {
			realname = payload->destfile_name;
			if(rename(payload->tempfile_name, payload->destfile_name)) {
589
				_alpm_log(handle, ALPM_LOG_ERROR, _("could not rename %s to %s (%s)\n"),
590
						payload->tempfile_name, payload->destfile_name, strerror(errno));
591
592
				ret = -1;
			}
Dave Reisner's avatar
Dave Reisner committed
593
594
595
		}
		if(ret != -1 && final_file) {
			STRDUP(*final_file, strrchr(realname, '/') + 1,
596
					RET_ERR(handle, ALPM_ERR_MEMORY, -1));
Dave Reisner's avatar
Dave Reisner committed
597
598
599
		}
	}

600
601
602
	if((ret == -1 || dload_interrupted) && payload->unlink_on_fail &&
			payload->tempfile_name) {
		unlink(payload->tempfile_name);
603
604
	}

605
	/* restore the old signal handlers */
606
607
	unmask_signal(SIGINT, &orig_sig_int);
	unmask_signal(SIGPIPE, &orig_sig_pipe);
608
609
610
611
612
	/* if we were interrupted, trip the old handler */
	if(dload_interrupted) {
		raise(SIGINT);
	}

613
	return ret;
614
615
616
}
#endif

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

629
	if(handle->fetchcb == NULL) {
Dave Reisner's avatar
Dave Reisner committed
630
#ifdef HAVE_LIBCURL
631
		return curl_download_internal(payload, localpath, final_file, final_url);
632
#else
633
634
635
		/* work around unused warnings when building without libcurl */
		(void)final_file;
		(void)final_url;
636
		RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
637
#endif
638
	} else {
639
640
		int ret = handle->fetchcb(payload->fileurl, localpath, payload->force);
		if(ret == -1 && !payload->errors_ok) {
641
			RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
642
		}
643
		return ret;
644
645
	}
}
646

647
648
static char *filecache_find_url(alpm_handle_t *handle, const char *url)
{
649
	const char *filebase = strrchr(url, '/');
650

651
	if(filebase == NULL) {
652
653
654
		return NULL;
	}

655
	filebase++;
656
	if(*filebase == '\0') {
657
658
659
		return NULL;
	}

660
	return _alpm_filecache_find(handle, filebase);
661
662
}

663
/** Fetch a remote pkg. */
664
char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
Dan McGee's avatar
Dan McGee committed
665
{
666
	char *filepath;
667
668
	const char *cachedir, *final_pkg_url = NULL;
	char *final_file = NULL;
669
	struct dload_payload payload;
670
	int ret = 0;
Dan McGee's avatar
Dan McGee committed
671

672
	CHECK_HANDLE(handle, return NULL);
673
	ASSERT(url, RET_ERR(handle, ALPM_ERR_WRONG_ARGS, NULL));
674

Dan McGee's avatar
Dan McGee committed
675
	/* find a valid cache dir to download to */
676
	cachedir = _alpm_filecache_setup(handle);
Dan McGee's avatar
Dan McGee committed
677

678
	memset(&payload, 0, sizeof(struct dload_payload));
679
680
681
682
683
684
685

	/* 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;
686
		payload.trust_remote_name = 1;
687
688

		/* download the file */
689
		ret = _alpm_download(&payload, cachedir, &final_file, &final_pkg_url);
690
691
692
693
694
695
696
		_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
697
698
	}

699
	/* attempt to download the signature */
700
	if(ret == 0 && final_pkg_url && (handle->siglevel & ALPM_SIG_PACKAGE)) {
701
		char *sig_filepath, *sig_final_file = NULL;
702
703
		size_t len;

704
		len = strlen(final_pkg_url) + 5;
705
		MALLOC(payload.fileurl, len, RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
706
		snprintf(payload.fileurl, len, "%s.sig", final_pkg_url);
707
708
709
710

		sig_filepath = filecache_find_url(handle, payload.fileurl);
		if(sig_filepath == NULL) {
			payload.handle = handle;
711
			payload.trust_remote_name = 1;
712
713
714
715
716
717
			payload.force = 1;
			payload.errors_ok = (handle->siglevel & ALPM_SIG_PACKAGE_OPTIONAL);

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

718
			ret = _alpm_download(&payload, cachedir, &sig_final_file, NULL);
719
720
721
722
723
724
725
726
727
728
			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);
729
		}
730
		free(sig_filepath);
731
		_alpm_dload_payload_reset(&payload);
732
733
	}

Dan McGee's avatar
Dan McGee committed
734
	/* we should be able to find the file the second time around */
735
736
737
	if(filepath == NULL) {
		filepath = _alpm_filecache_find(handle, final_file);
	}
738
	free(final_file);
Dave Reisner's avatar
Dave Reisner committed
739

740
	return filepath;
Dan McGee's avatar
Dan McGee committed
741
742
}

743
744
void _alpm_dload_payload_reset(struct dload_payload *payload)
{
745
	ASSERT(payload, return);
746

747
	FREE(payload->remote_name);
748
749
	FREE(payload->tempfile_name);
	FREE(payload->destfile_name);
750
751
	FREE(payload->content_disp_name);
	FREE(payload->fileurl);
752
	memset(payload, '\0', sizeof(*payload));
753
754
}

755
/* vim: set noet: */