Skip to content

Commit 10ac298

Browse files
committed
attr_file: fix unescaping of escapes required for fnmatch
When parsing attribute patterns, we will eventually unescape the parsed pattern. This is required because we require custom escapes for whitespace characters, as normally they are used to terminate the current pattern. Thing is, we don't only unescape those whitespace characters, but in fact all escaped sequences. So for example if the pattern was "\*", we unescape that to "*". As this is directly passed to fnmatch(3) later, fnmatch would treat it as a simple glob matching all files where it should instead only match a file with name "*". Fix the issue by unescaping spaces, only. Add a bunch of tests to exercise escape parsing.
1 parent eb146e5 commit 10ac298

File tree

2 files changed

+92
-2
lines changed

2 files changed

+92
-2
lines changed

src/attr_file.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,34 @@ static size_t trailing_space_length(const char *p, size_t len)
574574
return len - n;
575575
}
576576

577+
static size_t unescape_spaces(char *str)
578+
{
579+
char *scan, *pos = str;
580+
bool escaped = false;
581+
582+
if (!str)
583+
return 0;
584+
585+
for (scan = str; *scan; scan++) {
586+
if (!escaped && *scan == '\\') {
587+
escaped = true;
588+
continue;
589+
}
590+
591+
/* Only insert the escape character for escaped non-spaces */
592+
if (escaped && !git__isspace(*scan))
593+
*pos++ = '\\';
594+
595+
*pos++ = *scan;
596+
escaped = false;
597+
}
598+
599+
if (pos != scan)
600+
*pos = '\0';
601+
602+
return (pos - str);
603+
}
604+
577605
/*
578606
* This will return 0 if the spec was filled out,
579607
* GIT_ENOTFOUND if the fnmatch does not require matching, or
@@ -701,8 +729,8 @@ int git_attr_fnmatch__parse(
701729
*base = git__next_line(pattern);
702730
return -1;
703731
} else {
704-
/* strip '\' that might have be used for internal whitespace */
705-
spec->length = git__unescape(spec->pattern);
732+
/* strip '\' that might have been used for internal whitespace */
733+
spec->length = unescape_spaces(spec->pattern);
706734
/* TODO: convert remaining '\' into '/' for POSIX ??? */
707735
}
708736

tests/ignore/path.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,65 @@ void test_ignore_path__negative_directory_rules_only_match_directories(void)
459459
assert_is_ignored(false, "src/A.keep");
460460
assert_is_ignored(false, ".gitignore");
461461
}
462+
463+
void test_ignore_path__escaped_character(void)
464+
{
465+
cl_git_rewritefile("attr/.gitignore", "\\c\n");
466+
assert_is_ignored(true, "c");
467+
assert_is_ignored(false, "\\c");
468+
}
469+
470+
void test_ignore_path__escaped_newline(void)
471+
{
472+
cl_git_rewritefile(
473+
"attr/.gitignore",
474+
"\\\nnewline\n"
475+
);
476+
477+
assert_is_ignored(true, "\nnewline");
478+
}
479+
480+
void test_ignore_path__escaped_glob(void)
481+
{
482+
cl_git_rewritefile("attr/.gitignore", "\\*\n");
483+
assert_is_ignored(true, "*");
484+
assert_is_ignored(false, "foo");
485+
}
486+
487+
void test_ignore_path__escaped_comments(void)
488+
{
489+
cl_git_rewritefile(
490+
"attr/.gitignore",
491+
"#foo\n"
492+
"\\#bar\n"
493+
"\\##baz\n"
494+
"\\#\\\\#qux\n"
495+
);
496+
497+
assert_is_ignored(false, "#foo");
498+
assert_is_ignored(true, "#bar");
499+
assert_is_ignored(false, "\\#bar");
500+
assert_is_ignored(true, "##baz");
501+
assert_is_ignored(false, "\\##baz");
502+
assert_is_ignored(true, "#\\#qux");
503+
assert_is_ignored(false, "##qux");
504+
assert_is_ignored(false, "\\##qux");
505+
}
506+
507+
void test_ignore_path__escaped_slash(void)
508+
{
509+
cl_git_rewritefile(
510+
"attr/.gitignore",
511+
"\\\\\n"
512+
"\\\\preceding\n"
513+
"inter\\\\mittent\n"
514+
"trailing\\\\\n"
515+
);
516+
517+
#ifndef GIT_WIN32
518+
assert_is_ignored(true, "\\");
519+
assert_is_ignored(true, "\\preceding");
520+
#endif
521+
assert_is_ignored(true, "inter\\mittent");
522+
assert_is_ignored(true, "trailing\\");
523+
}

0 commit comments

Comments
 (0)