Blob Blame History Raw
From 2188bcf999693fea405b692d84cef1c4a3fcbfa0 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Thu, 15 Aug 2013 13:05:25 +0200
Subject: [PATCH 01/13] urlglob: better detect unclosed braces, empty lists and
 overflows

A rather big overhaul and cleanup.

1 - curl wouldn't properly detect and reject globbing that ended with an
open brace if there were brackets or braces before it. Like "{}{" or
"[0-1]{"

2 - curl wouldn't properly reject empty lists so that "{}{}" would
result in curl getting (nil) strings in the output.

3 - By using strtoul() instead of sscanf() the code will now detected
over and underflows. It now also better parses the step argument to only
accept positive numbers and only step counters that is smaller than the
delta between the maximum and minimum numbers.

4 - By switching to unsigned longs instead of signed ints for the
counters, the max values for []-ranges are now very large (on 64bit
machines).

5 - Bumped the maximum number of globs in a single URL to 100 (from 10)

6 - Simplified the code somewhat and now it stores fixed strings as
single- entry lists. That's also one of the reasons why I did (5) as now
all strings between "globs" will take a slot in the array.

Added test 1234 and 1235 to verify. Updated test 87.

This commit fixes three separate bug reports.

Bug: http://curl.haxx.se/bug/view.cgi?id=1264
Bug: http://curl.haxx.se/bug/view.cgi?id=1265
Bug: http://curl.haxx.se/bug/view.cgi?id=1266
Reported-by: Will Dietz

[upstream commit 5ca96cb84410270e233c92bf1b2583cba40c3fad]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_operate.c     |  15 +-
 src/tool_urlglob.c     | 473 +++++++++++++++++++++++++------------------------
 src/tool_urlglob.h     |  25 +--
 tests/data/Makefile.am |   2 +-
 tests/data/test1234    |  32 ++++
 tests/data/test1235    |  94 ++++++++++
 tests/data/test87      |  36 +++-
 7 files changed, 420 insertions(+), 257 deletions(-)
 create mode 100644 tests/data/test1234
 create mode 100644 tests/data/test1235

diff --git a/src/tool_operate.c b/src/tool_operate.c
index ed60e70..dbbbc26 100644
--- a/src/tool_operate.c
+++ b/src/tool_operate.c
@@ -196,6 +196,7 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
   bool stillflags;
   int res = 0;
   int i;
+  unsigned long li;
 
   bool orig_noprogress;
   bool orig_isatty;
@@ -465,10 +466,10 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
 
   for(urlnode = config->url_list; urlnode; urlnode = urlnode->next) {
 
-    int up; /* upload file counter within a single upload glob */
+    unsigned long up; /* upload file counter within a single upload glob */
     char *infiles; /* might be a glob pattern */
     char *outfiles;
-    int infilenum;
+    unsigned long infilenum;
     URLGlob *inglob;
 
     int metalink = 0; /* nonzero for metalink download. */
@@ -533,7 +534,7 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
       char *uploadfile; /* a single file, never a glob */
       int separator;
       URLGlob *urls;
-      int urlnum;
+      unsigned long urlnum;
 
       uploadfile = NULL;
       urls = NULL;
@@ -583,7 +584,7 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
       separator= ((!outfiles || curlx_strequal(outfiles, "-")) && urlnum > 1);
 
       /* Here's looping around each globbed URL */
-      for(i = 0 ; i < urlnum; i++) {
+      for(li = 0 ; li < urlnum; li++) {
 
         int infd;
         bool infdopen;
@@ -628,7 +629,7 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
             if(res)
               goto show_error;
           }
-          else if(!i) {
+          else if(!li) {
             this_url = strdup(urlnode->url);
             if(!this_url) {
               res = CURLE_OUT_OF_MEMORY;
@@ -863,8 +864,8 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
         }
 
         if(urlnum > 1 && !(config->mute)) {
-          fprintf(config->errors, "\n[%d/%d]: %s --> %s\n",
-                  i+1, urlnum, this_url, outfile ? outfile : "<stdout>");
+          fprintf(config->errors, "\n[%lu/%lu]: %s --> %s\n",
+                  li+1, urlnum, this_url, outfile ? outfile : "<stdout>");
           if(separator)
             printf("%s%s\n", CURLseparator, this_url);
         }
diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 2821d00..0e454c1 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -35,39 +35,53 @@ typedef enum {
   GLOB_ERROR
 } GlobCode;
 
-/*
- * glob_word()
- *
- * Input a full globbed string, set the forth argument to the amount of
- * strings we get out of this. Return GlobCode.
- */
-static GlobCode glob_word(URLGlob *, /* object anchor */
-                          char *,    /* globbed string */
-                          size_t,       /* position */
-                          int *);    /* returned number of strings */
-
-static GlobCode glob_set(URLGlob *glob, char *pattern,
-                         size_t pos, int *amount)
+void glob_cleanup(URLGlob* glob);
+
+static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount)
+{
+  URLPattern *pat = &glob->pattern[glob->size];
+  pat->type = UPTSet;
+  pat->content.Set.size = 1;
+  pat->content.Set.ptr_s = 0;
+  pat->globindex = -1;
+
+  (*amount)++;
+
+  pat->content.Set.elements = malloc(sizeof(char*));
+
+  if(!pat->content.Set.elements) {
+    snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
+    return GLOB_NO_MEM;
+  }
+  pat->content.Set.elements[0] = strdup(glob->glob_buffer);
+  if(!pat->content.Set.elements[0]) {
+    snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
+    return GLOB_NO_MEM;
+  }
+
+  return GLOB_OK;
+}
+
+static GlobCode glob_set(URLGlob *glob, char **patternp,
+                         size_t pos, unsigned long *amount,
+                         int globindex)
 {
   /* processes a set expression with the point behind the opening '{'
      ','-separated elements are collected until the next closing '}'
   */
   URLPattern *pat;
-  GlobCode res;
   bool done = FALSE;
-  char* buf = glob->glob_buffer;
+  char *buf = glob->glob_buffer;
+  char *pattern = *patternp;
+  char *opattern = pattern;
 
-  pat = &glob->pattern[glob->size / 2];
+  pat = &glob->pattern[glob->size];
   /* patterns 0,1,2,... correspond to size=1,3,5,... */
   pat->type = UPTSet;
   pat->content.Set.size = 0;
   pat->content.Set.ptr_s = 0;
   pat->content.Set.elements = NULL;
-
-  if(++glob->size > (GLOB_PATTERN_NUM*2)) {
-    snprintf(glob->errormsg, sizeof(glob->errormsg), "too many globs used\n");
-    return GLOB_ERROR;
-  }
+  pat->globindex = globindex;
 
   while(!done) {
     switch (*pattern) {
@@ -82,60 +96,46 @@ static GlobCode glob_set(URLGlob *glob, char *pattern,
                "nested braces not supported at pos %zu\n", pos);
       return GLOB_ERROR;
 
-    case ',':
     case '}':                           /* set element completed */
+      if(opattern == pattern) {
+        snprintf(glob->errormsg, sizeof(glob->errormsg),
+                 "no string within braces at pos %zu\n", pos);
+        return GLOB_ERROR;
+      }
+      /* add 1 since it'll be incremented below */
+      (*amount) *= (pat->content.Set.size+1);
+      /* fall-through */
+    case ',':
+
       *buf = '\0';
       if(pat->content.Set.elements) {
         char **new_arr = realloc(pat->content.Set.elements,
                                  (pat->content.Set.size + 1) * sizeof(char*));
         if(!new_arr) {
-          short elem;
-          for(elem = 0; elem < pat->content.Set.size; elem++)
-            Curl_safefree(pat->content.Set.elements[elem]);
-          Curl_safefree(pat->content.Set.elements);
-          pat->content.Set.ptr_s = 0;
-          pat->content.Set.size = 0;
+          snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
+          return GLOB_NO_MEM;
         }
+
         pat->content.Set.elements = new_arr;
       }
       else
         pat->content.Set.elements = malloc(sizeof(char*));
+
       if(!pat->content.Set.elements) {
         snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
         return GLOB_NO_MEM;
       }
+
       pat->content.Set.elements[pat->content.Set.size] =
         strdup(glob->glob_buffer);
       if(!pat->content.Set.elements[pat->content.Set.size]) {
-        short elem;
-        for(elem = 0; elem < pat->content.Set.size; elem++)
-          Curl_safefree(pat->content.Set.elements[elem]);
-        Curl_safefree(pat->content.Set.elements);
-        pat->content.Set.ptr_s = 0;
-        pat->content.Set.size = 0;
         snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
         return GLOB_NO_MEM;
       }
       ++pat->content.Set.size;
 
       if(*pattern == '}') {
-        /* entire set pattern completed */
-        int wordamount;
-
-        /* always check for a literal (may be "") between patterns */
-        res = glob_word(glob, ++pattern, ++pos, &wordamount);
-        if(res) {
-          short elem;
-          for(elem = 0; elem < pat->content.Set.size; elem++)
-            Curl_safefree(pat->content.Set.elements[elem]);
-          Curl_safefree(pat->content.Set.elements);
-          pat->content.Set.ptr_s = 0;
-          pat->content.Set.size = 0;
-          return res;
-        }
-
-        *amount = pat->content.Set.size * wordamount;
-
+        pattern++; /* pass the closing brace */
         done = TRUE;
         continue;
       }
@@ -161,11 +161,14 @@ static GlobCode glob_set(URLGlob *glob, char *pattern,
       ++pos;
     }
   }
+
+  *patternp = pattern; /* return with the new position */
   return GLOB_OK;
 }
 
-static GlobCode glob_range(URLGlob *glob, char *pattern,
-                           size_t pos, int *amount)
+static GlobCode glob_range(URLGlob *glob, char **patternp,
+                           size_t pos, unsigned long *amount,
+                           int globindex)
 {
   /* processes a range expression with the point behind the opening '['
      - char range: e.g. "a-z]", "B-Q]"
@@ -174,84 +177,119 @@ static GlobCode glob_range(URLGlob *glob, char *pattern,
      expression is checked for well-formedness and collected until the next ']'
   */
   URLPattern *pat;
-  char *c;
-  char sep;
-  char sep2;
-  int step;
   int rc;
-  GlobCode res;
-  int wordamount = 1;
+  char *pattern = *patternp;
+  char *c;
 
-  pat = &glob->pattern[glob->size / 2];
-  /* patterns 0,1,2,... correspond to size=1,3,5,... */
-  if(++glob->size > (GLOB_PATTERN_NUM*2)) {
-    snprintf(glob->errormsg, sizeof(glob->errormsg), "too many globs used\n");
-    return GLOB_ERROR;
-  }
+  pat = &glob->pattern[glob->size];
+  pat->globindex = globindex;
 
   if(ISALPHA(*pattern)) {
     /* character range detected */
     char min_c;
     char max_c;
+    int step=1;
 
     pat->type = UPTCharRange;
 
-    rc = sscanf(pattern, "%c-%c%c%d%c", &min_c, &max_c, &sep, &step, &sep2);
+    rc = sscanf(pattern, "%c-%c", &min_c, &max_c);
 
-    if((rc < 3) || (min_c >= max_c) || ((max_c - min_c) > ('z' - 'a'))) {
-      /* the pattern is not well-formed */
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "error: bad range specification after pos %zu\n", pos);
-      return GLOB_ERROR;
+    if((rc == 2) && (pattern[3] == ':')) {
+      char *endp;
+      unsigned long lstep;
+      errno = 0;
+      lstep = strtoul(&pattern[3], &endp, 10);
+      if(errno || (*endp != ']'))
+        step = -1;
+      else {
+        pattern = endp+1;
+        step = (int)lstep;
+        if(step > (max_c - min_c))
+          step = -1;
+      }
     }
+    else
+      pattern+=3;
 
-    /* check the (first) separating character */
-    if((sep != ']') && (sep != ':')) {
+    if((rc != 2) || (min_c >= max_c) || ((max_c - min_c) > ('z' - 'a')) ||
+       (step < 0) ) {
+      /* the pattern is not well-formed */
       snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "error: unsupported character (%c) after range at pos %zu\n",
-               sep, pos);
+               "error: bad range specification after pos %zu\n", pos);
       return GLOB_ERROR;
     }
 
     /* if there was a ":[num]" thing, use that as step or else use 1 */
-    pat->content.CharRange.step =
-      ((sep == ':') && (rc == 5) && (sep2 == ']')) ? step : 1;
-
+    pat->content.CharRange.step = step;
     pat->content.CharRange.ptr_c = pat->content.CharRange.min_c = min_c;
     pat->content.CharRange.max_c = max_c;
+
+    *amount *= (pat->content.CharRange.max_c -
+                pat->content.CharRange.min_c + 1);
   }
   else if(ISDIGIT(*pattern)) {
     /* numeric range detected */
-    int min_n;
-    int max_n;
+    unsigned long min_n;
+    unsigned long max_n;
+    unsigned long step_n;
+    char *endp;
 
     pat->type = UPTNumRange;
     pat->content.NumRange.padlength = 0;
 
-    rc = sscanf(pattern, "%d-%d%c%d%c", &min_n, &max_n, &sep, &step, &sep2);
+    if(*pattern == '0') {
+      /* leading zero specified, count them! */
+      c = pattern;
+      while(ISDIGIT(*c)) {
+        c++;
+        ++pat->content.NumRange.padlength; /* padding length is set for all
+                                              instances of this pattern */
+      }
+    }
 
-    if((rc < 2) || (min_n > max_n)) {
+    errno = 0;
+    min_n = strtoul(pattern, &endp, 10);
+    if(errno || (endp == pattern))
+      endp=NULL;
+    else {
+      if(*endp != '-')
+        endp = NULL;
+      else {
+        pattern = endp+1;
+        errno = 0;
+        max_n = strtoul(pattern, &endp, 10);
+        if(errno || (*endp == ':')) {
+          pattern = endp+1;
+          errno = 0;
+          step_n = strtoul(pattern, &endp, 10);
+          if(errno)
+            /* over/underflow situation */
+            endp = NULL;
+        }
+        else
+          step_n = 1;
+        if(*endp == ']') {
+          pattern= endp+1;
+        }
+        else
+          endp = NULL;
+      }
+    }
+
+    if(!endp || (min_n > max_n) || (step_n > (max_n - min_n))) {
       /* the pattern is not well-formed */
       snprintf(glob->errormsg, sizeof(glob->errormsg),
                "error: bad range specification after pos %zu\n", pos);
       return GLOB_ERROR;
     }
+    /* typecasting to ints are fine here since we make sure above that we
+       are within 31 bits */
     pat->content.NumRange.ptr_n = pat->content.NumRange.min_n = min_n;
     pat->content.NumRange.max_n = max_n;
+    pat->content.NumRange.step = step_n;
 
-    /* if there was a ":[num]" thing, use that as step or else use 1 */
-    pat->content.NumRange.step =
-      ((sep == ':') && (rc == 5) && (sep2 == ']')) ? step : 1;
-
-    if(*pattern == '0') {
-      /* leading zero specified */
-      c = pattern;
-      while(ISDIGIT(*c)) {
-        c++;
-        ++pat->content.NumRange.padlength; /* padding length is set for all
-                                              instances of this pattern */
-      }
-    }
+    *amount *= (pat->content.NumRange.max_n -
+                pat->content.NumRange.min_n + 1);
   }
   else {
     snprintf(glob->errormsg, sizeof(glob->errormsg),
@@ -259,105 +297,87 @@ static GlobCode glob_range(URLGlob *glob, char *pattern,
     return GLOB_ERROR;
   }
 
-  c = (char*)strchr(pattern, ']'); /* continue after next ']' */
-  if(c)
-    c++;
-  else {
-    snprintf(glob->errormsg, sizeof(glob->errormsg), "missing ']'");
-    return GLOB_ERROR; /* missing ']' */
-  }
-
-  /* always check for a literal (may be "") between patterns */
-
-  res = glob_word(glob, c, pos + (c - pattern), &wordamount);
-  if(res == GLOB_ERROR) {
-    wordamount = 1;
-    res = GLOB_OK;
-  }
-
-  if(!res) {
-    if(pat->type == UPTCharRange)
-      *amount = wordamount * (pat->content.CharRange.max_c -
-                              pat->content.CharRange.min_c + 1);
-    else
-      *amount = wordamount * (pat->content.NumRange.max_n -
-                              pat->content.NumRange.min_n + 1);
-  }
-
-  return res; /* GLOB_OK or GLOB_NO_MEM */
+  *patternp = pattern;
+  return GLOB_OK;
 }
 
-static GlobCode glob_word(URLGlob *glob, char *pattern,
-                          size_t pos, int *amount)
+static GlobCode glob_parse(URLGlob *glob, char *pattern,
+                           size_t pos, unsigned long *amount)
 {
   /* processes a literal string component of a URL
      special characters '{' and '[' branch to set/range processing functions
    */
   char* buf = glob->glob_buffer;
-  size_t litindex;
   GlobCode res = GLOB_OK;
+  int globindex = 0; /* count "actual" globs */
+
+  while(*pattern && !res) {
+    int sublen = 0;
+    while(*pattern && *pattern != '{' && *pattern != '[') {
+      if(*pattern == '}' || *pattern == ']') {
+        snprintf(glob->errormsg, sizeof(glob->errormsg),
+                 "unmatched close brace/bracket at pos %zu\n", pos);
+        return GLOB_ERROR;
+      }
 
-  *amount = 1; /* default is one single string */
+      /* only allow \ to escape known "special letters" */
+      if(*pattern == '\\' &&
+         (*(pattern+1) == '{' || *(pattern+1) == '[' ||
+          *(pattern+1) == '}' || *(pattern+1) == ']') ) {
 
-  while(*pattern != '\0' && *pattern != '{' && *pattern != '[') {
-    if(*pattern == '}' || *pattern == ']') {
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "unmatched close brace/bracket at pos %zu\n", pos);
-      return GLOB_ERROR;
+        /* escape character, skip '\' */
+        ++pattern;
+        ++pos;
+      }
+      *buf++ = *pattern++; /* copy character to literal */
+      ++pos;
+      sublen++;
+    }
+    if(sublen) {
+      /* we got a literal string, add it as a single-item list */
+      *buf = '\0';
+      res = glob_fixed(glob, amount);
     }
+    else {
+      if(!*amount)
+        *amount = 1;
 
-    /* only allow \ to escape known "special letters" */
-    if(*pattern == '\\' &&
-        (*(pattern+1) == '{' || *(pattern+1) == '[' ||
-         *(pattern+1) == '}' || *(pattern+1) == ']') ) {
+      switch (*pattern) {
+      case '\0': /* done  */
+        break;
 
-      /* escape character, skip '\' */
-      ++pattern;
-      ++pos;
+      case '{':
+        /* process set pattern */
+        pattern++;
+        res = glob_set(glob, &pattern, ++pos, amount, globindex++);
+        break;
+
+      case '[':
+        /* process range pattern */
+        pattern++;
+        res = glob_range(glob, &pattern, ++pos, amount, globindex++);
+        break;
+      }
     }
-    *buf++ = *pattern++; /* copy character to literal */
-    ++pos;
-  }
-  *buf = '\0';
-  litindex = glob->size / 2;
-  /* literals 0,1,2,... correspond to size=0,2,4,... */
-  glob->literal[litindex] = strdup(glob->glob_buffer);
-  if(!glob->literal[litindex]) {
-    snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
-    return GLOB_NO_MEM;
-  }
-  ++glob->size;
-
-  switch (*pattern) {
-  case '\0':
-    /* singular URL processed  */
-    break;
-
-  case '{':
-    /* process set pattern */
-    res = glob_set(glob, ++pattern, ++pos, amount);
-    break;
-
-  case '[':
-    /* process range pattern */
-    res = glob_range(glob, ++pattern, ++pos, amount);
-    break;
-  }
 
-  if(res)
-    Curl_safefree(glob->literal[litindex]);
+    if(++glob->size > GLOB_PATTERN_NUM) {
+      snprintf(glob->errormsg, sizeof(glob->errormsg),
+               "too many globs used\n");
+      return GLOB_ERROR;
+    }
 
+  }
   return res;
 }
 
-int glob_url(URLGlob** glob, char* url, int *urlnum, FILE *error)
+int glob_url(URLGlob** glob, char* url, unsigned long *urlnum, FILE *error)
 {
   /*
    * We can deal with any-size, just make a buffer with the same length
    * as the specified URL!
    */
   URLGlob *glob_expand;
-  int amount;
+  unsigned long amount = 0;
   char *glob_buffer;
   GlobCode res;
 
@@ -372,12 +392,10 @@ int glob_url(URLGlob** glob, char* url, int *urlnum, FILE *error)
     Curl_safefree(glob_buffer);
     return CURLE_OUT_OF_MEMORY;
   }
-  glob_expand->size = 0;
   glob_expand->urllen = strlen(url);
   glob_expand->glob_buffer = glob_buffer;
-  glob_expand->beenhere = 0;
 
-  res = glob_word(glob_expand, url, 1, &amount);
+  res = glob_parse(glob_expand, url, 1, &amount);
   if(!res)
     *urlnum = amount;
   else {
@@ -388,8 +406,7 @@ int glob_url(URLGlob** glob, char* url, int *urlnum, FILE *error)
               glob_expand->errormsg);
     }
     /* it failed, we cleanup */
-    Curl_safefree(glob_buffer);
-    Curl_safefree(glob_expand);
+    glob_cleanup(glob_expand);
     *urlnum = 1;
     return (res == GLOB_NO_MEM) ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT;
   }
@@ -404,19 +421,14 @@ void glob_cleanup(URLGlob* glob)
   int elem;
 
   for(i = glob->size - 1; i < glob->size; --i) {
-    if(!(i & 1)) {     /* even indexes contain literals */
-      Curl_safefree(glob->literal[i/2]);
-    }
-    else {              /* odd indexes contain sets or ranges */
-      if((glob->pattern[i/2].type == UPTSet) &&
-         (glob->pattern[i/2].content.Set.elements)) {
-        for(elem = glob->pattern[i/2].content.Set.size - 1;
-             elem >= 0;
-             --elem) {
-          Curl_safefree(glob->pattern[i/2].content.Set.elements[elem]);
-        }
-        Curl_safefree(glob->pattern[i/2].content.Set.elements);
+    if((glob->pattern[i].type == UPTSet) &&
+       (glob->pattern[i].content.Set.elements)) {
+      for(elem = glob->pattern[i].content.Set.size - 1;
+          elem >= 0;
+          --elem) {
+        Curl_safefree(glob->pattern[i].content.Set.elements[elem]);
       }
+      Curl_safefree(glob->pattern[i].content.Set.elements);
     }
   }
   Curl_safefree(glob->glob_buffer);
@@ -426,7 +438,6 @@ void glob_cleanup(URLGlob* glob)
 int glob_next_url(char **globbed, URLGlob *glob)
 {
   URLPattern *pat;
-  char *lit;
   size_t i;
   size_t j;
   size_t len;
@@ -442,7 +453,7 @@ int glob_next_url(char **globbed, URLGlob *glob)
 
     /* implement a counter over the index ranges of all patterns,
        starting with the rightmost pattern */
-    for(i = glob->size / 2 - 1; carry && (i < glob->size); --i) {
+    for(i = glob->size - 1; carry && (i < glob->size); --i) {
       carry = FALSE;
       pat = &glob->pattern[i];
       switch (pat->type) {
@@ -480,38 +491,30 @@ int glob_next_url(char **globbed, URLGlob *glob)
   }
 
   for(j = 0; j < glob->size; ++j) {
-    if(!(j&1)) {              /* every other term (j even) is a literal */
-      lit = glob->literal[j/2];
-      len = snprintf(buf, buflen, "%s", lit);
-      buf += len;
-      buflen -= len;
-    }
-    else {                              /* the rest (i odd) are patterns */
-      pat = &glob->pattern[j/2];
-      switch(pat->type) {
-      case UPTSet:
-        if(pat->content.Set.elements) {
-          len = strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
-          snprintf(buf, buflen, "%s",
-                   pat->content.Set.elements[pat->content.Set.ptr_s]);
-          buf += len;
-          buflen -= len;
-        }
-        break;
-      case UPTCharRange:
-        *buf++ = pat->content.CharRange.ptr_c;
-        break;
-      case UPTNumRange:
-        len = snprintf(buf, buflen, "%0*d",
-                       pat->content.NumRange.padlength,
-                       pat->content.NumRange.ptr_n);
+    pat = &glob->pattern[j];
+    switch(pat->type) {
+    case UPTSet:
+      if(pat->content.Set.elements) {
+        len = strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
+        snprintf(buf, buflen, "%s",
+                 pat->content.Set.elements[pat->content.Set.ptr_s]);
         buf += len;
         buflen -= len;
-        break;
-      default:
-        printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
-        return CURLE_FAILED_INIT;
       }
+      break;
+    case UPTCharRange:
+      *buf++ = pat->content.CharRange.ptr_c;
+      break;
+    case UPTNumRange:
+      len = snprintf(buf, buflen, "%0*ld",
+                     pat->content.NumRange.padlength,
+                     pat->content.NumRange.ptr_n);
+      buf += len;
+      buflen -= len;
+      break;
+    default:
+      printf("internal error: invalid pattern type (%d)\n", (int)pat->type);
+      return CURLE_FAILED_INIT;
     }
   }
   *buf = '\0';
@@ -549,34 +552,44 @@ int glob_match_url(char **result, char *filename, URLGlob *glob)
       unsigned long i;
       char *ptr = filename;
       unsigned long num = strtoul(&filename[1], &filename, 10);
-      i = num - 1UL;
+      URLPattern *pat =NULL;
+
+      if(num < glob->size) {
+        num--; /* make it zero based */
+        /* find the correct glob entry */
+        for(i=0; i<glob->size; i++) {
+          if(glob->pattern[i].globindex == (int)num) {
+            pat = &glob->pattern[i];
+            break;
+          }
+        }
+      }
 
-      if(num && (i <= glob->size / 2)) {
-        URLPattern pat = glob->pattern[i];
-        switch (pat.type) {
+      if(pat) {
+        switch (pat->type) {
         case UPTSet:
-          if(pat.content.Set.elements) {
-            appendthis = pat.content.Set.elements[pat.content.Set.ptr_s];
+          if(pat->content.Set.elements) {
+            appendthis = pat->content.Set.elements[pat->content.Set.ptr_s];
             appendlen =
-              strlen(pat.content.Set.elements[pat.content.Set.ptr_s]);
+              strlen(pat->content.Set.elements[pat->content.Set.ptr_s]);
           }
           break;
         case UPTCharRange:
-          numbuf[0] = pat.content.CharRange.ptr_c;
+          numbuf[0] = pat->content.CharRange.ptr_c;
           numbuf[1] = 0;
           appendthis = numbuf;
           appendlen = 1;
           break;
         case UPTNumRange:
           snprintf(numbuf, sizeof(numbuf), "%0*d",
-                   pat.content.NumRange.padlength,
-                   pat.content.NumRange.ptr_n);
+                   pat->content.NumRange.padlength,
+                   pat->content.NumRange.ptr_n);
           appendthis = numbuf;
           appendlen = strlen(numbuf);
           break;
         default:
-          printf("internal error: invalid pattern type (%d)\n",
-                 (int)pat.type);
+          fprintf(stderr, "internal error: invalid pattern type (%d)\n",
+                  (int)pat->type);
           Curl_safefree(target);
           return CURLE_FAILED_INIT;
         }
diff --git a/src/tool_urlglob.h b/src/tool_urlglob.h
index 9c08137..e1e9c63 100644
--- a/src/tool_urlglob.h
+++ b/src/tool_urlglob.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -31,11 +31,13 @@ typedef enum {
 
 typedef struct {
   URLPatternType type;
+  int globindex; /* the number of this particular glob or -1 if not used
+                    within {} or [] */
   union {
     struct {
       char **elements;
-      short size;
-      short ptr_s;
+      int size;
+      int ptr_s;
     } Set;
     struct {
       char min_c;
@@ -44,21 +46,20 @@ typedef struct {
       int step;
     } CharRange;
     struct {
-      int min_n;
-      int max_n;
-      short padlength;
-      int ptr_n;
-      int step;
+      unsigned long min_n;
+      unsigned long max_n;
+      int padlength;
+      unsigned long ptr_n;
+      unsigned long step;
     } NumRange ;
   } content;
 } URLPattern;
 
 /* the total number of globs supported */
-#define GLOB_PATTERN_NUM 9
+#define GLOB_PATTERN_NUM 100
 
 typedef struct {
-  char *literal[10];
-  URLPattern pattern[GLOB_PATTERN_NUM+1];
+  URLPattern pattern[GLOB_PATTERN_NUM];
   size_t size;
   size_t urllen;
   char *glob_buffer;
@@ -66,7 +67,7 @@ typedef struct {
   char errormsg[80]; /* error message buffer */
 } URLGlob;
 
-int glob_url(URLGlob**, char*, int *, FILE *);
+int glob_url(URLGlob**, char*, unsigned long *, FILE *);
 int glob_next_url(char **, URLGlob *);
 int glob_match_url(char **, char*, URLGlob *);
 void glob_cleanup(URLGlob* glob);
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 0d5c29d..f661b71 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -93,7 +93,7 @@ test1200 test1201 test1202 test1203 test1204 test1205 test1206 test1207 \
 test1208 test1209 test1210 test1211 test1212 test1213 test1214 test1215 \
 test1216 test1217 test1218 test1219 \
 test1220 test1221 test1222 test1223 test1224 test1225 test1226 test1227 \
-test1228 test1229 test1230 test1231 test1232 test1233 \
+test1228 test1229 test1230 test1231 test1232 test1233 test1234 test1235 \
 \
 test1300 test1301 test1302 test1303 test1304 test1305 test1306 test1307 \
 test1308 test1309 test1310 test1311 test1312 test1313 test1314 test1315 \
diff --git a/tests/data/test1234 b/tests/data/test1234
new file mode 100644
index 0000000..9d7a79f
--- /dev/null
+++ b/tests/data/test1234
@@ -0,0 +1,32 @@
+<testcase>
+<info>
+<keywords>
+{} list
+FAILURE
+</keywords>
+</info>
+# Server-side
+<reply>
+</reply>
+
+# Client-side
+<client>
+<server>
+none
+</server>
+ <name>
+abusing {}-globbing
+ </name>
+ <command>
+"%HOSTIP:%HTTPPORT/1234[0-1]{" "%HOSTIP:%HTTPPORT/{}{}{}{"
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+# 3 == CURLE_URL_MALFORMAT
+<errorcode>
+3
+</errorcode>
+</verify>
+</testcase>
diff --git a/tests/data/test1235 b/tests/data/test1235
new file mode 100644
index 0000000..6c2a6a9
--- /dev/null
+++ b/tests/data/test1235
@@ -0,0 +1,94 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+{} list
+</keywords>
+</info>
+# Server-side
+<reply>
+<data1>
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 15
+
+the number one
+</data1>
+<data2>
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 16
+
+two is nice too
+</data2>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+multiple requests using {}{} in the URL
+ </name>
+ <command>
+"%HOSTIP:%HTTPPORT/{1235,1235}{0001,0002}"
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /12350001 HTTP/1.1
+User-Agent: curl/7.8.1-pre3 (sparc-sun-solaris2.7) libcurl 7.8.1-pre3 (OpenSSL 0.9.6a) (krb4 enabled)
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+GET /12350002 HTTP/1.1
+User-Agent: curl/7.8.1-pre3 (sparc-sun-solaris2.7) libcurl 7.8.1-pre3 (OpenSSL 0.9.6a) (krb4 enabled)
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+GET /12350001 HTTP/1.1
+User-Agent: curl/7.8.1-pre3 (sparc-sun-solaris2.7) libcurl 7.8.1-pre3 (OpenSSL 0.9.6a) (krb4 enabled)
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+GET /12350002 HTTP/1.1
+User-Agent: curl/7.8.1-pre3 (sparc-sun-solaris2.7) libcurl 7.8.1-pre3 (OpenSSL 0.9.6a) (krb4 enabled)
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+</protocol>
+<stdout>
+--_curl_--%HOSTIP:%HTTPPORT/12350001
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 15
+
+the number one
+--_curl_--%HOSTIP:%HTTPPORT/12350002
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 16
+
+two is nice too
+--_curl_--%HOSTIP:%HTTPPORT/12350001
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 15
+
+the number one
+--_curl_--%HOSTIP:%HTTPPORT/12350002
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 16
+
+two is nice too
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test87 b/tests/data/test87
index 40b274b..4e43679 100644
--- a/tests/data/test87
+++ b/tests/data/test87
@@ -8,29 +8,51 @@ FAILURE
 #
 # Server-side
 <reply>
+<data1>
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 15
+
+the number one
+</data1>
+<data2>
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 16
+
+two is nice too
+</data2>
+
 </reply>
 #
 # Client-side
 <client>
 <server>
-none
+http
 </server>
 <features>
 http
 </features>
  <name>
-urlglob with bad -o #[num] usage
+urlglob with out of range -o #[num] usage
  </name>
  <command option="no-output">
-"http://%HOSTIP:%HTTPPORT/[870001-870003]" -o "log/dumpit#2.dump"
+"http://%HOSTIP:%HTTPPORT/[870001-870002]" -o "log/dumpit#2.dump"
 </command>
 </client>
 
 #
-# Verify data after the test has been "shot"
+# Verify data after the test has been "shot". Note that the command line
+# will write both responses into the same file name so only the second
+# survives
+#
 <verify>
-<errorcode>
-2
-</errorcode>
+<file name="log/dumpit#2.dump" [mode="text"]>
+HTTP/1.1 200 OK
+Funny-head: yesyes
+Content-Length: 16
+
+two is nice too
+</file>
 </verify>
 </testcase>
-- 
1.8.3.1


From 9eefa66b8e003814102daf1b4f1f8cbc4b1c5207 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Fri, 16 Aug 2013 11:52:59 +0200
Subject: [PATCH 02/13] glob: error out on range overflow

The new multiply() function detects range value overflows. 32bit
machines will overflow on a 32bit boundary while 64bit hosts support
ranges up to the full 64 bit range.

Added test 1236 to verify.

Bug: http://curl.haxx.se/bug/view.cgi?id=1267
Reported-by: Will Dietz

[upstream commit f15a88f2b25ee44d8c8d3bdcf2508fdf50f8b868]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c     | 34 ++++++++++++++++++++++++++++------
 tests/data/Makefile.am |  1 +
 tests/data/test1236    | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+), 6 deletions(-)
 create mode 100644 tests/data/test1236

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 0e454c1..ac13d68 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -62,6 +62,19 @@ static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount)
   return GLOB_OK;
 }
 
+/* multiply
+ *
+ * Multiplies and checks for overflow.
+ */
+static int multiply(unsigned long *amount, long with)
+{
+  unsigned long sum = *amount * with;
+  if(sum/with != *amount)
+    return 1; /* didn't fit, bail out */
+  *amount = sum;
+  return 0;
+}
+
 static GlobCode glob_set(URLGlob *glob, char **patternp,
                          size_t pos, unsigned long *amount,
                          int globindex)
@@ -102,8 +115,11 @@ static GlobCode glob_set(URLGlob *glob, char **patternp,
                  "no string within braces at pos %zu\n", pos);
         return GLOB_ERROR;
       }
-      /* add 1 since it'll be incremented below */
-      (*amount) *= (pat->content.Set.size+1);
+      /* add 1 to size since it'll be incremented below */
+      if(multiply(amount, pat->content.Set.size+1)) {
+        strcpy(glob->errormsg, "range overflow!\n");
+        return GLOB_ERROR;
+      }
       /* fall-through */
     case ',':
 
@@ -224,8 +240,11 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
     pat->content.CharRange.ptr_c = pat->content.CharRange.min_c = min_c;
     pat->content.CharRange.max_c = max_c;
 
-    *amount *= (pat->content.CharRange.max_c -
-                pat->content.CharRange.min_c + 1);
+    if(multiply(amount, (pat->content.CharRange.max_c -
+                         pat->content.CharRange.min_c + 1))) {
+      strcpy(glob->errormsg, "range overflow!\n");
+      return GLOB_ERROR;
+    }
   }
   else if(ISDIGIT(*pattern)) {
     /* numeric range detected */
@@ -288,8 +307,11 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
     pat->content.NumRange.max_n = max_n;
     pat->content.NumRange.step = step_n;
 
-    *amount *= (pat->content.NumRange.max_n -
-                pat->content.NumRange.min_n + 1);
+    if(multiply(amount, (pat->content.NumRange.max_n -
+                         pat->content.NumRange.min_n + 1))) {
+      strcpy(glob->errormsg, "range overflow!\n");
+      return GLOB_ERROR;
+    }
   }
   else {
     snprintf(glob->errormsg, sizeof(glob->errormsg),
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index f661b71..dd7f6f9 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -94,6 +94,7 @@ test1208 test1209 test1210 test1211 test1212 test1213 test1214 test1215 \
 test1216 test1217 test1218 test1219 \
 test1220 test1221 test1222 test1223 test1224 test1225 test1226 test1227 \
 test1228 test1229 test1230 test1231 test1232 test1233 test1234 test1235 \
+test1236 \
 \
 test1300 test1301 test1302 test1303 test1304 test1305 test1306 test1307 \
 test1308 test1309 test1310 test1311 test1312 test1313 test1314 test1315 \
diff --git a/tests/data/test1236 b/tests/data/test1236
new file mode 100644
index 0000000..0829be3
--- /dev/null
+++ b/tests/data/test1236
@@ -0,0 +1,33 @@
+<testcase>
+<info>
+<keywords>
+globbing
+FAILURE
+</keywords>
+</info>
+# Server-side
+<reply>
+</reply>
+
+# Client-side
+<client>
+<server>
+none
+</server>
+ <name>
+[] globbing overflowing the range counter
+ </name>
+# 2^62 == 4611686018427387904
+ <command>
+"%HOSTIP:%HTTPPORT/1234[0-1]{" "%HOSTIP:%HTTPPORT/[1-4611686018427387904][1-4611686018427387904]"
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+# 3 == CURLE_URL_MALFORMAT
+<errorcode>
+3
+</errorcode>
+</verify>
+</testcase>
-- 
1.8.3.1


From 6e7c5ae88cc75c2f00d8cc837fd1b74ee7a845ca Mon Sep 17 00:00:00 2001
From: Steve Holme <steve_holme@hotmail.com>
Date: Mon, 26 Aug 2013 11:41:35 +0100
Subject: [PATCH 03/13] tool_urlglob.c: Fixed compiler warnings

warning: 'variable' may be used uninitialized in this function

[upstream commit 84789e12fb1d6d22532bd2ce7bfae3a160648a60]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index ac13d68..1644077 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -249,8 +249,8 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
   else if(ISDIGIT(*pattern)) {
     /* numeric range detected */
     unsigned long min_n;
-    unsigned long max_n;
-    unsigned long step_n;
+    unsigned long max_n = 0;
+    unsigned long step_n = 0;
     char *endp;
 
     pat->type = UPTNumRange;
-- 
1.8.3.1


From c05a515575dbbefee2eba21fa1c0484fbd90279c Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Fri, 6 Sep 2013 14:12:44 +0200
Subject: [PATCH 04/13] urlglob: avoid NULL pointer dereference

Thanks to clang-analyzer

[upstream commit 2a7f1425d98919b362fbe979a6428fa9aeebcd78]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 1644077..7c7bd4b 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -287,7 +287,7 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
         }
         else
           step_n = 1;
-        if(*endp == ']') {
+        if(endp && (*endp == ']')) {
           pattern= endp+1;
         }
         else
-- 
1.8.3.1


From 2e9c57512191dd504902029201bee891fcd83000 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Fri, 6 Sep 2013 14:20:03 +0200
Subject: [PATCH 05/13] urlglob: avoid error code translation

By using the correct values from the start we don't have to translate
them!

[upstream commit d6cda9e8ababe633f4b0b58776b1a9c4534e8095]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 7c7bd4b..647bbc5 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -31,8 +31,8 @@
 
 typedef enum {
   GLOB_OK,
-  GLOB_NO_MEM,
-  GLOB_ERROR
+  GLOB_NO_MEM = CURLE_OUT_OF_MEMORY,
+  GLOB_ERROR = CURLE_URL_MALFORMAT
 } GlobCode;
 
 void glob_cleanup(URLGlob* glob);
@@ -423,14 +423,12 @@ int glob_url(URLGlob** glob, char* url, unsigned long *urlnum, FILE *error)
   else {
     if(error && glob_expand->errormsg[0]) {
       /* send error description to the error-stream */
-      fprintf(error, "curl: (%d) [globbing] %s",
-              (res == GLOB_NO_MEM) ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT,
-              glob_expand->errormsg);
+      fprintf(error, "curl: (%d) [globbing] %s", res, glob_expand->errormsg);
     }
     /* it failed, we cleanup */
     glob_cleanup(glob_expand);
     *urlnum = 1;
-    return (res == GLOB_NO_MEM) ? CURLE_OUT_OF_MEMORY : CURLE_URL_MALFORMAT;
+    return res;
   }
 
   *glob = glob_expand;
-- 
1.8.3.1


From a987dc7fa4052dc2de17ad0ef71ac67825ad67ed Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Fri, 6 Sep 2013 23:27:47 +0200
Subject: [PATCH 06/13] urlglob: improved error messages and column number on
 bad use

Introduce a convenience macro and keep of the column better so that it
can point out the offending column better.

Updated test 75 accordingly.

[upstream commit 9fa42beddc5e1f469ddf276a0715f2de82f51b6b]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 145 +++++++++++++++++++++++------------------------------
 src/tool_urlglob.h |   3 +-
 tests/data/test75  |   4 +-
 3 files changed, 68 insertions(+), 84 deletions(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 647bbc5..5f94519 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -35,6 +35,9 @@ typedef enum {
   GLOB_ERROR = CURLE_URL_MALFORMAT
 } GlobCode;
 
+#define GLOBERROR(string, column, code) \
+  glob->error = string, glob->pos = column, code;
+
 void glob_cleanup(URLGlob* glob);
 
 static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount)
@@ -49,15 +52,12 @@ static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount)
 
   pat->content.Set.elements = malloc(sizeof(char*));
 
-  if(!pat->content.Set.elements) {
-    snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
-    return GLOB_NO_MEM;
-  }
+  if(!pat->content.Set.elements)
+    return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
+
   pat->content.Set.elements[0] = strdup(glob->glob_buffer);
-  if(!pat->content.Set.elements[0]) {
-    snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
-    return GLOB_NO_MEM;
-  }
+  if(!pat->content.Set.elements[0])
+    return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
 
   return GLOB_OK;
 }
@@ -76,7 +76,7 @@ static int multiply(unsigned long *amount, long with)
 }
 
 static GlobCode glob_set(URLGlob *glob, char **patternp,
-                         size_t pos, unsigned long *amount,
+                         size_t *posp, unsigned long *amount,
                          int globindex)
 {
   /* processes a set expression with the point behind the opening '{'
@@ -87,6 +87,7 @@ static GlobCode glob_set(URLGlob *glob, char **patternp,
   char *buf = glob->glob_buffer;
   char *pattern = *patternp;
   char *opattern = pattern;
+  size_t opos = *posp-1;
 
   pat = &glob->pattern[glob->size];
   /* patterns 0,1,2,... correspond to size=1,3,5,... */
@@ -99,27 +100,20 @@ static GlobCode glob_set(URLGlob *glob, char **patternp,
   while(!done) {
     switch (*pattern) {
     case '\0':                  /* URL ended while set was still open */
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "unmatched brace at pos %zu\n", pos);
-      return GLOB_ERROR;
+      return GLOBERROR("unmatched brace", opos, GLOB_ERROR);
 
     case '{':
     case '[':                   /* no nested expressions at this time */
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "nested braces not supported at pos %zu\n", pos);
-      return GLOB_ERROR;
+      return GLOBERROR("nested brace", *posp, GLOB_ERROR);
 
     case '}':                           /* set element completed */
-      if(opattern == pattern) {
-        snprintf(glob->errormsg, sizeof(glob->errormsg),
-                 "no string within braces at pos %zu\n", pos);
-        return GLOB_ERROR;
-      }
+      if(opattern == pattern)
+        return GLOBERROR("empty string within braces", *posp, GLOB_ERROR);
+
       /* add 1 to size since it'll be incremented below */
-      if(multiply(amount, pat->content.Set.size+1)) {
-        strcpy(glob->errormsg, "range overflow!\n");
-        return GLOB_ERROR;
-      }
+      if(multiply(amount, pat->content.Set.size+1))
+        return GLOBERROR("range overflow", 0, GLOB_ERROR);
+
       /* fall-through */
     case ',':
 
@@ -127,27 +121,21 @@ static GlobCode glob_set(URLGlob *glob, char **patternp,
       if(pat->content.Set.elements) {
         char **new_arr = realloc(pat->content.Set.elements,
                                  (pat->content.Set.size + 1) * sizeof(char*));
-        if(!new_arr) {
-          snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
-          return GLOB_NO_MEM;
-        }
+        if(!new_arr)
+          return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
 
         pat->content.Set.elements = new_arr;
       }
       else
         pat->content.Set.elements = malloc(sizeof(char*));
 
-      if(!pat->content.Set.elements) {
-        snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
-        return GLOB_NO_MEM;
-      }
+      if(!pat->content.Set.elements)
+        return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
 
       pat->content.Set.elements[pat->content.Set.size] =
         strdup(glob->glob_buffer);
-      if(!pat->content.Set.elements[pat->content.Set.size]) {
-        snprintf(glob->errormsg, sizeof(glob->errormsg), "out of memory\n");
-        return GLOB_NO_MEM;
-      }
+      if(!pat->content.Set.elements[pat->content.Set.size])
+        return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
       ++pat->content.Set.size;
 
       if(*pattern == '}') {
@@ -158,23 +146,21 @@ static GlobCode glob_set(URLGlob *glob, char **patternp,
 
       buf = glob->glob_buffer;
       ++pattern;
-      ++pos;
+      ++(*posp);
       break;
 
     case ']':                           /* illegal closing bracket */
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "illegal pattern at pos %zu\n", pos);
-      return GLOB_ERROR;
+      return GLOBERROR("unexpected close bracket", *posp, GLOB_ERROR);
 
     case '\\':                          /* escaped character, skip '\' */
       if(pattern[1]) {
         ++pattern;
-        ++pos;
+        ++(*posp);
       }
       /* intentional fallthrough */
     default:
       *buf++ = *pattern++;              /* copy character to set element */
-      ++pos;
+      ++(*posp);
     }
   }
 
@@ -183,7 +169,7 @@ static GlobCode glob_set(URLGlob *glob, char **patternp,
 }
 
 static GlobCode glob_range(URLGlob *glob, char **patternp,
-                           size_t pos, unsigned long *amount,
+                           size_t *posp, unsigned long *amount,
                            int globindex)
 {
   /* processes a range expression with the point behind the opening '['
@@ -227,13 +213,12 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
     else
       pattern+=3;
 
+    *posp += (pattern - *patternp);
+
     if((rc != 2) || (min_c >= max_c) || ((max_c - min_c) > ('z' - 'a')) ||
-       (step < 0) ) {
+       (step < 0) )
       /* the pattern is not well-formed */
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "error: bad range specification after pos %zu\n", pos);
-      return GLOB_ERROR;
-    }
+      return GLOBERROR("bad range", *posp, GLOB_ERROR);
 
     /* if there was a ":[num]" thing, use that as step or else use 1 */
     pat->content.CharRange.step = step;
@@ -241,10 +226,8 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
     pat->content.CharRange.max_c = max_c;
 
     if(multiply(amount, (pat->content.CharRange.max_c -
-                         pat->content.CharRange.min_c + 1))) {
-      strcpy(glob->errormsg, "range overflow!\n");
-      return GLOB_ERROR;
-    }
+                         pat->content.CharRange.min_c + 1)))
+      return GLOBERROR("range overflow", *posp, GLOB_ERROR);
   }
   else if(ISDIGIT(*pattern)) {
     /* numeric range detected */
@@ -295,12 +278,12 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
       }
     }
 
-    if(!endp || (min_n > max_n) || (step_n > (max_n - min_n))) {
+    *posp += (pattern - *patternp);
+
+    if(!endp || (min_n > max_n) || (step_n > (max_n - min_n)))
       /* the pattern is not well-formed */
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "error: bad range specification after pos %zu\n", pos);
-      return GLOB_ERROR;
-    }
+      return GLOBERROR("bad range", *posp, GLOB_ERROR);
+
     /* typecasting to ints are fine here since we make sure above that we
        are within 31 bits */
     pat->content.NumRange.ptr_n = pat->content.NumRange.min_n = min_n;
@@ -308,16 +291,11 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
     pat->content.NumRange.step = step_n;
 
     if(multiply(amount, (pat->content.NumRange.max_n -
-                         pat->content.NumRange.min_n + 1))) {
-      strcpy(glob->errormsg, "range overflow!\n");
-      return GLOB_ERROR;
-    }
-  }
-  else {
-    snprintf(glob->errormsg, sizeof(glob->errormsg),
-             "illegal character in range specification at pos %zu\n", pos);
-    return GLOB_ERROR;
+                         pat->content.NumRange.min_n + 1)))
+      return GLOBERROR("range overflow", *posp, GLOB_ERROR);
   }
+  else
+    return GLOBERROR("bad range specification", *posp, GLOB_ERROR);
 
   *patternp = pattern;
   return GLOB_OK;
@@ -336,11 +314,8 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
   while(*pattern && !res) {
     int sublen = 0;
     while(*pattern && *pattern != '{' && *pattern != '[') {
-      if(*pattern == '}' || *pattern == ']') {
-        snprintf(glob->errormsg, sizeof(glob->errormsg),
-                 "unmatched close brace/bracket at pos %zu\n", pos);
-        return GLOB_ERROR;
-      }
+      if(*pattern == '}' || *pattern == ']')
+        return GLOBERROR("unmatched close brace/bracket", pos, GLOB_ERROR);
 
       /* only allow \ to escape known "special letters" */
       if(*pattern == '\\' &&
@@ -371,23 +346,21 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
       case '{':
         /* process set pattern */
         pattern++;
-        res = glob_set(glob, &pattern, ++pos, amount, globindex++);
+        pos++;
+        res = glob_set(glob, &pattern, &pos, amount, globindex++);
         break;
 
       case '[':
         /* process range pattern */
         pattern++;
-        res = glob_range(glob, &pattern, ++pos, amount, globindex++);
+        pos++;
+        res = glob_range(glob, &pattern, &pos, amount, globindex++);
         break;
       }
     }
 
-    if(++glob->size > GLOB_PATTERN_NUM) {
-      snprintf(glob->errormsg, sizeof(glob->errormsg),
-               "too many globs used\n");
-      return GLOB_ERROR;
-    }
-
+    if(++glob->size > GLOB_PATTERN_NUM)
+      return GLOBERROR("too many globs", pos, GLOB_ERROR);
   }
   return res;
 }
@@ -421,9 +394,19 @@ int glob_url(URLGlob** glob, char* url, unsigned long *urlnum, FILE *error)
   if(!res)
     *urlnum = amount;
   else {
-    if(error && glob_expand->errormsg[0]) {
+    if(error && glob_expand->error) {
+      char text[128];
+      const char *t;
+      if(glob_expand->pos) {
+        snprintf(text, sizeof(text), "%s in column %zu", glob_expand->error,
+                 glob_expand->pos);
+        t = text;
+      }
+      else
+        t = glob_expand->error;
+
       /* send error description to the error-stream */
-      fprintf(error, "curl: (%d) [globbing] %s", res, glob_expand->errormsg);
+      fprintf(error, "curl: (%d) [globbing] %s\n", res, t);
     }
     /* it failed, we cleanup */
     glob_cleanup(glob_expand);
diff --git a/src/tool_urlglob.h b/src/tool_urlglob.h
index e1e9c63..9fa6f83 100644
--- a/src/tool_urlglob.h
+++ b/src/tool_urlglob.h
@@ -64,7 +64,8 @@ typedef struct {
   size_t urllen;
   char *glob_buffer;
   char beenhere;
-  char errormsg[80]; /* error message buffer */
+  const char *error; /* error message */
+  size_t pos;        /* column position of error or 0 */
 } URLGlob;
 
 int glob_url(URLGlob**, char*, unsigned long *, FILE *);
diff --git a/tests/data/test75 b/tests/data/test75
index 07f6990..a9f4d16 100644
--- a/tests/data/test75
+++ b/tests/data/test75
@@ -24,7 +24,7 @@ http
 HTTP, urlglob retrieval with bad range
  </name>
  <command option="no-output">
-"http://%HOSTIP:%HTTPPORT/[2-1]" -o "log/weee#1.dump" --stderr -
+"http://a-site-never-accessed.example.org/[2-1]" -o "log/weee#1.dump" --stderr -
 </command>
 # The error message on stdout implicitly depends on the length of the
 # URL, so refuse to run if the length is unexpected.
@@ -43,7 +43,7 @@ perl %SRCDIR/libtest/test75.pl http://%HOSTIP:%HTTPPORT/ 22
 3
 </errorcode>
 <stdout mode="text">
-curl: (3) [globbing] error: bad range specification after pos 24
+curl: (3) [globbing] bad range in column 47
 </stdout>
 </verify>
 </testcase>
-- 
1.8.3.1


From 8f377feb97183859a8c93e19fdb82351d45364b1 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 22 Oct 2013 00:01:17 +0200
Subject: [PATCH 07/13] glob: fix regression from commit 5ca96cb844

Plain strings after glob ranges/lists weren't treated correctly but
caused broken URLs to get used.

Reported-by: Javier Barroso

[upstream commit 867b52a7ac528cee42aa663004d57d7ebdab5ecc]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 5f94519..4208327 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -40,7 +40,8 @@ typedef enum {
 
 void glob_cleanup(URLGlob* glob);
 
-static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount)
+static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount,
+                           char *fixed, size_t len)
 {
   URLPattern *pat = &glob->pattern[glob->size];
   pat->type = UPTSet;
@@ -55,10 +56,13 @@ static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount)
   if(!pat->content.Set.elements)
     return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
 
-  pat->content.Set.elements[0] = strdup(glob->glob_buffer);
+  pat->content.Set.elements[0] = malloc(len+1);
   if(!pat->content.Set.elements[0])
     return GLOBERROR("out of memory", 0, GLOB_NO_MEM);
 
+  memcpy(pat->content.Set.elements[0], fixed, len);
+  pat->content.Set.elements[0][len] = 0;
+
   return GLOB_OK;
 }
 
@@ -307,11 +311,11 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
   /* processes a literal string component of a URL
      special characters '{' and '[' branch to set/range processing functions
    */
-  char* buf = glob->glob_buffer;
   GlobCode res = GLOB_OK;
   int globindex = 0; /* count "actual" globs */
 
   while(*pattern && !res) {
+    char *buf = glob->glob_buffer;
     int sublen = 0;
     while(*pattern && *pattern != '{' && *pattern != '[') {
       if(*pattern == '}' || *pattern == ']')
@@ -333,7 +337,7 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
     if(sublen) {
       /* we got a literal string, add it as a single-item list */
       *buf = '\0';
-      res = glob_fixed(glob, amount);
+      res = glob_fixed(glob, amount, glob->glob_buffer, sublen);
     }
     else {
       if(!*amount)
-- 
1.8.3.1


From c02ec7eeb1406eec37420766bdd310496772c874 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Sun, 3 Nov 2013 10:08:10 +0100
Subject: [PATCH 08/13] glob_range: pass the closing bracket for a-z ranges

Regression since commit 5ca96cb844102 (release in 7.33.0)

Reported-by: Marcin Gryszkalis

[upstream commit bce03fe14452da555468616db52003ba05c0e288]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index 4208327..aa87e5a 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -215,7 +215,7 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
       }
     }
     else
-      pattern+=3;
+      pattern += 4;
 
     *posp += (pattern - *patternp);
 
-- 
1.8.3.1


From d7f9c9489d0f6024a47f1311d25b42b957d556a5 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Thu, 28 Nov 2013 23:31:31 +0100
Subject: [PATCH 09/13] globbing: curl glob counter mismatch with {} list use

The "fixed string" function wrongly bumped the "urlnum" counter which
made curl output the total number of URLs wrong when using
{one,two,three} lists in globs.

Reported-by: Michael-O
Bug: http://curl.haxx.se/bug/view.cgi?id=1305

[upstream commit 0dd6522036daa8468c55c4a0c6b70d1c510e879a]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index aa87e5a..ec5014b 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -40,8 +40,7 @@ typedef enum {
 
 void glob_cleanup(URLGlob* glob);
 
-static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount,
-                           char *fixed, size_t len)
+static GlobCode glob_fixed(URLGlob *glob, char *fixed, size_t len)
 {
   URLPattern *pat = &glob->pattern[glob->size];
   pat->type = UPTSet;
@@ -49,8 +48,6 @@ static GlobCode glob_fixed(URLGlob *glob, unsigned long *amount,
   pat->content.Set.ptr_s = 0;
   pat->globindex = -1;
 
-  (*amount)++;
-
   pat->content.Set.elements = malloc(sizeof(char*));
 
   if(!pat->content.Set.elements)
@@ -314,6 +311,8 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
   GlobCode res = GLOB_OK;
   int globindex = 0; /* count "actual" globs */
 
+  *amount = 1;
+
   while(*pattern && !res) {
     char *buf = glob->glob_buffer;
     int sublen = 0;
@@ -337,12 +336,9 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
     if(sublen) {
       /* we got a literal string, add it as a single-item list */
       *buf = '\0';
-      res = glob_fixed(glob, amount, glob->glob_buffer, sublen);
+      res = glob_fixed(glob, glob->glob_buffer, sublen);
     }
     else {
-      if(!*amount)
-        *amount = 1;
-
       switch (*pattern) {
       case '\0': /* done  */
         break;
-- 
1.8.3.1


From 584265e9422e98857bbc4e050c9e9cfb3ec08209 Mon Sep 17 00:00:00 2001
From: Paul Marks <pmarks@google.com>
Date: Sun, 30 Mar 2014 07:50:37 +0200
Subject: [PATCH 10/13] curl: stop interpreting IPv6 literals as glob patterns.

This makes it possible to fetch from an IPv6 literal without specifying
the -g option.  Globbing remains available elsehwere in the URL.

For example:
  curl http://[::1]/file[1-3].txt

This creates no ambiguity, because there is no overlap between the
syntax of valid globs and valid IPv6 literals.  Globs contain hyphens
and at most 1 colon, while IPv6 literals have no hyphens, and at least 2
colons.

The peek_ipv6() parser simply whitelists a set of characters and counts
colons, because the real validation happens later on.  The character set
includes A-Z, in case someone decides to implement support for scopes
like [fe80::1%25eth0] in the future.

Signed-off-by: Paul Marks <pmarks@google.com>

[upstream commit 0bc4938eecccefdf8906bf9c488e4cd9c8467e99]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_urlglob.c  | 48 +++++++++++++++++++++++++++++++++++++++++++++---
 tests/data/test1230 |  2 +-
 2 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c
index ec5014b..943e0ab 100644
--- a/src/tool_urlglob.c
+++ b/src/tool_urlglob.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -302,6 +302,36 @@ static GlobCode glob_range(URLGlob *glob, char **patternp,
   return GLOB_OK;
 }
 
+static bool peek_ipv6(const char *str, size_t *skip)
+{
+  /*
+   * Scan for a potential IPv6 literal.
+   * - Valid globs contain a hyphen and <= 1 colon.
+   * - IPv6 literals contain no hyphens and >= 2 colons.
+   */
+  size_t i = 0;
+  size_t colons = 0;
+  if(str[i++] != '[') {
+    return FALSE;
+  }
+  for(;;) {
+    const char c = str[i++];
+    if(ISALNUM(c) || c == '.' || c == '%') {
+      /* ok */
+    }
+    else if(c == ':') {
+      colons++;
+    }
+    else if(c == ']') {
+      *skip = i;
+      return colons >= 2;
+    }
+    else {
+      return FALSE;
+    }
+  }
+}
+
 static GlobCode glob_parse(URLGlob *glob, char *pattern,
                            size_t pos, unsigned long *amount)
 {
@@ -315,8 +345,20 @@ static GlobCode glob_parse(URLGlob *glob, char *pattern,
 
   while(*pattern && !res) {
     char *buf = glob->glob_buffer;
-    int sublen = 0;
-    while(*pattern && *pattern != '{' && *pattern != '[') {
+    size_t sublen = 0;
+    while(*pattern && *pattern != '{') {
+      if(*pattern == '[') {
+        /* Skip over potential IPv6 literals. */
+        size_t skip;
+        if(peek_ipv6(pattern, &skip)) {
+          memcpy(buf, pattern, skip);
+          buf += skip;
+          pattern += skip;
+          sublen += skip;
+          continue;
+        }
+        break;
+      }
       if(*pattern == '}' || *pattern == ']')
         return GLOBERROR("unmatched close brace/bracket", pos, GLOB_ERROR);
 
diff --git a/tests/data/test1230 b/tests/data/test1230
index a071850..4b94870 100644
--- a/tests/data/test1230
+++ b/tests/data/test1230
@@ -55,7 +55,7 @@ HTTP CONNECT to IPv6 numerical address
  </name>
 # 0x4ce == 1230, the test number
  <command>
--g http://[1234:1234:1234::4ce]:%HTTPPORT/wanted/page/1230 -p -x %HOSTIP:%HTTPPORT
+http://[1234:1234:1234::4ce]:%HTTPPORT/wanted/page/1230 -p -x %HOSTIP:%HTTPPORT
 </command>
 </client>
 
-- 
1.8.3.1


From 864cfb5e9ccfba43b00ce9e1ee6ecc4a4329447b Mon Sep 17 00:00:00 2001
From: Till Maas <opensource@till.name>
Date: Sat, 15 Mar 2014 22:42:50 +0100
Subject: [PATCH 11/13] URL parser: IPv6 zone identifiers are now supported

[upstream commit 9317eced98408c7fefa6dd5f1559050e1ec8a3b7]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 docs/KNOWN_BUGS | 12 +---------
 docs/MANUAL     |  6 ++---
 lib/url.c       | 69 +++++++++++++++++++++++++++++++++++++++++++++++----------
 3 files changed, 61 insertions(+), 26 deletions(-)

diff --git a/docs/KNOWN_BUGS b/docs/KNOWN_BUGS
index 7bd6e6e..34ea148 100644
--- a/docs/KNOWN_BUGS
+++ b/docs/KNOWN_BUGS
@@ -158,17 +158,7 @@ may have been fixed since this was written!
   --cflags suffers from the same effects with CFLAGS/CPPFLAGS.
 
 30. You need to use -g to the command line tool in order to use RFC2732-style
-  IPv6 numerical addresses in URLs.
-
-29. IPv6 URLs with zone ID is not nicely supported.
-  http://www.ietf.org/internet-drafts/draft-fenner-literal-zone-02.txt (expired)
-  specifies the use of a plus sign instead of a percent when specifying zone
-  IDs in URLs to get around the problem of percent signs being
-  special. According to the reporter, Firefox deals with the URL _with_ a
-  percent letter (which seems like a blatant URL spec violation).
-  libcurl supports zone IDs where the percent sign is URL-escaped (i.e. %25).
-
-   See http://curl.haxx.se/bug/view.cgi?id=1371118
+  or RFC6874-style IPv6 numerical addresses in URLs.
 
 26. NTLM authentication using SSPI (on Windows) when (lib)curl is running in
   "system context" will make it use wrong(?) user name - at least when compared
diff --git a/docs/MANUAL b/docs/MANUAL
index 4ad2e13..da8f602 100644
--- a/docs/MANUAL
+++ b/docs/MANUAL
@@ -956,9 +956,9 @@ IPv6
   When this style is used, the -g option must be given to stop curl from
   interpreting the square brackets as special globbing characters.  Link local
   and site local addresses including a scope identifier, such as fe80::1234%1,
-  may also be used, but the scope portion must be numeric and the percent
-  character must be URL escaped. The previous example in an SFTP URL might
-  look like:
+  may also be used, but the scope portion must be numeric or match an existing
+  network interface on Linux and the percent character must be URL escaped. The
+  previous example in an SFTP URL might look like:
 
     sftp://[fe80::1234%251]/
 
diff --git a/lib/url.c b/lib/url.c
index 0174ff4..2642f92 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -3938,23 +3938,59 @@ static CURLcode parseurlandfillconn(struct SessionHandle *data,
   if(result != CURLE_OK)
     return result;
 
-  if(conn->host.name[0] == '[') {
+  if(conn->host.name[0] == '[' && !data->state.this_is_a_follow) {
     /* This looks like an IPv6 address literal.  See if there is an address
-       scope.  */
-    char *percent = strstr (conn->host.name, "%25");
+       scope if there is no location header */
+    char *percent = strchr(conn->host.name, '%');
     if(percent) {
+      unsigned int identifier_offset = 3;
       char *endp;
-      unsigned long scope = strtoul (percent + 3, &endp, 10);
+      unsigned long scope;
+      if(strncmp("%25", percent, 3) != 0) {
+        infof(data,
+              "Please URL encode %% as %%25, see RFC 6874.\n");
+        identifier_offset = 1;
+      }
+      scope = strtoul(percent + identifier_offset, &endp, 10);
       if(*endp == ']') {
         /* The address scope was well formed.  Knock it out of the
            hostname. */
         memmove(percent, endp, strlen(endp)+1);
-        if(!data->state.this_is_a_follow)
-          /* Don't honour a scope given in a Location: header */
-          conn->scope = (unsigned int)scope;
+        conn->scope = (unsigned int)scope;
+      }
+      else {
+        /* Zone identifier is not numeric */
+#ifdef HAVE_NET_IF_H
+        char ifname[IFNAMSIZ + 2];
+        char *square_bracket;
+        unsigned int scopeidx = 0;
+        strncpy(ifname, percent + identifier_offset, IFNAMSIZ + 2);
+        /* Ensure nullbyte termination */
+        ifname[IFNAMSIZ + 1] = '\0';
+        square_bracket = strchr(ifname, ']');
+        if(square_bracket) {
+          /* Remove ']' */
+          *square_bracket = '\0';
+          scopeidx = if_nametoindex(ifname);
+          if(scopeidx == 0) {
+            infof(data, "Invalid network interface: %s; %s\n", ifname,
+                  strerror(errno));
+          }
+        }
+        if(scopeidx > 0) {
+          /* Remove zone identifier from hostname */
+          memmove(percent,
+                  percent + identifier_offset + strlen(ifname),
+                  identifier_offset + strlen(ifname));
+          conn->scope = scopeidx;
+        }
+        else {
+#endif /* HAVE_NET_IF_H */
+          infof(data, "Invalid IPv6 address format\n");
+#ifdef HAVE_NET_IF_H
+        }
+#endif /* HAVE_NET_IF_H */
       }
-      else
-        infof(data, "Invalid IPv6 address format\n");
     }
   }
 
@@ -4309,12 +4345,21 @@ static CURLcode parse_proxy(struct SessionHandle *data,
   /* start scanning for port number at this point */
   portptr = proxyptr;
 
-  /* detect and extract RFC2732-style IPv6-addresses */
+  /* detect and extract RFC6874-style IPv6-addresses */
   if(*proxyptr == '[') {
     char *ptr = ++proxyptr; /* advance beyond the initial bracket */
-    while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '%') ||
-                   (*ptr == '.')))
+    while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.')))
+      ptr++;
+    if(*ptr == '%') {
+      /* There might be a zone identifier */
+      if(strncmp("%25", ptr, 3))
+        infof(data, "Please URL encode %% as %%25, see RFC 6874.\n");
       ptr++;
+      /* Allow unresered characters as defined in RFC 3986 */
+      while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') ||
+                     (*ptr == '.') || (*ptr == '_') || (*ptr == '~')))
+        ptr++;
+    }
     if(*ptr == ']')
       /* yeps, it ended nicely with a bracket as well */
       *ptr++ = 0;
-- 
1.8.3.1


From a842be97b0d8d3e1bf323bc88f2eceecd2d42087 Mon Sep 17 00:00:00 2001
From: Dan Fandrich <dan@coneharvesters.com>
Date: Mon, 31 Mar 2014 09:02:55 +0200
Subject: [PATCH 12/13] docs: Removed mention of -g hack when using IPv6
 literals

This limitation was removed in commit 0bc4938e

[upstream commit ed4972ffdb11fc62a8bae33ff4eafbd73973ad9f]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 docs/MANUAL | 2 +-
 docs/TODO   | 8 --------
 2 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/docs/MANUAL b/docs/MANUAL
index da8f602..11960e1 100644
--- a/docs/MANUAL
+++ b/docs/MANUAL
@@ -50,7 +50,7 @@ SIMPLE USAGE
 
   Get the main page from an IPv6 web server:
 
-        curl -g "http://[2001:1890:1112:1::20]/"
+        curl "http://[2001:1890:1112:1::20]/"
 
 DOWNLOAD TO A FILE
 
diff --git a/docs/TODO b/docs/TODO
index 8b133dc..825b7af 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -92,7 +92,6 @@
  15.5 provide formpost headers
  15.6 url-specific options
  15.7 warning when setting an option
- 15.8 IPv6 addresses with globbing
 
  16. Build
  16.1 roffit
@@ -538,13 +537,6 @@ to provide the data to send.
   This can be useful to tell when support for a particular feature hasn't been
   compiled into the library.
 
-15.8 IPv6 addresses with globbing
-
-  Currently the command line client needs to get url globbing disabled (with
-  -g) for it to support IPv6 numerical addresses. This is a rather silly flaw
-  that should be corrected. It probably involves a smarter detection of the
-  '[' and ']' letters.
-
 16. Build
 
 16.1 roffit
-- 
1.8.3.1


From 4dd964688eda563ae960a9e3e573c295ca728efa Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Mon, 31 Mar 2014 09:35:32 +0200
Subject: [PATCH 13/13] ipv6: strip off zone identifiers in redirects too

Follow up to 9317eced984 makes test 1056 work again.

[upstream commit 13682d1a24bba5386530805d8fbcf987b19c3552]

Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 lib/url.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/url.c b/lib/url.c
index 2642f92..c551262 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -3938,7 +3938,7 @@ static CURLcode parseurlandfillconn(struct SessionHandle *data,
   if(result != CURLE_OK)
     return result;
 
-  if(conn->host.name[0] == '[' && !data->state.this_is_a_follow) {
+  if(conn->host.name[0] == '[') {
     /* This looks like an IPv6 address literal.  See if there is an address
        scope if there is no location header */
     char *percent = strchr(conn->host.name, '%');
-- 
1.8.3.1