The "Location"-Header in a HTTP Redirect used to require a full URL, but as of RFC 7231, relative references are also allowed.
ftp(1) does not understand this; the following patch addresses that issue. Comments? diff --git a/usr.bin/ftp/fetch.c b/usr.bin/ftp/fetch.c index d5b13b6..32f0368 100644 --- a/usr.bin/ftp/fetch.c +++ b/usr.bin/ftp/fetch.c @@ -115,6 +115,7 @@ static int parse_url(const char *, const char *, struct urlinfo *, static void url_decode(char *); static void freeauthinfo(struct authinfo *); static void freeurlinfo(struct urlinfo *); +static int isfullurl(const char *); static int redirect_loop; @@ -1010,9 +1011,29 @@ negotiate_connection(FETCH *fin, const char *url, const char *penv, getmtime(cp, mtime); } else if (match_token(&cp, "Location:")) { - location = ftp_strdup(cp); + if (!isfullurl(cp)) { + /* Redirect using "relative reference", + * RFC 7231 7.1.2. The destination is just a + * path, which may be absolute or relative */ + + /* This is going to be the base URL */ + char *bu = ftp_strdup(url); + + /* Cut off URL at the first slash if we're + * being redirected using an absolute path, or + * at the last slash if relative. */ + if (cp[0] == '/') + *strchr(strstr(bu, "//")+2, '/') = '\0'; + else + *strrchr(bu, '/') = '\0'; + + location = malloc(strlen(bu) + strlen(cp) + 1); + sprintf(location, "%s%s", bu, cp); + free(bu); + } else + location = ftp_strdup(cp); DPRINTF("%s: parsed location as `%s'\n", - __func__, cp); + __func__, location); } else if (match_token(&cp, "Transfer-Encoding:")) { if (match_token(&cp, "binary")) { @@ -2334,3 +2355,14 @@ auto_put(int argc, char **argv, const char *uploadserver) FREEPTR(uargv[2]); return (rval); } + +/* Determine whether the given string is an URL (starting with <scheme>:) + * or a lone path (be it absolute or relative) */ +static int +isfullurl(const char *url) +{ + size_t slash = strcspn(url, "/"); + size_t colon = strcspn(url, ":"); + + return slash > colon; +}