Blob Blame History Raw
diff --git a/exclude.c b/exclude.c
index 13c4253..232249f 100644
--- a/exclude.c
+++ b/exclude.c
@@ -79,6 +79,10 @@ static filter_rule **mergelist_parents;
 static int mergelist_cnt = 0;
 static int mergelist_size = 0;
 
+#define LOCAL_RULE   1
+#define REMOTE_RULE  2
+static uchar cur_elide_value = REMOTE_RULE;
+
 /* Each filter_list_struct describes a singly-linked list by keeping track
  * of both the head and tail pointers.  The list is slightly unusual in that
  * a parent-dir's content can be appended to the end of the local list in a
@@ -218,6 +222,7 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_
 				slash_cnt++;
 		}
 	}
+	rule->elide = 0;
 	strlcpy(rule->pattern + pre_len, pat, pat_len + 1);
 	pat_len += pre_len;
 	if (suf_len) {
@@ -364,6 +369,8 @@ void implied_include_partial_string(const char *s_start, const char *s_end)
 void free_implied_include_partial_string()
 {
 	if (partial_string_buf) {
+		if (partial_string_len)
+			add_implied_include("", 0);
 		free(partial_string_buf);
 		partial_string_buf = NULL;
 	}
@@ -374,9 +381,8 @@ void free_implied_include_partial_string()
  * that the receiver uses to validate the file list from the sender. */
 void add_implied_include(const char *arg, int skip_daemon_module)
 {
-	filter_rule *rule;
 	int arg_len, saw_wild = 0, saw_live_open_brkt = 0, backslash_cnt = 0;
-	int slash_cnt = 1; /* We know we're adding a leading slash. */
+	int slash_cnt = 0;
 	const char *cp;
 	char *p;
 	if (am_server || old_style_args || list_only || read_batch || filesfrom_host != NULL)
@@ -407,6 +413,7 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 		arg++;
 	arg_len = strlen(arg);
 	if (arg_len) {
+		char *new_pat;
 		if (strpbrk(arg, "*[?")) {
 			/* We need to add room to escape backslashes if wildcard chars are present. */
 			for (cp = arg; (cp = strchr(cp, '\\')) != NULL; cp++)
@@ -414,16 +421,9 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 			saw_wild = 1;
 		}
 		arg_len++; /* Leave room for the prefixed slash */
-		rule = new0(filter_rule);
-		if (!implied_filter_list.head)
-			implied_filter_list.head = implied_filter_list.tail = rule;
-		else {
-			rule->next = implied_filter_list.head;
-			implied_filter_list.head = rule;
-		}
-		rule->rflags = FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0);
-		p = rule->pattern = new_array(char, arg_len + 1);
+		p = new_pat = new_array(char, arg_len + 1);
 		*p++ = '/';
+		slash_cnt++;
 		for (cp = arg; *cp; ) {
 			switch (*cp) {
 			  case '\\':
@@ -439,15 +439,70 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 				break;
 			  case '/':
 				if (p[-1] == '/') { /* This is safe because of the initial slash. */
+					if (*++cp == '\0') {
+						slash_cnt--;
+						p--;
+					}
+				} else if (cp[1] == '\0') {
 					cp++;
-					break;
+				} else {
+					slash_cnt++;
+					*p++ = *cp++;
 				}
-				if (relative_paths) {
-					filter_rule const *ent;
+				break;
+			  case '.':
+				if (p[-1] == '/') {
+					if (cp[1] == '/') {
+						cp += 2;
+						if (!*cp) {
+							slash_cnt--;
+							p--;
+						}
+					} else if (cp[1] == '\0') {
+						cp++;
+						slash_cnt--;
+						p--;
+					} else
+						*p++ = *cp++;
+				} else
+					*p++ = *cp++;
+				break;
+			  case '[':
+				saw_live_open_brkt = 1;
+				*p++ = *cp++;
+				break;
+			  default:
+				*p++ = *cp++;
+				break;
+			}
+		}
+		*p = '\0';
+		arg_len = p - new_pat;
+		if (!arg_len)
+			free(new_pat);
+		else {
+			filter_rule *rule = new0(filter_rule);
+			rule->rflags = FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0);
+			rule->u.slash_cnt = slash_cnt;
+			arg = rule->pattern = new_pat;
+			if (!implied_filter_list.head)
+				implied_filter_list.head = implied_filter_list.tail = rule;
+			else {
+				rule->next = implied_filter_list.head;
+				implied_filter_list.head = rule;
+			}
+			if (DEBUG_GTE(FILTER, 3))
+				rprintf(FINFO, "[%s] add_implied_include(%s)\n", who_am_i(), arg);
+			if (saw_live_open_brkt)
+				maybe_add_literal_brackets_rule(rule, arg_len);
+			if (relative_paths && slash_cnt) {
+				filter_rule const *ent;
+				slash_cnt = 1;
+				for (p = new_pat + 1; (p = strchr(p, '/')) != NULL; p++) {
 					int found = 0;
 					*p = '\0';
 					for (ent = implied_filter_list.head; ent; ent = ent->next) {
-						if (ent != rule && strcmp(ent->pattern, rule->pattern) == 0) {
+						if (ent != rule && strcmp(ent->pattern, new_pat) == 0) {
 							found = 1;
 							break;
 						}
@@ -456,9 +511,9 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 						filter_rule *R_rule = new0(filter_rule);
 						R_rule->rflags = FILTRULE_INCLUDE | FILTRULE_DIRECTORY;
 						/* Check if our sub-path has wildcards or escaped backslashes */
-						if (saw_wild && strpbrk(rule->pattern, "*[?\\"))
+						if (saw_wild && strpbrk(new_pat, "*[?\\"))
 							R_rule->rflags |= FILTRULE_WILD;
-						R_rule->pattern = strdup(rule->pattern);
+						R_rule->pattern = strdup(new_pat);
 						R_rule->u.slash_cnt = slash_cnt;
 						R_rule->next = implied_filter_list.head;
 						implied_filter_list.head = R_rule;
@@ -469,32 +524,16 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 						if (saw_live_open_brkt)
 							maybe_add_literal_brackets_rule(R_rule, -1);
 					}
+					*p = '/';
+					slash_cnt++;
 				}
-				slash_cnt++;
-				*p++ = *cp++;
-				break;
-			  case '[':
-				saw_live_open_brkt = 1;
-				*p++ = *cp++;
-				break;
-			  default:
-				*p++ = *cp++;
-				break;
 			}
 		}
-		*p = '\0';
-		rule->u.slash_cnt = slash_cnt;
-		arg = rule->pattern;
-		arg_len = p - arg; /* We recompute it due to backslash weirdness. */
-		if (DEBUG_GTE(FILTER, 3))
-			rprintf(FINFO, "[%s] add_implied_include(%s)\n", who_am_i(), rule->pattern);
-		if (saw_live_open_brkt)
-			maybe_add_literal_brackets_rule(rule, arg_len);
 	}
 
 	if (recurse || xfer_dirs) {
 		/* Now create a rule with an added "/" & "**" or "*" at the end */
-		rule = new0(filter_rule);
+		filter_rule *rule = new0(filter_rule);
 		rule->rflags = FILTRULE_INCLUDE | FILTRULE_WILD;
 		if (recurse)
 			rule->rflags |= FILTRULE_WILD2;
@@ -502,7 +541,7 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 		if (!saw_wild && backslash_cnt) {
 			/* We are appending a wildcard, so now the backslashes need to be escaped. */
 			p = rule->pattern = new_array(char, arg_len + backslash_cnt + 3 + 1);
-			for (cp = arg; *cp; ) {
+			for (cp = arg; *cp; ) { /* Note that arg_len != 0 because backslash_cnt > 0 */
 				if (*cp == '\\')
 					*p++ = '\\';
 				*p++ = *cp++;
@@ -514,13 +553,15 @@ void add_implied_include(const char *arg, int skip_daemon_module)
 				p += arg_len;
 			}
 		}
-		if (p[-1] != '/')
+		if (p[-1] != '/') {
 			*p++ = '/';
+			slash_cnt++;
+		}
 		*p++ = '*';
 		if (recurse)
 			*p++ = '*';
 		*p = '\0';
-		rule->u.slash_cnt = slash_cnt + 1;
+		rule->u.slash_cnt = slash_cnt;
 		rule->next = implied_filter_list.head;
 		implied_filter_list.head = rule;
 		if (DEBUG_GTE(FILTER, 3))
@@ -869,7 +910,7 @@ static int rule_matches(const char *fname, filter_rule *ex, int name_flags)
 	const char *strings[16]; /* more than enough */
 	const char *name = fname + (*fname == '/');
 
-	if (!*name)
+	if (!*name || ex->elide == cur_elide_value)
 		return 0;
 
 	if (!(name_flags & NAME_IS_XATTR) ^ !(ex->rflags & FILTRULE_XATTR))
@@ -985,6 +1026,15 @@ int name_is_excluded(const char *fname, int name_flags, int filter_level)
 	return 0;
 }
 
+int check_server_filter(filter_rule_list *listp, enum logcode code, const char *name, int name_flags)
+{
+	int ret;
+	cur_elide_value = LOCAL_RULE;
+	ret = check_filter(listp, code, name, name_flags);
+	cur_elide_value = REMOTE_RULE;
+	return ret;
+}
+
 /* Return -1 if file "name" is defined to be excluded by the specified
  * exclude list, 1 if it is included, and 0 if it was not matched. */
 int check_filter(filter_rule_list *listp, enum logcode code,
@@ -1550,7 +1600,7 @@ char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer,
 
 static void send_rules(int f_out, filter_rule_list *flp)
 {
-	filter_rule *ent, *prev = NULL;
+	filter_rule *ent;
 
 	for (ent = flp->head; ent; ent = ent->next) {
 		unsigned int len, plen, dlen;
@@ -1565,21 +1615,15 @@ static void send_rules(int f_out, filter_rule_list *flp)
 		 * merge files as an optimization (since they can only have
 		 * include/exclude rules). */
 		if (ent->rflags & FILTRULE_SENDER_SIDE)
-			elide = am_sender ? 1 : -1;
+			elide = am_sender ? LOCAL_RULE : REMOTE_RULE;
 		if (ent->rflags & FILTRULE_RECEIVER_SIDE)
-			elide = elide ? 0 : am_sender ? -1 : 1;
+			elide = elide ? 0 : am_sender ? REMOTE_RULE : LOCAL_RULE;
 		else if (delete_excluded && !elide
 		 && (!(ent->rflags & FILTRULE_PERDIR_MERGE)
 		  || ent->rflags & FILTRULE_NO_PREFIXES))
-			elide = am_sender ? 1 : -1;
-		if (elide < 0) {
-			if (prev)
-				prev->next = ent->next;
-			else
-				flp->head = ent->next;
-		} else
-			prev = ent;
-		if (elide > 0)
+			elide = am_sender ? LOCAL_RULE : REMOTE_RULE;
+		ent->elide = elide;
+		if (elide == LOCAL_RULE)
 			continue;
 		if (ent->rflags & FILTRULE_CVS_IGNORE
 		    && !(ent->rflags & FILTRULE_MERGE_FILE)) {
@@ -1607,7 +1651,6 @@ static void send_rules(int f_out, filter_rule_list *flp)
 		if (dlen)
 			write_byte(f_out, '/');
 	}
-	flp->tail = prev;
 }
 
 /* This is only called by the client. */
diff --git a/options.c b/options.c
index afc33ce..4d0a1a6 100644
--- a/options.c
+++ b/options.c
@@ -2426,7 +2426,9 @@ char *safe_arg(const char *opt, const char *arg)
 	char *ret;
 	if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) {
 		const char *f;
-		if (!old_style_args && *arg == '~' && (relative_paths || !strchr(arg, '/'))) {
+		if (!old_style_args && *arg == '~' 
+				&& ((relative_paths && !strstr(arg, "/./")) 
+				|| !strchr(arg, '/'))) {
 			extras++;
 			escape_leading_tilde = 1;
 		}
diff --git a/flist.c b/flist.c
index 630d685..8c2397b 100644
--- a/flist.c
+++ b/flist.c
@@ -904,10 +904,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
 		exit_cleanup(RERR_UNSUPPORTED);
 	}
 
-	if (*thisname != '.' || thisname[1] != '\0') {
+	if (*thisname == '/' ? thisname[1] != '.' || thisname[2] != '\0' : *thisname != '.' || thisname[1] != '\0') {
 		int filt_flags = S_ISDIR(mode) ? NAME_IS_DIR : NAME_IS_FILE;
 		if (!trust_sender_filter /* a per-dir filter rule means we must trust the sender's filtering */
-		 && filter_list.head && check_filter(&filter_list, FINFO, thisname, filt_flags) < 0) {
+		 && filter_list.head && check_server_filter(&filter_list, FINFO, thisname, filt_flags) < 0) {
 			rprintf(FERROR, "ERROR: rejecting excluded file-list name: %s\n", thisname);
 			exit_cleanup(RERR_PROTOCOL);
 		}
diff --git a/rsync.h b/rsync.h
index 53fff2d..b357dad 100644
--- a/rsync.h
+++ b/rsync.h
@@ -899,6 +899,7 @@ typedef struct filter_struct {
 		int slash_cnt;
 		struct filter_list_struct *mergelist;
 	} u;
+	uchar elide;
 } filter_rule;
 
 typedef struct filter_list_struct {