Module Name: src Committed By: christos Date: Sun Sep 11 20:49:27 UTC 2022
Modified Files: src/usr.bin/ftp: fetch.c Log Message: PR/57003: Handle relative URLs (patch by kim@) To generate a diff of this commit: cvs rdiff -u -r1.234 -r1.235 src/usr.bin/ftp/fetch.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/usr.bin/ftp/fetch.c diff -u src/usr.bin/ftp/fetch.c:1.234 src/usr.bin/ftp/fetch.c:1.235 --- src/usr.bin/ftp/fetch.c:1.234 Sun Aug 1 11:29:30 2021 +++ src/usr.bin/ftp/fetch.c Sun Sep 11 16:49:27 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: fetch.c,v 1.234 2021/08/01 15:29:30 andvar Exp $ */ +/* $NetBSD: fetch.c,v 1.235 2022/09/11 20:49:27 christos Exp $ */ /*- * Copyright (c) 1997-2015 The NetBSD Foundation, Inc. @@ -37,7 +37,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: fetch.c,v 1.234 2021/08/01 15:29:30 andvar Exp $"); +__RCSID("$NetBSD: fetch.c,v 1.235 2022/09/11 20:49:27 christos Exp $"); #endif /* not lint */ /* @@ -106,12 +106,13 @@ __dead static void timeouthttp(int); static int auth_url(const char *, char **, const struct authinfo *); static void base64_encode(const unsigned char *, size_t, unsigned char *); #endif -static int go_fetch(const char *); +static int go_fetch(const char *, struct urlinfo *); static int fetch_ftp(const char *); -static int fetch_url(const char *, const char *, char *, char *); +static int fetch_url(const char *, const char *, char *, char *, + struct urlinfo *); static const char *match_token(const char **, const char *); static int parse_url(const char *, const char *, struct urlinfo *, - struct authinfo *); + struct authinfo *, struct urlinfo *); static void url_decode(char *); static void freeauthinfo(struct authinfo *); static void freeurlinfo(struct urlinfo *); @@ -274,7 +275,7 @@ auth_url(const char *challenge, char **r scheme = "Basic"; /* only support Basic authentication */ gotpass = NULL; - DPRINTF("auth_url: challenge `%s'\n", challenge); + DPRINTF("%s: challenge `%s'\n", __func__, challenge); if (! match_token(&cp, scheme)) { warnx("Unsupported authentication challenge `%s'", @@ -336,7 +337,7 @@ auth_url(const char *challenge, char **r *response = ftp_malloc(rlen); (void)strlcpy(*response, scheme, rlen); len = strlcat(*response, " ", rlen); - /* use `clen - 1' to not encode the trailing NUL */ + /* use `clen - 1' to not encode the trailing NUL */ base64_encode((unsigned char *)clear, clen - 1, (unsigned char *)*response + len); memset(clear, 0, clen); @@ -367,7 +368,7 @@ base64_encode(const unsigned char *clear | ((clear[i + 1] >> 4) & 0x0f)]; *(cp++) = enc[((clear[i + 1] << 2) & 0x3c) | ((clear[i + 2] >> 6) & 0x03)]; - *(cp++) = enc[((clear[i + 2] ) & 0x3f)]; + *(cp++) = enc[((clear[i + 2] ) & 0x3f)]; } *cp = '\0'; while (i-- > len) @@ -400,6 +401,42 @@ url_decode(char *url) *q = '\0'; } +static const char * +get_port(const struct urlinfo *ui) +{ + + switch(ui->utype) { + case HTTP_URL_T: + return httpport; + case FTP_URL_T: + return ftpport; + case FILE_URL_T: + return ""; +#ifdef WITH_SSL + case HTTPS_URL_T: + return httpsport; +#endif + default: + return NULL; + } +} + +static int +use_relative(const struct urlinfo *ui) +{ + if (ui == NULL) + return 0; + switch (ui->utype) { + case HTTP_URL_T: + case FILE_URL_T: +#ifdef WITH_SSL + case HTTPS_URL_T: +#endif + return 1; + default: + return 0; + } +} /* * Parse URL of form (per RFC 3986): @@ -435,7 +472,7 @@ url_decode(char *url) static int parse_url(const char *url, const char *desc, struct urlinfo *ui, - struct authinfo *auth) + struct authinfo *auth, struct urlinfo *rui) { const char *origurl, *tport; char *cp, *ep, *thost; @@ -446,29 +483,26 @@ parse_url(const char *url, const char *d DPRINTF("parse_url: %s `%s'\n", desc, url); origurl = url; - tport = NULL; if (STRNEQUAL(url, HTTP_URL)) { url += sizeof(HTTP_URL) - 1; ui->utype = HTTP_URL_T; ui->portnum = HTTP_PORT; - tport = httpport; } else if (STRNEQUAL(url, FTP_URL)) { url += sizeof(FTP_URL) - 1; ui->utype = FTP_URL_T; ui->portnum = FTP_PORT; - tport = ftpport; } else if (STRNEQUAL(url, FILE_URL)) { url += sizeof(FILE_URL) - 1; ui->utype = FILE_URL_T; - tport = ""; #ifdef WITH_SSL } else if (STRNEQUAL(url, HTTPS_URL)) { url += sizeof(HTTPS_URL) - 1; ui->utype = HTTPS_URL_T; ui->portnum = HTTPS_PORT; - tport = httpsport; #endif + } else if (rui != NULL) { + copyurlinfo(ui, rui); } else { warnx("Invalid %s `%s'", desc, url); cleanup_parse_url: @@ -477,6 +511,7 @@ parse_url(const char *url, const char *d return (-1); } + if (*url == '\0') return (0); @@ -541,7 +576,8 @@ parse_url(const char *url, const char *d #endif /* INET6 */ if ((cp = strchr(thost, ':')) != NULL) *cp++ = '\0'; - ui->host = thost; + if (*thost != '\0') + ui->host = thost; /* look for [:port] */ if (cp != NULL) { @@ -556,7 +592,9 @@ parse_url(const char *url, const char *d } ui->portnum = nport; tport = cp; - } + } else + tport = get_port(ui); + if (tport != NULL) ui->port = ftp_strdup(tport); @@ -567,8 +605,8 @@ parse_url(const char *url, const char *d ui->path = ftp_strdup(emptypath); } - DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) " - "path `%s'\n", + DPRINTF("%s: user `%s' pass `%s' host %s port %s(%d) " + "path `%s'\n", __func__, STRorNULL(auth->user), STRorNULL(auth->pass), STRorNULL(ui->host), STRorNULL(ui->port), ui->portnum ? ui->portnum : -1, STRorNULL(ui->path)); @@ -581,7 +619,7 @@ sigjmp_buf httpabort; static int ftp_socket(const struct urlinfo *ui, void **ssl) { - struct addrinfo hints, *res, *res0 = NULL; + struct addrinfo hints, *res, *res0 = NULL; int error; int s; const char *host = ui->host; @@ -686,7 +724,7 @@ handle_noproxy(const char *host, in_port if (*cp == '\0') continue; if ((np = strrchr(cp, ':')) != NULL) { - *np++ = '\0'; + *np++ = '\0'; np_port = strtoul(np, &ep, 10); if (*np == '\0' || *ep != '\0') continue; @@ -718,7 +756,7 @@ handle_proxy(const char *url, const char } initurlinfo(&pui); - if (parse_url(penv, "proxy URL", &pui, pauth) == -1) + if (parse_url(penv, "proxy URL", &pui, pauth, NULL) == -1) return -1; if ((!IS_HTTP_TYPE(pui.utype) && pui.utype != FTP_URL_T) || @@ -889,9 +927,9 @@ print_connect(FETCH *fin, const struct u } #endif -#define C_OK 0 -#define C_CLEANUP 1 -#define C_IMPROPER 2 +#define C_OK 0 +#define C_CLEANUP 1 +#define C_IMPROPER 2 static int getresponseline(FETCH *fin, char *buf, size_t buflen, int *len) @@ -990,7 +1028,7 @@ parse_posinfo(const char **cp, struct po static void do_auth(int hcode, const char *url, const char *penv, struct authinfo *wauth, struct authinfo *pauth, char **auth, const char *message, - volatile int *rval) + volatile int *rval, struct urlinfo *ui) { struct authinfo aauth; char *response; @@ -1025,7 +1063,8 @@ do_auth(int hcode, const char *url, cons if (auth_url(*auth, &response, &aauth) == 0) { *rval = fetch_url(url, penv, hcode == 401 ? pauth->auth : response, - hcode == 401 ? response: wauth->auth); + hcode == 401 ? response : wauth->auth, + ui); memset(response, 0, strlen(response)); FREEPTR(response); } @@ -1036,12 +1075,12 @@ static int negotiate_connection(FETCH *fin, const char *url, const char *penv, struct posinfo *pi, time_t *mtime, struct authinfo *wauth, struct authinfo *pauth, volatile int *rval, volatile int *ischunked, - char **auth) + char **auth, struct urlinfo *ui) { int len, hcode, rv; char buf[FTPBUFLEN], *ep; const char *cp, *token; - char *location, *message; + char *location, *message; *auth = message = location = NULL; @@ -1156,18 +1195,19 @@ negotiate_connection(FETCH *fin, const c fprintf(ttyout, "Redirected via %s\n", location); *rval = fetch_url(url, location, - pauth->auth, wauth->auth); + pauth->auth, wauth->auth, ui); } else { if (verbose) fprintf(ttyout, "Redirected to %s\n", location); - *rval = go_fetch(location); + *rval = go_fetch(location, ui); } goto cleanup_fetch_url; #ifndef NO_AUTH case 401: case 407: - do_auth(hcode, url, penv, wauth, pauth, auth, message, rval); + do_auth(hcode, url, penv, wauth, pauth, auth, message, rval, + ui); goto cleanup_fetch_url; #endif default: @@ -1232,7 +1272,7 @@ connectmethod(FETCH *fin, const char *ur message = ftp_strdup(ep); break; } - + for (;;) { int len; if (getresponseline(fin, buf, sizeof(buf), &len) != C_OK) @@ -1261,7 +1301,8 @@ connectmethod(FETCH *fin, const char *ur break; #ifndef NO_AUTH case 407: - do_auth(hcode, url, penv, wauth, pauth, auth, message, rval); + do_auth(hcode, url, penv, wauth, pauth, auth, message, rval, + ui); goto cleanup_fetch_url; #endif default: @@ -1299,7 +1340,8 @@ out: * is still open (e.g, ftp xfer with trailing /) */ static int -fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth) +fetch_url(const char *url, const char *proxyenv, char *proxyauth, + char *wwwauth, struct urlinfo *rui) { sigfunc volatile oldint; sigfunc volatile oldpipe; @@ -1308,7 +1350,7 @@ fetch_url(const char *url, const char *p int volatile s; struct stat sb; int volatile isproxy; - int volatile rval, ischunked; + int volatile rval, ischunked; size_t flen; static size_t bufsize; static char *xferbuf; @@ -1319,7 +1361,7 @@ fetch_url(const char *url, const char *p char *volatile location; char *volatile message; char *volatile decodedpath; - struct authinfo wauth, pauth; + struct authinfo wauth, pauth; struct posinfo pi; off_t hashbytes; int (*volatile closefunc)(FILE *); @@ -1352,7 +1394,7 @@ fetch_url(const char *url, const char *p if (sigsetjmp(httpabort, 1)) goto cleanup_fetch_url; - if (parse_url(url, "URL", &ui, &wauth) == -1) + if (parse_url(url, "URL", &ui, &wauth, rui) == -1) goto cleanup_fetch_url; copyurlinfo(&oui, &ui); @@ -1368,7 +1410,7 @@ fetch_url(const char *url, const char *p rval = fetch_ftp(url); goto cleanup_fetch_url; } - if (!IS_HTTP_TYPE(ui.utype) || outfile == NULL) { + if (!IS_HTTP_TYPE(ui.utype) || outfile == NULL) { warnx("Invalid URL (no file after host) `%s'", url); goto cleanup_fetch_url; } @@ -1423,7 +1465,8 @@ fetch_url(const char *url, const char *p filesize = sb.st_size; } if (restart_point) { - if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) { + if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) + < 0) { warn("Can't seek to restart `%s'", decodedpath); goto cleanup_fetch_url; @@ -1535,7 +1578,7 @@ fetch_url(const char *url, const char *p switch (negotiate_connection(fin, url, penv, &pi, &mtime, &wauth, &pauth, &rval, &ischunked, - __UNVOLATILE(&auth))) { + __UNVOLATILE(&auth), &ui)) { case C_OK: break; case C_CLEANUP: @@ -1642,7 +1685,7 @@ fetch_url(const char *url, const char *p } /* - * XXX: Work around bug in Apache 1.3.9 and + * XXX: Work around bug in Apache 1.3.9 and * 1.3.11, which incorrectly put trailing * space after the chunk-size. */ @@ -1850,10 +1893,10 @@ fetch_ftp(const char *url) char dirbuf[4]; int dirhasglob, filehasglob, rval, transtype, xargc; int oanonftp, oautologin; - struct authinfo auth; + struct authinfo auth; struct urlinfo ui; - DPRINTF("fetch_ftp: `%s'\n", url); + DPRINTF("%s: `%s'\n", __func__, url); dir = file = NULL; rval = 1; transtype = TYPE_I; @@ -1862,7 +1905,7 @@ fetch_ftp(const char *url) initauthinfo(&auth, NULL); if (STRNEQUAL(url, FTP_URL)) { - if ((parse_url(url, "URL", &ui, &auth) == -1) || + if ((parse_url(url, "URL", &ui, &auth, NULL) == -1) || (auth.user != NULL && *auth.user == '\0') || EMPTYSTRING(ui.host)) { warnx("Invalid URL `%s'", url); @@ -1874,7 +1917,8 @@ fetch_ftp(const char *url) */ /* check for trailing ';type=[aid]' */ - if (! EMPTYSTRING(ui.path) && (cp = strrchr(ui.path, ';')) != NULL) { + if (! EMPTYSTRING(ui.path) + && (cp = strrchr(ui.path, ';')) != NULL) { if (strcasecmp(cp, ";type=a") == 0) transtype = TYPE_A; else if (strcasecmp(cp, ";type=i") == 0) @@ -1916,12 +1960,12 @@ fetch_ftp(const char *url) * If we are dealing with classic `[user@]host:[path]' syntax, * then a path of the form `/file' (resulting from input of the * form `host:/file') means that we should do "CWD /" before - * retrieving the file. So we set dir="/" and file="file". + * retrieving the file. So we set dir="/" and file="file". * * But if we are dealing with URLs like `ftp://host/path' then * a path of the form `/file' (resulting from a URL of the form * `ftp://host//file') means that we should do `CWD ' (with an - * empty argument) before retrieving the file. So we set + * empty argument) before retrieving the file. So we set * dir="" and file="file". * * If the path does not contain / at all, we set dir=NULL. @@ -1952,8 +1996,8 @@ fetch_ftp(const char *url) url_decode(file); /* but still don't url_decode(dir) */ } - DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s " - "path `%s' dir `%s' file `%s'\n", + DPRINTF("%s: user `%s' pass `%s' host %s port %s " + "path `%s' dir `%s' file `%s'\n", __func__, STRorNULL(auth.user), STRorNULL(auth.pass), STRorNULL(ui.host), STRorNULL(ui.port), STRorNULL(ui.path), STRorNULL(dir), STRorNULL(file)); @@ -2002,7 +2046,7 @@ fetch_ftp(const char *url) setbinary(1, xargv); break; default: - errx(1, "fetch_ftp: unknown transfer type %d", transtype); + errx(1, "%s: unknown transfer type %d", __func__, transtype); } /* @@ -2024,7 +2068,7 @@ fetch_ftp(const char *url) * (urltype is FTP_URL_T), then RFC 3986 says we need to * send a separate CWD command for each unescaped "/" * in the path, and we have to interpret %hex escaping - * *after* we find the slashes. It's possible to get + * *after* we find the slashes. It's possible to get * empty components here, (from multiple adjacent * slashes in the path) and RFC 3986 says that we should * still do `CWD ' (with a null argument) in such cases. @@ -2067,7 +2111,7 @@ fetch_ftp(const char *url) * "CWD /", "CWD foo", "CWD bar", "RETR file" * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar" * "CWD /foo", "CWD bar", "RETR file" - * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" + * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" * "CWD /foo/bar", "RETR file" * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL * "RETR /foo/bar/file" @@ -2084,7 +2128,7 @@ fetch_ftp(const char *url) url_decode(dir); } else nextpart = NULL; - DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n", + DPRINTF("%s: dir `%s', nextpart `%s'\n", __func__, STRorNULL(dir), STRorNULL(nextpart)); if (ui.utype == FTP_URL_T || *dir != '\0') { (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf)); @@ -2179,7 +2223,7 @@ fetch_ftp(const char *url) * is still open (e.g, ftp xfer with trailing /) */ static int -go_fetch(const char *url) +go_fetch(const char *url, struct urlinfo *rui) { char *proxyenv; char *p; @@ -2228,7 +2272,7 @@ go_fetch(const char *url) || STRNEQUAL(url, HTTPS_URL) #endif || STRNEQUAL(url, FILE_URL)) - return (fetch_url(url, NULL, NULL, NULL)); + return (fetch_url(url, NULL, NULL, NULL, rui)); /* * If it contains "://" but does not begin with ftp:// @@ -2243,13 +2287,20 @@ go_fetch(const char *url) errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url); /* + * Refer to previous urlinfo if provided. This makes relative + * redirects work. + */ + if (use_relative(rui)) + return fetch_url(url, NULL, NULL, NULL, rui); + + /* * Try FTP URL-style and host:file arguments next. * If ftpproxy is set with an FTP URL, use fetch_url() * Otherwise, use fetch_ftp(). */ proxyenv = getoptionvalue("ftp_proxy"); if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL)) - return (fetch_url(url, NULL, NULL, NULL)); + return (fetch_url(url, NULL, NULL, NULL, rui)); return (fetch_ftp(url)); } @@ -2292,7 +2343,7 @@ auto_fetch(int argc, char *argv[]) redirect_loop = 0; if (!anonftp) anonftp = 2; /* Handle "automatic" transfers. */ - rval = go_fetch(argv[argpos]); + rval = go_fetch(argv[argpos], NULL); if (outfile != NULL && strcmp(outfile, "-") != 0 && outfile[0] != '|') { FREEPTR(outfile); @@ -2331,7 +2382,7 @@ auto_put(int argc, char **argv, const ch pathsep = NULL; rval = 1; - DPRINTF("auto_put: target `%s'\n", uploadserver); + DPRINTF("%s: target `%s'\n", __func__, uploadserver); path = ftp_strdup(uploadserver); len = strlen(path); @@ -2340,7 +2391,7 @@ auto_put(int argc, char **argv, const ch * make sure we always pass a directory to auto_fetch */ if (argc > 1) { /* more than one file to upload */ - len = strlen(uploadserver) + 2; /* path + "/" + "\0" */ + len = strlen(uploadserver) + 2; /* path + "/" + "\0" */ free(path); path = (char *)ftp_malloc(len); (void)strlcpy(path, uploadserver, len); @@ -2364,7 +2415,7 @@ auto_put(int argc, char **argv, const ch uargc++; } } - DPRINTF("auto_put: URL `%s' argv[2] `%s'\n", + DPRINTF("%s: URL `%s' argv[2] `%s'\n", __func__, path, STRorNULL(uargv[2])); /* connect and cwd */