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 */

Reply via email to