]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
curl: add --follow bagder/request-mode 16543/head
authorDaniel Stenberg <daniel@haxx.se>
Fri, 25 Apr 2025 11:09:00 +0000 (13:09 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 31 Jul 2025 07:21:30 +0000 (09:21 +0200)
Makes curl follow redirects an act on the response code and change a
custom method accordingly, contrary to --location.

Potential future command line to send QUERY and following a redirect
according to the status code:

    curl -d "request-body" -X QUERY --follow https://example.com

add test 794,796,797

Assisted-by: Daniel Böhmer <post@daniel-boehmer.de>
Closes #16543

16 files changed:
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/follow.md [new file with mode: 0644]
docs/cmdline-opts/location-trusted.md
docs/cmdline-opts/location.md
docs/options-in-versions
src/config2setopts.c
src/tool_cfgable.h
src/tool_getparam.c
src/tool_getparam.h
src/tool_listhelp.c
src/tool_setopt.c
src/tool_setopt.h
tests/data/Makefile.am
tests/data/test794 [new file with mode: 0644]
tests/data/test796 [new file with mode: 0644]
tests/data/test797 [new file with mode: 0644]

index c5f99477faeaf0e37d294f3c08cdf8678eeda518..768529847a89f4e1e899922c3258bda3695b905b 100644 (file)
@@ -101,6 +101,7 @@ DPAGES = \
   fail-with-body.md \
   fail.md \
   false-start.md \
+  follow.md \
   form-escape.md \
   form-string.md \
   form.md \
diff --git a/docs/cmdline-opts/follow.md b/docs/cmdline-opts/follow.md
new file mode 100644 (file)
index 0000000..9d4225a
--- /dev/null
@@ -0,0 +1,25 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+SPDX-License-Identifier: curl
+Long: follow
+Help: Follow redirects per spec
+Category: http
+Added: 8.16.0
+Multi: boolean
+See-also:
+  - request
+  - location
+Example:
+  - -X POST --follow $URL
+---
+
+# `--follow`
+
+Instructs curl to follow HTTP redirects and to do the custom request method
+set with --request when following redirects as the HTTP specification says.
+
+The method string set with --request is used in subsequent requests for the
+status codes 307 or 308, but may be reset to GET for 301, 302 and 303.
+
+This is subtly different than --location, as that option always set the custom
+method in all subsequent requests independent of response code.
index 5e20d8cc1fce7d33dfed59d9aee0b4e00f9b0de3..7d9810802c5d67681e0349169c3bbbb72e474247 100644 (file)
@@ -9,6 +9,7 @@ Added: 7.10.4
 Multi: boolean
 See-also:
   - user
+  - follow
 Example:
   - --location-trusted -u user:password $URL
   - --location-trusted -H "Cookie: session=abc" $URL
index 8d17e45e28441dd0643aa76e6b9afa61a77a4ad2..86ae7e358013b09513683c142e96f12003ba5ea8 100644 (file)
@@ -11,6 +11,7 @@ Multi: boolean
 See-also:
   - resolve
   - alt-svc
+  - follow
 Example:
   - -L $URL
 ---
index bf34e76f2c34b3350c583aa6a099a065763b795c..20f4ea013dcd4d34d286f0cf68a703be4933d1f2 100644 (file)
 --range (-r)                         4.0
 --rate                               7.84.0
 --raw                                7.16.2
+--follow                             8.16.0
 --referer (-e)                       4.0
 --remote-header-name (-J)            7.20.0
 --remote-name (-O)                   4.0
index 9a2b20eae2845178edb6c8f70d78d9c4442489d4..dfc41398e964b5c9d25f0d2bc5f8f2ca92bb60fd 100644 (file)
@@ -512,8 +512,7 @@ static CURLcode http_setopts(struct OperationConfig *config,
 {
   long postRedir = 0;
 
-  my_setopt_long(curl, CURLOPT_FOLLOWLOCATION,
-                 config->followlocation);
+  my_setopt_long(curl, CURLOPT_FOLLOWLOCATION, config->followlocation);
   my_setopt_long(curl, CURLOPT_UNRESTRICTED_AUTH,
                  config->unrestricted_auth);
   my_setopt_str(curl, CURLOPT_AWS_SIGV4, config->aws_sigv4);
index d23902885e4d92ee1579a76015c8098408f60c87..b8756dae105889bca3834a3da091fef64edb1cc3 100644 (file)
@@ -224,6 +224,7 @@ struct OperationConfig {
   long happy_eyeballs_timeout_ms; /* happy eyeballs timeout in milliseconds.
                                      0 is valid. default: CURL_HET_DEFAULT. */
   unsigned long timecond;
+  long followlocation;      /* follow http redirects mode */
   HttpReq httpreq;
   long proxyver;             /* set to CURLPROXY_HTTP* define */
   long ftp_ssl_ccc_mode;
@@ -264,7 +265,6 @@ struct OperationConfig {
   BIT(show_headers);        /* show headers to data output */
   BIT(no_body);             /* do not get the body */
   BIT(dirlistonly);         /* only get the FTP dir list */
-  BIT(followlocation);      /* follow http redirects */
   BIT(unrestricted_auth);   /* Continue to send authentication (user+password)
                                when following redirects, even when hostname
                                changed */
index 4540a5913649419284b6bbe918a2904e1dafce03..b2e651541aa4a99b2e73a4762d48202c5986c384 100644 (file)
@@ -144,6 +144,7 @@ static const struct LongShort aliases[]= {
   {"fail-early",                 ARG_BOOL, ' ', C_FAIL_EARLY},
   {"fail-with-body",             ARG_BOOL, ' ', C_FAIL_WITH_BODY},
   {"false-start",                ARG_BOOL, ' ', C_FALSE_START},
+  {"follow",                     ARG_BOOL, ' ', C_FOLLOW},
   {"form",                       ARG_STRG, 'F', C_FORM},
   {"form-escape",                ARG_BOOL, ' ', C_FORM_ESCAPE},
   {"form-string",                ARG_STRG, ' ', C_FORM_STRING},
@@ -2097,12 +2098,6 @@ static ParameterError opt_bool(struct OperationConfig *config,
   case C_LIST_ONLY: /* --list-only */
     config->dirlistonly = toggle; /* only list the names of the FTP dir */
     break;
-  case C_LOCATION_TRUSTED: /* --location-trusted */
-    config->unrestricted_auth = toggle;
-    FALLTHROUGH();
-  case C_LOCATION: /* --location */
-    config->followlocation = toggle; /* Follow Location: HTTP headers */
-    break;
   case C_MANUAL: /* --manual */
     if(toggle)   /* --no-manual shows no manual... */
       return PARAM_MANUAL_REQUESTED;
@@ -2165,6 +2160,19 @@ static ParameterError opt_bool(struct OperationConfig *config,
   case C_MPTCP: /* --mptcp */
     config->mptcp = toggle;
     break;
+  case C_LOCATION_TRUSTED: /* --location-trusted */
+    config->unrestricted_auth = toggle;
+    FALLTHROUGH();
+  case C_LOCATION: /* --location */
+    if(config->followlocation == CURLFOLLOW_OBEYCODE)
+      warnf(global, "--location overrides --follow");
+    config->followlocation = toggle ? CURLFOLLOW_ALL : 0;
+    break;
+  case C_FOLLOW: /* --follow */
+    if(config->followlocation == CURLFOLLOW_ALL)
+      warnf(global, "--follow overrides --location");
+    config->followlocation = toggle ? CURLFOLLOW_OBEYCODE : 0;
+    break;
   default:
     return PARAM_OPTION_UNKNOWN;
   }
index 47e87eab9264d5c6e91cdde6ad48e7f6b3042271..184cecb5282da7bf3b1146329a37affe119355ed 100644 (file)
@@ -90,6 +90,7 @@ typedef enum {
   C_FAIL_EARLY,
   C_FAIL_WITH_BODY,
   C_FALSE_START,
+  C_FOLLOW,
   C_FORM,
   C_FORM_ESCAPE,
   C_FORM_STRING,
index 8572250b5c3ce9622d6a08e3fea912ec9ef8c3a6..8e11dca1c93665d676058d515cdfbb1174b51efe 100644 (file)
@@ -201,6 +201,9 @@ const struct helptxt helptext[] = {
   {"    --false-start",
    "Enable TLS False Start",
    CURLHELP_DEPRECATED},
+  {"    --follow",
+   "Follow redirects per spec",
+   CURLHELP_HTTP},
   {"-F, --form <name=content>",
    "Specify multipart MIME data",
    CURLHELP_HTTP | CURLHELP_UPLOAD | CURLHELP_POST | CURLHELP_IMAP |
index 52c80104662c1a1acb14c3c585d6c994388873c0..5c905c86fd7388232c32f661959ed0fcdff95f65 100644 (file)
@@ -150,6 +150,14 @@ const struct NameValue setopt_nv_CURL_NETRC[] = {
   NVEND,
 };
 
+const struct NameValue setopt_nv_CURLOPT_FOLLOWLOCATION[] = {
+  NV(0L),
+  NV(CURLFOLLOW_ALL),
+  NV(CURLFOLLOW_OBEYCODE),
+  NV(CURLFOLLOW_FIRSTONLY),
+  NVEND,
+};
+
 /* These options have non-zero default values. */
 static const struct NameValue setopt_nv_CURLNONZERODEFAULTS[] = {
   NV1(CURLOPT_SSL_VERIFYPEER, 1),
index 8869f2e78155fedadb0bd69e9ed15de2ea725ad6..2d179fa993865988b7c968361b5dcc3ea4ad1ccf 100644 (file)
@@ -54,6 +54,7 @@ extern const struct NameValue setopt_nv_CURLFTPSSL_CCC[];
 extern const struct NameValue setopt_nv_CURLUSESSL[];
 extern const struct NameValueUnsigned setopt_nv_CURLSSLOPT[];
 extern const struct NameValue setopt_nv_CURL_NETRC[];
+extern const struct NameValue setopt_nv_CURLOPT_FOLLOWLOCATION[];
 extern const struct NameValueUnsigned setopt_nv_CURLAUTH[];
 extern const struct NameValueUnsigned setopt_nv_CURLHSTS[];
 
index 4fda5f817895ccd6b6884a3ce160fb4f0c36b3bd..98e282923166db45da5d580488964a3c5b59dcce 100644 (file)
@@ -110,7 +110,7 @@ test736 test737 test738 test739 test740 test741 test742 test743 test744 \
 test745 test746 test747 test748 test749 test750 test751 test752 test753 \
 test754 test755 test756 test757 \
 test780 test781 test782 test783 test784 test785 test786 test787 test788 \
-test789 test790 test791 test792 test793 \
+test789 test790 test791 test792 test793 test794         test796 test797 \
 \
 test799 test800 test801 test802 test803 test804 test805 test806 test807 \
 test808 test809 test810 test811 test812 test813 test814 test815 test816 \
diff --git a/tests/data/test794 b/tests/data/test794
new file mode 100644 (file)
index 0000000..b6af967
--- /dev/null
@@ -0,0 +1,95 @@
+<testcase>
+<info>
+<keywords>
+--follow
+--location
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data crlf="yes">
+HTTP/1.1 302 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Location: %TESTNUMBER0001
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+<data1 crlf="yes">
+HTTP/1.1 200 OK
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+
+-bar-
+</data1>
+<datacheck crlf="yes">
+HTTP/1.1 302 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Location: %TESTNUMBER0001
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+HTTP/1.1 200 OK
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+
+-bar-
+</datacheck>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+
+<name>
+--follow + --location with custom POST method, 302 => GET
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER --no-progress-meter -X IGLOO -d moo --location --follow
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol nonewline="yes" crlf="yes">
+IGLOO /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Content-Length: 3
+Content-Type: application/x-www-form-urlencoded
+
+mooGET /%TESTNUMBER0001 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+
+</protocol>
+<stderr mode="text">
+Warning: --follow overrides --location
+</stderr>
+</verify>
+</testcase>
diff --git a/tests/data/test796 b/tests/data/test796
new file mode 100644 (file)
index 0000000..2e0c407
--- /dev/null
@@ -0,0 +1,92 @@
+<testcase>
+<info>
+<keywords>
+--follow
+--location
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data crlf="yes">
+HTTP/1.1 302 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Location: %TESTNUMBER0001
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+<data1 crlf="yes">
+HTTP/1.1 200 OK
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+
+-bar-
+</data1>
+<datacheck crlf="yes">
+HTTP/1.1 302 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Location: %TESTNUMBER0001
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+HTTP/1.1 200 OK
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+
+-bar-
+</datacheck>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+
+<name>
+--follow with custom POST method, 302 => GET
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -X IGLOO -d moo --follow
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol nonewline="yes" crlf="yes">
+IGLOO /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Content-Length: 3
+Content-Type: application/x-www-form-urlencoded
+
+mooGET /%TESTNUMBER0001 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test797 b/tests/data/test797
new file mode 100644 (file)
index 0000000..b0602c1
--- /dev/null
@@ -0,0 +1,94 @@
+<testcase>
+<info>
+<keywords>
+--follow
+--location
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data crlf="yes">
+HTTP/1.1 308 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Location: %TESTNUMBER0001
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+<data1 crlf="yes">
+HTTP/1.1 200 OK
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+
+-bar-
+</data1>
+<datacheck crlf="yes">
+HTTP/1.1 308 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Location: %TESTNUMBER0001
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+HTTP/1.1 200 OK
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+
+-bar-
+</datacheck>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+http
+</features>
+
+<name>
+--follow with custom POST method, 308 => custom
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -X IGLOO -d moo --follow
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol nonewline="yes" crlf="yes">
+IGLOO /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Content-Length: 3
+Content-Type: application/x-www-form-urlencoded
+
+mooIGLOO /%TESTNUMBER0001 HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+Content-Length: 3
+Content-Type: application/x-www-form-urlencoded
+
+moo
+</protocol>
+</verify>
+</testcase>