1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-24 09:08:49 -05:00

FTP: url-decode path before evaluation

Closes #4428
This commit is contained in:
Zenju 2019-09-25 17:48:53 +02:00 committed by Daniel Stenberg
parent 73089bf7f3
commit 500fb0e4cb
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
4 changed files with 126 additions and 170 deletions

289
lib/ftp.c
View File

@ -1446,22 +1446,30 @@ static CURLcode ftp_state_list(struct connectdata *conn)
The other ftp_filemethods will CWD into dir/dir/ first and The other ftp_filemethods will CWD into dir/dir/ first and
then just do LIST (in that case: nothing to do here) then just do LIST (in that case: nothing to do here)
*/ */
char *cmd, *lstArg; char *lstArg = NULL;
const char *inpath = ftp->path; char *cmd;
lstArg = NULL; if((data->set.ftp_filemethod == FTPFILE_NOCWD) && ftp->path) {
if((data->set.ftp_filemethod == FTPFILE_NOCWD) && /* url-decode before evaluation: e.g. paths starting/ending with %2f */
inpath && inpath[0] && strchr(inpath, '/')) { const char *slashPos = NULL;
/* chop off the file part if format is dir/file char *rawPath = NULL;
otherwise remove the trailing slash for dir/dir/ result = Curl_urldecode(data, ftp->path, 0, &rawPath, NULL, TRUE);
and full paths like %2f/ except for / */
size_t n = strrchr(inpath, '/') - inpath;
if(n == 0)
++n;
result = Curl_urldecode(data, inpath, n, &lstArg, NULL, TRUE);
if(result) if(result)
return result; return result;
slashPos = strrchr(rawPath, '/');
if(slashPos) {
/* chop off the file part if format is dir/file otherwise remove
the trailing slash for dir/dir/ except for absolute path / */
size_t n = slashPos - rawPath;
if(n == 0)
++n;
lstArg = rawPath;
lstArg[n] = '\0';
}
else
free(rawPath);
} }
cmd = aprintf("%s%s%s", cmd = aprintf("%s%s%s",
@ -1470,15 +1478,12 @@ static CURLcode ftp_state_list(struct connectdata *conn)
(data->set.ftp_list_only?"NLST":"LIST"), (data->set.ftp_list_only?"NLST":"LIST"),
lstArg? " ": "", lstArg? " ": "",
lstArg? lstArg: ""); lstArg? lstArg: "");
free(lstArg);
if(!cmd) { if(!cmd)
free(lstArg);
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
}
result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd); result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd);
free(lstArg);
free(cmd); free(cmd);
if(result) if(result)
@ -3132,8 +3137,8 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
ssize_t nread; ssize_t nread;
int ftpcode; int ftpcode;
CURLcode result = CURLE_OK; CURLcode result = CURLE_OK;
char *path = NULL; char *rawPath = NULL;
size_t pathlen = 0; size_t pathLen = 0;
if(!ftp) if(!ftp)
return CURLE_OK; return CURLE_OK;
@ -3181,8 +3186,8 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
} }
if(!result) if(!result)
/* get the "raw" path */ /* get the url-decoded "raw" path */
result = Curl_urldecode(data, ftp->path, 0, &path, &pathlen, TRUE); result = Curl_urldecode(data, ftp->path, 0, &rawPath, &pathLen, TRUE);
if(result) { if(result) {
/* We can limp along anyway (and should try to since we may already be in /* We can limp along anyway (and should try to since we may already be in
* the error path) */ * the error path) */
@ -3192,22 +3197,22 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status,
ftpc->prevpath = NULL; /* no path remembering */ ftpc->prevpath = NULL; /* no path remembering */
} }
else { /* remember working directory for connection reuse */ else { /* remember working directory for connection reuse */
if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (path[0] == '/')) if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/'))
free(path); /* full path => no CWDs happened => keep ftpc->prevpath */ free(rawPath); /* full path => no CWDs happened => keep ftpc->prevpath */
else { else {
free(ftpc->prevpath); free(ftpc->prevpath);
if(!ftpc->cwdfail) { if(!ftpc->cwdfail) {
if(data->set.ftp_filemethod == FTPFILE_NOCWD) if(data->set.ftp_filemethod == FTPFILE_NOCWD)
pathlen = 0; /* relative path => working directory is FTP home */ pathLen = 0; /* relative path => working directory is FTP home */
else else
pathlen -= ftpc->file?strlen(ftpc->file):0; /* file is url-decoded */ pathLen -= ftpc->file?strlen(ftpc->file):0; /* file is url-decoded */
path[pathlen] = '\0'; rawPath[pathLen] = '\0';
ftpc->prevpath = path; ftpc->prevpath = rawPath;
} }
else { else {
free(path); free(rawPath);
ftpc->prevpath = NULL; /* no path */ ftpc->prevpath = NULL; /* no path */
} }
} }
@ -4087,192 +4092,142 @@ CURLcode ftp_parse_url_path(struct connectdata *conn)
/* the ftp struct is already inited in ftp_connect() */ /* the ftp struct is already inited in ftp_connect() */
struct FTP *ftp = data->req.protop; struct FTP *ftp = data->req.protop;
struct ftp_conn *ftpc = &conn->proto.ftpc; struct ftp_conn *ftpc = &conn->proto.ftpc;
const char *slash_pos; /* position of the first '/' char in curpos */ const char *slashPos = NULL;
const char *path_to_use = ftp->path; const char *fileName = NULL;
const char *cur_pos;
const char *filename = NULL;
char *path = NULL;
size_t pathlen = 0;
CURLcode result = CURLE_OK; CURLcode result = CURLE_OK;
char *rawPath = NULL; /* url-decoded "raw" path */
cur_pos = path_to_use; /* current position in path. point at the begin of size_t pathLen = 0;
next path component */
ftpc->ctl_valid = FALSE; ftpc->ctl_valid = FALSE;
ftpc->cwdfail = FALSE; ftpc->cwdfail = FALSE;
/* url-decode ftp path before further evaluation */
result = Curl_urldecode(data, ftp->path, 0, &rawPath, &pathLen, TRUE);
if(result)
return result;
switch(data->set.ftp_filemethod) { switch(data->set.ftp_filemethod) {
case FTPFILE_NOCWD: case FTPFILE_NOCWD: /* fastest, but less standard-compliant */
/* fastest, but less standard-compliant */
/* if((pathLen > 0) && (rawPath[pathLen - 1] != '/'))
The best time to check whether the path is a file or directory is right fileName = rawPath; /* this is a full file path */
here. so: /*
else: ftpc->file is not used anywhere other than for operations on
the first condition in the if() right here, is there just in case a file. In other words, never for directory operations.
someone decides to set path to NULL one day So we can safely leave filename as NULL here and use it as a
*/ argument in dir/file decisions.
if(path_to_use[0] && */
(path_to_use[strlen(path_to_use) - 1] != '/') )
filename = path_to_use; /* this is a full file path */
/*
else {
ftpc->file is not used anywhere other than for operations on a file.
In other words, never for directory operations.
So we can safely leave filename as NULL here and use it as a
argument in dir/file decisions.
}
*/
break;
case FTPFILE_SINGLECWD:
/* get the last slash */
if(!path_to_use[0]) {
/* no dir, no file */
ftpc->dirdepth = 0;
break; break;
}
slash_pos = strrchr(cur_pos, '/');
if(slash_pos || !*cur_pos) {
size_t dirlen = slash_pos-cur_pos;
ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0])); case FTPFILE_SINGLECWD:
if(!ftpc->dirs) slashPos = strrchr(rawPath, '/');
return CURLE_OUT_OF_MEMORY; if(slashPos) {
/* get path before last slash, except for / */
size_t dirlen = slashPos - rawPath;
if(dirlen == 0)
dirlen++;
if(!dirlen) ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0]));
dirlen++; if(!ftpc->dirs) {
free(rawPath);
return CURLE_OUT_OF_MEMORY;
}
result = Curl_urldecode(conn->data, slash_pos ? cur_pos : "/", ftpc->dirs[0] = calloc(1, dirlen + 1);
slash_pos ? dirlen : 1, if(!ftpc->dirs[0]) {
&ftpc->dirs[0], NULL, free(rawPath);
TRUE); return CURLE_OUT_OF_MEMORY;
if(result) { }
freedirs(ftpc);
return result; strncpy(ftpc->dirs[0], rawPath, dirlen);
ftpc->dirdepth = 1; /* we consider it to be a single dir */
fileName = slashPos + 1; /* rest is file name */
} }
ftpc->dirdepth = 1; /* we consider it to be a single dir */ else
filename = slash_pos ? slash_pos + 1 : cur_pos; /* rest is file name */ fileName = rawPath; /* file name only (or empty) */
} break;
else
filename = cur_pos; /* this is a file name only */
break;
default: /* allow pretty much anything */ default: /* allow pretty much anything */
case FTPFILE_MULTICWD: case FTPFILE_MULTICWD: {
ftpc->dirdepth = 0; /* current position: begin of next path component */
ftpc->diralloc = 5; /* default dir depth to allocate */ const char *curPos = rawPath;
ftpc->dirs = calloc(ftpc->diralloc, sizeof(ftpc->dirs[0]));
if(!ftpc->dirs)
return CURLE_OUT_OF_MEMORY;
/* we have a special case for listing the root dir only */ int dirAlloc = 0; /* number of entries allocated for the 'dirs' array */
if(!strcmp(path_to_use, "/")) { const char *str = rawPath;
cur_pos++; /* make it point to the zero byte */ for(; *str != 0; ++str)
ftpc->dirs[0] = strdup("/"); if (*str == '/')
ftpc->dirdepth++; ++dirAlloc;
}
else { if(dirAlloc > 0) {
/* parse the URL path into separate path components */ ftpc->dirs = calloc(dirAlloc, sizeof(ftpc->dirs[0]));
while((slash_pos = strchr(cur_pos, '/')) != NULL) { if(!ftpc->dirs) {
/* 1 or 0 pointer offset to indicate absolute directory */ free(rawPath);
ssize_t absolute_dir = ((cur_pos - ftp->path > 0) && return CURLE_OUT_OF_MEMORY;
(ftpc->dirdepth == 0))?1:0; }
/* parse the URL path into separate path components */
while((slashPos = strchr(curPos, '/')) != NULL) {
size_t compLen = slashPos - curPos;
/* path starts with a slash: add that as a directory */
if((compLen == 0) && (ftpc->dirdepth == 0))
++compLen;
/* seek out the next path component */
if(slash_pos-cur_pos) {
/* we skip empty path components, like "x//y" since the FTP command /* we skip empty path components, like "x//y" since the FTP command
CWD requires a parameter and a non-existent parameter a) doesn't CWD requires a parameter and a non-existent parameter a) doesn't
work on many servers and b) has no effect on the others. */ work on many servers and b) has no effect on the others. */
size_t len = slash_pos - cur_pos + absolute_dir; if(compLen > 0) {
result = Curl_urldecode(conn->data, cur_pos - absolute_dir, len, char *comp = calloc(1, compLen + 1);
&ftpc->dirs[ftpc->dirdepth], NULL, if(!comp) {
TRUE); free(rawPath);
if(result) {
freedirs(ftpc);
return result;
}
}
else {
cur_pos = slash_pos + 1; /* jump to the rest of the string */
if(!ftpc->dirdepth) {
/* path starts with a slash, add that as a directory */
ftpc->dirs[ftpc->dirdepth] = strdup("/");
if(!ftpc->dirs[ftpc->dirdepth++]) { /* run out of memory ... */
failf(data, "no memory");
freedirs(ftpc);
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
} }
strncpy(comp, curPos, compLen);
ftpc->dirs[ftpc->dirdepth++] = comp;
} }
continue; curPos = slashPos + 1;
}
cur_pos = slash_pos + 1; /* jump to the rest of the string */
if(++ftpc->dirdepth >= ftpc->diralloc) {
/* enlarge array */
char **bigger;
ftpc->diralloc *= 2; /* double the size each time */
bigger = realloc(ftpc->dirs, ftpc->diralloc * sizeof(ftpc->dirs[0]));
if(!bigger) {
freedirs(ftpc);
return CURLE_OUT_OF_MEMORY;
}
ftpc->dirs = bigger;
} }
} }
DEBUGASSERT(ftpc->dirdepth <= dirAlloc);
fileName = curPos; /* the rest is the file name (or empty) */
} }
filename = cur_pos; /* the rest is the file name */
break; break;
} /* switch */ } /* switch */
if(filename && *filename) { if(fileName && *fileName)
result = Curl_urldecode(conn->data, filename, 0, &ftpc->file, NULL, TRUE); ftpc->file = strdup(fileName);
if(result) {
freedirs(ftpc);
return result;
}
}
else else
ftpc->file = NULL; /* instead of point to a zero byte, we make it a NULL ftpc->file = NULL; /* instead of point to a zero byte,
pointer */ we make it a NULL pointer */
if(data->set.upload && !ftpc->file && (ftp->transfer == FTPTRANSFER_BODY)) { if(data->set.upload && !ftpc->file && (ftp->transfer == FTPTRANSFER_BODY)) {
/* We need a file name when uploading. Return error! */ /* We need a file name when uploading. Return error! */
failf(data, "Uploading to a URL without a file name!"); failf(data, "Uploading to a URL without a file name!");
free(rawPath);
return CURLE_URL_MALFORMAT; return CURLE_URL_MALFORMAT;
} }
ftpc->cwddone = FALSE; /* default to not done */ ftpc->cwddone = FALSE; /* default to not done */
/* prevpath and ftpc->file are url-decoded so convert the input path if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/'))
before we compare the strings */
result = Curl_urldecode(conn->data, ftp->path, 0, &path, &pathlen, TRUE);
if(result) {
freedirs(ftpc);
return result;
}
if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (path[0] == '/'))
ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ ftpc->cwddone = TRUE; /* skip CWD for absolute paths */
else { /* newly created FTP connections are already in entry path */ else { /* newly created FTP connections are already in entry path */
const char *oldpath = conn->bits.reuse ? ftpc->prevpath : ""; const char *oldPath = conn->bits.reuse ? ftpc->prevpath : "";
if(oldpath) { if(oldPath) {
size_t n = pathLen;
if(data->set.ftp_filemethod == FTPFILE_NOCWD) if(data->set.ftp_filemethod == FTPFILE_NOCWD)
pathlen = 0; /* CWD to entry for relative paths */ n = 0; /* CWD to entry for relative paths */
else else
pathlen -= ftpc->file?strlen(ftpc->file):0; n -= ftpc->file?strlen(ftpc->file):0;
path[pathlen] = '\0'; if((strlen(oldPath) == n) && !strncmp(rawPath, oldPath, n)) {
if(!strcmp(path, oldpath)) {
infof(data, "Request has same path as previous transfer\n"); infof(data, "Request has same path as previous transfer\n");
ftpc->cwddone = TRUE; ftpc->cwddone = TRUE;
} }
} }
} }
free(path);
free(rawPath);
return CURLE_OK; return CURLE_OK;
} }

View File

@ -121,7 +121,6 @@ struct ftp_conn {
char *entrypath; /* the PWD reply when we logged on */ char *entrypath; /* the PWD reply when we logged on */
char **dirs; /* realloc()ed array for path components */ char **dirs; /* realloc()ed array for path components */
int dirdepth; /* number of entries used in the 'dirs' array */ int dirdepth; /* number of entries used in the 'dirs' array */
int diralloc; /* number of entries allocated for the 'dirs' array */
char *file; /* url-decoded file name (or path) */ char *file; /* url-decoded file name (or path) */
bool dont_check; /* Set to TRUE to prevent the final (post-transfer) bool dont_check; /* Set to TRUE to prevent the final (post-transfer)
file size and 226/250 status check. It should still file size and 226/250 status check. It should still

View File

@ -34,7 +34,8 @@ FTP URL with type=i
USER anonymous USER anonymous
PASS ftp@example.com PASS ftp@example.com
PWD PWD
CWD /tmp CWD /
CWD tmp
CWD moo CWD moo
EPSV EPSV
TYPE I TYPE I

View File

@ -32,7 +32,8 @@ FTP URL with type=a
USER anonymous USER anonymous
PASS ftp@example.com PASS ftp@example.com
PWD PWD
CWD /tmp CWD /
CWD tmp
CWD moo CWD moo
EPSV EPSV
TYPE A TYPE A