cURL / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] WILDCARDMATCH/CHUNKING/FNMATCH added

From: none <pavel_at_pavel.(none)>
Date: Wed, 12 May 2010 15:33:22 +0200

---
 docs/libcurl/curl_easy_setopt.3 |   85 ++++
 docs/libcurl/libcurl-errors.3   |   10 +
 include/curl/curl.h             |  109 +++++
 lib/Makefile.inc                |    2 +
 lib/curl_fnmatch.c              |  410 ++++++++++++++++
 lib/curl_fnmatch.h              |   44 ++
 lib/fileinfo.c                  |   66 +++
 lib/fileinfo.h                  |   33 ++
 lib/ftp.c                       |  261 ++++++++++-
 lib/ftp.h                       |   13 +
 lib/ftplistparser.c             | 1009 ++++++++++++++++++++++++++++++++=
+++++++
 lib/ftplistparser.h             |   38 ++
 lib/multi.c                     |   38 ++-
 lib/strerror.c                  |    6 +
 lib/transfer.c                  |   49 ++-
 lib/url.c                       |   24 +
 lib/urldata.h                   |    8 +
 lib/wildcard.c                  |   66 +++
 lib/wildcard.h                  |   58 +++
 tests/data/Makefile.am          |    1 +
 tests/data/test1113             |   71 +++
 tests/data/test1114             |  136 ++++++
 tests/data/test146              |    2 +-
 tests/data/test149              |    2 +-
 tests/data/test539              |    2 +-
 tests/data/test574              |   71 +++
 tests/data/test575              |   79 +++
 tests/data/test576              |  192 ++++++++
 tests/data/test577              |   38 ++
 tests/directories.pm            |  266 ++++++++++
 tests/ftpserver.pl              |  107 ++++-
 tests/libtest/Makefile.inc      |    9 +
 tests/libtest/lib574.c          |   56 +++
 tests/libtest/lib575.c          |  109 +++++
 tests/libtest/lib576.c          |  107 ++++
 tests/libtest/lib577.c          |  217 +++++++++
 36 files changed, 3778 insertions(+), 16 deletions(-)
 create mode 100644 lib/curl_fnmatch.c
 create mode 100644 lib/curl_fnmatch.h
 create mode 100644 lib/fileinfo.c
 create mode 100644 lib/fileinfo.h
 create mode 100644 lib/ftplistparser.c
 create mode 100644 lib/ftplistparser.h
 create mode 100644 lib/wildcard.c
 create mode 100644 lib/wildcard.h
 create mode 100644 tests/data/test1113
 create mode 100644 tests/data/test1114
 create mode 100644 tests/data/test574
 create mode 100644 tests/data/test575
 create mode 100644 tests/data/test576
 create mode 100644 tests/data/test577
 create mode 100644 tests/directories.pm
 create mode 100644 tests/libtest/lib574.c
 create mode 100644 tests/libtest/lib575.c
 create mode 100644 tests/libtest/lib576.c
 create mode 100644 tests/libtest/lib577.c
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_se=
topt.3
index 88b216c..295eb74 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -84,6 +84,54 @@ If this option is set and libcurl has been built with=
 the standard name
 resolver, timeouts will not occur while the name resolve takes place.
 Consider building libcurl with c-ares support to enable asynchronous DN=
S
 lookups, which enables nice timeouts for name resolves without signals.=
+.IP CURLOPT_WILDCARDMATCH
+Set this option to 1 if you want to transfer multiple files according t=
o a
+file name pattern. The pattern can be specified as part of the \fICURLO=
PT_URL\fP
+option, using an fnmatch-like pattern (Shell Pattern Matching) in the l=
ast part
+of URL (file name).
+
+By default, libcurl uses its internal implementation of fnmatch(). You =
can
+provide your own matching function by the \fICURLOPT_FNMATCH_FUNCTION\f=
R option.
+
+This feature is only supported by the FTP download for now.
+
+A brief introduction of its syntax follows:
+.RS
+.IP "\fB*\fR - ASTERISK"
+\&ftp://example.com/some/path/\fB*.txt\fR (for all txt's from the root
+directory)
+.RE
+.RS
+.IP "\fB?\fR - QUESTION MARK"
+Question mark matches any (exactly one) character.
+
+\&ftp://example.com/some/path/\fBphoto?.jpeg\fR
+.RE
+.RS
+.IP "\fB[\fR - BRACKET EXPRESSION"
+The left bracket opens a bracket expression. The question mark and aste=
risk have
+no special meaning in a bracket expression. Each bracket expression end=
s by the
+right bracket and matches exactly one character. Some examples follow:
+
+\fB[a-zA-Z0\-9]\fR or \fB[f\-gF\-G]\fR \- character interval
+
+\fB[abc]\fR - character enumeration
+
+\fB[^abc]\fR or \fB[!abc]\fR - negation
+
+\fB[[:\fR\fIname\fR\fB:]]\fR class expression. Supported classes are
+\fBalnum\fR,\fBlower\fR, \fBspace\fR, \fBalpha\fR, \fBdigit\fR, \fBprin=
t\fR,
+\fBupper\fR, \fBblank\fR, \fBgraph\fR, \fBxdigit\fR.
+
+\fB[][-!^]\fR - special case \- matches only '\-', ']', '[', '!' or '^'=
. These
+characters have no special purpose.
+
+\fB[\\[\\]\\\\]\fR - escape syntax. Matches '[', ']' or '\\'.
+
+Using the rules above, a file name pattern can be constructed:
+
+\&ftp://example.com/some/path/\fB[a-z[:upper:]\\\\].jpeg\fR
+.RE
 .PP
 .SH CALLBACK OPTIONS
 .IP CURLOPT_WRITEFUNCTION
@@ -424,6 +472,43 @@ in 7.20.0)
 .IP CURLOPT_INTERLEAVEDATA
 This is the stream that will be passed to \fICURLOPT_INTERLEAVEFUNCTION=
\fP when
 interleaved RTP data is received. (Added in 7.20.0)
+.IP CURLOPT_CHUNK_BGN_FUNCTION
+Function pointer that should match the following prototype: \fBlong fun=
ction
+(const void *transfer_info, void *ptr, int remains)\fR. This function
+gets called by libcurl before a part of the stream is going to be trans=
ferred
+(if the transfer supports chunks).
+
+This callback makes sense only when using the \fICURLOPT_WILDCARDMATCH\=
fR
+option for now.
+
+The target of transfer_info parameter is a "feature depended" structure=
. For the
+FTP wildcard download, the target is curl_fileinfo structure (see
+\fIcurl/curl.h\fR).
+The parameter ptr is a pointer given by \fICURLOPT_CHUNK_DATA\fR. The p=
arameter
+remains contains number of chunks remaining per the transfer. If the fe=
ature is
+not available, the parameter has zero value.
+
+Return \fICURL_CHUNK_BGN_FUNC_OK\fR if everything is fine,
+\fICURL_CHUNK_BGN_FUNC_SKIP\fR if you want to skip the concrete chunk o=
r
+\fICURL_CHUNK_BGN_FUNC_FAIL\fR to tell libcurl to stop if some error oc=
curred.
+.IP CURLOPT_CHUNK_END_FUNCTION
+Function pointer that should match the following prototype:
+\fBlong function(void *ptr)\fR. This function gets called by libcurl as=
 soon as
+a part of the stream has been transferred (or skipped).
+
+Return \fICURL_CHUNK_END_FUNC_OK\fR if everything is fine or
+\fBCURL_CHUNK_END_FUNC_FAIL\fR to tell the lib to stop if some error oc=
curred.
+.IP CURLOPT_CHUNK_DATA
+Pass a pointer that will be untouched by libcurl and passed as the ptr =
argument
+to the \fICURL_CHUNK_BGN_FUNTION\fR and \fICURL_CHUNK_END_FUNTION\fR.
+.IP CURLOPT_FNMATCH_FUNCTION
+Function pointer that should match \fBint function(const char *pattern,=
 const
+char *string)\fR prototype (see \fIcurl/curl.h\fR). It is used internal=
ly for
+the wildcard matching feature.
+
+Return \fICURL_FNMATCHFUNC_MATCH\fR if pattern matches the string,
+\fICURL_FNMATCHFUNC_NOMATCH\fR if not or \fICURL_FNMATCHFUNC_FAIL\fR if=
 an error
+occurred.
 .SH ERROR OPTIONS
 .IP CURLOPT_ERRORBUFFER
 Pass a char * to a buffer that the libcurl may store human readable err=
or
diff --git a/docs/libcurl/libcurl-errors.3 b/docs/libcurl/libcurl-errors=
.3
index b4f2940..c3c854e 100644
--- a/docs/libcurl/libcurl-errors.3
+++ b/docs/libcurl/libcurl-errors.3
@@ -218,6 +218,16 @@ return code is only returned from \fIcurl_easy_recv=
(3)\fP and
 Failed to load CRL file (Added in 7.19.0)
 .IP "CURLE_SSL_ISSUER_ERROR (83)"
 Issuer check failed (Added in 7.19.0)
+.IP "CURLE_FTP_PRET_FAILED (84)"
+PRET command failed
+.IP "CURLE_RTSP_CSEQ_ERROR (85)"
+Mismatch of RTSP CSeq numbers.
+.IP "CURLE_RTSP_SESSION_ERROR (86)"
+Mismatch of RTSP Session Identifiers.
+.IP "CURLE_FTP_BAD_FILE_LIST (87)"
+Unable to parse FTP file list (during FTP wildcard downloading).
+.IP "CURLE_CHUNK_FAILED (88)"
+Chunk callback reported error.
 .IP "CURLE_OBSOLETE*"
 These error codes will never be returned. They were used in an old libc=
url
 version and are currently unused.
diff --git a/include/curl/curl.h b/include/curl/curl.h
index e635968..a89c171 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -198,6 +198,96 @@ typedef size_t (*curl_write_callback)(char *buffer,=
                                       size_t nitems,
                                       void *outstream);
 =
+
+
+/* enumeration of file types */
+typedef enum {
+  CURLFILETYPE_FILE =3D 0,
+  CURLFILETYPE_DIRECTORY,
+  CURLFILETYPE_SYMLINK,
+  CURLFILETYPE_DEVICE_BLOCK,
+  CURLFILETYPE_DEVICE_CHAR,
+  CURLFILETYPE_NAMEDPIPE,
+  CURLFILETYPE_SOCKET,
+  CURLFILETYPE_DOOR, /* is possible only on Sun Solaris now */
+
+  CURLFILETYPE_UNKNOWN /* should never occur */
+} curlfiletype;
+
+#define CURLFINFOFLAG_KNOWN_FILENAME    (1<<0)
+#define CURLFINFOFLAG_KNOWN_FILETYPE    (1<<1)
+#define CURLFINFOFLAG_KNOWN_TIME        (1<<2)
+#define CURLFINFOFLAG_KNOWN_PERM        (1<<3)
+#define CURLFINFOFLAG_KNOWN_UID         (1<<4)
+#define CURLFINFOFLAG_KNOWN_GID         (1<<5)
+#define CURLFINFOFLAG_KNOWN_SIZE        (1<<6)
+#define CURLFINFOFLAG_KNOWN_HLINKCOUNT  (1<<7)
+
+/* Content of this structure depends on information which is known and =
is
+   achievable (e.g. by FTP LIST parsing). Please see the url_easy_setop=
t(3) man
+   page for callbacks returning this structure -- some fields are manda=
tory,
+   some others are optional. The FLAG field has special meaning. */
+struct curl_fileinfo {
+  char *filename;
+  curlfiletype filetype;
+  time_t time;
+  int32_t perm;
+  int uid;
+  int gid;
+  curl_off_t size;
+  long int hardlinks;
+
+  struct {
+    /* If some of these fields is not NULL, it is a pointer to b_data. =
*/
+    char *time;
+    char *perm;
+    char *user;
+    char *group;
+    char *target; /* pointer to the target filename of a symlink */
+  } strings;
+
+  int32_t flags;
+
+  /* used internally */
+  char * b_data;
+  size_t b_size;
+  size_t b_used;
+};
+
+/* return codes for CURLOPT_CHUNK_BGN_FUNCTION */
+#define CURL_CHUNK_BGN_FUNC_OK      0
+#define CURL_CHUNK_BGN_FUNC_FAIL    1 /* tell the lib to end the task *=
/
+#define CURL_CHUNK_BGN_FUNC_SKIP    2 /* skip this chunk over */
+
+/* if splitting of data transfer is enabled, this callback is called be=
fore
+   download of an individual chunk started. Note that parameter "remain=
s" works
+   only for FTP wildcard downloading (for now), otherwise is not used *=
/
+typedef long (*curl_chunk_bgn_callback)(const void *transfer_info,
+                                        void *ptr,
+                                        int remains);
+
+/* return codes for CURLOPT_CHUNK_END_FUNCTION */
+#define CURL_CHUNK_END_FUNC_OK      0
+#define CURL_CHUNK_END_FUNC_FAIL    1 /* tell the lib to end the task *=
/
+
+/* If splitting of data transfer is enabled this callback is called aft=
er
+   download of an individual chunk finished.
+   Note! After this callback was set then it have to be called FOR ALL =
chunks.
+   Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC.
+   This is the reason why we don't need "transfer_info" parameter in th=
is
+   callback and we are not interested in "remains" parameter too. */
+typedef long (*curl_chunk_end_callback)(void *ptr);
+
+/* return codes for FNMATCHFUNCTION */
+#define CURL_FNMATCHFUNC_MATCH    0 /* string corresponds to the patter=
n */
+#define CURL_FNMATCHFUNC_NOMATCH  1 /* pattern doesn't match the string=
 */
+#define CURL_FNMATCHFUNC_FAIL     2 /* an error occurred */
+
+/* callback type for wildcard downloading pattern matching. If the
+   string matches the pattern, return CURL_FNMATCHFUNC_MATCH value, etc=
. */
+typedef int (*curl_fnmatch_callback)(const char *pattern,
+                                     const char *string);
+
 /* These are the return codes for the seek callbacks */
 #define CURL_SEEKFUNC_OK       0
 #define CURL_SEEKFUNC_FAIL     1 /* fail the entire transfer */
@@ -409,6 +499,8 @@ typedef enum {
   CURLE_FTP_PRET_FAILED,         /* 84 - a PRET command failed */
   CURLE_RTSP_CSEQ_ERROR,         /* 85 - mismatch of RTSP CSeq numbers =
*/
   CURLE_RTSP_SESSION_ERROR,      /* 86 - mismatch of RTSP Session Ident=
ifiers */
+  CURLE_FTP_BAD_FILE_LIST,       /* 87 - unable to parse FTP file list =
*/
+  CURLE_CHUNK_FAILED,            /* 88 - chunk callback reported error =
*/
 =
   CURL_LAST /* never use! */
 } CURLcode;
@@ -1316,6 +1408,23 @@ typedef enum {
   /* Let the application define a custom write method for RTP data */
   CINIT(INTERLEAVEFUNCTION, FUNCTIONPOINT, 196),
 =
+  /* Turn on wildcard matching */
+  CINIT(WILDCARDMATCH, LONG, 197),
+
+  /* Directory matching callback called before downloading of an
+     individual file (chunk) started */
+  CINIT(CHUNK_BGN_FUNCTION, FUNCTIONPOINT, 198),
+
+  /* Directory matching callback called after the file (chunk)
+     was downloaded, or skipped */
+  CINIT(CHUNK_END_FUNCTION, FUNCTIONPOINT, 199),
+
+  /* Change match (fnmatch-like) callback for wildcard matching */
+  CINIT(FNMATCH_FUNCTION, FUNCTIONPOINT, 200),
+
+  /* Let the application define custom chunk data pointer */
+  CINIT(CHUNK_DATA, OBJECTPOINT, 201),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 =
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index e35e8bb..0a5e26e 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -4,6 +4,7 @@ CSOURCES =3D file.c timeval.c base64.c hostip.c progress=
.c formdata.c	\
   cookie.c http.c sendf.c ftp.c url.c dict.c if2ip.c speedcheck.c	\
   ldap.c ssluse.c version.c getenv.c escape.c mprintf.c telnet.c	\
   netrc.c getinfo.c transfer.c strequal.c easy.c security.c krb4.c	\
+  curl_fnmatch.c fileinfo.c ftplistparser.c wildcard.c \
   krb5.c memdebug.c http_chunks.c strtok.c connect.c llist.c hash.c	\
   multi.c content_encoding.c share.c http_digest.c md5.c curl_rand.c	\
   http_negotiate.c http_ntlm.c inet_pton.c strtoofft.c strerror.c	\
@@ -18,6 +19,7 @@ HHEADERS =3D arpa_telnet.h netrc.h file.h timeval.h qs=
sl.h hostip.h	\
   progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h	\
   if2ip.h speedcheck.h urldata.h curl_ldap.h ssluse.h escape.h telnet.h=
	\
   getinfo.h strequal.h krb4.h memdebug.h http_chunks.h curl_rand.h	\
+  curl_fnmatch.h wildcard.h fileinfo.h ftplistparser.h \
   strtok.h connect.h llist.h hash.h content_encoding.h share.h		\
   curl_md5.h http_digest.h http_negotiate.h http_ntlm.h inet_pton.h	\
   strtoofft.h strerror.h inet_ntop.h curlx.h curl_memory.h setup.h	\
diff --git a/lib/curl_fnmatch.c b/lib/curl_fnmatch.c
new file mode 100644
index 0000000..a7fe6c9
--- /dev/null
+++ b/lib/curl_fnmatch.c
@@ -0,0 +1,410 @@
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#include "curl_fnmatch.h"
+#include "setup.h"
+
+#define CURLFNM_CHARSET_LEN (sizeof(char) * 256)
+#define CURLFNM_CHSET_SIZE (CURLFNM_CHARSET_LEN + 15)
+
+#define CURLFNM_NEGATE  CURLFNM_CHARSET_LEN
+
+#define CURLFNM_ALNUM   (CURLFNM_CHARSET_LEN + 1)
+#define CURLFNM_DIGIT   (CURLFNM_CHARSET_LEN + 2)
+#define CURLFNM_XDIGIT  (CURLFNM_CHARSET_LEN + 3)
+#define CURLFNM_ALPHA   (CURLFNM_CHARSET_LEN + 4)
+#define CURLFNM_PRINT   (CURLFNM_CHARSET_LEN + 5)
+#define CURLFNM_BLANK   (CURLFNM_CHARSET_LEN + 6)
+#define CURLFNM_LOWER   (CURLFNM_CHARSET_LEN + 7)
+#define CURLFNM_GRAPH   (CURLFNM_CHARSET_LEN + 8)
+#define CURLFNM_SPACE   (CURLFNM_CHARSET_LEN + 9)
+#define CURLFNM_UPPER   (CURLFNM_CHARSET_LEN + 10)
+
+typedef enum {
+  CURLFNM_LOOP_DEFAULT =3D 0,
+  CURLFNM_LOOP_BACKSLASH
+} loop_state;
+
+typedef enum {
+  CURLFNM_SCHS_DEFAULT =3D 0,
+  CURLFNM_SCHS_MAYRANGE,
+  CURLFNM_SCHS_MAYRANGE2,
+  CURLFNM_SCHS_RIGHTBR,
+  CURLFNM_SCHS_RIGHTBRLEFTBR
+} setcharset_state;
+
+typedef enum {
+  CURLFNM_PKW_INIT =3D 0,
+  CURLFNM_PKW_DDOT
+} parsekey_state;
+
+#define SETCHARSET_OK     1
+#define SETCHARSET_FAIL   0
+
+static int parsekeyword(unsigned char **pattern, unsigned char *charset=
)
+{
+  parsekey_state state =3D CURLFNM_PKW_INIT;
+#define KEYLEN 10
+  char keyword[KEYLEN] =3D { 0 };
+  int found =3D FALSE;
+  int i;
+  register unsigned char *p =3D *pattern;
+  for(i =3D 0; !found; i++) {
+    char c =3D *p++;
+    if(i >=3D KEYLEN)
+      return SETCHARSET_FAIL;
+    switch(state) {
+    case CURLFNM_PKW_INIT:
+      if(ISALPHA(c) && ISLOWER(c))
+        keyword[i] =3D c;
+      else if(c =3D=3D ':')
+        state =3D CURLFNM_PKW_DDOT;
+      else
+        return 0;
+      break;
+    case CURLFNM_PKW_DDOT:
+      if(c =3D=3D ']')
+        found =3D TRUE;
+      else
+        return SETCHARSET_FAIL;
+    }
+  }
+#undef KEYLEN
+
+  *pattern =3D p; /* move caller's pattern pointer */
+  if(strcmp(keyword, "digit") =3D=3D 0)
+    charset[CURLFNM_DIGIT] =3D 1;
+  else if(strcmp(keyword, "alnum") =3D=3D 0)
+    charset[CURLFNM_ALNUM] =3D 1;
+  else if(strcmp(keyword, "alpha") =3D=3D 0)
+    charset[CURLFNM_ALPHA] =3D 1;
+  else if(strcmp(keyword, "xdigit") =3D=3D 0)
+    charset[CURLFNM_XDIGIT] =3D 1;
+  else if(strcmp(keyword, "print") =3D=3D 0)
+    charset[CURLFNM_PRINT] =3D 1;
+  else if(strcmp(keyword, "graph") =3D=3D 0)
+    charset[CURLFNM_GRAPH] =3D 1;
+  else if(strcmp(keyword, "space") =3D=3D 0)
+    charset[CURLFNM_SPACE] =3D 1;
+  else if(strcmp(keyword, "blank") =3D=3D 0)
+    charset[CURLFNM_BLANK] =3D 1;
+  else if(strcmp(keyword, "upper") =3D=3D 0)
+    charset[CURLFNM_UPPER] =3D 1;
+  else if(strcmp(keyword, "lower") =3D=3D 0)
+    charset[CURLFNM_LOWER] =3D 1;
+  else
+    return SETCHARSET_FAIL;
+  return SETCHARSET_OK;
+}
+
+/* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern point=
er) */
+static int setcharset(unsigned char **p, unsigned char *charset)
+{
+  setcharset_state state =3D CURLFNM_SCHS_DEFAULT;
+  unsigned char rangestart =3D 0;
+  unsigned char lastchar   =3D 0;
+  bool something_found =3D FALSE;
+  register unsigned char c;
+  for(;;) {
+    c =3D **p;
+    switch(state){
+    case CURLFNM_SCHS_DEFAULT:
+      if(ISALNUM(c)) { /* ASCII value */
+        rangestart =3D c;
+        charset[c] =3D 1;
+        (*p)++;
+        state =3D CURLFNM_SCHS_MAYRANGE;
+        something_found =3D TRUE;
+      }
+      else if(c =3D=3D ']') {
+        if(something_found)
+          return SETCHARSET_OK;
+        else
+          something_found =3D TRUE;
+        state =3D CURLFNM_SCHS_RIGHTBR;
+        charset[c] =3D 1;
+        (*p)++;
+      }
+      else if(c =3D=3D '[') {
+        char c2 =3D *((*p)+1);
+        if(c2 =3D=3D ':') { /* there has to be a keyword */
+          (*p) +=3D 2;
+          if(parsekeyword(p, charset)) {
+            state =3D CURLFNM_SCHS_DEFAULT;
+          }
+          else
+            return SETCHARSET_FAIL;
+        }
+        else {
+          charset[c] =3D 1;
+          (*p)++;
+        }
+        something_found =3D TRUE;
+      }
+      else if(c =3D=3D '?' || c =3D=3D '*') {
+        something_found =3D TRUE;
+        charset[c] =3D 1;
+        (*p)++;
+      }
+      else if(c =3D=3D '^' || c =3D=3D '!') {
+        if(!something_found) {
+          if(charset[CURLFNM_NEGATE]) {
+            charset[c] =3D 1;
+            something_found =3D 1;
+          }
+          else
+            charset[CURLFNM_NEGATE] =3D 1; /* negate charset */
+        }
+        else
+          charset[c] =3D 1;
+        (*p)++;
+      }
+      else if(c =3D=3D '\\') {
+        c =3D *(++(*p));
+        if(ISPRINT((c))) {
+          something_found =3D TRUE;
+          state =3D CURLFNM_SCHS_MAYRANGE;
+          charset[c] =3D 1;
+          rangestart =3D c;
+          (*p)++;
+        }
+        else
+          return SETCHARSET_FAIL;
+      }
+      else if(c =3D=3D '\0') {
+        return SETCHARSET_FAIL;
+      }
+      else {
+        charset[c] =3D 1;
+        (*p)++;
+        something_found =3D TRUE;
+      }
+      break;
+    case CURLFNM_SCHS_MAYRANGE:
+      if(c =3D=3D '-'){
+        charset[c] =3D 1;
+        (*p)++;
+        lastchar =3D '-';
+        state =3D CURLFNM_SCHS_MAYRANGE2;
+      }
+      else if(c =3D=3D '[') {
+        state =3D CURLFNM_SCHS_DEFAULT;
+      }
+      else if(ISALNUM(c)) {
+        charset[c] =3D 1;
+        (*p)++;
+      }
+      else if(c =3D=3D '\\') {
+        c =3D *(++(*p));
+        if(isprint(c)) {
+          charset[c] =3D 1;
+          (*p)++;
+        }
+        else
+          return SETCHARSET_FAIL;
+      }
+      else if(c =3D=3D ']') {
+        return SETCHARSET_OK;
+      }
+      else
+        return SETCHARSET_FAIL;
+      break;
+    case CURLFNM_SCHS_MAYRANGE2:
+      if(c =3D=3D '\\') {
+        c =3D *(++(*p));
+        if(!ISPRINT(c))
+          return SETCHARSET_FAIL;
+      }
+      if(c =3D=3D ']') {
+        return SETCHARSET_OK;
+      }
+      else if(c =3D=3D '\\') {
+        c =3D *(++(*p));
+        if(ISPRINT(c)) {
+          charset[c] =3D 1;
+          state =3D CURLFNM_SCHS_DEFAULT;
+          (*p)++;
+        }
+        else
+          return SETCHARSET_FAIL;
+      }
+      if(c >=3D rangestart) {
+        if((ISLOWER(c) && ISLOWER(rangestart)) ||
+           (ISDIGIT(c) && ISDIGIT(rangestart)) ||
+           (ISUPPER(c) && ISUPPER(rangestart))) {
+          charset[lastchar] =3D 0;
+          rangestart++;
+          while(rangestart++ <=3D c)
+            charset[rangestart-1] =3D 1;
+          (*p)++;
+          state =3D CURLFNM_SCHS_DEFAULT;
+        }
+        else
+          return SETCHARSET_FAIL;
+      }
+      break;
+    case CURLFNM_SCHS_RIGHTBR:
+      if(c =3D=3D '[') {
+        state =3D CURLFNM_SCHS_RIGHTBRLEFTBR;
+        charset[c] =3D 1;
+        (*p)++;
+      }
+      else if(c =3D=3D ']') {
+        return SETCHARSET_OK;
+      }
+      else if(c =3D=3D '\0') {
+        return SETCHARSET_FAIL;
+      }
+      else if(ISPRINT(c)){
+        charset[c] =3D 1;
+        (*p)++;
+        state =3D CURLFNM_SCHS_DEFAULT;
+      }
+      else
+        return SETCHARSET_FAIL;
+      break;
+    case CURLFNM_SCHS_RIGHTBRLEFTBR:
+      if(c =3D=3D ']') {
+        return SETCHARSET_OK;
+      }
+      else {
+        state  =3D CURLFNM_SCHS_DEFAULT;
+        charset[c] =3D 1;
+        (*p)++;
+      }
+      break;
+    }
+  }
+  return SETCHARSET_FAIL;
+}
+
+static int loop(const unsigned char *pattern, const unsigned char *stri=
ng)
+{
+  loop_state state =3D CURLFNM_LOOP_DEFAULT;
+  register unsigned char *p =3D (unsigned char *)pattern;
+  register unsigned char *s =3D (unsigned char *)string;
+  unsigned char charset[CURLFNM_CHSET_SIZE] =3D { 0 };
+  int rc =3D 0;
+
+  for (;;) {
+    switch(state) {
+    case CURLFNM_LOOP_DEFAULT:
+      if(*p =3D=3D '*') {
+        while(*(p+1) =3D=3D '*') /* eliminate multiple stars */
+          p++;
+        if(*s =3D=3D '\0' && *(p+1) =3D=3D '\0')
+          return CURL_FNMATCH_MATCH;
+        rc =3D loop(p + 1, s); /* *.txt matches .txt <=3D> .txt matches=
 .txt */
+        if(rc =3D=3D CURL_FNMATCH_MATCH)
+          return CURL_FNMATCH_MATCH;
+        if(*s) /* let the star eat up one character */
+          s++;
+        else
+          return CURL_FNMATCH_NOMATCH;
+      }
+      else if(*p =3D=3D '?') {
+        if(ISPRINT(*s)) {
+          s++;
+          p++;
+        }
+        else if(*s =3D=3D '\0')
+          return CURL_FNMATCH_NOMATCH;
+        else
+          return CURL_FNMATCH_FAIL; /* cannot deal with other character=
 */
+      }
+      else if(*p =3D=3D '\0') {
+        if(*s =3D=3D '\0')
+          return CURL_FNMATCH_MATCH;
+        else
+          return CURL_FNMATCH_NOMATCH;
+      }
+      else if(*p =3D=3D '\\') {
+        state =3D CURLFNM_LOOP_BACKSLASH;
+        p++;
+      }
+      else if(*p =3D=3D '[') {
+        unsigned char *pp =3D p+1; /* cannot handle with pointer to reg=
ister */
+        if(setcharset(&pp, charset)) {
+          bool found =3D FALSE;
+          if(charset[(unsigned int)*s])
+            found =3D TRUE;
+          else if(charset[CURLFNM_ALNUM])
+            found =3D ISALNUM(*s);
+          else if(charset[CURLFNM_ALPHA])
+            found =3D ISALPHA(*s);
+          else if(charset[CURLFNM_DIGIT])
+            found =3D ISDIGIT(*s);
+          else if(charset[CURLFNM_XDIGIT])
+            found =3D ISXDIGIT(*s);
+          else if(charset[CURLFNM_PRINT])
+            found =3D ISPRINT(*s);
+          else if(charset[CURLFNM_SPACE])
+            found =3D ISSPACE(*s);
+          else if(charset[CURLFNM_UPPER])
+            found =3D ISUPPER(*s);
+          else if(charset[CURLFNM_LOWER])
+            found =3D ISLOWER(*s);
+          else if(charset[CURLFNM_BLANK])
+            found =3D ISBLANK(*s);
+          else if(charset[CURLFNM_GRAPH])
+            found =3D ISGRAPH(*s);
+
+          if(charset[CURLFNM_NEGATE])
+            found =3D !found;
+
+          if(found) {
+            p =3D pp+1;
+            s++;
+            memset(charset, 0, CURLFNM_CHSET_SIZE);
+          }
+          else
+            return CURL_FNMATCH_NOMATCH;
+        }
+        else
+          return CURL_FNMATCH_FAIL;
+      }
+      else {
+        if(*p++ !=3D *s++)
+          return CURL_FNMATCH_NOMATCH;
+      }
+      break;
+    case CURLFNM_LOOP_BACKSLASH:
+      if(ISPRINT(*p)) {
+        if(*p++ =3D=3D *s++)
+          state =3D CURLFNM_LOOP_DEFAULT;
+        else
+          return CURL_FNMATCH_NOMATCH;
+      }
+      else
+        return CURL_FNMATCH_FAIL;
+      break;
+    }
+  }
+}
+
+int Curl_fnmatch(const char *pattern, const char *string)
+{
+  if(!pattern || !string) {
+    return CURL_FNMATCH_FAIL;
+  }
+  return loop((unsigned char *)pattern, (unsigned char *)string);
+}
diff --git a/lib/curl_fnmatch.h b/lib/curl_fnmatch.h
new file mode 100644
index 0000000..3ffbc45
--- /dev/null
+++ b/lib/curl_fnmatch.h
@@ -0,0 +1,44 @@
+#ifndef HEADER_CURL_FNMATCH_H
+#define HEADER_CURL_FNMATCH_H
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#define CURL_FNMATCH_MATCH    0
+#define CURL_FNMATCH_NOMATCH  1
+#define CURL_FNMATCH_FAIL     2
+
+/* default pattern matching function
+ * =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+ * Implemented with recursive backtracking, if you want to use Curl_fnm=
atch,
+ * please note that there is not implemented UTF/UNICODE support.
+ *
+ * Implemented features:
+ * '?' notation, does not match UTF characters
+ * '*' can also work with UTF string
+ * [a-zA-Z0-9] enumeration support
+ *
+ * keywords: alnum, digit, xdigit, alpha, print, blank, lower, graph, s=
pace
+ *           and upper (use as "[[:alnum:]]")
+ */
+int Curl_fnmatch(const char *pattern, const char *string);
+
+#endif /* HEADER_CURL_FNMATCH_H */
diff --git a/lib/fileinfo.c b/lib/fileinfo.c
new file mode 100644
index 0000000..2a184f7
--- /dev/null
+++ b/lib/fileinfo.c
@@ -0,0 +1,66 @@
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2010, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#include <stdlib.h>
+#include "strdup.h"
+#include "fileinfo.h"
+
+struct curl_fileinfo *Curl_fileinfo_alloc(void)
+{
+  struct curl_fileinfo *tmp =3D malloc(sizeof(struct curl_fileinfo));
+  if(!tmp)
+    return NULL;
+  memset(tmp, 0, sizeof(struct curl_fileinfo));
+  return tmp;
+}
+
+void Curl_fileinfo_dtor(void *user, void *element)
+{
+  struct curl_fileinfo *finfo =3D element;
+  (void) user;
+  if(!finfo)
+    return;
+
+  if(finfo->b_data){
+    free(finfo->b_data);
+  }
+
+  free(finfo);
+}
+
+struct curl_fileinfo *Curl_fileinfo_dup(const struct curl_fileinfo *src=
)
+{
+  struct curl_fileinfo *ptr =3D malloc(sizeof(struct curl_fileinfo));
+  if(!ptr)
+    return NULL;
+  *ptr =3D *src;
+
+  ptr->b_data =3D malloc(src->b_size);
+  if(!ptr->b_data) {
+    free(ptr);
+    return NULL;
+  }
+  else {
+    memcpy(ptr->b_data, src->b_data, src->b_size);
+    return ptr;
+  }
+}
diff --git a/lib/fileinfo.h b/lib/fileinfo.h
new file mode 100644
index 0000000..b040ef4
--- /dev/null
+++ b/lib/fileinfo.h
@@ -0,0 +1,33 @@
+#ifndef __FILEINFO_H
+#define __FILEINFO_H
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2010, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#include <curl/curl.h>
+
+struct curl_fileinfo *Curl_fileinfo_alloc(void);
+
+void Curl_fileinfo_dtor(void *, void *);
+
+struct curl_fileinfo *Curl_fileinfo_dup(const struct curl_fileinfo *src=
);
+
+#endif /* __FILEINFO_H */
diff --git a/lib/ftp.c b/lib/ftp.c
index 95e2a12..afd15b4 100644
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -71,6 +71,8 @@
 #include "http.h" /* for HTTP proxy tunnel stuff */
 #include "socks.h"
 #include "ftp.h"
+#include "fileinfo.h"
+#include "ftplistparser.h"
 =
 #if defined(HAVE_KRB4) || defined(HAVE_GSSAPI)
 #include "krb4.h"
@@ -110,6 +112,7 @@
 #define ftp_pasv_verbose(a,b,c,d)  do { } while(0)
 #endif
 =
+void Curl_ftp_wc_data_dtor(void *ptr);
 /* Local API functions */
 static CURLcode ftp_sendquote(struct connectdata *conn,
                               struct curl_slist *quote);
@@ -144,6 +147,12 @@ static CURLcode ftp_doing(struct connectdata *conn,=
                           bool *dophase_done);
 static CURLcode ftp_setup_connection(struct connectdata * conn);
 =
+static CURLcode init_wc_data(struct connectdata *conn);
+static CURLcode wc_statemach(struct connectdata *conn);
+
+static CURLcode ftp_state_post_retr_size(struct connectdata *conn,
+                                         curl_off_t filesize);
+
 /* easy-to-use macro: */
 #define FTPSENDF(x,y,z)    if((result =3D Curl_ftpsendf(x,y,z)) !=3D CU=
RLE_OK) \
                               return result
@@ -1469,8 +1478,14 @@ static CURLcode ftp_state_quote(struct connectdat=
a *conn,
       if(ftp->transfer !=3D FTPTRANSFER_BODY)
         state(conn, FTP_STOP);
       else {
-        PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
-        state(conn, FTP_RETR_SIZE);
+        if(ftpc->known_filesize !=3D -1) {
+          Curl_pgrsSetDownloadSize(data, ftpc->known_filesize);
+          result =3D ftp_state_post_retr_size(conn, ftpc->known_filesiz=
e);
+        }
+        else {
+          PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
+          state(conn, FTP_RETR_SIZE);
+        }
       }
       break;
     case FTP_STOR_PREQUOTE:
@@ -2855,6 +2870,8 @@ static CURLcode ftp_init(struct connectdata *conn)=
   if(TRUE =3D=3D isBadFtpString(ftp->passwd))
     return CURLE_URL_MALFORMAT;
 =
+  conn->proto.ftpc.known_filesize =3D -1; /* unknown size for now */
+
   return CURLE_OK;
 }
 =
@@ -3018,6 +3035,13 @@ static CURLcode ftp_done(struct connectdata *conn=
, CURLcode status,
   if(ftpc->prevpath)
     free(ftpc->prevpath);
 =
+  if(data->set.wildcardmatch) {
+    if(data->set.chunk_end && ftpc->file) {
+      data->set.chunk_end(data->wildcard.customptr);
+    }
+    ftpc->known_filesize =3D -1;
+  }
+
   /* get the "raw" path */
   path =3D curl_easy_unescape(data, path_to_use, 0, NULL);
   if(!path) {
@@ -3445,6 +3469,221 @@ CURLcode ftp_perform(struct connectdata *conn,
   return result;
 }
 =
+void Curl_ftp_wc_data_dtor(void *ptr)
+{
+  struct ftp_wc_tmpdata *tmp =3D ptr;
+  if(tmp)
+    ftp_parselist_data_free(&tmp->parser);
+  Curl_safefree(tmp);
+}
+
+static CURLcode init_wc_data(struct connectdata *conn)
+{
+  char *last_slash;
+  char *path =3D conn->data->state.path;
+  struct WildcardData *wildcard =3D &(conn->data->wildcard);
+  CURLcode ret =3D CURLE_OK;
+  struct ftp_wc_tmpdata *ftp_tmp;
+
+  last_slash =3D strrchr(conn->data->state.path, '/');
+  if(last_slash) {
+    last_slash++;
+    if(last_slash[0] =3D=3D '\0') {
+      wildcard->state =3D CURLWC_CLEAN;
+      ret =3D ftp_parse_url_path(conn);
+      return ret;
+    }
+    else {
+      wildcard->pattern =3D strdup(last_slash);
+      if (!wildcard->pattern)
+        return CURLE_OUT_OF_MEMORY;
+      last_slash[0] =3D '\0'; /* cut file from path */
+    }
+  }
+  else { /* there is only 'wildcard pattern' or nothing */
+    if(path[0]) {
+      wildcard->pattern =3D strdup(path);
+      if (!wildcard->pattern)
+        return CURLE_OUT_OF_MEMORY;
+      path[0] =3D '\0';
+    }
+    else { /* only list */
+      conn->data->set.wildcardmatch =3D 0L;
+      ret =3D ftp_parse_url_path(conn);
+      return ret;
+    }
+  }
+
+  /* program continues only if URL is not ending with slash, allocate n=
eeded
+     resources for wildcard transfer */
+
+  /* allocate ftp protocol specific temporary wildcard data */
+  ftp_tmp =3D malloc(sizeof(struct ftp_wc_tmpdata));
+  if(!ftp_tmp) {
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* INITIALIZE parselist structure */
+  ftp_tmp->parser =3D ftp_parselist_data_alloc();
+  if(!ftp_tmp->parser)
+    return CURLE_OUT_OF_MEMORY;
+
+  wildcard->tmp =3D ftp_tmp; /* put it to the WildcardData tmp pointer =
*/
+  wildcard->tmp_dtor =3D Curl_ftp_wc_data_dtor;
+
+  /* wildcard does not support NOCWD option (assert it?) */
+  if(conn->data->set.ftp_filemethod =3D=3D FTPFILE_NOCWD)
+    conn->data->set.ftp_filemethod =3D FTPFILE_MULTICWD;
+
+  /* try to parse ftp url */
+  ret =3D ftp_parse_url_path(conn);
+  if(ret) {
+    return ret;
+  }
+
+  /* backup old write_function */
+  ftp_tmp->backup.write_function =3D conn->data->set.fwrite_func;
+  /* parsing write function (callback included directly from ftplistpar=
ser.c) */
+  conn->data->set.fwrite_func =3D ftp_parselist;
+  /* backup old file descriptor */
+  ftp_tmp->backup.file_descriptor =3D conn->data->set.out;
+  /* let the writefunc callback know what curl pointer is working with =
*/
+  conn->data->set.out =3D conn;
+
+  wildcard->path =3D strdup(conn->data->state.path);
+  if(!wildcard->path) {
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  infof(conn->data, "Wildcard - Parsing started\n");
+  return CURLE_OK;
+}
+
+static CURLcode wc_statemach(struct connectdata *conn)
+{
+  struct ftp_conn *ftpc =3D &conn->proto.ftpc;
+  struct WildcardData *wildcard =3D &(conn->data->wildcard);
+  struct ftp_wc_tmpdata *ftp_tmp =3D wildcard->tmp;
+  char *tmp_path;
+  CURLcode ret =3D CURLE_OK;
+  long userresponse =3D 0;
+  switch (wildcard->state) {
+  case CURLWC_INIT:
+    ret =3D init_wc_data(conn);
+    if(wildcard->state =3D=3D CURLWC_CLEAN)
+      /* only listing! */
+      break;
+    else
+      wildcard->state =3D ret ? CURLWC_ERROR : CURLWC_MATCHING;
+    break;
+
+  case CURLWC_MATCHING:
+    /* In this state is LIST response successfully parsed, so lets rest=
ore
+       previous WRITEFUNCTION callback and WRITEDATA pointer */
+    ftp_tmp =3D wildcard->tmp;
+    conn->data->set.fwrite_func =3D ftp_tmp->backup.write_function;
+    conn->data->set.out =3D ftp_tmp->backup.file_descriptor;
+    wildcard->state =3D CURLWC_DOWNLOADING;
+
+    if(ftp_parselist_geterror(ftp_tmp->parser)) {
+      /* error found in LIST parsing */
+      wildcard->state =3D CURLWC_CLEAN;
+      return wc_statemach(conn);
+    }
+    else if(wildcard->filelist->size =3D=3D 0) {
+      /* no corresponding file */
+      wildcard->state =3D CURLWC_CLEAN;
+      return CURLE_REMOTE_FILE_NOT_FOUND;
+    }
+    ret =3D wc_statemach(conn);
+    break;
+
+  case CURLWC_DOWNLOADING: {
+    /* filelist has at least one file, lets get first one */
+    struct curl_fileinfo *finfo =3D wildcard->filelist->head->ptr;
+    tmp_path =3D malloc(strlen(conn->data->state.path) +
+                      strlen(finfo->filename) + 1);
+    if(!tmp_path) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+
+    tmp_path[0] =3D 0;
+    /* make full path to matched file */
+    strcat(tmp_path, wildcard->path);
+    strcat(tmp_path, finfo->filename);
+    /* switch default "state.pathbuffer" and tmp_path, good to see
+       ftp_parse_url_path function to understand this trick */
+    if(conn->data->state.pathbuffer)
+      free(conn->data->state.pathbuffer);
+    conn->data->state.pathbuffer =3D tmp_path;
+    conn->data->state.path =3D tmp_path;
+
+    infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename);=
+    if(conn->data->set.chunk_bgn) {
+      userresponse =3D conn->data->set.chunk_bgn(
+          finfo, wildcard->customptr, (int)wildcard->filelist->size);
+      switch(userresponse) {
+      case CURL_CHUNK_BGN_FUNC_SKIP:
+        infof(conn->data, "Wildcard - \"%s\" skipped by user\n",
+              finfo->filename);
+        wildcard->state =3D CURLWC_SKIP;
+        return wc_statemach(conn);
+        break;
+      case CURL_CHUNK_BGN_FUNC_FAIL:
+        return CURLE_CHUNK_FAILED;
+        break;
+      }
+    }
+
+    if(finfo->filetype !=3D CURLFILETYPE_FILE) {
+      wildcard->state =3D CURLWC_SKIP;
+      return wc_statemach(conn);
+    }
+
+    if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE)
+      ftpc->known_filesize =3D finfo->size;
+
+    ret =3D ftp_parse_url_path(conn);
+    if(ret) {
+      return ret;
+    }
+
+    /* we don't need the Curl_fileinfo of first file anymore */
+    Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NUL=
L);
+
+    if(wildcard->filelist->size =3D=3D 0) { /* remains only one file to=
 down. */
+      wildcard->state =3D CURLWC_CLEAN;
+      /* after that will be ftp_do called once again and no transfer
+         will be done because of CURLWC_CLEAN state */
+      return CURLE_OK;
+    }
+  } break;
+
+  case CURLWC_SKIP: {
+    if(conn->data->set.chunk_end)
+      conn->data->set.chunk_end(conn->data->wildcard.customptr);
+    Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NUL=
L);
+    wildcard->state =3D (wildcard->filelist->size =3D=3D 0) ?
+                      CURLWC_CLEAN : CURLWC_DOWNLOADING;
+    ret =3D wc_statemach(conn);
+  } break;
+
+  case CURLWC_CLEAN:
+    ret =3D CURLE_OK;
+    if(ftp_tmp) {
+      ret =3D ftp_parselist_geterror(ftp_tmp->parser);
+    }
+    wildcard->state =3D ret ? CURLWC_ERROR : CURLWC_DONE;
+    break;
+
+  case CURLWC_DONE:
+  case CURLWC_ERROR:
+    break;
+  }
+
+  return ret;
+}
+
 /**********************************************************************=
*
  *
  * ftp_do()
@@ -3471,9 +3710,21 @@ static CURLcode ftp_do(struct connectdata *conn, =
bool *done)
   if(retcode)
     return retcode;
 =
-  retcode =3D ftp_parse_url_path(conn);
-  if(retcode)
-    return retcode;
+  if(conn->data->set.wildcardmatch) {
+    retcode =3D wc_statemach(conn);
+    if(conn->data->wildcard.state =3D=3D CURLWC_SKIP ||
+      conn->data->wildcard.state =3D=3D CURLWC_DONE) {
+      /* do not call ftp_regular_transfer */
+      return CURLE_OK;
+    }
+    if(retcode) /* error, loop or skipping the file */
+      return retcode;
+  }
+  else { /* no wildcard FSM needed */
+    retcode =3D ftp_parse_url_path(conn);
+    if(retcode)
+      return retcode;
+  }
 =
   retcode =3D ftp_regular_transfer(conn, done);
 =
diff --git a/lib/ftp.h b/lib/ftp.h
index 7a4f89e..d8ef348 100644
--- a/lib/ftp.h
+++ b/lib/ftp.h
@@ -79,6 +79,17 @@ typedef enum {
   FTP_LAST  /* never used */
 } ftpstate;
 =
+struct ftp_parselist_data; /* defined later in ftplistparser.c */
+
+struct ftp_wc_tmpdata {
+  struct ftp_parselist_data *parser;
+
+  struct {
+    curl_write_callback write_function;
+    FILE *file_descriptor;
+  } backup;
+};
+
 typedef enum {
   FTPFILE_MULTICWD  =3D 1, /* as defined by RFC1738 */
   FTPFILE_NOCWD     =3D 2, /* use SIZE / RETR / STOR on the full path *=
/
@@ -135,6 +146,8 @@ struct ftp_conn {
   int count3; /* general purpose counter for the state machine */
   ftpstate state; /* always use ftp.c:state() to change state! */
   char * server_os;     /* The target server operating system. */
+  curl_off_t known_filesize; /* file size is different from -1, if wild=
card
+                                LIST parsing was done and wc_statemach =
set it */
 };
 =
 #endif /* HEADER_CURL_FTP_H */
diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c
new file mode 100644
index 0000000..9d42e8f
--- /dev/null
+++ b/lib/ftplistparser.c
@@ -0,0 +1,1009 @@
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+/**
+ * Now implemented:
+ *
+ * 1) UNIX version 1
+ * drwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog
+ * 2) UNIX version 2
+ * drwxr-xr-x 1 user01 ftp  512 Jan 29 1997  prog
+ * 3) UNIX version 3
+ * drwxr-xr-x 1      1   1  512 Jan 29 23:32 prog
+ * 4) UNIX symlink
+ * lrwxr-xr-x 1 user01 ftp  512 Jan 29 23:32 prog -> prog2000
+ * 5) DOS style
+ * 01-29-97 11:32PM <DIR> prog
+ */
+
+#include <time.h>
+
+#include "ftplistparser.h"
+#include "curl_fnmatch.h"
+
+#include "urldata.h"
+#include "ftp.h"
+#include "fileinfo.h"
+#include "llist.h"
+#include "strtoofft.h"
+#include "rawstr.h"
+#include "ftp.h"
+
+/* allocs buffer which will contain one line of LIST command response *=
/
+#define FTP_BUFFER_ALLOCSIZE 160
+
+typedef enum {
+  PL_UNIX_FILETYPE =3D 0,
+  PL_UNIX_PERMISSION,
+  PL_UNIX_HLINKS,
+  PL_UNIX_USER,
+  PL_UNIX_GROUP,
+  PL_UNIX_SIZE,
+  PL_UNIX_TIME,
+  PL_UNIX_FILENAME,
+  PL_UNIX_SYMLINK
+} pl_unix_mainstate;
+
+typedef union {
+  enum {
+    PL_UNIX_HLINKS_PRESPACE =3D 0,
+    PL_UNIX_HLINKS_NUMBER
+  } hlinks;
+
+  enum {
+    PL_UNIX_USER_PRESPACE =3D 0,
+    PL_UNIX_USER_PARSING
+  } user;
+
+  enum {
+    PL_UNIX_GROUP_PRESPACE =3D 0,
+    PL_UNIX_GROUP_NAME
+  } group;
+
+  enum {
+    PL_UNIX_SIZE_PRESPACE =3D 0,
+    PL_UNIX_SIZE_NUMBER
+  } size;
+
+  enum {
+    PL_UNIX_TIME_PREPART1 =3D 0,
+    PL_UNIX_TIME_PART1,
+    PL_UNIX_TIME_PREPART2,
+    PL_UNIX_TIME_PART2,
+    PL_UNIX_TIME_PREPART3,
+    PL_UNIX_TIME_PART3
+  } time;
+
+  enum {
+    PL_UNIX_FILENAME_PRESPACE =3D 0,
+    PL_UNIX_FILENAME_NAME,
+    PL_UNIX_FILENAME_WINDOWSEOL
+  } filename;
+
+  enum {
+    PL_UNIX_SYMLINK_PRESPACE =3D 0,
+    PL_UNIX_SYMLINK_NAME,
+    PL_UNIX_SYMLINK_PRETARGET1,
+    PL_UNIX_SYMLINK_PRETARGET2,
+    PL_UNIX_SYMLINK_PRETARGET3,
+    PL_UNIX_SYMLINK_PRETARGET4,
+    PL_UNIX_SYMLINK_TARGET,
+    PL_UNIX_SYMLINK_WINDOWSEOL
+  } symlink;
+} pl_unix_substate;
+
+typedef enum {
+  PL_WINNT_DATE =3D 0,
+  PL_WINNT_TIME,
+  PL_WINNT_DIRORSIZE,
+  PL_WINNT_FILENAME
+} pl_winNT_mainstate;
+
+typedef union {
+  enum {
+    PL_WINNT_TIME_PRESPACE =3D 0,
+    PL_WINNT_TIME_TIME
+  } time;
+  enum {
+    PL_WINNT_DIRORSIZE_PRESPACE =3D 0,
+    PL_WINNT_DIRORSIZE_CONTENT
+  } dirorsize;
+  enum {
+    PL_WINNT_FILENAME_PRESPACE =3D 0,
+    PL_WINNT_FILENAME_CONTENT,
+    PL_WINNT_FILENAME_WINEOL
+  } filename;
+} pl_winNT_substate;
+
+/* This struct is used in wildcard downloading - for parsing LIST respo=
nse */
+struct ftp_parselist_data {
+  enum {
+    OS_TYPE_UNKNOWN =3D 0,
+    OS_TYPE_UNIX,
+    OS_TYPE_WIN_NT
+  } os_type;
+
+  union {
+    struct {
+      pl_unix_mainstate main;
+      pl_unix_substate sub;
+    } UNIX;
+
+    struct {
+      pl_winNT_mainstate main;
+      pl_winNT_substate sub;
+    } NT;
+  } state;
+
+  struct {
+    char *buffer;
+    size_t bufferlength; /* how many bytes is allocated at *buffer */
+    size_t bufferin; /* how many bytes is in buffer */
+  } tmpdata;
+
+  struct {
+    curl_write_callback old_fwritefunc;
+    FILE *old_file_descriptor;
+  } backup;
+
+  CURLcode error;
+  struct curl_fileinfo *file_data;
+  unsigned int item_length;
+  size_t item_offset;
+  struct {
+    size_t filename;
+    size_t user;
+    size_t group;
+    size_t time;
+    size_t perm;
+    size_t symlink_target;
+  } offsets;
+};
+
+struct ftp_parselist_data *ftp_parselist_data_alloc(void)
+{
+  struct ftp_parselist_data *parselist_data =3D
+      malloc(sizeof(struct ftp_parselist_data));
+  if(!parselist_data)
+    return ZERO_NULL;
+  memset(parselist_data, 0, sizeof(struct ftp_parselist_data));
+  return parselist_data;
+}
+
+
+void ftp_parselist_data_free(struct ftp_parselist_data **pl_data)
+{
+  if(*pl_data)
+    free(*pl_data);
+  *pl_data =3D NULL;
+}
+
+
+CURLcode ftp_parselist_geterror(struct ftp_parselist_data *pl_data)
+{
+  return pl_data->error;
+}
+
+
+#define FTP_LP_MALFORMATED_PERM 0x01000000
+
+static int ftp_pl_get_permission(const char *str)
+{
+  int permissions =3D 0;
+  /* USER */
+  if(str[0] =3D=3D 'r')
+    permissions |=3D 1 << 8;
+  else if(str[0] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+  if(str[1] =3D=3D 'w')
+    permissions |=3D 1 << 7;
+  else if(str[1] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+
+  if(str[2] =3D=3D 'x')
+    permissions |=3D 1 << 6;
+  else if(str[2] =3D=3D 's') {
+    permissions |=3D 1 << 6;
+    permissions |=3D 1 << 11;
+  }
+  else if(str[2] =3D=3D 'S')
+    permissions |=3D 1 << 11;
+  else if(str[2] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+  /* GROUP */
+  if(str[3] =3D=3D 'r')
+    permissions |=3D 1 << 5;
+  else if(str[3] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+  if(str[4] =3D=3D 'w')
+    permissions |=3D 1 << 4;
+  else if(str[4] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+  if(str[5] =3D=3D 'x')
+    permissions |=3D 1 << 3;
+  else if(str[5] =3D=3D 's') {
+    permissions |=3D 1 << 3;
+    permissions |=3D 1 << 10;
+  }
+  else if(str[5] =3D=3D 'S')
+    permissions |=3D 1 << 10;
+  else if(str[5] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+  /* others */
+  if(str[6] =3D=3D 'r')
+    permissions |=3D 1 << 2;
+  else if(str[6] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+  if(str[7] =3D=3D 'w')
+    permissions |=3D 1 << 1;
+  else if(str[7] !=3D '-')
+      permissions |=3D FTP_LP_MALFORMATED_PERM;
+  if(str[8] =3D=3D 'x')
+    permissions |=3D 1;
+  else if(str[8] =3D=3D 't') {
+    permissions |=3D 1;
+    permissions |=3D 1 << 9;
+  }
+  else if(str[8] =3D=3D 'T')
+    permissions |=3D 1 << 9;
+  else if(str[8] !=3D '-')
+    permissions |=3D FTP_LP_MALFORMATED_PERM;
+
+  return permissions;
+}
+
+static void PL_ERROR(struct connectdata *conn, CURLcode err)
+{
+  struct ftp_wc_tmpdata *tmpdata =3D conn->data->wildcard.tmp;
+  struct ftp_parselist_data *parser =3D tmpdata->parser;
+  if(parser->file_data)
+    Curl_fileinfo_dtor(NULL, parser->file_data);
+  parser->file_data =3D NULL;
+  parser->error =3D err;
+}
+
+static bool ftp_pl_gettime(struct ftp_parselist_data *parser, char *str=
ing)
+{
+  (void)parser;
+  (void)string;
+  /* TODO
+   * There could be possible parse timestamp from server. Leaving unimp=
lemented
+   * for now.
+   * If you want implement this, please add CURLFINFOFLAG_KNOWN_TIME fl=
ag to
+   * parser->file_data->flags
+   *
+   * Ftp servers are giving usually these formats:
+   *  Apr 11  1998 (unknown time.. set it to 00:00:00?)
+   *  Apr 11 12:21 (unknown year -> set it to NOW() time?)
+   *  08-05-09  02:49PM  (ms-dos format)
+   *  20100421092538 -> for MLST/MLSD response
+   */
+
+  return FALSE;
+}
+
+static CURLcode ftp_pl_insert_finfo(struct connectdata *conn,
+                                    struct curl_fileinfo *finfo)
+{
+  curl_fnmatch_callback compare;
+  struct WildcardData *wc =3D &conn->data->wildcard;
+  struct ftp_wc_tmpdata *tmpdata =3D wc->tmp;
+  struct curl_llist *llist =3D wc->filelist;
+  struct ftp_parselist_data *parser =3D tmpdata->parser;
+  bool add =3D TRUE;
+
+  /* move finfo pointers to b_data */
+  char *str =3D finfo->b_data;
+  finfo->filename       =3D str + parser->offsets.filename;
+  finfo->strings.group  =3D parser->offsets.group ?
+                          str + parser->offsets.group : NULL;
+  finfo->strings.perm   =3D parser->offsets.perm ?
+                          str + parser->offsets.perm : NULL;
+  finfo->strings.target =3D parser->offsets.symlink_target ?
+                          str + parser->offsets.symlink_target : NULL;
+  finfo->strings.time   =3D str + parser->offsets.time;
+  finfo->strings.user   =3D parser->offsets.user ?
+                          str + parser->offsets.user : NULL;
+
+  /* get correct fnmatch callback */
+  compare =3D conn->data->set.fnmatch;
+  if(!compare)
+    compare =3D Curl_fnmatch;
+
+  /* filter pattern-corresponding filenames */
+  if(compare(wc->pattern, finfo->filename) =3D=3D 0) {
+    /* discard symlink which is containing multiple " -> " */
+    if((finfo->filetype =3D=3D CURLFILETYPE_SYMLINK) &&
+       (strstr(finfo->strings.target, " -> "))) {
+      add =3D FALSE;
+    }
+  }
+  else {
+    add =3D FALSE;
+  }
+
+  if(add) {
+    if(!Curl_llist_insert_next(llist, llist->tail, finfo)) {
+      Curl_fileinfo_dtor(NULL, finfo);
+      tmpdata->parser->file_data =3D NULL;
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+  else {
+    Curl_fileinfo_dtor(NULL, finfo);
+  }
+
+  tmpdata->parser->file_data =3D NULL;
+  return CURLE_OK;
+}
+
+size_t ftp_parselist(char *buffer, size_t size, size_t nmemb, void *con=
nptr)
+{
+  size_t bufflen =3D size*nmemb;
+  struct connectdata *conn =3D (struct connectdata *)connptr;
+  struct ftp_wc_tmpdata *tmpdata =3D conn->data->wildcard.tmp;
+  struct ftp_parselist_data *parser =3D tmpdata->parser;
+  struct curl_fileinfo *finfo;
+  unsigned long i =3D 0;
+  CURLcode rc;
+
+  if(parser->error) { /* error in previous call */
+    /* scenario:
+     * 1. call =3D> OK..
+     * 2. call =3D> OUT_OF_MEMORY (or other error)
+     * 3. (last) call =3D> is skipped RIGHT HERE and the error is hadle=
d later
+     *    in wc_statemach()
+     */
+    return bufflen;
+  }
+
+  if(parser->os_type =3D=3D OS_TYPE_UNKNOWN && bufflen > 0) {
+    /* considering info about FILE response format */
+    parser->os_type =3D (buffer[0] >=3D '0' && buffer[0] <=3D '9') ?
+                       OS_TYPE_WIN_NT : OS_TYPE_UNIX;
+  }
+
+  while(i < bufflen) { /* FSM */
+
+    char c =3D buffer[i];
+    if(!parser->file_data) { /* tmp file data is not allocated yet */
+      parser->file_data =3D Curl_fileinfo_alloc();
+      if(!parser->file_data) {
+        parser->error =3D CURLE_OUT_OF_MEMORY;
+        return bufflen;
+      }
+      parser->file_data->b_data =3D malloc(FTP_BUFFER_ALLOCSIZE);
+      if(!parser->file_data->b_data) {
+        PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
+        return bufflen;
+      }
+      parser->file_data->b_size =3D FTP_BUFFER_ALLOCSIZE;
+      parser->item_offset =3D 0;
+      parser->item_length =3D 0;
+    }
+
+    finfo =3D parser->file_data;
+    finfo->b_data[finfo->b_used++] =3D buffer[i];
+
+    if(finfo->b_used >=3D finfo->b_size - 1) {
+      /* if it is important, extend buffer space for file data */
+      char *tmp =3D realloc(finfo->b_data,
+                          finfo->b_size + FTP_BUFFER_ALLOCSIZE);
+      if(tmp) {
+        finfo->b_size +=3D FTP_BUFFER_ALLOCSIZE;
+        finfo->b_data =3D tmp;
+      }
+      else {
+        Curl_fileinfo_dtor(NULL, parser->file_data);
+        parser->file_data =3D NULL;
+      }
+    }
+
+    switch (parser->os_type) {
+    case OS_TYPE_UNIX:
+      switch (parser->state.UNIX.main) {
+      case PL_UNIX_FILETYPE:
+        switch (c) {
+        case '-':
+          finfo->filetype =3D CURLFILETYPE_FILE;
+          break;
+        case 'd':
+          finfo->filetype =3D CURLFILETYPE_DIRECTORY;
+          break;
+        case 'l':
+          finfo->filetype =3D CURLFILETYPE_SYMLINK;
+          break;
+        case 'p':
+          finfo->filetype =3D CURLFILETYPE_NAMEDPIPE;
+          break;
+        case 's':
+          finfo->filetype =3D CURLFILETYPE_SOCKET;
+          break;
+        case 'c':
+          finfo->filetype =3D CURLFILETYPE_DEVICE_CHAR;
+          break;
+        case 'b':
+          finfo->filetype =3D CURLFILETYPE_DEVICE_BLOCK;
+          break;
+        case 'D':
+          finfo->filetype =3D CURLFILETYPE_DOOR;
+          break;
+        default:
+          PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+          return bufflen;
+          break;
+        }
+        parser->state.UNIX.main =3D PL_UNIX_PERMISSION;
+        parser->item_length =3D 0;
+        parser->item_offset =3D 1;
+        break;
+      case PL_UNIX_PERMISSION:
+        parser->item_length++;
+        if(parser->item_length <=3D 9) {
+          if(!strchr("rwx-tTsS", c)) {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+        }
+        else if(parser->item_length =3D=3D 10) {
+          int32_t perm;
+          if(c !=3D ' ') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          finfo->b_data[10] =3D 0; /* terminate permissions */
+          perm =3D ftp_pl_get_permission(finfo->b_data + parser->item_o=
ffset);
+          if(perm & FTP_LP_MALFORMATED_PERM) {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          parser->file_data->flags |=3D CURLFINFOFLAG_KNOWN_PERM;
+          parser->file_data->perm =3D perm;
+          parser->offsets.perm =3D parser->item_offset;
+
+          parser->item_length =3D 0;
+          parser->state.UNIX.main =3D PL_UNIX_HLINKS;
+          parser->state.UNIX.sub.hlinks =3D PL_UNIX_HLINKS_PRESPACE;
+        }
+        break;
+      case PL_UNIX_HLINKS:
+        switch(parser->state.UNIX.sub.hlinks) {
+        case PL_UNIX_HLINKS_PRESPACE:
+          if(c !=3D ' ') {
+            if(c >=3D '0' && c <=3D '9') {
+              parser->item_offset =3D finfo->b_used - 1;
+              parser->item_length =3D 1;
+              parser->state.UNIX.sub.hlinks =3D PL_UNIX_HLINKS_NUMBER;
+            }
+            else {
+              PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+              return bufflen;
+            }
+          }
+          break;
+        case PL_UNIX_HLINKS_NUMBER:
+          parser->item_length ++;
+          if(c =3D=3D ' ') {
+            char *p;
+            long int hlinks;
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            hlinks =3D strtol(finfo->b_data + parser->item_offset, &p, =
10);
+            if(p[0] =3D=3D '\0' && hlinks !=3D LONG_MAX && hlinks !=3D =
LONG_MIN) {
+              parser->file_data->flags |=3D CURLFINFOFLAG_KNOWN_HLINKCO=
UNT;
+              parser->file_data->hardlinks =3D hlinks;
+            }
+            parser->item_length =3D 0;
+            parser->item_offset =3D 0;
+            parser->state.UNIX.main =3D PL_UNIX_USER;
+            parser->state.UNIX.sub.user =3D PL_UNIX_USER_PRESPACE;
+          }
+          else if(c < '0' || c > '9') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      case PL_UNIX_USER:
+        switch(parser->state.UNIX.sub.user) {
+        case PL_UNIX_USER_PRESPACE:
+          if(c !=3D ' ') {
+            parser->item_offset =3D finfo->b_used - 1;
+            parser->item_length =3D 1;
+            parser->state.UNIX.sub.user =3D PL_UNIX_USER_PARSING;
+          }
+          break;
+        case PL_UNIX_USER_PARSING:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            parser->offsets.user =3D parser->item_offset;
+            parser->state.UNIX.main =3D PL_UNIX_GROUP;
+            parser->state.UNIX.sub.group =3D PL_UNIX_GROUP_PRESPACE;
+            parser->item_offset =3D 0;
+            parser->item_length =3D 0;
+          }
+          break;
+        }
+        break;
+      case PL_UNIX_GROUP:
+        switch(parser->state.UNIX.sub.group) {
+        case PL_UNIX_GROUP_PRESPACE:
+          if(c !=3D ' ') {
+            parser->item_offset =3D finfo->b_used - 1;
+            parser->item_length =3D 1;
+            parser->state.UNIX.sub.group =3D PL_UNIX_GROUP_NAME;
+          }
+          break;
+        case PL_UNIX_GROUP_NAME:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            parser->offsets.group =3D parser->item_offset;
+            parser->state.UNIX.main =3D PL_UNIX_SIZE;
+            parser->state.UNIX.sub.group =3D PL_UNIX_SIZE_PRESPACE;
+            parser->item_offset =3D 0;
+            parser->item_length =3D 0;
+          }
+          break;
+        }
+        break;
+      case PL_UNIX_SIZE:
+        switch(parser->state.UNIX.sub.size) {
+        case PL_UNIX_SIZE_PRESPACE:
+          if(c !=3D ' ') {
+            if(c >=3D '0' && c <=3D '9') {
+              parser->item_offset =3D finfo->b_used - 1;
+              parser->item_length =3D 1;
+              parser->state.UNIX.sub.size =3D PL_UNIX_SIZE_NUMBER;
+            }
+            else {
+              PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+              return bufflen;
+            }
+          }
+          break;
+        case PL_UNIX_SIZE_NUMBER:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            char *p;
+            curl_off_t fsize;
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            fsize =3D curlx_strtoofft(finfo->b_data+parser->item_offset=
, &p, 10);
+            if(p[0] =3D=3D '\0' && fsize !=3D CURL_LLONG_MAX &&
+                               fsize !=3D CURL_LLONG_MIN) {
+              parser->file_data->flags |=3D CURLFINFOFLAG_KNOWN_SIZE;
+              parser->file_data->size =3D fsize;
+            }
+            parser->item_length =3D 0;
+            parser->item_offset =3D 0;
+            parser->state.UNIX.main =3D PL_UNIX_TIME;
+            parser->state.UNIX.sub.time =3D PL_UNIX_TIME_PREPART1;
+          }
+          else if (!ISDIGIT(c)) {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      case PL_UNIX_TIME:
+        switch(parser->state.UNIX.sub.time) {
+        case PL_UNIX_TIME_PREPART1:
+          if(c !=3D ' ') {
+            if(ISALNUM(c)) {
+              parser->item_offset =3D finfo->b_used -1;
+              parser->item_length =3D 1;
+              parser->state.UNIX.sub.time =3D PL_UNIX_TIME_PART1;
+            }
+            else {
+              PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+              return bufflen;
+            }
+          }
+          break;
+        case PL_UNIX_TIME_PART1:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            parser->state.UNIX.sub.size =3D PL_UNIX_TIME_PREPART2;
+          }
+          else if(!ISALNUM(c) && c !=3D '.') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        case PL_UNIX_TIME_PREPART2:
+          parser->item_length++;
+          if(c !=3D ' ') {
+            if(ISALNUM(c)) {
+              parser->state.UNIX.sub.time =3D PL_UNIX_TIME_PART2;
+            }
+            else {
+              PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+              return bufflen;
+            }
+          }
+          break;
+        case PL_UNIX_TIME_PART2:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            parser->state.UNIX.sub.size =3D PL_UNIX_TIME_PREPART3;
+          }
+          else if(!ISALNUM(c) && c !=3D '.') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        case PL_UNIX_TIME_PREPART3:
+          parser->item_length++;
+          if(c !=3D ' ') {
+            if(ISALNUM(c)) {
+              parser->state.UNIX.sub.time =3D PL_UNIX_TIME_PART3;
+            }
+            else {
+              PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+              return bufflen;
+            }
+          }
+          break;
+        case PL_UNIX_TIME_PART3:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            finfo->b_data[parser->item_offset + parser->item_length -1]=
 =3D 0;
+            parser->offsets.time =3D parser->item_offset;
+            if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offs=
et)) {
+              parser->file_data->flags |=3D CURLFINFOFLAG_KNOWN_TIME;
+            }
+            if(finfo->filetype =3D=3D CURLFILETYPE_SYMLINK) {
+              parser->state.UNIX.main =3D PL_UNIX_SYMLINK;
+              parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_PRESPA=
CE;
+            }
+            else {
+              parser->state.UNIX.main =3D PL_UNIX_FILENAME;
+              parser->state.UNIX.sub.filename =3D PL_UNIX_FILENAME_PRES=
PACE;
+            }
+          }
+          else if(!ISALNUM(c) && c !=3D '.' && c !=3D ':') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      case PL_UNIX_FILENAME:
+        switch(parser->state.UNIX.sub.filename) {
+        case PL_UNIX_FILENAME_PRESPACE:
+          if(c !=3D ' ') {
+            parser->item_offset =3D finfo->b_used - 1;
+            parser->item_length =3D 1;
+            parser->state.UNIX.sub.filename =3D PL_UNIX_FILENAME_NAME;
+          }
+          break;
+        case PL_UNIX_FILENAME_NAME:
+          parser->item_length++;
+          if(c =3D=3D '\r') {
+            parser->item_length--;
+            parser->state.UNIX.sub.filename =3D PL_UNIX_FILENAME_WINDOW=
SEOL;
+          }
+          else if(c =3D=3D '\n') {
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            parser->offsets.filename =3D parser->item_offset;
+            parser->state.UNIX.main =3D PL_UNIX_FILETYPE;
+            rc =3D ftp_pl_insert_finfo(conn, finfo);
+            if(rc) {
+              PL_ERROR(conn, rc);
+              return bufflen;
+            }
+          }
+          break;
+        case PL_UNIX_FILENAME_WINDOWSEOL:
+          if(c =3D=3D '\n') {
+            finfo->b_data[parser->item_offset + parser->item_length] =3D=
 0;
+            parser->offsets.filename =3D parser->item_offset;
+            parser->state.UNIX.main =3D PL_UNIX_FILETYPE;
+            rc =3D ftp_pl_insert_finfo(conn, finfo);
+            if(rc) {
+              PL_ERROR(conn, rc);
+              return bufflen;
+            }
+          }
+          else {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      case PL_UNIX_SYMLINK:
+        switch(parser->state.UNIX.sub.symlink) {
+        case PL_UNIX_SYMLINK_PRESPACE:
+          if(c !=3D ' ') {
+            parser->item_offset =3D finfo->b_used - 1;
+            parser->item_length =3D 1;
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_NAME;
+          }
+          break;
+        case PL_UNIX_SYMLINK_NAME:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_PRETARGE=
T1;
+          }
+          else if(c =3D=3D '\r' || c =3D=3D '\n') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        case PL_UNIX_SYMLINK_PRETARGET1:
+          parser->item_length++;
+          if(c =3D=3D '-') {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_PRETARGE=
T2;
+          }
+          else if(c =3D=3D '\r' || c =3D=3D '\n') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          else {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_NAME;
+          }
+          break;
+        case PL_UNIX_SYMLINK_PRETARGET2:
+          parser->item_length++;
+          if(c =3D=3D '>') {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_PRETARGE=
T3;
+          }
+          else if(c =3D=3D '\r' || c =3D=3D '\n') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          else {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_NAME;
+          }
+          break;
+        case PL_UNIX_SYMLINK_PRETARGET3:
+          parser->item_length++;
+          if(c =3D=3D ' ') {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_PRETARGE=
T4;
+            /* now place where is symlink following */
+            finfo->b_data[parser->item_offset + parser->item_length - 4=
] =3D 0;
+            parser->offsets.filename =3D parser->item_offset;
+            parser->item_length =3D 0;
+            parser->item_offset =3D 0;
+          }
+          else if(c =3D=3D '\r' || c =3D=3D '\n') {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          else {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_NAME;
+          }
+          break;
+        case PL_UNIX_SYMLINK_PRETARGET4:
+          if(c !=3D '\r' && c !=3D '\n') {
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_TARGET;
+            parser->item_offset =3D finfo->b_used - 1;
+            parser->item_length =3D 1;
+          }
+          else {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        case PL_UNIX_SYMLINK_TARGET:
+          parser->item_length ++;
+          if(c =3D=3D '\r') {
+            parser->item_length --;
+            parser->state.UNIX.sub.symlink =3D PL_UNIX_SYMLINK_WINDOWSE=
OL;
+          }
+          else if(c =3D=3D '\n') {
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            parser->offsets.symlink_target =3D parser->item_offset;
+            rc =3D ftp_pl_insert_finfo(conn, finfo);
+            if(rc) {
+              PL_ERROR(conn, rc);
+              return bufflen;
+            }
+            parser->state.UNIX.main =3D PL_UNIX_FILETYPE;
+          }
+          break;
+        case PL_UNIX_SYMLINK_WINDOWSEOL:
+          if(c =3D=3D '\n') {
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            parser->offsets.symlink_target =3D parser->item_offset;
+            rc =3D ftp_pl_insert_finfo(conn, finfo);
+            if(rc) {
+              PL_ERROR(conn, rc);
+              return bufflen;
+            }
+            parser->state.UNIX.main =3D PL_UNIX_FILETYPE;
+          }
+          else {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      }
+      break;
+    case OS_TYPE_WIN_NT:
+      switch(parser->state.NT.main) {
+      case PL_WINNT_DATE:
+        parser->item_length++;
+        if(parser->item_length < 9) {
+          if(!strchr("0123456789-", c)) { /* only simple control */
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+        }
+        else if(parser->item_length =3D=3D 9) {
+          if(c =3D=3D ' ') {
+            parser->state.NT.main =3D PL_WINNT_TIME;
+            parser->state.NT.sub.time =3D PL_WINNT_TIME_PRESPACE;
+          }
+          else {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+        }
+        else {
+          PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+          return bufflen;
+        }
+        break;
+      case PL_WINNT_TIME:
+        parser->item_length++;
+        switch(parser->state.NT.sub.time) {
+        case PL_WINNT_TIME_PRESPACE:
+          if(!ISSPACE(c)) {
+            parser->state.NT.sub.time =3D PL_WINNT_TIME_TIME;
+          }
+          break;
+        case PL_WINNT_TIME_TIME:
+          if(c =3D=3D ' ') {
+            parser->offsets.time =3D parser->item_offset;
+            finfo->b_data[parser->item_offset + parser->item_length -1]=
 =3D 0;
+            parser->state.NT.main =3D PL_WINNT_DIRORSIZE;
+            parser->state.NT.sub.dirorsize =3D PL_WINNT_DIRORSIZE_PRESP=
ACE;
+            parser->item_length =3D 0;
+          }
+          else if(!strchr("APM0123456789:", c)) {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      case PL_WINNT_DIRORSIZE:
+        switch(parser->state.NT.sub.dirorsize) {
+        case PL_WINNT_DIRORSIZE_PRESPACE:
+          if(c =3D=3D ' ') {
+
+          }
+          else {
+            parser->item_offset =3D finfo->b_used - 1;
+            parser->item_length =3D 1;
+            parser->state.NT.sub.dirorsize =3D PL_WINNT_DIRORSIZE_CONTE=
NT;
+          }
+          break;
+        case PL_WINNT_DIRORSIZE_CONTENT:
+          parser->item_length ++;
+          if(c =3D=3D ' ') {
+            finfo->b_data[parser->item_offset + parser->item_length - 1=
] =3D 0;
+            if(strcmp("<DIR>", finfo->b_data + parser->item_offset) =3D=
=3D 0) {
+              finfo->filetype =3D CURLFILETYPE_DIRECTORY;
+              finfo->size =3D 0;
+            }
+            else {
+              char *endptr;
+              finfo->size =3D curlx_strtoofft(finfo->b_data + parser->i=
tem_offset,
+                                            &endptr, 10);
+              if(!*endptr) {
+                if(finfo->size < CURL_LLONG_MAX &&
+                   finfo->size > CURL_LLONG_MIN) {
+
+                }
+                else if(finfo->size =3D=3D CURL_LLONG_MAX ||
+                        finfo->size =3D=3D CURL_LLONG_MIN) {
+                  if(errno =3D=3D ERANGE) {
+                    PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+                    return bufflen;
+                  }
+                }
+                else {
+                  PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+                  return bufflen;
+                }
+              }
+              else {
+                PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+                return bufflen;
+              }
+              /* correct file size */
+              parser->file_data->filetype =3D CURLFILETYPE_FILE;
+            }
+
+            parser->file_data->flags |=3D CURLFINFOFLAG_KNOWN_SIZE;
+            parser->item_length =3D 0;
+            parser->state.NT.main =3D PL_WINNT_FILENAME;
+            parser->state.NT.sub.filename =3D PL_WINNT_FILENAME_PRESPAC=
E;
+          }
+          break;
+        }
+        break;
+      case PL_WINNT_FILENAME:
+        switch (parser->state.NT.sub.filename) {
+        case PL_WINNT_FILENAME_PRESPACE:
+          if(c !=3D ' ') {
+            parser->item_offset =3D finfo->b_used -1;
+            parser->item_length =3D 1;
+            parser->state.NT.sub.filename =3D PL_WINNT_FILENAME_CONTENT=
;
+          }
+          break;
+        case PL_WINNT_FILENAME_CONTENT:
+          parser->item_length++;
+          if(c =3D=3D '\r') {
+            parser->state.NT.sub.filename =3D PL_WINNT_FILENAME_WINEOL;=
+            finfo->b_data[finfo->b_used - 1] =3D 0;
+          }
+          else if(c =3D=3D '\n') {
+            parser->offsets.filename =3D parser->item_offset;
+            finfo->b_data[finfo->b_used - 1] =3D 0;
+            parser->offsets.filename =3D parser->item_offset;
+            rc =3D ftp_pl_insert_finfo(conn, finfo);
+            if(rc) {
+              PL_ERROR(conn, rc);
+              return bufflen;
+            }
+            parser->state.NT.main =3D PL_WINNT_DATE;
+            parser->state.NT.sub.filename =3D 0;
+          }
+          break;
+        case PL_WINNT_FILENAME_WINEOL:
+          if(c =3D=3D '\n') {
+            parser->offsets.filename =3D parser->item_offset;
+            rc =3D ftp_pl_insert_finfo(conn, finfo);
+            if(rc) {
+              PL_ERROR(conn, rc);
+              return bufflen;
+            }
+            parser->state.NT.main =3D PL_WINNT_DATE;
+            parser->state.NT.sub.filename =3D 0;
+          }
+          else {
+            PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+            return bufflen;
+          }
+          break;
+        }
+        break;
+      }
+      break;
+    default:
+      return bufflen+1;
+      break;
+    }
+
+    i++;
+  }
+
+  return bufflen;
+}
diff --git a/lib/ftplistparser.h b/lib/ftplistparser.h
new file mode 100644
index 0000000..20d75ef
--- /dev/null
+++ b/lib/ftplistparser.h
@@ -0,0 +1,38 @@
+#ifndef __FTPLISTPARSER_H_
+#define __FTPLISTPARSER_H_
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#include <curl/curl.h>
+
+/* WRITEFUNCTION callback for parsing LIST responses */
+size_t ftp_parselist(char *buffer, size_t size, size_t nmemb, void *con=
nptr);
+
+struct ftp_parselist_data; /* defined inside ftplibparser.c */
+
+CURLcode ftp_parselist_geterror(struct ftp_parselist_data *pl_data);
+
+struct ftp_parselist_data *ftp_parselist_data_alloc(void);
+
+void ftp_parselist_data_free(struct ftp_parselist_data **pl_data);
+
+#endif /* __FTPLISTPARSER_H_ */
diff --git a/lib/multi.c b/lib/multi.c
index 476cb81..34d7ecc 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -1128,6 +1128,17 @@ static CURLMcode multi_runsingle(struct Curl_mult=
i *multi,
 =
         if(CURLE_OK =3D=3D easy->result) {
           if(!dophase_done) {
+            /* some steps needed for wildcard matching */
+            if(easy->easy_handle->set.wildcardmatch) {
+              struct WildcardData *wc =3D &easy->easy_handle->wildcard;=
+              if(wc->state =3D=3D CURLWC_DONE || wc->state =3D=3D CURLW=
C_SKIP) {
+                /* skip some states if it is important */
+                Curl_done(&easy->easy_conn, CURLE_OK, FALSE);
+                multistate(easy, CURLM_STATE_DONE);
+                result =3D CURLM_CALL_MULTI_PERFORM;
+                break;
+              }
+            }
             /* DO was not completed in one function call, we must conti=
nue
                DOING... */
             multistate(easy, CURLM_STATE_DOING);
@@ -1338,7 +1349,7 @@ static CURLMcode multi_runsingle(struct Curl_multi=
 *multi,
         easy->easy_conn->writechannel_inuse =3D FALSE;
       }
 =
-      if(easy->result)  {
+      if(easy->result) {
         /* The transfer phase returned error, we mark the connection to=
 get
          * closed to prevent being re-used. This is because we can't po=
ssibly
          * know if the connection is in a good shape or not now.  Unles=
s it is
@@ -1449,6 +1460,16 @@ static CURLMcode multi_runsingle(struct Curl_mult=
i *multi,
           easy->easy_conn =3D NULL;
       }
 =
+      if(easy->easy_handle->set.wildcardmatch) {
+        if(easy->easy_handle->wildcard.state !=3D CURLWC_DONE) {
+          /* if a wildcard is set and we are not ending -> lets start a=
gain
+             with CURLM_STATE_INIT */
+          result =3D CURLM_CALL_MULTI_PERFORM;
+          multistate(easy, CURLM_STATE_INIT);
+          break;
+        }
+      }
+
       /* after we have DONE what we're supposed to do, go COMPLETED, an=
d
          it doesn't matter what the Curl_done() returned! */
       multistate(easy, CURLM_STATE_COMPLETED);
@@ -1550,11 +1571,26 @@ CURLMcode curl_multi_perform(CURLM *multi_handle=
, int *running_handles)
   easy=3Dmulti->easy.next;
   while(easy !=3D &multi->easy) {
     CURLMcode result;
+    struct WildcardData *wc =3D &easy->easy_handle->wildcard;
+
+    if(easy->easy_handle->set.wildcardmatch) {
+      if(!wc->filelist) {
+        CURLcode ret =3D Curl_wildcard_init(wc); /* init wildcard struc=
tures */
+        if(ret)
+          return CURLM_OUT_OF_MEMORY;
+      }
+    }
 =
     do
       result =3D multi_runsingle(multi, easy);
     while (CURLM_CALL_MULTI_PERFORM =3D=3D result);
 =
+    if(easy->easy_handle->set.wildcardmatch) {
+      /* destruct wildcard structures if it is needed */
+      if(wc->state =3D=3D CURLWC_DONE || result)
+        Curl_wildcard_dtor(wc);
+    }
+
     if(result)
       returncode =3D result;
 =
diff --git a/lib/strerror.c b/lib/strerror.c
index 673e89c..6fde477 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -275,6 +275,12 @@ curl_easy_strerror(CURLcode error)
   case CURLE_RTSP_SESSION_ERROR:
     return "RTSP session error";
 =
+  case CURLE_FTP_BAD_FILE_LIST:
+    return "Unable to parse FTP file list";
+
+  case CURLE_CHUNK_FAILED:
+    return "Chunk callback failed";
+
     /* error codes not used by current libcurl */
   case CURLE_OBSOLETE4:
   case CURLE_OBSOLETE10:
diff --git a/lib/transfer.c b/lib/transfer.c
index c9234a7..823d41a 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -2005,12 +2005,7 @@ CURLcode Curl_retry_request(struct connectdata *c=
onn,
   return CURLE_OK;
 }
 =
-/*
- * Curl_perform() is the internal high-level function that gets called =
by the
- * external curl_easy_perform() function. It inits, performs and cleans=
 up a
- * single file transfer.
- */
-CURLcode Curl_perform(struct SessionHandle *data)
+static CURLcode Curl_do_perform(struct SessionHandle *data)
 {
   CURLcode res;
   CURLcode res2;
@@ -2045,6 +2040,15 @@ CURLcode Curl_perform(struct SessionHandle *data)=
       res =3D Curl_do(&conn, &do_done);
 =
       if(res =3D=3D CURLE_OK) {
+        if(conn->data->set.wildcardmatch) {
+          if(conn->data->wildcard.state =3D=3D CURLWC_DONE ||
+             conn->data->wildcard.state =3D=3D CURLWC_SKIP) {
+            /* keep connection open for application to use the socket *=
/
+            conn->bits.close =3D FALSE;
+            res =3D Curl_done(&conn, CURLE_OK, FALSE);
+            break;
+          }
+        }
         res =3D Transfer(conn); /* now fetch that URL please */
         if((res =3D=3D CURLE_OK) || (res =3D=3D CURLE_RECV_ERROR)) {
           bool retry =3D FALSE;
@@ -2162,6 +2166,39 @@ CURLcode Curl_perform(struct SessionHandle *data)=
 }
 =
 /*
+ * Curl_perform() is the internal high-level function that gets called =
by the
+ * external curl_easy_perform() function. It inits, performs and cleans=
 up a
+ * single file transfer.
+ */
+CURLcode Curl_perform(struct SessionHandle *data)
+{
+  CURLcode res;
+  if(!data->set.wildcardmatch)
+    return Curl_do_perform(data);
+
+  /* init main wildcard structures */
+  res =3D Curl_wildcard_init(&data->wildcard);
+  if(res)
+    return res;
+
+  res =3D Curl_do_perform(data);
+  if(res) {
+    Curl_wildcard_dtor(&data->wildcard);
+    return res;
+  }
+
+  /* wildcard loop */
+  while(!res && data->wildcard.state !=3D CURLWC_DONE)
+    res =3D Curl_do_perform(data);
+
+  Curl_wildcard_dtor(&data->wildcard);
+
+  /* wildcard download finished or failed */
+  data->wildcard.state =3D CURLWC_INIT;
+  return res;
+}
+
+/*
  * Curl_setup_transfer() is called to setup some basic properties for t=
he
  * upcoming transfer.
  */
diff --git a/lib/url.c b/lib/url.c
index ec7f46a..c7612cf 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -760,6 +760,10 @@ CURLcode Curl_init_userdefined(struct UserDefined *=
set)
   res =3D setstropt(&set->str[STRING_SSL_CAPATH], (char *) CURL_CA_PATH=
);
 #endif
 =
+  set->wildcardmatch  =3D 0L;
+  set->chunk_bgn      =3D ZERO_NULL;
+  set->chunk_end      =3D ZERO_NULL;
+
   return res;
 }
 =
@@ -828,6 +832,9 @@ CURLcode Curl_open(struct SessionHandle **curl)
     data->progress.flags |=3D PGRS_HIDE;
     data->state.current_speed =3D -1; /* init to negative =3D=3D imposs=
ible */
 =
+    data->wildcard.state =3D CURLWC_INIT;
+    data->wildcard.filelist =3D NULL;
+    data->set.fnmatch =3D ZERO_NULL;
     /* This no longer creates a connection cache here. It is instead ma=
de on
        the first call to curl_easy_perform() or when the handle is adde=
d to a
        multi stack. */
@@ -2445,6 +2452,23 @@ CURLcode Curl_setopt(struct SessionHandle *data, =
CURLoption option,
     /* Set the user defined RTP write function */
     data->set.fwrite_rtp =3D va_arg(param, curl_write_callback);
     break;
+
+  case CURLOPT_WILDCARDMATCH:
+    data->set.wildcardmatch =3D va_arg(param, long);
+    break;
+  case CURLOPT_CHUNK_BGN_FUNCTION:
+    data->set.chunk_bgn =3D va_arg(param, curl_chunk_bgn_callback);
+    break;
+  case CURLOPT_CHUNK_END_FUNCTION:
+    data->set.chunk_end =3D va_arg(param, curl_chunk_end_callback);
+    break;
+  case CURLOPT_FNMATCH_FUNCTION:
+    data->set.fnmatch =3D va_arg(param, curl_fnmatch_callback);
+    break;
+  case CURLOPT_CHUNK_DATA:
+    data->wildcard.customptr =3D va_arg(param, void *);
+    break;
+
   default:
     /* unknown tag and its companion, just ignore: */
     result =3D CURLE_FAILED_INIT; /* correct this */
diff --git a/lib/urldata.h b/lib/urldata.h
index 30782c2..9cfe574 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -153,6 +153,7 @@
 #include "ssh.h"
 #include "http.h"
 #include "rtsp.h"
+#include "wildcard.h"
 =
 #ifdef HAVE_GSSAPI
 # ifdef HAVE_GSSGNU
@@ -1409,6 +1410,12 @@ struct UserDefined {
   /* Common RTSP header options */
   Curl_RtspReq rtspreq; /* RTSP request type */
   long rtspversion; /* like httpversion, for RTSP */
+  bool wildcardmatch; /* enable wildcard matching */
+  curl_chunk_bgn_callback chunk_bgn; /* called before part of transfer =
starts */
+  curl_chunk_end_callback chunk_end; /* called after part transferring
+                                        stopped */
+  curl_fnmatch_callback fnmatch; /* callback to decide which file corre=
sponds
+                                    to pattern (e.g. if WILDCARDMATCH i=
s on) */
 };
 =
 struct Names {
@@ -1450,6 +1457,7 @@ struct SessionHandle {
   struct Progress progress;    /* for all the progress meter data */
   struct UrlState state;       /* struct for fields used for state info=
 and
                                   other dynamic purposes */
+  struct WildcardData wildcard; /* wildcard download state info */
   struct PureInfo info;        /* stats, reports and info data */
 #if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV)
   iconv_t outbound_cd;         /* for translating to the network encodi=
ng */
diff --git a/lib/wildcard.c b/lib/wildcard.c
new file mode 100644
index 0000000..43ac546
--- /dev/null
+++ b/lib/wildcard.c
@@ -0,0 +1,66 @@
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#include "wildcard.h"
+#include "llist.h"
+#include "fileinfo.h"
+
+/* The last #include file should be: */
+#include "memdebug.h"
+
+CURLcode Curl_wildcard_init(struct WildcardData *wc)
+{
+  /* now allocate only wc->filelist, everything else
+     will be allocated if it is needed. */
+  wc->filelist =3D Curl_llist_alloc(Curl_fileinfo_dtor);
+  if(!wc->filelist) {;
+    return CURLE_OUT_OF_MEMORY;
+  }
+  return CURLE_OK;
+}
+
+void Curl_wildcard_dtor(struct WildcardData *wc)
+{
+  if(!wc)
+    return;
+
+  if(wc->tmp_dtor) {
+    wc->tmp_dtor(wc->tmp);
+    wc->tmp =3D NULL;
+  }
+
+  if(wc->filelist) {
+    Curl_llist_destroy(wc->filelist, NULL);
+    wc->filelist =3D NULL;
+  }
+
+  if(wc->path) {
+    free(wc->path);
+    wc->path =3D NULL;
+  }
+
+  if(wc->pattern) {
+    free(wc->pattern);
+    wc->pattern =3D NULL;
+  }
+  wc->customptr =3D NULL;
+}
diff --git a/lib/wildcard.h b/lib/wildcard.h
new file mode 100644
index 0000000..1a1c1bb
--- /dev/null
+++ b/lib/wildcard.h
@@ -0,0 +1,58 @@
+#ifndef __WILDCARD_H
+#define __WILDCARD_H
+/**********************************************************************=
*****
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2010, Daniel Stenberg, <daniel_at_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
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or =
sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY O=
F ANY
+ * KIND, either express or implied.
+ *
+ **********************************************************************=
*****/
+
+#include <curl/curl.h>
+
+/* list of wildcard process states */
+typedef enum {
+  CURLWC_INIT =3D 0,
+  CURLWC_MATCHING, /* library is trying to get list of addresses for
+                      downloading */
+  CURLWC_DOWNLOADING,
+  CURLWC_CLEAN, /* deallocate resources and reset settings */
+  CURLWC_SKIP,  /* skip over concrete file */
+  CURLWC_ERROR, /* error cases */
+  CURLWC_DONE   /* if is wildcard->state =3D=3D CURLWC_DONE wildcard lo=
op in
+                   Curl_perform() will end */
+} curl_wildcard_states;
+
+typedef void (*curl_wildcard_tmp_dtor)(void *ptr);
+
+/* struct keeping information about wildcard download process */
+struct WildcardData {
+  curl_wildcard_states state;
+  char *path; /* path to the directory, where we trying wildcard-match =
*/
+  char *pattern; /* wildcard pattern */
+  struct curl_llist *filelist; /* llist with struct Curl_fileinfo */
+  void *tmp; /* pointer to protocol specific temporary data */
+  curl_wildcard_tmp_dtor tmp_dtor;
+  void *customptr;  /* for CURLOPT_CHUNK_DATA pointer */
+};
+
+CURLcode Curl_wildcard_init(struct WildcardData *wc);
+void Curl_wildcard_dtor(struct WildcardData *wc);
+
+struct SessionHandle;
+
+#endif /* __WILDCARD_H */
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index dabe3d1..a8cf5d7 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -60,6 +60,7 @@ EXTRA_DIST =3D test1 test108 test117 test127 test20 te=
st27 test34 test46	   \
  test1072 test1073 test1074 test1075 test1076 test1077 test1078 test107=
9   \
  test1080 test1081 test1082 test1083 test1084 test1085 test633 test634 =
    \
  test635 test636 test637 test558 test559 test1086 test1087 test1088    =
    \
+ test574 test575 test576 test577 test1113 test1114 \
  test1089 test1090 test1091 test1092 test1093 test1094 test1095 test109=
6   \
  test1097 test560 test561 test1098 test1099 test562 test563 test1100   =
    \
  test564 test1101 test1102 test1103 test1104 test299 test310 test311   =
    \
diff --git a/tests/data/test1113 b/tests/data/test1113
new file mode 100644
index 0000000..6ff1d19
--- /dev/null
+++ b/tests/data/test1113
@@ -0,0 +1,71 @@
+<testcase>
+<info>
+<keywords>
+FTP
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data mode=3D"text">
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+ftp
+</server>
+<tool>
+lib574
+</tool>
+<name>
+FTP wildcard download - changed fnmatch, 2x perform (DOS LIST response)=
+</name>
+<command>
+ftp://%HOSTIP:%FTPPORT/fully_simulated/DOS/*.txt
+</command>
+</client>
+
+############################################
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+0
+</errorcode>
+<strip>
+^RETR.*
+^EPSV.*
+^PWD.*
+^CWD.*
+^TYPE.*
+^LIST.*
+</strip>
+<strippart>
+s/USER.*/USER/
+s/PASS.*/PASS/
+s/QUIT.*/QUIT/
+</strippart>
+# THERE SHOULD NOT BE "SIZE"! and once "USER && PASS"
+<protocol>
+USER
+PASS
+QUIT
+</protocol>
+<stdout mode=3D"text">
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test1114 b/tests/data/test1114
new file mode 100644
index 0000000..8eee429
--- /dev/null
+++ b/tests/data/test1114
@@ -0,0 +1,136 @@
+<testcase>
+<info>
+<keywords>
+FTP
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+ftp
+</server>
+<tool>
+lib576
+</tool>
+ <name>
+FTP wildcard download - skip/parser_correctness/CURLOPT_FNMATCH_FUNCTIO=
N (DOS)
+ </name>
+ <command>
+ftp://%HOSTIP:%FTPPORT/fully_simulated/DOS/*
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+0
+</errorcode>
+<stdout mode=3D"text">
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      12
+Filename:     .
+Size:         0B
+Time:         04-27-10  05:12AM
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      11
+Filename:     ..
+Size:         0B
+Time:         04-23-10  03:12AM
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      10
+Filename:     chmod1
+Size:         38B
+Time:         01-11-10  10:00AM
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 444
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      9
+Filename:     chmod2
+Size:         38B
+Time:         02-01-10  08:00AM
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 666
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      8
+Filename:     chmod3
+Size:         38B
+Time:         02-01-10  08:00AM
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 777
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      7
+Filename:     chmod4
+Size:         0B
+Time:         05-04-10  04:31AM
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      6
+Filename:     chmod5
+Size:         0B
+Time:         05-04-10  04:31AM
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      5
+Filename:     empty_file.dat
+Size:         0B
+Time:         04-27-10  11:01AM
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      4
+Filename:     file.txt
+Size:         35B
+Time:         04-27-10  11:01AM
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This is content of file "file.txt"
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      3
+Filename:     .NeXT
+Size:         0B
+Time:         01-23-05  02:05AM
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      2
+Filename:     someothertext.txt
+Size:         47B
+Time:         04-27-10  11:01AM
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+# THIS CONTENT WAS SKIPPED IN CHUNK_BGN CALLBACK #
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      1
+Filename:     weirddir.txt
+Size:         0B
+Time:         04-23-10  03:12AM
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test146 b/tests/data/test146
index e04f8f0..3cd4bd5 100644
--- a/tests/data/test146
+++ b/tests/data/test146
@@ -45,7 +45,7 @@ EPSV
 TYPE I
 SIZE 146
 RETR 146
-CWD /nowhere/anywhere
+CWD /
 EPSV
 SIZE 146
 RETR 146
diff --git a/tests/data/test149 b/tests/data/test149
index bdbdcc7..f7973f4 100644
--- a/tests/data/test149
+++ b/tests/data/test149
@@ -34,7 +34,7 @@ CWD dir1
 EPSV
 TYPE I
 STOR 149
-CWD /nowhere/anywhere
+CWD /
 CWD dir2
 EPSV
 STOR 149
diff --git a/tests/data/test539 b/tests/data/test539
index f1ad701..2406c54 100644
--- a/tests/data/test539
+++ b/tests/data/test539
@@ -53,7 +53,7 @@ TYPE I
 SIZE 539
 RETR 539
 SYST
-CWD /nowhere/anywhere
+CWD /
 EPSV
 TYPE A
 LIST path/to/the/file/539./
diff --git a/tests/data/test574 b/tests/data/test574
new file mode 100644
index 0000000..9d50045
--- /dev/null
+++ b/tests/data/test574
@@ -0,0 +1,71 @@
+<testcase>
+<info>
+<keywords>
+FTP
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data mode=3D"text">
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+ftp
+</server>
+<tool>
+lib574
+</tool>
+<name>
+FTP wildcard download - changed fnmatch, 2x perform (UNIX LIST response=
)
+</name>
+<command>
+ftp://%HOSTIP:%FTPPORT/fully_simulated/UNIX/*.txt
+</command>
+</client>
+
+############################################
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+0
+</errorcode>
+<strip>
+^RETR.*
+^EPSV.*
+^PWD.*
+^CWD.*
+^TYPE.*
+^LIST.*
+</strip>
+<strippart>
+s/USER.*/USER/
+s/PASS.*/PASS/
+s/QUIT.*/QUIT/
+</strippart>
+# THERE SHOULD NOT BE "SIZE"! and once "USER && PASS"
+<protocol>
+USER
+PASS
+QUIT
+</protocol>
+<stdout mode=3D"text">
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test575 b/tests/data/test575
new file mode 100644
index 0000000..c460467
--- /dev/null
+++ b/tests/data/test575
@@ -0,0 +1,79 @@
+<testcase>
+<info>
+<keywords>
+FTP
+multi
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+ftp
+</server>
+<tool>
+lib575
+</tool>
+ <name>
+FTP wildcard download - dup_handle && multi interface
+ </name>
+ <command>
+ftp://%HOSTIP:%FTPPORT/fully_simulated/UNIX/*
+</command>
+</client>
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^RETR.*
+^EPSV.*
+^CWD.*
+^PWD.*
+^TYPE.*
+</strip>
+<strippart>
+s/^USER.*/USER/
+s/^PASS.*/PASS/
+s/^LIST.*/LIST/
+s/^QUIT.*/QUIT/
+</strippart>
+<errorcode>
+0
+</errorcode>
+<protocol>
+USER
+PASS
+LIST
+LIST
+QUIT
+USER
+PASS
+LIST
+QUIT
+</protocol>
+<stdout mode=3D"text">
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+This file should have permissions 444
+This file should have permissions 666
+This file should have permissions 777
+This is content of file "file.txt"
+Some junk ;-) This file does not really exist.
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test576 b/tests/data/test576
new file mode 100644
index 0000000..4d4b90c
--- /dev/null
+++ b/tests/data/test576
@@ -0,0 +1,192 @@
+<testcase>
+<info>
+<keywords>
+FTP
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+ftp
+</server>
+<tool>
+lib576
+</tool>
+ <name>
+FTP wildcard download - skip/parser_correctness/CURLOPT_FNMATCH_FUNCTIO=
N (UNIX)
+ </name>
+ <command>
+ftp://%HOSTIP:%FTPPORT/fully_simulated/UNIX/*
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+0
+</errorcode>
+<stdout mode=3D"text">
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      14
+Filename:     .
+Permissions:  rwxrwxrwx (parsed =3D> 777)
+Size:         20480B
+User:         ftp-default
+Group:        ftp-default
+Time:         Apr 27  5:12
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      13
+Filename:     ..
+Permissions:  rwxrwxrwx (parsed =3D> 777)
+Size:         20480B
+User:         ftp-default
+Group:        ftp-default
+Time:         Apr 23  3:12
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      12
+Filename:     chmod1
+Permissions:  r--r--r-- (parsed =3D> 444)
+Size:         38B
+User:         ftp-default
+Group:        ftp-default
+Time:         Jan 11 10:00
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 444
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      11
+Filename:     chmod2
+Permissions:  rw-rw-rw- (parsed =3D> 666)
+Size:         38B
+User:         ftp-default
+Group:        ftp-default
+Time:         Feb  1  8:00
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 666
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      10
+Filename:     chmod3
+Permissions:  rwxrwxrwx (parsed =3D> 777)
+Size:         38B
+User:         ftp-default
+Group:        ftp-default
+Time:         Feb  1  8:00
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 777
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      9
+Filename:     chmod4
+Permissions:  --S--S--t (parsed =3D> 7001)
+Size:         4096B
+User:         ftp-default
+Group:        ftp-default
+Time:         May  4  4:31
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      8
+Filename:     chmod5
+Permissions:  --s--s--T (parsed =3D> 7110)
+Size:         4096B
+User:         ftp-default
+Group:        ftp-default
+Time:         May  4  4:31
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      7
+Filename:     empty_file.dat
+Permissions:  rw-r--r-- (parsed =3D> 644)
+Size:         0B
+User:         ftp-default
+Group:        ftp-default
+Time:         Apr 27 11:01
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      6
+Filename:     file.txt
+Permissions:  rw-r--r-- (parsed =3D> 644)
+Size:         35B
+User:         ftp-default
+Group:        ftp-default
+Time:         Apr 27 11:01
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+This is content of file "file.txt"
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      5
+Filename:     link
+Permissions:  rwxrwxrwx (parsed =3D> 777)
+Size:         0B
+User:         ftp-default
+Group:        ftp-default
+Time:         Jan  6  4:42
+Filetype:     symlink
+Target:       file.txt
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      4
+Filename:     link_absolute
+Permissions:  rwxrwxrwx (parsed =3D> 777)
+Size:         0B
+User:         ftp-default
+Group:        ftp-default
+Time:         Jan  6  4:45
+Filetype:     symlink
+Target:       /data/ftp/file.txt
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      3
+Filename:     .NeXT
+Permissions:  rwxrwxrwx (parsed =3D> 777)
+Size:         4096B
+User:         ftp-default
+Group:        ftp-default
+Time:         Jan 23  2:05
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      2
+Filename:     someothertext.txt
+Permissions:  rw-r--r-- (parsed =3D> 644)
+Size:         47B
+User:         ftp-default
+Group:        ftp-default
+Time:         Apr 27 11:01
+Filetype:     regular file
+Content:
+-------------------------------------------------------------
+# THIS CONTENT WAS SKIPPED IN CHUNK_BGN CALLBACK #
+-------------------------------------------------------------
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+Remains:      1
+Filename:     weirddir.txt
+Permissions:  rwxr-xrwx (parsed =3D> 757)
+Size:         4096B
+User:         ftp-default
+Group:        ftp-default
+Time:         Apr 23  3:12
+Filetype:     directory
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test577 b/tests/data/test577
new file mode 100644
index 0000000..5f1898f
--- /dev/null
+++ b/tests/data/test577
@@ -0,0 +1,38 @@
+<testcase>
+<info>
+<keywords>
+wildcardmatch
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+</reply>
+
+# Client-side
+<client>
+<server>
+none
+</server>
+# tool is what to use instead of 'curl'
+<tool>
+lib577
+</tool>
+
+ <name>
+Curl_fnmatch() testing
+ </name>
+ <command>
+nothing
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<stdout mode=3D"text">
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/directories.pm b/tests/directories.pm
new file mode 100644
index 0000000..6f20e6f
--- /dev/null
+++ b/tests/directories.pm
@@ -0,0 +1,266 @@
+%file_chmod1 =3D (
+  'name'      =3D> 'chmod1',
+  'content'   =3D> "This file should have permissions 444\n",
+  'perm'      =3D> 'r--r--r--',
+  'time'      =3D> 'Jan 11 10:00',
+  'dostime'   =3D> '01-11-10  10:00AM',
+);
+
+%file_chmod2 =3D (
+  'name'      =3D> 'chmod2',
+  'content'   =3D> "This file should have permissions 666\n",
+  'perm'      =3D> 'rw-rw-rw-',
+  'time'      =3D> 'Feb  1  8:00',
+  'dostime'   =3D> '02-01-10  08:00AM',
+);
+
+%file_chmod3 =3D (
+  'name'      =3D> 'chmod3',
+  'content'   =3D> "This file should have permissions 777\n",
+  'perm'      =3D> 'rwxrwxrwx',
+  'time'      =3D> 'Feb  1  8:00',
+  'dostime'   =3D> '02-01-10  08:00AM',
+);
+
+%file_chmod4 =3D (
+  'type'      =3D> 'd',
+  'name'      =3D> 'chmod4',
+  'content'   =3D> "This file should have permissions 001\n",
+  'perm'      =3D> '--S--S--t',
+  'time'      =3D> 'May  4  4:31',
+  'dostime'   =3D> '05-04-10  04:31AM'
+);
+
+%file_chmod5 =3D (
+  'type'      =3D> 'd',
+  'name'      =3D> 'chmod5',
+  'content'   =3D> "This file should have permissions 110\n",
+  'perm'      =3D> '--s--s--T',
+  'time'      =3D> 'May  4  4:31',
+  'dostime'   =3D> '05-04-10  04:31AM'
+);
+
+%link_link =3D (
+  'type'      =3D> 'l',
+  'name'      =3D> 'link -> file.txt',
+  'size'      =3D> '8',
+  'perm'      =3D> 'rwxrwxrwx',
+  'time'      =3D> 'Jan  6  4:42'
+);
+
+%link_link_absolute =3D (
+  'type'      =3D> 'l',
+  'name'      =3D> 'link_absolute -> /data/ftp/file.txt',
+  'size'      =3D> '15',
+  'perm'      =3D> 'rwxrwxrwx',
+  'time'      =3D> 'Jan  6  4:45'
+);
+
+%dir_dot =3D (
+  'type'      =3D> "d",
+  'name'      =3D> ".",
+  'hlink'     =3D> "4",
+  'time'      =3D> "Apr 27  5:12",
+  'size'      =3D> "20480",
+  'dostime'   =3D> "04-27-10  05:12AM",
+  'perm'      =3D> "rwxrwxrwx"
+);
+
+%dir_ddot =3D (
+  'type'      =3D> "d",
+  'name'      =3D> "..",
+  'hlink'     =3D> "4",
+  'size'      =3D> "20480",
+  'time'      =3D> "Apr 23  3:12",
+  'dostime'   =3D> "04-23-10  03:12AM",
+  'perm'      =3D> "rwxrwxrwx"
+);
+
+%dir_weirddir_txt =3D (
+  'type'      =3D> "d",
+  'name'      =3D> "weirddir.txt",
+  'hlink'     =3D> "2",
+  'size'      =3D> "4096",
+  'time'      =3D> "Apr 23  3:12",
+  'dostime'   =3D> "04-23-10  03:12AM",
+  'perm'      =3D> "rwxr-xrwx"
+);
+
+%dir_UNIX =3D (
+  'type'      =3D> "d",
+  'name'      =3D> "UNIX",
+  'hlink'     =3D> "11",
+  'size'      =3D> "4096",
+  'time'      =3D> "Nov 01  2008",
+  'dostime'   =3D> "11-01-08  11:11AM",
+  'perm'      =3D> "rwx--x--x"
+);
+
+%dir_DOS =3D (
+  'type'      =3D> "d",
+  'name'      =3D> "DOS",
+  'hlink'     =3D> "11",
+  'size'      =3D> "4096",
+  'time'      =3D> "Nov 01  2008",
+  'dostime'   =3D> "11-01-08  11:11AM",
+  'perm'      =3D> "rwx--x--x"
+);
+
+%dir_dot_NeXT =3D (
+  'type'      =3D> "d",
+  'name'      =3D> ".NeXT",
+  'hlink'     =3D> "4",
+  'size'      =3D> "4096",
+  'time'      =3D> "Jan 23  2:05",
+  'dostime'   =3D> "01-23-05  02:05AM",
+  'perm'      =3D> "rwxrwxrwx"
+);
+
+%file_empty_file_dat =3D (
+  'name'      =3D> "empty_file.dat",
+  'content'   =3D> "",
+  'perm'      =3D> "rw-r--r--",
+  'time'      =3D> "Apr 27 11:01",
+  'dostime'   =3D> "04-27-10  11:01AM"
+);
+
+%file_file_txt =3D (
+  'name'      =3D> "file.txt",
+  'content'   =3D> "This is content of file \"file.txt\"\n",
+  'time'      =3D> "Apr 27 11:01",
+  'dostime'   =3D> "04-27-10  11:01AM",
+  'perm'      =3D> "rw-r--r--"
+);
+
+%file_someothertext_txt =3D (
+  'name'      =3D> "someothertext.txt",
+  'content'   =3D> "Some junk ;-) This file does not really exist.\n",
+  'time'      =3D> "Apr 27 11:01",
+  'dostime'   =3D> "04-27-10  11:01AM",
+  'perm'      =3D> "rw-r--r--"
+);
+
+%lists =3D (
+  '/fully_simulated/' =3D> {
+    'files'   =3D> [ \%dir_dot, \%dir_ddot, \%dir_DOS, \%dir_UNIX ],
+    'eol'     =3D> "\r\n",
+    'type'    =3D> "unix"
+  },
+  '/fully_simulated/UNIX/' =3D> {
+    'files'   =3D> [ \%dir_dot, \%dir_ddot,
+                   \%file_chmod1, \%file_chmod2, \%file_chmod3, \%file_=
chmod4, \%file_chmod5,
+                   \%file_empty_file_dat, \%file_file_txt,
+                   \%link_link, \%link_link_absolute, \%dir_dot_NeXT,
+                   \%file_someothertext_txt, \%dir_weirddir_txt ],
+    'eol'     =3D> "\r\n",
+    'type'    =3D> 'unix'
+  },
+  '/fully_simulated/DOS/' =3D> {
+    'files'   =3D> [ \%dir_dot, \%dir_ddot,
+                   \%file_chmod1, \%file_chmod2, \%file_chmod3, \%file_=
chmod4, \%file_chmod5,
+                   \%file_empty_file_dat, \%file_file_txt,
+                   \%dir_dot_NeXT, \%file_someothertext_txt, \%dir_weir=
ddir_txt ],
+    'eol'     =3D> "\r\n",
+    'type'    =3D> 'dos'
+  }
+);
+
+sub ftp_createcontent($) {
+  my (%list) =3D @_;
+
+  $type =3D $$list{'type'};
+  $eol  =3D $$list{'eol'};
+  $list_ref =3D $$list{'files'};
+
+  my @diroutput;
+  my @contentlist;
+  if($type eq "unix") {
+    for(@$list_ref) {
+      my %file =3D %$_;
+      my $line =3D "";
+      my $ftype  =3D $file{'type'}  ? $file{'type'}  : "-";
+      my $fperm  =3D $file{'perm'}  ? $file{'perm'}  : "rwxr-xr-x";
+      my $fuser  =3D $file{'user'}  ? sprintf("%15s", $file{'user'})   =
: "ftp-default";
+      my $fgroup =3D $file{'group'} ? sprintf("%15s", $file{'group'})  =
: "ftp-default";
+      my $fsize =3D "";
+      if($file{'type'} eq "d") {
+        $fsize =3D $file{'size'} ? sprintf("%7s", $file{'size'}) : spri=
ntf("%7d", 4096);
+      }
+      else {
+        $fsize =3D sprintf("%7d", length $file{'content'});
+      }
+      my $fhlink =3D $file{'hlink'} ? sprintf("%4d",  $file{'hlink'})  =
: "   1";
+      my $ftime  =3D $file{'time'}  ? sprintf("%10s", $file{'time'})   =
: "Jan 9  1933";
+      push(@contentlist, "$ftype$fperm $fhlink $fuser $fgroup $fsize $f=
time $file{'name'}$eol");
+    }
+
+    return @contentlist;
+  }
+  elsif($type =3D~ /^dos$/) {
+    for(@$list_ref) {
+      my %file =3D %$_;
+      my $line =3D "";
+      my $time =3D $file{'dostime'} ? $file{'dostime'} : "06-25-97  09:=
12AM";
+      my $size_or_dir;
+      if($file{'type'} =3D~ /^d$/) {
+        $size_or_dir =3D "      <DIR>         ";
+      }
+      else {
+        $size_or_dir =3D sprintf("%20d", length $file{'content'});
+      }
+      push(@contentlist, "$time $size_or_dir $file{'name'}$eol");
+    }
+    return @contentlist;
+  }
+}
+
+sub wildcard_filesize($$) {
+  my ($list_type, $file) =3D @_;
+  $list =3D $lists{$list_type};
+  if($list) {
+    my $files =3D $list->{'files'};
+    for(@$files) {
+      my %f =3D %$_;
+      if ($f{'name'} eq $file) {
+        if($f{'content'}) {
+          return length $f{'content'};
+        }
+        elsif ($f{'type'} ne "d"){
+          return 0;
+        }
+        else {
+          return -1;
+        }
+      }
+    }
+  }
+  return -1;
+}
+sub wildcard_getfile($$) {
+  my ($list_type, $file) =3D @_;
+  $list =3D $lists{$list_type};
+  if($list) {
+    my $files =3D $list->{'files'};
+    for(@$files) {
+      my %f =3D %$_;
+      if ($f{'name'} eq $file) {
+        if($f{'content'}) {
+          return (length $f{'content'}, $f{'content'});
+        }
+        elsif ($f{'type'} ne "d"){
+          return (0, "");
+        }
+        else {
+          return (-1, 0);
+        }
+      }
+    }
+  }
+  return (-1, 0);
+}
+
+sub ftp_contentlist {
+  my $listname =3D $_[0];
+  $list =3D $lists{$listname};
+  return ftp_createcontent(\$list);
+}
diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl
index cc69585..ed2a832 100755
--- a/tests/ftpserver.pl
+++ b/tests/ftpserver.pl
@@ -54,6 +54,7 @@ use IPC::Open2;
 =
 require "getpart.pm";
 require "ftp.pm";
+require "directories.pm";
 =
 use serverhelp qw(
     servername_str
@@ -137,6 +138,13 @@ my %customcount;  #
 my %delayreply;   #
 =
 #**********************************************************************=
+# global variables for to test ftp wildcardmatching or other test that
+# need flexible LIST responses.. and corresponding files.
+# $ftptargetdir is keeping the fake "name" of LIST directory.
+my $ftplistparserstate;
+my $ftptargetdir;
+
+#**********************************************************************=
 # global vars used for signal handling
 #
 my $got_exit_signal =3D 0; # set if program should finish execution ASA=
P
@@ -344,6 +352,8 @@ sub protocolsetup {
             'LIST' =3D> \&LIST_ftp,
             'NLST' =3D> \&NLST_ftp,
             'PASV' =3D> \&PASV_ftp,
+            'CWD'  =3D> \&CWD_ftp,
+            'PWD'  =3D> \&PWD_ftp,
             'EPSV' =3D> \&PASV_ftp,
             'RETR' =3D> \&RETR_ftp,
             'SIZE' =3D> \&SIZE_ftp,
@@ -362,7 +372,6 @@ sub protocolsetup {
             'CWD'  =3D> '250 CWD command successful.',
             'SYST' =3D> '215 UNIX Type: L8', # just fake something
             'QUIT' =3D> '221 bye bye baby', # just reply something
-            'PWD'  =3D> '257 "/nowhere/anywhere" is current directory',=
             'MKD'  =3D> '257 Created your requested directory',
             'REST' =3D> '350 Yeah yeah we set it there for you',
             'DELE' =3D> '200 OK OK OK whatever you say',
@@ -683,6 +692,64 @@ sub REST_ftp {
     logmsg "Set REST position to $rest\n"
 }
 =
+sub switch_directory_goto {
+  my $target_dir =3D $_;
+
+  if(!$ftptargetdir) {
+    $ftptargetdir =3D "/";
+  }
+
+  if($target_dir eq "") {
+    $ftptargetdir =3D "/";
+  }
+  elsif($target_dir eq "..") {
+    if($ftptargetdir eq "/") {
+      $ftptargetdir =3D "/";
+    }
+    else {
+      $ftptargetdir =3D~ s/[[:alnum:]]+\/$//;
+    }
+  }
+  else {
+    $ftptargetdir .=3D $target_dir . "/";
+  }
+}
+
+sub switch_directory {
+    my $target_dir =3D $_[0];
+
+    if($target_dir eq "/") {
+        $ftptargetdir =3D "/";
+    }
+    else {
+        my @dirs =3D split("/", $target_dir);
+        for(@dirs) {
+          switch_directory_goto($_);
+        }
+    }
+}
+
+sub CWD_ftp {
+  my ($folder, $fullcommand) =3D $_[0];
+  switch_directory($folder);
+  if($ftptargetdir =3D~ /^\/fully_simulated/) {
+    $ftplistparserstate =3D "enabled";
+  }
+  else {
+    undef $ftplistparserstate;
+  }
+}
+
+sub PWD_ftp {
+    my $mydir;
+    $mydir =3D $ftptargetdir ? $ftptargetdir : "/";
+
+    if($mydir ne "/") {
+        $mydir =3D~ s/\/$//;
+    }
+    sendcontrol "257 \"$mydir\" is current directory\r\n";
+}
+
 sub LIST_ftp {
   #  print "150 ASCII data connection for /bin/ls (193.15.23.1,59196) (=
0 bytes)\r\n";
 =
@@ -699,6 +766,10 @@ my @ftpdir=3D("total 20\r\n",
 "drwxrwxrwx   2 98       1            512 Oct 30 14:33 pub\r\n",
 "dr-xr-xr-x   5 0        1            512 Oct  1  1997 usr\r\n");
 =
+    if($ftplistparserstate) {
+      @ftpdir =3D ftp_contentlist($ftptargetdir);
+    }
+
     logmsg "pass LIST data on data connection\n";
     for(@ftpdir) {
         senddata $_;
@@ -748,6 +819,16 @@ sub MDTM_ftp {
 =
 sub SIZE_ftp {
     my $testno =3D $_[0];
+    if($ftplistparserstate) {
+        my $size =3D wildcard_filesize($ftptargetdir, $testno);
+        if($size =3D=3D -1) {
+            sendcontrol "550 $testno: No such file or directory.\r\n";
+        }
+        else {
+            sendcontrol "213 $size\r\n";
+        }
+        return 0;
+    }
 =
     if($testno =3D~ /^verifiedserver$/) {
         my $response =3D "WE ROOLZ: $$\r\n";
@@ -803,6 +884,21 @@ sub SIZE_ftp {
 sub RETR_ftp {
     my ($testno) =3D @_;
 =
+    if($ftplistparserstate) {
+        my @content =3D wildcard_getfile($ftptargetdir, $testno);
+        if($content[0] =3D=3D -1) {
+            #file not found
+        }
+        else {
+            my $size =3D length $content[1];
+            sendcontrol "150 Binary data connection for $testno ($size =
bytes).\r\n",
+            senddata $content[1];
+            close_dataconn(0);
+            sendcontrol "226 File transfer complete\r\n";
+        }
+        return 0;
+    }
+
     if($testno =3D~ /^verifiedserver$/) {
         # this is the secret command that verifies that this actually i=
s
         # the curl test server
@@ -1326,6 +1422,15 @@ while(1) {
     &customize(); # read test control instructions
 =
     sendcontrol @welcome;
+
+    #remove global variables from last connection
+    if($ftplistparserstate) {
+      undef $ftplistparserstate;
+    }
+    if($ftptargetdir) {
+      undef $ftptargetdir;
+    }
+
     if($verbose) {
         for(@welcome) {
             print STDERR "OUT: $_";
diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc
index 58d5d92..814c01e 100644
--- a/tests/libtest/Makefile.inc
+++ b/tests/libtest/Makefile.inc
@@ -8,6 +8,7 @@ SUPPORTFILES =3D first.c test.h
 noinst_PROGRAMS =3D lib500 lib501 lib502 lib503 lib504 lib505 lib506	\
   lib507 lib508 lib510 lib511 lib512 lib513 lib514 lib515 lib516	\
   lib517 lib518 lib519 lib520 lib521 lib523 lib524 lib525 lib526 lib527=
	\
+  lib574 lib575 lib576 lib577 \
   lib529 lib530 lib532 lib533 lib536 lib537 lib540 lib541 lib542 lib543=
 \
   lib544 lib545 lib547 lib548 lib549 lib552 lib553 lib554 lib555 lib556=
 \
   lib539 lib557 lib558 lib559 lib560 lib562 lib564 lib565 lib566 lib567=
 \
@@ -124,6 +125,14 @@ lib559_CFLAGS =3D -DLIB559
 =
 lib560_SOURCES =3D lib560.c $(SUPPORTFILES)
 =
+lib574_SOURCES =3D lib574.c $(SUPPORTFILES)
+
+lib575_SOURCES =3D lib575.c $(SUPPORTFILES)
+
+lib576_SOURCES =3D lib576.c $(SUPPORTFILES)
+
+lib577_SOURCES =3D lib577.c $(SUPPORTFILES)
+
 lib562_SOURCES =3D lib562.c $(SUPPORTFILES)
 =
 lib564_SOURCES =3D lib564.c $(SUPPORTFILES) $(TESTUTIL)
diff --git a/tests/libtest/lib574.c b/tests/libtest/lib574.c
new file mode 100644
index 0000000..69b2979
--- /dev/null
+++ b/tests/libtest/lib574.c
@@ -0,0 +1,56 @@
+/**********************************************************************=
*******
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ */
+
+#include "test.h"
+
+#include "memdebug.h"
+
+static int new_fnmatch(const char *pattern, const char *string)
+{
+  (void)pattern;
+  (void)string;
+  return CURL_FNMATCHFUNC_MATCH;
+}
+
+int test(char *URL)
+{
+  int res;
+  CURL *curl;
+
+  if (curl_global_init(CURL_GLOBAL_ALL) !=3D CURLE_OK) {
+    fprintf(stderr, "curl_global_init() failed\n");
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  if ((curl =3D curl_easy_init()) =3D=3D NULL) {
+    fprintf(stderr, "curl_easy_init() failed\n");
+    curl_global_cleanup();
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  test_setopt(curl, CURLOPT_URL, URL);
+  test_setopt(curl, CURLOPT_WILDCARDMATCH, 1L);
+  test_setopt(curl, CURLOPT_FNMATCH_FUNCTION, new_fnmatch);
+
+  res =3D curl_easy_perform(curl);
+  if(res) {
+    fprintf(stderr, "curl_easy_perform() failed %d\n", res);
+    goto test_cleanup;
+  }
+  res =3D curl_easy_perform(curl);
+  if(res) {
+    fprintf(stderr, "curl_easy_perform() failed %d\n", res);
+    goto test_cleanup;
+  }
+
+test_cleanup:
+  curl_easy_cleanup(curl);
+  curl_global_cleanup();
+  return res;
+}
diff --git a/tests/libtest/lib575.c b/tests/libtest/lib575.c
new file mode 100644
index 0000000..3bf15ea
--- /dev/null
+++ b/tests/libtest/lib575.c
@@ -0,0 +1,109 @@
+/**********************************************************************=
*******
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ */
+
+#include "test.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "testutil.h"
+#include "memdebug.h"
+
+/* 3x download!
+ * 1. normal
+ * 2. dup handle
+ * 3. with multi interface
+ */
+
+int test(char *URL)
+{
+  CURLMcode m;
+  CURL *handle =3D NULL, *duphandle;
+  CURLM *mhandle =3D NULL;
+  int res =3D 0;
+  int still_running =3D 0;
+
+  if(curl_global_init(CURL_GLOBAL_ALL)) {
+    fprintf(stderr, "curl_global_init() failed\n");
+    goto test_cleanup;
+  }
+
+  handle =3D curl_easy_init();
+  if(!handle) {
+    res =3D CURLE_OUT_OF_MEMORY;
+    goto test_cleanup;
+  }
+
+  test_setopt(handle, CURLOPT_URL, URL);
+  test_setopt(handle, CURLOPT_WILDCARDMATCH, 1L);
+  test_setopt(handle, CURLOPT_VERBOSE, 1L);
+
+  res =3D curl_easy_perform(handle);
+  if(res)
+    goto test_cleanup;
+
+  res =3D curl_easy_perform(handle);
+  if(res)
+    goto test_cleanup;
+
+  duphandle =3D curl_easy_duphandle(handle);
+  if(!duphandle)
+    goto test_cleanup;
+  curl_easy_cleanup(handle);
+  handle =3D duphandle;
+
+  mhandle =3D curl_multi_init();
+  if(!mhandle) {
+    fprintf(stderr, "curl_multi_init() failed\n");
+    goto test_cleanup;
+  }
+
+  curl_multi_add_handle(mhandle, handle);
+
+  while(CURLM_CALL_MULTI_PERFORM =3D=3D
+        curl_multi_perform(mhandle, &still_running));
+
+  while(still_running) {
+    struct timeval timeout;
+    int rc;
+    fd_set fdread;
+    fd_set fdwrite;
+    fd_set fdexcep;
+    int max_fdset =3D -1;
+    FD_ZERO(&fdread);
+    FD_ZERO(&fdwrite);
+    FD_ZERO(&fdexcep);
+    timeout.tv_sec =3D 3;
+    timeout.tv_usec =3D 0;
+
+    m =3D curl_multi_fdset(mhandle, &fdread, &fdwrite, &fdexcep, &max_f=
dset);
+    rc =3D select(max_fdset + 1, &fdread, &fdwrite, &fdexcep, &timeout)=
;
+    if(rc =3D=3D -1) {
+      fprintf(stderr, "select() error\n");
+      goto test_cleanup;
+    }
+    else if(rc =3D=3D 0) {
+      fprintf(stderr, "select() timeout!\n");
+      goto test_cleanup;
+    }
+    else {
+      while(CURLM_CALL_MULTI_PERFORM =3D=3D
+          curl_multi_perform(mhandle, &still_running));
+    }
+  }
+
+test_cleanup:
+  if(mhandle)
+    curl_multi_cleanup(mhandle);
+  if(handle)
+    curl_easy_cleanup(handle);
+  curl_global_cleanup();
+  return res;
+}
diff --git a/tests/libtest/lib576.c b/tests/libtest/lib576.c
new file mode 100644
index 0000000..7f2c7ae
--- /dev/null
+++ b/tests/libtest/lib576.c
@@ -0,0 +1,107 @@
+/**********************************************************************=
*******
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ */
+
+#include "test.h"
+#include "testutil.h"
+#include "memdebug.h"
+
+typedef struct {
+  int remains;
+  int print_content;
+} chunk_data_t;
+
+long chunk_bgn(const struct curl_fileinfo *finfo, void *ptr, int remain=
s);
+long chunk_end(void *ptr);
+
+long chunk_bgn(const struct curl_fileinfo *finfo, void *ptr, int remain=
s)
+{
+  chunk_data_t *ch_d =3D ptr;
+  ch_d->remains =3D remains;
+
+  printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\n");
+  printf("Remains:      %d\n", remains);
+  printf("Filename:     %s\n", finfo->filename);
+  if(finfo->strings.perm) {
+    printf("Permissions:  %s", finfo->strings.perm);
+    if(finfo->flags & CURLFINFOFLAG_KNOWN_PERM)
+      printf(" (parsed =3D> %o)", finfo->perm);
+    printf("\n");
+  }
+  printf("Size:         %lldB\n", (long long int)finfo->size);
+  if(finfo->strings.user)
+    printf("User:         %s\n", finfo->strings.user);
+  if(finfo->strings.group)
+    printf("Group:        %s\n", finfo->strings.group);
+  if(finfo->strings.time)
+    printf("Time:         %s\n", finfo->strings.time);
+  printf("Filetype:     ");
+  switch(finfo->filetype) {
+  case CURLFILETYPE_FILE:
+    printf("regular file\n");
+    break;
+  case CURLFILETYPE_DIRECTORY:
+    printf("directory\n");
+    break;
+  case CURLFILETYPE_SYMLINK:
+    printf("symlink\n");
+    printf("Target:       %s\n", finfo->strings.target);
+    break;
+  default:
+    printf("other type\n");
+    break;
+  }
+  if(finfo->filetype =3D=3D CURLFILETYPE_FILE) {
+    ch_d->print_content =3D 1;
+    printf("Content:\n-------------------------------------------------=
------------\n");
+  }
+  if(strcmp(finfo->filename, "someothertext.txt") =3D=3D 0) {
+    printf("# THIS CONTENT WAS SKIPPED IN CHUNK_BGN CALLBACK #\n");
+    return CURL_CHUNK_BGN_FUNC_SKIP;
+  }
+  return CURL_CHUNK_BGN_FUNC_OK;
+}
+
+long chunk_end(void *ptr)
+{
+  chunk_data_t *ch_d =3D ptr;
+  if(ch_d->print_content) {
+    ch_d->print_content =3D 0;
+    printf("-----------------------------------------------------------=
--\n");
+  }
+  if(ch_d->remains =3D=3D 1)
+    printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\n");
+  return CURL_CHUNK_END_FUNC_OK;
+}
+
+int test(char *URL)
+{
+  CURL *handle =3D NULL;
+  CURLcode res =3D 0;
+  chunk_data_t chunk_data =3D {0,0};
+  curl_global_init(CURL_GLOBAL_ALL);
+  handle =3D curl_easy_init();
+  if(!handle) {
+    res =3D CURLE_OUT_OF_MEMORY;
+    goto test_cleanup;
+  }
+
+  test_setopt(handle, CURLOPT_URL, URL);
+  test_setopt(handle, CURLOPT_WILDCARDMATCH, 1L);
+  test_setopt(handle, CURLOPT_CHUNK_BGN_FUNCTION, chunk_bgn);
+  test_setopt(handle, CURLOPT_CHUNK_END_FUNCTION, chunk_end);
+  test_setopt(handle, CURLOPT_CHUNK_DATA, &chunk_data);
+
+  res =3D curl_easy_perform(handle);
+
+test_cleanup:
+  if(handle)
+    curl_easy_cleanup(handle);
+  curl_global_cleanup();
+  return res;
+}
diff --git a/tests/libtest/lib577.c b/tests/libtest/lib577.c
new file mode 100644
index 0000000..8b434f8
--- /dev/null
+++ b/tests/libtest/lib577.c
@@ -0,0 +1,217 @@
+/**********************************************************************=
*******
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ */
+
+#include "test.h"
+
+#include "memdebug.h"
+
+#include "curl_fnmatch.h"
+
+#define MATCH   CURL_FNMATCH_MATCH
+#define NOMATCH CURL_FNMATCH_NOMATCH
+#define ERROR   CURL_FNMATCH_FAIL
+
+#define MAX_PATTERN_L 100
+#define MAX_STRING_L  100
+
+struct testcase {
+  char pattern[MAX_PATTERN_L];
+  char string[MAX_STRING_L];
+  int  result;
+};
+
+static const struct testcase tests[] =3D {
+  /* brackets syntax */
+  { "\\[",                      "[",                      MATCH },
+  { "[",                        "[",                      ERROR },
+  { "[]",                       "[]",                     ERROR },
+  { "[][]",                     "[",                      MATCH },
+  { "[][]",                     "]",                      MATCH },
+  { "[[]",                      "[",                      MATCH },
+  { "[[[]",                     "[",                      MATCH },
+  { "[[[[]",                    "[",                      MATCH },
+  { "[[[[]",                    "[",                      MATCH },
+
+  { "[][[]",                    "]",                      MATCH },
+  { "[][[[]",                   "[",                      MATCH },
+  { "[[]",                      "]",                      NOMATCH },
+
+  { "[a-z]",                    "a",                      MATCH },
+  { "[a-z]",                    "A",                      NOMATCH },
+  { "?[a-z]",                   "?Z",                     NOMATCH },
+  { "[A-Z]",                    "C",                      MATCH },
+  { "[A-Z]",                    "c",                      NOMATCH },
+  { "[0-9]",                    "7",                      MATCH },
+  { "[7-8]",                    "7",                      MATCH },
+  { "[7-]",                     "7",                      MATCH },
+  { "[7-]",                     "-",                      MATCH },
+  { "[7-]",                     "[",                      NOMATCH },
+  { "[a-bA-F]",                 "F",                      MATCH },
+  { "[a-bA-B9]",                "9",                      MATCH },
+  { "[a-bA-B98]",               "8",                      MATCH },
+  { "[a-bA-B98]",               "C",                      NOMATCH },
+  { "[a-bA-Z9]",                "F",                      MATCH },
+  { "[a-bA-Z9]ero*",            "Zero chance.",           MATCH },
+  { "S[a-][x]opho*",            "Saxophone",              MATCH },
+  { "S[a-][x]opho*",            "SaXophone",              NOMATCH },
+  { "S[a-][x]*.txt",            "S-x.txt",                MATCH },
+  { "[\\a-\\b]",                "a",                      MATCH },
+  { "[\\a-\\b]",                "b",                      MATCH },
+  { "[?*[][?*[][?*[]",          "?*[",                    MATCH },
+  { "[][?*-]",                  "]",                      MATCH },
+  { "[][?*-]",                  "[",                      MATCH },
+  { "[][?*-]",                  "?",                      MATCH },
+  { "[][?*-]",                  "*",                      MATCH },
+  { "[][?*-]",                  "-",                      MATCH },
+  { "[]?*-]",                   "-",                      MATCH },
+  { "?/b/c",                    "a/b/c",                  MATCH },
+  { "^_{}~",                    "^_{}~",                  MATCH },
+  { "!#%+,-./01234567889",      "!#%+,-./01234567889",    MATCH },
+  { "PQRSTUVWXYZ]abcdefg",      "PQRSTUVWXYZ]abcdefg",    MATCH },
+  { ":;=3D_at_ABCDEFGHIJKLMNO",      ":;=3D_at_ABCDEFGHIJKLMNO",    MATCH },
+
+  /* negate */
+  { "[!a]",                     "b",                      MATCH },
+  { "[!a]",                     "a",                      NOMATCH },
+  { "[^a]",                     "b",                      MATCH },
+  { "[^a]",                     "a",                      NOMATCH },
+  { "[^a-z0-9A-Z]",             "a",                      NOMATCH },
+  { "[^a-z0-9A-Z]",             "-",                      MATCH },
+  { "curl[!a-z]lib",            "curl lib",               MATCH },
+  { "curl[! ]lib",              "curl lib",               NOMATCH },
+  { "[! ][ ]",                  "  ",                     NOMATCH },
+  { "[! ][ ]",                  "a ",                     MATCH },
+  { "*[^a].t?t",                "a.txt",                  NOMATCH },
+  { "*[^a].t?t",                "ba.txt",                 NOMATCH },
+  { "*[^a].t?t",                "ab.txt",                 MATCH },
+  { "[!?*[]",                   "?",                      NOMATCH },
+  { "[!!]",                     "!",                      NOMATCH },
+  { "[!!]",                     "x",                      MATCH },
+
+  { "[[:alpha:]]",              "a",                      MATCH },
+  { "[[:alpha:]]",              "9",                      NOMATCH },
+  { "[[:alnum:]]",              "a",                      MATCH },
+  { "[[:alnum:]]",              "[",                      NOMATCH },
+  { "[[:alnum:]]",              "]",                      NOMATCH },
+  { "[[:alnum:]]",              "9",                      MATCH },
+  { "[[:digit:]]",              "9",                      MATCH },
+  { "[[:xdigit:]]",             "9",                      MATCH },
+  { "[[:xdigit:]]",             "F",                      MATCH },
+  { "[[:xdigit:]]",             "G",                      NOMATCH },
+  { "[[:upper:]]",              "U",                      MATCH },
+  { "[[:upper:]]",              "u",                      NOMATCH },
+  { "[[:lower:]]",              "l",                      MATCH },
+  { "[[:lower:]]",              "L",                      NOMATCH },
+  { "[[:print:]]",              "L",                      MATCH },
+  { "[[:print:]]",              {'\10'},                  NOMATCH },
+  { "[[:print:]]",              {'\10'},                  NOMATCH },
+  { "[[:space:]]",              " ",                      MATCH },
+  { "[[:space:]]",              "x",                      NOMATCH },
+  { "[[:graph:]]",              " ",                      NOMATCH },
+  { "[[:graph:]]",              "x",                      MATCH },
+  { "[[:blank:]]",              {'\t'},                   MATCH },
+  { "[[:blank:]]",              {' '},                    MATCH },
+  { "[[:blank:]]",              {'\r'},                   NOMATCH },
+  { "[^[:blank:]]",             {'\t'},                   NOMATCH },
+  { "[^[:print:]]",             {'\10'},                  MATCH },
+  { "[[:lower:]][[:lower:]]",   "ll",                     MATCH },
+
+  { "Curl[[:blank:]];-)",       "Curl ;-)",               MATCH },
+  { "*[[:blank:]]*",            " ",                      MATCH },
+  { "*[[:blank:]]*",            "",                       NOMATCH },
+  { "*[[:blank:]]*",            "hi, im_Pavel",           MATCH },
+
+  /* common using */
+  { "filename.dat",             "filename.dat",           MATCH },
+  { "*curl*",                   "lets use curl!!",        MATCH },
+  { "filename.txt",             "filename.dat",           NOMATCH },
+  { "*.txt",                    "text.txt",               MATCH },
+  { "*.txt",                    "a.txt",                  MATCH },
+  { "*.txt",                    ".txt",                   MATCH },
+  { "*.txt",                    "txt",                    NOMATCH },
+  { "??.txt",                   "99.txt",                 MATCH },
+  { "??.txt",                   "a99.txt",                NOMATCH },
+  { "?.???",                    "a.txt",                  MATCH },
+  { "*.???",                    "somefile.dat",           MATCH },
+  { "*.???",                    "photo.jpeg",             NOMATCH },
+  { ".*",                       ".htaccess",              MATCH },
+  { ".*",                       ".",                      MATCH },
+  { ".*",                       "..",                     MATCH },
+
+  /* many stars =3D> one star */
+  { "**.txt",                   "text.txt",               MATCH },
+  { "***.txt",                  "t.txt",                  MATCH },
+  { "****.txt",                 ".txt",                   MATCH },
+
+  /* empty string or pattern */
+  { "",                         "",                       MATCH } ,
+  { "",                         "hello",                  NOMATCH },
+  { "file",                     "",                       NOMATCH  },
+  { "?",                        "",                       NOMATCH },
+  { "*",                        "",                       MATCH },
+  { "x",                        "",                       NOMATCH },
+
+  /* backslash */
+  { "\\",                       "\\",                     ERROR },
+  { "\\\\",                     "\\",                     MATCH },
+  { "\\\\",                     "\\\\",                   NOMATCH },
+  { "\\?",                      "?",                      MATCH },
+  { "\\*",                      "*",                      MATCH },
+  { "?.txt",                    "?.txt",                  MATCH },
+  { "*.txt",                    "*.txt",                  MATCH },
+  { "\\?.txt",                  "?.txt",                  MATCH },
+  { "\\*.txt",                  "*.txt",                  MATCH },
+  { "\\?.txt",                  "x.txt",                  NOMATCH },
+  { "\\*.txt",                  "x.txt",                  NOMATCH },
+  { "\\*\\\\.txt",              "*\\.txt",                MATCH },
+  { "*\\**\\?*\\\\*",           "cc*cc?cc\\cc*cc",        MATCH },
+  { "*\\**\\?*\\\\*",           "cc*cc?cccc",             NOMATCH },
+  { "*\\**\\?*\\\\*",           "cc*cc?cc\\cc*cc",        MATCH },
+  { "*\\?*\\**",                "cc?c*c",                 MATCH },
+  { "*\\?*\\**curl*",           "cc?c*curl",              MATCH },
+  { "*\\?*\\**",                "cc?cc",                  NOMATCH },
+  { "\\\"\\$\\&\\'\\(\\)",      "\"$&'()",                MATCH },
+  { "\\*\\?\\[\\\\\\`\\|",      "*?[\\`|",                MATCH },
+  { "[\\a\\b]c",                "ac",                     MATCH },
+  { "[\\a\\b]c",                "bc",                     MATCH },
+  { "[\\a\\b]d",                "bc",                     NOMATCH },
+  { "[a-bA-B\\?]",              "?",                      MATCH },
+  { "cu[a-ab-b\\r]l",           "curl",                   MATCH },
+  { "[\\a-z]",                  "c",                      MATCH },
+
+  { "?*?*?.*?*",                "abc.c",                  MATCH },
+  { "?*?*?.*?*",                "abcc",                   NOMATCH },
+  { "?*?*?.*?*",                "abc.",                   NOMATCH },
+  { "?*?*?.*?*",                "abc.c++",                MATCH },
+  { "?*?*?.*?*",                "abcdef.c++",             MATCH },
+  { "?*?*?.?",                  "abcdef.c",               MATCH },
+  { "?*?*?.?",                  "abcdef.cd",              NOMATCH },
+
+  { "Lindm=C3=A4tarv",               "Lindm=C3=A4tarv",             MAT=
CH },
+
+  { "",                         "",                       MATCH }
+};
+
+
+int test(char *URL)
+{
+  int testnum =3D sizeof(tests) / sizeof(struct testcase);
+  int i, rc;
+  (void)URL; /* not used */
+  printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D\n");
+  for(i =3D 0; i < testnum; i++) {
+    rc =3D Curl_fnmatch(tests[i].pattern, tests[i].string);
+    if(rc !=3D tests[i].result) {
+      printf("Curl_fnmatch(\"%s\", \"%s\") should return %d (returns %d=
)\n",
+             tests[i].pattern, tests[i].string, tests[i].result, rc);
+    }
+  }
+  printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D\n");
+  return 0;
+}
-- =
1.7.1
------------SfdLspeOyyI1Ljwe3WHAMX
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html
------------SfdLspeOyyI1Ljwe3WHAMX--
Received on 2001-09-17