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