diff --git a/lib/curl_rtmp.c b/lib/curl_rtmp.c index 59fcc4e1ff..caa92e7af3 100644 --- a/lib/curl_rtmp.c +++ b/lib/curl_rtmp.c @@ -85,6 +85,7 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMP, /* defport */ CURLPROTO_RTMP, /* protocol */ CURLPROTO_RTMP, /* family */ @@ -109,6 +110,7 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPT, /* defport */ CURLPROTO_RTMPT, /* protocol */ CURLPROTO_RTMPT, /* family */ @@ -133,6 +135,7 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMP, /* defport */ CURLPROTO_RTMPE, /* protocol */ CURLPROTO_RTMPE, /* family */ @@ -157,6 +160,7 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPT, /* defport */ CURLPROTO_RTMPTE, /* protocol */ CURLPROTO_RTMPTE, /* family */ @@ -181,6 +185,7 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPS, /* defport */ CURLPROTO_RTMPS, /* protocol */ CURLPROTO_RTMP, /* family */ @@ -205,6 +210,7 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_RTMPS, /* defport */ CURLPROTO_RTMPTS, /* protocol */ CURLPROTO_RTMPT, /* family */ diff --git a/lib/dict.c b/lib/dict.c index 7d9c18dc9d..143b76f519 100644 --- a/lib/dict.c +++ b/lib/dict.c @@ -93,6 +93,7 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_DICT, /* defport */ CURLPROTO_DICT, /* protocol */ CURLPROTO_DICT, /* family */ diff --git a/lib/file.c b/lib/file.c index d753a3d1c3..7751acf40f 100644 --- a/lib/file.c +++ b/lib/file.c @@ -120,6 +120,7 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ 0, /* defport */ CURLPROTO_FILE, /* protocol */ CURLPROTO_FILE, /* family */ diff --git a/lib/ftp.c b/lib/ftp.c index 5afa67a2ea..4b52773817 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -250,6 +250,7 @@ const struct Curl_handler Curl_handler_ftp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_FTP, /* defport */ CURLPROTO_FTP, /* protocol */ CURLPROTO_FTP, /* family */ @@ -282,6 +283,7 @@ const struct Curl_handler Curl_handler_ftps = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_FTPS, /* defport */ CURLPROTO_FTPS, /* protocol */ CURLPROTO_FTP, /* family */ diff --git a/lib/gopher.c b/lib/gopher.c index 051e6e7ab5..00d42f0ec7 100644 --- a/lib/gopher.c +++ b/lib/gopher.c @@ -79,6 +79,7 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_GOPHER, /* defport */ CURLPROTO_GOPHER, /* protocol */ CURLPROTO_GOPHER, /* family */ @@ -104,6 +105,7 @@ const struct Curl_handler Curl_handler_gophers = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_GOPHER, /* defport */ CURLPROTO_GOPHERS, /* protocol */ CURLPROTO_GOPHER, /* family */ diff --git a/lib/http.c b/lib/http.c index cef73a5781..1fe0904998 100644 --- a/lib/http.c +++ b/lib/http.c @@ -64,6 +64,7 @@ #include "http_negotiate.h" #include "http_aws_sigv4.h" #include "url.h" +#include "urlapi-int.h" #include "share.h" #include "hostip.h" #include "dynhds.h" @@ -145,6 +146,7 @@ const struct Curl_handler Curl_handler_http = { Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_HTTP, /* defport */ CURLPROTO_HTTP, /* protocol */ CURLPROTO_HTTP, /* family */ @@ -174,6 +176,7 @@ const struct Curl_handler Curl_handler_https = { Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_HTTPS, /* defport */ CURLPROTO_HTTPS, /* protocol */ CURLPROTO_HTTP, /* family */ @@ -1091,6 +1094,283 @@ static bool http_should_fail(struct Curl_easy *data, int httpcode) return data->state.authproblem; } +CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, + followtype type) +{ + bool disallowport = FALSE; + bool reachedmax = FALSE; + char *follow_url = NULL; + CURLUcode uc; + + DEBUGASSERT(type != FOLLOW_NONE); + + if(type != FOLLOW_FAKE) + data->state.requests++; /* count all real follows */ + if(type == FOLLOW_REDIR) { + if((data->set.maxredirs != -1) && + (data->state.followlocation >= data->set.maxredirs)) { + reachedmax = TRUE; + type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected + to URL */ + } + else { + data->state.followlocation++; /* count redirect-followings, including + auth reloads */ + + if(data->set.http_auto_referer) { + CURLU *u; + char *referer = NULL; + + /* We are asked to automatically set the previous URL as the referer + when we get the next URL. We pick the ->url field, which may or may + not be 100% correct */ + + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } + + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; + + uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &referer, 0); + + curl_url_cleanup(u); + + if(uc || !referer) + return CURLE_OUT_OF_MEMORY; + + data->state.referer = referer; + data->state.referer_alloc = TRUE; /* yes, free this later */ + } + } + } + + if((type != FOLLOW_RETRY) && + (data->req.httpcode != 401) && (data->req.httpcode != 407) && + Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { + /* If this is not redirect due to a 401 or 407 response and an absolute + URL: do not allow a custom port number */ + disallowport = TRUE; + } + + DEBUGASSERT(data->state.uh); + uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) + ((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : + ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | + CURLU_ALLOW_SPACE | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); + if(uc) { + if(type != FOLLOW_FAKE) { + failf(data, "The redirect target URL could not be parsed: %s", + curl_url_strerror(uc)); + return Curl_uc_to_curlcode(uc); + } + + /* the URL could not be parsed for some reason, but since this is FAKE + mode, just duplicate the field as-is */ + follow_url = strdup(newurl); + if(!follow_url) + return CURLE_OUT_OF_MEMORY; + } + else { + uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + + /* Clear auth if this redirects to a different port number or protocol, + unless permitted */ + if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { + char *portnum; + int port; + bool clear = FALSE; + + if(data->set.use_port && data->state.allow_port) + /* a custom port is used */ + port = (int)data->set.use_port; + else { + uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, + CURLU_DEFAULT_PORT); + if(uc) { + free(follow_url); + return Curl_uc_to_curlcode(uc); + } + port = atoi(portnum); + free(portnum); + } + if(port != data->info.conn_remote_port) { + infof(data, "Clear auth, redirects to port from %u to %u", + data->info.conn_remote_port, port); + clear = TRUE; + } + else { + char *scheme; + const struct Curl_handler *p; + uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); + if(uc) { + free(follow_url); + return Curl_uc_to_curlcode(uc); + } + + p = Curl_get_scheme_handler(scheme); + if(p && (p->protocol != data->info.conn_protocol)) { + infof(data, "Clear auth, redirects scheme from %s to %s", + data->info.conn_scheme, scheme); + clear = TRUE; + } + free(scheme); + } + if(clear) { + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); + } + } + } + DEBUGASSERT(follow_url); + + if(type == FOLLOW_FAKE) { + /* we are only figuring out the new URL if we would have followed locations + but now we are done so we can get out! */ + data->info.wouldredirect = follow_url; + + if(reachedmax) { + failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; + } + return CURLE_OK; + } + + if(disallowport) + data->state.allow_port = FALSE; + + if(data->state.url_alloc) + Curl_safefree(data->state.url); + + data->state.url = follow_url; + data->state.url_alloc = TRUE; + Curl_req_soft_reset(&data->req, data); + infof(data, "Issue another request to this URL: '%s'", data->state.url); + + /* + * We get here when the HTTP code is 300-399 (and 401). We need to perform + * differently based on exactly what return code there was. + * + * News from 7.10.6: we can also get here on a 401 or 407, in case we act on + * an HTTP (proxy-) authentication scheme other than Basic. + */ + switch(data->info.httpcode) { + /* 401 - Act on a WWW-Authenticate, we keep on moving and do the + Authorization: XXXX header in the HTTP request code snippet */ + /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the + Proxy-Authorization: XXXX header in the HTTP request code snippet */ + /* 300 - Multiple Choices */ + /* 306 - Not used */ + /* 307 - Temporary Redirect */ + default: /* for all above (and the unknown ones) */ + /* Some codes are explicitly mentioned since I have checked RFC2616 and + * they seem to be OK to POST to. + */ + break; + case 301: /* Moved Permanently */ + /* (quote from RFC7231, section 6.4.2) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_301)) { + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); + } + break; + case 302: /* Found */ + /* (quote from RFC7231, section 6.4.3) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_302)) { + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); + } + break; + + case 303: /* See Other */ + /* 'See Other' location is not the resource but a substitute for the + * resource. In this case we switch the method to GET/HEAD, unless the + * method is POST and the user specified to keep it as POST. + * https://github.com/curl/curl/issues/5237#issuecomment-614641049 + */ + if(data->state.httpreq != HTTPREQ_GET && + ((data->state.httpreq != HTTPREQ_POST && + data->state.httpreq != HTTPREQ_POST_FORM && + data->state.httpreq != HTTPREQ_POST_MIME) || + !(data->set.keep_post & CURL_REDIR_POST_303))) { + data->state.httpreq = HTTPREQ_GET; + infof(data, "Switch to %s", + data->req.no_body ? "HEAD" : "GET"); + } + break; + case 304: /* Not Modified */ + /* 304 means we did a conditional request and it was "Not modified". + * We should not get any Location: header in this response! + */ + break; + case 305: /* Use Proxy */ + /* (quote from RFC2616, section 10.3.6): + * "The requested resource MUST be accessed through the proxy given + * by the Location field. The Location field gives the URI of the + * proxy. The recipient is expected to repeat this single request + * via the proxy. 305 responses MUST only be generated by origin + * servers." + */ + break; + } + Curl_pgrsTime(data, TIMER_REDIRECT); + Curl_pgrsResetTransferSizes(data); + + return CURLE_OK; +} + /* * Curl_compareheader() * diff --git a/lib/http.h b/lib/http.h index d12813a6ff..a15a982356 100644 --- a/lib/http.h +++ b/lib/http.h @@ -42,6 +42,18 @@ typedef enum { HTTPREQ_HEAD } Curl_HttpReq; + +/* When redirecting transfers. */ +typedef enum { + FOLLOW_NONE, /* not used within the function, just a placeholder to + allow initing to this */ + FOLLOW_FAKE, /* only records stuff, not actually following */ + FOLLOW_RETRY, /* set if this is a request retry as opposed to a real + redirect following */ + FOLLOW_REDIR /* a full true redirect */ +} followtype; + + #ifndef CURL_DISABLE_HTTP #if defined(USE_HTTP3) @@ -103,6 +115,10 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth); CURLcode Curl_http_auth_act(struct Curl_easy *data); +/* follow a redirect or not */ +CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, + followtype type); + /* If only the PICKNONE bit is set, there has been a round-trip and we selected to use no auth at all. Ie, we actively select no auth, as opposed to not having one selected. The other CURLAUTH_* defines are present in the diff --git a/lib/imap.c b/lib/imap.c index df9dc343b5..e5ee40145d 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -134,6 +134,7 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_IMAP, /* defport */ CURLPROTO_IMAP, /* protocol */ CURLPROTO_IMAP, /* family */ @@ -164,6 +165,7 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_IMAPS, /* defport */ CURLPROTO_IMAPS, /* protocol */ CURLPROTO_IMAP, /* family */ diff --git a/lib/ldap.c b/lib/ldap.c index 7bba2162b5..4eeb013070 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -187,6 +187,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ CURLPROTO_LDAP, /* family */ @@ -216,6 +217,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAPS, /* defport */ CURLPROTO_LDAPS, /* protocol */ CURLPROTO_LDAP, /* family */ diff --git a/lib/mqtt.c b/lib/mqtt.c index 69eaf344ae..fe242c7784 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -92,6 +92,7 @@ const struct Curl_handler Curl_handler_mqtt = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_MQTT, /* defport */ CURLPROTO_MQTT, /* protocol */ CURLPROTO_MQTT, /* family */ diff --git a/lib/multi.c b/lib/multi.c index 6123f50dab..2c3ddb556f 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1855,289 +1855,13 @@ static void multi_posttransfer(struct Curl_easy *data) * This function DOES NOT FREE the given url. */ static CURLcode multi_follow(struct Curl_easy *data, - char *newurl, /* the Location: string */ + const struct Curl_handler *handler, + const char *newurl, /* the Location: string */ followtype type) /* see transfer.h */ { -#ifdef CURL_DISABLE_HTTP - (void)data; - (void)newurl; - (void)type; - /* Location: following will not happen when HTTP is disabled */ + if(handler && handler->follow) + return handler->follow(data, newurl, type); return CURLE_TOO_MANY_REDIRECTS; -#else - - /* Location: redirect */ - bool disallowport = FALSE; - bool reachedmax = FALSE; - CURLUcode uc; - - DEBUGASSERT(type != FOLLOW_NONE); - - if(type != FOLLOW_FAKE) - data->state.requests++; /* count all real follows */ - if(type == FOLLOW_REDIR) { - if((data->set.maxredirs != -1) && - (data->state.followlocation >= data->set.maxredirs)) { - reachedmax = TRUE; - type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected - to URL */ - } - else { - data->state.followlocation++; /* count redirect-followings, including - auth reloads */ - - if(data->set.http_auto_referer) { - CURLU *u; - char *referer = NULL; - - /* We are asked to automatically set the previous URL as the referer - when we get the next URL. We pick the ->url field, which may or may - not be 100% correct */ - - if(data->state.referer_alloc) { - Curl_safefree(data->state.referer); - data->state.referer_alloc = FALSE; - } - - /* Make a copy of the URL without credentials and fragment */ - u = curl_url(); - if(!u) - return CURLE_OUT_OF_MEMORY; - - uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_USER, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); - if(!uc) - uc = curl_url_get(u, CURLUPART_URL, &referer, 0); - - curl_url_cleanup(u); - - if(uc || !referer) - return CURLE_OUT_OF_MEMORY; - - data->state.referer = referer; - data->state.referer_alloc = TRUE; /* yes, free this later */ - } - } - } - - if((type != FOLLOW_RETRY) && - (data->req.httpcode != 401) && (data->req.httpcode != 407) && - Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { - /* If this is not redirect due to a 401 or 407 response and an absolute - URL: do not allow a custom port number */ - disallowport = TRUE; - } - - DEBUGASSERT(data->state.uh); - uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) - ((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : - ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | - CURLU_ALLOW_SPACE | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); - if(uc) { - if(type != FOLLOW_FAKE) { - failf(data, "The redirect target URL could not be parsed: %s", - curl_url_strerror(uc)); - return Curl_uc_to_curlcode(uc); - } - - /* the URL could not be parsed for some reason, but since this is FAKE - mode, just duplicate the field as-is */ - newurl = strdup(newurl); - if(!newurl) - return CURLE_OUT_OF_MEMORY; - } - else { - uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0); - if(uc) - return Curl_uc_to_curlcode(uc); - - /* Clear auth if this redirects to a different port number or protocol, - unless permitted */ - if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { - char *portnum; - int port; - bool clear = FALSE; - - if(data->set.use_port && data->state.allow_port) - /* a custom port is used */ - port = (int)data->set.use_port; - else { - uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, - CURLU_DEFAULT_PORT); - if(uc) { - free(newurl); - return Curl_uc_to_curlcode(uc); - } - port = atoi(portnum); - free(portnum); - } - if(port != data->info.conn_remote_port) { - infof(data, "Clear auth, redirects to port from %u to %u", - data->info.conn_remote_port, port); - clear = TRUE; - } - else { - char *scheme; - const struct Curl_handler *p; - uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); - if(uc) { - free(newurl); - return Curl_uc_to_curlcode(uc); - } - - p = Curl_get_scheme_handler(scheme); - if(p && (p->protocol != data->info.conn_protocol)) { - infof(data, "Clear auth, redirects scheme from %s to %s", - data->info.conn_scheme, scheme); - clear = TRUE; - } - free(scheme); - } - if(clear) { - Curl_safefree(data->state.aptr.user); - Curl_safefree(data->state.aptr.passwd); - } - } - } - - if(type == FOLLOW_FAKE) { - /* we are only figuring out the new URL if we would have followed locations - but now we are done so we can get out! */ - data->info.wouldredirect = newurl; - - if(reachedmax) { - failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; - } - return CURLE_OK; - } - - if(disallowport) - data->state.allow_port = FALSE; - - if(data->state.url_alloc) - Curl_safefree(data->state.url); - - data->state.url = newurl; - data->state.url_alloc = TRUE; - Curl_req_soft_reset(&data->req, data); - infof(data, "Issue another request to this URL: '%s'", data->state.url); - - /* - * We get here when the HTTP code is 300-399 (and 401). We need to perform - * differently based on exactly what return code there was. - * - * News from 7.10.6: we can also get here on a 401 or 407, in case we act on - * an HTTP (proxy-) authentication scheme other than Basic. - */ - switch(data->info.httpcode) { - /* 401 - Act on a WWW-Authenticate, we keep on moving and do the - Authorization: XXXX header in the HTTP request code snippet */ - /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the - Proxy-Authorization: XXXX header in the HTTP request code snippet */ - /* 300 - Multiple Choices */ - /* 306 - Not used */ - /* 307 - Temporary Redirect */ - default: /* for all above (and the unknown ones) */ - /* Some codes are explicitly mentioned since I have checked RFC2616 and - * they seem to be OK to POST to. - */ - break; - case 301: /* Moved Permanently */ - /* (quote from RFC7231, section 6.4.2) - * - * Note: For historical reasons, a user agent MAY change the request - * method from POST to GET for the subsequent request. If this - * behavior is undesired, the 307 (Temporary Redirect) status code - * can be used instead. - * - * ---- - * - * Many webservers expect this, so these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and - * can be overridden with CURLOPT_POSTREDIR. - */ - if((data->state.httpreq == HTTPREQ_POST - || data->state.httpreq == HTTPREQ_POST_FORM - || data->state.httpreq == HTTPREQ_POST_MIME) - && !(data->set.keep_post & CURL_REDIR_POST_301)) { - infof(data, "Switch from POST to GET"); - data->state.httpreq = HTTPREQ_GET; - Curl_creader_set_rewind(data, FALSE); - } - break; - case 302: /* Found */ - /* (quote from RFC7231, section 6.4.3) - * - * Note: For historical reasons, a user agent MAY change the request - * method from POST to GET for the subsequent request. If this - * behavior is undesired, the 307 (Temporary Redirect) status code - * can be used instead. - * - * ---- - * - * Many webservers expect this, so these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and - * can be overridden with CURLOPT_POSTREDIR. - */ - if((data->state.httpreq == HTTPREQ_POST - || data->state.httpreq == HTTPREQ_POST_FORM - || data->state.httpreq == HTTPREQ_POST_MIME) - && !(data->set.keep_post & CURL_REDIR_POST_302)) { - infof(data, "Switch from POST to GET"); - data->state.httpreq = HTTPREQ_GET; - Curl_creader_set_rewind(data, FALSE); - } - break; - - case 303: /* See Other */ - /* 'See Other' location is not the resource but a substitute for the - * resource. In this case we switch the method to GET/HEAD, unless the - * method is POST and the user specified to keep it as POST. - * https://github.com/curl/curl/issues/5237#issuecomment-614641049 - */ - if(data->state.httpreq != HTTPREQ_GET && - ((data->state.httpreq != HTTPREQ_POST && - data->state.httpreq != HTTPREQ_POST_FORM && - data->state.httpreq != HTTPREQ_POST_MIME) || - !(data->set.keep_post & CURL_REDIR_POST_303))) { - data->state.httpreq = HTTPREQ_GET; - infof(data, "Switch to %s", - data->req.no_body ? "HEAD" : "GET"); - } - break; - case 304: /* Not Modified */ - /* 304 means we did a conditional request and it was "Not modified". - * We should not get any Location: header in this response! - */ - break; - case 305: /* Use Proxy */ - /* (quote from RFC2616, section 10.3.6): - * "The requested resource MUST be accessed through the proxy given - * by the Location field. The Location field gives the URI of the - * proxy. The recipient is expected to repeat this single request - * via the proxy. 305 responses MUST only be generated by origin - * servers." - */ - break; - } - Curl_pgrsTime(data, TIMER_REDIRECT); - Curl_pgrsResetTransferSizes(data); - - return CURLE_OK; -#endif /* CURL_DISABLE_HTTP */ } static CURLMcode state_performing(struct Curl_easy *data, @@ -2236,6 +1960,7 @@ static CURLMcode state_performing(struct Curl_easy *data, multi_done(data, result, TRUE); } else if(data->req.done && !Curl_cwriter_is_paused(data)) { + const struct Curl_handler *handler = data->conn->handler; /* call this even if the readwrite function returned error */ multi_posttransfer(data); @@ -2256,7 +1981,7 @@ static CURLMcode state_performing(struct Curl_easy *data, follow = FOLLOW_RETRY; (void)multi_done(data, CURLE_OK, FALSE); /* multi_done() might return CURLE_GOT_NOTHING */ - result = multi_follow(data, newurl, follow); + result = multi_follow(data, handler, newurl, follow); if(!result) { multistate(data, MSTATE_SETUP); rc = CURLM_CALL_MULTI_PERFORM; @@ -2271,7 +1996,7 @@ static CURLMcode state_performing(struct Curl_easy *data, free(newurl); newurl = data->req.location; data->req.location = NULL; - result = multi_follow(data, newurl, FOLLOW_FAKE); + result = multi_follow(data, handler, newurl, FOLLOW_FAKE); if(result) { *stream_errorp = TRUE; result = multi_done(data, result, TRUE); @@ -2380,6 +2105,7 @@ static CURLMcode state_do(struct Curl_easy *data, * unexpectedly died. If possible, send the connection back to the * CONNECT phase so we can try again. */ + const struct Curl_handler *handler = data->conn->handler; char *newurl = NULL; followtype follow = FOLLOW_NONE; CURLcode drc; @@ -2399,7 +2125,7 @@ static CURLMcode state_do(struct Curl_easy *data, if(newurl) { if(!drc || (drc == CURLE_SEND_ERROR)) { follow = FOLLOW_RETRY; - drc = multi_follow(data, newurl, follow); + drc = multi_follow(data, handler, newurl, follow); if(!drc) { multistate(data, MSTATE_SETUP); rc = CURLM_CALL_MULTI_PERFORM; diff --git a/lib/openldap.c b/lib/openldap.c index 9676ad3d0d..22e5bdd279 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -134,6 +134,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ CURLPROTO_LDAP, /* family */ @@ -163,6 +164,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_LDAPS, /* defport */ CURLPROTO_LDAPS, /* protocol */ CURLPROTO_LDAP, /* family */ diff --git a/lib/pop3.c b/lib/pop3.c index 83dd64cdad..8750374d0d 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -138,6 +138,7 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_POP3, /* defport */ CURLPROTO_POP3, /* protocol */ CURLPROTO_POP3, /* family */ @@ -168,6 +169,7 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_POP3S, /* defport */ CURLPROTO_POP3S, /* protocol */ CURLPROTO_POP3, /* family */ diff --git a/lib/rtsp.c b/lib/rtsp.c index e26586cace..4325240ee6 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -117,6 +117,7 @@ const struct Curl_handler Curl_handler_rtsp = { ZERO_NULL, /* write_resp_hd */ rtsp_conncheck, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_RTSP, /* defport */ CURLPROTO_RTSP, /* protocol */ CURLPROTO_RTSP, /* family */ diff --git a/lib/smb.c b/lib/smb.c index 7ae339f8a9..752386d342 100644 --- a/lib/smb.c +++ b/lib/smb.c @@ -273,6 +273,7 @@ const struct Curl_handler Curl_handler_smb = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMB, /* defport */ CURLPROTO_SMB, /* protocol */ CURLPROTO_SMB, /* family */ @@ -301,6 +302,7 @@ const struct Curl_handler Curl_handler_smbs = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMBS, /* defport */ CURLPROTO_SMBS, /* protocol */ CURLPROTO_SMB, /* family */ diff --git a/lib/smtp.c b/lib/smtp.c index c7fb0a4ca2..7c631a1877 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -135,6 +135,7 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMTP, /* defport */ CURLPROTO_SMTP, /* protocol */ CURLPROTO_SMTP, /* family */ @@ -165,6 +166,7 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SMTPS, /* defport */ CURLPROTO_SMTPS, /* protocol */ CURLPROTO_SMTP, /* family */ diff --git a/lib/telnet.c b/lib/telnet.c index 4a7f6d060d..b75320f613 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -190,6 +190,7 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_TELNET, /* defport */ CURLPROTO_TELNET, /* protocol */ CURLPROTO_TELNET, /* family */ diff --git a/lib/tftp.c b/lib/tftp.c index 4d47be8387..3f214d55b6 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -185,6 +185,7 @@ const struct Curl_handler Curl_handler_tftp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_TFTP, /* defport */ CURLPROTO_TFTP, /* protocol */ CURLPROTO_TFTP, /* family */ diff --git a/lib/transfer.h b/lib/transfer.h index 8c9b88c178..b67f8a8947 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -33,15 +33,6 @@ void Curl_init_CONNECT(struct Curl_easy *data); CURLcode Curl_pretransfer(struct Curl_easy *data); -typedef enum { - FOLLOW_NONE, /* not used within the function, just a placeholder to - allow initing to this */ - FOLLOW_FAKE, /* only records stuff, not actually following */ - FOLLOW_RETRY, /* set if this is a request retry as opposed to a real - redirect following */ - FOLLOW_REDIR /* a full true redirect */ -} followtype; - CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp); int Curl_single_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); diff --git a/lib/urldata.h b/lib/urldata.h index b18594d137..de53cba4b2 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -676,6 +676,12 @@ struct Curl_handler { /* attach() attaches this transfer to this connection */ void (*attach)(struct Curl_easy *data, struct connectdata *conn); + /* return CURLE_OK if a redirect to `newurl` should be followed, + CURLE_TOO_MANY_REDIRECTS otherwise. May alter `data` to change + the way the follow request is performed. */ + CURLcode (*follow)(struct Curl_easy *data, const char *newurl, + followtype type); + int defport; /* Default port. */ curl_prot_t protocol; /* See CURLPROTO_* - this needs to be the single specific protocol bit */ diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 6bae061ac4..8d42ddd07c 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -161,6 +161,7 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ CURLPROTO_SCP, /* family */ @@ -189,6 +190,7 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index e19ffefe8b..edfadc8568 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -139,6 +139,7 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ CURLPROTO_SCP, /* family */ @@ -169,6 +170,7 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ diff --git a/lib/vssh/wolfssh.c b/lib/vssh/wolfssh.c index 5400821129..e78a18e71a 100644 --- a/lib/vssh/wolfssh.c +++ b/lib/vssh/wolfssh.c @@ -95,6 +95,7 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION @@ -125,6 +126,7 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ diff --git a/lib/ws.c b/lib/ws.c index bae0ad6c49..2816739f78 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -1334,6 +1334,7 @@ const struct Curl_handler Curl_handler_ws = { Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_HTTP, /* defport */ CURLPROTO_WS, /* protocol */ CURLPROTO_HTTP, /* family */ @@ -1360,6 +1361,7 @@ const struct Curl_handler Curl_handler_wss = { Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ + Curl_http_follow, /* follow */ PORT_HTTPS, /* defport */ CURLPROTO_WSS, /* protocol */ CURLPROTO_HTTP, /* family */