fish: Handle backslash escapes in guestfish double-quoted strings.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 14 Jul 2011 17:17:39 +0000 (18:17 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 14 Jul 2011 17:17:39 +0000 (18:17 +0100)
fish/fish.c
fish/guestfish.pod
regressions/Makefile.am
regressions/test-guestfish-escapes.sh [new file with mode: 0755]

index a57472f..4e45cea 100644 (file)
@@ -61,6 +61,7 @@ static void shell_script (void);
 static void script (int prompt);
 static void cmdline (char *argv[], int optind, int argc);
 static struct parsed_command parse_command_line (char *buf, int *exit_on_error_rtn);
+static int parse_quoted_string (char *p);
 static int execute_and_inline (const char *cmd, int exit_on_error);
 static void initialize_readline (void);
 static void cleanup_readline (void);
@@ -758,9 +759,8 @@ parse_command_line (char *buf, int *exit_on_error_rtn)
      */
     if (*p == '"') {
       p++;
-      len = strcspn (p, "\"");
-      if (p[len] == '\0') {
-        fprintf (stderr, _("%s: unterminated double quote\n"), program_name);
+      len = parse_quoted_string (p);
+      if (len == -1) {
         pcmd.status = -1;
         return pcmd;
       }
@@ -771,7 +771,6 @@ parse_command_line (char *buf, int *exit_on_error_rtn)
         pcmd.status = -1;
         return pcmd;
       }
-      p[len] = '\0';
       pend = p[len+1] ? &p[len+2] : &p[len+1];
     } else if (*p == '\'') {
       p++;
@@ -835,6 +834,87 @@ parse_command_line (char *buf, int *exit_on_error_rtn)
   return pcmd;
 }
 
+static int
+hexdigit (char d)
+{
+  switch (d) {
+  case '0'...'9': return d - '0';
+  case 'a'...'f': return d - 'a' + 10;
+  case 'A'...'F': return d - 'A' + 10;
+  default: return -1;
+  }
+}
+
+/* Parse double-quoted strings, replacing backslash escape sequences
+ * with the true character.  Since the string is returned in place,
+ * the escapes must make the string shorter.
+ */
+static int
+parse_quoted_string (char *p)
+{
+  char *start = p;
+
+  for (; *p && *p != '"'; p++) {
+    if (*p == '\\') {
+      int m = 1, c;
+
+      switch (p[1]) {
+      case '\\': break;
+      case 'a': *p = '\a'; break;
+      case 'b': *p = '\b'; break;
+      case 'f': *p = '\f'; break;
+      case 'n': *p = '\n'; break;
+      case 'r': *p = '\r'; break;
+      case 't': *p = '\t'; break;
+      case 'v': *p = '\v'; break;
+      case '"': *p = '"'; break;
+      case '\'': *p = '\''; break;
+      case '?': *p = '?'; break;
+
+      case '0'...'7':           /* octal escape - always 3 digits */
+        m = 3;
+        if (p[2] >= '0' && p[2] <= '7' &&
+            p[3] >= '0' && p[3] <= '7') {
+          c = (p[1] - '0') * 0100 + (p[2] - '0') * 010 + (p[3] - '0');
+          if (c < 1 || c > 255)
+            goto error;
+          *p = c;
+        }
+        else
+          goto error;
+        break;
+
+      case 'x':                 /* hex escape - always 2 digits */
+        m = 3;
+        if (c_isxdigit (p[2]) && c_isxdigit (p[3])) {
+          c = hexdigit (p[2]) * 0x10 + hexdigit (p[3]);
+          if (c < 1 || c > 255)
+            goto error;
+          *p = c;
+        }
+        else
+          goto error;
+        break;
+
+      default:
+      error:
+        fprintf (stderr, _("%s: invalid escape sequence in string (starting at offset %d)\n"),
+                 program_name, (int) (p - start));
+        return -1;
+      }
+      memmove (p+1, p+1+m, strlen (p+1+m) + 1);
+    }
+  }
+
+  if (!*p) {
+    fprintf (stderr, _("%s: unterminated double quote\n"), program_name);
+    return -1;
+  }
+
+  *p = '\0';
+  return p - start;
+}
+
 /* Used to handle "<!" (execute command and inline result). */
 static int
 execute_and_inline (const char *cmd, int global_exit_on_error)
index a3d944d..9f53c1c 100644 (file)
@@ -518,6 +518,64 @@ must be escaped with a backslash.
  command "/bin/echo 'foo      bar'"
  command "/bin/echo \'foo\'"
 
+=head2 ESCAPE SEQUENCES IN DOUBLE QUOTED ARGUMENTS
+
+In double-quoted arguments (only) use backslash to insert special
+characters:
+
+=over 4
+
+=item C<\a>
+
+Alert (bell) character.
+
+=item C<\b>
+
+Backspace character.
+
+=item C<\f>
+
+Form feed character.
+
+=item C<\n>
+
+Newline character.
+
+=item C<\r>
+
+Carriage return character.
+
+=item C<\t>
+
+Horizontal tab character.
+
+=item C<\v>
+
+Vertical tab character.
+
+=item C<\">
+
+A literal double quote character.
+
+=item C<\ooo>
+
+A character with octal value I<ooo>.  There must be precisely 3 octal
+digits (unlike C).
+
+=item C<\xhh>
+
+A character with hex value I<hh>.  There must be precisely 2 hex
+digits.
+
+In the current implementation C<\000> and C<\x00> cannot be used
+in strings.
+
+=item C<\\>
+
+A literal backslash character.
+
+=back
+
 =head1 OPTIONAL ARGUMENTS
 
 Some commands take optional arguments.  These arguments appear in this
index a612ee3..726fb76 100644 (file)
@@ -42,6 +42,7 @@ TESTS = \
        test-find0.sh \
        test-guestfish-a.sh \
        test-guestfish-d.sh \
+       test-guestfish-escapes.sh \
        test-guestfish-tilde.sh \
        test-inspect-fstab.sh \
        test-launch-race.pl \
diff --git a/regressions/test-guestfish-escapes.sh b/regressions/test-guestfish-escapes.sh
new file mode 100755 (executable)
index 0000000..6f4f434
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/bash -
+# libguestfs
+# Copyright (C) 2011 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# Test guestfish string escapes.
+
+set -e
+
+rm -f test.output test.error
+
+../fish/guestfish <<'EOF' 2>test.error | od > test.output
+echo ""
+echo " "
+echo "  "
+echo "\n"
+echo "\r"
+echo "\n\n"
+echo "\x01"
+echo "\001"
+echo "\100"
+
+# These are invalid:
+-echo "\x00"
+-echo "\000"
+-echo "\x"
+-echo "\x0"
+-echo "\7"
+-echo "\77"
+-echo "\777"
+-echo "\"
+-echo "\\\"
+-echo "
+-echo """
+EOF
+
+if [ "$(cat test.error)" != "\
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: invalid escape sequence in string (starting at offset 0)
+guestfish: unterminated double quote
+guestfish: unterminated double quote
+guestfish: unterminated double quote
+guestfish: command arguments not separated by whitespace" ]; then
+    echo "unexpected stderr from guestfish:"
+    cat test.error
+    echo "[end of stderr]"
+    exit 1
+fi
+
+if [ "$(cat test.output)" != "\
+0000000 020012 020012 005040 005012 005015 005012 000412 000412
+0000020 040012 000012
+0000023" ]; then
+    echo "unexpected stdout from guestfish:"
+    cat test.output
+    echo "[end of stdout]"
+    exit 1
+fi
+
+rm -f test.output test.error