aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/libcurl/curl_easy_setopt.385
-rw-r--r--docs/libcurl/libcurl-errors.310
-rw-r--r--include/curl/curl.h109
-rw-r--r--lib/Makefile.inc2
-rw-r--r--lib/curl_fnmatch.c410
-rw-r--r--lib/curl_fnmatch.h44
-rw-r--r--lib/fileinfo.c66
-rw-r--r--lib/fileinfo.h33
-rw-r--r--lib/ftp.c261
-rw-r--r--lib/ftp.h13
-rw-r--r--lib/ftplistparser.c1009
-rw-r--r--lib/ftplistparser.h38
-rw-r--r--lib/multi.c38
-rw-r--r--lib/strerror.c6
-rw-r--r--lib/transfer.c49
-rw-r--r--lib/url.c24
-rw-r--r--lib/urldata.h8
-rw-r--r--lib/wildcard.c66
-rw-r--r--lib/wildcard.h58
-rw-r--r--tests/data/Makefile.am1
-rw-r--r--tests/data/test111371
-rw-r--r--tests/data/test1114136
-rw-r--r--tests/data/test1462
-rw-r--r--tests/data/test1492
-rw-r--r--tests/data/test5392
-rw-r--r--tests/data/test57471
-rw-r--r--tests/data/test57579
-rw-r--r--tests/data/test576192
-rw-r--r--tests/data/test57738
-rw-r--r--tests/directories.pm266
-rwxr-xr-xtests/ftpserver.pl107
-rw-r--r--tests/libtest/Makefile.inc9
-rw-r--r--tests/libtest/lib574.c56
-rw-r--r--tests/libtest/lib575.c109
-rw-r--r--tests/libtest/lib576.c107
-rw-r--r--tests/libtest/lib577.c217
36 files changed, 3778 insertions, 16 deletions
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 88b216c3d..295eb749d 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 DNS
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 to a
+file name pattern. The pattern can be specified as part of the \fICURLOPT_URL\fP
+option, using an fnmatch-like pattern (Shell Pattern Matching) in the last 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\fR 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 asterisk have
+no special meaning in a bracket expression. Each bracket expression ends 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, \fBprint\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 function
+(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 transferred
+(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 parameter
+remains contains number of chunks remaining per the transfer. If the feature 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 or
+\fICURL_CHUNK_BGN_FUNC_FAIL\fR to tell libcurl to stop if some error occurred.
+.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 occurred.
+.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 internally 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 error
diff --git a/docs/libcurl/libcurl-errors.3 b/docs/libcurl/libcurl-errors.3
index b4f2940b2..c3c854e90 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 libcurl
version and are currently unused.
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 802136690..d59e01de6 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 = 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_setopt(3) man
+ page for callbacks returning this structure -- some fields are mandatory,
+ 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 before
+ download of an individual chunk started. Note that parameter "remains" 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 after
+ 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 this
+ 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 pattern */
+#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 Identifiers */
+ 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;
@@ -1322,6 +1414,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 e0c091f4a..76247a2d2 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -4,6 +4,7 @@ CSOURCES = 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 = arpa_telnet.h netrc.h file.h timeval.h qssl.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 000000000..a7fe6c980
--- /dev/null
+++ b/lib/curl_fnmatch.c
@@ -0,0 +1,410 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF 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 = 0,
+ CURLFNM_LOOP_BACKSLASH
+} loop_state;
+
+typedef enum {
+ CURLFNM_SCHS_DEFAULT = 0,
+ CURLFNM_SCHS_MAYRANGE,
+ CURLFNM_SCHS_MAYRANGE2,
+ CURLFNM_SCHS_RIGHTBR,
+ CURLFNM_SCHS_RIGHTBRLEFTBR
+} setcharset_state;
+
+typedef enum {
+ CURLFNM_PKW_INIT = 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 = CURLFNM_PKW_INIT;
+#define KEYLEN 10
+ char keyword[KEYLEN] = { 0 };
+ int found = FALSE;
+ int i;
+ register unsigned char *p = *pattern;
+ for(i = 0; !found; i++) {
+ char c = *p++;
+ if(i >= KEYLEN)
+ return SETCHARSET_FAIL;
+ switch(state) {
+ case CURLFNM_PKW_INIT:
+ if(ISALPHA(c) && ISLOWER(c))
+ keyword[i] = c;
+ else if(c == ':')
+ state = CURLFNM_PKW_DDOT;
+ else
+ return 0;
+ break;
+ case CURLFNM_PKW_DDOT:
+ if(c == ']')
+ found = TRUE;
+ else
+ return SETCHARSET_FAIL;
+ }
+ }
+#undef KEYLEN
+
+ *pattern = p; /* move caller's pattern pointer */
+ if(strcmp(keyword, "digit") == 0)
+ charset[CURLFNM_DIGIT] = 1;
+ else if(strcmp(keyword, "alnum") == 0)
+ charset[CURLFNM_ALNUM] = 1;
+ else if(strcmp(keyword, "alpha") == 0)
+ charset[CURLFNM_ALPHA] = 1;
+ else if(strcmp(keyword, "xdigit") == 0)
+ charset[CURLFNM_XDIGIT] = 1;
+ else if(strcmp(keyword, "print") == 0)
+ charset[CURLFNM_PRINT] = 1;
+ else if(strcmp(keyword, "graph") == 0)
+ charset[CURLFNM_GRAPH] = 1;
+ else if(strcmp(keyword, "space") == 0)
+ charset[CURLFNM_SPACE] = 1;
+ else if(strcmp(keyword, "blank") == 0)
+ charset[CURLFNM_BLANK] = 1;
+ else if(strcmp(keyword, "upper") == 0)
+ charset[CURLFNM_UPPER] = 1;
+ else if(strcmp(keyword, "lower") == 0)
+ charset[CURLFNM_LOWER] = 1;
+ else
+ return SETCHARSET_FAIL;
+ return SETCHARSET_OK;
+}
+
+/* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern pointer) */
+static int setcharset(unsigned char **p, unsigned char *charset)
+{
+ setcharset_state state = CURLFNM_SCHS_DEFAULT;
+ unsigned char rangestart = 0;
+ unsigned char lastchar = 0;
+ bool something_found = FALSE;
+ register unsigned char c;
+ for(;;) {
+ c = **p;
+ switch(state){
+ case CURLFNM_SCHS_DEFAULT:
+ if(ISALNUM(c)) { /* ASCII value */
+ rangestart = c;
+ charset[c] = 1;
+ (*p)++;
+ state = CURLFNM_SCHS_MAYRANGE;
+ something_found = TRUE;
+ }
+ else if(c == ']') {
+ if(something_found)
+ return SETCHARSET_OK;
+ else
+ something_found = TRUE;
+ state = CURLFNM_SCHS_RIGHTBR;
+ charset[c] = 1;
+ (*p)++;
+ }
+ else if(c == '[') {
+ char c2 = *((*p)+1);
+ if(c2 == ':') { /* there has to be a keyword */
+ (*p) += 2;
+ if(parsekeyword(p, charset)) {
+ state = CURLFNM_SCHS_DEFAULT;
+ }
+ else
+ return SETCHARSET_FAIL;
+ }
+ else {
+ charset[c] = 1;
+ (*p)++;
+ }
+ something_found = TRUE;
+ }
+ else if(c == '?' || c == '*') {
+ something_found = TRUE;
+ charset[c] = 1;
+ (*p)++;
+ }
+ else if(c == '^' || c == '!') {
+ if(!something_found) {
+ if(charset[CURLFNM_NEGATE]) {
+ charset[c] = 1;
+ something_found = 1;
+ }
+ else
+ charset[CURLFNM_NEGATE] = 1; /* negate charset */
+ }
+ else
+ charset[c] = 1;
+ (*p)++;
+ }
+ else if(c == '\\') {
+ c = *(++(*p));
+ if(ISPRINT((c))) {
+ something_found = TRUE;
+ state = CURLFNM_SCHS_MAYRANGE;
+ charset[c] = 1;
+ rangestart = c;
+ (*p)++;
+ }
+ else
+ return SETCHARSET_FAIL;
+ }
+ else if(c == '\0') {
+ return SETCHARSET_FAIL;
+ }
+ else {
+ charset[c] = 1;
+ (*p)++;
+ something_found = TRUE;
+ }
+ break;
+ case CURLFNM_SCHS_MAYRANGE:
+ if(c == '-'){
+ charset[c] = 1;
+ (*p)++;
+ lastchar = '-';
+ state = CURLFNM_SCHS_MAYRANGE2;
+ }
+ else if(c == '[') {
+ state = CURLFNM_SCHS_DEFAULT;
+ }
+ else if(ISALNUM(c)) {
+ charset[c] = 1;
+ (*p)++;
+ }
+ else if(c == '\\') {
+ c = *(++(*p));
+ if(isprint(c)) {
+ charset[c] = 1;
+ (*p)++;
+ }
+ else
+ return SETCHARSET_FAIL;
+ }
+ else if(c == ']') {
+ return SETCHARSET_OK;
+ }
+ else
+ return SETCHARSET_FAIL;
+ break;
+ case CURLFNM_SCHS_MAYRANGE2:
+ if(c == '\\') {
+ c = *(++(*p));
+ if(!ISPRINT(c))
+ return SETCHARSET_FAIL;
+ }
+ if(c == ']') {
+ return SETCHARSET_OK;
+ }
+ else if(c == '\\') {
+ c = *(++(*p));
+ if(ISPRINT(c)) {
+ charset[c] = 1;
+ state = CURLFNM_SCHS_DEFAULT;
+ (*p)++;
+ }
+ else
+ return SETCHARSET_FAIL;
+ }
+ if(c >= rangestart) {
+ if((ISLOWER(c) && ISLOWER(rangestart)) ||
+ (ISDIGIT(c) && ISDIGIT(rangestart)) ||
+ (ISUPPER(c) && ISUPPER(rangestart))) {
+ charset[lastchar] = 0;
+ rangestart++;
+ while(rangestart++ <= c)
+ charset[rangestart-1] = 1;
+ (*p)++;
+ state = CURLFNM_SCHS_DEFAULT;
+ }
+ else
+ return SETCHARSET_FAIL;
+ }
+ break;
+ case CURLFNM_SCHS_RIGHTBR:
+ if(c == '[') {
+ state = CURLFNM_SCHS_RIGHTBRLEFTBR;
+ charset[c] = 1;
+ (*p)++;
+ }
+ else if(c == ']') {
+ return SETCHARSET_OK;
+ }
+ else if(c == '\0') {
+ return SETCHARSET_FAIL;
+ }
+ else if(ISPRINT(c)){
+ charset[c] = 1;
+ (*p)++;
+ state = CURLFNM_SCHS_DEFAULT;
+ }
+ else
+ return SETCHARSET_FAIL;
+ break;
+ case CURLFNM_SCHS_RIGHTBRLEFTBR:
+ if(c == ']') {
+ return SETCHARSET_OK;
+ }
+ else {
+ state = CURLFNM_SCHS_DEFAULT;
+ charset[c] = 1;
+ (*p)++;
+ }
+ break;
+ }
+ }
+ return SETCHARSET_FAIL;
+}
+
+static int loop(const unsigned char *pattern, const unsigned char *string)
+{
+ loop_state state = CURLFNM_LOOP_DEFAULT;
+ register unsigned char *p = (unsigned char *)pattern;
+ register unsigned char *s = (unsigned char *)string;
+ unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 };
+ int rc = 0;
+
+ for (;;) {
+ switch(state) {
+ case CURLFNM_LOOP_DEFAULT:
+ if(*p == '*') {
+ while(*(p+1) == '*') /* eliminate multiple stars */
+ p++;
+ if(*s == '\0' && *(p+1) == '\0')
+ return CURL_FNMATCH_MATCH;
+ rc = loop(p + 1, s); /* *.txt matches .txt <=> .txt matches .txt */
+ if(rc == 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 == '?') {
+ if(ISPRINT(*s)) {
+ s++;
+ p++;
+ }
+ else if(*s == '\0')
+ return CURL_FNMATCH_NOMATCH;
+ else
+ return CURL_FNMATCH_FAIL; /* cannot deal with other character */
+ }
+ else if(*p == '\0') {
+ if(*s == '\0')
+ return CURL_FNMATCH_MATCH;
+ else
+ return CURL_FNMATCH_NOMATCH;
+ }
+ else if(*p == '\\') {
+ state = CURLFNM_LOOP_BACKSLASH;
+ p++;
+ }
+ else if(*p == '[') {
+ unsigned char *pp = p+1; /* cannot handle with pointer to register */
+ if(setcharset(&pp, charset)) {
+ bool found = FALSE;
+ if(charset[(unsigned int)*s])
+ found = TRUE;
+ else if(charset[CURLFNM_ALNUM])
+ found = ISALNUM(*s);
+ else if(charset[CURLFNM_ALPHA])
+ found = ISALPHA(*s);
+ else if(charset[CURLFNM_DIGIT])
+ found = ISDIGIT(*s);
+ else if(charset[CURLFNM_XDIGIT])
+ found = ISXDIGIT(*s);
+ else if(charset[CURLFNM_PRINT])
+ found = ISPRINT(*s);
+ else if(charset[CURLFNM_SPACE])
+ found = ISSPACE(*s);
+ else if(charset[CURLFNM_UPPER])
+ found = ISUPPER(*s);
+ else if(charset[CURLFNM_LOWER])
+ found = ISLOWER(*s);
+ else if(charset[CURLFNM_BLANK])
+ found = ISBLANK(*s);
+ else if(charset[CURLFNM_GRAPH])
+ found = ISGRAPH(*s);
+
+ if(charset[CURLFNM_NEGATE])
+ found = !found;
+
+ if(found) {
+ p = pp+1;
+ s++;
+ memset(charset, 0, CURLFNM_CHSET_SIZE);
+ }
+ else
+ return CURL_FNMATCH_NOMATCH;
+ }
+ else
+ return CURL_FNMATCH_FAIL;
+ }
+ else {
+ if(*p++ != *s++)
+ return CURL_FNMATCH_NOMATCH;
+ }
+ break;
+ case CURLFNM_LOOP_BACKSLASH:
+ if(ISPRINT(*p)) {
+ if(*p++ == *s++)
+ state = 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 000000000..3ffbc4575
--- /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@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 OF 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
+ * =================================
+ * Implemented with recursive backtracking, if you want to use Curl_fnmatch,
+ * 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, space
+ * 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 000000000..2a184f789
--- /dev/null
+++ b/lib/fileinfo.c
@@ -0,0 +1,66 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF 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 = 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 = 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 = malloc(sizeof(struct curl_fileinfo));
+ if(!ptr)
+ return NULL;
+ *ptr = *src;
+
+ ptr->b_data = 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 000000000..b040ef4ce
--- /dev/null
+++ b/lib/fileinfo.h
@@ -0,0 +1,33 @@
+#ifndef __FILEINFO_H
+#define __FILEINFO_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF 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 95e2a1200..afd15b4d1 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 = Curl_ftpsendf(x,y,z)) != CURLE_OK) \
return result
@@ -1469,8 +1478,14 @@ static CURLcode ftp_state_quote(struct connectdata *conn,
if(ftp->transfer != FTPTRANSFER_BODY)
state(conn, FTP_STOP);
else {
- PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file);
- state(conn, FTP_RETR_SIZE);
+ if(ftpc->known_filesize != -1) {
+ Curl_pgrsSetDownloadSize(data, ftpc->known_filesize);
+ result = ftp_state_post_retr_size(conn, ftpc->known_filesize);
+ }
+ 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 == isBadFtpString(ftp->passwd))
return CURLE_URL_MALFORMAT;
+ conn->proto.ftpc.known_filesize = -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 = -1;
+ }
+
/* get the "raw" path */
path = 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 = 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 = conn->data->state.path;
+ struct WildcardData *wildcard = &(conn->data->wildcard);
+ CURLcode ret = CURLE_OK;
+ struct ftp_wc_tmpdata *ftp_tmp;
+
+ last_slash = strrchr(conn->data->state.path, '/');
+ if(last_slash) {
+ last_slash++;
+ if(last_slash[0] == '\0') {
+ wildcard->state = CURLWC_CLEAN;
+ ret = ftp_parse_url_path(conn);
+ return ret;
+ }
+ else {
+ wildcard->pattern = strdup(last_slash);
+ if (!wildcard->pattern)
+ return CURLE_OUT_OF_MEMORY;
+ last_slash[0] = '\0'; /* cut file from path */
+ }
+ }
+ else { /* there is only 'wildcard pattern' or nothing */
+ if(path[0]) {
+ wildcard->pattern = strdup(path);
+ if (!wildcard->pattern)
+ return CURLE_OUT_OF_MEMORY;
+ path[0] = '\0';
+ }
+ else { /* only list */
+ conn->data->set.wildcardmatch = 0L;
+ ret = ftp_parse_url_path(conn);
+ return ret;
+ }
+ }
+
+ /* program continues only if URL is not ending with slash, allocate needed
+ resources for wildcard transfer */
+
+ /* allocate ftp protocol specific temporary wildcard data */
+ ftp_tmp = malloc(sizeof(struct ftp_wc_tmpdata));
+ if(!ftp_tmp) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* INITIALIZE parselist structure */
+ ftp_tmp->parser = ftp_parselist_data_alloc();
+ if(!ftp_tmp->parser)
+ return CURLE_OUT_OF_MEMORY;
+
+ wildcard->tmp = ftp_tmp; /* put it to the WildcardData tmp pointer */
+ wildcard->tmp_dtor = Curl_ftp_wc_data_dtor;
+
+ /* wildcard does not support NOCWD option (assert it?) */
+ if(conn->data->set.ftp_filemethod == FTPFILE_NOCWD)
+ conn->data->set.ftp_filemethod = FTPFILE_MULTICWD;
+
+ /* try to parse ftp url */
+ ret = ftp_parse_url_path(conn);
+ if(ret) {
+ return ret;
+ }
+
+ /* backup old write_function */
+ ftp_tmp->backup.write_function = conn->data->set.fwrite_func;
+ /* parsing write function (callback included directly from ftplistparser.c) */
+ conn->data->set.fwrite_func = ftp_parselist;
+ /* backup old file descriptor */
+ ftp_tmp->backup.file_descriptor = conn->data->set.out;
+ /* let the writefunc callback know what curl pointer is working with */
+ conn->data->set.out = conn;
+
+ wildcard->path = 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 = &conn->proto.ftpc;
+ struct WildcardData *wildcard = &(conn->data->wildcard);
+ struct ftp_wc_tmpdata *ftp_tmp = wildcard->tmp;
+ char *tmp_path;
+ CURLcode ret = CURLE_OK;
+ long userresponse = 0;
+ switch (wildcard->state) {
+ case CURLWC_INIT:
+ ret = init_wc_data(conn);
+ if(wildcard->state == CURLWC_CLEAN)
+ /* only listing! */
+ break;
+ else
+ wildcard->state = ret ? CURLWC_ERROR : CURLWC_MATCHING;
+ break;
+
+ case CURLWC_MATCHING:
+ /* In this state is LIST response successfully parsed, so lets restore
+ previous WRITEFUNCTION callback and WRITEDATA pointer */
+ ftp_tmp = wildcard->tmp;
+ conn->data->set.fwrite_func = ftp_tmp->backup.write_function;
+ conn->data->set.out = ftp_tmp->backup.file_descriptor;
+ wildcard->state = CURLWC_DOWNLOADING;
+
+ if(ftp_parselist_geterror(ftp_tmp->parser)) {
+ /* error found in LIST parsing */
+ wildcard->state = CURLWC_CLEAN;
+ return wc_statemach(conn);
+ }
+ else if(wildcard->filelist->size == 0) {
+ /* no corresponding file */
+ wildcard->state = CURLWC_CLEAN;
+ return CURLE_REMOTE_FILE_NOT_FOUND;
+ }
+ ret = wc_statemach(conn);
+ break;
+
+ case CURLWC_DOWNLOADING: {
+ /* filelist has at least one file, lets get first one */
+ struct curl_fileinfo *finfo = wildcard->filelist->head->ptr;
+ tmp_path = malloc(strlen(conn->data->state.path) +
+ strlen(finfo->filename) + 1);
+ if(!tmp_path) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ tmp_path[0] = 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 = tmp_path;
+ conn->data->state.path = tmp_path;
+
+ infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename);
+ if(conn->data->set.chunk_bgn) {
+ userresponse = 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 = CURLWC_SKIP;
+ return wc_statemach(conn);
+ break;
+ case CURL_CHUNK_BGN_FUNC_FAIL:
+ return CURLE_CHUNK_FAILED;
+ break;
+ }
+ }
+
+ if(finfo->filetype != CURLFILETYPE_FILE) {
+ wildcard->state = CURLWC_SKIP;
+ return wc_statemach(conn);
+ }
+
+ if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE)
+ ftpc->known_filesize = finfo->size;
+
+ ret = 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, NULL);
+
+ if(wildcard->filelist->size == 0) { /* remains only one file to down. */
+ wildcard->state = 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, NULL);
+ wildcard->state = (wildcard->filelist->size == 0) ?
+ CURLWC_CLEAN : CURLWC_DOWNLOADING;
+ ret = wc_statemach(conn);
+ } break;
+
+ case CURLWC_CLEAN:
+ ret = CURLE_OK;
+ if(ftp_tmp) {
+ ret = ftp_parselist_geterror(ftp_tmp->parser);
+ }
+ wildcard->state = 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 = ftp_parse_url_path(conn);
- if(retcode)
- return retcode;
+ if(conn->data->set.wildcardmatch) {
+ retcode = wc_statemach(conn);
+ if(conn->data->wildcard.state == CURLWC_SKIP ||
+ conn->data->wildcard.state == 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 = ftp_parse_url_path(conn);
+ if(retcode)
+ return retcode;
+ }
retcode = ftp_regular_transfer(conn, done);
diff --git a/lib/ftp.h b/lib/ftp.h
index 7a4f89e18..d8ef34823 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 = 1, /* as defined by RFC1738 */
FTPFILE_NOCWD = 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 wildcard
+ 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 000000000..9d42e8f37
--- /dev/null
+++ b/lib/ftplistparser.c
@@ -0,0 +1,1009 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF 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 = 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 = 0,
+ PL_UNIX_HLINKS_NUMBER
+ } hlinks;
+
+ enum {
+ PL_UNIX_USER_PRESPACE = 0,
+ PL_UNIX_USER_PARSING
+ } user;
+
+ enum {
+ PL_UNIX_GROUP_PRESPACE = 0,
+ PL_UNIX_GROUP_NAME
+ } group;
+
+ enum {
+ PL_UNIX_SIZE_PRESPACE = 0,
+ PL_UNIX_SIZE_NUMBER
+ } size;
+
+ enum {
+ PL_UNIX_TIME_PREPART1 = 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 = 0,
+ PL_UNIX_FILENAME_NAME,
+ PL_UNIX_FILENAME_WINDOWSEOL
+ } filename;
+
+ enum {
+ PL_UNIX_SYMLINK_PRESPACE = 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 = 0,
+ PL_WINNT_TIME,
+ PL_WINNT_DIRORSIZE,
+ PL_WINNT_FILENAME
+} pl_winNT_mainstate;
+
+typedef union {
+ enum {
+ PL_WINNT_TIME_PRESPACE = 0,
+ PL_WINNT_TIME_TIME
+ } time;
+ enum {
+ PL_WINNT_DIRORSIZE_PRESPACE = 0,
+ PL_WINNT_DIRORSIZE_CONTENT
+ } dirorsize;
+ enum {
+ PL_WINNT_FILENAME_PRESPACE = 0,
+ PL_WINNT_FILENAME_CONTENT,
+ PL_WINNT_FILENAME_WINEOL
+ } filename;
+} pl_winNT_substate;
+
+/* This struct is used in wildcard downloading - for parsing LIST response */
+struct ftp_parselist_data {
+ enum {
+ OS_TYPE_UNKNOWN = 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 =
+ 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 = 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 = 0;
+ /* USER */
+ if(str[0] == 'r')
+ permissions |= 1 << 8;
+ else if(str[0] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ if(str[1] == 'w')
+ permissions |= 1 << 7;
+ else if(str[1] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+
+ if(str[2] == 'x')
+ permissions |= 1 << 6;
+ else if(str[2] == 's') {
+ permissions |= 1 << 6;
+ permissions |= 1 << 11;
+ }
+ else if(str[2] == 'S')
+ permissions |= 1 << 11;
+ else if(str[2] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ /* GROUP */
+ if(str[3] == 'r')
+ permissions |= 1 << 5;
+ else if(str[3] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ if(str[4] == 'w')
+ permissions |= 1 << 4;
+ else if(str[4] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ if(str[5] == 'x')
+ permissions |= 1 << 3;
+ else if(str[5] == 's') {
+ permissions |= 1 << 3;
+ permissions |= 1 << 10;
+ }
+ else if(str[5] == 'S')
+ permissions |= 1 << 10;
+ else if(str[5] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ /* others */
+ if(str[6] == 'r')
+ permissions |= 1 << 2;
+ else if(str[6] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ if(str[7] == 'w')
+ permissions |= 1 << 1;
+ else if(str[7] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+ if(str[8] == 'x')
+ permissions |= 1;
+ else if(str[8] == 't') {
+ permissions |= 1;
+ permissions |= 1 << 9;
+ }
+ else if(str[8] == 'T')
+ permissions |= 1 << 9;
+ else if(str[8] != '-')
+ permissions |= FTP_LP_MALFORMATED_PERM;
+
+ return permissions;
+}
+
+static void PL_ERROR(struct connectdata *conn, CURLcode err)
+{
+ struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
+ struct ftp_parselist_data *parser = tmpdata->parser;
+ if(parser->file_data)
+ Curl_fileinfo_dtor(NULL, parser->file_data);
+ parser->file_data = NULL;
+ parser->error = err;
+}
+
+static bool ftp_pl_gettime(struct ftp_parselist_data *parser, char *string)
+{
+ (void)parser;
+ (void)string;
+ /* TODO
+ * There could be possible parse timestamp from server. Leaving unimplemented
+ * for now.
+ * If you want implement this, please add CURLFINFOFLAG_KNOWN_TIME flag 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 = &conn->data->wildcard;
+ struct ftp_wc_tmpdata *tmpdata = wc->tmp;
+ struct curl_llist *llist = wc->filelist;
+ struct ftp_parselist_data *parser = tmpdata->parser;
+ bool add = TRUE;
+
+ /* move finfo pointers to b_data */
+ char *str = finfo->b_data;
+ finfo->filename = str + parser->offsets.filename;
+ finfo->strings.group = parser->offsets.group ?
+ str + parser->offsets.group : NULL;
+ finfo->strings.perm = parser->offsets.perm ?
+ str + parser->offsets.perm : NULL;
+ finfo->strings.target = parser->offsets.symlink_target ?
+ str + parser->offsets.symlink_target : NULL;
+ finfo->strings.time = str + parser->offsets.time;
+ finfo->strings.user = parser->offsets.user ?
+ str + parser->offsets.user : NULL;
+
+ /* get correct fnmatch callback */
+ compare = conn->data->set.fnmatch;
+ if(!compare)
+ compare = Curl_fnmatch;
+
+ /* filter pattern-corresponding filenames */
+ if(compare(wc->pattern, finfo->filename) == 0) {
+ /* discard symlink which is containing multiple " -> " */
+ if((finfo->filetype == CURLFILETYPE_SYMLINK) &&
+ (strstr(finfo->strings.target, " -> "))) {
+ add = FALSE;
+ }
+ }
+ else {
+ add = FALSE;
+ }
+
+ if(add) {
+ if(!Curl_llist_insert_next(llist, llist->tail, finfo)) {
+ Curl_fileinfo_dtor(NULL, finfo);
+ tmpdata->parser->file_data = NULL;
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ Curl_fileinfo_dtor(NULL, finfo);
+ }
+
+ tmpdata->parser->file_data = NULL;
+ return CURLE_OK;
+}
+
+size_t ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr)
+{
+ size_t bufflen = size*nmemb;
+ struct connectdata *conn = (struct connectdata *)connptr;
+ struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp;
+ struct ftp_parselist_data *parser = tmpdata->parser;
+ struct curl_fileinfo *finfo;
+ unsigned long i = 0;
+ CURLcode rc;
+
+ if(parser->error) { /* error in previous call */
+ /* scenario:
+ * 1. call => OK..
+ * 2. call => OUT_OF_MEMORY (or other error)
+ * 3. (last) call => is skipped RIGHT HERE and the error is hadled later
+ * in wc_statemach()
+ */
+ return bufflen;
+ }
+
+ if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
+ /* considering info about FILE response format */
+ parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
+ OS_TYPE_WIN_NT : OS_TYPE_UNIX;
+ }
+
+ while(i < bufflen) { /* FSM */
+
+ char c = buffer[i];
+ if(!parser->file_data) { /* tmp file data is not allocated yet */
+ parser->file_data = Curl_fileinfo_alloc();
+ if(!parser->file_data) {
+ parser->error = CURLE_OUT_OF_MEMORY;
+ return bufflen;
+ }
+ parser->file_data->b_data = malloc(FTP_BUFFER_ALLOCSIZE);
+ if(!parser->file_data->b_data) {
+ PL_ERROR(conn, CURLE_OUT_OF_MEMORY);
+ return bufflen;
+ }
+ parser->file_data->b_size = FTP_BUFFER_ALLOCSIZE;
+ parser->item_offset = 0;
+ parser->item_length = 0;
+ }
+
+ finfo = parser->file_data;
+ finfo->b_data[finfo->b_used++] = buffer[i];
+
+ if(finfo->b_used >= finfo->b_size - 1) {
+ /* if it is important, extend buffer space for file data */
+ char *tmp = realloc(finfo->b_data,
+ finfo->b_size + FTP_BUFFER_ALLOCSIZE);
+ if(tmp) {
+ finfo->b_size += FTP_BUFFER_ALLOCSIZE;
+ finfo->b_data = tmp;
+ }
+ else {
+ Curl_fileinfo_dtor(NULL, parser->file_data);
+ parser->file_data = NULL;
+ }
+ }
+
+ switch (parser->os_type) {
+ case OS_TYPE_UNIX:
+ switch (parser->state.UNIX.main) {
+ case PL_UNIX_FILETYPE:
+ switch (c) {
+ case '-':
+ finfo->filetype = CURLFILETYPE_FILE;
+ break;
+ case 'd':
+ finfo->filetype = CURLFILETYPE_DIRECTORY;
+ break;
+ case 'l':
+ finfo->filetype = CURLFILETYPE_SYMLINK;
+ break;
+ case 'p':
+ finfo->filetype = CURLFILETYPE_NAMEDPIPE;
+ break;
+ case 's':
+ finfo->filetype = CURLFILETYPE_SOCKET;
+ break;
+ case 'c':
+ finfo->filetype = CURLFILETYPE_DEVICE_CHAR;
+ break;
+ case 'b':
+ finfo->filetype = CURLFILETYPE_DEVICE_BLOCK;
+ break;
+ case 'D':
+ finfo->filetype = CURLFILETYPE_DOOR;
+ break;
+ default:
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ break;
+ }
+ parser->state.UNIX.main = PL_UNIX_PERMISSION;
+ parser->item_length = 0;
+ parser->item_offset = 1;
+ break;
+ case PL_UNIX_PERMISSION:
+ parser->item_length++;
+ if(parser->item_length <= 9) {
+ if(!strchr("rwx-tTsS", c)) {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ }
+ else if(parser->item_length == 10) {
+ int32_t perm;
+ if(c != ' ') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ finfo->b_data[10] = 0; /* terminate permissions */
+ perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
+ if(perm & FTP_LP_MALFORMATED_PERM) {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM;
+ parser->file_data->perm = perm;
+ parser->offsets.perm = parser->item_offset;
+
+ parser->item_length = 0;
+ parser->state.UNIX.main = PL_UNIX_HLINKS;
+ parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE;
+ }
+ break;
+ case PL_UNIX_HLINKS:
+ switch(parser->state.UNIX.sub.hlinks) {
+ case PL_UNIX_HLINKS_PRESPACE:
+ if(c != ' ') {
+ if(c >= '0' && c <= '9') {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.hlinks = 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 == ' ') {
+ char *p;
+ long int hlinks;
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
+ if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
+ parser->file_data->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
+ parser->file_data->hardlinks = hlinks;
+ }
+ parser->item_length = 0;
+ parser->item_offset = 0;
+ parser->state.UNIX.main = PL_UNIX_USER;
+ parser->state.UNIX.sub.user = 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 != ' ') {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
+ }
+ break;
+ case PL_UNIX_USER_PARSING:
+ parser->item_length++;
+ if(c == ' ') {
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ parser->offsets.user = parser->item_offset;
+ parser->state.UNIX.main = PL_UNIX_GROUP;
+ parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
+ parser->item_offset = 0;
+ parser->item_length = 0;
+ }
+ break;
+ }
+ break;
+ case PL_UNIX_GROUP:
+ switch(parser->state.UNIX.sub.group) {
+ case PL_UNIX_GROUP_PRESPACE:
+ if(c != ' ') {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
+ }
+ break;
+ case PL_UNIX_GROUP_NAME:
+ parser->item_length++;
+ if(c == ' ') {
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ parser->offsets.group = parser->item_offset;
+ parser->state.UNIX.main = PL_UNIX_SIZE;
+ parser->state.UNIX.sub.group = PL_UNIX_SIZE_PRESPACE;
+ parser->item_offset = 0;
+ parser->item_length = 0;
+ }
+ break;
+ }
+ break;
+ case PL_UNIX_SIZE:
+ switch(parser->state.UNIX.sub.size) {
+ case PL_UNIX_SIZE_PRESPACE:
+ if(c != ' ') {
+ if(c >= '0' && c <= '9') {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.size = 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 == ' ') {
+ char *p;
+ curl_off_t fsize;
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10);
+ if(p[0] == '\0' && fsize != CURL_LLONG_MAX &&
+ fsize != CURL_LLONG_MIN) {
+ parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
+ parser->file_data->size = fsize;
+ }
+ parser->item_length = 0;
+ parser->item_offset = 0;
+ parser->state.UNIX.main = PL_UNIX_TIME;
+ parser->state.UNIX.sub.time = 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 != ' ') {
+ if(ISALNUM(c)) {
+ parser->item_offset = finfo->b_used -1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.time = 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 == ' ') {
+ parser->state.UNIX.sub.size = PL_UNIX_TIME_PREPART2;
+ }
+ else if(!ISALNUM(c) && c != '.') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ break;
+ case PL_UNIX_TIME_PREPART2:
+ parser->item_length++;
+ if(c != ' ') {
+ if(ISALNUM(c)) {
+ parser->state.UNIX.sub.time = 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 == ' ') {
+ parser->state.UNIX.sub.size = PL_UNIX_TIME_PREPART3;
+ }
+ else if(!ISALNUM(c) && c != '.') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ break;
+ case PL_UNIX_TIME_PREPART3:
+ parser->item_length++;
+ if(c != ' ') {
+ if(ISALNUM(c)) {
+ parser->state.UNIX.sub.time = 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 == ' ') {
+ finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
+ parser->offsets.time = parser->item_offset;
+ if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
+ parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
+ }
+ if(finfo->filetype == CURLFILETYPE_SYMLINK) {
+ parser->state.UNIX.main = PL_UNIX_SYMLINK;
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE;
+ }
+ else {
+ parser->state.UNIX.main = PL_UNIX_FILENAME;
+ parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE;
+ }
+ }
+ else if(!ISALNUM(c) && c != '.' && c != ':') {
+ 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 != ' ') {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
+ }
+ break;
+ case PL_UNIX_FILENAME_NAME:
+ parser->item_length++;
+ if(c == '\r') {
+ parser->item_length--;
+ parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
+ }
+ else if(c == '\n') {
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ parser->offsets.filename = parser->item_offset;
+ parser->state.UNIX.main = PL_UNIX_FILETYPE;
+ rc = ftp_pl_insert_finfo(conn, finfo);
+ if(rc) {
+ PL_ERROR(conn, rc);
+ return bufflen;
+ }
+ }
+ break;
+ case PL_UNIX_FILENAME_WINDOWSEOL:
+ if(c == '\n') {
+ finfo->b_data[parser->item_offset + parser->item_length] = 0;
+ parser->offsets.filename = parser->item_offset;
+ parser->state.UNIX.main = PL_UNIX_FILETYPE;
+ rc = 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 != ' ') {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
+ }
+ break;
+ case PL_UNIX_SYMLINK_NAME:
+ parser->item_length++;
+ if(c == ' ') {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1;
+ }
+ else if(c == '\r' || c == '\n') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ break;
+ case PL_UNIX_SYMLINK_PRETARGET1:
+ parser->item_length++;
+ if(c == '-') {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2;
+ }
+ else if(c == '\r' || c == '\n') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ else {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
+ }
+ break;
+ case PL_UNIX_SYMLINK_PRETARGET2:
+ parser->item_length++;
+ if(c == '>') {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3;
+ }
+ else if(c == '\r' || c == '\n') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ else {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
+ }
+ break;
+ case PL_UNIX_SYMLINK_PRETARGET3:
+ parser->item_length++;
+ if(c == ' ') {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
+ /* now place where is symlink following */
+ finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
+ parser->offsets.filename = parser->item_offset;
+ parser->item_length = 0;
+ parser->item_offset = 0;
+ }
+ else if(c == '\r' || c == '\n') {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ else {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
+ }
+ break;
+ case PL_UNIX_SYMLINK_PRETARGET4:
+ if(c != '\r' && c != '\n') {
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ }
+ else {
+ PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST);
+ return bufflen;
+ }
+ break;
+ case PL_UNIX_SYMLINK_TARGET:
+ parser->item_length ++;
+ if(c == '\r') {
+ parser->item_length --;
+ parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
+ }
+ else if(c == '\n') {
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ parser->offsets.symlink_target = parser->item_offset;
+ rc = ftp_pl_insert_finfo(conn, finfo);
+ if(rc) {
+ PL_ERROR(conn, rc);
+ return bufflen;
+ }
+ parser->state.UNIX.main = PL_UNIX_FILETYPE;
+ }
+ break;
+ case PL_UNIX_SYMLINK_WINDOWSEOL:
+ if(c == '\n') {
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ parser->offsets.symlink_target = parser->item_offset;
+ rc = ftp_pl_insert_finfo(conn, finfo);
+ if(rc) {
+ PL_ERROR(conn, rc);
+ return bufflen;
+ }
+ parser->state.UNIX.main = 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 == 9) {
+ if(c == ' ') {
+ parser->state.NT.main = PL_WINNT_TIME;
+ parser->state.NT.sub.time = 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 = PL_WINNT_TIME_TIME;
+ }
+ break;
+ case PL_WINNT_TIME_TIME:
+ if(c == ' ') {
+ parser->offsets.time = parser->item_offset;
+ finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
+ parser->state.NT.main = PL_WINNT_DIRORSIZE;
+ parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
+ parser->item_length = 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 == ' ') {
+
+ }
+ else {
+ parser->item_offset = finfo->b_used - 1;
+ parser->item_length = 1;
+ parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
+ }
+ break;
+ case PL_WINNT_DIRORSIZE_CONTENT:
+ parser->item_length ++;
+ if(c == ' ') {
+ finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
+ if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
+ finfo->filetype = CURLFILETYPE_DIRECTORY;
+ finfo->size = 0;
+ }
+ else {
+ char *endptr;
+ finfo->size = curlx_strtoofft(finfo->b_data + parser->item_offset,
+ &endptr, 10);
+ if(!*endptr) {
+ if(finfo->size < CURL_LLONG_MAX &&
+ finfo->size > CURL_LLONG_MIN) {
+
+ }
+ else if(finfo->size == CURL_LLONG_MAX ||
+ finfo->size == CURL_LLONG_MIN) {
+ if(errno == 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 = CURLFILETYPE_FILE;
+ }
+
+ parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE;
+ parser->item_length = 0;
+ parser->state.NT.main = PL_WINNT_FILENAME;
+ parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE;
+ }
+ break;
+ }
+ break;
+ case PL_WINNT_FILENAME:
+ switch (parser->state.NT.sub.filename) {
+ case PL_WINNT_FILENAME_PRESPACE:
+ if(c != ' ') {
+ parser->item_offset = finfo->b_used -1;
+ parser->item_length = 1;
+ parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
+ }
+ break;
+ case PL_WINNT_FILENAME_CONTENT:
+ parser->item_length++;
+ if(c == '\r') {
+ parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
+ finfo->b_data[finfo->b_used - 1] = 0;
+ }
+ else if(c == '\n') {
+ parser->offsets.filename = parser->item_offset;
+ finfo->b_data[finfo->b_used - 1] = 0;
+ parser->offsets.filename = parser->item_offset;
+ rc = ftp_pl_insert_finfo(conn, finfo);
+ if(rc) {
+ PL_ERROR(conn, rc);
+ return bufflen;
+ }
+ parser->state.NT.main = PL_WINNT_DATE;
+ parser->state.NT.sub.filename = 0;
+ }
+ break;
+ case PL_WINNT_FILENAME_WINEOL:
+ if(c == '\n') {
+ parser->offsets.filename = parser->item_offset;
+ rc = ftp_pl_insert_finfo(conn, finfo);
+ if(rc) {
+ PL_ERROR(conn, rc);
+ return bufflen;
+ }
+ parser->state.NT.main = PL_WINNT_DATE;
+ parser->state.NT.sub.filename = 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 000000000..20d75efa2
--- /dev/null
+++ b/lib/ftplistparser.h
@@ -0,0 +1,38 @@
+#ifndef __FTPLISTPARSER_H_
+#define __FTPLISTPARSER_H_
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF 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 *connptr);
+
+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 476cb8138..34d7eccbb 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -1128,6 +1128,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(CURLE_OK == easy->result) {
if(!dophase_done) {
+ /* some steps needed for wildcard matching */
+ if(easy->easy_handle->set.wildcardmatch) {
+ struct WildcardData *wc = &easy->easy_handle->wildcard;
+ if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
+ /* skip some states if it is important */
+ Curl_done(&easy->easy_conn, CURLE_OK, FALSE);
+ multistate(easy, CURLM_STATE_DONE);
+ result = CURLM_CALL_MULTI_PERFORM;
+ break;
+ }
+ }
/* DO was not completed in one function call, we must continue
DOING... */
multistate(easy, CURLM_STATE_DOING);
@@ -1338,7 +1349,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
easy->easy_conn->writechannel_inuse = 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 possibly
* know if the connection is in a good shape or not now. Unless it is
@@ -1449,6 +1460,16 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
easy->easy_conn = NULL;
}
+ if(easy->easy_handle->set.wildcardmatch) {
+ if(easy->easy_handle->wildcard.state != CURLWC_DONE) {
+ /* if a wildcard is set and we are not ending -> lets start again
+ with CURLM_STATE_INIT */
+ result = CURLM_CALL_MULTI_PERFORM;
+ multistate(easy, CURLM_STATE_INIT);
+ break;
+ }
+ }
+
/* after we have DONE what we're supposed to do, go COMPLETED, and
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=multi->easy.next;
while(easy != &multi->easy) {
CURLMcode result;
+ struct WildcardData *wc = &easy->easy_handle->wildcard;
+
+ if(easy->easy_handle->set.wildcardmatch) {
+ if(!wc->filelist) {
+ CURLcode ret = Curl_wildcard_init(wc); /* init wildcard structures */
+ if(ret)
+ return CURLM_OUT_OF_MEMORY;
+ }
+ }
do
result = multi_runsingle(multi, easy);
while (CURLM_CALL_MULTI_PERFORM == result);
+ if(easy->easy_handle->set.wildcardmatch) {
+ /* destruct wildcard structures if it is needed */
+ if(wc->state == CURLWC_DONE || result)
+ Curl_wildcard_dtor(wc);
+ }
+
if(result)
returncode = result;
diff --git a/lib/strerror.c b/lib/strerror.c
index 673e89c1f..6fde47702 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 c9234a79d..823d41a4b 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -2005,12 +2005,7 @@ CURLcode Curl_retry_request(struct connectdata *conn,
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 = Curl_do(&conn, &do_done);
if(res == CURLE_OK) {
+ if(conn->data->set.wildcardmatch) {
+ if(conn->data->wildcard.state == CURLWC_DONE ||
+ conn->data->wildcard.state == CURLWC_SKIP) {
+ /* keep connection open for application to use the socket */
+ conn->bits.close = FALSE;
+ res = Curl_done(&conn, CURLE_OK, FALSE);
+ break;
+ }
+ }
res = Transfer(conn); /* now fetch that URL please */
if((res == CURLE_OK) || (res == CURLE_RECV_ERROR)) {
bool retry = 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 = Curl_wildcard_init(&data->wildcard);
+ if(res)
+ return res;
+
+ res = Curl_do_perform(data);
+ if(res) {
+ Curl_wildcard_dtor(&data->wildcard);
+ return res;
+ }
+
+ /* wildcard loop */
+ while(!res && data->wildcard.state != CURLWC_DONE)
+ res = Curl_do_perform(data);
+
+ Curl_wildcard_dtor(&data->wildcard);
+
+ /* wildcard download finished or failed */
+ data->wildcard.state = CURLWC_INIT;
+ return res;
+}
+
+/*
* Curl_setup_transfer() is called to setup some basic properties for the
* upcoming transfer.
*/
diff --git a/lib/url.c b/lib/url.c
index 1db65cab7..cc73750e0 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -770,6 +770,10 @@ CURLcode Curl_init_userdefined(struct UserDefined *set)
res = setstropt(&set->str[STRING_SSL_CAPATH], (char *) CURL_CA_PATH);
#endif
+ set->wildcardmatch = 0L;
+ set->chunk_bgn = ZERO_NULL;
+ set->chunk_end = ZERO_NULL;
+
return res;
}
@@ -838,6 +842,9 @@ CURLcode Curl_open(struct SessionHandle **curl)
data->progress.flags |= PGRS_HIDE;
data->state.current_speed = -1; /* init to negative == impossible */
+ data->wildcard.state = CURLWC_INIT;
+ data->wildcard.filelist = NULL;
+ data->set.fnmatch = ZERO_NULL;
/* This no longer creates a connection cache here. It is instead made on
the first call to curl_easy_perform() or when the handle is added to a
multi stack. */
@@ -2455,6 +2462,23 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
/* Set the user defined RTP write function */
data->set.fwrite_rtp = va_arg(param, curl_write_callback);
break;
+
+ case CURLOPT_WILDCARDMATCH:
+ data->set.wildcardmatch = va_arg(param, long);
+ break;
+ case CURLOPT_CHUNK_BGN_FUNCTION:
+ data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback);
+ break;
+ case CURLOPT_CHUNK_END_FUNCTION:
+ data->set.chunk_end = va_arg(param, curl_chunk_end_callback);
+ break;
+ case CURLOPT_FNMATCH_FUNCTION:
+ data->set.fnmatch = va_arg(param, curl_fnmatch_callback);
+ break;
+ case CURLOPT_CHUNK_DATA:
+ data->wildcard.customptr = va_arg(param, void *);
+ break;
+
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_FAILED_INIT; /* correct this */
diff --git a/lib/urldata.h b/lib/urldata.h
index 94e904fe5..477e4599e 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -156,6 +156,7 @@
#include "ssh.h"
#include "http.h"
#include "rtsp.h"
+#include "wildcard.h"
#ifdef HAVE_GSSAPI
# ifdef HAVE_GSSGNU
@@ -1419,6 +1420,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 corresponds
+ to pattern (e.g. if WILDCARDMATCH is on) */
};
struct Names {
@@ -1460,6 +1467,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 encoding */
diff --git a/lib/wildcard.c b/lib/wildcard.c
new file mode 100644
index 000000000..43ac546b2
--- /dev/null
+++ b/lib/wildcard.c
@@ -0,0 +1,66 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF 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 = 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 = NULL;
+ }
+
+ if(wc->filelist) {
+ Curl_llist_destroy(wc->filelist, NULL);
+ wc->filelist = NULL;
+ }
+
+ if(wc->path) {
+ free(wc->path);
+ wc->path = NULL;
+ }
+
+ if(wc->pattern) {
+ free(wc->pattern);
+ wc->pattern = NULL;
+ }
+ wc->customptr = NULL;
+}
diff --git a/lib/wildcard.h b/lib/wildcard.h
new file mode 100644
index 000000000..1a1c1bb0c
--- /dev/null
+++ b/lib/wildcard.h
@@ -0,0 +1,58 @@
+#ifndef __WILDCARD_H
+#define __WILDCARD_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * 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 OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include <curl/curl.h>
+
+/* list of wildcard process states */
+typedef enum {
+ CURLWC_INIT = 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 == CURLWC_DONE wildcard loop 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 dabe3d195..a8cf5d7a6 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -60,6 +60,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46 \
test1072 test1073 test1074 test1075 test1076 test1077 test1078 test1079 \
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 test1096 \
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 000000000..6ff1d1951
--- /dev/null
+++ b/tests/data/test1113
@@ -0,0 +1,71 @@
+<testcase>
+<info>
+<keywords>
+FTP
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data mode="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="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 000000000..8eee4293b
--- /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_FUNCTION (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="text">
+=============================================================
+Remains: 12
+Filename: .
+Size: 0B
+Time: 04-27-10 05:12AM
+Filetype: directory
+=============================================================
+Remains: 11
+Filename: ..
+Size: 0B
+Time: 04-23-10 03:12AM
+Filetype: directory
+=============================================================
+Remains: 10
+Filename: chmod1
+Size: 38B
+Time: 01-11-10 10:00AM
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 444
+-------------------------------------------------------------
+=============================================================
+Remains: 9
+Filename: chmod2
+Size: 38B
+Time: 02-01-10 08:00AM
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 666
+-------------------------------------------------------------
+=============================================================
+Remains: 8
+Filename: chmod3
+Size: 38B
+Time: 02-01-10 08:00AM
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 777
+-------------------------------------------------------------
+=============================================================
+Remains: 7
+Filename: chmod4
+Size: 0B
+Time: 05-04-10 04:31AM
+Filetype: directory
+=============================================================
+Remains: 6
+Filename: chmod5
+Size: 0B
+Time: 05-04-10 04:31AM
+Filetype: directory
+=============================================================
+Remains: 5
+Filename: empty_file.dat
+Size: 0B
+Time: 04-27-10 11:01AM
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+-------------------------------------------------------------
+=============================================================
+Remains: 4
+Filename: file.txt
+Size: 35B
+Time: 04-27-10 11:01AM
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This is content of file "file.txt"
+-------------------------------------------------------------
+=============================================================
+Remains: 3
+Filename: .NeXT
+Size: 0B
+Time: 01-23-05 02:05AM
+Filetype: directory
+=============================================================
+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 #
+-------------------------------------------------------------
+=============================================================
+Remains: 1
+Filename: weirddir.txt
+Size: 0B
+Time: 04-23-10 03:12AM
+Filetype: directory
+=============================================================
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test146 b/tests/data/test146
index e04f8f04d..3cd4bd5aa 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 bdbdcc77f..f7973f497 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 f1ad70107..2406c5473 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 000000000..9d500457a
--- /dev/null
+++ b/tests/data/test574
@@ -0,0 +1,71 @@
+<testcase>
+<info>
+<keywords>
+FTP
+wildcardmatch
+ftplistparser
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data mode="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="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 000000000..c460467d2
--- /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="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 000000000..4d4b90c80
--- /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_FUNCTION (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="text">
+=============================================================
+Remains: 14
+Filename: .
+Permissions: rwxrwxrwx (parsed => 777)
+Size: 20480B
+User: ftp-default
+Group: ftp-default
+Time: Apr 27 5:12
+Filetype: directory
+=============================================================
+Remains: 13
+Filename: ..
+Permissions: rwxrwxrwx (parsed => 777)
+Size: 20480B
+User: ftp-default
+Group: ftp-default
+Time: Apr 23 3:12
+Filetype: directory
+=============================================================
+Remains: 12
+Filename: chmod1
+Permissions: r--r--r-- (parsed => 444)
+Size: 38B
+User: ftp-default
+Group: ftp-default
+Time: Jan 11 10:00
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 444
+-------------------------------------------------------------
+=============================================================
+Remains: 11
+Filename: chmod2
+Permissions: rw-rw-rw- (parsed => 666)
+Size: 38B
+User: ftp-default
+Group: ftp-default
+Time: Feb 1 8:00
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 666
+-------------------------------------------------------------
+=============================================================
+Remains: 10
+Filename: chmod3
+Permissions: rwxrwxrwx (parsed => 777)
+Size: 38B
+User: ftp-default
+Group: ftp-default
+Time: Feb 1 8:00
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+This file should have permissions 777
+-------------------------------------------------------------
+=============================================================
+Remains: 9
+Filename: chmod4
+Permissions: --S--S--t (parsed => 7001)
+Size: 4096B
+User: ftp-default
+Group: ftp-default
+Time: May 4 4:31
+Filetype: directory
+=============================================================
+Remains: 8
+Filename: chmod5
+Permissions: --s--s--T (parsed => 7110)
+Size: 4096B
+User: ftp-default
+Group: ftp-default
+Time: May 4 4:31
+Filetype: directory
+=============================================================
+Remains: 7
+Filename: empty_file.dat
+Permissions: rw-r--r-- (parsed => 644)
+Size: 0B
+User: ftp-default
+Group: ftp-default
+Time: Apr 27 11:01
+Filetype: regular file
+Content:
+-------------------------------------------------------------
+-------------------------------------------------------------
+=============================================================
+Remains: 6
+Filename: file.txt
+Permissions: rw-r--r-- (parsed => 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"
+-------------------------------------------------------------
+=============================================================
+Remains: 5
+Filename: link
+Permissions: rwxrwxrwx (parsed => 777)
+Size: 0B
+User: ftp-default
+Group: ftp-default
+Time: Jan 6 4:42
+Filetype: symlink
+Target: file.txt
+=============================================================
+Remains: 4
+Filename: link_absolute
+Permissions: rwxrwxrwx (parsed => 777)
+Size: 0B
+User: ftp-default
+Group: ftp-default
+Time: Jan 6 4:45
+Filetype: symlink
+Target: /data/ftp/file.txt
+=============================================================
+Remains: 3
+Filename: .NeXT
+Permissions: rwxrwxrwx (parsed => 777)
+Size: 4096B
+User: ftp-default
+Group: ftp-default
+Time: Jan 23 2:05
+Filetype: directory
+=============================================================
+Remains: 2
+Filename: someothertext.txt
+Permissions: rw-r--r-- (parsed => 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 #
+-------------------------------------------------------------
+=============================================================
+Remains: 1
+Filename: weirddir.txt
+Permissions: rwxr-xrwx (parsed => 757)
+Size: 4096B
+User: ftp-default
+Group: ftp-default
+Time: Apr 23 3:12
+Filetype: directory
+=============================================================
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test577 b/tests/data/test577
new file mode 100644
index 000000000..5f1898f70
--- /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="text">
+===========================
+===========================
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/directories.pm b/tests/directories.pm
new file mode 100644
index 000000000..6f20e6feb
--- /dev/null
+++ b/tests/directories.pm
@@ -0,0 +1,266 @@
+%file_chmod1 = (
+ 'name' => 'chmod1',
+ 'content' => "This file should have permissions 444\n",
+ 'perm' => 'r--r--r--',
+ 'time' => 'Jan 11 10:00',
+ 'dostime' => '01-11-10 10:00AM',
+);
+
+%file_chmod2 = (
+ 'name' => 'chmod2',
+ 'content' => "This file should have permissions 666\n",
+ 'perm' => 'rw-rw-rw-',
+ 'time' => 'Feb 1 8:00',
+ 'dostime' => '02-01-10 08:00AM',
+);
+
+%file_chmod3 = (
+ 'name' => 'chmod3',
+ 'content' => "This file should have permissions 777\n",
+ 'perm' => 'rwxrwxrwx',
+ 'time' => 'Feb 1 8:00',
+ 'dostime' => '02-01-10 08:00AM',
+);
+
+%file_chmod4 = (
+ 'type' => 'd',
+ 'name' => 'chmod4',
+ 'content' => "This file should have permissions 001\n",
+ 'perm' => '--S--S--t',
+ 'time' => 'May 4 4:31',
+ 'dostime' => '05-04-10 04:31AM'
+);
+
+%file_chmod5 = (
+ 'type' => 'd',
+ 'name' => 'chmod5',
+ 'content' => "This file should have permissions 110\n",
+ 'perm' => '--s--s--T',
+ 'time' => 'May 4 4:31',
+ 'dostime' => '05-04-10 04:31AM'
+);
+
+%link_link = (
+ 'type' => 'l',
+ 'name' => 'link -> file.txt',
+ 'size' => '8',
+ 'perm' => 'rwxrwxrwx',
+ 'time' => 'Jan 6 4:42'
+);
+
+%link_link_absolute = (
+ 'type' => 'l',
+ 'name' => 'link_absolute -> /data/ftp/file.txt',
+ 'size' => '15',
+ 'perm' => 'rwxrwxrwx',
+ 'time' => 'Jan 6 4:45'
+);
+
+%dir_dot = (
+ 'type' => "d",
+ 'name' => ".",
+ 'hlink' => "4",
+ 'time' => "Apr 27 5:12",
+ 'size' => "20480",
+ 'dostime' => "04-27-10 05:12AM",
+ 'perm' => "rwxrwxrwx"
+);
+
+%dir_ddot = (
+ 'type' => "d",
+ 'name' => "..",
+ 'hlink' => "4",
+ 'size' => "20480",
+ 'time' => "Apr 23 3:12",
+ 'dostime' => "04-23-10 03:12AM",
+ 'perm' => "rwxrwxrwx"
+);
+
+%dir_weirddir_txt = (
+ 'type' => "d",
+ 'name' => "weirddir.txt",
+ 'hlink' => "2",
+ 'size' => "4096",
+ 'time' => "Apr 23 3:12",
+ 'dostime' => "04-23-10 03:12AM",
+ 'perm' => "rwxr-xrwx"
+);
+
+%dir_UNIX = (
+ 'type' => "d",
+ 'name' => "UNIX",
+ 'hlink' => "11",
+ 'size' => "4096",
+ 'time' => "Nov 01 2008",
+ 'dostime' => "11-01-08 11:11AM",
+ 'perm' => "rwx--x--x"
+);
+
+%dir_DOS = (
+ 'type' => "d",
+ 'name' => "DOS",
+ 'hlink' => "11",
+ 'size' => "4096",
+ 'time' => "Nov 01 2008",
+ 'dostime' => "11-01-08 11:11AM",
+ 'perm' => "rwx--x--x"
+);
+
+%dir_dot_NeXT = (
+ 'type' => "d",
+ 'name' => ".NeXT",
+ 'hlink' => "4",
+ 'size' => "4096",
+ 'time' => "Jan 23 2:05",
+ 'dostime' => "01-23-05 02:05AM",
+ 'perm' => "rwxrwxrwx"
+);
+
+%file_empty_file_dat = (
+ 'name' => "empty_file.dat",
+ 'content' => "",
+ 'perm' => "rw-r--r--",
+ 'time' => "Apr 27 11:01",
+ 'dostime' => "04-27-10 11:01AM"
+);
+
+%file_file_txt = (
+ 'name' => "file.txt",
+ 'content' => "This is content of file \"file.txt\"\n",
+ 'time' => "Apr 27 11:01",
+ 'dostime' => "04-27-10 11:01AM",
+ 'perm' => "rw-r--r--"
+);
+
+%file_someothertext_txt = (
+ 'name' => "someothertext.txt",
+ 'content' => "Some junk ;-) This file does not really exist.\n",
+ 'time' => "Apr 27 11:01",
+ 'dostime' => "04-27-10 11:01AM",
+ 'perm' => "rw-r--r--"
+);
+
+%lists = (
+ '/fully_simulated/' => {
+ 'files' => [ \%dir_dot, \%dir_ddot, \%dir_DOS, \%dir_UNIX ],
+ 'eol' => "\r\n",
+ 'type' => "unix"
+ },
+ '/fully_simulated/UNIX/' => {
+ 'files' => [ \%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' => "\r\n",
+ 'type' => 'unix'
+ },
+ '/fully_simulated/DOS/' => {
+ 'files' => [ \%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_weirddir_txt ],
+ 'eol' => "\r\n",
+ 'type' => 'dos'
+ }
+);
+
+sub ftp_createcontent($) {
+ my (%list) = @_;
+
+ $type = $$list{'type'};
+ $eol = $$list{'eol'};
+ $list_ref = $$list{'files'};
+
+ my @diroutput;
+ my @contentlist;
+ if($type eq "unix") {
+ for(@$list_ref) {
+ my %file = %$_;
+ my $line = "";
+ my $ftype = $file{'type'} ? $file{'type'} : "-";
+ my $fperm = $file{'perm'} ? $file{'perm'} : "rwxr-xr-x";
+ my $fuser = $file{'user'} ? sprintf("%15s", $file{'user'}) : "ftp-default";
+ my $fgroup = $file{'group'} ? sprintf("%15s", $file{'group'}) : "ftp-default";
+ my $fsize = "";
+ if($file{'type'} eq "d") {
+ $fsize = $file{'size'} ? sprintf("%7s", $file{'size'}) : sprintf("%7d", 4096);
+ }
+ else {
+ $fsize = sprintf("%7d", length $file{'content'});
+ }
+ my $fhlink = $file{'hlink'} ? sprintf("%4d", $file{'hlink'}) : " 1";
+ my $ftime = $file{'time'} ? sprintf("%10s", $file{'time'}) : "Jan 9 1933";
+ push(@contentlist, "$ftype$fperm $fhlink $fuser $fgroup $fsize $ftime $file{'name'}$eol");
+ }
+
+ return @contentlist;
+ }
+ elsif($type =~ /^dos$/) {
+ for(@$list_ref) {
+ my %file = %$_;
+ my $line = "";
+ my $time = $file{'dostime'} ? $file{'dostime'} : "06-25-97 09:12AM";
+ my $size_or_dir;
+ if($file{'type'} =~ /^d$/) {
+ $size_or_dir = " <DIR> ";
+ }
+ else {
+ $size_or_dir = sprintf("%20d", length $file{'content'});
+ }
+ push(@contentlist, "$time $size_or_dir $file{'name'}$eol");
+ }
+ return @contentlist;
+ }
+}
+
+sub wildcard_filesize($$) {
+ my ($list_type, $file) = @_;
+ $list = $lists{$list_type};
+ if($list) {
+ my $files = $list->{'files'};
+ for(@$files) {
+ my %f = %$_;
+ 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) = @_;
+ $list = $lists{$list_type};
+ if($list) {
+ my $files = $list->{'files'};
+ for(@$files) {
+ my %f = %$_;
+ 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 = $_[0];
+ $list = $lists{$listname};
+ return ftp_createcontent(\$list);
+}
diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl
index cc69585c8..ed2a83268 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 = 0; # set if program should finish execution ASAP
@@ -344,6 +352,8 @@ sub protocolsetup {
'LIST' => \&LIST_ftp,
'NLST' => \&NLST_ftp,
'PASV' => \&PASV_ftp,
+ 'CWD' => \&CWD_ftp,
+ 'PWD' => \&PWD_ftp,
'EPSV' => \&PASV_ftp,
'RETR' => \&RETR_ftp,
'SIZE' => \&SIZE_ftp,
@@ -362,7 +372,6 @@ sub protocolsetup {
'CWD' => '250 CWD command successful.',
'SYST' => '215 UNIX Type: L8', # just fake something
'QUIT' => '221 bye bye baby', # just reply something
- 'PWD' => '257 "/nowhere/anywhere" is current directory',
'MKD' => '257 Created your requested directory',
'REST' => '350 Yeah yeah we set it there for you',
'DELE' => '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 = $_;
+
+ if(!$ftptargetdir) {
+ $ftptargetdir = "/";
+ }
+
+ if($target_dir eq "") {
+ $ftptargetdir = "/";
+ }
+ elsif($target_dir eq "..") {
+ if($ftptargetdir eq "/") {
+ $ftptargetdir = "/";
+ }
+ else {
+ $ftptargetdir =~ s/[[:alnum:]]+\/$//;
+ }
+ }
+ else {
+ $ftptargetdir .= $target_dir . "/";
+ }
+}
+
+sub switch_directory {
+ my $target_dir = $_[0];
+
+ if($target_dir eq "/") {
+ $ftptargetdir = "/";
+ }
+ else {
+ my @dirs = split("/", $target_dir);
+ for(@dirs) {
+ switch_directory_goto($_);
+ }
+ }
+}
+
+sub CWD_ftp {
+ my ($folder, $fullcommand) = $_[0];
+ switch_directory($folder);
+ if($ftptargetdir =~ /^\/fully_simulated/) {
+ $ftplistparserstate = "enabled";
+ }
+ else {
+ undef $ftplistparserstate;
+ }
+}
+
+sub PWD_ftp {
+ my $mydir;
+ $mydir = $ftptargetdir ? $ftptargetdir : "/";
+
+ if($mydir ne "/") {
+ $mydir =~ 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=("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 = 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 = $_[0];
+ if($ftplistparserstate) {
+ my $size = wildcard_filesize($ftptargetdir, $testno);
+ if($size == -1) {
+ sendcontrol "550 $testno: No such file or directory.\r\n";
+ }
+ else {
+ sendcontrol "213 $size\r\n";
+ }
+ return 0;
+ }
if($testno =~ /^verifiedserver$/) {
my $response = "WE ROOLZ: $$\r\n";
@@ -803,6 +884,21 @@ sub SIZE_ftp {
sub RETR_ftp {
my ($testno) = @_;
+ if($ftplistparserstate) {
+ my @content = wildcard_getfile($ftptargetdir, $testno);
+ if($content[0] == -1) {
+ #file not found
+ }
+ else {
+ my $size = 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 =~ /^verifiedserver$/) {
# this is the secret command that verifies that this actually is
# 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 58d5d9226..814c01e6c 100644
--- a/tests/libtest/Makefile.inc
+++ b/tests/libtest/Makefile.inc
@@ -8,6 +8,7 @@ SUPPORTFILES = first.c test.h
noinst_PROGRAMS = 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 = -DLIB559
lib560_SOURCES = lib560.c $(SUPPORTFILES)
+lib574_SOURCES = lib574.c $(SUPPORTFILES)
+
+lib575_SOURCES = lib575.c $(SUPPORTFILES)
+
+lib576_SOURCES = lib576.c $(SUPPORTFILES)
+
+lib577_SOURCES = lib577.c $(SUPPORTFILES)
+
lib562_SOURCES = lib562.c $(SUPPORTFILES)
lib564_SOURCES = lib564.c $(SUPPORTFILES) $(TESTUTIL)
diff --git a/tests/libtest/lib574.c b/tests/libtest/lib574.c
new file mode 100644
index 000000000..69b2979a8
--- /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) != CURLE_OK) {
+ fprintf(stderr, "curl_global_init() failed\n");
+ return TEST_ERR_MAJOR_BAD;
+ }
+
+ if ((curl = curl_easy_init()) == 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 = curl_easy_perform(curl);
+ if(res) {
+ fprintf(stderr, "curl_easy_perform() failed %d\n", res);
+ goto test_cleanup;
+ }
+ res = 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 000000000..3bf15ea26
--- /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 = NULL, *duphandle;
+ CURLM *mhandle = NULL;
+ int res = 0;
+ int still_running = 0;
+
+ if(curl_global_init(CURL_GLOBAL_ALL)) {
+ fprintf(stderr, "curl_global_init() failed\n");
+ goto test_cleanup;
+ }
+
+ handle = curl_easy_init();
+ if(!handle) {
+ res = 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 = curl_easy_perform(handle);
+ if(res)
+ goto test_cleanup;
+
+ res = curl_easy_perform(handle);
+ if(res)
+ goto test_cleanup;
+
+ duphandle = curl_easy_duphandle(handle);
+ if(!duphandle)
+ goto test_cleanup;
+ curl_easy_cleanup(handle);
+ handle = duphandle;
+
+ mhandle = 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 ==
+ 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 = -1;
+ FD_ZERO(&fdread);
+ FD_ZERO(&fdwrite);
+ FD_ZERO(&fdexcep);
+ timeout.tv_sec = 3;
+ timeout.tv_usec = 0;
+
+ m = curl_multi_fdset(mhandle, &fdread, &fdwrite, &fdexcep, &max_fdset);
+ rc = select(max_fdset + 1, &fdread, &fdwrite, &fdexcep, &timeout);
+ if(rc == -1) {
+ fprintf(stderr, "select() error\n");
+ goto test_cleanup;
+ }
+ else if(rc == 0) {
+ fprintf(stderr, "select() timeout!\n");
+ goto test_cleanup;
+ }
+ else {
+ while(CURLM_CALL_MULTI_PERFORM ==
+ 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 000000000..7f2c7ae37
--- /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 remains);
+long chunk_end(void *ptr);
+
+long chunk_bgn(const struct curl_fileinfo *finfo, void *ptr, int remains)
+{
+ chunk_data_t *ch_d = ptr;
+ ch_d->remains = remains;
+
+ printf("=============================================================\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 => %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 == CURLFILETYPE_FILE) {
+ ch_d->print_content = 1;
+ printf("Content:\n-------------------------------------------------------------\n");
+ }
+ if(strcmp(finfo->filename, "someothertext.txt") == 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 = ptr;
+ if(ch_d->print_content) {
+ ch_d->print_content = 0;
+ printf("-------------------------------------------------------------\n");
+ }
+ if(ch_d->remains == 1)
+ printf("=============================================================\n");
+ return CURL_CHUNK_END_FUNC_OK;
+}
+
+int test(char *URL)
+{
+ CURL *handle = NULL;
+ CURLcode res = 0;
+ chunk_data_t chunk_data = {0,0};
+ curl_global_init(CURL_GLOBAL_ALL);
+ handle = curl_easy_init();
+ if(!handle) {
+ res = 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 = 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 000000000..8b434f8fb
--- /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[] = {
+ /* 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 },
+ { ":;=@ABCDEFGHIJKLMNO", ":;=@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 => 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ätarv", "Lindmätarv", MATCH },
+
+ { "", "", MATCH }
+};
+
+
+int test(char *URL)
+{
+ int testnum = sizeof(tests) / sizeof(struct testcase);
+ int i, rc;
+ (void)URL; /* not used */
+ printf("===========================\n");
+ for(i = 0; i < testnum; i++) {
+ rc = Curl_fnmatch(tests[i].pattern, tests[i].string);
+ if(rc != 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("===========================\n");
+ return 0;
+}