Base code merged to SPIN 2.4
[platform/upstream/curl.git] / src / tool_urlglob.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2014, 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 _MPRINTF_REPLACE /* we want curl-functions instead of native ones */
25 #include <curl/mprintf.h>
26
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[3], &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 + 1)))
226       return GLOBERROR("range overflow", *posp, CURLE_URL_MALFORMAT);
227   }
228   else if(ISDIGIT(*pattern)) {
229     /* numeric range detected */
230     unsigned long min_n;
231     unsigned long max_n = 0;
232     unsigned long step_n = 0;
233     char *endp;
234
235     pat->type = UPTNumRange;
236     pat->content.NumRange.padlength = 0;
237
238     if(*pattern == '0') {
239       /* leading zero specified, count them! */
240       c = pattern;
241       while(ISDIGIT(*c)) {
242         c++;
243         ++pat->content.NumRange.padlength; /* padding length is set for all
244                                               instances of this pattern */
245       }
246     }
247
248     errno = 0;
249     min_n = strtoul(pattern, &endp, 10);
250     if(errno || (endp == pattern))
251       endp=NULL;
252     else {
253       if(*endp != '-')
254         endp = NULL;
255       else {
256         pattern = endp+1;
257         errno = 0;
258         max_n = strtoul(pattern, &endp, 10);
259         if(errno || (*endp == ':')) {
260           pattern = endp+1;
261           errno = 0;
262           step_n = strtoul(pattern, &endp, 10);
263           if(errno)
264             /* over/underflow situation */
265             endp = NULL;
266         }
267         else
268           step_n = 1;
269         if(endp && (*endp == ']')) {
270           pattern= endp+1;
271         }
272         else
273           endp = NULL;
274       }
275     }
276
277     *posp += (pattern - *patternp);
278
279     if(!endp || (min_n > max_n) || (step_n > (max_n - min_n)))
280       /* the pattern is not well-formed */
281       return GLOBERROR("bad range", *posp, CURLE_URL_MALFORMAT);
282
283     /* typecasting to ints are fine here since we make sure above that we
284        are within 31 bits */
285     pat->content.NumRange.ptr_n = pat->content.NumRange.min_n = min_n;
286     pat->content.NumRange.max_n = max_n;
287     pat->content.NumRange.step = step_n;
288
289     if(multiply(amount, (pat->content.NumRange.max_n -
290                          pat->content.NumRange.min_n + 1)))
291       return GLOBERROR("range overflow", *posp, CURLE_URL_MALFORMAT);
292   }
293   else
294     return GLOBERROR("bad range specification", *posp, CURLE_URL_MALFORMAT);
295
296   *patternp = pattern;
297   return CURLE_OK;
298 }
299
300 static bool peek_ipv6(const char *str, size_t *skip)
301 {
302   /*
303    * Scan for a potential IPv6 literal.
304    * - Valid globs contain a hyphen and <= 1 colon.
305    * - IPv6 literals contain no hyphens and >= 2 colons.
306    */
307   size_t i = 0;
308   size_t colons = 0;
309   if(str[i++] != '[') {
310     return FALSE;
311   }
312   for(;;) {
313     const char c = str[i++];
314     if(ISALNUM(c) || c == '.' || c == '%') {
315       /* ok */
316     }
317     else if(c == ':') {
318       colons++;
319     }
320     else if(c == ']') {
321       *skip = i;
322       return colons >= 2 ? TRUE : FALSE;
323     }
324     else {
325       return FALSE;
326     }
327   }
328 }
329
330 static CURLcode glob_parse(URLGlob *glob, char *pattern,
331                            size_t pos, unsigned long *amount)
332 {
333   /* processes a literal string component of a URL
334      special characters '{' and '[' branch to set/range processing functions
335    */
336   CURLcode res = CURLE_OK;
337   int globindex = 0; /* count "actual" globs */
338
339   *amount = 1;
340
341   while(*pattern && !res) {
342     char *buf = glob->glob_buffer;
343     size_t sublen = 0;
344     while(*pattern && *pattern != '{') {
345       if(*pattern == '[') {
346         /* Skip over potential IPv6 literals. */
347         size_t skip;
348         if(peek_ipv6(pattern, &skip)) {
349           memcpy(buf, pattern, skip);
350           buf += skip;
351           pattern += skip;
352           sublen += skip;
353           continue;
354         }
355         break;
356       }
357       if(*pattern == '}' || *pattern == ']')
358         return GLOBERROR("unmatched close brace/bracket", pos,
359                          CURLE_URL_MALFORMAT);
360
361       /* only allow \ to escape known "special letters" */
362       if(*pattern == '\\' &&
363          (*(pattern+1) == '{' || *(pattern+1) == '[' ||
364           *(pattern+1) == '}' || *(pattern+1) == ']') ) {
365
366         /* escape character, skip '\' */
367         ++pattern;
368         ++pos;
369       }
370       *buf++ = *pattern++; /* copy character to literal */
371       ++pos;
372       sublen++;
373     }
374     if(sublen) {
375       /* we got a literal string, add it as a single-item list */
376       *buf = '\0';
377       res = glob_fixed(glob, glob->glob_buffer, sublen);
378     }
379     else {
380       switch (*pattern) {
381       case '\0': /* done  */
382         break;
383
384       case '{':
385         /* process set pattern */
386         pattern++;
387         pos++;
388         res = glob_set(glob, &pattern, &pos, amount, globindex++);
389         break;
390
391       case '[':
392         /* process range pattern */
393         pattern++;
394         pos++;
395         res = glob_range(glob, &pattern, &pos, amount, globindex++);
396         break;
397       }
398     }
399
400     if(++glob->size > GLOB_PATTERN_NUM)
401       return GLOBERROR("too many globs", pos, CURLE_URL_MALFORMAT);
402   }
403   return res;
404 }
405
406 CURLcode glob_url(URLGlob** glob, char* url, unsigned long *urlnum,
407                   FILE *error)
408 {
409   /*
410    * We can deal with any-size, just make a buffer with the same length
411    * as the specified URL!
412    */
413   URLGlob *glob_expand;
414   unsigned long amount = 0;
415   char *glob_buffer;
416   CURLcode res;
417
418   *glob = NULL;
419
420   glob_buffer = malloc(strlen(url) + 1);
421   if(!glob_buffer)
422     return CURLE_OUT_OF_MEMORY;
423
424   glob_expand = calloc(1, sizeof(URLGlob));
425   if(!glob_expand) {
426     Curl_safefree(glob_buffer);
427     return CURLE_OUT_OF_MEMORY;
428   }
429   glob_expand->urllen = strlen(url);
430   glob_expand->glob_buffer = glob_buffer;
431
432   res = glob_parse(glob_expand, url, 1, &amount);
433   if(!res)
434     *urlnum = amount;
435   else {
436     if(error && glob_expand->error) {
437       char text[128];
438       const char *t;
439       if(glob_expand->pos) {
440         snprintf(text, sizeof(text), "%s in column %zu", glob_expand->error,
441                  glob_expand->pos);
442         t = text;
443       }
444       else
445         t = glob_expand->error;
446
447       /* send error description to the error-stream */
448       fprintf(error, "curl: (%d) [globbing] %s\n", res, t);
449     }
450     /* it failed, we cleanup */
451     glob_cleanup(glob_expand);
452     *urlnum = 1;
453     return res;
454   }
455
456   *glob = glob_expand;
457   return CURLE_OK;
458 }
459
460 void glob_cleanup(URLGlob* glob)
461 {
462   size_t i;
463   int elem;
464
465   for(i = 0; i < glob->size; i++) {
466     if((glob->pattern[i].type == UPTSet) &&
467        (glob->pattern[i].content.Set.elements)) {
468       for(elem = glob->pattern[i].content.Set.size - 1;
469           elem >= 0;
470           --elem) {
471         Curl_safefree(glob->pattern[i].content.Set.elements[elem]);
472       }
473       Curl_safefree(glob->pattern[i].content.Set.elements);
474     }
475   }
476   Curl_safefree(glob->glob_buffer);
477   Curl_safefree(glob);
478 }
479
480 CURLcode glob_next_url(char **globbed, URLGlob *glob)
481 {
482   URLPattern *pat;
483   size_t i;
484   size_t len;
485   size_t buflen = glob->urllen + 1;
486   char *buf = glob->glob_buffer;
487
488   *globbed = NULL;
489
490   if(!glob->beenhere)
491     glob->beenhere = 1;
492   else {
493     bool carry = TRUE;
494
495     /* implement a counter over the index ranges of all patterns, starting
496        with the rightmost pattern */
497     for(i = 0; carry && (i < glob->size); i++) {
498       carry = FALSE;
499       pat = &glob->pattern[glob->size - 1 - i];
500       switch (pat->type) {
501       case UPTSet:
502         if((pat->content.Set.elements) &&
503            (++pat->content.Set.ptr_s == pat->content.Set.size)) {
504           pat->content.Set.ptr_s = 0;
505           carry = TRUE;
506         }
507         break;
508       case UPTCharRange:
509         pat->content.CharRange.ptr_c =
510           (char)(pat->content.CharRange.step +
511                  (int)((unsigned char)pat->content.CharRange.ptr_c));
512         if(pat->content.CharRange.ptr_c > pat->content.CharRange.max_c) {
513           pat->content.CharRange.ptr_c = pat->content.CharRange.min_c;
514           carry = TRUE;
515         }
516         break;
517       case UPTNumRange:
518         pat->content.NumRange.ptr_n += pat->content.NumRange.step;
519         if(pat->content.NumRange.ptr_n > pat->content.NumRange.max_n) {
520           pat->content.NumRange.ptr_n = pat->content.NumRange.min_n;
521           carry = TRUE;
522         }
523         break;
524       default:
525         printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
526         return CURLE_FAILED_INIT;
527       }
528     }
529     if(carry) {         /* first pattern ptr has run into overflow, done! */
530       /* TODO: verify if this should actally return CURLE_OK. */
531       return CURLE_OK; /* CURLE_OK to match previous behavior */
532     }
533   }
534
535   for(i = 0; i < glob->size; ++i) {
536     pat = &glob->pattern[i];
537     switch(pat->type) {
538     case UPTSet:
539       if(pat->content.Set.elements) {
540         len = strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
541         snprintf(buf, buflen, "%s",
542                  pat->content.Set.elements[pat->content.Set.ptr_s]);
543         buf += len;
544         buflen -= len;
545       }
546       break;
547     case UPTCharRange:
548       *buf++ = pat->content.CharRange.ptr_c;
549       break;
550     case UPTNumRange:
551       len = snprintf(buf, buflen, "%0*ld",
552                      pat->content.NumRange.padlength,
553                      pat->content.NumRange.ptr_n);
554       buf += len;
555       buflen -= len;
556       break;
557     default:
558       printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
559       return CURLE_FAILED_INIT;
560     }
561   }
562   *buf = '\0';
563
564   *globbed = strdup(glob->glob_buffer);
565   if(!*globbed)
566     return CURLE_OUT_OF_MEMORY;
567
568   return CURLE_OK;
569 }
570
571 CURLcode glob_match_url(char **result, char *filename, URLGlob *glob)
572 {
573   char *target;
574   size_t allocsize;
575   char numbuf[18];
576   char *appendthis = NULL;
577   size_t appendlen = 0;
578   size_t stringlen = 0;
579
580   *result = NULL;
581
582   /* We cannot use the glob_buffer for storage here since the filename may
583    * be longer than the URL we use. We allocate a good start size, then
584    * we need to realloc in case of need.
585    */
586   allocsize = strlen(filename) + 1; /* make it at least one byte to store the
587                                        trailing zero */
588   target = malloc(allocsize);
589   if(!target)
590     return CURLE_OUT_OF_MEMORY;
591
592   while(*filename) {
593     if(*filename == '#' && ISDIGIT(filename[1])) {
594       unsigned long i;
595       char *ptr = filename;
596       unsigned long num = strtoul(&filename[1], &filename, 10);
597       URLPattern *pat =NULL;
598
599       if(num < glob->size) {
600         num--; /* make it zero based */
601         /* find the correct glob entry */
602         for(i=0; i<glob->size; i++) {
603           if(glob->pattern[i].globindex == (int)num) {
604             pat = &glob->pattern[i];
605             break;
606           }
607         }
608       }
609
610       if(pat) {
611         switch (pat->type) {
612         case UPTSet:
613           if(pat->content.Set.elements) {
614             appendthis = pat->content.Set.elements[pat->content.Set.ptr_s];
615             appendlen =
616               strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
617           }
618           break;
619         case UPTCharRange:
620           numbuf[0] = pat->content.CharRange.ptr_c;
621           numbuf[1] = 0;
622           appendthis = numbuf;
623           appendlen = 1;
624           break;
625         case UPTNumRange:
626           snprintf(numbuf, sizeof(numbuf), "%0*d",
627                    pat->content.NumRange.padlength,
628                    pat->content.NumRange.ptr_n);
629           appendthis = numbuf;
630           appendlen = strlen(numbuf);
631           break;
632         default:
633           fprintf(stderr, "internal error: invalid pattern type (%d)\n",
634                   (int)pat->type);
635           Curl_safefree(target);
636           return CURLE_FAILED_INIT;
637         }
638       }
639       else {
640         /* #[num] out of range, use the #[num] in the output */
641         filename = ptr;
642         appendthis = filename++;
643         appendlen = 1;
644       }
645     }
646     else {
647       appendthis = filename++;
648       appendlen = 1;
649     }
650     if(appendlen + stringlen >= allocsize) {
651       char *newstr;
652       /* we append a single byte to allow for the trailing byte to be appended
653          at the end of this function outside the while() loop */
654       allocsize = (appendlen + stringlen) * 2;
655       newstr = realloc(target, allocsize + 1);
656       if(!newstr) {
657         Curl_safefree(target);
658         return CURLE_OUT_OF_MEMORY;
659       }
660       target = newstr;
661     }
662     memcpy(&target[stringlen], appendthis, appendlen);
663     stringlen += appendlen;
664   }
665   target[stringlen]= '\0';
666   *result = target;
667   return CURLE_OK;
668 }
669