Changeset 3232 in MondoRescue for branches/3.2/mindi-busybox/networking/wget.c


Ignore:
Timestamp:
Jan 1, 2014, 12:47:38 AM (10 years ago)
Author:
Bruno Cornec
Message:
  • Update mindi-busybox to 1.21.1
File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/3.2/mindi-busybox/networking/wget.c

    r2859 r3232  
    99 * Kuhn's copyrights are licensed GPLv2-or-later.  File as a whole remains GPLv2.
    1010 */
     11
     12//usage:#define wget_trivial_usage
     13//usage:    IF_FEATURE_WGET_LONG_OPTIONS(
     14//usage:       "[-c|--continue] [-s|--spider] [-q|--quiet] [-O|--output-document FILE]\n"
     15//usage:       "    [--header 'header: value'] [-Y|--proxy on/off] [-P DIR]\n"
     16/* Since we ignore these opts, we don't show them in --help */
     17/* //usage:    "    [--no-check-certificate] [--no-cache]" */
     18//usage:       "    [-U|--user-agent AGENT]" IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
     19//usage:    )
     20//usage:    IF_NOT_FEATURE_WGET_LONG_OPTIONS(
     21//usage:       "[-csq] [-O FILE] [-Y on/off] [-P DIR] [-U AGENT]"
     22//usage:            IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
     23//usage:    )
     24//usage:#define wget_full_usage "\n\n"
     25//usage:       "Retrieve files via HTTP or FTP\n"
     26//usage:     "\n    -s  Spider mode - only check file existence"
     27//usage:     "\n    -c  Continue retrieval of aborted transfer"
     28//usage:     "\n    -q  Quiet"
     29//usage:     "\n    -P DIR  Save to DIR (default .)"
     30//usage:    IF_FEATURE_WGET_TIMEOUT(
     31//usage:     "\n    -T SEC  Network read timeout is SEC seconds"
     32//usage:    )
     33//usage:     "\n    -O FILE Save to FILE ('-' for stdout)"
     34//usage:     "\n    -U STR  Use STR for User-Agent header"
     35//usage:     "\n    -Y  Use proxy ('on' or 'off')"
     36
    1137#include "libbb.h"
    1238
     39#if 0
     40# define log_io(...) bb_error_msg(__VA_ARGS__)
     41#else
     42# define log_io(...) ((void)0)
     43#endif
     44
     45
    1346struct host_info {
    14     // May be used if we ever will want to free() all xstrdup()s...
    15     /* char *allocated; */
     47    char *allocated;
    1648    const char *path;
    1749    const char *user;
     
    3163    bb_progress_t pmt;
    3264#endif
     65        char *dir_prefix;
     66#if ENABLE_FEATURE_WGET_LONG_OPTIONS
     67        char *post_data;
     68        char *extra_headers;
     69#endif
     70        char *fname_out;        /* where to direct output (-O) */
     71        const char *proxy_flag; /* Use proxies if env vars are set */
     72        const char *user_agent; /* "User-Agent" header field */
    3373#if ENABLE_FEATURE_WGET_TIMEOUT
    3474    unsigned timeout_seconds;
    3575#endif
     76    int output_fd;
     77    int o_flags;
    3678    smallint chunked;         /* chunked transfer encoding */
    3779    smallint got_clen;        /* got content-length: from server  */
     80    /* Local downloads do benefit from big buffer.
     81     * With 512 byte buffer, it was measured to be
     82     * an order of magnitude slower than with big one.
     83     */
     84    uint64_t just_to_align_next_member;
     85    char wget_buf[CONFIG_FEATURE_COPYBUF_KB*1024];
    3886} FIX_ALIASING;
    39 #define G (*(struct globals*)&bb_common_bufsiz1)
    40 struct BUG_G_too_big {
    41     char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
    42 };
     87#define G (*ptr_to_globals)
    4388#define INIT_G() do { \
     89        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
    4490    IF_FEATURE_WGET_TIMEOUT(G.timeout_seconds = 900;) \
    4591} while (0)
     
    74120
    75121    if (flag == PROGRESS_START)
    76         bb_progress_init(&G.pmt);
    77 
    78     bb_progress_update(&G.pmt, G.curfile, G.beg_range, G.transferred,
    79                G.chunked ? 0 : G.beg_range + G.transferred + G.content_len);
     122        bb_progress_init(&G.pmt, G.curfile);
     123
     124    bb_progress_update(&G.pmt,
     125            G.beg_range,
     126            G.transferred,
     127            (G.chunked || !G.got_clen) ? 0 : G.beg_range + G.transferred + G.content_len
     128    );
    80129
    81130    if (flag == PROGRESS_END) {
     131        bb_progress_free(&G.pmt);
    82132        bb_putchar_stderr('\n');
    83133        G.transferred = 0;
     
    125175}
    126176
    127 /* Read NMEMB bytes into PTR from STREAM.  Returns the number of bytes read,
    128  * and a short count if an eof or non-interrupt error is encountered.  */
    129 static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
    130 {
    131     size_t ret;
    132     char *p = (char*)ptr;
    133 
    134     do {
    135         clearerr(stream);
    136         errno = 0;
    137         ret = fread(p, 1, nmemb, stream);
    138         p += ret;
    139         nmemb -= ret;
    140     } while (nmemb && ferror(stream) && errno == EINTR);
    141 
    142     return p - (char*)ptr;
    143 }
    144 
    145 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
    146  * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
    147 static char *safe_fgets(char *s, int size, FILE *stream)
    148 {
    149     char *ret;
    150 
    151     do {
    152         clearerr(stream);
    153         errno = 0;
    154         ret = fgets(s, size, stream);
    155     } while (ret == NULL && ferror(stream) && errno == EINTR);
    156 
    157     return ret;
    158 }
    159 
    160177#if ENABLE_FEATURE_WGET_AUTHENTICATION
    161 /* Base64-encode character string. buf is assumed to be char buf[512]. */
    162 static char *base64enc_512(char buf[512], const char *str)
     178/* Base64-encode character string. */
     179static char *base64enc(const char *str)
    163180{
    164181    unsigned len = strlen(str);
    165     if (len > 512/4*3 - 10) /* paranoia */
    166         len = 512/4*3 - 10;
    167     bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
    168     return buf;
     182    if (len > sizeof(G.wget_buf)/4*3 - 10) /* paranoia */
     183        len = sizeof(G.wget_buf)/4*3 - 10;
     184    bb_uuencode(G.wget_buf, str, len, bb_uuenc_tbl_base64);
     185    return G.wget_buf;
    169186}
    170187#endif
     
    187204    fp = fdopen(xconnect_stream(lsa), "r+");
    188205    if (fp == NULL)
    189         bb_perror_msg_and_die("fdopen");
     206        bb_perror_msg_and_die(bb_msg_memory_exhausted);
    190207
    191208    return fp;
    192209}
    193210
    194 static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
     211/* Returns '\n' if it was seen, else '\0'. Trims at first '\r' or '\n' */
     212static char fgets_and_trim(FILE *fp)
     213{
     214    char c;
     215    char *buf_ptr;
     216
     217    if (fgets(G.wget_buf, sizeof(G.wget_buf) - 1, fp) == NULL)
     218        bb_perror_msg_and_die("error getting response");
     219
     220    buf_ptr = strchrnul(G.wget_buf, '\n');
     221    c = *buf_ptr;
     222    *buf_ptr = '\0';
     223    buf_ptr = strchrnul(G.wget_buf, '\r');
     224    *buf_ptr = '\0';
     225
     226    log_io("< %s", G.wget_buf);
     227
     228    return c;
     229}
     230
     231static int ftpcmd(const char *s1, const char *s2, FILE *fp)
    195232{
    196233    int result;
    197234    if (s1) {
    198         if (!s2) s2 = "";
     235        if (!s2)
     236            s2 = "";
    199237        fprintf(fp, "%s%s\r\n", s1, s2);
    200238        fflush(fp);
     239        log_io("> %s%s", s1, s2);
    201240    }
    202241
    203242    do {
    204         char *buf_ptr;
    205 
    206         if (fgets(buf, 510, fp) == NULL) {
    207             bb_perror_msg_and_die("error getting response");
    208         }
    209         buf_ptr = strstr(buf, "\r\n");
    210         if (buf_ptr) {
    211             *buf_ptr = '\0';
    212         }
    213     } while (!isdigit(buf[0]) || buf[3] != ' ');
    214 
    215     buf[3] = '\0';
    216     result = xatoi_positive(buf);
    217     buf[3] = ' ';
     243        fgets_and_trim(fp);
     244    } while (!isdigit(G.wget_buf[0]) || G.wget_buf[3] != ' ');
     245
     246    G.wget_buf[3] = '\0';
     247    result = xatoi_positive(G.wget_buf);
     248    G.wget_buf[3] = ' ';
    218249    return result;
    219250}
    220251
    221 static void parse_url(char *src_url, struct host_info *h)
     252static void parse_url(const char *src_url, struct host_info *h)
    222253{
    223254    char *url, *p, *sp;
    224255
    225     /* h->allocated = */ url = xstrdup(src_url);
     256    free(h->allocated);
     257    h->allocated = url = xstrdup(src_url);
    226258
    227259    if (strncmp(url, "http://", 7) == 0) {
     
    271303    sp = strrchr(h->host, '@');
    272304    if (sp != NULL) {
    273         h->user = h->host;
     305        // URL-decode "user:password" string before base64-encoding:
     306        // wget http://test:my%20pass@example.com should send
     307        // Authorization: Basic dGVzdDpteSBwYXNz
     308        // which decodes to "test:my pass".
     309        // Standard wget and curl do this too.
    274310        *sp = '\0';
     311        h->user = percent_decode_in_place(h->host, /*strict:*/ 0);
    275312        h->host = sp + 1;
    276313    }
     
    279316}
    280317
    281 static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
     318static char *gethdr(FILE *fp)
    282319{
    283320    char *s, *hdrval;
    284321    int c;
    285322
    286     /* *istrunc = 0; */
    287 
    288323    /* retrieve header line */
    289     if (fgets(buf, bufsiz, fp) == NULL)
     324    c = fgets_and_trim(fp);
     325
     326    /* end of the headers? */
     327    if (G.wget_buf[0] == '\0')
    290328        return NULL;
    291329
    292     /* see if we are at the end of the headers */
    293     for (s = buf; *s == '\r'; ++s)
    294         continue;
    295     if (*s == '\n')
    296         return NULL;
    297 
    298330    /* convert the header name to lower case */
    299     for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s) {
     331    for (s = G.wget_buf; isalnum(*s) || *s == '-' || *s == '.'; ++s) {
    300332        /* tolower for "A-Z", no-op for "0-9a-z-." */
    301         *s = (*s | 0x20);
     333        *s |= 0x20;
    302334    }
    303335
    304336    /* verify we are at the end of the header name */
    305337    if (*s != ':')
    306         bb_error_msg_and_die("bad header line: %s", sanitize_string(buf));
     338        bb_error_msg_and_die("bad header line: %s", sanitize_string(G.wget_buf));
    307339
    308340    /* locate the start of the header value */
     
    310342    hdrval = skip_whitespace(s);
    311343
    312     /* locate the end of header */
    313     while (*s && *s != '\r' && *s != '\n')
    314         ++s;
    315 
    316     /* end of header found */
    317     if (*s) {
    318         *s = '\0';
    319         return hdrval;
    320     }
    321 
    322     /* Rats! The buffer isn't big enough to hold the entire header value */
    323     while (c = getc(fp), c != EOF && c != '\n')
    324         continue;
    325     /* *istrunc = 1; */
     344    if (c != '\n') {
     345        /* Rats! The buffer isn't big enough to hold the entire header value */
     346        while (c = getc(fp), c != EOF && c != '\n')
     347            continue;
     348    }
     349
    326350    return hdrval;
    327351}
    328352
    329 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
    330 static char *URL_escape(const char *str)
    331 {
    332     /* URL encode, see RFC 2396 */
    333     char *dst;
    334     char *res = dst = xmalloc(strlen(str) * 3 + 1);
    335     unsigned char c;
    336 
    337     while (1) {
    338         c = *str++;
    339         if (c == '\0'
    340         /* || strchr("!&'()*-.=_~", c) - more code */
    341          || c == '!'
    342          || c == '&'
    343          || c == '\''
    344          || c == '('
    345          || c == ')'
    346          || c == '*'
    347          || c == '-'
    348          || c == '.'
    349          || c == '='
    350          || c == '_'
    351          || c == '~'
    352          || (c >= '0' && c <= '9')
    353          || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
    354         ) {
    355             *dst++ = c;
    356             if (c == '\0')
    357                 return res;
    358         } else {
    359             *dst++ = '%';
    360             *dst++ = bb_hexdigits_upcase[c >> 4];
    361             *dst++ = bb_hexdigits_upcase[c & 0xf];
    362         }
    363     }
    364 }
    365 #endif
     353static void reset_beg_range_to_zero(void)
     354{
     355    bb_error_msg("restart failed");
     356    G.beg_range = 0;
     357    xlseek(G.output_fd, 0, SEEK_SET);
     358    /* Done at the end instead: */
     359    /* ftruncate(G.output_fd, 0); */
     360}
    366361
    367362static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
    368363{
    369     char buf[512];
    370364    FILE *sfp;
    371365    char *str;
     
    376370
    377371    sfp = open_socket(lsa);
    378     if (ftpcmd(NULL, NULL, sfp, buf) != 220)
    379         bb_error_msg_and_die("%s", sanitize_string(buf+4));
     372    if (ftpcmd(NULL, NULL, sfp) != 220)
     373        bb_error_msg_and_die("%s", sanitize_string(G.wget_buf + 4));
    380374
    381375    /*
     
    386380    if (str)
    387381        *str++ = '\0';
    388     switch (ftpcmd("USER ", target->user, sfp, buf)) {
     382    switch (ftpcmd("USER ", target->user, sfp)) {
    389383    case 230:
    390384        break;
    391385    case 331:
    392         if (ftpcmd("PASS ", str, sfp, buf) == 230)
     386        if (ftpcmd("PASS ", str, sfp) == 230)
    393387            break;
    394388        /* fall through (failed login) */
    395389    default:
    396         bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
    397     }
    398 
    399     ftpcmd("TYPE I", NULL, sfp, buf);
     390        bb_error_msg_and_die("ftp login: %s", sanitize_string(G.wget_buf + 4));
     391    }
     392
     393    ftpcmd("TYPE I", NULL, sfp);
    400394
    401395    /*
    402396     * Querying file size
    403397     */
    404     if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
    405         G.content_len = BB_STRTOOFF(buf+4, NULL, 10);
     398    if (ftpcmd("SIZE ", target->path, sfp) == 213) {
     399        G.content_len = BB_STRTOOFF(G.wget_buf + 4, NULL, 10);
    406400        if (G.content_len < 0 || errno) {
    407401            bb_error_msg_and_die("SIZE value is garbage");
     
    413407     * Entering passive mode
    414408     */
    415     if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
     409    if (ftpcmd("PASV", NULL, sfp) != 227) {
    416410 pasv_error:
    417         bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
     411        bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(G.wget_buf));
    418412    }
    419413    // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
    420414    // Server's IP is N1.N2.N3.N4 (we ignore it)
    421415    // Server's port for data connection is P1*256+P2
    422     str = strrchr(buf, ')');
     416    str = strrchr(G.wget_buf, ')');
    423417    if (str) str[0] = '\0';
    424     str = strrchr(buf, ',');
     418    str = strrchr(G.wget_buf, ',');
    425419    if (!str) goto pasv_error;
    426420    port = xatou_range(str+1, 0, 255);
    427421    *str = '\0';
    428     str = strrchr(buf, ',');
     422    str = strrchr(G.wget_buf, ',');
    429423    if (!str) goto pasv_error;
    430424    port += xatou_range(str+1, 0, 255) * 256;
    431     set_nport(lsa, htons(port));
     425    set_nport(&lsa->u.sa, htons(port));
    432426
    433427    *dfpp = open_socket(lsa);
    434428
    435     if (G.beg_range) {
    436         sprintf(buf, "REST %"OFF_FMT"u", G.beg_range);
    437         if (ftpcmd(buf, NULL, sfp, buf) == 350)
     429    if (G.beg_range != 0) {
     430        sprintf(G.wget_buf, "REST %"OFF_FMT"u", G.beg_range);
     431        if (ftpcmd(G.wget_buf, NULL, sfp) == 350)
    438432            G.content_len -= G.beg_range;
    439     }
    440 
    441     if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
    442         bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
     433        else
     434            reset_beg_range_to_zero();
     435    }
     436
     437    if (ftpcmd("RETR ", target->path, sfp) > 150)
     438        bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(G.wget_buf));
    443439
    444440    return sfp;
    445441}
    446442
    447 static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
    448 {
    449     char buf[4*1024]; /* made bigger to speed up local xfers */
     443static void NOINLINE retrieve_file_data(FILE *dfp)
     444{
    450445#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
    451446# if ENABLE_FEATURE_WGET_TIMEOUT
    452     unsigned second_cnt;
     447    unsigned second_cnt = G.timeout_seconds;
    453448# endif
    454449    struct pollfd polldata;
     
    466461
    467462#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
     463        /* Must use nonblocking I/O, otherwise fread will loop
     464         * and *block* until it reads full buffer,
     465         * which messes up progress bar and/or timeout logic.
     466         * Because of nonblocking I/O, we need to dance
     467         * very carefully around EAGAIN. See explanation at
     468         * clearerr() calls.
     469         */
    468470        ndelay_on(polldata.fd);
    469471#endif
     
    472474            unsigned rdsz;
    473475
    474             rdsz = sizeof(buf);
    475             if (G.got_clen) {
    476                 if (G.content_len < (off_t)sizeof(buf)) {
    477                     if ((int)G.content_len <= 0)
    478                         break;
    479                     rdsz = (unsigned)G.content_len;
    480                 }
    481             }
    482476#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
    483 # if ENABLE_FEATURE_WGET_TIMEOUT
    484             second_cnt = G.timeout_seconds;
    485 # endif
    486             while (1) {
    487                 if (safe_poll(&polldata, 1, 1000) != 0)
    488                     break; /* error, EOF, or data is available */
    489 # if ENABLE_FEATURE_WGET_TIMEOUT
    490                 if (second_cnt != 0 && --second_cnt == 0) {
    491                     progress_meter(PROGRESS_END);
    492                     bb_perror_msg_and_die("download timed out");
    493                 }
    494 # endif
    495                 /* Needed for "stalled" indicator */
    496                 progress_meter(PROGRESS_BUMP);
    497             }
    498 #endif
    499477            /* fread internally uses read loop, which in our case
    500478             * is usually exited when we get EAGAIN.
     
    506484             */
    507485            clearerr(dfp);
     486#endif
    508487            errno = 0;
    509             n = safe_fread(buf, rdsz, dfp);
    510             /* man fread:
     488            rdsz = sizeof(G.wget_buf);
     489            if (G.got_clen) {
     490                if (G.content_len < (off_t)sizeof(G.wget_buf)) {
     491                    if ((int)G.content_len <= 0)
     492                        break;
     493                    rdsz = (unsigned)G.content_len;
     494                }
     495            }
     496            n = fread(G.wget_buf, 1, rdsz, dfp);
     497
     498            if (n > 0) {
     499                xwrite(G.output_fd, G.wget_buf, n);
     500#if ENABLE_FEATURE_WGET_STATUSBAR
     501                G.transferred += n;
     502#endif
     503                if (G.got_clen) {
     504                    G.content_len -= n;
     505                    if (G.content_len == 0)
     506                        break;
     507                }
     508#if ENABLE_FEATURE_WGET_TIMEOUT
     509                second_cnt = G.timeout_seconds;
     510#endif
     511                continue;
     512            }
     513
     514            /* n <= 0.
     515             * man fread:
    511516             * If error occurs, or EOF is reached, the return value
    512517             * is a short item count (or zero).
    513518             * fread does not distinguish between EOF and error.
    514519             */
    515             if (n <= 0) {
    516 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
    517                 if (errno == EAGAIN) /* poll lied, there is no data? */
    518                     continue; /* yes */
    519 #endif
    520                 if (ferror(dfp))
     520            if (errno != EAGAIN) {
     521                if (ferror(dfp)) {
     522                    progress_meter(PROGRESS_END);
    521523                    bb_perror_msg_and_die(bb_msg_read_error);
     524                }
    522525                break; /* EOF, not error */
    523526            }
    524527
    525             xwrite(output_fd, buf, n);
    526 #if ENABLE_FEATURE_WGET_STATUSBAR
    527             G.transferred += n;
     528#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
     529            /* It was EAGAIN. There is no data. Wait up to one second
     530             * then abort if timed out, or update the bar and try reading again.
     531             */
     532            if (safe_poll(&polldata, 1, 1000) == 0) {
     533# if ENABLE_FEATURE_WGET_TIMEOUT
     534                if (second_cnt != 0 && --second_cnt == 0) {
     535                    progress_meter(PROGRESS_END);
     536                    bb_error_msg_and_die("download timed out");
     537                }
     538# endif
     539                /* We used to loop back to poll here,
     540                 * but there is no great harm in letting fread
     541                 * to try reading anyway.
     542                 */
     543            }
     544            /* Need to do it _every_ second for "stalled" indicator
     545             * to be shown properly.
     546             */
    528547            progress_meter(PROGRESS_BUMP);
    529548#endif
    530             if (G.got_clen) {
    531                 G.content_len -= n;
    532                 if (G.content_len == 0)
    533                     break;
    534             }
    535         }
     549        } /* while (reading data) */
     550
    536551#if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
    537         ndelay_off(polldata.fd);
    538 #endif
    539 
     552        clearerr(dfp);
     553        ndelay_off(polldata.fd); /* else fgets can get very unhappy */
     554#endif
    540555        if (!G.chunked)
    541556            break;
    542557
    543         safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
     558        fgets_and_trim(dfp); /* Eat empty line */
    544559 get_clen:
    545         safe_fgets(buf, sizeof(buf), dfp);
    546         G.content_len = STRTOOFF(buf, NULL, 16);
     560        fgets_and_trim(dfp);
     561        G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
    547562        /* FIXME: error check? */
    548563        if (G.content_len == 0)
    549564            break; /* all done! */
    550565        G.got_clen = 1;
    551     }
    552 
     566        /*
     567         * Note that fgets may result in some data being buffered in dfp.
     568         * We loop back to fread, which will retrieve this data.
     569         * Also note that code has to be arranged so that fread
     570         * is done _before_ one-second poll wait - poll doesn't know
     571         * about stdio buffering and can result in spurious one second waits!
     572         */
     573    }
     574
     575    /* If -c failed, we restart from the beginning,
     576     * but we do not truncate file then, we do it only now, at the end.
     577     * This lets user to ^C if his 99% complete 10 GB file download
     578     * failed to restart *without* losing the almost complete file.
     579     */
     580    {
     581        off_t pos = lseek(G.output_fd, 0, SEEK_CUR);
     582        if (pos != (off_t)-1)
     583            ftruncate(G.output_fd, pos);
     584    }
     585
     586    /* Draw full bar and free its resources */
     587    G.chunked = 0;  /* makes it show 100% even for chunked download */
     588    G.got_clen = 1; /* makes it show 100% even for download of (formerly) unknown size */
    553589    progress_meter(PROGRESS_END);
    554590}
    555591
    556 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
    557 int wget_main(int argc UNUSED_PARAM, char **argv)
    558 {
    559     char buf[512];
    560     struct host_info server, target;
     592static void download_one_url(const char *url)
     593{
     594    bool use_proxy;                 /* Use proxies if env vars are set  */
     595    int redir_limit;
    561596    len_and_sockaddr *lsa;
    562     unsigned opt;
    563     int redir_limit;
    564     char *proxy = NULL;
    565     char *dir_prefix = NULL;
    566 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
    567     char *post_data;
    568     char *extra_headers = NULL;
    569     llist_t *headers_llist = NULL;
    570 #endif
    571597    FILE *sfp;                      /* socket to web/ftp server         */
    572598    FILE *dfp;                      /* socket to ftp server (data)      */
    573     char *fname_out;                /* where to direct output (-O)      */
    574     int output_fd = -1;
    575     bool use_proxy;                 /* Use proxies if env vars are set  */
    576     const char *proxy_flag = "on";  /* Use proxies if env vars are set  */
    577     const char *user_agent = "Wget";/* "User-Agent" header field        */
    578 
    579     static const char keywords[] ALIGN1 =
    580         "content-length\0""transfer-encoding\0""chunked\0""location\0";
    581     enum {
    582         KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
    583     };
    584 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
    585     static const char wget_longopts[] ALIGN1 =
    586         /* name, has_arg, val */
    587         "continue\0"         No_argument       "c"
    588         "spider\0"           No_argument       "s"
    589         "quiet\0"            No_argument       "q"
    590         "output-document\0"  Required_argument "O"
    591         "directory-prefix\0" Required_argument "P"
    592         "proxy\0"            Required_argument "Y"
    593         "user-agent\0"       Required_argument "U"
    594 #if ENABLE_FEATURE_WGET_TIMEOUT
    595         "timeout\0"          Required_argument "T"
    596 #endif
    597         /* Ignored: */
    598         // "tries\0"            Required_argument "t"
    599         /* Ignored (we always use PASV): */
    600         "passive-ftp\0"      No_argument       "\xff"
    601         "header\0"           Required_argument "\xfe"
    602         "post-data\0"        Required_argument "\xfd"
    603         /* Ignored (we don't do ssl) */
    604         "no-check-certificate\0" No_argument   "\xfc"
    605         ;
    606 #endif
    607 
    608     INIT_G();
    609 
    610 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
    611     applet_long_options = wget_longopts;
    612 #endif
    613     /* server.allocated = target.allocated = NULL; */
    614     opt_complementary = "-1" IF_FEATURE_WGET_TIMEOUT(":T+") IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
    615     opt = getopt32(argv, "csqO:P:Y:U:T:" /*ignored:*/ "t:",
    616                 &fname_out, &dir_prefix,
    617                 &proxy_flag, &user_agent,
    618                 IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
    619                 NULL /* -t RETRIES */
    620                 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
    621                 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
    622                 );
    623 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
    624     if (headers_llist) {
    625         int size = 1;
    626         char *cp;
    627         llist_t *ll = headers_llist;
    628         while (ll) {
    629             size += strlen(ll->data) + 2;
    630             ll = ll->link;
    631         }
    632         extra_headers = cp = xmalloc(size);
    633         while (headers_llist) {
    634             cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
    635         }
    636     }
    637 #endif
    638 
    639     /* TODO: compat issue: should handle "wget URL1 URL2..." */
    640 
     599    char *proxy = NULL;
     600    char *fname_out_alloc;
     601    char *redirected_path = NULL;
     602    struct host_info server;
     603    struct host_info target;
     604
     605    server.allocated = NULL;
     606    target.allocated = NULL;
     607    server.user = NULL;
    641608    target.user = NULL;
    642     parse_url(argv[optind], &target);
     609
     610    parse_url(url, &target);
    643611
    644612    /* Use the proxy if necessary */
    645     use_proxy = (strcmp(proxy_flag, "off") != 0);
     613    use_proxy = (strcmp(G.proxy_flag, "off") != 0);
    646614    if (use_proxy) {
    647615        proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
    648         if (proxy && proxy[0]) {
    649             server.user = NULL;
     616        use_proxy = (proxy && proxy[0]);
     617        if (use_proxy)
    650618            parse_url(proxy, &server);
    651         } else {
    652             use_proxy = 0;
    653         }
    654619    }
    655620    if (!use_proxy) {
    656621        server.port = target.port;
    657622        if (ENABLE_FEATURE_IPV6) {
    658             server.host = xstrdup(target.host);
     623            //free(server.allocated); - can't be non-NULL
     624            server.host = server.allocated = xstrdup(target.host);
    659625        } else {
    660626            server.host = target.host;
     
    665631        strip_ipv6_scope_id(target.host);
    666632
    667     /* Guess an output filename, if there was no -O FILE */
    668     if (!(opt & WGET_OPT_OUTNAME)) {
    669         fname_out = bb_get_last_path_component_nostrip(target.path);
     633    /* If there was no -O FILE, guess output filename */
     634    fname_out_alloc = NULL;
     635    if (!(option_mask32 & WGET_OPT_OUTNAME)) {
     636        G.fname_out = bb_get_last_path_component_nostrip(target.path);
    670637        /* handle "wget http://kernel.org//" */
    671         if (fname_out[0] == '/' || !fname_out[0])
    672             fname_out = (char*)"index.html";
     638        if (G.fname_out[0] == '/' || !G.fname_out[0])
     639            G.fname_out = (char*)"index.html";
    673640        /* -P DIR is considered only if there was no -O FILE */
    674         if (dir_prefix)
    675             fname_out = concat_path_file(dir_prefix, fname_out);
    676     } else {
    677         if (LONE_DASH(fname_out)) {
    678             /* -O - */
    679             output_fd = 1;
    680             opt &= ~WGET_OPT_CONTINUE;
     641        if (G.dir_prefix)
     642            G.fname_out = fname_out_alloc = concat_path_file(G.dir_prefix, G.fname_out);
     643        else {
     644            /* redirects may free target.path later, need to make a copy */
     645            G.fname_out = fname_out_alloc = xstrdup(G.fname_out);
    681646        }
    682647    }
    683648#if ENABLE_FEATURE_WGET_STATUSBAR
    684     G.curfile = bb_get_last_path_component_nostrip(fname_out);
    685 #endif
    686 
    687     /* Impossible?
    688     if ((opt & WGET_OPT_CONTINUE) && !fname_out)
    689         bb_error_msg_and_die("can't specify continue (-c) without a filename (-O)");
    690     */
     649    G.curfile = bb_get_last_path_component_nostrip(G.fname_out);
     650#endif
    691651
    692652    /* Determine where to start transfer */
    693     if (opt & WGET_OPT_CONTINUE) {
    694         output_fd = open(fname_out, O_WRONLY);
    695         if (output_fd >= 0) {
    696             G.beg_range = xlseek(output_fd, 0, SEEK_END);
     653    G.beg_range = 0;
     654    if (option_mask32 & WGET_OPT_CONTINUE) {
     655        G.output_fd = open(G.fname_out, O_WRONLY);
     656        if (G.output_fd >= 0) {
     657            G.beg_range = xlseek(G.output_fd, 0, SEEK_END);
    697658        }
    698659        /* File doesn't exist. We do not create file here yet.
    699          * We are not sure it exists on remove side */
     660         * We are not sure it exists on remote side */
    700661    }
    701662
     
    703664 resolve_lsa:
    704665    lsa = xhost2sockaddr(server.host, server.port);
    705     if (!(opt & WGET_OPT_QUIET)) {
     666    if (!(option_mask32 & WGET_OPT_QUIET)) {
    706667        char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
    707668        fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
     
    709670    }
    710671 establish_session:
     672    /*G.content_len = 0; - redundant, got_clen = 0 is enough */
     673    G.got_clen = 0;
     674    G.chunked = 0;
    711675    if (use_proxy || !target.is_ftp) {
    712676        /*
     
    716680        int status;
    717681
     682
    718683        /* Open socket to http server */
    719684        sfp = open_socket(lsa);
     
    725690                target.path);
    726691        } else {
    727             if (opt & WGET_OPT_POST_DATA)
     692            if (option_mask32 & WGET_OPT_POST_DATA)
    728693                fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
    729694            else
     
    732697
    733698        fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
    734             target.host, user_agent);
     699            target.host, G.user_agent);
    735700
    736701        /* Ask server to close the connection as soon as we are done
     
    742707        if (target.user) {
    743708            fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
    744                 base64enc_512(buf, target.user));
     709                base64enc(target.user));
    745710        }
    746711        if (use_proxy && server.user) {
    747712            fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
    748                 base64enc_512(buf, server.user));
    749         }
    750 #endif
    751 
    752         if (G.beg_range)
     713                base64enc(server.user));
     714        }
     715#endif
     716
     717        if (G.beg_range != 0)
    753718            fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
    754719
    755720#if ENABLE_FEATURE_WGET_LONG_OPTIONS
    756         if (extra_headers)
    757             fputs(extra_headers, sfp);
    758 
    759         if (opt & WGET_OPT_POST_DATA) {
    760             char *estr = URL_escape(post_data);
     721        if (G.extra_headers)
     722            fputs(G.extra_headers, sfp);
     723
     724        if (option_mask32 & WGET_OPT_POST_DATA) {
    761725            fprintf(sfp,
    762726                "Content-Type: application/x-www-form-urlencoded\r\n"
     
    764728                "\r\n"
    765729                "%s",
    766                 (int) strlen(estr), estr
     730                (int) strlen(G.post_data), G.post_data
    767731            );
    768             free(estr);
    769732        } else
    770733#endif
     
    779742         */
    780743 read_response:
    781         if (fgets(buf, sizeof(buf), sfp) == NULL)
    782             bb_error_msg_and_die("no response from server");
    783 
    784         str = buf;
     744        fgets_and_trim(sfp);
     745
     746        str = G.wget_buf;
    785747        str = skip_non_whitespace(str);
    786748        str = skip_whitespace(str);
     
    791753        case 0:
    792754        case 100:
    793             while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
     755            while (gethdr(sfp) != NULL)
    794756                /* eat all remaining headers */;
    795757            goto read_response;
     
    820782*/
    821783        case 204:
     784            if (G.beg_range != 0) {
     785                /* "Range:..." was not honored by the server.
     786                 * Restart download from the beginning.
     787                 */
     788                reset_beg_range_to_zero();
     789            }
    822790            break;
    823791        case 300:  /* redirection */
     
    826794        case 303:
    827795            break;
    828         case 206:
    829             if (G.beg_range)
     796        case 206: /* Partial Content */
     797            if (G.beg_range != 0)
     798                /* "Range:..." worked. Good. */
    830799                break;
     800            /* Partial Content even though we did not ask for it??? */
    831801            /* fall through */
    832802        default:
    833             bb_error_msg_and_die("server returned error: %s", sanitize_string(buf));
     803            bb_error_msg_and_die("server returned error: %s", sanitize_string(G.wget_buf));
    834804        }
    835805
     
    837807         * Retrieve HTTP headers.
    838808         */
    839         while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
     809        while ((str = gethdr(sfp)) != NULL) {
     810            static const char keywords[] ALIGN1 =
     811                "content-length\0""transfer-encoding\0""location\0";
     812            enum {
     813                KEY_content_length = 1, KEY_transfer_encoding, KEY_location
     814            };
     815            smalluint key;
     816
    840817            /* gethdr converted "FOO:" string to lowercase */
    841             smalluint key;
     818
    842819            /* strip trailing whitespace */
    843820            char *s = strchrnul(str, '\0') - 1;
     
    846823                s--;
    847824            }
    848             key = index_in_strings(keywords, buf) + 1;
     825            key = index_in_strings(keywords, G.wget_buf) + 1;
    849826            if (key == KEY_content_length) {
    850827                G.content_len = BB_STRTOOFF(str, NULL, 10);
     
    856833            }
    857834            if (key == KEY_transfer_encoding) {
    858                 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
     835                if (strcmp(str_tolower(str), "chunked") != 0)
    859836                    bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
    860                 G.chunked = G.got_clen = 1;
     837                G.chunked = 1;
    861838            }
    862839            if (key == KEY_location && status >= 300) {
     
    864841                    bb_error_msg_and_die("too many redirections");
    865842                fclose(sfp);
    866                 G.got_clen = 0;
    867                 G.chunked = 0;
    868                 if (str[0] == '/')
    869                     /* free(target.allocated); */
    870                     target.path = /* target.allocated = */ xstrdup(str+1);
     843                if (str[0] == '/') {
     844                    free(redirected_path);
     845                    target.path = redirected_path = xstrdup(str+1);
    871846                    /* lsa stays the same: it's on the same server */
    872                 else {
     847                } else {
    873848                    parse_url(str, &target);
    874849                    if (!use_proxy) {
     850                        free(server.allocated);
     851                        server.allocated = NULL;
    875852                        server.host = target.host;
    876853                        /* strip_ipv6_scope_id(target.host); - no! */
     
    897874    }
    898875
    899     if (opt & WGET_OPT_SPIDER) {
    900         if (ENABLE_FEATURE_CLEAN_UP)
    901             fclose(sfp);
    902         return EXIT_SUCCESS;
    903     }
    904 
    905     if (output_fd < 0) {
    906         int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
     876    free(lsa);
     877
     878    if (!(option_mask32 & WGET_OPT_SPIDER)) {
     879        if (G.output_fd < 0)
     880            G.output_fd = xopen(G.fname_out, G.o_flags);
     881        retrieve_file_data(dfp);
     882        if (!(option_mask32 & WGET_OPT_OUTNAME)) {
     883            xclose(G.output_fd);
     884            G.output_fd = -1;
     885        }
     886    }
     887
     888    if (dfp != sfp) {
     889        /* It's ftp. Close data connection properly */
     890        fclose(dfp);
     891        if (ftpcmd(NULL, NULL, sfp) != 226)
     892            bb_error_msg_and_die("ftp error: %s", sanitize_string(G.wget_buf + 4));
     893        /* ftpcmd("QUIT", NULL, sfp); - why bother? */
     894    }
     895    fclose(sfp);
     896
     897    free(server.allocated);
     898    free(target.allocated);
     899    free(fname_out_alloc);
     900    free(redirected_path);
     901}
     902
     903int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
     904int wget_main(int argc UNUSED_PARAM, char **argv)
     905{
     906#if ENABLE_FEATURE_WGET_LONG_OPTIONS
     907    static const char wget_longopts[] ALIGN1 =
     908        /* name, has_arg, val */
     909        "continue\0"         No_argument       "c"
     910//FIXME: -s isn't --spider, it's --save-headers!
     911        "spider\0"           No_argument       "s"
     912        "quiet\0"            No_argument       "q"
     913        "output-document\0"  Required_argument "O"
     914        "directory-prefix\0" Required_argument "P"
     915        "proxy\0"            Required_argument "Y"
     916        "user-agent\0"       Required_argument "U"
     917#if ENABLE_FEATURE_WGET_TIMEOUT
     918        "timeout\0"          Required_argument "T"
     919#endif
     920        /* Ignored: */
     921        // "tries\0"            Required_argument "t"
     922        /* Ignored (we always use PASV): */
     923        "passive-ftp\0"      No_argument       "\xff"
     924        "header\0"           Required_argument "\xfe"
     925        "post-data\0"        Required_argument "\xfd"
     926        /* Ignored (we don't do ssl) */
     927        "no-check-certificate\0" No_argument   "\xfc"
     928        /* Ignored (we don't support caching) */
     929        "no-cache\0"         No_argument       "\xfb"
     930        ;
     931#endif
     932
     933#if ENABLE_FEATURE_WGET_LONG_OPTIONS
     934    llist_t *headers_llist = NULL;
     935#endif
     936
     937    INIT_G();
     938
     939    IF_FEATURE_WGET_TIMEOUT(G.timeout_seconds = 900;)
     940    G.proxy_flag = "on";   /* use proxies if env vars are set */
     941    G.user_agent = "Wget"; /* "User-Agent" header field */
     942
     943#if ENABLE_FEATURE_WGET_LONG_OPTIONS
     944    applet_long_options = wget_longopts;
     945#endif
     946    opt_complementary = "-1" IF_FEATURE_WGET_TIMEOUT(":T+") IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
     947    getopt32(argv, "csqO:P:Y:U:T:" /*ignored:*/ "t:",
     948        &G.fname_out, &G.dir_prefix,
     949        &G.proxy_flag, &G.user_agent,
     950        IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
     951        NULL /* -t RETRIES */
     952        IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
     953        IF_FEATURE_WGET_LONG_OPTIONS(, &G.post_data)
     954    );
     955    argv += optind;
     956
     957#if ENABLE_FEATURE_WGET_LONG_OPTIONS
     958    if (headers_llist) {
     959        int size = 1;
     960        char *cp;
     961        llist_t *ll = headers_llist;
     962        while (ll) {
     963            size += strlen(ll->data) + 2;
     964            ll = ll->link;
     965        }
     966        G.extra_headers = cp = xmalloc(size);
     967        while (headers_llist) {
     968            cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
     969        }
     970    }
     971#endif
     972
     973    G.output_fd = -1;
     974    G.o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
     975    if (G.fname_out) { /* -O FILE ? */
     976        if (LONE_DASH(G.fname_out)) { /* -O - ? */
     977            G.output_fd = 1;
     978            option_mask32 &= ~WGET_OPT_CONTINUE;
     979        }
    907980        /* compat with wget: -O FILE can overwrite */
    908         if (opt & WGET_OPT_OUTNAME)
    909             o_flags = O_WRONLY | O_CREAT | O_TRUNC;
    910         output_fd = xopen(fname_out, o_flags);
    911     }
    912 
    913     retrieve_file_data(dfp, output_fd);
    914     xclose(output_fd);
    915 
    916     if (dfp != sfp) {
    917         /* It's ftp. Close it properly */
    918         fclose(dfp);
    919         if (ftpcmd(NULL, NULL, sfp, buf) != 226)
    920             bb_error_msg_and_die("ftp error: %s", sanitize_string(buf+4));
    921         /* ftpcmd("QUIT", NULL, sfp, buf); - why bother? */
    922     }
     981        G.o_flags = O_WRONLY | O_CREAT | O_TRUNC;
     982    }
     983
     984    while (*argv)
     985        download_one_url(*argv++);
     986
     987    if (G.output_fd >= 0)
     988        xclose(G.output_fd);
    923989
    924990    return EXIT_SUCCESS;
Note: See TracChangeset for help on using the changeset viewer.