]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: Avoid a crash due to a python-brace-format string with a NUL byte.
authorBruno Haible <bruno@clisp.org>
Sun, 12 Mar 2023 10:45:11 +0000 (11:45 +0100)
committerBruno Haible <bruno@clisp.org>
Tue, 14 Mar 2023 01:57:28 +0000 (02:57 +0100)
* gettext-tools/src/format-python-brace.c: Improve comments.
(parse_directive): Don't read past the string end if c1 is NUL.
* gettext-tools/tests/xgettext-python-6: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add it.

gettext-tools/src/format-python-brace.c
gettext-tools/tests/Makefile.am
gettext-tools/tests/xgettext-python-6 [new file with mode: 0755]

index 969e58d1138c50ba8b6a946ffb4f7440184817ad..229917952a301e10583f753ae96a025f1103c3ba 100644 (file)
 
 #define _(str) gettext (str)
 
-/* Python brace format strings are defined by PEP3101 together with
-   'format' method of string class.
+/* Python brace format strings are defined by PEP3101 together with the
+   'format' method of the string class.
    A format string directive here consists of
      - an opening brace '{',
      - an identifier [_A-Za-z][_0-9A-Za-z]*|[0-9]+,
-     - an optional getattr ('.') or getitem ('['..']') operator with
-       an identifier as argument,
-     - an optional format specifier starting with ':', with a
-       (unnested) format string as argument,
+     - an optional sequence of
+         - getattr ('.' identifier) or
+         - getitem ('[' identifier ']')
+       operators,
+     - optionally, a ':' and a format specifier, where a format specifier is
+       - either a format directive of the form '{' ... '}' without a format
+         specifier, or
+       - of the form [[fill]align][sign][#][0][minimumwidth][.precision][type]
+         where
+           - the fill character is any character,
+           - the align flag is one of '<', '>', '=', '^',
+           - the sign is one of '+', '-', ' ',
+           - the # flag is '#',
+           - the 0 flag is '0',
+           - minimumwidth is a non-empty sequence of digits,
+           - precision is a non-empty sequence of digits,
+           - type is one of
+             - 'b', 'c', 'd', 'o', 'x', 'X', 'n' for integers,
+             - 'e', 'E', 'f', 'F', 'g', 'G', 'n', '%' for floating-point values,
      - a closing brace '}'.
-   Brace characters '{' and '}' can be escaped by doubles '{{' and '}}'.
+   Brace characters '{' and '}' can be escaped by doubling them: '{{' and '}}'.
 */
 
 struct named_arg
@@ -132,7 +147,8 @@ parse_directive (struct spec *spec,
       && !parse_numeric_field (spec, &format, translated, fdi, invalid_reason))
     {
       *invalid_reason =
-        xasprintf (_("In the directive number %u, '%c' cannot start a field name."), spec->directives, *format);
+        xasprintf (_("In the directive number %u, '%c' cannot start a field name."),
+                   spec->directives, *format);
       FDI_SET (format, FMTDIR_ERROR);
       return false;
     }
@@ -151,7 +167,8 @@ parse_directive (struct spec *spec,
                                   invalid_reason))
             {
               *invalid_reason =
-                xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."), spec->directives, *format);
+                xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."),
+                           spec->directives, *format);
               FDI_SET (format, FMTDIR_ERROR);
               return false;
             }
@@ -165,7 +182,8 @@ parse_directive (struct spec *spec,
                                        invalid_reason))
             {
               *invalid_reason =
-                xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."), spec->directives, *format);
+                xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."),
+                           spec->directives, *format);
               FDI_SET (format, FMTDIR_ERROR);
               return false;
             }
@@ -187,7 +205,8 @@ parse_directive (struct spec *spec,
       if (!is_toplevel)
         {
           *invalid_reason =
-            xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."), spec->directives);
+            xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."),
+                       spec->directives);
           FDI_SET (format, FMTDIR_ERROR);
           return false;
         }
@@ -197,7 +216,7 @@ parse_directive (struct spec *spec,
          specifiers below, because otherwise we would need to evaluate
          Python expressions by ourselves:
 
-           - A nested format directive expanding to the whole string
+           - A nested format directive expanding to an argument
            - The Standard Format Specifiers, as described in PEP3101,
              not including a nested format directive  */
       format++;
@@ -228,6 +247,15 @@ parse_directive (struct spec *spec,
           int c1, c2;
 
           c1 = format[0];
+          if (c1 == '\0')
+            {
+              *invalid_reason =
+                xasprintf (_("In the directive number %u, there is an unterminated format directive."),
+                           spec->directives);
+              FDI_SET (format, FMTDIR_ERROR);
+              return false;
+            }
+
           c2 = format[1];
 
           if (c2 == '<' || c2 == '>' || c2 == '=' || c2 == '^')
index b0d96c83e4a3a1a4641bb0f0ad37a170f560ca12..cfbc7179ced7628869d021d240de8bfbe5c68223 100644 (file)
@@ -139,7 +139,7 @@ TESTS = gettext-1 gettext-2 \
        xgettext-properties-4 \
        xgettext-rst-1 xgettext-rst-2 \
        xgettext-python-1 xgettext-python-2 xgettext-python-3 \
-       xgettext-python-4 xgettext-python-5 \
+       xgettext-python-4 xgettext-python-5 xgettext-python-6 \
        xgettext-python-stackovfl-1 xgettext-python-stackovfl-2 \
        xgettext-python-stackovfl-3 xgettext-python-stackovfl-4 \
        xgettext-ruby-1 \
diff --git a/gettext-tools/tests/xgettext-python-6 b/gettext-tools/tests/xgettext-python-6
new file mode 100755 (executable)
index 0000000..72657a0
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test Python support: a python-brace-format string with a NUL byte.
+
+tr X '\0' <<\EOF > xg-py-6.py
+_("{0:X>}")
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d xg-py-6.tmp xg-py-6.py || Exit 1
+LC_ALL=C tr -d '\r' < xg-py-6.tmp.po > xg-py-6.po || Exit 1
+
+cat <<EOF > xg-py-6.ok
+msgid "{0:"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-py-6.ok xg-py-6.po
+result=$?
+
+exit $result