fix handling of special symbols in file operations. Path fixes at least following cases: - attempt to enter directory named '#' - attempt to open file named '#' - attempt to dereference link named '#' - attempt to chown file / dir named '#' The fix is basically following mechanical conversion: --- curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, something); +++ something_uri = path_to_uri(something); +++ curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, something_uri); === curl_easy_perform(); +++ free_uri(something_uri); What is not yet converted is ftp hostname. Reported-by: Jaakko Perttilä Gentoo-bug: http://bugs.gentoo.org/458110 Based-on-patch: https://github.com/jomat/curlftpfs/commit/da20298fd0d0dcefc7d6d69ffecbc5544e783cfe Signed-off-by: Sergei Trofimovich diff --git a/ftpfs.c b/ftpfs.c index ffd0b28..f21a267 100644 --- a/ftpfs.c +++ b/ftpfs.c @@ -257,6 +257,7 @@ static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h, int err = 0; CURLcode curl_res; char* dir_path = get_fulldir_path(path); + char* dir_path_uri = path_to_uri(dir_path); DEBUG(1, "ftpfs_getdir: %s\n", dir_path); struct buffer buf; @@ -264,7 +265,7 @@ static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h, pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_res = curl_easy_perform(ftpfs.connection); pthread_mutex_unlock(&ftpfs.lock); @@ -278,6 +279,7 @@ static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h, NULL, NULL, NULL, 0, h, filler); } + free_uri(dir_path_uri); free(dir_path); buf_free(&buf); return op_return(err, "ftpfs_getdir"); @@ -287,6 +289,7 @@ static int ftpfs_getattr(const char* path, struct stat* sbuf) { int err; CURLcode curl_res; char* dir_path = get_dir_path(path); + char* dir_path_uri = path_to_uri(dir_path); DEBUG(2, "ftpfs_getattr: %s dir_path=%s\n", path, dir_path); struct buffer buf; @@ -294,7 +297,7 @@ static int ftpfs_getattr(const char* path, struct stat* sbuf) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_res = curl_easy_perform(ftpfs.connection); pthread_mutex_unlock(&ftpfs.lock); @@ -309,6 +312,7 @@ static int ftpfs_getattr(const char* path, struct stat* sbuf) { err = parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1, name, sbuf, NULL, 0, NULL, NULL); + free_uri(dir_path_uri); free(dir_path); buf_free(&buf); if (err) return op_return(-ENOENT, "ftpfs_getattr"); @@ -329,6 +333,7 @@ static size_t ftpfs_read_chunk(const char* full_path, char* rbuf, int running_handles = 0; int err = 0; struct ftpfs_file* fh = get_ftpfs_file(fi); + char* full_path_uri = path_to_uri(full_path); /* TODO: optimize bu pushing up conversion to context */ DEBUG(2, "ftpfs_read_chunk: %s %p %zu %lld %p %p\n", full_path, rbuf, size, offset, fi, fh); @@ -355,7 +360,7 @@ static size_t ftpfs_read_chunk(const char* full_path, char* rbuf, cancel_previous_multi(); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &fh->buf); if (offset) { char range[15]; @@ -444,6 +449,7 @@ static size_t ftpfs_read_chunk(const char* full_path, char* rbuf, pthread_mutex_unlock(&ftpfs.lock); + free_uri(full_path_uri); if (err) return CURLFTPFS_BAD_READ; return size; } @@ -497,11 +503,12 @@ int write_thread_ctr = 0; static void *ftpfs_write_thread(void *data) { struct ftpfs_file *fh = data; char range[15]; - + char* full_path_uri = path_to_uri(fh->full_path); /* TODO: optimize bu pushing up conversion to context */ + DEBUG(2, "enter streaming write thread #%d path=%s pos=%lld\n", ++write_thread_ctr, fh->full_path, fh->pos); - curl_easy_setopt_or_die(fh->write_conn, CURLOPT_URL, fh->full_path); + curl_easy_setopt_or_die(fh->write_conn, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(fh->write_conn, CURLOPT_UPLOAD, 1); curl_easy_setopt_or_die(fh->write_conn, CURLOPT_INFILESIZE, (curl_off_t)-1); curl_easy_setopt_or_die(fh->write_conn, CURLOPT_READFUNCTION, write_data_bg); @@ -541,6 +548,8 @@ static void *ftpfs_write_thread(void *data) { sem_post(&fh->data_written); /* ftpfs_write may return */ + free_uri(full_path_uri); + return NULL; } @@ -621,16 +630,19 @@ static void free_ftpfs_file(struct ftpfs_file *fh) { } static int buffer_file(struct ftpfs_file *fh) { + char* full_path_uri = path_to_uri(fh->full_path); /* TODO: optimize bu pushing up conversion to context */ // If we want to write to the file, we have to load it all at once, // modify it in memory and then upload it as a whole as most FTP servers // don't support resume for uploads. pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, fh->full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &fh->buf); CURLcode curl_res = curl_easy_perform(ftpfs.connection); pthread_mutex_unlock(&ftpfs.lock); + free_uri(full_path_uri); + if (curl_res != 0) { return -EACCES; } @@ -643,10 +655,11 @@ static int create_empty_file(const char * path) int err = 0; char *full_path = get_full_path(path); + char* full_path_uri = path_to_uri(full_path); pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_INFILESIZE, 0); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_UPLOAD, 1); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_READDATA, NULL); @@ -656,7 +669,9 @@ static int create_empty_file(const char * path) if (curl_res != 0) { err = -EPERM; - } + } + + free_uri(full_path_uri); free(full_path); return err; } @@ -875,6 +890,7 @@ static int ftpfs_chmod(const char* path, mode_t mode) { struct curl_slist* header = NULL; char* full_path = get_dir_path(path); + char* full_path_uri = path_to_uri(full_path); char* filename = get_file_name(path); char* cmd = g_strdup_printf("SITE CHMOD %.3o %s", mode_c, filename); struct buffer buf; @@ -885,7 +901,7 @@ static int ftpfs_chmod(const char* path, mode_t mode) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody); CURLcode curl_res = curl_easy_perform(ftpfs.connection); @@ -896,12 +912,13 @@ static int ftpfs_chmod(const char* path, mode_t mode) { if (curl_res != 0) { err = -EPERM; } - + buf_free(&buf); curl_slist_free_all(header); + free_uri(full_path_uri); free(full_path); free(filename); - free(cmd); + free(cmd); return op_return(err, "ftpfs_chmod"); } @@ -912,6 +929,7 @@ static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) { struct curl_slist* header = NULL; char* full_path = get_dir_path(path); + char* full_path_uri = path_to_uri(full_path); char* filename = get_file_name(path); char* cmd = g_strdup_printf("SITE CHUID %i %s", uid, filename); char* cmd2 = g_strdup_printf("SITE CHGID %i %s", gid, filename); @@ -924,7 +942,7 @@ static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody); CURLcode curl_res = curl_easy_perform(ftpfs.connection); @@ -938,6 +956,7 @@ static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) { buf_free(&buf); curl_slist_free_all(header); + free_uri(full_path_uri); free(full_path); free(filename); free(cmd); @@ -1001,6 +1020,7 @@ static int ftpfs_rmdir(const char* path) { int err = 0; struct curl_slist* header = NULL; char* full_path = get_dir_path(path); + char* full_path_uri = path_to_uri(full_path); char* filename = get_file_name(path); char* cmd = g_strdup_printf("RMD %s", filename); struct buffer buf; @@ -1014,7 +1034,7 @@ static int ftpfs_rmdir(const char* path) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody); CURLcode curl_res = curl_easy_perform(ftpfs.connection); @@ -1028,6 +1048,7 @@ static int ftpfs_rmdir(const char* path) { buf_free(&buf); curl_slist_free_all(header); + free_uri(full_path_uri); free(full_path); free(filename); free(cmd); @@ -1038,6 +1059,7 @@ static int ftpfs_mkdir(const char* path, mode_t mode) { int err = 0; struct curl_slist* header = NULL; char* full_path = get_dir_path(path); + char* full_path_uri = path_to_uri(full_path); char* filename = get_file_name(path); char* cmd = g_strdup_printf("MKD %s", filename); struct buffer buf; @@ -1048,7 +1070,7 @@ static int ftpfs_mkdir(const char* path, mode_t mode) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody); CURLcode curl_res = curl_easy_perform(ftpfs.connection); @@ -1062,6 +1084,7 @@ static int ftpfs_mkdir(const char* path, mode_t mode) { buf_free(&buf); curl_slist_free_all(header); + free_uri(full_path_uri); free(full_path); free(filename); free(cmd); @@ -1076,6 +1099,7 @@ static int ftpfs_unlink(const char* path) { int err = 0; struct curl_slist* header = NULL; char* full_path = get_dir_path(path); + char* full_path_uri = path_to_uri(full_path); char* filename = get_file_name(path); char* cmd = g_strdup_printf("DELE %s", filename); struct buffer buf; @@ -1086,7 +1110,7 @@ static int ftpfs_unlink(const char* path) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_POSTQUOTE, header); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, full_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_NOBODY, ftpfs.safe_nobody); CURLcode curl_res = curl_easy_perform(ftpfs.connection); @@ -1100,6 +1124,7 @@ static int ftpfs_unlink(const char* path) { buf_free(&buf); curl_slist_free_all(header); + free_uri(full_path_uri); free(full_path); free(filename); free(cmd); @@ -1301,6 +1326,7 @@ static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) { int err; CURLcode curl_res; char* dir_path = get_dir_path(path); + char* dir_path_uri = path_to_uri(dir_path); DEBUG(2, "dir_path: %s %s\n", path, dir_path); struct buffer buf; @@ -1308,7 +1334,7 @@ static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) { pthread_mutex_lock(&ftpfs.lock); cancel_previous_multi(); - curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path); + curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path_uri); curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf); curl_res = curl_easy_perform(ftpfs.connection); pthread_mutex_unlock(&ftpfs.lock); @@ -1323,6 +1349,7 @@ static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) { err = parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1, name, NULL, linkbuf, size, NULL, NULL); + free_uri(dir_path_uri); free(dir_path); buf_free(&buf); if (err) return op_return(-ENOENT, "ftpfs_readlink"); diff --git a/path_utils.c b/path_utils.c index db3d7e4..214b5e6 100644 --- a/path_utils.c +++ b/path_utils.c @@ -92,3 +92,72 @@ char* get_dir_path(const char* path) { return ret; } + +/* + * the chars not needed to be escaped: + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + */ +static inline int is_unreserved_rfc3986(char c) +{ + int is_locase_alpha = (c >= 'a' && c <= 'z'); + int is_upcase_alpha = (c >= 'a' && c <= 'z'); + int is_digit = (c >= '0' && c <= '9'); + int is_special = c == '-' + || c == '.' + || c == '_' + || c == '~'; + int is_unreserved = is_locase_alpha + || is_upcase_alpha + || is_digit + || is_special; + + return is_unreserved; +} + +static inline int is_unreserved(char c) +{ + return is_unreserved_rfc3986(c) || c == '/'; +} + +char* path_to_uri(const char* path) +{ + static const char hex[] = "0123456789ABCDEF"; + size_t path_len = strlen(path); + size_t host_len = strlen(ftpfs.host); + /* at worst: c -> %XX */ + char * encoded_path = malloc (3 * path_len + 1); + const char * s = path; + char * d = encoded_path; + + /* + * 'path' is always prefixed with 'ftpfs.host' + */ + memcpy (d, ftpfs.host, host_len); + s += host_len; + d += host_len; + + for (; *s; ++s) + { + char c = *s; + if (is_unreserved (c)) + { + *d++ = c; + } + else + { + unsigned int hi = ((unsigned)c >> 4) & 0xF; + unsigned int lo = ((unsigned)c >> 0) & 0xF; + *d++ = '%'; + *d++ = hex[hi]; + *d++ = hex[lo]; + } + } + *d = '\0'; + + return encoded_path; +} + +void free_uri(char* path) +{ + free(path); +} diff --git a/path_utils.h b/path_utils.h index 084ae4d..e3e9bca 100644 --- a/path_utils.h +++ b/path_utils.h @@ -6,4 +6,11 @@ char* get_full_path(const char* path); char* get_fulldir_path(const char* path); char* get_dir_path(const char* path); +/* + * Transforms UNIX path to RFC3986 encoded path + * (CURLOPT_URL accepts only such paths) + */ +char* path_to_uri(const char* path); +void free_uri(char* path); + #endif /* __CURLFTPFS_PATH_UTILS_H__ */