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