uclient-fetch: add support for setting output directory
[project/uclient.git] / uclient-fetch.c
1 /*
2 * uclient - ustream based protocol client library
3 *
4 * Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #define _GNU_SOURCE
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <dlfcn.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <glob.h>
27 #include <stdint.h>
28 #include <inttypes.h>
29 #include <signal.h>
30
31 #include <libubox/blobmsg.h>
32
33 #include "uclient.h"
34 #include "uclient-utils.h"
35
36 #ifdef __APPLE__
37 #define LIB_EXT "dylib"
38 #else
39 #define LIB_EXT "so"
40 #endif
41
42 static const char *user_agent = "uclient-fetch";
43 static const char *post_data;
44 static struct ustream_ssl_ctx *ssl_ctx;
45 static const struct ustream_ssl_ops *ssl_ops;
46 static int quiet = false;
47 static bool verify = true;
48 static bool proxy = true;
49 static bool default_certs = false;
50 static bool no_output;
51 static const char *output_file;
52 static int output_fd = -1;
53 static int error_ret;
54 static int out_bytes;
55 static char *auth_str;
56 static char **urls;
57 static int n_urls;
58 static int timeout;
59 static bool resume, cur_resume;
60
61 static int init_request(struct uclient *cl);
62 static void request_done(struct uclient *cl);
63
64 static const char *
65 get_proxy_url(char *url)
66 {
67 char prefix[16];
68 char *sep;
69
70 if (!proxy)
71 return NULL;
72
73 sep = strchr(url, ':');
74 if (!sep)
75 return NULL;
76
77 if (sep - url > 5)
78 return NULL;
79
80 memcpy(prefix, url, sep - url);
81 strcpy(prefix + (sep - url), "_proxy");
82 return getenv(prefix);
83 }
84
85 static int open_output_file(const char *path, uint64_t resume_offset)
86 {
87 char *filename = NULL;
88 int flags;
89 int ret;
90
91 if (cur_resume)
92 flags = O_RDWR;
93 else
94 flags = O_WRONLY | O_EXCL;
95
96 flags |= O_CREAT;
97
98 if (output_file) {
99 if (!strcmp(output_file, "-")) {
100 if (!quiet)
101 fprintf(stderr, "Writing to stdout\n");
102
103 return STDOUT_FILENO;
104 }
105 } else {
106 filename = uclient_get_url_filename(path, "index.html");
107 output_file = filename;
108 }
109
110 if (!quiet)
111 fprintf(stderr, "Writing to '%s'\n", output_file);
112 ret = open(output_file, flags, 0644);
113 free(filename);
114
115 if (ret < 0)
116 return ret;
117
118 if (resume_offset &&
119 lseek(ret, resume_offset, SEEK_SET) < 0) {
120 if (!quiet)
121 fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset);
122 close(ret);
123 return -1;
124 }
125
126 out_bytes += resume_offset;
127
128 return ret;
129 }
130
131 static void header_done_cb(struct uclient *cl)
132 {
133 static const struct blobmsg_policy policy = {
134 .name = "content-range",
135 .type = BLOBMSG_TYPE_STRING
136 };
137 struct blob_attr *attr;
138 uint64_t resume_offset = 0, resume_end, resume_size;
139 static int retries;
140
141 if (retries < 10 && uclient_http_redirect(cl)) {
142 if (!quiet)
143 fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
144
145 retries++;
146 return;
147 }
148
149 if (cl->status_code == 204 && cur_resume) {
150 /* Resume attempt failed, try normal download */
151 cur_resume = false;
152 init_request(cl);
153 return;
154 }
155
156 switch (cl->status_code) {
157 case 416:
158 if (!quiet)
159 fprintf(stderr, "File download already fully retrieved; nothing to do.\n");
160 request_done(cl);
161 break;
162 case 206:
163 if (!cur_resume) {
164 if (!quiet)
165 fprintf(stderr, "Error: Partial content received, full content requested\n");
166 error_ret = 8;
167 request_done(cl);
168 break;
169 }
170
171 blobmsg_parse(&policy, 1, &attr, blob_data(cl->meta), blob_len(cl->meta));
172 if (!attr) {
173 if (!quiet)
174 fprintf(stderr, "Content-Range header is missing\n");
175 error_ret = 8;
176 break;
177 }
178
179 if (sscanf(blobmsg_get_string(attr), "bytes %"PRIu64"-%"PRIu64"/%"PRIu64,
180 &resume_offset, &resume_end, &resume_size) != 3) {
181 if (!quiet)
182 fprintf(stderr, "Content-Range header is invalid\n");
183 error_ret = 8;
184 break;
185 }
186 case 204:
187 case 200:
188 if (no_output)
189 break;
190 output_fd = open_output_file(cl->url->location, resume_offset);
191 if (output_fd < 0) {
192 if (!quiet)
193 perror("Cannot open output file");
194 error_ret = 3;
195 request_done(cl);
196 }
197 break;
198
199 default:
200 if (!quiet)
201 fprintf(stderr, "HTTP error %d\n", cl->status_code);
202 request_done(cl);
203 error_ret = 8;
204 break;
205 }
206 }
207
208 static void read_data_cb(struct uclient *cl)
209 {
210 char buf[256];
211 int len;
212
213 if (!no_output && output_fd < 0)
214 return;
215
216 while (1) {
217 len = uclient_read(cl, buf, sizeof(buf));
218 if (!len)
219 return;
220
221 out_bytes += len;
222 if (!no_output)
223 write(output_fd, buf, len);
224 }
225 }
226
227 static void msg_connecting(struct uclient *cl)
228 {
229 char addr[INET6_ADDRSTRLEN];
230 int port;
231
232 if (quiet)
233 return;
234
235 uclient_get_addr(addr, &port, &cl->remote_addr);
236 fprintf(stderr, "Connecting to %s:%d\n", addr, port);
237 }
238
239 static void check_resume_offset(struct uclient *cl)
240 {
241 char range_str[64];
242 struct stat st;
243 char *file;
244 int ret;
245
246 file = uclient_get_url_filename(cl->url->location, "index.html");
247 if (!file)
248 return;
249
250 ret = stat(file, &st);
251 free(file);
252 if (ret)
253 return;
254
255 if (!st.st_size)
256 return;
257
258 snprintf(range_str, sizeof(range_str), "bytes=%"PRIu64"-", (uint64_t) st.st_size);
259 uclient_http_set_header(cl, "Range", range_str);
260 }
261
262 static int init_request(struct uclient *cl)
263 {
264 int rc;
265
266 out_bytes = 0;
267 uclient_http_set_ssl_ctx(cl, ssl_ops, ssl_ctx, verify);
268
269 if (timeout)
270 cl->timeout_msecs = timeout * 1000;
271
272 rc = uclient_connect(cl);
273 if (rc)
274 return rc;
275
276 msg_connecting(cl);
277
278 rc = uclient_http_set_request_type(cl, post_data ? "POST" : "GET");
279 if (rc)
280 return rc;
281
282 uclient_http_reset_headers(cl);
283 uclient_http_set_header(cl, "User-Agent", user_agent);
284 if (cur_resume)
285 check_resume_offset(cl);
286
287 if (post_data) {
288 uclient_http_set_header(cl, "Content-Type", "application/x-www-form-urlencoded");
289 uclient_write(cl, post_data, strlen(post_data));
290 }
291
292 rc = uclient_request(cl);
293 if (rc)
294 return rc;
295
296 return 0;
297 }
298
299 static void request_done(struct uclient *cl)
300 {
301 const char *proxy_url;
302
303 if (n_urls) {
304 proxy_url = get_proxy_url(*urls);
305 if (proxy_url) {
306 uclient_set_url(cl, proxy_url, NULL);
307 uclient_set_proxy_url(cl, *urls, auth_str);
308 } else {
309 uclient_set_url(cl, *urls, auth_str);
310 }
311 n_urls--;
312 cur_resume = resume;
313 error_ret = init_request(cl);
314 if (error_ret == 0)
315 return;
316 }
317
318 if (output_fd >= 0 && !output_file) {
319 close(output_fd);
320 output_fd = -1;
321 }
322 uclient_disconnect(cl);
323 uloop_end();
324 }
325
326
327 static void eof_cb(struct uclient *cl)
328 {
329 if (!cl->data_eof) {
330 if (!quiet)
331 fprintf(stderr, "Connection reset prematurely\n");
332 error_ret = 4;
333 } else if (!quiet) {
334 fprintf(stderr, "Download completed (%d bytes)\n", out_bytes);
335 }
336 request_done(cl);
337 }
338
339 static void handle_uclient_error(struct uclient *cl, int code)
340 {
341 const char *type = "Unknown error";
342 bool ignore = false;
343
344 switch(code) {
345 case UCLIENT_ERROR_CONNECT:
346 type = "Connection failed";
347 error_ret = 4;
348 break;
349 case UCLIENT_ERROR_TIMEDOUT:
350 type = "Connection timed out";
351 error_ret = 4;
352 break;
353 case UCLIENT_ERROR_SSL_INVALID_CERT:
354 type = "Invalid SSL certificate";
355 ignore = !verify;
356 error_ret = 5;
357 break;
358 case UCLIENT_ERROR_SSL_CN_MISMATCH:
359 type = "Server hostname does not match SSL certificate";
360 ignore = !verify;
361 error_ret = 5;
362 break;
363 default:
364 error_ret = 1;
365 break;
366 }
367
368 if (!quiet)
369 fprintf(stderr, "Connection error: %s%s\n", type, ignore ? " (ignored)" : "");
370
371 if (ignore)
372 error_ret = 0;
373 else
374 request_done(cl);
375 }
376
377 static const struct uclient_cb cb = {
378 .header_done = header_done_cb,
379 .data_read = read_data_cb,
380 .data_eof = eof_cb,
381 .error = handle_uclient_error,
382 };
383
384 static int usage(const char *progname)
385 {
386 fprintf(stderr,
387 "Usage: %s [options] <URL>\n"
388 "Options:\n"
389 " -q: Turn off status messages\n"
390 " -O <file>: Redirect output to file (use \"-\" for stdout)\n"
391 " -P <dir>: Set directory for output files\n"
392 " --user=<user> HTTP authentication username\n"
393 " --password=<password> HTTP authentication password\n"
394 " --user-agent|-U <str> Set HTTP user agent\n"
395 " --post-data=STRING use the POST method; send STRING as the data\n"
396 " --spider|-s Spider mode - only check file existence\n"
397 " --timeout=N|-T N Set connect/request timeout to N seconds\n"
398 " --proxy=on|off|-Y on|off Enable/disable env var configured proxy\n"
399 "\n"
400 "HTTPS options:\n"
401 " --ca-certificate=<cert>: Load CA certificates from file <cert>\n"
402 " --no-check-certificate: don't validate the server's certificate\n"
403 "\n", progname);
404 return 1;
405 }
406
407 static void init_ca_cert(void)
408 {
409 glob_t gl;
410 int i;
411
412 glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
413 for (i = 0; i < gl.gl_pathc; i++)
414 ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
415 }
416
417 static void init_ustream_ssl(void)
418 {
419 void *dlh;
420
421 dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL);
422 if (!dlh)
423 return;
424
425 ssl_ops = dlsym(dlh, "ustream_ssl_ops");
426 if (!ssl_ops)
427 return;
428
429 ssl_ctx = ssl_ops->context_new(false);
430 }
431
432 static int no_ssl(const char *progname)
433 {
434 fprintf(stderr, "%s: SSL support not available, please install ustream-ssl\n", progname);
435 return 1;
436 }
437
438 enum {
439 L_NO_CHECK_CERTIFICATE,
440 L_CA_CERTIFICATE,
441 L_USER,
442 L_PASSWORD,
443 L_USER_AGENT,
444 L_POST_DATA,
445 L_SPIDER,
446 L_TIMEOUT,
447 L_CONTINUE,
448 L_PROXY,
449 L_NO_PROXY,
450 };
451
452 static const struct option longopts[] = {
453 [L_NO_CHECK_CERTIFICATE] = { "no-check-certificate", no_argument },
454 [L_CA_CERTIFICATE] = { "ca-certificate", required_argument },
455 [L_USER] = { "user", required_argument },
456 [L_PASSWORD] = { "password", required_argument },
457 [L_USER_AGENT] = { "user-agent", required_argument },
458 [L_POST_DATA] = { "post-data", required_argument },
459 [L_SPIDER] = { "spider", no_argument },
460 [L_TIMEOUT] = { "timeout", required_argument },
461 [L_CONTINUE] = { "continue", no_argument },
462 [L_PROXY] = { "proxy", required_argument },
463 [L_NO_PROXY] = { "no-proxy", no_argument },
464 {}
465 };
466
467
468
469 int main(int argc, char **argv)
470 {
471 const char *progname = argv[0];
472 const char *proxy_url;
473 char *username = NULL;
474 char *password = NULL;
475 struct uclient *cl;
476 int longopt_idx = 0;
477 bool has_cert = false;
478 int i, ch;
479 int rc;
480
481 signal(SIGPIPE, SIG_IGN);
482 init_ustream_ssl();
483
484 while ((ch = getopt_long(argc, argv, "cO:P:qsU:Y:", longopts, &longopt_idx)) != -1) {
485 switch(ch) {
486 case 0:
487 switch (longopt_idx) {
488 case L_NO_CHECK_CERTIFICATE:
489 verify = false;
490 break;
491 case L_CA_CERTIFICATE:
492 has_cert = true;
493 if (ssl_ctx)
494 ssl_ops->context_add_ca_crt_file(ssl_ctx, optarg);
495 break;
496 case L_USER:
497 if (!strlen(optarg))
498 break;
499 username = strdup(optarg);
500 memset(optarg, '*', strlen(optarg));
501 break;
502 case L_PASSWORD:
503 if (!strlen(optarg))
504 break;
505 password = strdup(optarg);
506 memset(optarg, '*', strlen(optarg));
507 break;
508 case L_USER_AGENT:
509 user_agent = optarg;
510 break;
511 case L_POST_DATA:
512 post_data = optarg;
513 break;
514 case L_SPIDER:
515 no_output = true;
516 break;
517 case L_TIMEOUT:
518 timeout = atoi(optarg);
519 break;
520 case L_CONTINUE:
521 resume = true;
522 break;
523 case L_PROXY:
524 if (strcmp(optarg, "on") != 0)
525 proxy = false;
526 break;
527 case L_NO_PROXY:
528 proxy = false;
529 break;
530 default:
531 return usage(progname);
532 }
533 break;
534 case 'c':
535 resume = true;
536 break;
537 case 'U':
538 user_agent = optarg;
539 break;
540 case 'O':
541 output_file = optarg;
542 break;
543 case 'P':
544 if (chdir(optarg)) {
545 if (!quiet)
546 perror("Change output directory");
547 exit(1);
548 }
549 break;
550 case 'q':
551 quiet = true;
552 break;
553 case 's':
554 no_output = true;
555 break;
556 case 'T':
557 timeout = atoi(optarg);
558 break;
559 case 'Y':
560 if (strcmp(optarg, "on") != 0)
561 proxy = false;
562 break;
563 default:
564 return usage(progname);
565 }
566 }
567
568 argv += optind;
569 argc -= optind;
570
571 if (verify && !has_cert)
572 default_certs = true;
573
574 if (argc < 1)
575 return usage(progname);
576
577 if (!ssl_ctx) {
578 for (i = 0; i < argc; i++) {
579 if (!strncmp(argv[i], "https", 5))
580 return no_ssl(progname);
581 }
582 }
583
584 urls = argv + 1;
585 n_urls = argc - 1;
586
587 uloop_init();
588
589 if (username) {
590 if (password)
591 asprintf(&auth_str, "%s:%s", username, password);
592 else
593 auth_str = username;
594 }
595
596 if (!quiet)
597 fprintf(stderr, "Downloading '%s'\n", argv[0]);
598
599 proxy_url = get_proxy_url(argv[0]);
600 if (proxy_url) {
601 cl = uclient_new(proxy_url, auth_str, &cb);
602 uclient_set_proxy_url(cl, argv[0], NULL);
603 } else {
604 cl = uclient_new(argv[0], auth_str, &cb);
605 }
606 if (!cl) {
607 fprintf(stderr, "Failed to allocate uclient context\n");
608 return 1;
609 }
610
611 if (ssl_ctx && default_certs)
612 init_ca_cert();
613
614 cur_resume = resume;
615 rc = init_request(cl);
616 if (!rc) {
617 /* no error received, we can enter main loop */
618 uloop_run();
619 } else {
620 fprintf(stderr, "Failed to establish connection\n");
621 error_ret = 4;
622 }
623
624 uloop_done();
625
626 uclient_free(cl);
627
628 if (output_fd >= 0 && output_fd != STDOUT_FILENO)
629 close(output_fd);
630
631 if (ssl_ctx)
632 ssl_ops->context_free(ssl_ctx);
633
634 return error_ret;
635 }