X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/820b6c9aa0cf587fda363c2e473ca9c140f7866c..e012b94f21da5dcf2043b3dc967e4723c7ba4b35:/util.c?ds=sidebyside
diff --git a/util.c b/util.c
index d896082d..28a00ef8 100644
--- a/util.c
+++ b/util.c
@@ -31,6 +31,7 @@ extern int verbose;
extern int dry_run;
extern int module_id;
extern int modify_window;
+extern char *partial_dir;
extern struct exclude_list_struct server_exclude_list;
int sanitize_paths = 0;
@@ -522,9 +523,10 @@ static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr,
if (!*s)
s = ".";
- s = strdup(s);
if (sanitize_paths)
- sanitize_path(s, NULL);
+ s = sanitize_path(NULL, s, NULL);
+ else
+ s = strdup(s);
memset(&globbuf, 0, sizeof globbuf);
if (!exclude_server_path(s))
@@ -648,80 +650,106 @@ size_t stringjoin(char *dest, size_t destsize, ...)
return ret;
}
-void clean_fname(char *name)
+unsigned int clean_fname(char *name)
{
- char *p;
- int l;
- int modified = 1;
+ char *limit = name - 1, *t = name, *f = name;
+ int anchored;
if (!name)
- return;
-
- while (modified) {
- modified = 0;
+ return 0;
- if ((p = strstr(name,"/./")) != NULL) {
- modified = 1;
- while (*p) {
- p[0] = p[2];
- p++;
- }
+ if ((anchored = *f == '/') != 0)
+ *t++ = *f++;
+ while (*f) {
+ /* discard extra slashes */
+ if (*f == '/') {
+ f++;
+ continue;
}
-
- if ((p = strstr(name,"//")) != NULL) {
- modified = 1;
- while (*p) {
- p[0] = p[1];
- p++;
+ if (*f == '.') {
+ /* discard "." dirs (but NOT a trailing '.'!) */
+ if (f[1] == '/') {
+ f += 2;
+ continue;
+ }
+ /* collapse ".." dirs */
+ if (f[1] == '.' && (f[2] == '/' || !f[2])) {
+ char *s = t - 1;
+ if (s == name && anchored) {
+ f += 2;
+ continue;
+ }
+ while (s > limit && *--s != '/') {}
+ if (s != t - 1 && (s < name || *s == '/')) {
+ t = s + 1;
+ f += 2;
+ continue;
+ }
+ *t++ = *f++;
+ *t++ = *f++;
+ limit = t;
}
}
+ while (*f && (*t++ = *f++) != '/') {}
+ }
- if (strncmp(p = name, "./", 2) == 0) {
- modified = 1;
- do {
- p[0] = p[2];
- } while (*p++);
- }
+ if (t > name+anchored && t[-1] == '/')
+ t--;
+ if (t == name)
+ *t++ = '.';
+ *t = '\0';
- l = strlen(p = name);
- if (l > 1 && p[l-1] == '/') {
- modified = 1;
- p[l-1] = 0;
- }
- }
+ return t - name;
}
-/**
- * Make path appear as if a chroot had occurred:
- *
- * @li 1. remove leading "/" (or replace with "." if at end)
+/* Make path appear as if a chroot had occurred. This handles a leading
+ * "/" (either removing it or expanding it) and any leading or embedded
+ * ".." components that attempt to escape past the module's top dir.
*
- * @li 2. remove leading ".." components (except those allowed by @p reldir)
+ * If dest is NULL, a buffer is allocated to hold the result. If dest is
+ * the same buffer as p (the path) OR if reldir is NULL, a leading slash
+ * is dropped instead of being expanded to be the module's top dir.
*
- * @li 3. delete any other "
/.." (recursively)
- *
- * Can only shrink paths, so sanitizes in place.
+ * If reldir is non-NULL (and non-empty), it is a sanitized directory that
+ * the path will be relative to, so allow as many '..'s at the beginning of
+ * the path as there are components in reldir. This is used for symbolic
+ * link targets. If reldir is non-null and the path began with "/", to be
+ * completely like a chroot we should add in depth levels of ".." at the
+ * beginning of the path, but that would blow the assumption that the path
+ * doesn't grow and it is not likely to end up being a valid symlink
+ * anyway, so just do the normal removal of the leading "/" instead.
*
* While we're at it, remove double slashes and "." components like
- * clean_fname() does, but DON'T remove a trailing slash because that
- * is sometimes significant on command line arguments.
- *
- * If @p reldir is non-null, it is a sanitized directory that the path will be
- * relative to, so allow as many ".." at the beginning of the path as
- * there are components in reldir. This is used for symbolic link targets.
- * If reldir is non-null and the path began with "/", to be completely like
- * a chroot we should add in depth levels of ".." at the beginning of the
- * path, but that would blow the assumption that the path doesn't grow and
- * it is not likely to end up being a valid symlink anyway, so just do
- * the normal removal of the leading "/" instead.
+ * clean_fname() does, but DON'T remove a trailing slash because that is
+ * sometimes significant on command line arguments.
*
- * Contributed by Dave Dykstra
+ * If the resulting path would be empty, change it into ".".
*/
-void sanitize_path(char *p, char *reldir)
+char *sanitize_path(char *dest, const char *p, const char *reldir)
{
char *start, *sanp;
int depth = 0;
int allowdotdot = 0;
+ int rlen = 0;
+
+ if (dest != p) {
+ int plen = strlen(p);
+ if (*p == '/' && reldir) {
+ rlen = strlen(lp_path(module_id));
+ reldir = NULL;
+ p++;
+ }
+ if (dest) {
+ if (rlen + plen + 1 >= MAXPATHLEN)
+ return NULL;
+ } else if (!(dest = new_array(char, rlen + plen + 1)))
+ out_of_memory("sanitize_path");
+ if (rlen) {
+ memcpy(dest, lp_path(module_id), rlen);
+ if (rlen > 1)
+ dest[rlen++] = '/';
+ }
+ }
if (reldir) {
int new_component = 1;
@@ -734,8 +762,8 @@ void sanitize_path(char *p, char *reldir)
}
}
}
- start = p;
- sanp = p;
+
+ start = sanp = dest + rlen;
while (*p == '/') {
/* remove leading slashes */
p++;
@@ -791,51 +819,13 @@ void sanitize_path(char *p, char *reldir)
start = sanp;
}
}
- if (sanp == start && !allowdotdot) {
+ if (sanp == dest) {
/* ended up with nothing, so put in "." component */
- /*
- * note that the !allowdotdot doesn't prevent this from
- * happening in all allowed ".." situations, but I didn't
- * think it was worth putting in an extra variable to ensure
- * it since an extra "." won't hurt in those situations.
- */
*sanp++ = '.';
}
*sanp = '\0';
-}
-
-/* Works much like sanitize_path(), with these differences: (1) a new buffer
- * is allocated for the sanitized path rather than modifying it in-place; (2)
- * a leading slash gets transformed into the rootdir value (which can be empty
- * or NULL if you just want the slash to get dropped); (3) no "reldir" can be
- * specified. */
-char *alloc_sanitize_path(const char *path, const char *rootdir)
-{
- char *buf;
- int rlen, plen = strlen(path);
- if (*path == '/' && rootdir) {
- rlen = strlen(rootdir);
- if (rlen == 1)
- path++;
- } else
- rlen = 0;
- if (!(buf = new_array(char, rlen + plen + 1)))
- out_of_memory("alloc_sanitize_path");
- if (rlen)
- memcpy(buf, rootdir, rlen);
- memcpy(buf + rlen, path, plen + 1);
-
- if (rlen > 1)
- rlen++;
- sanitize_path(buf + rlen, NULL);
- if (rlen && buf[rlen] == '.' && buf[rlen+1] == '\0') {
- if (rlen > 1)
- rlen--;
- buf[rlen] = '\0';
- }
-
- return buf;
+ return dest;
}
char curr_dir[MAXPATHLEN];
@@ -879,7 +869,7 @@ int push_dir(char *dir)
curr_dir_len += len;
}
- clean_fname(curr_dir);
+ curr_dir_len = clean_fname(curr_dir);
return 1;
}
@@ -902,17 +892,20 @@ int pop_dir(char *dir)
/**
* Return the filename, turning any newlines into '?'s. This ensures that
- * outputting it on a line of its own cannot generate an empty line.
+ * outputting it on a line of its own cannot generate an empty line. This
+ * function can handle only 2 names at a time!
**/
const char *safe_fname(const char *fname)
{
- static char fbuf[MAXPATHLEN];
+ static char fbuf1[MAXPATHLEN], fbuf2[MAXPATHLEN];
+ static char *fbuf = fbuf2;
char *nl = strchr(fname, '\n');
if (!nl)
return fname;
- strlcpy(fbuf, fname, sizeof fbuf);
+ fbuf = fbuf == fbuf1 ? fbuf2 : fbuf1;
+ strlcpy(fbuf, fname, MAXPATHLEN);
nl = fbuf + (nl - (char *)fname);
do {
*nl = '?';
@@ -935,6 +928,7 @@ char *full_fname(const char *fn)
if (result)
free(result);
+ fn = safe_fname(fn);
if (*fn == '/')
p1 = p2 = "";
else {
@@ -966,6 +960,69 @@ char *full_fname(const char *fn)
return result;
}
+static char partial_fname[MAXPATHLEN];
+
+char *partial_dir_fname(const char *fname)
+{
+ char *t = partial_fname;
+ int sz = sizeof partial_fname;
+ const char *fn;
+
+ if ((fn = strrchr(fname, '/')) != NULL) {
+ fn++;
+ if (*partial_dir != '/') {
+ int len = fn - fname;
+ strncpy(t, fname, len); /* safe */
+ t += len;
+ sz -= len;
+ }
+ } else
+ fn = fname;
+ if ((int)pathjoin(t, sz, partial_dir, fn) >= sz)
+ return NULL;
+ if (server_exclude_list.head
+ && check_exclude(&server_exclude_list, partial_fname, 0) < 0)
+ return NULL;
+
+ return partial_fname;
+}
+
+/* If no --partial-dir option was specified, we don't need to do anything
+ * (the partial-dir is essentially '.'), so just return success. */
+int handle_partial_dir(const char *fname, int create)
+{
+ char *fn, *dir;
+
+ if (fname != partial_fname)
+ return 1;
+ if (!create && *partial_dir == '/')
+ return 1;
+ if (!(fn = strrchr(partial_fname, '/')))
+ return 1;
+
+ *fn = '\0';
+ dir = partial_fname;
+ if (create) {
+ STRUCT_STAT st;
+#if SUPPORT_LINKS
+ int statret = do_lstat(dir, &st);
+#else
+ int statret = do_stat(dir, &st);
+#endif
+ if (statret == 0 && !S_ISDIR(st.st_mode)) {
+ if (do_unlink(dir) < 0)
+ return 0;
+ statret = -1;
+ }
+ if (statret < 0 && do_mkdir(dir, 0700) < 0)
+ return 0;
+ } else
+ do_rmdir(dir);
+ *fn = '/';
+
+ return 1;
+}
+
/** We need to supply our own strcmp function for file list comparisons
to ensure that signed/unsigned usage is consistent between machines. */
int u_strcmp(const char *cs1, const char *cs2)