busybox/networking/wget.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * wget - retrieve a file using HTTP or FTP
   4 *
   5 * Chip Rosenthal Covad Communications <chip@laserlink.net>
   6 *
   7 */
   8
   9#include "libbb.h"
  10
  11struct host_info {
  12        // May be used if we ever will want to free() all xstrdup()s...
  13        /* char *allocated; */
  14        const char *path;
  15        const char *user;
  16        char       *host;
  17        int         port;
  18        smallint    is_ftp;
  19};
  20
  21
  22/* Globals (can be accessed from signal handlers) */
  23struct globals {
  24        off_t content_len;        /* Content-length of the file */
  25        off_t beg_range;          /* Range at which continue begins */
  26#if ENABLE_FEATURE_WGET_STATUSBAR
  27        off_t lastsize;
  28        off_t totalsize;
  29        off_t transferred;        /* Number of bytes transferred so far */
  30        const char *curfile;      /* Name of current file being transferred */
  31        unsigned lastupdate_sec;
  32        unsigned start_sec;
  33#endif
  34        smallint chunked;             /* chunked transfer encoding */
  35};
  36#define G (*(struct globals*)&bb_common_bufsiz1)
  37struct BUG_G_too_big {
  38        char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
  39};
  40#define content_len     (G.content_len    )
  41#define beg_range       (G.beg_range      )
  42#define lastsize        (G.lastsize       )
  43#define totalsize       (G.totalsize      )
  44#define transferred     (G.transferred    )
  45#define curfile         (G.curfile        )
  46#define lastupdate_sec  (G.lastupdate_sec )
  47#define start_sec       (G.start_sec      )
  48#define chunked         (G.chunked        )
  49#define INIT_G() do { } while (0)
  50
  51
  52#if ENABLE_FEATURE_WGET_STATUSBAR
  53enum {
  54        STALLTIME = 5                   /* Seconds when xfer considered "stalled" */
  55};
  56
  57static unsigned int getttywidth(void)
  58{
  59        unsigned width;
  60        get_terminal_width_height(0, &width, NULL);
  61        return width;
  62}
  63
  64static void progressmeter(int flag)
  65{
  66        /* We can be called from signal handler */
  67        int save_errno = errno;
  68        off_t abbrevsize;
  69        unsigned since_last_update, elapsed;
  70        unsigned ratio;
  71        int barlength, i;
  72
  73        if (flag == -1) { /* first call to progressmeter */
  74                start_sec = monotonic_sec();
  75                lastupdate_sec = start_sec;
  76                lastsize = 0;
  77                totalsize = content_len + beg_range; /* as content_len changes.. */
  78        }
  79
  80        ratio = 100;
  81        if (totalsize != 0 && !chunked) {
  82                /* long long helps to have it working even if !LFS */
  83                ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize);
  84                if (ratio > 100) ratio = 100;
  85        }
  86
  87        fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
  88
  89        barlength = getttywidth() - 49;
  90        if (barlength > 0) {
  91                /* god bless gcc for variable arrays :) */
  92                i = barlength * ratio / 100;
  93                {
  94                        char buf[i+1];
  95                        memset(buf, '*', i);
  96                        buf[i] = '\0';
  97                        fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
  98                }
  99        }
 100        i = 0;
 101        abbrevsize = transferred + beg_range;
 102        while (abbrevsize >= 100000) {
 103                i++;
 104                abbrevsize >>= 10;
 105        }
 106        /* see http://en.wikipedia.org/wiki/Tera */
 107        fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
 108
 109// Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
 110
 111        elapsed = monotonic_sec();
 112        since_last_update = elapsed - lastupdate_sec;
 113        if (transferred > lastsize) {
 114                lastupdate_sec = elapsed;
 115                lastsize = transferred;
 116                if (since_last_update >= STALLTIME) {
 117                        /* We "cut off" these seconds from elapsed time
 118                         * by adjusting start time */
 119                        start_sec += since_last_update;
 120                }
 121                since_last_update = 0; /* we are un-stalled now */
 122        }
 123        elapsed -= start_sec; /* now it's "elapsed since start" */
 124
 125        if (since_last_update >= STALLTIME) {
 126                fprintf(stderr, " - stalled -");
 127        } else {
 128                off_t to_download = totalsize - beg_range;
 129                if (transferred <= 0 || (int)elapsed <= 0 || transferred > to_download || chunked) {
 130                        fprintf(stderr, "--:--:-- ETA");
 131                } else {
 132                        /* to_download / (transferred/elapsed) - elapsed: */
 133                        int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed);
 134                        /* (long long helps to have working ETA even if !LFS) */
 135                        i = eta % 3600;
 136                        fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60);
 137                }
 138        }
 139
 140        if (flag == 0) {
 141                /* last call to progressmeter */
 142                alarm(0);
 143                transferred = 0;
 144                fputc('\n', stderr);
 145        } else {
 146                if (flag == -1) { /* first call to progressmeter */
 147                        signal_SA_RESTART_empty_mask(SIGALRM, progressmeter);
 148                }
 149                alarm(1);
 150        }
 151
 152        errno = save_errno;
 153}
 154/* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
 155 * much of which was blatantly stolen from openssh.  */
 156/*-
 157 * Copyright (c) 1992, 1993
 158 *      The Regents of the University of California.  All rights reserved.
 159 *
 160 * Redistribution and use in source and binary forms, with or without
 161 * modification, are permitted provided that the following conditions
 162 * are met:
 163 * 1. Redistributions of source code must retain the above copyright
 164 *    notice, this list of conditions and the following disclaimer.
 165 * 2. Redistributions in binary form must reproduce the above copyright
 166 *    notice, this list of conditions and the following disclaimer in the
 167 *    documentation and/or other materials provided with the distribution.
 168 *
 169 * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
 170 *              ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
 171 *
 172 * 4. Neither the name of the University nor the names of its contributors
 173 *    may be used to endorse or promote products derived from this software
 174 *    without specific prior written permission.
 175 *
 176 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 177 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 178 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 179 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 180 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 181 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 182 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 183 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 184 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 185 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 186 * SUCH DAMAGE.
 187 *
 188 */
 189#else /* FEATURE_WGET_STATUSBAR */
 190
 191static ALWAYS_INLINE void progressmeter(int flag UNUSED_PARAM) { }
 192
 193#endif
 194
 195
 196/* Read NMEMB bytes into PTR from STREAM.  Returns the number of bytes read,
 197 * and a short count if an eof or non-interrupt error is encountered.  */
 198static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
 199{
 200        size_t ret;
 201        char *p = (char*)ptr;
 202
 203        do {
 204                clearerr(stream);
 205                ret = fread(p, 1, nmemb, stream);
 206                p += ret;
 207                nmemb -= ret;
 208        } while (nmemb && ferror(stream) && errno == EINTR);
 209
 210        return p - (char*)ptr;
 211}
 212
 213/* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
 214 * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
 215static char *safe_fgets(char *s, int size, FILE *stream)
 216{
 217        char *ret;
 218
 219        do {
 220                clearerr(stream);
 221                ret = fgets(s, size, stream);
 222        } while (ret == NULL && ferror(stream) && errno == EINTR);
 223
 224        return ret;
 225}
 226
 227#if ENABLE_FEATURE_WGET_AUTHENTICATION
 228/* Base64-encode character string. buf is assumed to be char buf[512]. */
 229static char *base64enc_512(char buf[512], const char *str)
 230{
 231        unsigned len = strlen(str);
 232        if (len > 512/4*3 - 10) /* paranoia */
 233                len = 512/4*3 - 10;
 234        bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
 235        return buf;
 236}
 237#endif
 238
 239
 240static FILE *open_socket(len_and_sockaddr *lsa)
 241{
 242        FILE *fp;
 243
 244        /* glibc 2.4 seems to try seeking on it - ??! */
 245        /* hopefully it understands what ESPIPE means... */
 246        fp = fdopen(xconnect_stream(lsa), "r+");
 247        if (fp == NULL)
 248                bb_perror_msg_and_die("fdopen");
 249
 250        return fp;
 251}
 252
 253
 254static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
 255{
 256        int result;
 257        if (s1) {
 258                if (!s2) s2 = "";
 259                fprintf(fp, "%s%s\r\n", s1, s2);
 260                fflush(fp);
 261        }
 262
 263        do {
 264                char *buf_ptr;
 265
 266                if (fgets(buf, 510, fp) == NULL) {
 267                        bb_perror_msg_and_die("error getting response");
 268                }
 269                buf_ptr = strstr(buf, "\r\n");
 270                if (buf_ptr) {
 271                        *buf_ptr = '\0';
 272                }
 273        } while (!isdigit(buf[0]) || buf[3] != ' ');
 274
 275        buf[3] = '\0';
 276        result = xatoi_u(buf);
 277        buf[3] = ' ';
 278        return result;
 279}
 280
 281
 282static void parse_url(char *src_url, struct host_info *h)
 283{
 284        char *url, *p, *sp;
 285
 286        /* h->allocated = */ url = xstrdup(src_url);
 287
 288        if (strncmp(url, "http://", 7) == 0) {
 289                h->port = bb_lookup_port("http", "tcp", 80);
 290                h->host = url + 7;
 291                h->is_ftp = 0;
 292        } else if (strncmp(url, "ftp://", 6) == 0) {
 293                h->port = bb_lookup_port("ftp", "tcp", 21);
 294                h->host = url + 6;
 295                h->is_ftp = 1;
 296        } else
 297                bb_error_msg_and_die("not an http or ftp url: %s", url);
 298
 299        // FYI:
 300        // "Real" wget 'http://busybox.net?var=a/b' sends this request:
 301        //   'GET /?var=a/b HTTP 1.0'
 302        //   and saves 'index.html?var=a%2Fb' (we save 'b')
 303        // wget 'http://busybox.net?login=john@doe':
 304        //   request: 'GET /?login=john@doe HTTP/1.0'
 305        //   saves: 'index.html?login=john@doe' (we save '?login=john@doe')
 306        // wget 'http://busybox.net#test/test':
 307        //   request: 'GET / HTTP/1.0'
 308        //   saves: 'index.html' (we save 'test')
 309        //
 310        // We also don't add unique .N suffix if file exists...
 311        sp = strchr(h->host, '/');
 312        p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
 313        p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
 314        if (!sp) {
 315                h->path = "";
 316        } else if (*sp == '/') {
 317                *sp = '\0';
 318                h->path = sp + 1;
 319        } else { // '#' or '?'
 320                // http://busybox.net?login=john@doe is a valid URL
 321                // memmove converts to:
 322                // http:/busybox.nett?login=john@doe...
 323                memmove(h->host - 1, h->host, sp - h->host);
 324                h->host--;
 325                sp[-1] = '\0';
 326                h->path = sp;
 327        }
 328
 329        sp = strrchr(h->host, '@');
 330        h->user = NULL;
 331        if (sp != NULL) {
 332                h->user = h->host;
 333                *sp = '\0';
 334                h->host = sp + 1;
 335        }
 336
 337        sp = h->host;
 338}
 339
 340
 341static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
 342{
 343        char *s, *hdrval;
 344        int c;
 345
 346        /* *istrunc = 0; */
 347
 348        /* retrieve header line */
 349        if (fgets(buf, bufsiz, fp) == NULL)
 350                return NULL;
 351
 352        /* see if we are at the end of the headers */
 353        for (s = buf; *s == '\r'; ++s)
 354                continue;
 355        if (*s == '\n')
 356                return NULL;
 357
 358        /* convert the header name to lower case */
 359        for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
 360                *s = tolower(*s);
 361
 362        /* verify we are at the end of the header name */
 363        if (*s != ':')
 364                bb_error_msg_and_die("bad header line: %s", buf);
 365
 366        /* locate the start of the header value */
 367        *s++ = '\0';
 368        hdrval = skip_whitespace(s);
 369
 370        /* locate the end of header */
 371        while (*s && *s != '\r' && *s != '\n')
 372                ++s;
 373
 374        /* end of header found */
 375        if (*s) {
 376                *s = '\0';
 377                return hdrval;
 378        }
 379
 380        /* Rats! The buffer isn't big enough to hold the entire header value. */
 381        while (c = getc(fp), c != EOF && c != '\n')
 382                continue;
 383        /* *istrunc = 1; */
 384        return hdrval;
 385}
 386
 387
 388int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 389int wget_main(int argc UNUSED_PARAM, char **argv)
 390{
 391        char buf[512];
 392        struct host_info server, target;
 393        len_and_sockaddr *lsa;
 394        int status;
 395        int port;
 396        int try = 5;
 397        unsigned opt;
 398        char *str;
 399        char *proxy = 0;
 400        char *dir_prefix = NULL;
 401#if ENABLE_FEATURE_WGET_LONG_OPTIONS
 402        char *extra_headers = NULL;
 403        llist_t *headers_llist = NULL;
 404#endif
 405        FILE *sfp = NULL;               /* socket to web/ftp server         */
 406        FILE *dfp;                      /* socket to ftp server (data)      */
 407        char *fname_out;                /* where to direct output (-O)      */
 408        bool got_clen = 0;              /* got content-length: from server  */
 409        int output_fd = -1;
 410        bool use_proxy = 1;             /* Use proxies if env vars are set  */
 411        const char *proxy_flag = "on";  /* Use proxies if env vars are set  */
 412        const char *user_agent = "Wget";/* "User-Agent" header field        */
 413
 414        static const char keywords[] ALIGN1 =
 415                "content-length\0""transfer-encoding\0""chunked\0""location\0";
 416        enum {
 417                KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
 418        };
 419        enum {
 420                WGET_OPT_CONTINUE   = 0x1,
 421                WGET_OPT_SPIDER     = 0x2,
 422                WGET_OPT_QUIET      = 0x4,
 423                WGET_OPT_OUTNAME    = 0x8,
 424                WGET_OPT_PREFIX     = 0x10,
 425                WGET_OPT_PROXY      = 0x20,
 426                WGET_OPT_USER_AGENT = 0x40,
 427                WGET_OPT_PASSIVE    = 0x80,
 428                WGET_OPT_HEADER     = 0x100,
 429        };
 430#if ENABLE_FEATURE_WGET_LONG_OPTIONS
 431        static const char wget_longopts[] ALIGN1 =
 432                /* name, has_arg, val */
 433                "continue\0"         No_argument       "c"
 434                "spider\0"           No_argument       "s"
 435                "quiet\0"            No_argument       "q"
 436                "output-document\0"  Required_argument "O"
 437                "directory-prefix\0" Required_argument "P"
 438                "proxy\0"            Required_argument "Y"
 439                "user-agent\0"       Required_argument "U"
 440                "passive-ftp\0"      No_argument       "\xff"
 441                "header\0"           Required_argument "\xfe"
 442                ;
 443#endif
 444
 445        INIT_G();
 446
 447#if ENABLE_FEATURE_WGET_LONG_OPTIONS
 448        applet_long_options = wget_longopts;
 449#endif
 450        /* server.allocated = target.allocated = NULL; */
 451        opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
 452        opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
 453                                &fname_out, &dir_prefix,
 454                                &proxy_flag, &user_agent,
 455                                NULL, /* -t RETRIES */
 456                                NULL /* -T NETWORK_READ_TIMEOUT */
 457                                USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
 458                                );
 459        if (strcmp(proxy_flag, "off") == 0) {
 460                /* Use the proxy if necessary */
 461                use_proxy = 0;
 462        }
 463#if ENABLE_FEATURE_WGET_LONG_OPTIONS
 464        if (headers_llist) {
 465                int size = 1;
 466                char *cp;
 467                llist_t *ll = headers_llist;
 468                while (ll) {
 469                        size += strlen(ll->data) + 2;
 470                        ll = ll->link;
 471                }
 472                extra_headers = cp = xmalloc(size);
 473                while (headers_llist) {
 474                        cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
 475                }
 476        }
 477#endif
 478
 479        parse_url(argv[optind], &target);
 480        server.host = target.host;
 481        server.port = target.port;
 482
 483        /* Use the proxy if necessary */
 484        if (use_proxy) {
 485                proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
 486                if (proxy && *proxy) {
 487                        parse_url(proxy, &server);
 488                } else {
 489                        use_proxy = 0;
 490                }
 491        }
 492
 493        /* Guess an output filename, if there was no -O FILE */
 494        if (!(opt & WGET_OPT_OUTNAME)) {
 495                fname_out = bb_get_last_path_component_nostrip(target.path);
 496                /* handle "wget http://kernel.org//" */
 497                if (fname_out[0] == '/' || !fname_out[0])
 498                        fname_out = (char*)"index.html";
 499                /* -P DIR is considered only if there was no -O FILE */
 500                if (dir_prefix)
 501                        fname_out = concat_path_file(dir_prefix, fname_out);
 502        } else {
 503                if (LONE_DASH(fname_out)) {
 504                        /* -O - */
 505                        output_fd = 1;
 506                        opt &= ~WGET_OPT_CONTINUE;
 507                }
 508        }
 509#if ENABLE_FEATURE_WGET_STATUSBAR
 510        curfile = bb_get_last_path_component_nostrip(fname_out);
 511#endif
 512
 513        /* Impossible?
 514        if ((opt & WGET_OPT_CONTINUE) && !fname_out)
 515                bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */
 516
 517        /* Determine where to start transfer */
 518        if (opt & WGET_OPT_CONTINUE) {
 519                output_fd = open(fname_out, O_WRONLY);
 520                if (output_fd >= 0) {
 521                        beg_range = xlseek(output_fd, 0, SEEK_END);
 522                }
 523                /* File doesn't exist. We do not create file here yet.
 524                   We are not sure it exists on remove side */
 525        }
 526
 527        /* We want to do exactly _one_ DNS lookup, since some
 528         * sites (i.e. ftp.us.debian.org) use round-robin DNS
 529         * and we want to connect to only one IP... */
 530        lsa = xhost2sockaddr(server.host, server.port);
 531        if (!(opt & WGET_OPT_QUIET)) {
 532                fprintf(stderr, "Connecting to %s (%s)\n", server.host,
 533                                xmalloc_sockaddr2dotted(&lsa->u.sa));
 534                /* We leak result of xmalloc_sockaddr2dotted */
 535        }
 536
 537        if (use_proxy || !target.is_ftp) {
 538                /*
 539                 *  HTTP session
 540                 */
 541                do {
 542                        got_clen = 0;
 543                        chunked = 0;
 544
 545                        if (!--try)
 546                                bb_error_msg_and_die("too many redirections");
 547
 548                        /* Open socket to http server */
 549                        if (sfp) fclose(sfp);
 550                        sfp = open_socket(lsa);
 551
 552                        /* Send HTTP request.  */
 553                        if (use_proxy) {
 554                                fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
 555                                        target.is_ftp ? "f" : "ht", target.host,
 556                                        target.path);
 557                        } else {
 558                                fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
 559                        }
 560
 561                        fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
 562                                target.host, user_agent);
 563
 564#if ENABLE_FEATURE_WGET_AUTHENTICATION
 565                        if (target.user) {
 566                                fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
 567                                        base64enc_512(buf, target.user));
 568                        }
 569                        if (use_proxy && server.user) {
 570                                fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
 571                                        base64enc_512(buf, server.user));
 572                        }
 573#endif
 574
 575                        if (beg_range)
 576                                fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
 577#if ENABLE_FEATURE_WGET_LONG_OPTIONS
 578                        if (extra_headers)
 579                                fputs(extra_headers, sfp);
 580#endif
 581                        fprintf(sfp, "Connection: close\r\n\r\n");
 582
 583                        /*
 584                        * Retrieve HTTP response line and check for "200" status code.
 585                        */
 586 read_response:
 587                        if (fgets(buf, sizeof(buf), sfp) == NULL)
 588                                bb_error_msg_and_die("no response from server");
 589
 590                        str = buf;
 591                        str = skip_non_whitespace(str);
 592                        str = skip_whitespace(str);
 593                        // FIXME: no error check
 594                        // xatou wouldn't work: "200 OK"
 595                        status = atoi(str);
 596                        switch (status) {
 597                        case 0:
 598                        case 100:
 599                                while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
 600                                        /* eat all remaining headers */;
 601                                goto read_response;
 602                        case 200:
 603/*
 604Response 204 doesn't say "null file", it says "metadata
 605has changed but data didn't":
 606
 607"10.2.5 204 No Content
 608The server has fulfilled the request but does not need to return
 609an entity-body, and might want to return updated metainformation.
 610The response MAY include new or updated metainformation in the form
 611of entity-headers, which if present SHOULD be associated with
 612the requested variant.
 613
 614If the client is a user agent, it SHOULD NOT change its document
 615view from that which caused the request to be sent. This response
 616is primarily intended to allow input for actions to take place
 617without causing a change to the user agent's active document view,
 618although any new or updated metainformation SHOULD be applied
 619to the document currently in the user agent's active view.
 620
 621The 204 response MUST NOT include a message-body, and thus
 622is always terminated by the first empty line after the header fields."
 623
 624However, in real world it was observed that some web servers
 625(e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
 626*/
 627                        case 204:
 628                                break;
 629                        case 300:       /* redirection */
 630                        case 301:
 631                        case 302:
 632                        case 303:
 633                                break;
 634                        case 206:
 635                                if (beg_range)
 636                                        break;
 637                                /* fall through */
 638                        default:
 639                                /* Show first line only and kill any ESC tricks */
 640                                buf[strcspn(buf, "\n\r\x1b")] = '\0';
 641                                bb_error_msg_and_die("server returned error: %s", buf);
 642                        }
 643
 644                        /*
 645                         * Retrieve HTTP headers.
 646                         */
 647                        while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
 648                                /* gethdr did already convert the "FOO:" string to lowercase */
 649                                smalluint key = index_in_strings(keywords, *&buf) + 1;
 650                                if (key == KEY_content_length) {
 651                                        content_len = BB_STRTOOFF(str, NULL, 10);
 652                                        if (errno || content_len < 0) {
 653                                                bb_error_msg_and_die("content-length %s is garbage", str);
 654                                        }
 655                                        got_clen = 1;
 656                                        continue;
 657                                }
 658                                if (key == KEY_transfer_encoding) {
 659                                        if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
 660                                                bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
 661                                        chunked = got_clen = 1;
 662                                }
 663                                if (key == KEY_location) {
 664                                        if (str[0] == '/')
 665                                                /* free(target.allocated); */
 666                                                target.path = /* target.allocated = */ xstrdup(str+1);
 667                                        else {
 668                                                parse_url(str, &target);
 669                                                if (use_proxy == 0) {
 670                                                        server.host = target.host;
 671                                                        server.port = target.port;
 672                                                }
 673                                                free(lsa);
 674                                                lsa = xhost2sockaddr(server.host, server.port);
 675                                                break;
 676                                        }
 677                                }
 678                        }
 679                } while (status >= 300);
 680
 681                dfp = sfp;
 682
 683        } else {
 684
 685                /*
 686                 *  FTP session
 687                 */
 688                if (!target.user)
 689                        target.user = xstrdup("anonymous:busybox@");
 690
 691                sfp = open_socket(lsa);
 692                if (ftpcmd(NULL, NULL, sfp, buf) != 220)
 693                        bb_error_msg_and_die("%s", buf+4);
 694
 695                /*
 696                 * Splitting username:password pair,
 697                 * trying to log in
 698                 */
 699                str = strchr(target.user, ':');
 700                if (str)
 701                        *(str++) = '\0';
 702                switch (ftpcmd("USER ", target.user, sfp, buf)) {
 703                case 230:
 704                        break;
 705                case 331:
 706                        if (ftpcmd("PASS ", str, sfp, buf) == 230)
 707                                break;
 708                        /* fall through (failed login) */
 709                default:
 710                        bb_error_msg_and_die("ftp login: %s", buf+4);
 711                }
 712
 713                ftpcmd("TYPE I", NULL, sfp, buf);
 714
 715                /*
 716                 * Querying file size
 717                 */
 718                if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) {
 719                        content_len = BB_STRTOOFF(buf+4, NULL, 10);
 720                        if (errno || content_len < 0) {
 721                                bb_error_msg_and_die("SIZE value is garbage");
 722                        }
 723                        got_clen = 1;
 724                }
 725
 726                /*
 727                 * Entering passive mode
 728                 */
 729                if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
 730 pasv_error:
 731                        bb_error_msg_and_die("bad response to %s: %s", "PASV", buf);
 732                }
 733                // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
 734                // Server's IP is N1.N2.N3.N4 (we ignore it)
 735                // Server's port for data connection is P1*256+P2
 736                str = strrchr(buf, ')');
 737                if (str) str[0] = '\0';
 738                str = strrchr(buf, ',');
 739                if (!str) goto pasv_error;
 740                port = xatou_range(str+1, 0, 255);
 741                *str = '\0';
 742                str = strrchr(buf, ',');
 743                if (!str) goto pasv_error;
 744                port += xatou_range(str+1, 0, 255) * 256;
 745                set_nport(lsa, htons(port));
 746                dfp = open_socket(lsa);
 747
 748                if (beg_range) {
 749                        sprintf(buf, "REST %"OFF_FMT"d", beg_range);
 750                        if (ftpcmd(buf, NULL, sfp, buf) == 350)
 751                                content_len -= beg_range;
 752                }
 753
 754                if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
 755                        bb_error_msg_and_die("bad response to %s: %s", "RETR", buf);
 756        }
 757
 758        if (opt & WGET_OPT_SPIDER) {
 759                if (ENABLE_FEATURE_CLEAN_UP)
 760                        fclose(sfp);
 761                return EXIT_SUCCESS;
 762        }
 763
 764        /*
 765         * Retrieve file
 766         */
 767
 768        /* Do it before progressmeter (want to have nice error message) */
 769        if (output_fd < 0) {
 770                int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
 771                /* compat with wget: -O FILE can overwrite */
 772                if (opt & WGET_OPT_OUTNAME)
 773                        o_flags = O_WRONLY | O_CREAT | O_TRUNC;
 774                output_fd = xopen(fname_out, o_flags);
 775        }
 776
 777        if (!(opt & WGET_OPT_QUIET))
 778                progressmeter(-1);
 779
 780        if (chunked)
 781                goto get_clen;
 782
 783        /* Loops only if chunked */
 784        while (1) {
 785                while (content_len > 0 || !got_clen) {
 786                        int n;
 787                        unsigned rdsz = sizeof(buf);
 788
 789                        if (content_len < sizeof(buf) && (chunked || got_clen))
 790                                rdsz = (unsigned)content_len;
 791                        n = safe_fread(buf, rdsz, dfp);
 792                        if (n <= 0) {
 793                                if (ferror(dfp)) {
 794                                        /* perror will not work: ferror doesn't set errno */
 795                                        bb_error_msg_and_die(bb_msg_read_error);
 796                                }
 797                                break;
 798                        }
 799                        xwrite(output_fd, buf, n);
 800#if ENABLE_FEATURE_WGET_STATUSBAR
 801                        transferred += n;
 802#endif
 803                        if (got_clen)
 804                                content_len -= n;
 805                }
 806
 807                if (!chunked)
 808                        break;
 809
 810                safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
 811 get_clen:
 812                safe_fgets(buf, sizeof(buf), dfp);
 813                content_len = STRTOOFF(buf, NULL, 16);
 814                /* FIXME: error check? */
 815                if (content_len == 0)
 816                        break; /* all done! */
 817        }
 818
 819        if (!(opt & WGET_OPT_QUIET))
 820                progressmeter(0);
 821
 822        if ((use_proxy == 0) && target.is_ftp) {
 823                fclose(dfp);
 824                if (ftpcmd(NULL, NULL, sfp, buf) != 226)
 825                        bb_error_msg_and_die("ftp error: %s", buf+4);
 826                ftpcmd("QUIT", NULL, sfp, buf);
 827        }
 828
 829        return EXIT_SUCCESS;
 830}
 831