]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: on 303, switch to GET master
authorDaniel Stenberg <daniel@haxx.se>
Thu, 9 Apr 2026 21:43:55 +0000 (23:43 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 10 Apr 2026 07:39:39 +0000 (09:39 +0200)
... 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
docs/libcurl/opts/CURLOPT_POSTREDIR.md
lib/http.c
tests/data/Makefile.am
tests/data/test1983 [new file with mode: 0644]
tests/data/test1984 [new file with mode: 0644]

index 307d7a558248bebafbbc72c4347fb18452d802ad..d8a1153ead306ed07ea5c7d6f96a3758e84e45ac 100644 (file)
@@ -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*
 (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.
 
 If you prefer a custom POST method to be reset to exactly the method `POST`,
 use CURLFOLLOW_FIRSTONLY instead.
index bf69af6f3491fd60547cd9d13047004942aa5d63..521d770a5eecb934b3647b086f70415519931659 100644 (file)
@@ -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
 
 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).
 
 
 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
 # DEFAULT
 
 0
index 12fb4287289fd9777685eebc3057c009827e7246..908b27d72f49704700410daefd50412622565bc8 100644 (file)
@@ -1115,6 +1115,11 @@ static void http_switch_to_get(struct Curl_easy *data, int code)
   Curl_creader_set_rewind(data, FALSE);
 }
 
   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)
 {
 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.
      */
      * 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;
     }
       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.
      */
      * 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;
     }
       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.
     /* '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;
     }
       http_switch_to_get(data, 303);
       switch_to_get = TRUE;
     }
index 1a41afc9569618e05e92a709bddc0014c4858826..05c9aad580fe2eb3d699cd187993bfa3a6bc5b08 100644 (file)
@@ -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 \
 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 \
 \
 test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \
 test2008 \
diff --git a/tests/data/test1983 b/tests/data/test1983
new file mode 100644 (file)
index 0000000..66e0de4
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+
+<info>
+<keywords>
+HTTP
+HTTP DELETE
+followlocation
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 303 OK swsclose
+Location: moo.html%AMPtestcase=/%TESTNUMBER0002
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Connection: close
+
+</data>
+<data2>
+HTTP/1.1 200 OK swsclose
+Location: this should be ignored
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Connection: close
+
+body
+</data2>
+<datacheck>
+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
+</datacheck>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+HTTP DELETE with --follow and 303 redirect
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/blah/%TESTNUMBER --follow -X DELETE
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="yes">
+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: */*
+
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test1984 b/tests/data/test1984
new file mode 100644 (file)
index 0000000..1513eb6
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+
+<info>
+<keywords>
+HTTP
+HTTP PUT
+followlocation
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 303 OK swsclose
+Location: moo.html%AMPtestcase=/%TESTNUMBER0002
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Connection: close
+
+</data>
+<data2>
+HTTP/1.1 200 OK swsclose
+Location: this should be ignored
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Connection: close
+
+body
+</data2>
+<datacheck>
+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
+</datacheck>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+HTTP PUT with 303 redirect and --follow
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/blah/%TESTNUMBER --follow -T %LOGDIR/upload
+</command>
+<file name="%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.
+</file>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="headers">
+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: */*
+
+</protocol>
+</verify>
+</testcase>