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