From d48810ba5b0b8d09b272092a70da1255c2346ab8 Mon Sep 17 00:00:00 2001 From: Wayne Davison Date: Thu, 20 Mar 2008 11:59:54 -0700 Subject: [PATCH] Some improvements to the file-name cleaning code: - Removed the CFN_KEEP_LEADING_DOT_DIR flag for clean_fname(). - Explicitly add an implied dot-dir to the transfer rather than keeping a leading a "./" prefix as a part of a relative pathname. - Added the CFN_KEEP_DOT_DIRS flag for clean_fname(). - Added the SP_KEEP_DOT_DIRS flag for sanitize_path(). - Call clean_fname() a couple more times. --- exclude.c | 2 +- flist.c | 32 ++++++++++++++++++++++---------- main.c | 4 ++-- options.c | 10 +++++----- rsync.h | 5 ++++- util.c | 50 ++++++++++++++++++++++++++++---------------------- 6 files changed, 62 insertions(+), 41 deletions(-) diff --git a/exclude.c b/exclude.c index d192b551..422a824d 100644 --- a/exclude.c +++ b/exclude.c @@ -295,7 +295,7 @@ static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr, strlcpy(to, merge_file, *len_ptr + 1); merge_file = to; } - if (!sanitize_path(fn, merge_file, r, dirbuf_depth)) { + if (!sanitize_path(fn, merge_file, r, dirbuf_depth, SP_DEFAULT)) { rprintf(FERROR, "merge-file name overflows: %s\n", merge_file); return NULL; diff --git a/flist.c b/flist.c index f6385ca6..dce55c7b 100644 --- a/flist.c +++ b/flist.c @@ -687,7 +687,7 @@ static struct file_struct *recv_file_entry(struct file_list *flist, clean_fname(thisname, 0); if (sanitize_paths) - sanitize_path(thisname, thisname, "", 0); + sanitize_path(thisname, thisname, "", 0, SP_DEFAULT); if ((basename = strrchr(thisname, '/')) != NULL) { int len = basename++ - thisname; @@ -939,7 +939,7 @@ static struct file_struct *recv_file_entry(struct file_list *flist, } else { read_sbuf(f, bp, linkname_len - 1); if (sanitize_paths) - sanitize_path(bp, bp, "", lastdir_depth); + sanitize_path(bp, bp, "", lastdir_depth, SP_DEFAULT); } } #endif @@ -1030,7 +1030,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist, } clean_fname(thisname, 0); if (sanitize_paths) - sanitize_path(thisname, thisname, "", 0); + sanitize_path(thisname, thisname, "", 0, SP_DEFAULT); if (stp && S_ISDIR(stp->st_mode)) { st = *stp; /* Needed for "symlink/." with --relative. */ @@ -1795,6 +1795,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) | (filesfrom_convert ? RL_CONVERT : 0) #endif | (eol_nulls || reading_remotely ? RL_EOL_NULLS : 0); + int implied_dot_dir = 0; rprintf(FLOG, "building file list\n"); if (show_filelist_p()) @@ -1836,13 +1837,13 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) if (use_ff_fd) { if (read_line(filesfrom_fd, fbuf, sizeof fbuf, rl_flags) == 0) break; - sanitize_path(fbuf, fbuf, "", 0); + sanitize_path(fbuf, fbuf, "", 0, SP_KEEP_DOT_DIRS); } else { if (argc-- == 0) break; strlcpy(fbuf, *argv++, MAXPATHLEN); if (sanitize_paths) - sanitize_path(fbuf, fbuf, "", 0); + sanitize_path(fbuf, fbuf, "", 0, SP_KEEP_DOT_DIRS); } len = strlen(fbuf); @@ -1892,8 +1893,10 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) *p = '\0'; if (p == fbuf) dir = "/"; - else + else { dir = fbuf; + clean_fname(dir, 0); + } fn = p + 3; while (*fn == '/') fn++; @@ -1901,8 +1904,15 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) *--fn = '\0'; /* ensure room for '.' */ } else fn = fbuf; - len = clean_fname(fn, CFN_KEEP_LEADING_DOT_DIR - | CFN_KEEP_TRAILING_SLASH + /* A leading ./ can be used in relative mode to affect + * the dest dir without its name being in the path. */ + if (*fn == '.' && fn[1] == '/' && !implied_dot_dir) { + send_file_name(f, flist, ".", NULL, + (flags | FLAG_IMPLIED_DIR) & ~FLAG_CONTENT_DIR, + ALL_FILTERS); + implied_dot_dir = 1; + } + len = clean_fname(fn, CFN_KEEP_TRAILING_SLASH | CFN_DROP_TRAILING_DOT_DIR); if (len == 1) { if (fn[0] == '/') { @@ -1996,15 +2006,17 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) int top_flags = FLAG_TOP_DIR | FLAG_CONTENT_DIR | flags; file = send_file_name(f, flist, fbuf, &st, top_flags, ALL_FILTERS); + if (!file) + continue; if (inc_recurse) { - if (name_type == DOT_NAME && file) { + if (name_type == DOT_NAME) { if (send_dir_depth < 0) { send_dir_depth = 0; change_local_filter_dir(fbuf, len, send_dir_depth); } send_directory(f, flist, fbuf, len, flags); } - } else if (file) + } else send_if_directory(f, flist, file, fbuf, len, flags); } else send_file_name(f, flist, fbuf, &st, flags, ALL_FILTERS); diff --git a/main.c b/main.c index 629f6d20..5985f808 100644 --- a/main.c +++ b/main.c @@ -902,9 +902,9 @@ static void do_server_recv(int f_in, int f_out, int argc, char *argv[]) if (sanitize_paths) { char **dir_p; for (dir_p = basis_dir; *dir_p; dir_p++) - *dir_p = sanitize_path(NULL, *dir_p, NULL, curr_dir_depth); + *dir_p = sanitize_path(NULL, *dir_p, NULL, curr_dir_depth, SP_DEFAULT); if (partial_dir) - partial_dir = sanitize_path(NULL, partial_dir, NULL, curr_dir_depth); + partial_dir = sanitize_path(NULL, partial_dir, NULL, curr_dir_depth, SP_DEFAULT); } check_alt_basis_dirs(); diff --git a/options.c b/options.c index b63956f2..f2d23f62 100644 --- a/options.c +++ b/options.c @@ -1032,7 +1032,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) case OPT_INCLUDE_FROM: arg = poptGetOptArg(pc); if (sanitize_paths) - arg = sanitize_path(NULL, arg, NULL, 0); + arg = sanitize_path(NULL, arg, NULL, 0, SP_DEFAULT); if (daemon_filter_list.head) { int rej; char *dir, *cp = strdup(arg); @@ -1438,11 +1438,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (sanitize_paths) { int i; for (i = argc; i-- > 0; ) - argv[i] = sanitize_path(NULL, argv[i], "", 0); + argv[i] = sanitize_path(NULL, argv[i], "", 0, SP_KEEP_DOT_DIRS); if (tmpdir) - tmpdir = sanitize_path(NULL, tmpdir, NULL, 0); + tmpdir = sanitize_path(NULL, tmpdir, NULL, 0, SP_DEFAULT); if (backup_dir) - backup_dir = sanitize_path(NULL, backup_dir, NULL, 0); + backup_dir = sanitize_path(NULL, backup_dir, NULL, 0, SP_DEFAULT); } if (daemon_filter_list.head && !am_sender) { struct filter_list_struct *elp = &daemon_filter_list; @@ -1650,7 +1650,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) } } else { if (sanitize_paths) - files_from = sanitize_path(NULL, files_from, NULL, 0); + files_from = sanitize_path(NULL, files_from, NULL, 0, SP_DEFAULT); if (daemon_filter_list.head) { char *dir; if (!*files_from) diff --git a/rsync.h b/rsync.h index d7dcb08a..7f7e3531 100644 --- a/rsync.h +++ b/rsync.h @@ -189,11 +189,14 @@ #define SIGNIFICANT_ITEM_FLAGS (~(\ ITEM_BASIS_TYPE_FOLLOWS | ITEM_XNAME_FOLLOWS | ITEM_LOCAL_CHANGE)) -#define CFN_KEEP_LEADING_DOT_DIR (1<<0) +#define CFN_KEEP_DOT_DIRS (1<<0) #define CFN_KEEP_TRAILING_SLASH (1<<1) #define CFN_DROP_TRAILING_DOT_DIR (1<<2) #define CFN_COLLAPSE_DOT_DOT_DIRS (1<<3) +#define SP_DEFAULT 0 +#define SP_KEEP_DOT_DIRS (1<<0) + /* Log-message categories. FLOG only goes to the log file, not the client; * FCLIENT is the opposite. */ enum logcode { diff --git a/util.c b/util.c index 5a8333e8..a8b17d53 100644 --- a/util.c +++ b/util.c @@ -547,11 +547,15 @@ void glob_expand(char *s, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr) s = "."; if (sanitize_paths) - s = sanitize_path(NULL, s, "", 0); - else + s = sanitize_path(NULL, s, "", 0, SP_KEEP_DOT_DIRS); + else { s = strdup(s); - if (!s) - out_of_memory("glob_expand"); + if (!s) + out_of_memory("glob_expand"); + clean_fname(s, CFN_KEEP_DOT_DIRS + | CFN_KEEP_TRAILING_SLASH + | CFN_COLLAPSE_DOT_DOT_DIRS); + } memset(&globbuf, 0, sizeof globbuf); glob(s, 0, NULL, &globbuf); @@ -715,13 +719,13 @@ int count_dir_elements(const char *p) return cnt; } -/* Turns multiple adjacent slashes into a single slash, drops interior "." - * elements, drops an intial "./" unless CFN_KEEP_LEADING_DOT_DIR is flagged, - * will even drop a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is - * flagged, removes a trailing slash (perhaps after removing the aforementioned - * dot) unless CFN_KEEP_TRAILING_SLASH is flagged, will even collapse ".." - * elements (except at the start of the string) if CFN_COLLAPSE_DOT_DOT_DIRS - * is flagged. If the resulting name would be empty, we return ".". */ +/* Turns multiple adjacent slashes into a single slash, drops all leading or + * interior "." elements unless CFN_KEEP_DOT_DIRS is flagged. Will also drop + * a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is flagged, removes + * a trailing slash (perhaps after removing the aforementioned dot) unless + * CFN_KEEP_TRAILING_SLASH is flagged, and will also collapse ".." elements + * (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged. If the + * resulting name would be empty, returns ".". */ unsigned int clean_fname(char *name, int flags) { char *limit = name - 1, *t = name, *f = name; @@ -732,7 +736,7 @@ unsigned int clean_fname(char *name, int flags) if ((anchored = *f == '/') != 0) *t++ = *f++; - else if (flags & CFN_KEEP_LEADING_DOT_DIR && *f == '.' && f[1] == '/') { + else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') { *t++ = *f++; *t++ = *f++; } @@ -744,7 +748,7 @@ unsigned int clean_fname(char *name, int flags) } if (*f == '.') { /* discard interior "." dirs */ - if (f[1] == '/') { + if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) { f += 2; continue; } @@ -801,10 +805,11 @@ unsigned int clean_fname(char *name, int flags) * ALWAYS collapses ".." elements (except for those at the start of the * string up to "depth" deep). If the resulting name would be empty, * change it into a ".". */ -char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) +char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, + int flags) { char *start, *sanp; - int rlen = 0, leave_one_dotdir = relative_paths; + int rlen = 0, drop_dot_dirs = !relative_paths || !(flags & SP_KEEP_DOT_DIRS); if (dest != p) { int plen = strlen(p); @@ -827,6 +832,11 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) } } + if (drop_dot_dirs) { + while (*p == '.' && p[1] == '/') + p += 2; + } + start = sanp = dest + rlen; /* This loop iterates once per filename component in p, pointing at * the start of the name (past any prior slash) for each iteration. */ @@ -836,10 +846,8 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) p++; continue; } - if (*p == '.' && (p[1] == '/' || p[1] == '\0')) { - if (leave_one_dotdir && p[1]) - leave_one_dotdir = 0; - else { + if (drop_dot_dirs) { + if (*p == '.' && (p[1] == '/' || p[1] == '\0')) { /* skip "." component */ p++; continue; @@ -852,10 +860,8 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) if (sanp != start) { /* back up sanp one level */ --sanp; /* now pointing at slash */ - while (sanp > start && sanp[-1] != '/') { - /* skip back up to slash */ + while (sanp > start && sanp[-1] != '/') sanp--; - } } continue; } -- 2.34.1