curl tool: reviewed code moved to tool_*.[ch] files
[platform/upstream/curl.git] / src / tool_urlglob.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2011, 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 "setup.h"
23
24 #include <curl/curl.h>
25
26 #define _MPRINTF_REPLACE /* we want curl-functions instead of native ones */
27 #include <curl/mprintf.h>
28
29 #include "tool_urlglob.h"
30 #include "tool_vms.h"
31
32 #include "memdebug.h" /* keep this as LAST include */
33
34 typedef enum {
35   GLOB_OK,
36   GLOB_NO_MEM,
37   GLOB_ERROR
38 } GlobCode;
39
40 /*
41  * glob_word()
42  *
43  * Input a full globbed string, set the forth argument to the amount of
44  * strings we get out of this. Return GlobCode.
45  */
46 static GlobCode glob_word(URLGlob *, /* object anchor */
47                           char *,    /* globbed string */
48                           size_t,       /* position */
49                           int *);    /* returned number of strings */
50
51 static GlobCode glob_set(URLGlob *glob, char *pattern,
52                          size_t pos, int *amount)
53 {
54   /* processes a set expression with the point behind the opening '{'
55      ','-separated elements are collected until the next closing '}'
56   */
57   URLPattern *pat;
58   GlobCode res;
59   bool done = FALSE;
60   char* buf = glob->glob_buffer;
61
62   pat = &glob->pattern[glob->size / 2];
63   /* patterns 0,1,2,... correspond to size=1,3,5,... */
64   pat->type = UPTSet;
65   pat->content.Set.size = 0;
66   pat->content.Set.ptr_s = 0;
67   pat->content.Set.elements = NULL;
68
69   ++glob->size;
70
71   while(!done) {
72     switch (*pattern) {
73     case '\0':                  /* URL ended while set was still open */
74       snprintf(glob->errormsg, sizeof(glob->errormsg),
75                "unmatched brace at pos %zu\n", pos);
76       return GLOB_ERROR;
77
78     case '{':
79     case '[':                   /* no nested expressions at this time */
80       snprintf(glob->errormsg, sizeof(glob->errormsg),
81                "nested braces not supported at pos %zu\n", pos);
82       return GLOB_ERROR;
83
84     case ',':
85     case '}':                           /* set element completed */
86       *buf = '\0';
87       if(pat->content.Set.elements) {
88         char **new_arr = realloc(pat->content.Set.elements,
89                                  (pat->content.Set.size + 1) * sizeof(char*));
90         if(!new_arr) {
91           short elem;
92           for(elem = 0; elem < pat->content.Set.size; elem++)
93             Curl_safefree(pat->content.Set.elements[elem]);
94           Curl_safefree(pat->content.Set.elements);
95           pat->content.Set.ptr_s = 0;
96           pat->content.Set.size = 0;
97         }
98         pat->content.Set.elements = new_arr;
99       }
100       else
101         pat->content.Set.elements = malloc(sizeof(char*));
102       if(!pat->content.Set.elements) {
103         snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
104         return GLOB_NO_MEM;
105       }
106       pat->content.Set.elements[pat->content.Set.size] =
107         strdup(glob->glob_buffer);
108       if(!pat->content.Set.elements[pat->content.Set.size]) {
109         short elem;
110         for(elem = 0; elem < pat->content.Set.size; elem++)
111           Curl_safefree(pat->content.Set.elements[elem]);
112         Curl_safefree(pat->content.Set.elements);
113         pat->content.Set.ptr_s = 0;
114         pat->content.Set.size = 0;
115         snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
116         return GLOB_NO_MEM;
117       }
118       ++pat->content.Set.size;
119
120       if(*pattern == '}') {
121         /* entire set pattern completed */
122         int wordamount;
123
124         /* always check for a literal (may be "") between patterns */
125         res = glob_word(glob, ++pattern, ++pos, &wordamount);
126         if(res) {
127           short elem;
128           for(elem = 0; elem < pat->content.Set.size; elem++)
129             Curl_safefree(pat->content.Set.elements[elem]);
130           Curl_safefree(pat->content.Set.elements);
131           pat->content.Set.ptr_s = 0;
132           pat->content.Set.size = 0;
133           return res;
134         }
135
136         *amount = pat->content.Set.size * wordamount;
137
138         done = TRUE;
139         continue;
140       }
141
142       buf = glob->glob_buffer;
143       ++pattern;
144       ++pos;
145       break;
146
147     case ']':                           /* illegal closing bracket */
148       snprintf(glob->errormsg, sizeof(glob->errormsg),
149                "illegal pattern at pos %zu\n", pos);
150       return GLOB_ERROR;
151
152     case '\\':                          /* escaped character, skip '\' */
153       if(pattern[1]) {
154         ++pattern;
155         ++pos;
156       }
157       /* intentional fallthrough */
158     default:
159       *buf++ = *pattern++;              /* copy character to set element */
160       ++pos;
161     }
162   }
163   return GLOB_OK;
164 }
165
166 static GlobCode glob_range(URLGlob *glob, char *pattern,
167                            size_t pos, int *amount)
168 {
169   /* processes a range expression with the point behind the opening '['
170      - char range: e.g. "a-z]", "B-Q]"
171      - num range: e.g. "0-9]", "17-2000]"
172      - num range with leading zeros: e.g. "001-999]"
173      expression is checked for well-formedness and collected until the next ']'
174   */
175   URLPattern *pat;
176   char *c;
177   char sep;
178   char sep2;
179   int step;
180   int rc;
181   GlobCode res;
182   int wordamount = 1;
183
184   pat = &glob->pattern[glob->size / 2];
185   /* patterns 0,1,2,... correspond to size=1,3,5,... */
186   ++glob->size;
187
188   if(ISALPHA(*pattern)) {
189     /* character range detected */
190     char min_c;
191     char max_c;
192
193     pat->type = UPTCharRange;
194
195     rc = sscanf(pattern, "%c-%c%c%d%c", &min_c, &max_c, &sep, &step, &sep2);
196
197     if((rc < 3) || (min_c >= max_c) || ((max_c - min_c) > ('z' - 'a'))) {
198       /* the pattern is not well-formed */
199       snprintf(glob->errormsg, sizeof(glob->errormsg),
200                "error: bad range specification after pos %zu\n", pos);
201       return GLOB_ERROR;
202     }
203
204     /* check the (first) separating character */
205     if((sep != ']') && (sep != ':')) {
206       snprintf(glob->errormsg, sizeof(glob->errormsg),
207                "error: unsupported character (%c) after range at pos %zu\n",
208                sep, pos);
209       return GLOB_ERROR;
210     }
211
212     /* if there was a ":[num]" thing, use that as step or else use 1 */
213     pat->content.CharRange.step =
214       ((sep == ':') && (rc == 5) && (sep2 == ']')) ? step : 1;
215
216     pat->content.CharRange.ptr_c = pat->content.CharRange.min_c = min_c;
217     pat->content.CharRange.max_c = max_c;
218   }
219   else if(ISDIGIT(*pattern)) {
220     /* numeric range detected */
221     int min_n;
222     int max_n;
223
224     pat->type = UPTNumRange;
225     pat->content.NumRange.padlength = 0;
226
227     rc = sscanf(pattern, "%d-%d%c%d%c", &min_n, &max_n, &sep, &step, &sep2);
228
229     if((rc < 2) || (min_n > max_n)) {
230       /* the pattern is not well-formed */
231       snprintf(glob->errormsg, sizeof(glob->errormsg),
232                "error: bad range specification after pos %zu\n", pos);
233       return GLOB_ERROR;
234     }
235     pat->content.NumRange.ptr_n = pat->content.NumRange.min_n = min_n;
236     pat->content.NumRange.max_n = max_n;
237
238     /* if there was a ":[num]" thing, use that as step or else use 1 */
239     pat->content.NumRange.step =
240       ((sep == ':') && (rc == 5) && (sep2 == ']')) ? step : 1;
241
242     if(*pattern == '0') {
243       /* leading zero specified */
244       c = pattern;
245       while(ISDIGIT(*c)) {
246         c++;
247         ++pat->content.NumRange.padlength; /* padding length is set for all
248                                               instances of this pattern */
249       }
250     }
251   }
252   else {
253     snprintf(glob->errormsg, sizeof(glob->errormsg),
254              "illegal character in range specification at pos %zu\n", pos);
255     return GLOB_ERROR;
256   }
257
258   c = (char*)strchr(pattern, ']'); /* continue after next ']' */
259   if(c)
260     c++;
261   else {
262     snprintf(glob->errormsg, sizeof(glob->errormsg), "missing ']'");
263     return GLOB_ERROR; /* missing ']' */
264   }
265
266   /* always check for a literal (may be "") between patterns */
267
268   res = glob_word(glob, c, pos + (c - pattern), &wordamount);
269   if(res == GLOB_ERROR) {
270     wordamount = 1;
271     res = GLOB_OK;
272   }
273
274   if(!res) {
275     if(pat->type == UPTCharRange)
276       *amount = wordamount * (pat->content.CharRange.max_c -
277                               pat->content.CharRange.min_c + 1);
278     else
279       *amount = wordamount * (pat->content.NumRange.max_n -
280                               pat->content.NumRange.min_n + 1);
281   }
282
283   return res; /* GLOB_OK or GLOB_NO_MEM */
284 }
285
286 static GlobCode glob_word(URLGlob *glob, char *pattern,
287                           size_t pos, int *amount)
288 {
289   /* processes a literal string component of a URL
290      special characters '{' and '[' branch to set/range processing functions
291    */
292   char* buf = glob->glob_buffer;
293   size_t litindex;
294   GlobCode res = GLOB_OK;
295
296   *amount = 1; /* default is one single string */
297
298   while(*pattern != '\0' && *pattern != '{' && *pattern != '[') {
299     if(*pattern == '}' || *pattern == ']') {
300       snprintf(glob->errormsg, sizeof(glob->errormsg),
301                "unmatched close brace/bracket at pos %zu\n", pos);
302       return GLOB_ERROR;
303     }
304
305     /* only allow \ to escape known "special letters" */
306     if(*pattern == '\\' &&
307         (*(pattern+1) == '{' || *(pattern+1) == '[' ||
308          *(pattern+1) == '}' || *(pattern+1) == ']') ) {
309
310       /* escape character, skip '\' */
311       ++pattern;
312       ++pos;
313     }
314     *buf++ = *pattern++; /* copy character to literal */
315     ++pos;
316   }
317   *buf = '\0';
318   litindex = glob->size / 2;
319   /* literals 0,1,2,... correspond to size=0,2,4,... */
320   glob->literal[litindex] = strdup(glob->glob_buffer);
321   if(!glob->literal[litindex]) {
322     snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
323     return GLOB_NO_MEM;
324   }
325   ++glob->size;
326
327   switch (*pattern) {
328   case '\0':
329     /* singular URL processed  */
330     break;
331
332   case '{':
333     /* process set pattern */
334     res = glob_set(glob, ++pattern, ++pos, amount);
335     break;
336
337   case '[':
338     /* process range pattern */
339     res = glob_range(glob, ++pattern, ++pos, amount);
340     break;
341   }
342
343   if(res)
344     Curl_safefree(glob->literal[litindex]);
345
346   return res;
347 }
348
349 int glob_url(URLGlob** glob, char* url, int *urlnum, FILE *error)
350 {
351   /*
352    * We can deal with any-size, just make a buffer with the same length
353    * as the specified URL!
354    */
355   URLGlob *glob_expand;
356   int amount;
357   char *glob_buffer;
358   GlobCode res;
359
360   *glob = NULL;
361
362   glob_buffer = malloc(strlen(url) + 1);
363   if(!glob_buffer)
364     return CURLE_OUT_OF_MEMORY;
365
366   glob_expand = calloc(1, sizeof(URLGlob));
367   if(!glob_expand) {
368     Curl_safefree(glob_buffer);
369     return CURLE_OUT_OF_MEMORY;
370   }
371   glob_expand->size = 0;
372   glob_expand->urllen = strlen(url);
373   glob_expand->glob_buffer = glob_buffer;
374   glob_expand->beenhere = 0;
375
376   res = glob_word(glob_expand, url, 1, &amount);
377   if(!res)
378     *urlnum = amount;
379   else {
380     if(error && glob_expand->errormsg[0]) {
381       /* send error description to the error-stream */
382       fprintf(error, "curl: (%d) [globbing] %s",
383               (res == GLOB_NO_MEM) ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT,
384               glob_expand->errormsg);
385     }
386     /* it failed, we cleanup */
387     Curl_safefree(glob_buffer);
388     Curl_safefree(glob_expand);
389     *urlnum = 1;
390     return (res == GLOB_NO_MEM) ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT;
391   }
392
393   *glob = glob_expand;
394   return CURLE_OK;
395 }
396
397 void glob_cleanup(URLGlob* glob)
398 {
399   size_t i;
400   int elem;
401
402   for(i = glob->size - 1; i < glob->size; --i) {
403     if(!(i & 1)) {     /* even indexes contain literals */
404       Curl_safefree(glob->literal[i/2]);
405     }
406     else {              /* odd indexes contain sets or ranges */
407       if((glob->pattern[i/2].type == UPTSet) &&
408          (glob->pattern[i/2].content.Set.elements)) {
409         for(elem = glob->pattern[i/2].content.Set.size - 1;
410              elem >= 0;
411              --elem) {
412           Curl_safefree(glob->pattern[i/2].content.Set.elements[elem]);
413         }
414         Curl_safefree(glob->pattern[i/2].content.Set.elements);
415       }
416     }
417   }
418   Curl_safefree(glob->glob_buffer);
419   Curl_safefree(glob);
420 }
421
422 char *glob_next_url(URLGlob *glob)
423 {
424   URLPattern *pat;
425   char *lit;
426   size_t i;
427   size_t j;
428   size_t len;
429   size_t buflen = glob->urllen + 1;
430   char *buf = glob->glob_buffer;
431
432   if(!glob->beenhere)
433     glob->beenhere = 1;
434   else {
435     bool carry = TRUE;
436
437     /* implement a counter over the index ranges of all patterns,
438        starting with the rightmost pattern */
439     for(i = glob->size / 2 - 1; carry && (i < glob->size); --i) {
440       carry = FALSE;
441       pat = &glob->pattern[i];
442       switch (pat->type) {
443       case UPTSet:
444         if((pat->content.Set.elements) &&
445            (++pat->content.Set.ptr_s == pat->content.Set.size)) {
446           pat->content.Set.ptr_s = 0;
447           carry = TRUE;
448         }
449         break;
450       case UPTCharRange:
451         pat->content.CharRange.ptr_c = (char)(pat->content.CharRange.step +
452                            (int)((unsigned char)pat->content.CharRange.ptr_c));
453         if(pat->content.CharRange.ptr_c > pat->content.CharRange.max_c) {
454           pat->content.CharRange.ptr_c = pat->content.CharRange.min_c;
455           carry = TRUE;
456         }
457         break;
458       case UPTNumRange:
459         pat->content.NumRange.ptr_n += pat->content.NumRange.step;
460         if(pat->content.NumRange.ptr_n > pat->content.NumRange.max_n) {
461           pat->content.NumRange.ptr_n = pat->content.NumRange.min_n;
462           carry = TRUE;
463         }
464         break;
465       default:
466         printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
467         exit (CURLE_FAILED_INIT);
468       }
469     }
470     if(carry)          /* first pattern ptr has run into overflow, done! */
471       return NULL;
472   }
473
474   for(j = 0; j < glob->size; ++j) {
475     if(!(j&1)) {              /* every other term (j even) is a literal */
476       lit = glob->literal[j/2];
477       len = snprintf(buf, buflen, "%s", lit);
478       buf += len;
479       buflen -= len;
480     }
481     else {                              /* the rest (i odd) are patterns */
482       pat = &glob->pattern[j/2];
483       switch(pat->type) {
484       case UPTSet:
485         if(pat->content.Set.elements) {
486           len = strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
487           snprintf(buf, buflen, "%s",
488                    pat->content.Set.elements[pat->content.Set.ptr_s]);
489           buf += len;
490           buflen -= len;
491         }
492         break;
493       case UPTCharRange:
494         *buf++ = pat->content.CharRange.ptr_c;
495         break;
496       case UPTNumRange:
497         len = snprintf(buf, buflen, "%0*d",
498                        pat->content.NumRange.padlength,
499                        pat->content.NumRange.ptr_n);
500         buf += len;
501         buflen -= len;
502         break;
503       default:
504         printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
505         exit (CURLE_FAILED_INIT);
506       }
507     }
508   }
509   *buf = '\0';
510   return strdup(glob->glob_buffer);
511 }
512
513 char *glob_match_url(char *filename, URLGlob *glob)
514 {
515   char *target;
516   size_t allocsize;
517   char numbuf[18];
518   char *appendthis = NULL;
519   size_t appendlen = 0;
520   size_t stringlen = 0;
521
522   /* We cannot use the glob_buffer for storage here since the filename may
523    * be longer than the URL we use. We allocate a good start size, then
524    * we need to realloc in case of need.
525    */
526   allocsize = strlen(filename) + 1; /* make it at least one byte to store the
527                                        trailing zero */
528   target = malloc(allocsize);
529   if(!target)
530     return NULL; /* major failure */
531
532   while(*filename) {
533     if(*filename == '#' && ISDIGIT(filename[1])) {
534       unsigned long i;
535       char *ptr = filename;
536       unsigned long num = strtoul(&filename[1], &filename, 10);
537       i = num - 1UL;
538
539       if(num && (i <= glob->size / 2)) {
540         URLPattern pat = glob->pattern[i];
541         switch (pat.type) {
542         case UPTSet:
543           if(pat.content.Set.elements) {
544             appendthis = pat.content.Set.elements[pat.content.Set.ptr_s];
545             appendlen =
546               strlen(pat.content.Set.elements[pat.content.Set.ptr_s]);
547           }
548           break;
549         case UPTCharRange:
550           numbuf[0] = pat.content.CharRange.ptr_c;
551           numbuf[1] = 0;
552           appendthis = numbuf;
553           appendlen = 1;
554           break;
555         case UPTNumRange:
556           snprintf(numbuf, sizeof(numbuf), "%0*d",
557                    pat.content.NumRange.padlength,
558                    pat.content.NumRange.ptr_n);
559           appendthis = numbuf;
560           appendlen = strlen(numbuf);
561           break;
562         default:
563           printf("internal error: invalid pattern type (%d)\n",
564                  (int)pat.type);
565           Curl_safefree(target);
566           return NULL;
567         }
568       }
569       else {
570         /* #[num] out of range, use the #[num] in the output */
571         filename = ptr;
572         appendthis = filename++;
573         appendlen = 1;
574       }
575     }
576     else {
577       appendthis = filename++;
578       appendlen = 1;
579     }
580     if(appendlen + stringlen >= allocsize) {
581       char *newstr;
582       /* we append a single byte to allow for the trailing byte to be appended
583          at the end of this function outside the while() loop */
584       allocsize = (appendlen + stringlen) * 2;
585       newstr = realloc(target, allocsize + 1);
586       if(!newstr) {
587         Curl_safefree(target);
588         return NULL;
589       }
590       target = newstr;
591     }
592     memcpy(&target[stringlen], appendthis, appendlen);
593     stringlen += appendlen;
594   }
595   target[stringlen]= '\0';
596   return target;
597 }