From e5087ac9fc6c80fbb1708b750e8187c7c876f0a4 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 9 Apr 2026 23:43:55 +0200 Subject: [PATCH] http: on 303, switch to GET ... unless it is a POST and the user explicitly asked to keep doing POST. Add test1983/1984: verify --follow with 303 and PUT + custom GET Fixes #20715 Reported-by: Dan Arnfield Closes #21280 --- docs/libcurl/opts/CURLOPT_FOLLOWLOCATION.md | 5 +- docs/libcurl/opts/CURLOPT_POSTREDIR.md | 17 +++-- lib/http.c | 22 +++--- tests/data/Makefile.am | 2 +- tests/data/test1983 | 71 +++++++++++++++++++ tests/data/test1984 | 76 +++++++++++++++++++++ 6 files changed, 169 insertions(+), 24 deletions(-) create mode 100644 tests/data/test1983 create mode 100644 tests/data/test1984 diff --git a/docs/libcurl/opts/CURLOPT_FOLLOWLOCATION.md b/docs/libcurl/opts/CURLOPT_FOLLOWLOCATION.md index 307d7a5582..d8a1153ead 100644 --- a/docs/libcurl/opts/CURLOPT_FOLLOWLOCATION.md +++ b/docs/libcurl/opts/CURLOPT_FOLLOWLOCATION.md @@ -94,8 +94,9 @@ change PUT etc - and therefore also not when libcurl issues a custom PUT. A (except for HEAD). To control for which of the 301/302/303 status codes libcurl should *not* -switch back to GET for when doing a custom POST, and instead keep the custom -method, use CURLOPT_POSTREDIR(3). +switch back to GET for when doing a custom POST (a POST transfer using a +modified method), and instead keep the custom method, use +CURLOPT_POSTREDIR(3). If you prefer a custom POST method to be reset to exactly the method `POST`, use CURLFOLLOW_FIRSTONLY instead. diff --git a/docs/libcurl/opts/CURLOPT_POSTREDIR.md b/docs/libcurl/opts/CURLOPT_POSTREDIR.md index bf69af6f34..521d770a5e 100644 --- a/docs/libcurl/opts/CURLOPT_POSTREDIR.md +++ b/docs/libcurl/opts/CURLOPT_POSTREDIR.md @@ -32,19 +32,22 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_POSTREDIR, Pass a bitmask to control how libcurl acts on redirects after POSTs that get a 301, 302 or 303 response back. A parameter with bit 0 set (value -**CURL_REDIR_POST_301**) tells the library to respect RFC 7231 (section -6.4.2 to 6.4.4) and not convert POST requests into GET requests when following -a 301 redirection. Setting bit 1 (value **CURL_REDIR_POST_302**) makes -libcurl maintain the request method after a 302 redirect whilst setting bit 2 -(value **CURL_REDIR_POST_303**) makes libcurl maintain the request method -after a 303 redirect. The value **CURL_REDIR_POST_ALL** is a convenience -define that sets all three bits. +**CURL_REDIR_POST_301**) tells the library to not convert POST requests into +GET requests when following a 301 redirection. Setting bit 1 (value +**CURL_REDIR_POST_302**) makes libcurl maintain the request method after a 302 +redirect whilst setting bit 2 (value **CURL_REDIR_POST_303**) makes libcurl +maintain the request method after a 303 redirect. The value +**CURL_REDIR_POST_ALL** is a convenience define that sets all three bits. The non-RFC behavior is ubiquitous in web browsers, so the library does the conversion by default to maintain consistency. A server may require a POST to remain a POST after such a redirection. This option is meaningful only when setting CURLOPT_FOLLOWLOCATION(3). +This option affects transfers where libcurl has been told to use HTTP POST +using for example CURLOPT_POST(3) or CURLOPT_MIMEPOST(3) and not if the +method has merely been modified with CURLOPT_CUSTOMREQUEST(3). + # DEFAULT 0 diff --git a/lib/http.c b/lib/http.c index 12fb428728..908b27d72f 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1115,6 +1115,11 @@ static void http_switch_to_get(struct Curl_easy *data, int code) Curl_creader_set_rewind(data, FALSE); } +#define HTTPREQ_IS_POST(data) \ + ((data)->state.httpreq == HTTPREQ_POST || \ + (data)->state.httpreq == HTTPREQ_POST_FORM || \ + (data)->state.httpreq == HTTPREQ_POST_MIME) + CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, followtype type) { @@ -1323,10 +1328,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, * 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.post301) { + if(HTTPREQ_IS_POST(data) && !data->set.post301) { http_switch_to_get(data, 301); switch_to_get = TRUE; } @@ -1348,10 +1350,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, * 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.post302) { + if(HTTPREQ_IS_POST(data) && !data->set.post302) { http_switch_to_get(data, 302); switch_to_get = TRUE; } @@ -1361,13 +1360,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, /* '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.post303)) { + if(!HTTPREQ_IS_POST(data) || !data->set.post303) { http_switch_to_get(data, 303); switch_to_get = TRUE; } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 1a41afc956..05c9aad580 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -241,7 +241,7 @@ test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \ test1955 test1956 test1957 test1958 test1959 test1960 test1964 test1965 \ \ test1970 test1971 test1972 test1973 test1974 test1975 test1976 test1977 \ -test1978 test1979 test1980 test1981 test1982 \ +test1978 test1979 test1980 test1981 test1982 test1983 test1984 \ \ test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \ test2008 \ diff --git a/tests/data/test1983 b/tests/data/test1983 new file mode 100644 index 0000000000..66e0de4e09 --- /dev/null +++ b/tests/data/test1983 @@ -0,0 +1,71 @@ + + + + + +HTTP +HTTP DELETE +followlocation + + +# Server-side + + +HTTP/1.1 303 OK swsclose +Location: moo.html%AMPtestcase=/%TESTNUMBER0002 +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + + + +HTTP/1.1 200 OK swsclose +Location: this should be ignored +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + +body + + +HTTP/1.1 303 OK swsclose +Location: moo.html%AMPtestcase=/%TESTNUMBER0002 +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + +HTTP/1.1 200 OK swsclose +Location: this should be ignored +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + +body + + + +# Client-side + + +http + + +HTTP DELETE with --follow and 303 redirect + + +http://%HOSTIP:%HTTPPORT/blah/%TESTNUMBER --follow -X DELETE + + + +# Verify data after the test has been "shot" + + +DELETE /blah/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /blah/moo.html%AMPtestcase=/%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1984 b/tests/data/test1984 new file mode 100644 index 0000000000..1513eb64f5 --- /dev/null +++ b/tests/data/test1984 @@ -0,0 +1,76 @@ + + + + + +HTTP +HTTP PUT +followlocation + + +# Server-side + + +HTTP/1.1 303 OK swsclose +Location: moo.html%AMPtestcase=/%TESTNUMBER0002 +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + + + +HTTP/1.1 200 OK swsclose +Location: this should be ignored +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + +body + + +HTTP/1.1 303 OK swsclose +Location: moo.html%AMPtestcase=/%TESTNUMBER0002 +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + +HTTP/1.1 200 OK swsclose +Location: this should be ignored +Date: Tue, 09 Nov 2010 14:49:00 GMT +Connection: close + +body + + + +# Client-side + + +http + + +HTTP PUT with 303 redirect and --follow + + +http://%HOSTIP:%HTTPPORT/blah/%TESTNUMBER --follow -T %LOGDIR/upload + + +Except for responses to a HEAD request, the representation of a 303 response ought to contain a short hypertext note with a hyperlink to the same URI reference provided in the Location header field. + + + +# Verify data after the test has been "shot" + + +PUT /blah/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Content-Length: 199 + +Except for responses to a HEAD request, the representation of a 303 response ought to contain a short hypertext note with a hyperlink to the same URI reference provided in the Location header field. +GET /blah/moo.html%AMPtestcase=/%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + -- 2.47.3