4 * Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
5 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 #include <sys/types.h>
34 #include <curl/curl.h>
39 #include "alpm_list.h"
46 static const char *get_filename(const char *url
)
48 char *filename
= strrchr(url
, '/');
49 if(filename
!= NULL
) {
55 static char *get_fullpath(const char *path
, const char *filename
,
59 /* len = localpath len + filename len + suffix len + null */
60 size_t len
= strlen(path
) + strlen(filename
) + strlen(suffix
) + 1;
61 MALLOC(filepath
, len
, return NULL
);
62 snprintf(filepath
, len
, "%s%s%s", path
, filename
, suffix
);
67 static CURL
*get_libcurl_handle(alpm_handle_t
*handle
)
70 curl_global_init(CURL_GLOBAL_SSL
);
71 handle
->curl
= curl_easy_init();
78 ABORT_OVER_MAXFILESIZE
81 static int dload_interrupted
;
82 static void inthandler(int UNUSED signum
)
84 dload_interrupted
= ABORT_SIGINT
;
87 static int curl_progress(void *file
, double dltotal
, double dlnow
,
88 double UNUSED ultotal
, double UNUSED ulnow
)
90 struct dload_payload
*payload
= (struct dload_payload
*)file
;
91 off_t current_size
, total_size
;
93 /* SIGINT sent, abort by alerting curl */
94 if(dload_interrupted
) {
98 current_size
= payload
->initial_size
+ (off_t
)dlnow
;
100 /* is our filesize still under any set limit? */
101 if(payload
->max_size
&& current_size
> payload
->max_size
) {
102 dload_interrupted
= ABORT_OVER_MAXFILESIZE
;
106 /* none of what follows matters if the front end has no callback */
107 if(payload
->handle
->dlcb
== NULL
) {
111 total_size
= payload
->initial_size
+ (off_t
)dltotal
;
113 if(DOUBLE_EQ(dltotal
, 0.0) || payload
->prevprogress
== total_size
) {
117 /* initialize the progress bar here to avoid displaying it when
118 * a repo is up to date and nothing gets downloaded */
119 if(payload
->prevprogress
== 0) {
120 payload
->handle
->dlcb(payload
->remote_name
, 0, (off_t
)dltotal
);
123 payload
->handle
->dlcb(payload
->remote_name
, current_size
, total_size
);
125 payload
->prevprogress
= current_size
;
130 static int curl_gethost(const char *url
, char *buffer
, size_t buf_len
)
135 if(strncmp(url
, "file://", 7) == 0) {
139 p
= strstr(url
, "//");
143 p
+= 2; /* jump over the found // */
144 hostlen
= strcspn(p
, "/");
146 /* there might be a user:pass@ on the URL. hide it. avoid using memrchr()
147 * for portability concerns. */
154 if(*q
== '@' && p
!= q
) {
155 hostlen
-= q
- p
+ 1;
160 if(hostlen
> buf_len
- 1) {
161 /* buffer overflow imminent */
164 memcpy(buffer
, p
, hostlen
);
165 buffer
[hostlen
] = '\0';
170 static int utimes_long(const char *path
, long seconds
)
173 struct timeval tv
[2];
174 memset(&tv
, 0, sizeof(tv
));
175 tv
[0].tv_sec
= tv
[1].tv_sec
= seconds
;
176 return utimes(path
, tv
);
181 /* prefix to avoid possible future clash with getumask(3) */
182 static mode_t
_getumask(void)
184 mode_t mask
= umask(0);
189 static size_t parse_headers(void *ptr
, size_t size
, size_t nmemb
, void *user
)
191 size_t realsize
= size
* nmemb
;
192 const char *fptr
, *endptr
= NULL
;
193 const char * const cd_header
= "Content-Disposition:";
194 const char * const fn_key
= "filename=";
195 struct dload_payload
*payload
= (struct dload_payload
*)user
;
197 if(_alpm_raw_ncmp(cd_header
, ptr
, strlen(cd_header
)) == 0) {
198 if((fptr
= strstr(ptr
, fn_key
))) {
199 fptr
+= strlen(fn_key
);
201 /* find the end of the field, which is either a semi-colon, or the end of
202 * the data. As per curl_easy_setopt(3), we cannot count on headers being
203 * null terminated, so we look for the closing \r\n */
204 endptr
= fptr
+ strcspn(fptr
, ";\r\n") - 1;
207 if(*fptr
== '"' && *endptr
== '"') {
212 STRNDUP(payload
->content_disp_name
, fptr
, endptr
- fptr
+ 1,
213 RET_ERR(payload
->handle
, ALPM_ERR_MEMORY
, realsize
));
220 static void curl_set_handle_opts(struct dload_payload
*payload
,
221 CURL
*curl
, char *error_buffer
)
223 alpm_handle_t
*handle
= payload
->handle
;
224 const char *useragent
= getenv("HTTP_USER_AGENT");
227 /* the curl_easy handle is initialized with the alpm handle, so we only need
228 * to reset the handle's parameters for each time it's used. */
229 curl_easy_reset(curl
);
230 curl_easy_setopt(curl
, CURLOPT_URL
, payload
->fileurl
);
231 curl_easy_setopt(curl
, CURLOPT_FAILONERROR
, 1L);
232 curl_easy_setopt(curl
, CURLOPT_ERRORBUFFER
, error_buffer
);
233 curl_easy_setopt(curl
, CURLOPT_CONNECTTIMEOUT
, 10L);
234 curl_easy_setopt(curl
, CURLOPT_FILETIME
, 1L);
235 curl_easy_setopt(curl
, CURLOPT_NOPROGRESS
, 0L);
236 curl_easy_setopt(curl
, CURLOPT_FOLLOWLOCATION
, 1L);
237 curl_easy_setopt(curl
, CURLOPT_PROGRESSFUNCTION
, curl_progress
);
238 curl_easy_setopt(curl
, CURLOPT_PROGRESSDATA
, (void *)payload
);
239 curl_easy_setopt(curl
, CURLOPT_LOW_SPEED_LIMIT
, 1024L);
240 curl_easy_setopt(curl
, CURLOPT_LOW_SPEED_TIME
, 10L);
241 curl_easy_setopt(curl
, CURLOPT_HEADERFUNCTION
, parse_headers
);
242 curl_easy_setopt(curl
, CURLOPT_WRITEHEADER
, (void *)payload
);
243 curl_easy_setopt(curl
, CURLOPT_NETRC
, CURL_NETRC_OPTIONAL
);
245 _alpm_log(handle
, ALPM_LOG_DEBUG
, "url: %s\n", payload
->fileurl
);
247 if(payload
->max_size
) {
248 _alpm_log(handle
, ALPM_LOG_DEBUG
, "maxsize: %jd\n",
249 (intmax_t)payload
->max_size
);
250 curl_easy_setopt(curl
, CURLOPT_MAXFILESIZE_LARGE
,
251 (curl_off_t
)payload
->max_size
);
254 if(useragent
!= NULL
) {
255 curl_easy_setopt(curl
, CURLOPT_USERAGENT
, useragent
);
258 if(!payload
->allow_resume
&& !payload
->force
&& payload
->destfile_name
&&
259 stat(payload
->destfile_name
, &st
) == 0) {
260 /* start from scratch, but only download if our local is out of date. */
261 curl_easy_setopt(curl
, CURLOPT_TIMECONDITION
, CURL_TIMECOND_IFMODSINCE
);
262 curl_easy_setopt(curl
, CURLOPT_TIMEVALUE
, (long)st
.st_mtime
);
263 _alpm_log(handle
, ALPM_LOG_DEBUG
,
264 "using time condition: %lu\n", (long)st
.st_mtime
);
265 } else if(stat(payload
->tempfile_name
, &st
) == 0 && payload
->allow_resume
) {
266 /* a previous partial download exists, resume from end of file. */
267 payload
->tempfile_openmode
= "ab";
268 curl_easy_setopt(curl
, CURLOPT_RESUME_FROM_LARGE
, (curl_off_t
)st
.st_size
);
269 _alpm_log(handle
, ALPM_LOG_DEBUG
,
270 "tempfile found, attempting continuation from %jd bytes\n",
271 (intmax_t)st
.st_size
);
272 payload
->initial_size
= st
.st_size
;
276 static void mask_signal(int signal
, void (*handler
)(int),
277 struct sigaction
*origaction
)
279 struct sigaction newaction
;
281 newaction
.sa_handler
= handler
;
282 sigemptyset(&newaction
.sa_mask
);
283 newaction
.sa_flags
= 0;
285 sigaction(signal
, NULL
, origaction
);
286 sigaction(signal
, &newaction
, NULL
);
289 static void unmask_signal(int signal
, struct sigaction sa
)
291 sigaction(signal
, &sa
, NULL
);
294 static FILE *create_tempfile(struct dload_payload
*payload
, const char *localpath
)
301 /* create a random filename, which is opened with O_EXCL */
302 len
= strlen(localpath
) + 14 + 1;
303 MALLOC(randpath
, len
, RET_ERR(payload
->handle
, ALPM_ERR_MEMORY
, NULL
));
304 snprintf(randpath
, len
, "%salpmtmp.XXXXXX", localpath
);
305 if((fd
= mkstemp(randpath
)) == -1 ||
306 fchmod(fd
, ~(_getumask()) & 0666) ||
307 !(fp
= fdopen(fd
, payload
->tempfile_openmode
))) {
310 _alpm_log(payload
->handle
, ALPM_LOG_ERROR
,
311 _("failed to create temporary file for download\n"));
314 /* fp now points to our alpmtmp.XXXXXX */
315 free(payload
->tempfile_name
);
316 payload
->tempfile_name
= randpath
;
317 free(payload
->remote_name
);
318 STRDUP(payload
->remote_name
, strrchr(randpath
, '/') + 1,
319 RET_ERR(payload
->handle
, ALPM_ERR_MEMORY
, NULL
));
324 /* RFC1123 states applications should support this length */
325 #define HOSTNAME_SIZE 256
327 static int curl_download_internal(struct dload_payload
*payload
,
328 const char *localpath
, char **final_file
)
333 char hostname
[HOSTNAME_SIZE
];
334 char error_buffer
[CURL_ERROR_SIZE
] = {0};
336 long timecond
, respcode
= 0, remote_time
= -1;
337 double remote_size
, bytes_dl
;
338 struct sigaction orig_sig_pipe
, orig_sig_int
;
339 /* shortcut to our handle within the payload */
340 alpm_handle_t
*handle
= payload
->handle
;
341 CURL
*curl
= get_libcurl_handle(handle
);
342 handle
->pm_errno
= 0;
344 payload
->tempfile_openmode
= "wb";
345 if(!payload
->remote_name
) {
346 STRDUP(payload
->remote_name
, get_filename(payload
->fileurl
),
347 RET_ERR(handle
, ALPM_ERR_MEMORY
, -1));
349 if(curl_gethost(payload
->fileurl
, hostname
, sizeof(hostname
)) != 0) {
350 _alpm_log(handle
, ALPM_LOG_ERROR
, _("url '%s' is invalid\n"), payload
->fileurl
);
351 RET_ERR(handle
, ALPM_ERR_SERVER_BAD_URL
, -1);
354 if(strlen(payload
->remote_name
) > 0 && strcmp(payload
->remote_name
, ".sig") != 0) {
355 payload
->destfile_name
= get_fullpath(localpath
, payload
->remote_name
, "");
356 payload
->tempfile_name
= get_fullpath(localpath
, payload
->remote_name
, ".part");
357 if(!payload
->destfile_name
|| !payload
->tempfile_name
) {
361 /* URL doesn't contain a filename, so make a tempfile. We can't support
362 * resuming this kind of download; partial transfers will be destroyed */
363 payload
->unlink_on_fail
= 1;
365 localf
= create_tempfile(payload
, localpath
);
371 curl_set_handle_opts(payload
, curl
, error_buffer
);
374 localf
= fopen(payload
->tempfile_name
, payload
->tempfile_openmode
);
380 _alpm_log(handle
, ALPM_LOG_DEBUG
,
381 "opened tempfile for download: %s (%s)\n", payload
->tempfile_name
,
382 payload
->tempfile_openmode
);
384 curl_easy_setopt(curl
, CURLOPT_WRITEDATA
, localf
);
386 /* ignore any SIGPIPE signals- these may occur if our FTP socket dies or
387 * something along those lines. Store the old signal handler first. */
388 mask_signal(SIGPIPE
, SIG_IGN
, &orig_sig_pipe
);
389 mask_signal(SIGINT
, &inthandler
, &orig_sig_int
);
391 /* perform transfer */
392 payload
->curlerr
= curl_easy_perform(curl
);
394 /* disconnect relationships from the curl handle for things that might go out
395 * of scope, but could still be touched on connection teardown. This really
396 * only applies to FTP transfers. See FS#26327 for an example. */
397 curl_easy_setopt(curl
, CURLOPT_NOPROGRESS
, 1L);
398 curl_easy_setopt(curl
, CURLOPT_ERRORBUFFER
, (char *)NULL
);
400 /* was it a success? */
401 switch(payload
->curlerr
) {
403 /* get http/ftp response code */
404 curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &respcode
);
405 _alpm_log(handle
, ALPM_LOG_DEBUG
, "response code: %ld\n", respcode
);
406 if(respcode
>= 400) {
407 payload
->unlink_on_fail
= 1;
408 /* non-translated message is same as libcurl */
409 snprintf(error_buffer
, sizeof(error_buffer
),
410 "The requested URL returned error: %ld", respcode
);
411 _alpm_log(handle
, ALPM_LOG_ERROR
,
412 _("failed retrieving file '%s' from %s : %s\n"),
413 payload
->remote_name
, hostname
, error_buffer
);
417 case CURLE_ABORTED_BY_CALLBACK
:
418 /* handle the interrupt accordingly */
419 if(dload_interrupted
== ABORT_OVER_MAXFILESIZE
) {
420 payload
->curlerr
= CURLE_FILESIZE_EXCEEDED
;
421 handle
->pm_errno
= ALPM_ERR_LIBCURL
;
422 /* use the 'size exceeded' message from libcurl */
423 _alpm_log(handle
, ALPM_LOG_ERROR
,
424 _("failed retrieving file '%s' from %s : %s\n"),
425 payload
->remote_name
, hostname
,
426 curl_easy_strerror(CURLE_FILESIZE_EXCEEDED
));
430 /* delete zero length downloads */
431 if(fstat(fileno(localf
), &st
) == 0 && st
.st_size
== 0) {
432 payload
->unlink_on_fail
= 1;
434 if(!payload
->errors_ok
) {
435 handle
->pm_errno
= ALPM_ERR_LIBCURL
;
436 _alpm_log(handle
, ALPM_LOG_ERROR
,
437 _("failed retrieving file '%s' from %s : %s\n"),
438 payload
->remote_name
, hostname
, error_buffer
);
440 _alpm_log(handle
, ALPM_LOG_DEBUG
,
441 "failed retrieving file '%s' from %s : %s\n",
442 payload
->remote_name
, hostname
, error_buffer
);
447 /* retrieve info about the state of the transfer */
448 curl_easy_getinfo(curl
, CURLINFO_FILETIME
, &remote_time
);
449 curl_easy_getinfo(curl
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &remote_size
);
450 curl_easy_getinfo(curl
, CURLINFO_SIZE_DOWNLOAD
, &bytes_dl
);
451 curl_easy_getinfo(curl
, CURLINFO_CONDITION_UNMET
, &timecond
);
452 curl_easy_getinfo(curl
, CURLINFO_EFFECTIVE_URL
, &effective_url
);
454 /* time condition was met and we didn't download anything. we need to
455 * clean up the 0 byte .part file that's left behind. */
456 if(timecond
== 1 && DOUBLE_EQ(bytes_dl
, 0)) {
457 _alpm_log(handle
, ALPM_LOG_DEBUG
, "file met time condition\n");
459 unlink(payload
->tempfile_name
);
463 /* remote_size isn't necessarily the full size of the file, just what the
464 * server reported as remaining to download. compare it to what curl reported
465 * as actually being transferred during curl_easy_perform() */
466 if(!DOUBLE_EQ(remote_size
, -1) && !DOUBLE_EQ(bytes_dl
, -1) &&
467 !DOUBLE_EQ(bytes_dl
, remote_size
)) {
468 handle
->pm_errno
= ALPM_ERR_RETRIEVE
;
469 _alpm_log(handle
, ALPM_LOG_ERROR
, _("%s appears to be truncated: %jd/%jd bytes\n"),
470 payload
->remote_name
, (intmax_t)bytes_dl
, (intmax_t)remote_size
);
474 if(payload
->content_disp_name
) {
475 /* content-disposition header has a better name for our file */
476 free(payload
->destfile_name
);
477 payload
->destfile_name
= get_fullpath(localpath
, payload
->content_disp_name
, "");
479 const char *effective_filename
= strrchr(effective_url
, '/');
480 if(effective_filename
&& strlen(effective_filename
) > 2) {
481 effective_filename
++;
483 /* if destfile was never set, we wrote to a tempfile. even if destfile is
484 * set, we may have followed some redirects and the effective url may
485 * have a better suggestion as to what to name our file. in either case,
486 * refactor destfile to this newly derived name. */
487 if(!payload
->destfile_name
|| strcmp(effective_filename
,
488 strrchr(payload
->destfile_name
, '/') + 1) != 0) {
489 free(payload
->destfile_name
);
490 payload
->destfile_name
= get_fullpath(localpath
, effective_filename
, "");
500 utimes_long(payload
->tempfile_name
, remote_time
);
504 const char *realname
= payload
->tempfile_name
;
505 if(payload
->destfile_name
) {
506 realname
= payload
->destfile_name
;
507 if(rename(payload
->tempfile_name
, payload
->destfile_name
)) {
508 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not rename %s to %s (%s)\n"),
509 payload
->tempfile_name
, payload
->destfile_name
, strerror(errno
));
513 if(ret
!= -1 && final_file
) {
514 STRDUP(*final_file
, strrchr(realname
, '/') + 1,
515 RET_ERR(handle
, ALPM_ERR_MEMORY
, -1));
519 if((ret
== -1 || dload_interrupted
) && payload
->unlink_on_fail
&&
520 payload
->tempfile_name
) {
521 unlink(payload
->tempfile_name
);
524 /* restore the old signal handlers */
525 unmask_signal(SIGINT
, orig_sig_int
);
526 unmask_signal(SIGPIPE
, orig_sig_pipe
);
527 /* if we were interrupted, trip the old handler */
528 if(dload_interrupted
) {
536 /** Download a file given by a URL to a local directory.
537 * Does not overwrite an existing file if the download fails.
538 * @param payload the payload context
539 * @param localpath the directory to save the file in
540 * @param final_file the real name of the downloaded file (may be NULL)
541 * @return 0 on success, -1 on error (pm_errno is set accordingly if errors_ok == 0)
543 int _alpm_download(struct dload_payload
*payload
, const char *localpath
,
546 alpm_handle_t
*handle
= payload
->handle
;
548 if(handle
->fetchcb
== NULL
) {
550 return curl_download_internal(payload
, localpath
, final_file
);
552 RET_ERR(handle
, ALPM_ERR_EXTERNAL_DOWNLOAD
, -1);
555 int ret
= handle
->fetchcb(payload
->fileurl
, localpath
, payload
->force
);
556 if(ret
== -1 && !payload
->errors_ok
) {
557 RET_ERR(handle
, ALPM_ERR_EXTERNAL_DOWNLOAD
, -1);
563 /** Fetch a remote pkg. */
564 char SYMEXPORT
*alpm_fetch_pkgurl(alpm_handle_t
*handle
, const char *url
)
567 const char *cachedir
;
568 char *final_file
= NULL
;
569 struct dload_payload payload
;
572 CHECK_HANDLE(handle
, return NULL
);
573 ASSERT(url
, RET_ERR(handle
, ALPM_ERR_WRONG_ARGS
, NULL
));
575 /* find a valid cache dir to download to */
576 cachedir
= _alpm_filecache_setup(handle
);
578 memset(&payload
, 0, sizeof(struct dload_payload
));
579 payload
.handle
= handle
;
580 STRDUP(payload
.fileurl
, url
, RET_ERR(handle
, ALPM_ERR_MEMORY
, NULL
));
581 payload
.allow_resume
= 1;
583 /* download the file */
584 ret
= _alpm_download(&payload
, cachedir
, &final_file
);
585 _alpm_dload_payload_reset(&payload
);
587 _alpm_log(handle
, ALPM_LOG_WARNING
, _("failed to download %s\n"), url
);
591 _alpm_log(handle
, ALPM_LOG_DEBUG
, "successfully downloaded %s\n", url
);
593 /* attempt to download the signature */
594 if(ret
== 0 && (handle
->siglevel
& ALPM_SIG_PACKAGE
)) {
595 char *sig_final_file
= NULL
;
598 len
= strlen(url
) + 5;
599 MALLOC(payload
.fileurl
, len
, RET_ERR(handle
, ALPM_ERR_MEMORY
, NULL
));
600 snprintf(payload
.fileurl
, len
, "%s.sig", url
);
601 payload
.handle
= handle
;
603 payload
.errors_ok
= (handle
->siglevel
& ALPM_SIG_PACKAGE_OPTIONAL
);
605 /* set hard upper limit of 16KiB */
606 payload
.max_size
= 16 * 1024;
608 ret
= _alpm_download(&payload
, cachedir
, &sig_final_file
);
609 if(ret
== -1 && !payload
.errors_ok
) {
610 _alpm_log(handle
, ALPM_LOG_WARNING
,
611 _("failed to download %s\n"), payload
.fileurl
);
612 /* Warn now, but don't return NULL. We will fail later during package
614 } else if(ret
== 0) {
615 _alpm_log(handle
, ALPM_LOG_DEBUG
,
616 "successfully downloaded %s\n", payload
.fileurl
);
618 FREE(sig_final_file
);
619 _alpm_dload_payload_reset(&payload
);
622 /* we should be able to find the file the second time around */
623 filepath
= _alpm_filecache_find(handle
, final_file
);
629 void _alpm_dload_payload_reset(struct dload_payload
*payload
)
631 ASSERT(payload
, return);
633 FREE(payload
->remote_name
);
634 FREE(payload
->tempfile_name
);
635 FREE(payload
->destfile_name
);
636 FREE(payload
->content_disp_name
);
637 FREE(payload
->fileurl
);
640 /* vim: set ts=2 sw=2 noet: */