Index: fetch.c
===================================================================
RCS file: /cvsroot/src/usr.bin/ftp/fetch.c,v
retrieving revision 1.234
diff -u -p -r1.234 fetch.c
--- fetch.c	1 Aug 2021 15:29:30 -0000	1.234
+++ fetch.c	11 Sep 2022 16:36:47 -0000
@@ -106,12 +106,12 @@ __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 *);
@@ -435,7 +435,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;
@@ -469,6 +469,26 @@ parse_url(const char *url, const char *d
 		ui->portnum = HTTPS_PORT;
 		tport = httpsport;
 #endif
+	} else if (rui != NULL) {
+		copyurlinfo(ui, rui);
+		switch(ui->utype) {
+		case HTTP_URL_T:
+			tport = httpport;
+			break;
+		case FTP_URL_T:
+			tport = ftpport;
+			break;
+		case FILE_URL_T:
+			tport = "";
+			break;
+#ifdef WITH_SSL
+		case HTTPS_URL_T:
+			tport = httpsport;
+			break;
+#endif
+		default:
+			break;
+		}
 	} else {
 		warnx("Invalid %s `%s'", desc, url);
  cleanup_parse_url:
@@ -541,7 +561,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) {
@@ -718,7 +739,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) ||
@@ -990,7 +1011,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 +1046,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,7 +1058,7 @@ 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;
@@ -1156,18 +1178,18 @@ 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:
@@ -1261,7 +1283,7 @@ 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 +1321,7 @@ 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;
@@ -1352,7 +1374,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);
@@ -1535,7 +1557,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:
@@ -1862,7 +1884,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);
@@ -2179,7 +2201,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 +2250,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 +2265,26 @@ 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 (rui != NULL) {
+		if ((rui->utype == HTTP_URL_T)
+#ifdef WITH_SSL
+		    || (rui->utype == HTTPS_URL_T)
+#endif
+		    || (rui->utype == FILE_URL_T))
+			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 +2327,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);
