Imported Upstream version 7.53.1
[platform/upstream/curl.git] / src / tool_urlglob.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2016, 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.haxx.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  ***************************************************************************/
22 #include "tool_setup.h"
23
24 #define ENABLE_CURLX_PRINTF
25 /* use our own printf() functions */
26 #include "curlx.h"
27 #include "tool_cfgable.h"
28 #include "tool_doswin.h"
29 #include "tool_urlglob.h"
30 #include "tool_vms.h"
31
32 #include "memdebug.h" /* keep this as LAST include */
33
34 #define GLOBERROR(string, column, code) \
35   glob->error = string, glob->pos = column, code
36
37 void glob_cleanup(URLGlob* glob);
38
39 static CURLcode glob_fixed(URLGlob *glob, char *fixed, size_t len)
40 {
41   URLPattern *pat = &glob->pattern[glob->size];
42   pat->type = UPTSet;
43   pat->content.Set.size = 1;
44   pat->content.Set.ptr_s = 0;
45   pat->globindex = -1;
46
47   pat->content.Set.elements = malloc(sizeof(char *));
48
49   if(!pat->content.Set.elements)
50     return GLOBERROR("out of memory", 0, CURLE_OUT_OF_MEMORY);
51
52   pat->content.Set.elements[0] = malloc(len+1);
53   if(!pat->content.Set.elements[0])
54     return GLOBERROR("out of memory", 0, CURLE_OUT_OF_MEMORY);
55
56   memcpy(pat->content.Set.elements[0], fixed, len);
57   pat->content.Set.elements[0][len] = 0;
58
59   return CURLE_OK;
60 }
61
62 /* multiply
63  *
64  * Multiplies and checks for overflow.
65  */
66 static int multiply(unsigned long *amount, long with)
67 {
68   unsigned long sum = *amount * with;
69   if(sum/with != *amount)
70     return 1; /* didn't fit, bail out */
71   *amount = sum;
72   return 0;
73 }
74
75 static CURLcode glob_set(URLGlob *glob, char **patternp,
76                          size_t *posp, unsigned long *amount,
77                          int globindex)
78 {
79   /* processes a set expression with the point behind the opening '{'
80      ','-separated elements are collected until the next closing '}'
81   */
82   URLPattern *pat;
83   bool done = FALSE;
84   char *buf = glob->glob_buffer;
85   char *pattern = *patternp;
86   char *opattern = pattern;
87   size_t opos = *posp-1;
88
89   pat = &glob->pattern[glob->size];
90   /* patterns 0,1,2,... correspond to size=1,3,5,... */
91   pat->type = UPTSet;
92   pat->content.Set.size = 0;
93   pat->content.Set.ptr_s = 0;
94   pat->content.Set.elements = NULL;
95   pat->globindex = globindex;
96
97   while(!done) {
98     switch (*pattern) {
99     case '\0':                  /* URL ended while set was still open */
100       return GLOBERROR("unmatched brace", opos, CURLE_URL_MALFORMAT);
101
102     case '{':
103     case '[':                   /* no nested expressions at this time */
104       return GLOBERROR("nested brace", *posp, CURLE_URL_MALFORMAT);
105
106     case '}':                           /* set element completed */
107       if(opattern == pattern)
108         return GLOBERROR("empty string within braces", *posp,
109                          CURLE_URL_MALFORMAT);
110
111       /* add 1 to size since it'll be incremented below */
112       if(multiply(amount, pat->content.Set.size+1))
113         return GLOBERROR("range overflow", 0, CURLE_URL_MALFORMAT);
114
115       /* fall-through */
116     case ',':
117
118       *buf = '\0';
119       if(pat->content.Set.elements) {
120         char **new_arr = realloc(pat->content.Set.elements,
121                                  (pat->content.Set.size + 1) * sizeof(char *));
122         if(!new_arr)
123           return GLOBERROR("out of memory", 0, CURLE_OUT_OF_MEMORY);
124
125         pat->content.Set.elements = new_arr;
126       }
127       else
128         pat->content.Set.elements = malloc(sizeof(char *));
129
130       if(!pat->content.Set.elements)
131         return GLOBERROR("out of memory", 0, CURLE_OUT_OF_MEMORY);
132
133       pat->content.Set.elements[pat->content.Set.size] =
134         strdup(glob->glob_buffer);
135       if(!pat->content.Set.elements[pat->content.Set.size])
136         return GLOBERROR("out of memory", 0, CURLE_OUT_OF_MEMORY);
137       ++pat->content.Set.size;
138
139       if(*pattern == '}') {
140         pattern++; /* pass the closing brace */
141         done = TRUE;
142         continue;
143       }
144
145       buf = glob->glob_buffer;
146       ++pattern;
147       ++(*posp);
148       break;
149
150     case ']':                           /* illegal closing bracket */
151       return GLOBERROR("unexpected close bracket", *posp, CURLE_URL_MALFORMAT);
152
153     case '\\':                          /* escaped character, skip '\' */
154       if(pattern[1]) {
155         ++pattern;
156         ++(*posp);
157       }
158       /* intentional fallthrough */
159     default:
160       *buf++ = *pattern++;              /* copy character to set element */
161       ++(*posp);
162     }
163   }
164
165   *patternp = pattern; /* return with the new position */
166   return CURLE_OK;
167 }
168
169 static CURLcode glob_range(URLGlob *glob, char **patternp,
170                            size_t *posp, unsigned long *amount,
171                            int globindex)
172 {
173   /* processes a range expression with the point behind the opening '['
174      - char range: e.g. "a-z]", "B-Q]"
175      - num range: e.g. "0-9]", "17-2000]"
176      - num range with leading zeros: e.g. "001-999]"
177      expression is checked for well-formedness and collected until the next ']'
178   */
179   URLPattern *pat;
180   int rc;
181   char *pattern = *patternp;
182   char *c;
183
184   pat = &glob->pattern[glob->size];
185   pat->globindex = globindex;
186
187   if(ISALPHA(*pattern)) {
188     /* character range detected */
189     char min_c;
190     char max_c;
191     char end_c;
192     unsigned long step = 1;
193
194     pat->type = UPTCharRange;
195
196     rc = sscanf(pattern, "%c-%c%c", &min_c, &max_c, &end_c);
197
198     if(rc == 3) {
199       if(end_c == ':') {
200         char *endp;
201         errno = 0;
202         step = strtoul(&pattern[4], &endp, 10);
203         if(errno || &pattern[4] == endp || *endp != ']')
204           step = 0;
205         else
206           pattern = endp+1;
207       }
208       else if(end_c != ']')
209         /* then this is wrong */
210         rc = 0;
211       else
212         /* end_c == ']' */
213         pattern += 4;
214     }
215
216     *posp += (pattern - *patternp);
217
218     if(rc != 3 || !step || step > (unsigned)INT_MAX ||
219        (min_c == max_c && step != 1) ||
220        (min_c != max_c && (min_c > max_c || step > (unsigned)(max_c - min_c) ||
221                            (max_c - min_c) > ('z' - 'a'))))
222       /* the pattern is not well-formed */
223       return GLOBERROR("bad range", *posp, CURLE_URL_MALFORMAT);
224
225     /* if there was a ":[num]" thing, use that as step or else use 1 */
226     pat->content.CharRange.step = (int)step;
227     pat->content.CharRange.ptr_c = pat->content.CharRange.min_c = min_c;
228     pat->content.CharRange.max_c = max_c;
229
230     if(multiply(amount, ((pat->content.CharRange.max_c -
231                           pat->content.CharRange.min_c) /
232                          pat->content.CharRange.step + 1)))
233       return GLOBERROR("range overflow", *posp, CURLE_URL_MALFORMAT);
234   }
235   else if(ISDIGIT(*pattern)) {
236     /* numeric range detected */
237     unsigned long min_n;
238     unsigned long max_n = 0;
239     unsigned long step_n = 0;
240     char *endp;
241
242     pat->type = UPTNumRange;
243     pat->content.NumRange.padlength = 0;
244
245     if(*pattern == '0') {
246       /* leading zero specified, count them! */
247       c = pattern;
248       while(ISDIGIT(*c)) {
249         c++;
250         ++pat->content.NumRange.padlength; /* padding length is set for all
251                                               instances of this pattern */
252       }
253     }
254
255     errno = 0;
256     min_n = strtoul(pattern, &endp, 10);
257     if(errno || (endp == pattern))
258       endp=NULL;
259     else {
260       if(*endp != '-')
261         endp = NULL;
262       else {
263         pattern = endp+1;
264         while(*pattern && ISBLANK(*pattern))
265           pattern++;
266         if(!ISDIGIT(*pattern)) {
267           endp = NULL;
268           goto fail;
269         }
270         errno = 0;
271         max_n = strtoul(pattern, &endp, 10);
272         if(errno || (*endp == ':')) {
273           pattern = endp+1;
274           errno = 0;
275           step_n = strtoul(pattern, &endp, 10);
276           if(errno)
277             /* over/underflow situation */
278             endp = NULL;
279         }
280         else
281           step_n = 1;
282         if(endp && (*endp == ']')) {
283           pattern= endp+1;
284         }
285         else
286           endp = NULL;
287       }
288     }
289
290     fail:
291     *posp += (pattern - *patternp);
292
293     if(!endp || !step_n ||
294        (min_n == max_n && step_n != 1) ||
295        (min_n != max_n && (min_n > max_n || step_n > (max_n - min_n))))
296       /* the pattern is not well-formed */
297       return GLOBERROR("bad range", *posp, CURLE_URL_MALFORMAT);
298
299     /* typecasting to ints are fine here since we make sure above that we
300        are within 31 bits */
301     pat->content.NumRange.ptr_n = pat->content.NumRange.min_n = min_n;
302     pat->content.NumRange.max_n = max_n;
303     pat->content.NumRange.step = step_n;
304
305     if(multiply(amount, ((pat->content.NumRange.max_n -
306                           pat->content.NumRange.min_n) /
307                          pat->content.NumRange.step + 1)))
308       return GLOBERROR("range overflow", *posp, CURLE_URL_MALFORMAT);
309   }
310   else
311     return GLOBERROR("bad range specification", *posp, CURLE_URL_MALFORMAT);
312
313   *patternp = pattern;
314   return CURLE_OK;
315 }
316
317 static bool peek_ipv6(const char *str, size_t *skip)
318 {
319   /*
320    * Scan for a potential IPv6 literal.
321    * - Valid globs contain a hyphen and <= 1 colon.
322    * - IPv6 literals contain no hyphens and >= 2 colons.
323    */
324   size_t i = 0;
325   size_t colons = 0;
326   if(str[i++] != '[') {
327     return FALSE;
328   }
329   for(;;) {
330     const char c = str[i++];
331     if(ISALNUM(c) || c == '.' || c == '%') {
332       /* ok */
333     }
334     else if(c == ':') {
335       colons++;
336     }
337     else if(c == ']') {
338       *skip = i;
339       return colons >= 2 ? TRUE : FALSE;
340     }
341     else {
342       return FALSE;
343     }
344   }
345 }
346
347 static CURLcode glob_parse(URLGlob *glob, char *pattern,
348                            size_t pos, unsigned long *amount)
349 {
350   /* processes a literal string component of a URL
351      special characters '{' and '[' branch to set/range processing functions
352    */
353   CURLcode res = CURLE_OK;
354   int globindex = 0; /* count "actual" globs */
355
356   *amount = 1;
357
358   while(*pattern && !res) {
359     char *buf = glob->glob_buffer;
360     size_t sublen = 0;
361     while(*pattern && *pattern != '{') {
362       if(*pattern == '[') {
363         /* Skip over potential IPv6 literals. */
364         size_t skip;
365         if(peek_ipv6(pattern, &skip)) {
366           memcpy(buf, pattern, skip);
367           buf += skip;
368           pattern += skip;
369           sublen += skip;
370           continue;
371         }
372         break;
373       }
374       if(*pattern == '}' || *pattern == ']')
375         return GLOBERROR("unmatched close brace/bracket", pos,
376                          CURLE_URL_MALFORMAT);
377
378       /* only allow \ to escape known "special letters" */
379       if(*pattern == '\\' &&
380          (*(pattern+1) == '{' || *(pattern+1) == '[' ||
381           *(pattern+1) == '}' || *(pattern+1) == ']') ) {
382
383         /* escape character, skip '\' */
384         ++pattern;
385         ++pos;
386       }
387       *buf++ = *pattern++; /* copy character to literal */
388       ++pos;
389       sublen++;
390     }
391     if(sublen) {
392       /* we got a literal string, add it as a single-item list */
393       *buf = '\0';
394       res = glob_fixed(glob, glob->glob_buffer, sublen);
395     }
396     else {
397       switch (*pattern) {
398       case '\0': /* done  */
399         break;
400
401       case '{':
402         /* process set pattern */
403         pattern++;
404         pos++;
405         res = glob_set(glob, &pattern, &pos, amount, globindex++);
406         break;
407
408       case '[':
409         /* process range pattern */
410         pattern++;
411         pos++;
412         res = glob_range(glob, &pattern, &pos, amount, globindex++);
413         break;
414       }
415     }
416
417     if(++glob->size >= GLOB_PATTERN_NUM)
418       return GLOBERROR("too many globs", pos, CURLE_URL_MALFORMAT);
419   }
420   return res;
421 }
422
423 CURLcode glob_url(URLGlob **glob, char *url, unsigned long *urlnum,
424                   FILE *error)
425 {
426   /*
427    * We can deal with any-size, just make a buffer with the same length
428    * as the specified URL!
429    */
430   URLGlob *glob_expand;
431   unsigned long amount = 0;
432   char *glob_buffer;
433   CURLcode res;
434
435   *glob = NULL;
436
437   glob_buffer = malloc(strlen(url) + 1);
438   if(!glob_buffer)
439     return CURLE_OUT_OF_MEMORY;
440   glob_buffer[0]=0;
441
442   glob_expand = calloc(1, sizeof(URLGlob));
443   if(!glob_expand) {
444     Curl_safefree(glob_buffer);
445     return CURLE_OUT_OF_MEMORY;
446   }
447   glob_expand->urllen = strlen(url);
448   glob_expand->glob_buffer = glob_buffer;
449
450   res = glob_parse(glob_expand, url, 1, &amount);
451   if(!res)
452     *urlnum = amount;
453   else {
454     if(error && glob_expand->error) {
455       char text[128];
456       const char *t;
457       if(glob_expand->pos) {
458         snprintf(text, sizeof(text), "%s in column %zu", glob_expand->error,
459                  glob_expand->pos);
460         t = text;
461       }
462       else
463         t = glob_expand->error;
464
465       /* send error description to the error-stream */
466       fprintf(error, "curl: (%d) [globbing] %s\n", res, t);
467     }
468     /* it failed, we cleanup */
469     glob_cleanup(glob_expand);
470     *urlnum = 1;
471     return res;
472   }
473
474   *glob = glob_expand;
475   return CURLE_OK;
476 }
477
478 void glob_cleanup(URLGlob* glob)
479 {
480   size_t i;
481   int elem;
482
483   for(i = 0; i < glob->size; i++) {
484     if((glob->pattern[i].type == UPTSet) &&
485        (glob->pattern[i].content.Set.elements)) {
486       for(elem = glob->pattern[i].content.Set.size - 1;
487           elem >= 0;
488           --elem) {
489         Curl_safefree(glob->pattern[i].content.Set.elements[elem]);
490       }
491       Curl_safefree(glob->pattern[i].content.Set.elements);
492     }
493   }
494   Curl_safefree(glob->glob_buffer);
495   Curl_safefree(glob);
496 }
497
498 CURLcode glob_next_url(char **globbed, URLGlob *glob)
499 {
500   URLPattern *pat;
501   size_t i;
502   size_t len;
503   size_t buflen = glob->urllen + 1;
504   char *buf = glob->glob_buffer;
505
506   *globbed = NULL;
507
508   if(!glob->beenhere)
509     glob->beenhere = 1;
510   else {
511     bool carry = TRUE;
512
513     /* implement a counter over the index ranges of all patterns, starting
514        with the rightmost pattern */
515     for(i = 0; carry && (i < glob->size); i++) {
516       carry = FALSE;
517       pat = &glob->pattern[glob->size - 1 - i];
518       switch(pat->type) {
519       case UPTSet:
520         if((pat->content.Set.elements) &&
521            (++pat->content.Set.ptr_s == pat->content.Set.size)) {
522           pat->content.Set.ptr_s = 0;
523           carry = TRUE;
524         }
525         break;
526       case UPTCharRange:
527         pat->content.CharRange.ptr_c =
528           (char)(pat->content.CharRange.step +
529                  (int)((unsigned char)pat->content.CharRange.ptr_c));
530         if(pat->content.CharRange.ptr_c > pat->content.CharRange.max_c) {
531           pat->content.CharRange.ptr_c = pat->content.CharRange.min_c;
532           carry = TRUE;
533         }
534         break;
535       case UPTNumRange:
536         pat->content.NumRange.ptr_n += pat->content.NumRange.step;
537         if(pat->content.NumRange.ptr_n > pat->content.NumRange.max_n) {
538           pat->content.NumRange.ptr_n = pat->content.NumRange.min_n;
539           carry = TRUE;
540         }
541         break;
542       default:
543         printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
544         return CURLE_FAILED_INIT;
545       }
546     }
547     if(carry) {         /* first pattern ptr has run into overflow, done! */
548       /* TODO: verify if this should actally return CURLE_OK. */
549       return CURLE_OK; /* CURLE_OK to match previous behavior */
550     }
551   }
552
553   for(i = 0; i < glob->size; ++i) {
554     pat = &glob->pattern[i];
555     switch(pat->type) {
556     case UPTSet:
557       if(pat->content.Set.elements) {
558         snprintf(buf, buflen, "%s",
559                  pat->content.Set.elements[pat->content.Set.ptr_s]);
560         len = strlen(buf);
561         buf += len;
562         buflen -= len;
563       }
564       break;
565     case UPTCharRange:
566       if(buflen) {
567         *buf++ = pat->content.CharRange.ptr_c;
568         *buf = '\0';
569         buflen--;
570       }
571       break;
572     case UPTNumRange:
573       snprintf(buf, buflen, "%0*ld",
574                pat->content.NumRange.padlength,
575                pat->content.NumRange.ptr_n);
576       len = strlen(buf);
577       buf += len;
578       buflen -= len;
579       break;
580     default:
581       printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
582       return CURLE_FAILED_INIT;
583     }
584   }
585
586   *globbed = strdup(glob->glob_buffer);
587   if(!*globbed)
588     return CURLE_OUT_OF_MEMORY;
589
590   return CURLE_OK;
591 }
592
593 CURLcode glob_match_url(char **result, char *filename, URLGlob *glob)
594 {
595   char *target;
596   size_t allocsize;
597   char numbuf[18];
598   char *appendthis = NULL;
599   size_t appendlen = 0;
600   size_t stringlen = 0;
601
602   *result = NULL;
603
604   /* We cannot use the glob_buffer for storage here since the filename may
605    * be longer than the URL we use. We allocate a good start size, then
606    * we need to realloc in case of need.
607    */
608   allocsize = strlen(filename) + 1; /* make it at least one byte to store the
609                                        trailing zero */
610   target = malloc(allocsize);
611   if(!target)
612     return CURLE_OUT_OF_MEMORY;
613
614   while(*filename) {
615     if(*filename == '#' && ISDIGIT(filename[1])) {
616       unsigned long i;
617       char *ptr = filename;
618       unsigned long num = strtoul(&filename[1], &filename, 10);
619       URLPattern *pat =NULL;
620
621       if(num < glob->size) {
622         num--; /* make it zero based */
623         /* find the correct glob entry */
624         for(i=0; i<glob->size; i++) {
625           if(glob->pattern[i].globindex == (int)num) {
626             pat = &glob->pattern[i];
627             break;
628           }
629         }
630       }
631
632       if(pat) {
633         switch(pat->type) {
634         case UPTSet:
635           if(pat->content.Set.elements) {
636             appendthis = pat->content.Set.elements[pat->content.Set.ptr_s];
637             appendlen =
638               strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
639           }
640           break;
641         case UPTCharRange:
642           numbuf[0] = pat->content.CharRange.ptr_c;
643           numbuf[1] = 0;
644           appendthis = numbuf;
645           appendlen = 1;
646           break;
647         case UPTNumRange:
648           snprintf(numbuf, sizeof(numbuf), "%0*lu",
649                    pat->content.NumRange.padlength,
650                    pat->content.NumRange.ptr_n);
651           appendthis = numbuf;
652           appendlen = strlen(numbuf);
653           break;
654         default:
655           fprintf(stderr, "internal error: invalid pattern type (%d)\n",
656                   (int)pat->type);
657           Curl_safefree(target);
658           return CURLE_FAILED_INIT;
659         }
660       }
661       else {
662         /* #[num] out of range, use the #[num] in the output */
663         filename = ptr;
664         appendthis = filename++;
665         appendlen = 1;
666       }
667     }
668     else {
669       appendthis = filename++;
670       appendlen = 1;
671     }
672     if(appendlen + stringlen >= allocsize) {
673       char *newstr;
674       /* we append a single byte to allow for the trailing byte to be appended
675          at the end of this function outside the while() loop */
676       allocsize = (appendlen + stringlen) * 2;
677       newstr = realloc(target, allocsize + 1);
678       if(!newstr) {
679         Curl_safefree(target);
680         return CURLE_OUT_OF_MEMORY;
681       }
682       target = newstr;
683     }
684     memcpy(&target[stringlen], appendthis, appendlen);
685     stringlen += appendlen;
686   }
687   target[stringlen]= '\0';
688
689 #if defined(MSDOS) || defined(WIN32)
690   {
691     char *sanitized;
692     SANITIZEcode sc = sanitize_file_name(&sanitized, target,
693                                          (SANITIZE_ALLOW_PATH |
694                                           SANITIZE_ALLOW_RESERVED));
695     Curl_safefree(target);
696     if(sc)
697       return CURLE_URL_MALFORMAT;
698     target = sanitized;
699   }
700 #endif /* MSDOS || WIN32 */
701
702   *result = target;
703   return CURLE_OK;
704 }