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