Jelajahi Sumber

setopt: add CURLOPT_PROTOCOLS_STR and CURLOPT_REDIR_PROTOCOLS_STR

... as replacements for deprecated CURLOPT_PROTOCOLS and
CURLOPT_REDIR_PROTOCOLS as these new ones do not risk running into the
32 bit limit the old ones are facing.

CURLINFO_PROTCOOL is now deprecated.

The curl tool is updated to use the new options.

Added test 1597 to verify the libcurl protocol parser.

Closes #8992
Daniel Stenberg 1 tahun lalu
induk
melakukan
e6f8445ede

+ 7 - 2
docs/libcurl/curl_easy_setopt.3

@@ -167,9 +167,14 @@ URL to work on. See \fICURLOPT_URL(3)\fP
 .IP CURLOPT_PATH_AS_IS
 Disable squashing /../ and /./ sequences in the path. See \fICURLOPT_PATH_AS_IS(3)\fP
 .IP CURLOPT_PROTOCOLS
-Allowed protocols. See \fICURLOPT_PROTOCOLS(3)\fP
+\fBDeprecated option\fP Allowed protocols. See \fICURLOPT_PROTOCOLS(3)\fP
+.IP CURLOPT_PROTOCOLS_STR
+Allowed protocols. See \fICURLOPT_PROTOCOLS_STR(3)\fP
 .IP CURLOPT_REDIR_PROTOCOLS
-Protocols to allow redirects to. See \fICURLOPT_REDIR_PROTOCOLS(3)\fP
+\fBDeprecated option\fP Protocols to allow redirects to. See
+\fICURLOPT_REDIR_PROTOCOLS(3)\fP
+.IP CURLOPT_REDIR_PROTOCOLS_STR
+Protocols to allow redirects to. See \fICURLOPT_REDIR_PROTOCOLS_STR(3)\fP
 .IP CURLOPT_DEFAULT_PROTOCOL
 Default protocol. See \fICURLOPT_DEFAULT_PROTOCOL(3)\fP
 .IP CURLOPT_PROXY

+ 4 - 0
docs/libcurl/opts/CURLINFO_PROTOCOL.3

@@ -32,6 +32,10 @@ CURLINFO_PROTOCOL \- get the protocol used in the connection
 CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_PROTOCOL, long *p);
 .fi
 .SH DESCRIPTION
+This option is deprecated. We strongly recommend using
+\fICURLINFO_SCHEME(3)\fP instead, because this option cannot return all
+possible protocols!
+
 Pass a pointer to a long to receive the version used in the last http
 connection.  The returned value will be exactly one of the CURLPROTO_* values:
 

+ 5 - 1
docs/libcurl/opts/CURLOPT_PROTOCOLS.3

@@ -32,6 +32,10 @@ CURLOPT_PROTOCOLS \- allowed protocols
 CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROTOCOLS, long bitmask);
 .fi
 .SH DESCRIPTION
+This option is deprecated. We strongly recommend using
+\fICURLOPT_PROTOCOLS_STR(3)\fP instead because this option cannot control all
+available protocols!
+
 Pass a long that holds a bitmask of CURLPROTO_* defines. If used, this bitmask
 limits what protocols libcurl may use in the transfer. This allows you to have
 a libcurl built to support a wide range of protocols but still limit specific
@@ -71,7 +75,7 @@ CURLPROTO_TELNET
 CURLPROTO_TFTP
 .fi
 .SH DEFAULT
-All protocols built-in
+All protocols built-in.
 .SH PROTOCOLS
 All
 .SH EXAMPLE

+ 82 - 0
docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.3

@@ -0,0 +1,82 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * SPDX-License-Identifier: curl
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_PROTOCOLS_STR 3 "11 Jun 2022" "libcurl 7.85.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_PROTOCOLS_STR \- allowed protocols
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROTOCOLS_STR, char *spec);
+.fi
+.SH DESCRIPTION
+Pass a pointer to a string that holds a comma-separated list of case
+insensitive protocol names (URL schemes) to allow in the transfer. This
+option allows applications to use libcurl built to support a wide range of
+protocols but still limit specific transfers to only be allowed to use a
+subset of them. By default, libcurl accepts all protocols it was built with
+support for. See also \fICURLOPT_REDIR_PROTOCOLS_STR(3)\fP.
+
+If trying to set a non-existing protocol or if no matching protocol at all is
+set, it returns error.
+
+These are the available protocols:
+
+DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,
+POP3, POP3S, RTMP, RTMPE, RTMPS, RTMPT, RTMPTE, RTMPTS, RTSP, SCP, SFTP, SMB,
+SMBS, SMTP, SMTPS, TELNET, TFTP
+
+You can set "ALL" as a short-cut to enable all protocols. Note that by setting
+all, you may enable protocols that were not supported the day you write this
+but are introduced in a future libcurl version.
+
+\fIcurl_version_info(3)\fP can be used to get a list of all supported
+protocols in the current libcurl. \fICURLINFO_SCHEME(3)\fP is the recommended
+way to figure out the protocol used in a previous transfer.
+.SH DEFAULT
+All protocols built-in
+.SH PROTOCOLS
+All
+.SH EXAMPLE
+.nf
+curl = curl_easy_init();
+if(curl) {
+  /* pass in the URL from an external source */
+  curl_easy_setopt(curl, CURLOPT_URL, argv[1]);
+
+  /* only allow HTTP, TFTP and SFTP */
+  curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "http,tftp,sftp");
+
+  /* Perform the request */
+  curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.85.0
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLOPT_REDIR_PROTOCOLS_STR "(3), " CURLOPT_URL "(3), "
+.BR curl_version_info "(3), " CURLINFO_SCHEME "(3), "

+ 4 - 0
docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.3

@@ -32,6 +32,10 @@ CURLOPT_REDIR_PROTOCOLS \- protocols allowed to redirect to
 CURLcode curl_easy_setopt(CURL *handle, CURLOPT_REDIR_PROTOCOLS, long bitmask);
 .fi
 .SH DESCRIPTION
+This option is deprecated. We strongly recommend using
+\fICURLOPT_REDIR_PROTOCOLS_STR(3)\fP instead because this option cannot
+control all available protocols!
+
 Pass a long that holds a bitmask of CURLPROTO_* defines. If used, this bitmask
 limits what protocols libcurl may use in a transfer that it follows to in a
 redirect when \fICURLOPT_FOLLOWLOCATION(3)\fP is enabled. This allows you to

+ 89 - 0
docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.3

@@ -0,0 +1,89 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" * SPDX-License-Identifier: curl
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_REDIR_PROTOCOLS_STR 3 "19 Jun 2014" "libcurl 7.37.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_REDIR_PROTOCOLS_STR \- protocols allowed to redirect to
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_REDIR_PROTOCOLS_STR,
+                          char *spec);
+.fi
+.SH DESCRIPTION
+Pass a pointer to a string that holds a comma-separated list of case
+insensitive protocol names (URL schemes). That list limits what protocols
+libcurl may use in a transfer that it follows to in a redirect when
+\fICURLOPT_FOLLOWLOCATION(3)\fP is enabled. This option allows applications to
+limit specific transfers to only be allowed to use a subset of protocols in
+redirections.
+
+Protocols denied by \fICURLOPT_PROTOCOLS_STR(3)\fP are not overridden by this
+option.
+
+By default libcurl will allow HTTP, HTTPS, FTP and FTPS on redirects (since
+7.65.2).  Older versions of libcurl allowed all protocols on redirect except
+several disabled for security reasons: Since 7.19.4 FILE and SCP are disabled,
+and since 7.40.0 SMB and SMBS are also disabled.
+
+These are the available protocols:
+
+DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,
+POP3, POP3S, RTMP, RTMPE, RTMPS, RTMPT, RTMPTE, RTMPTS, RTSP, SCP, SFTP, SMB,
+SMBS, SMTP, SMTPS, TELNET, TFTP
+
+You can set "ALL" as a short-cut to enable all protocols. Note that by setting
+all, you may enable protocols that were not supported the day you write this
+but are introduced in a future libcurl version.
+
+If trying to set a non-existing protocol or if no matching protocol at all is
+set, it returns error.
+.SH DEFAULT
+HTTP, HTTPS, FTP and FTPS (Added in 7.65.2).
+
+Older versions defaulted to all protocols except FILE, SCP and since 7.40.0
+SMB and SMBS.
+.SH PROTOCOLS
+All
+.SH EXAMPLE
+.nf
+curl = curl_easy_init();
+if(curl) {
+  /* pass in the URL from an external source */
+  curl_easy_setopt(curl, CURLOPT_URL, argv[1]);
+
+  /* only allow redirects to HTTP and HTTPS URLs */
+  curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
+
+  /* Perform the request */
+  curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.85.0.
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLOPT_PROTOCOLS_STR "(3), "

+ 2 - 0
docs/libcurl/opts/Makefile.inc

@@ -267,6 +267,7 @@ man_MANS =                                      \
   CURLOPT_PROGRESSDATA.3                        \
   CURLOPT_PROGRESSFUNCTION.3                    \
   CURLOPT_PROTOCOLS.3                           \
+  CURLOPT_PROTOCOLS_STR.3                       \
   CURLOPT_PROXY.3                               \
   CURLOPT_PROXYAUTH.3                           \
   CURLOPT_PROXYHEADER.3                         \
@@ -307,6 +308,7 @@ man_MANS =                                      \
   CURLOPT_READDATA.3                            \
   CURLOPT_READFUNCTION.3                        \
   CURLOPT_REDIR_PROTOCOLS.3                     \
+  CURLOPT_REDIR_PROTOCOLS_STR.3                 \
   CURLOPT_REFERER.3                             \
   CURLOPT_REQUEST_TARGET.3                      \
   CURLOPT_RESOLVE.3                             \

+ 2 - 0
docs/libcurl/symbols-in-versions

@@ -722,6 +722,7 @@ CURLOPT_PRIVATE                 7.10.3
 CURLOPT_PROGRESSDATA            7.1
 CURLOPT_PROGRESSFUNCTION        7.1           7.32.0
 CURLOPT_PROTOCOLS               7.19.4
+CURLOPT_PROTOCOLS_STR           7.85.0
 CURLOPT_PROXY                   7.1
 CURLOPT_PROXY_CAINFO            7.52.0
 CURLOPT_PROXY_CAINFO_BLOB       7.77.0
@@ -762,6 +763,7 @@ CURLOPT_RANGE                   7.1
 CURLOPT_READDATA                7.9.7
 CURLOPT_READFUNCTION            7.1
 CURLOPT_REDIR_PROTOCOLS         7.19.4
+CURLOPT_REDIR_PROTOCOLS_STR     7.85.0
 CURLOPT_REFERER                 7.1
 CURLOPT_REQUEST_TARGET          7.55.0
 CURLOPT_RESOLVE                 7.21.3

+ 11 - 1
include/curl/curl.h

@@ -1010,7 +1010,8 @@ typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy,
 #define CURLHSTS_ENABLE       (long)(1<<0)
 #define CURLHSTS_READONLYFILE (long)(1<<1)
 
-/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */
+/* The CURLPROTO_ defines below are for the **deprecated** CURLOPT_*PROTOCOLS
+   options. Do not use. */
 #define CURLPROTO_HTTP   (1<<0)
 #define CURLPROTO_HTTPS  (1<<1)
 #define CURLPROTO_FTP    (1<<2)
@@ -2144,6 +2145,15 @@ typedef enum {
   /* set the SSH host key callback custom pointer */
   CURLOPT(CURLOPT_SSH_HOSTKEYDATA, CURLOPTTYPE_CBPOINT, 317),
 
+  /* specify which protocols that are allowed to be used for the transfer,
+     which thus helps the app which takes URLs from users or other external
+     inputs and want to restrict what protocol(s) to deal with. Defaults to
+     all built-in protocols. */
+  CURLOPT(CURLOPT_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 318),
+
+  /* specify which protocols that libcurl is allowed to follow directs to */
+  CURLOPT(CURLOPT_REDIR_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 319),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 

+ 7 - 5
include/curl/typecheck-gcc.h

@@ -272,9 +272,9 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t,
    (option) == CURLOPT_DNS_SERVERS ||                                         \
    (option) == CURLOPT_DOH_URL ||                                             \
    (option) == CURLOPT_EGDSOCKET ||                                           \
-   (option) == CURLOPT_FTPPORT ||                                             \
    (option) == CURLOPT_FTP_ACCOUNT ||                                         \
    (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER ||                             \
+   (option) == CURLOPT_FTPPORT ||                                             \
    (option) == CURLOPT_HSTS ||                                                \
    (option) == CURLOPT_INTERFACE ||                                           \
    (option) == CURLOPT_ISSUERCERT ||                                          \
@@ -288,10 +288,8 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t,
    (option) == CURLOPT_PASSWORD ||                                            \
    (option) == CURLOPT_PINNEDPUBLICKEY ||                                     \
    (option) == CURLOPT_PRE_PROXY ||                                           \
+   (option) == CURLOPT_PROTOCOLS_STR ||                                       \
    (option) == CURLOPT_PROXY ||                                               \
-   (option) == CURLOPT_PROXYPASSWORD ||                                       \
-   (option) == CURLOPT_PROXYUSERNAME ||                                       \
-   (option) == CURLOPT_PROXYUSERPWD ||                                        \
    (option) == CURLOPT_PROXY_CAINFO ||                                        \
    (option) == CURLOPT_PROXY_CAPATH ||                                        \
    (option) == CURLOPT_PROXY_CRLFILE ||                                       \
@@ -299,17 +297,21 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t,
    (option) == CURLOPT_PROXY_KEYPASSWD ||                                     \
    (option) == CURLOPT_PROXY_PINNEDPUBLICKEY ||                               \
    (option) == CURLOPT_PROXY_SERVICE_NAME ||                                  \
+   (option) == CURLOPT_PROXY_SSL_CIPHER_LIST ||                               \
    (option) == CURLOPT_PROXY_SSLCERT ||                                       \
    (option) == CURLOPT_PROXY_SSLCERTTYPE ||                                   \
    (option) == CURLOPT_PROXY_SSLKEY ||                                        \
    (option) == CURLOPT_PROXY_SSLKEYTYPE ||                                    \
-   (option) == CURLOPT_PROXY_SSL_CIPHER_LIST ||                               \
    (option) == CURLOPT_PROXY_TLS13_CIPHERS ||                                 \
    (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD ||                              \
    (option) == CURLOPT_PROXY_TLSAUTH_TYPE ||                                  \
    (option) == CURLOPT_PROXY_TLSAUTH_USERNAME ||                              \
+   (option) == CURLOPT_PROXYPASSWORD ||                                       \
+   (option) == CURLOPT_PROXYUSERNAME ||                                       \
+   (option) == CURLOPT_PROXYUSERPWD ||                                        \
    (option) == CURLOPT_RANDOM_FILE ||                                         \
    (option) == CURLOPT_RANGE ||                                               \
+   (option) == CURLOPT_REDIR_PROTOCOLS_STR ||                                 \
    (option) == CURLOPT_REFERER ||                                             \
    (option) == CURLOPT_REQUEST_TARGET ||                                      \
    (option) == CURLOPT_RTSP_SESSION_ID ||                                     \

+ 5 - 3
lib/easyoptions.c

@@ -203,6 +203,7 @@ struct curl_easyoption Curl_easyopts[] = {
   {"PROGRESSDATA", CURLOPT_XFERINFODATA, CURLOT_CBPTR, CURLOT_FLAG_ALIAS},
   {"PROGRESSFUNCTION", CURLOPT_PROGRESSFUNCTION, CURLOT_FUNCTION, 0},
   {"PROTOCOLS", CURLOPT_PROTOCOLS, CURLOT_LONG, 0},
+  {"PROTOCOLS_STR", CURLOPT_PROTOCOLS_STR, CURLOT_STRING, 0},
   {"PROXY", CURLOPT_PROXY, CURLOT_STRING, 0},
   {"PROXYAUTH", CURLOPT_PROXYAUTH, CURLOT_VALUES, 0},
   {"PROXYHEADER", CURLOPT_PROXYHEADER, CURLOT_SLIST, 0},
@@ -245,6 +246,7 @@ struct curl_easyoption Curl_easyopts[] = {
   {"READDATA", CURLOPT_READDATA, CURLOT_CBPTR, 0},
   {"READFUNCTION", CURLOPT_READFUNCTION, CURLOT_FUNCTION, 0},
   {"REDIR_PROTOCOLS", CURLOPT_REDIR_PROTOCOLS, CURLOT_LONG, 0},
+  {"REDIR_PROTOCOLS_STR", CURLOPT_REDIR_PROTOCOLS_STR, CURLOT_STRING, 0},
   {"REFERER", CURLOPT_REFERER, CURLOT_STRING, 0},
   {"REQUEST_TARGET", CURLOPT_REQUEST_TARGET, CURLOT_STRING, 0},
   {"RESOLVE", CURLOPT_RESOLVE, CURLOT_SLIST, 0},
@@ -275,14 +277,14 @@ struct curl_easyoption Curl_easyopts[] = {
   {"SOCKS5_GSSAPI_SERVICE", CURLOPT_SOCKS5_GSSAPI_SERVICE, CURLOT_STRING, 0},
   {"SSH_AUTH_TYPES", CURLOPT_SSH_AUTH_TYPES, CURLOT_VALUES, 0},
   {"SSH_COMPRESSION", CURLOPT_SSH_COMPRESSION, CURLOT_LONG, 0},
+  {"SSH_HOSTKEYDATA", CURLOPT_SSH_HOSTKEYDATA, CURLOT_CBPTR, 0},
+  {"SSH_HOSTKEYFUNCTION", CURLOPT_SSH_HOSTKEYFUNCTION, CURLOT_FUNCTION, 0},
   {"SSH_HOST_PUBLIC_KEY_MD5", CURLOPT_SSH_HOST_PUBLIC_KEY_MD5,
    CURLOT_STRING, 0},
   {"SSH_HOST_PUBLIC_KEY_SHA256", CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256,
    CURLOT_STRING, 0},
   {"SSH_KEYDATA", CURLOPT_SSH_KEYDATA, CURLOT_CBPTR, 0},
   {"SSH_KEYFUNCTION", CURLOPT_SSH_KEYFUNCTION, CURLOT_FUNCTION, 0},
-  {"SSH_HOSTKEYDATA", CURLOPT_SSH_HOSTKEYDATA, CURLOT_CBPTR, 0},
-  {"SSH_HOSTKEYFUNCTION", CURLOPT_SSH_HOSTKEYFUNCTION, CURLOT_FUNCTION, 0},
   {"SSH_KNOWNHOSTS", CURLOPT_SSH_KNOWNHOSTS, CURLOT_STRING, 0},
   {"SSH_PRIVATE_KEYFILE", CURLOPT_SSH_PRIVATE_KEYFILE, CURLOT_STRING, 0},
   {"SSH_PUBLIC_KEYFILE", CURLOPT_SSH_PUBLIC_KEYFILE, CURLOT_STRING, 0},
@@ -364,6 +366,6 @@ struct curl_easyoption Curl_easyopts[] = {
  */
 int Curl_easyopts_check(void)
 {
-  return ((CURLOPT_LASTENTRY%10000) != (317 + 1));
+  return ((CURLOPT_LASTENTRY%10000) != (319 + 1));
 }
 #endif

+ 97 - 2
lib/setopt.c

@@ -148,6 +148,85 @@ static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp)
 #define C_SSLVERSION_VALUE(x) (x & 0xffff)
 #define C_SSLVERSION_MAX_VALUE(x) (x & 0xffff0000)
 
+static CURLcode protocol2num(char *str, curl_off_t *val)
+{
+  bool found_comma = FALSE;
+  static struct scheme {
+    const char *name;
+    long bit;
+  } const protos[] = {
+    { "dict", CURLPROTO_DICT },
+    { "file", CURLPROTO_FILE },
+    { "ftp", CURLPROTO_FTP },
+    { "ftps", CURLPROTO_FTPS },
+    { "gopher", CURLPROTO_GOPHER },
+    { "gophers", CURLPROTO_GOPHERS },
+    { "http", CURLPROTO_HTTP },
+    { "https", CURLPROTO_HTTPS },
+    { "imap", CURLPROTO_IMAP },
+    { "imaps", CURLPROTO_IMAPS },
+    { "ldap", CURLPROTO_LDAP },
+    { "ldaps", CURLPROTO_LDAPS },
+    { "mqtt", CURLPROTO_MQTT },
+    { "pop3", CURLPROTO_POP3 },
+    { "pop3s", CURLPROTO_POP3S },
+    { "rtmp", CURLPROTO_RTMP },
+    { "rtmpe", CURLPROTO_RTMPE },
+    { "rtmps", CURLPROTO_RTMPS },
+    { "rtmpt", CURLPROTO_RTMPT },
+    { "rtmpte", CURLPROTO_RTMPTE },
+    { "rtmpts", CURLPROTO_RTMPTS },
+    { "rtsp", CURLPROTO_RTSP },
+    { "scp", CURLPROTO_SCP },
+    { "sftp", CURLPROTO_SFTP },
+    { "smb", CURLPROTO_SMB },
+    { "smbs", CURLPROTO_SMBS },
+    { "smtp", CURLPROTO_SMTP },
+    { "smtps", CURLPROTO_SMTPS },
+    { "telnet", CURLPROTO_TELNET },
+    { "tftp", CURLPROTO_TFTP },
+    { NULL, 0 }
+  };
+
+  if(!str)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  else if(curl_strequal(str, "all")) {
+    *val = ~0;
+    return CURLE_OK;
+  }
+
+  *val = 0;
+
+  do {
+    size_t tlen;
+    struct scheme const *pp;
+    char *token;
+    token = strchr(str, ',');
+    found_comma = token ? TRUE : FALSE;
+    if(!token)
+      token = strchr(str, '\0');
+    tlen = token - str;
+    if(tlen) {
+      for(pp = protos; pp->name; pp++) {
+        if((strlen(pp->name) == tlen) &&
+           curl_strnequal(str, pp->name, tlen)) {
+          *val |= pp->bit;
+          break;
+        }
+      }
+      if(!(pp->name))
+        /* protocol name didn't match */
+        return CURLE_BAD_FUNCTION_ARGUMENT;
+    }
+    if(found_comma)
+      str = token + 1;
+  } while(found_comma);
+  if(!*val)
+    /* no matching protocol */
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  return CURLE_OK;
+}
+
 /*
  * Do not make Curl_vsetopt() static: it is called from
  * packages/OS400/ccsidcurl.c.
@@ -2560,14 +2639,30 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
        transfer, which thus helps the app which takes URLs from users or other
        external inputs and want to restrict what protocol(s) to deal
        with. Defaults to CURLPROTO_ALL. */
-    data->set.allowed_protocols = (unsigned int)va_arg(param, long);
+    data->set.allowed_protocols = (curl_off_t)va_arg(param, long);
     break;
 
   case CURLOPT_REDIR_PROTOCOLS:
     /* set the bitmask for the protocols that libcurl is allowed to follow to,
        as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs
        to be set in both bitmasks to be allowed to get redirected to. */
-    data->set.redir_protocols = (unsigned int)va_arg(param, long);
+    data->set.redir_protocols = (curl_off_t)va_arg(param, long);
+    break;
+
+  case CURLOPT_PROTOCOLS_STR:
+    argptr = va_arg(param, char *);
+    result = protocol2num(argptr, &bigsize);
+    if(result)
+      return result;
+    data->set.allowed_protocols = bigsize;
+    break;
+
+  case CURLOPT_REDIR_PROTOCOLS_STR:
+    argptr = va_arg(param, char *);
+    result = protocol2num(argptr, &bigsize);
+    if(result)
+      return result;
+    data->set.redir_protocols = bigsize;
     break;
 
   case CURLOPT_DEFAULT_PROTOCOL:

+ 2 - 2
lib/urldata.h

@@ -1782,8 +1782,8 @@ struct UserDefined {
 #ifdef ENABLE_IPV6
   unsigned int scope_id;  /* Scope id for IPv6 */
 #endif
-  unsigned int allowed_protocols;
-  unsigned int redir_protocols;
+  curl_off_t allowed_protocols;
+  curl_off_t redir_protocols;
   unsigned int mime_options;      /* Mime option flags. */
 
 #ifndef CURL_DISABLE_RTSP

+ 2 - 4
src/tool_cfgable.c

@@ -36,11 +36,7 @@ void config_init(struct OperationConfig *config)
   config->use_httpget = FALSE;
   config->create_dirs = FALSE;
   config->maxredirs = DEFAULT_MAXREDIRS;
-  config->proto = CURLPROTO_ALL;
   config->proto_present = FALSE;
-  config->proto_redir = CURLPROTO_ALL & /* All except FILE, SCP and SMB */
-    ~(CURLPROTO_FILE | CURLPROTO_SCP | CURLPROTO_SMB |
-      CURLPROTO_SMBS);
   config->proto_redir_present = FALSE;
   config->proto_default = NULL;
   config->tcp_nodelay = TRUE; /* enabled by default */
@@ -172,6 +168,8 @@ static void free_config_fields(struct OperationConfig *config)
   Curl_safefree(config->ftp_alternative_to_user);
 
   Curl_safefree(config->aws_sigv4);
+  Curl_safefree(config->proto_str);
+  Curl_safefree(config->proto_redir_str);
 }
 
 void config_free(struct OperationConfig *config)

+ 2 - 2
src/tool_cfgable.h

@@ -67,9 +67,9 @@ struct OperationConfig {
   bool disable_epsv;
   bool disable_eprt;
   bool ftp_pret;
-  long proto;
+  char *proto_str;
   bool proto_present;
-  long proto_redir;
+  char *proto_redir_str;
   bool proto_redir_present;
   char *proto_default;
   curl_off_t resume_from;

+ 7 - 3
src/tool_getparam.c

@@ -1184,12 +1184,16 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
         break;
       case 'D': /* --proto */
         config->proto_present = TRUE;
-        if(proto2num(config, &config->proto, nextarg))
-          return PARAM_BAD_USE;
+        err = proto2num(config, (unsigned int)CURLPROTO_ALL,
+                        &config->proto_str, nextarg);
+        if(err)
+          return err;
         break;
       case 'E': /* --proto-redir */
         config->proto_redir_present = TRUE;
-        if(proto2num(config, &config->proto_redir, nextarg))
+        if(proto2num(config, CURLPROTO_HTTP|CURLPROTO_HTTPS|
+                     CURLPROTO_FTP|CURLPROTO_FTPS,
+                     &config->proto_redir_str, nextarg))
           return PARAM_BAD_USE;
         break;
       case 'F': /* --resolve */

+ 3 - 2
src/tool_operate.c

@@ -1989,9 +1989,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
           my_setopt(curl, CURLOPT_NEW_FILE_PERMS, config->create_file_mode);
 
         if(config->proto_present)
-          my_setopt_flags(curl, CURLOPT_PROTOCOLS, config->proto);
+          my_setopt_str(curl, CURLOPT_PROTOCOLS_STR, config->proto_str);
         if(config->proto_redir_present)
-          my_setopt_flags(curl, CURLOPT_REDIR_PROTOCOLS, config->proto_redir);
+          my_setopt_str(curl, CURLOPT_REDIR_PROTOCOLS_STR,
+                        config->proto_redir_str);
 
         if(config->content_disposition
            && (urlnode->flags & GETOUT_USEREMOTE))

+ 30 - 12
src/tool_paramhlp.c

@@ -275,8 +275,8 @@ ParameterError str2udouble(double *valp, const char *str, long max)
 }
 
 /*
- * Parse the string and modify the long in the given address. Return
- * non-zero on failure, zero on success.
+ * Parse the string and provide an allocated libcurl compatible protocol
+ * string output. Return non-zero on failure, zero on success.
  *
  * The string is a list of protocols
  *
@@ -285,17 +285,22 @@ ParameterError str2udouble(double *valp, const char *str, long max)
  * data.
  */
 
-long proto2num(struct OperationConfig *config, long *val, const char *str)
+ParameterError proto2num(struct OperationConfig *config,
+                         unsigned int val, char **ostr, const char *str)
 {
   char *buffer;
   const char *sep = ",";
   char *token;
+  char obuf[256];
+  size_t olen = sizeof(obuf);
+  char *optr;
+  struct sprotos const *pp;
 
   static struct sprotos {
     const char *name;
-    long bit;
+    unsigned int bit;
   } const protos[] = {
-    { "all", CURLPROTO_ALL },
+    { "all", (unsigned int)CURLPROTO_ALL },
     { "http", CURLPROTO_HTTP },
     { "https", CURLPROTO_HTTPS },
     { "ftp", CURLPROTO_FTP },
@@ -305,6 +310,7 @@ long proto2num(struct OperationConfig *config, long *val, const char *str)
     { "telnet", CURLPROTO_TELNET },
     { "ldap", CURLPROTO_LDAP },
     { "ldaps", CURLPROTO_LDAPS },
+    { "mqtt", CURLPROTO_MQTT },
     { "dict", CURLPROTO_DICT },
     { "file", CURLPROTO_FILE },
     { "tftp", CURLPROTO_TFTP },
@@ -316,6 +322,7 @@ long proto2num(struct OperationConfig *config, long *val, const char *str)
     { "smtps", CURLPROTO_SMTPS },
     { "rtsp", CURLPROTO_RTSP },
     { "gopher", CURLPROTO_GOPHER },
+    { "gophers", CURLPROTO_GOPHERS },
     { "smb", CURLPROTO_SMB },
     { "smbs", CURLPROTO_SMBS },
     { NULL, 0 }
@@ -335,8 +342,6 @@ long proto2num(struct OperationConfig *config, long *val, const char *str)
       token = strtok(NULL, sep)) {
     enum e_action { allow, deny, set } action = allow;
 
-    struct sprotos const *pp;
-
     /* Process token modifiers */
     while(!ISALNUM(*token)) { /* may be NULL if token is all modifiers */
       switch (*token++) {
@@ -359,13 +364,13 @@ long proto2num(struct OperationConfig *config, long *val, const char *str)
       if(curl_strequal(token, pp->name)) {
         switch(action) {
         case deny:
-          *val &= ~(pp->bit);
+          val &= ~(pp->bit);
           break;
         case allow:
-          *val |= pp->bit;
+          val |= pp->bit;
           break;
         case set:
-          *val = pp->bit;
+          val = pp->bit;
           break;
         }
         break;
@@ -376,12 +381,25 @@ long proto2num(struct OperationConfig *config, long *val, const char *str)
       /* If they have specified only this protocol, we say treat it as
          if no protocols are allowed */
       if(action == set)
-        *val = 0;
+        val = 0;
       warnf(config->global, "unrecognized protocol '%s'\n", token);
     }
   }
   Curl_safefree(buffer);
-  return 0;
+
+  optr = obuf;
+  for(pp = &protos[1]; pp->name; pp++) {
+    if(val & pp->bit) {
+      size_t n = msnprintf(optr, olen, "%s%s",
+                           olen != sizeof(obuf) ? "," : "",
+                           pp->name);
+      olen -= n;
+      optr += n;
+    }
+  }
+  *ostr = strdup(obuf);
+
+  return *ostr ? PARAM_OK : PARAM_NO_MEM;
 }
 
 /**

+ 3 - 1
src/tool_paramhlp.h

@@ -39,7 +39,9 @@ ParameterError oct2nummax(long *val, const char *str, long max);
 ParameterError str2unummax(long *val, const char *str, long max);
 ParameterError str2udouble(double *val, const char *str, long max);
 
-long proto2num(struct OperationConfig *config, long *val, const char *str);
+ParameterError proto2num(struct OperationConfig *config,
+                         unsigned int val, char **obuf,
+                         const char *str);
 
 int check_protocol(const char *str);
 

+ 1 - 1
tests/data/Makefile.inc

@@ -201,7 +201,7 @@ test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \
 test1558 test1559 test1560 test1561 test1562 test1563 test1564 test1565 \
 test1566 test1567 test1568 test1569 test1570 \
 \
-test1590 test1591 test1592 test1593 test1594 test1595 test1596 \
+test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \
 \
 test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \
 test1608 test1609 test1610 test1611 test1612 test1613 \

+ 1 - 3
tests/data/test1401

@@ -91,9 +91,7 @@ int main(int argc, char *argv[])
   curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L);
 %endif
   curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);
-  curl_easy_setopt(hnd, CURLOPT_PROTOCOLS, (long)CURLPROTO_FILE |
-                                           (long)CURLPROTO_FTP |
-                                           (long)CURLPROTO_HTTP);
+  curl_easy_setopt(hnd, CURLOPT_PROTOCOLS_STR, "http,ftp,file");
 
   /* Here is a list of options the curl code used that cannot get generated
      as source easily. You may choose to either not use them or implement

+ 32 - 0
tests/data/test1597

@@ -0,0 +1,32 @@
+<testcase>
+<info>
+<keywords>
+CURLOPT_PROTOCOLS_STR
+</keywords>
+</info>
+
+# Server-side
+<reply>
+</reply>
+# Client-side
+<client>
+<server>
+none
+</server>
+<name>
+CURLOPT_PROTOCOLS_STR
+</name>
+<tool>
+lib%TESTNUMBER
+</tool>
+<command>
+-
+</command>
+</client>
+
+<verify>
+<stdout>
+Tested 15 strings
+</stdout>
+</verify>
+</testcase>

+ 4 - 1
tests/libtest/Makefile.inc

@@ -60,7 +60,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect                \
  lib1540         lib1542 lib1543 \
  lib1550 lib1551 lib1552 lib1553 lib1554 lib1555 lib1556 lib1557 \
  lib1558 lib1559 lib1560 lib1564 lib1565 lib1567 lib1568 lib1569 \
- lib1591 lib1592 lib1593 lib1594 lib1596 \
+ lib1591 lib1592 lib1593 lib1594 lib1596 lib1597 \
          lib1905 lib1906 lib1907 lib1908 lib1910 lib1911 lib1912 lib1913 \
          lib1915 lib1916 lib1917 lib1918 lib1919 \
  lib1933 lib1934 lib1935 lib1936 lib1937 lib1938 lib1939 lib1940 \
@@ -657,6 +657,9 @@ lib1596_SOURCES = lib1594.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib1596_LDADD = $(TESTUTIL_LIBS)
 lib1596_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1596
 
+lib1597_SOURCES = lib1597.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1597_LDADD = $(TESTUTIL_LIBS)
+
 lib1905_SOURCES = lib1905.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib1905_LDADD = $(TESTUTIL_LIBS)
 lib1905_CPPFLAGS = $(AM_CPPFLAGS)

+ 85 - 0
tests/libtest/lib1597.c

@@ -0,0 +1,85 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/* Testing CURLOPT_PROTOCOLS_STR */
+
+#include "test.h"
+
+#include "memdebug.h"
+
+struct pair {
+  const char *in;
+  CURLcode exp;
+};
+
+int test(char *URL)
+{
+  CURL *curl = NULL;
+  int res = 0;
+  CURLcode result = CURLE_OK;
+  int i;
+
+  struct pair prots[] = {
+    {"goobar", CURLE_BAD_FUNCTION_ARGUMENT},
+    {"http ", CURLE_BAD_FUNCTION_ARGUMENT},
+    {" http", CURLE_BAD_FUNCTION_ARGUMENT},
+    {"http", CURLE_OK},
+    {"http,", CURLE_OK},
+    {"https,", CURLE_OK},
+    {"https,http", CURLE_OK},
+    {"http,http", CURLE_OK},
+    {"HTTP,HTTP", CURLE_OK},
+    {",HTTP,HTTP", CURLE_OK},
+    {"http,http,ft", CURLE_BAD_FUNCTION_ARGUMENT},
+    {"", CURLE_BAD_FUNCTION_ARGUMENT},
+    {",,", CURLE_BAD_FUNCTION_ARGUMENT},
+    {"DICT,FILE,FTP,FTPS,GOPHER,GOPHERS,HTTP,HTTPS,IMAP,IMAPS,LDAP,LDAPS,"
+     "POP3,POP3S,RTMP,RTMPE,RTMPS,RTMPT,RTMPTE,RTMPTS,RTSP,SCP,SFTP,SMB,"
+     "SMBS,SMTP,SMTPS,TELNET,TFTP", CURLE_OK},
+    {"all", CURLE_OK},
+    {NULL, FALSE},
+  };
+  (void)URL;
+
+  global_init(CURL_GLOBAL_ALL);
+
+  easy_init(curl);
+
+  for(i = 0; prots[i].in; i++) {
+    result = curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, prots[i].in);
+    if(result != prots[i].exp) {
+      printf("unexpectedly '%s' returned %u\n",
+             prots[i].in, result);
+      break;
+    }
+  }
+  printf("Tested %u strings\n", i);
+  res = (int)result;
+
+  test_cleanup:
+  curl_easy_cleanup(curl);
+  curl_global_cleanup();
+
+  return (int)result;
+}