Skip to content

Commit

Permalink
Add support for shell-like escaping in %files
Browse files Browse the repository at this point in the history
Turn off the special meaning of glob characters within a file name if
they're preceded by a backslash.  This was already partially implemented
using rpmIsGlob() which interprets the backslash as an escape character,
we just need to make sure any backslashes are discarded as soon as
they've been consumed, i.e. before the file name is passed to lstat(2)
via addFile(), so that it actually works.

Note that, while rpmGlob() understands escapes too, they're only
applicable to spaces and quotes during string tokenization, not to
globs, and changing its behavior would break the API, so we need to
handle glob escapes separately.  For this reason, we also need to use
rpmIsGlob() when processing special directories like %doc, so do that.

For curly braces specifically, we need to push the brace detection logic
from rpmIsGlob() down to __glob_pattern_p() so that backslashes are also
applicable to those.  That's probably where it belongs, anyway.  Since
we're now passing it the flags directly, we need to make sure the
behavior stays the same in the other few places where __glob_pattern_p()
is called, by turning off the GLOB_BRACE flag there.  This part is
basically a revert of commit 630a097,
although functionally equivalent.

Fixes: rpm-software-management#1749
  • Loading branch information
dmnks committed Apr 22, 2022
1 parent 4f34fa9 commit cd108fa
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 41 deletions.
22 changes: 17 additions & 5 deletions build/files.c
Original file line number Diff line number Diff line change
Expand Up @@ -2152,13 +2152,14 @@ static int generateBuildIDs(FileList fl, ARGV_t *files)
* Add a file to a binary package.
* @param pkg
* @param fl package file tree walk data
* @param fileName file to add
* @param fn file to add
* @return RPMRC_OK on success
*/
static rpmRC processBinaryFile(Package pkg, FileList fl, const char * fileName)
static rpmRC processBinaryFile(Package pkg, FileList fl, const char *fn)
{
int quote = 1; /* XXX permit quoted glob characters. */
int doGlob;
char *fileName = xstrdup(fn);
char *diskPath = NULL;
rpmRC rc = RPMRC_OK;
size_t fnlen = strlen(fileName);
Expand All @@ -2168,7 +2169,8 @@ static rpmRC processBinaryFile(Package pkg, FileList fl, const char * fileName)
if (trailing_slash && !fl->cur.isDir)
fl->cur.isDir = -1;

doGlob = rpmIsGlob(fileName, quote);
if (!(doGlob = rpmIsGlob(fileName, quote)))
rpmUnescape(fileName, NULL);

/* Check that file starts with leading "/" */
if (*fileName != '/') {
Expand Down Expand Up @@ -2220,6 +2222,7 @@ static rpmRC processBinaryFile(Package pkg, FileList fl, const char * fileName)
}

exit:
free(fileName);
free(diskPath);
if (rc) {
fl->processingFailed = 1;
Expand Down Expand Up @@ -2416,7 +2419,7 @@ static void processSpecialDir(rpmSpec spec, Package pkg, FileList fl,
fi = 0;
while (*files != NULL) {
char *origfile = rpmGenPath(basepath, *files, NULL);
char *eorigfile = rpmEscapeSpaces(origfile);
char *eorigfile = NULL;
ARGV_t globFiles;
int globFilesCount, i;
char *newfile;
Expand All @@ -2426,7 +2429,17 @@ static void processSpecialDir(rpmSpec spec, Package pkg, FileList fl,
copyFileEntry(&sd->entries[fi].curEntry, &fl->cur);
copyFileEntry(&sd->entries[fi].defEntry, &fl->def);
fi++;
files++;

if (!rpmIsGlob(origfile, 1)) {
rasprintf(&newfile, "%s/%s", sd->dirname, basename(origfile));
processBinaryFile(pkg, fl, newfile);
free(newfile);
free(origfile);
continue;
}

eorigfile = rpmEscapeSpaces(origfile);
if (rpmGlob(eorigfile, &globFilesCount, &globFiles) == 0) {
for (i = 0; i < globFilesCount; i++) {
rasprintf(&newfile, "%s/%s", sd->dirname, basename(globFiles[i]));
Expand All @@ -2440,7 +2453,6 @@ static void processSpecialDir(rpmSpec spec, Package pkg, FileList fl,
}
free(eorigfile);
free(origfile);
files++;
}
free(basepath);

Expand Down
7 changes: 7 additions & 0 deletions include/rpm/rpmfileutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ int rpmGlob(const char * patterns, int * argcPtr, ARGV_t * argvPtr);
*/
char * rpmEscapeSpaces(const char * s);

/** \ingroup rpmfileutil
* Unescape each char listed in accept by removing a backslash preceding it.
* @param s string
* @param accept chars to escape (NULL for all)
*/
void rpmUnescape(char *s, const char *accept);

/** \ingroup rpmfileutil
* Return type of compression used in file.
* @param file name of file
Expand Down
16 changes: 16 additions & 0 deletions rpmio/rpmfileutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,22 @@ char * rpmEscapeSpaces(const char * s)
return t;
}

void rpmUnescape(char *s, const char *accept)
{
char *p, *q;
int esc = 0;
p = q = s;
while (*q != '\0') {
*p = *q++;
esc = (*p == '\\') && \
(accept == NULL || ((*q != '\0') && strchr(accept, *q))) && \
!esc;
if (!esc)
p++;
}
*p = '\0';
}

int rpmFileHasSuffix(const char *path, const char *suffix)
{
size_t plen = strlen(path);
Expand Down
56 changes: 21 additions & 35 deletions rpmio/rpmglob.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ static inline const char *next_brace_sub(const char *begin)
return *cp != '\0' ? cp : NULL;
}

static int __glob_pattern_p(const char *pattern, int quote);
static int __glob_pattern_p(const char *pattern, int flags);

/* Do glob searching for PATTERN, placing results in PGLOB.
The bits defined above may be set in FLAGS.
Expand Down Expand Up @@ -395,7 +395,7 @@ glob(const char *pattern, int flags,
return GLOB_NOMATCH;
}

if (__glob_pattern_p(dirname, !(flags & GLOB_NOESCAPE))) {
if (__glob_pattern_p(dirname, flags & ~GLOB_BRACE)) {
/* The directory name contains metacharacters, so we
have to glob for the directory, and then glob for
the pattern in each directory found. */
Expand Down Expand Up @@ -621,10 +621,11 @@ static int prefix_array(const char *dirname, char **array, size_t n)
}

/* Return nonzero if PATTERN contains any metacharacters.
Metacharacters can be quoted with backslashes if QUOTE is nonzero. */
static int __glob_pattern_p(const char *pattern, int quote)
Metacharacters can be quoted with backslashes if FLAGS does not contain
GLOB_NOESCAPE. */
static int __glob_pattern_p(const char *pattern, int flags)
{
register const char *p;
register const char *p, *q;
int openBrackets = 0;

for (p = pattern; *p != '\0'; ++p)
Expand All @@ -634,7 +635,7 @@ static int __glob_pattern_p(const char *pattern, int quote)
return 1;

case '\\':
if (quote && p[1] != '\0')
if (!(flags & GLOB_NOESCAPE) && p[1] != '\0')
++p;
break;

Expand All @@ -646,6 +647,15 @@ static int __glob_pattern_p(const char *pattern, int quote)
if (openBrackets)
return 1;
break;

case '{':
if (!(flags & GLOB_BRACE))
break;
q = p;
while (*q != '}')
if ((q = next_brace_sub(q + 1)) == NULL)
break;
return 1;
}

return 0;
Expand All @@ -670,7 +680,7 @@ glob_in_dir(const char *pattern, const char *directory, int flags,
int meta;
int save;

meta = __glob_pattern_p(pattern, !(flags & GLOB_NOESCAPE));
meta = __glob_pattern_p(pattern, flags & ~GLOB_BRACE);
if (meta == 0) {
if (flags & (GLOB_NOCHECK | GLOB_NOMAGIC))
/* We need not do any tests. The PATTERN contains no meta
Expand Down Expand Up @@ -942,32 +952,8 @@ int rpmGlob(const char * patterns, int * argcPtr, ARGV_t * argvPtr)

int rpmIsGlob(const char * pattern, int quote)
{
if (!__glob_pattern_p(pattern, quote)) {

const char *begin;
const char *next;
const char *rest;

begin = strchr(pattern, '{');
if (begin == NULL)
return 0;
/*
* Find the first sub-pattern and at the same time find the
* rest after the closing brace.
*/
next = next_brace_sub(begin + 1);
if (next == NULL)
return 0;

/* Now find the end of the whole brace expression. */
rest = next;
while (*rest != '}') {
rest = next_brace_sub(rest + 1);
if (rest == NULL)
return 0;
}
/* Now we can be sure that brace expression is well-foermed. */
}

return 1;
int flags = GLOB_BRACE;
if (!quote)
flags |= GLOB_NOESCAPE;
return __glob_pattern_p(pattern, flags);
}
3 changes: 2 additions & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ EXTRA_DIST += data/SPECS/hello2-suid.spec
EXTRA_DIST += data/SPECS/hello-g3.spec
EXTRA_DIST += data/SPECS/foo.spec
EXTRA_DIST += data/SPECS/globtest.spec
EXTRA_DIST += data/SPECS/globesctest.spec
EXTRA_DIST += data/SPECS/versiontest.spec
EXTRA_DIST += data/SPECS/conflicttest.spec
EXTRA_DIST += data/SPECS/configtest.spec
Expand Down Expand Up @@ -192,7 +193,7 @@ populate_testing: $(check_DATA)
for d in dev etc magic tmp var; do if [ ! -d testing/$${d} ]; then mkdir testing/$${d}; fi; done
for node in urandom stdin stderr stdout null full; do ln -s /dev/$${node} testing/dev/$${node}; done
for cf in hosts resolv.conf passwd shadow group gshadow mtab ; do [ -f /etc/$${cf} ] && ln -s /etc/$${cf} testing/etc/$${cf}; done
for prog in gzip cat patch tar sh ln chmod rm mkdir uname grep sed find file ionice mktemp nice cut sort diff touch install wc coreutils xargs; do p=`which $${prog}`; if [ "$${p}" != "" ]; then ln -s $${p} testing/$(bindir)/; fi; done
for prog in gzip cat cp patch tar sh ln chmod rm mkdir uname grep sed find file ionice mktemp nice cut sort diff touch install wc coreutils xargs; do p=`which $${prog}`; if [ "$${p}" != "" ]; then ln -s $${p} testing/$(bindir)/; fi; done
for d in /proc /sys /selinux /etc/selinux; do if [ -d $${d} ]; then ln -s $${d} testing/$${d}; fi; done
(cd testing/magic && file -C)
chmod -R u-w testing/
Expand Down
70 changes: 70 additions & 0 deletions tests/data/SPECS/globesctest.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# We need to hardcode the install prefix so that we can compare the %%doc
# filenames in the resulting package (with "rpm -qpl") against a static list in
# tests/rpmbuild.at.
%global _prefix /opt

Name: globesctest
Version: 1.0
Release: 1
Summary: Testing file glob escape behavior
Group: Testing
License: GPL
BuildArch: noarch

%description
%{summary}.


%build
touch 'foo[bar]' bar baz 'foo bar'

%install
mkdir -p %{buildroot}/opt

# Glob escaping
touch '%{buildroot}/opt/foo[bar]'
touch '%{buildroot}/opt/foo[bar baz]'
touch '%{buildroot}/opt/foo\[bar\]'
touch '%{buildroot}/opt/foo*'
touch '%{buildroot}/opt/foo\bar'
touch %{buildroot}/opt/foo\\
touch '%{buildroot}/opt/foo?bar'
touch '%{buildroot}/opt/foo{bar,baz}'

# Regression checks
touch '%{buildroot}/opt/foo-bar1'
touch '%{buildroot}/opt/foo-bar2'
touch '%{buildroot}/opt/fooxbarybaz'
touch "%{buildroot}/opt/foo'baz"
touch '%{buildroot}/opt/foobar'
touch '%{buildroot}/opt/foobaz'
touch '%{buildroot}/opt/foobara'
touch '%{buildroot}/opt/foobarb'
touch '%{buildroot}/opt/foobaza'
touch '%{buildroot}/opt/foobazb'
touch '%{buildroot}/opt/foobaya'
touch '%{buildroot}/opt/foobayb'
touch '%{buildroot}/opt/foobawa'
touch '%{buildroot}/opt/foobawb'

%files

%doc foo\[bar\] ba* "foo bar"

# Glob escaping
/opt/foo\[bar\]
"/opt/foo\[bar baz\]"
/opt/foo\\\[bar\\\]
/opt/foo\*
/opt/foo\\bar
/opt/foo\\
/opt/foo\?bar
/opt/foo\{bar,baz\}

# Regression checks
/opt/foo-bar*
/opt/foo?bar?baz
/opt/foo'baz
/opt/foo{bar,baz}
/opt/foo{bar{a,b},baz{a,b}}
/opt/foo{bay*,baw*}
41 changes: 41 additions & 0 deletions tests/rpmbuild.at
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,47 @@ warning: absolute symlink: /opt/globtest/linkgood -> /opt/globtest/zab
])
AT_CLEANUP

AT_SETUP([rpmbuild glob escape])
AT_KEYWORDS([build])
AT_CHECK([
RPMDB_INIT

runroot rpmbuild -bb --quiet /data/SPECS/globesctest.spec
runroot rpm -qpl /build/RPMS/noarch/globesctest-1.0-1.noarch.rpm
],
[0],
[/opt/foo'baz
/opt/foo*
/opt/foo-bar1
/opt/foo-bar2
/opt/foo?bar
/opt/foo[[bar baz]]
/opt/foo[[bar]]
/opt/foo\
/opt/foo\[[bar\]]
/opt/foo\bar
/opt/foobar
/opt/foobara
/opt/foobarb
/opt/foobawa
/opt/foobawb
/opt/foobaya
/opt/foobayb
/opt/foobaz
/opt/foobaza
/opt/foobazb
/opt/fooxbarybaz
/opt/foo{bar,baz}
/opt/share/doc/globesctest-1.0
/opt/share/doc/globesctest-1.0/bar
/opt/share/doc/globesctest-1.0/baz
/opt/share/doc/globesctest-1.0/foo bar
/opt/share/doc/globesctest-1.0/foo[[bar]]
],
[],
)
AT_CLEANUP

AT_SETUP([rpmbuild prefixpostfix])
AT_KEYWORDS([build])
AT_CHECK([
Expand Down

0 comments on commit cd108fa

Please sign in to comment.