xref: /curl/src/tool_paramhlp.c (revision cf337d85)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "tool_setup.h"
25 
26 #include "strcase.h"
27 
28 #define ENABLE_CURLX_PRINTF
29 /* use our own printf() functions */
30 #include "curlx.h"
31 
32 #include "tool_cfgable.h"
33 #include "tool_getparam.h"
34 #include "tool_getpass.h"
35 #include "tool_msgs.h"
36 #include "tool_paramhlp.h"
37 #include "tool_libinfo.h"
38 #include "tool_util.h"
39 #include "tool_version.h"
40 #include "dynbuf.h"
41 
42 #include "memdebug.h" /* keep this as LAST include */
43 
new_getout(struct OperationConfig * config)44 struct getout *new_getout(struct OperationConfig *config)
45 {
46   struct getout *node = calloc(1, sizeof(struct getout));
47   struct getout *last = config->url_last;
48   if(node) {
49     static int outnum = 0;
50 
51     /* append this new node last in the list */
52     if(last)
53       last->next = node;
54     else
55       config->url_list = node; /* first node */
56 
57     /* move the last pointer */
58     config->url_last = node;
59 
60     node->flags = config->default_node_flags;
61     node->num = outnum++;
62   }
63   return node;
64 }
65 
66 #define ISCRLF(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
67 
68 /* memcrlf() has two modes. Both operate on a given memory area with
69    a specified size.
70 
71    countcrlf FALSE - return number of bytes from the start that DO NOT include
72    any CR or LF or NULL
73 
74    countcrlf TRUE - return number of bytes from the start that are ONLY CR or
75    LF or NULL.
76 
77 */
memcrlf(char * orig,bool countcrlf,size_t max)78 static size_t memcrlf(char *orig,
79                       bool countcrlf, /* TRUE if we count CRLF, FALSE
80                                          if we count non-CRLF */
81                       size_t max)
82 {
83   char *ptr;
84   size_t total = max;
85   for(ptr = orig; max; max--, ptr++) {
86     bool crlf = ISCRLF(*ptr);
87     if(countcrlf ^ crlf)
88       return ptr - orig;
89   }
90   return total; /* no delimiter found */
91 }
92 
93 #define MAX_FILE2STRING (256*1024*1024) /* big enough ? */
94 
file2string(char ** bufp,FILE * file)95 ParameterError file2string(char **bufp, FILE *file)
96 {
97   struct curlx_dynbuf dyn;
98   DEBUGASSERT(MAX_FILE2STRING < INT_MAX); /* needs to fit in an int later */
99   curlx_dyn_init(&dyn, MAX_FILE2STRING);
100   if(file) {
101     do {
102       char buffer[4096];
103       char *ptr;
104       size_t nread = fread(buffer, 1, sizeof(buffer), file);
105       if(ferror(file)) {
106         curlx_dyn_free(&dyn);
107         *bufp = NULL;
108         return PARAM_READ_ERROR;
109       }
110       ptr = buffer;
111       while(nread) {
112         size_t nlen = memcrlf(ptr, FALSE, nread);
113         if(curlx_dyn_addn(&dyn, ptr, nlen))
114           return PARAM_NO_MEM;
115         nread -= nlen;
116 
117         if(nread) {
118           ptr += nlen;
119           nlen = memcrlf(ptr, TRUE, nread);
120           ptr += nlen;
121           nread -= nlen;
122         }
123       }
124     } while(!feof(file));
125   }
126   *bufp = curlx_dyn_ptr(&dyn);
127   return PARAM_OK;
128 }
129 
file2memory(char ** bufp,size_t * size,FILE * file)130 ParameterError file2memory(char **bufp, size_t *size, FILE *file)
131 {
132   if(file) {
133     size_t nread;
134     struct curlx_dynbuf dyn;
135     /* The size needs to fit in an int later */
136     DEBUGASSERT(MAX_FILE2MEMORY < INT_MAX);
137     curlx_dyn_init(&dyn, MAX_FILE2MEMORY);
138     do {
139       char buffer[4096];
140       nread = fread(buffer, 1, sizeof(buffer), file);
141       if(ferror(file)) {
142         curlx_dyn_free(&dyn);
143         *size = 0;
144         *bufp = NULL;
145         return PARAM_READ_ERROR;
146       }
147       if(nread)
148         if(curlx_dyn_addn(&dyn, buffer, nread))
149           return PARAM_NO_MEM;
150     } while(!feof(file));
151     *size = curlx_dyn_len(&dyn);
152     *bufp = curlx_dyn_ptr(&dyn);
153   }
154   else {
155     *size = 0;
156     *bufp = NULL;
157   }
158   return PARAM_OK;
159 }
160 
161 /*
162  * Parse the string and write the long in the given address. Return PARAM_OK
163  * on success, otherwise a parameter specific error enum.
164  *
165  * Since this function gets called with the 'nextarg' pointer from within the
166  * getparameter a lot, we must check it for NULL before accessing the str
167  * data.
168  */
getnum(long * val,const char * str,int base)169 static ParameterError getnum(long *val, const char *str, int base)
170 {
171   if(str) {
172     char *endptr = NULL;
173     long num;
174     if(!str[0])
175       return PARAM_BLANK_STRING;
176     errno = 0;
177     num = strtol(str, &endptr, base);
178     if(errno == ERANGE)
179       return PARAM_NUMBER_TOO_LARGE;
180     if((endptr != str) && (*endptr == '\0')) {
181       *val = num;
182       return PARAM_OK;  /* Ok */
183     }
184   }
185   return PARAM_BAD_NUMERIC; /* badness */
186 }
187 
str2num(long * val,const char * str)188 ParameterError str2num(long *val, const char *str)
189 {
190   return getnum(val, str, 10);
191 }
192 
oct2nummax(long * val,const char * str,long max)193 ParameterError oct2nummax(long *val, const char *str, long max)
194 {
195   ParameterError result = getnum(val, str, 8);
196   if(result != PARAM_OK)
197     return result;
198   else if(*val > max)
199     return PARAM_NUMBER_TOO_LARGE;
200   else if(*val < 0)
201     return PARAM_NEGATIVE_NUMERIC;
202 
203   return PARAM_OK;
204 }
205 
206 /*
207  * Parse the string and write the long in the given address. Return PARAM_OK
208  * on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
209  *
210  * Since this function gets called with the 'nextarg' pointer from within the
211  * getparameter a lot, we must check it for NULL before accessing the str
212  * data.
213  */
214 
str2unum(long * val,const char * str)215 ParameterError str2unum(long *val, const char *str)
216 {
217   ParameterError result = getnum(val, str, 10);
218   if(result != PARAM_OK)
219     return result;
220   if(*val < 0)
221     return PARAM_NEGATIVE_NUMERIC;
222 
223   return PARAM_OK;
224 }
225 
226 /*
227  * Parse the string and write the long in the given address if it is below the
228  * maximum allowed value. Return PARAM_OK on success, otherwise a parameter
229  * error enum. ONLY ACCEPTS POSITIVE NUMBERS!
230  *
231  * Since this function gets called with the 'nextarg' pointer from within the
232  * getparameter a lot, we must check it for NULL before accessing the str
233  * data.
234  */
235 
str2unummax(long * val,const char * str,long max)236 ParameterError str2unummax(long *val, const char *str, long max)
237 {
238   ParameterError result = str2unum(val, str);
239   if(result != PARAM_OK)
240     return result;
241   if(*val > max)
242     return PARAM_NUMBER_TOO_LARGE;
243 
244   return PARAM_OK;
245 }
246 
247 
248 /*
249  * Parse the string and write the double in the given address. Return PARAM_OK
250  * on success, otherwise a parameter specific error enum.
251  *
252  * The 'max' argument is the maximum value allowed, as the numbers are often
253  * multiplied when later used.
254  *
255  * Since this function gets called with the 'nextarg' pointer from within the
256  * getparameter a lot, we must check it for NULL before accessing the str
257  * data.
258  */
259 
str2double(double * val,const char * str,double max)260 static ParameterError str2double(double *val, const char *str, double max)
261 {
262   if(str) {
263     char *endptr;
264     double num;
265     errno = 0;
266     num = strtod(str, &endptr);
267     if(errno == ERANGE)
268       return PARAM_NUMBER_TOO_LARGE;
269     if(num > max) {
270       /* too large */
271       return PARAM_NUMBER_TOO_LARGE;
272     }
273     if((endptr != str) && (endptr == str + strlen(str))) {
274       *val = num;
275       return PARAM_OK;  /* Ok */
276     }
277   }
278   return PARAM_BAD_NUMERIC; /* badness */
279 }
280 
281 /*
282  * Parse the string as seconds with decimals, and write the number of
283  * milliseconds that corresponds in the given address. Return PARAM_OK on
284  * success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
285  *
286  * The 'max' argument is the maximum value allowed, as the numbers are often
287  * multiplied when later used.
288  *
289  * Since this function gets called with the 'nextarg' pointer from within the
290  * getparameter a lot, we must check it for NULL before accessing the str
291  * data.
292  */
293 
secs2ms(long * valp,const char * str)294 ParameterError secs2ms(long *valp, const char *str)
295 {
296   double value;
297   ParameterError result = str2double(&value, str, (double)LONG_MAX/1000);
298   if(result != PARAM_OK)
299     return result;
300   if(value < 0)
301     return PARAM_NEGATIVE_NUMERIC;
302 
303   *valp = (long)(value*1000);
304   return PARAM_OK;
305 }
306 
307 /*
308  * Implement protocol sets in null-terminated array of protocol name pointers.
309  */
310 
311 /* Return index of prototype token in set, card(set) if not found.
312    Can be called with proto == NULL to get card(set). */
protoset_index(const char * const * protoset,const char * proto)313 static size_t protoset_index(const char * const *protoset, const char *proto)
314 {
315   const char * const *p = protoset;
316 
317   DEBUGASSERT(proto == proto_token(proto));     /* Ensure it is tokenized. */
318 
319   for(; *p; p++)
320     if(proto == *p)
321       break;
322   return p - protoset;
323 }
324 
325 /* Include protocol token in set. */
protoset_set(const char ** protoset,const char * proto)326 static void protoset_set(const char **protoset, const char *proto)
327 {
328   if(proto) {
329     size_t n = protoset_index(protoset, proto);
330 
331     if(!protoset[n]) {
332       DEBUGASSERT(n < proto_count);
333       protoset[n] = proto;
334       protoset[n + 1] = NULL;
335     }
336   }
337 }
338 
339 /* Exclude protocol token from set. */
protoset_clear(const char ** protoset,const char * proto)340 static void protoset_clear(const char **protoset, const char *proto)
341 {
342   if(proto) {
343     size_t n = protoset_index(protoset, proto);
344 
345     if(protoset[n]) {
346       size_t m = protoset_index(protoset, NULL) - 1;
347 
348       protoset[n] = protoset[m];
349       protoset[m] = NULL;
350     }
351   }
352 }
353 
354 /*
355  * Parse the string and provide an allocated libcurl compatible protocol
356  * string output. Return non-zero on failure, zero on success.
357  *
358  * The string is a list of protocols
359  *
360  * Since this function gets called with the 'nextarg' pointer from within the
361  * getparameter a lot, we must check it for NULL before accessing the str
362  * data.
363  */
364 
365 #define MAX_PROTOSTRING (64*11) /* Enough room for 64 10-chars proto names. */
366 
proto2num(struct OperationConfig * config,const char * const * val,char ** ostr,const char * str)367 ParameterError proto2num(struct OperationConfig *config,
368                          const char * const *val, char **ostr, const char *str)
369 {
370   char *buffer;
371   const char *sep = ",";
372   char *token;
373   const char **protoset;
374   struct curlx_dynbuf obuf;
375   size_t proto;
376   CURLcode result;
377 
378   curlx_dyn_init(&obuf, MAX_PROTOSTRING);
379 
380   if(!str)
381     return PARAM_OPTION_AMBIGUOUS;
382 
383   buffer = strdup(str); /* because strtok corrupts it */
384   if(!buffer)
385     return PARAM_NO_MEM;
386 
387   protoset = malloc((proto_count + 1) * sizeof(*protoset));
388   if(!protoset) {
389     free(buffer);
390     return PARAM_NO_MEM;
391   }
392 
393   /* Preset protocol set with default values. */
394   protoset[0] = NULL;
395   for(; *val; val++) {
396     const char *p = proto_token(*val);
397 
398     if(p)
399       protoset_set(protoset, p);
400   }
401 
402   /* Allow strtok() here since this isn't used threaded */
403   /* !checksrc! disable BANNEDFUNC 2 */
404   for(token = strtok(buffer, sep);
405       token;
406       token = strtok(NULL, sep)) {
407     enum e_action { allow, deny, set } action = allow;
408 
409     /* Process token modifiers */
410     while(!ISALNUM(*token)) { /* may be NULL if token is all modifiers */
411       switch(*token++) {
412       case '=':
413         action = set;
414         break;
415       case '-':
416         action = deny;
417         break;
418       case '+':
419         action = allow;
420         break;
421       default: /* Includes case of terminating NULL */
422         free(buffer);
423         free((char *) protoset);
424         return PARAM_BAD_USE;
425       }
426     }
427 
428     if(curl_strequal(token, "all")) {
429       switch(action) {
430       case deny:
431         protoset[0] = NULL;
432         break;
433       case allow:
434       case set:
435         memcpy((char *) protoset,
436                built_in_protos, (proto_count + 1) * sizeof(*protoset));
437         break;
438       }
439     }
440     else {
441       const char *p = proto_token(token);
442 
443       if(p)
444         switch(action) {
445         case deny:
446           protoset_clear(protoset, p);
447           break;
448         case set:
449           protoset[0] = NULL;
450           FALLTHROUGH();
451         case allow:
452           protoset_set(protoset, p);
453           break;
454         }
455       else { /* unknown protocol */
456         /* If they have specified only this protocol, we say treat it as
457            if no protocols are allowed */
458         if(action == set)
459           protoset[0] = NULL;
460         warnf(config->global, "unrecognized protocol '%s'", token);
461       }
462     }
463   }
464   free(buffer);
465 
466   /* We need the protocols in alphabetic order for CI tests requirements. */
467   qsort((char *) protoset, protoset_index(protoset, NULL), sizeof(*protoset),
468         struplocompare4sort);
469 
470   result = curlx_dyn_addn(&obuf, "", 0);
471   for(proto = 0; protoset[proto] && !result; proto++)
472     result = curlx_dyn_addf(&obuf, "%s,", protoset[proto]);
473   free((char *) protoset);
474   curlx_dyn_setlen(&obuf, curlx_dyn_len(&obuf) - 1);
475   free(*ostr);
476   *ostr = curlx_dyn_ptr(&obuf);
477 
478   return *ostr ? PARAM_OK : PARAM_NO_MEM;
479 }
480 
481 /**
482  * Check if the given string is a protocol supported by libcurl
483  *
484  * @param str  the protocol name
485  * @return PARAM_OK  protocol supported
486  * @return PARAM_LIBCURL_UNSUPPORTED_PROTOCOL  protocol not supported
487  * @return PARAM_REQUIRES_PARAMETER   missing parameter
488  */
check_protocol(const char * str)489 ParameterError check_protocol(const char *str)
490 {
491   if(!str)
492     return PARAM_REQUIRES_PARAMETER;
493 
494   if(proto_token(str))
495     return PARAM_OK;
496   return PARAM_LIBCURL_UNSUPPORTED_PROTOCOL;
497 }
498 
499 /**
500  * Parses the given string looking for an offset (which may be a
501  * larger-than-integer value). The offset CANNOT be negative!
502  *
503  * @param val  the offset to populate
504  * @param str  the buffer containing the offset
505  * @return PARAM_OK if successful, a parameter specific error enum if failure.
506  */
str2offset(curl_off_t * val,const char * str)507 ParameterError str2offset(curl_off_t *val, const char *str)
508 {
509   char *endptr;
510   if(str[0] == '-')
511     /* offsets aren't negative, this indicates weird input */
512     return PARAM_NEGATIVE_NUMERIC;
513 
514 #if(SIZEOF_CURL_OFF_T > SIZEOF_LONG)
515   {
516     CURLofft offt = curlx_strtoofft(str, &endptr, 10, val);
517     if(CURL_OFFT_FLOW == offt)
518       return PARAM_NUMBER_TOO_LARGE;
519     else if(CURL_OFFT_INVAL == offt)
520       return PARAM_BAD_NUMERIC;
521   }
522 #else
523   errno = 0;
524   *val = strtol(str, &endptr, 0);
525   if((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE)
526     return PARAM_NUMBER_TOO_LARGE;
527 #endif
528   if((endptr != str) && (endptr == str + strlen(str)))
529     return PARAM_OK;
530 
531   return PARAM_BAD_NUMERIC;
532 }
533 
534 #define MAX_USERPWDLENGTH (100*1024)
checkpasswd(const char * kind,const size_t i,const bool last,char ** userpwd)535 static CURLcode checkpasswd(const char *kind, /* for what purpose */
536                             const size_t i,   /* operation index */
537                             const bool last,  /* TRUE if last operation */
538                             char **userpwd)   /* pointer to allocated string */
539 {
540   char *psep;
541   char *osep;
542 
543   if(!*userpwd)
544     return CURLE_OK;
545 
546   /* Attempt to find the password separator */
547   psep = strchr(*userpwd, ':');
548 
549   /* Attempt to find the options separator */
550   osep = strchr(*userpwd, ';');
551 
552   if(!psep && **userpwd != ';') {
553     /* no password present, prompt for one */
554     char passwd[2048] = "";
555     char prompt[256];
556     struct curlx_dynbuf dyn;
557 
558     curlx_dyn_init(&dyn, MAX_USERPWDLENGTH);
559     if(osep)
560       *osep = '\0';
561 
562     /* build a nice-looking prompt */
563     if(!i && last)
564       curlx_msnprintf(prompt, sizeof(prompt),
565                       "Enter %s password for user '%s':",
566                       kind, *userpwd);
567     else
568       curlx_msnprintf(prompt, sizeof(prompt),
569                       "Enter %s password for user '%s' on URL #%zu:",
570                       kind, *userpwd, i + 1);
571 
572     /* get password */
573     getpass_r(prompt, passwd, sizeof(passwd));
574     if(osep)
575       *osep = ';';
576 
577     if(curlx_dyn_addf(&dyn, "%s:%s", *userpwd, passwd))
578       return CURLE_OUT_OF_MEMORY;
579 
580     /* return the new string */
581     free(*userpwd);
582     *userpwd = curlx_dyn_ptr(&dyn);
583   }
584 
585   return CURLE_OK;
586 }
587 
add2list(struct curl_slist ** list,const char * ptr)588 ParameterError add2list(struct curl_slist **list, const char *ptr)
589 {
590   struct curl_slist *newlist = curl_slist_append(*list, ptr);
591   if(newlist)
592     *list = newlist;
593   else
594     return PARAM_NO_MEM;
595 
596   return PARAM_OK;
597 }
598 
ftpfilemethod(struct OperationConfig * config,const char * str)599 int ftpfilemethod(struct OperationConfig *config, const char *str)
600 {
601   if(curl_strequal("singlecwd", str))
602     return CURLFTPMETHOD_SINGLECWD;
603   if(curl_strequal("nocwd", str))
604     return CURLFTPMETHOD_NOCWD;
605   if(curl_strequal("multicwd", str))
606     return CURLFTPMETHOD_MULTICWD;
607 
608   warnf(config->global, "unrecognized ftp file method '%s', using default",
609         str);
610 
611   return CURLFTPMETHOD_MULTICWD;
612 }
613 
ftpcccmethod(struct OperationConfig * config,const char * str)614 int ftpcccmethod(struct OperationConfig *config, const char *str)
615 {
616   if(curl_strequal("passive", str))
617     return CURLFTPSSL_CCC_PASSIVE;
618   if(curl_strequal("active", str))
619     return CURLFTPSSL_CCC_ACTIVE;
620 
621   warnf(config->global, "unrecognized ftp CCC method '%s', using default",
622         str);
623 
624   return CURLFTPSSL_CCC_PASSIVE;
625 }
626 
delegation(struct OperationConfig * config,const char * str)627 long delegation(struct OperationConfig *config, const char *str)
628 {
629   if(curl_strequal("none", str))
630     return CURLGSSAPI_DELEGATION_NONE;
631   if(curl_strequal("policy", str))
632     return CURLGSSAPI_DELEGATION_POLICY_FLAG;
633   if(curl_strequal("always", str))
634     return CURLGSSAPI_DELEGATION_FLAG;
635 
636   warnf(config->global, "unrecognized delegation method '%s', using none",
637         str);
638 
639   return CURLGSSAPI_DELEGATION_NONE;
640 }
641 
642 /*
643  * my_useragent: returns allocated string with default user agent
644  */
my_useragent(void)645 static char *my_useragent(void)
646 {
647   return strdup(CURL_NAME "/" CURL_VERSION);
648 }
649 
650 #define isheadersep(x) ((((x)==':') || ((x)==';')))
651 
652 /*
653  * inlist() returns true if the given 'checkfor' header is present in the
654  * header list.
655  */
inlist(const struct curl_slist * head,const char * checkfor)656 static bool inlist(const struct curl_slist *head,
657                    const char *checkfor)
658 {
659   size_t thislen = strlen(checkfor);
660   DEBUGASSERT(thislen);
661   DEBUGASSERT(checkfor[thislen-1] != ':');
662 
663   for(; head; head = head->next) {
664     if(curl_strnequal(head->data, checkfor, thislen) &&
665        isheadersep(head->data[thislen]) )
666       return TRUE;
667   }
668 
669   return FALSE;
670 }
671 
get_args(struct OperationConfig * config,const size_t i)672 CURLcode get_args(struct OperationConfig *config, const size_t i)
673 {
674   CURLcode result = CURLE_OK;
675   bool last = (config->next ? FALSE : TRUE);
676 
677   if(config->jsoned) {
678     ParameterError err = PARAM_OK;
679     /* --json also implies json Content-Type: and Accept: headers - if
680        they are not set with -H */
681     if(!inlist(config->headers, "Content-Type"))
682       err = add2list(&config->headers, "Content-Type: application/json");
683     if(!err && !inlist(config->headers, "Accept"))
684       err = add2list(&config->headers, "Accept: application/json");
685     if(err)
686       return CURLE_OUT_OF_MEMORY;
687   }
688 
689   /* Check we have a password for the given host user */
690   if(config->userpwd && !config->oauth_bearer) {
691     result = checkpasswd("host", i, last, &config->userpwd);
692     if(result)
693       return result;
694   }
695 
696   /* Check we have a password for the given proxy user */
697   if(config->proxyuserpwd) {
698     result = checkpasswd("proxy", i, last, &config->proxyuserpwd);
699     if(result)
700       return result;
701   }
702 
703   /* Check we have a user agent */
704   if(!config->useragent) {
705     config->useragent = my_useragent();
706     if(!config->useragent) {
707       errorf(config->global, "out of memory");
708       result = CURLE_OUT_OF_MEMORY;
709     }
710   }
711 
712   return result;
713 }
714 
715 /*
716  * Parse the string and modify ssl_version in the val argument. Return PARAM_OK
717  * on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
718  *
719  * Since this function gets called with the 'nextarg' pointer from within the
720  * getparameter a lot, we must check it for NULL before accessing the str
721  * data.
722  */
723 
str2tls_max(long * val,const char * str)724 ParameterError str2tls_max(long *val, const char *str)
725 {
726    static struct s_tls_max {
727     const char *tls_max_str;
728     long tls_max;
729   } const tls_max_array[] = {
730     { "default", CURL_SSLVERSION_MAX_DEFAULT },
731     { "1.0",     CURL_SSLVERSION_MAX_TLSv1_0 },
732     { "1.1",     CURL_SSLVERSION_MAX_TLSv1_1 },
733     { "1.2",     CURL_SSLVERSION_MAX_TLSv1_2 },
734     { "1.3",     CURL_SSLVERSION_MAX_TLSv1_3 }
735   };
736   size_t i = 0;
737   if(!str)
738     return PARAM_REQUIRES_PARAMETER;
739   for(i = 0; i < sizeof(tls_max_array)/sizeof(tls_max_array[0]); i++) {
740     if(!strcmp(str, tls_max_array[i].tls_max_str)) {
741       *val = tls_max_array[i].tls_max;
742       return PARAM_OK;
743     }
744   }
745   return PARAM_BAD_USE;
746 }
747