aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2018-12-23 05:16:46 +0100
committerJay Berkenbilt <ejb@ql.org>2018-12-23 15:15:40 +0100
commit52a0b767c8b2acb18bbdc076b258092dc122a1c6 (patch)
tree532dcc2f489cef80a61af55ddae7765bb989ba19
parent6b90f3db4da5191d158a1eb19232408757c8c302 (diff)
downloadqpdf-52a0b767c8b2acb18bbdc076b258092dc122a1c6.tar.zst
Slightly improve bash completion arg parsing
-rw-r--r--qpdf/qpdf.cc81
-rw-r--r--qpdf/qtest/qpdf.test6
-rw-r--r--qpdf/qtest/qpdf/completion-quoting.out1
-rw-r--r--qpdf/qtest/qpdf/completion-usage-empty.out1
4 files changed, 76 insertions, 13 deletions
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc
index ec75aee4..45bce84f 100644
--- a/qpdf/qpdf.cc
+++ b/qpdf/qpdf.cc
@@ -1414,30 +1414,85 @@ void
ArgParser::handleBashArguments()
{
// Do a minimal job of parsing bash_line into arguments. This
- // doesn't do everything the shell does, but it should be good
- // enough for purposes of handling completion. We can't use
- // new_argv because this has to interoperate with @file arguments.
-
- enum { st_top, st_quote } state = st_top;
+ // doesn't do everything the shell does (e.g. $(...), variable
+ // expansion, arithmetic, globs, etc.), but it should be good
+ // enough for purposes of handling completion. As we build up the
+ // new argv, we can't use this->new_argv because this code has to
+ // interoperate with @file arguments, so memory for both ways of
+ // fabricating argv has to be protected.
+
+ bool last_was_backslash = false;
+ enum { st_top, st_squote, st_dquote } state = st_top;
std::string arg;
for (std::string::iterator iter = bash_line.begin();
iter != bash_line.end(); ++iter)
{
char ch = (*iter);
- if ((state == st_top) && QUtil::is_space(ch) && (! arg.empty()))
+ if (last_was_backslash)
{
- bash_argv.push_back(
- PointerHolder<char>(
- true, QUtil::copy_string(arg.c_str())));
- arg.clear();
+ arg.append(1, ch);
+ last_was_backslash = false;
+ }
+ else if (ch == '\\')
+ {
+ last_was_backslash = true;
}
else
{
- if (ch == '"')
+ bool append = false;
+ switch (state)
{
- state = (state == st_top ? st_quote : st_top);
+ case st_top:
+ if (QUtil::is_space(ch))
+ {
+ if (! arg.empty())
+ {
+ bash_argv.push_back(
+ PointerHolder<char>(
+ true, QUtil::copy_string(arg.c_str())));
+ arg.clear();
+ }
+ }
+ else if (ch == '"')
+ {
+ state = st_dquote;
+ }
+ else if (ch == '\'')
+ {
+ state = st_squote;
+ }
+ else
+ {
+ append = true;
+ }
+ break;
+
+ case st_squote:
+ if (ch == '\'')
+ {
+ state = st_top;
+ }
+ else
+ {
+ append = true;
+ }
+ break;
+
+ case st_dquote:
+ if (ch == '"')
+ {
+ state = st_top;
+ }
+ else
+ {
+ append = true;
+ }
+ break;
+ }
+ if (append)
+ {
+ arg.append(1, ch);
}
- arg.append(1, ch);
}
}
if (bash_argv.empty())
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 1919f737..f2f0579f 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -117,6 +117,12 @@ my @completion_tests = (
['qpdf --decode-lzzz', 15, 'decode-l'],
['qpdf --decode-level=', undef, 'decode-level'],
['qpdf --check -', undef, 'later-arg'],
+ ['qpdf infile outfile oops --ch', undef, 'usage-empty'],
+ ['qpdf --encrypt \'user " password\' ', undef, 'quoting'],
+ ['qpdf --encrypt \'user password\' ', undef, 'quoting'],
+ ['qpdf --encrypt "user password" ', undef, 'quoting'],
+ ['qpdf --encrypt "user pass\'word" ', undef, 'quoting'],
+ ['qpdf --encrypt user\ password ', undef, 'quoting'],
);
$n_tests += scalar(@completion_tests);
foreach my $c (@completion_tests)
diff --git a/qpdf/qtest/qpdf/completion-quoting.out b/qpdf/qtest/qpdf/completion-quoting.out
new file mode 100644
index 00000000..0d8a0622
--- /dev/null
+++ b/qpdf/qtest/qpdf/completion-quoting.out
@@ -0,0 +1 @@
+owner-password
diff --git a/qpdf/qtest/qpdf/completion-usage-empty.out b/qpdf/qtest/qpdf/completion-usage-empty.out
new file mode 100644
index 00000000..442e725d
--- /dev/null
+++ b/qpdf/qtest/qpdf/completion-usage-empty.out
@@ -0,0 +1 @@
+!--check