Home | History | Annotate | Line # | Download | only in src
      1 /* Shell format strings.
      2    Copyright (C) 2003-2004, 2006 Free Software Foundation, Inc.
      3    Written by Bruno Haible <bruno (at) clisp.org>, 2003.
      4 
      5    This program is free software; you can redistribute it and/or modify
      6    it under the terms of the GNU General Public License as published by
      7    the Free Software Foundation; either version 2, or (at your option)
      8    any later version.
      9 
     10    This program is distributed in the hope that it will be useful,
     11    but WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13    GNU General Public License for more details.
     14 
     15    You should have received a copy of the GNU General Public License
     16    along with this program; if not, write to the Free Software Foundation,
     17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
     18 
     19 #ifdef HAVE_CONFIG_H
     20 # include <config.h>
     21 #endif
     22 
     23 #include <stdbool.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 
     27 #include "format.h"
     28 #include "c-ctype.h"
     29 #include "xalloc.h"
     30 #include "format-invalid.h"
     31 #include "gettext.h"
     32 
     33 #define _(str) gettext (str)
     34 
     35 /* Shell format strings are simply strings subjects to variable substitution.
     36    A variable substitution starts with '$' and is finished by either
     37    - a nonempty sequence of alphanumeric ASCII characters, the first being
     38      not a digit, or
     39    - an opening brace '{', a nonempty sequence of alphanumeric ASCII
     40      characters, the first being not a digit, and a closing brace '}'.
     41    We don't support variable references like $1, $$ or $? since they make
     42    no sense when 'envsubst' is invoked.
     43    We don't support non-ASCII variable names, to avoid dependencies w.r.t. the
     44    current encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
     45    encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
     46    SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
     47    encodings.
     48    We don't support the POSIX syntax for default or alternate values:
     49      ${variable-default}        ${variable:-default}
     50      ${variable=default}        ${variable:=default}
     51      ${variable+replacement}    ${variable:+replacement}
     52      ${variable?ignored}        ${variable:?ignored}
     53    because the translator might be tempted to change the default value; if
     54    we allow it we have a security problem; if we don't allow it the translator
     55    will be surprised.
     56  */
     57 
     58 struct named_arg
     59 {
     60   char *name;
     61 };
     62 
     63 struct spec
     64 {
     65   unsigned int directives;
     66   unsigned int named_arg_count;
     67   unsigned int allocated;
     68   struct named_arg *named;
     69 };
     70 
     71 
     72 static int
     73 named_arg_compare (const void *p1, const void *p2)
     74 {
     75   return strcmp (((const struct named_arg *) p1)->name,
     76 		 ((const struct named_arg *) p2)->name);
     77 }
     78 
     79 #define INVALID_NON_ASCII_VARIABLE() \
     80   xstrdup (_("The string refers to a shell variable with a non-ASCII name."))
     81 #define INVALID_SHELL_SYNTAX() \
     82   xstrdup (_("The string refers to a shell variable with complex shell brace syntax. This syntax is unsupported here due to security reasons."))
     83 #define INVALID_CONTEXT_DEPENDENT_VARIABLE() \
     84   xstrdup (_("The string refers to a shell variable whose value may be different inside shell functions."))
     85 #define INVALID_EMPTY_VARIABLE() \
     86   xstrdup (_("The string refers to a shell variable with an empty name."))
     87 
     88 static void *
     89 format_parse (const char *format, bool translated, char **invalid_reason)
     90 {
     91   struct spec spec;
     92   struct spec *result;
     93 
     94   spec.directives = 0;
     95   spec.named_arg_count = 0;
     96   spec.allocated = 0;
     97   spec.named = NULL;
     98 
     99   for (; *format != '\0';)
    100     if (*format++ == '$')
    101       {
    102 	/* A variable substitution.  */
    103 	char *name;
    104 
    105 	spec.directives++;
    106 
    107 	if (*format == '{')
    108 	  {
    109 	    const char *name_start;
    110 	    const char *name_end;
    111 	    size_t n;
    112 
    113 	    name_start = ++format;
    114 	    for (; *format != '\0'; format++)
    115 	      {
    116 		if (*format == '}')
    117 		  break;
    118 		if (!c_isascii (*format))
    119 		  {
    120 		    *invalid_reason = INVALID_NON_ASCII_VARIABLE ();
    121 		    goto bad_format;
    122 		  }
    123 		if (format > name_start
    124 		    && (*format == '-' || *format == '=' || *format == '+'
    125 			|| *format == '?' || *format == ':'))
    126 		  {
    127 		    *invalid_reason = INVALID_SHELL_SYNTAX ();
    128 		    goto bad_format;
    129 		  }
    130 		if (!(c_isalnum (*format) || *format == '_')
    131 		    || (format == name_start && c_isdigit (*format)))
    132 		  {
    133 		    *invalid_reason = INVALID_CONTEXT_DEPENDENT_VARIABLE ();
    134 		    goto bad_format;
    135 		  }
    136 	      }
    137 	    if (*format == '\0')
    138 	      {
    139 		*invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
    140 		goto bad_format;
    141 	      }
    142 	    name_end = format++;
    143 
    144 	    n = name_end - name_start;
    145 	    if (n == 0)
    146 	      {
    147 		*invalid_reason = INVALID_EMPTY_VARIABLE ();
    148 		goto bad_format;
    149 	      }
    150 	    name = (char *) xmalloc (n + 1);
    151 	    memcpy (name, name_start, n);
    152 	    name[n] = '\0';
    153 	  }
    154 	else if (c_isalpha (*format) || *format == '_')
    155 	  {
    156 	    const char *name_start;
    157 	    const char *name_end;
    158 	    size_t n;
    159 
    160 	    name_start = format;
    161 	    do
    162 	      format++;
    163 	    while (*format != '\0' && (c_isalnum (*format) || *format == '_'));
    164 	    name_end = format;
    165 
    166 	    n = name_end - name_start;
    167 	    name = (char *) xmalloc (n + 1);
    168 	    memcpy (name, name_start, n);
    169 	    name[n] = '\0';
    170 	  }
    171 	else if (*format != '\0')
    172 	  {
    173 	    if (!c_isascii (*format))
    174 	      {
    175 		*invalid_reason = INVALID_NON_ASCII_VARIABLE ();
    176 		goto bad_format;
    177 	      }
    178 	    else
    179 	      {
    180 		*invalid_reason = INVALID_CONTEXT_DEPENDENT_VARIABLE ();
    181 		goto bad_format;
    182 	      }
    183 	  }
    184 	else
    185 	  {
    186 	    *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
    187 	    goto bad_format;
    188 	  }
    189 
    190 	/* Named argument.  */
    191 	if (spec.allocated == spec.named_arg_count)
    192 	  {
    193 	    spec.allocated = 2 * spec.allocated + 1;
    194 	    spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg));
    195 	  }
    196 	spec.named[spec.named_arg_count].name = name;
    197 	spec.named_arg_count++;
    198       }
    199 
    200   /* Sort the named argument array, and eliminate duplicates.  */
    201   if (spec.named_arg_count > 1)
    202     {
    203       unsigned int i, j;
    204 
    205       qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
    206 	     named_arg_compare);
    207 
    208       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
    209       for (i = j = 0; i < spec.named_arg_count; i++)
    210 	if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0)
    211 	  free (spec.named[i].name);
    212 	else
    213 	  {
    214 	    if (j < i)
    215 	      spec.named[j].name = spec.named[i].name;
    216 	    j++;
    217 	  }
    218       spec.named_arg_count = j;
    219     }
    220 
    221   result = (struct spec *) xmalloc (sizeof (struct spec));
    222   *result = spec;
    223   return result;
    224 
    225  bad_format:
    226   if (spec.named != NULL)
    227     {
    228       unsigned int i;
    229       for (i = 0; i < spec.named_arg_count; i++)
    230 	free (spec.named[i].name);
    231       free (spec.named);
    232     }
    233   return NULL;
    234 }
    235 
    236 static void
    237 format_free (void *descr)
    238 {
    239   struct spec *spec = (struct spec *) descr;
    240 
    241   if (spec->named != NULL)
    242     {
    243       unsigned int i;
    244       for (i = 0; i < spec->named_arg_count; i++)
    245 	free (spec->named[i].name);
    246       free (spec->named);
    247     }
    248   free (spec);
    249 }
    250 
    251 static int
    252 format_get_number_of_directives (void *descr)
    253 {
    254   struct spec *spec = (struct spec *) descr;
    255 
    256   return spec->directives;
    257 }
    258 
    259 static bool
    260 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
    261 	      formatstring_error_logger_t error_logger,
    262 	      const char *pretty_msgstr)
    263 {
    264   struct spec *spec1 = (struct spec *) msgid_descr;
    265   struct spec *spec2 = (struct spec *) msgstr_descr;
    266   bool err = false;
    267 
    268   if (spec1->named_arg_count + spec2->named_arg_count > 0)
    269     {
    270       unsigned int i, j;
    271       unsigned int n1 = spec1->named_arg_count;
    272       unsigned int n2 = spec2->named_arg_count;
    273 
    274       /* Check the argument names are the same.
    275 	 Both arrays are sorted.  We search for the first difference.  */
    276       for (i = 0, j = 0; i < n1 || j < n2; )
    277 	{
    278 	  int cmp = (i >= n1 ? 1 :
    279 		     j >= n2 ? -1 :
    280 		     strcmp (spec1->named[i].name, spec2->named[j].name));
    281 
    282 	  if (cmp > 0)
    283 	    {
    284 	      if (error_logger)
    285 		error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in 'msgid'"),
    286 			      spec2->named[j].name, pretty_msgstr);
    287 	      err = true;
    288 	      break;
    289 	    }
    290 	  else if (cmp < 0)
    291 	    {
    292 	      if (equality)
    293 		{
    294 		  if (error_logger)
    295 		    error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
    296 				  spec1->named[i].name, pretty_msgstr);
    297 		  err = true;
    298 		  break;
    299 		}
    300 	      else
    301 		i++;
    302 	    }
    303 	  else
    304 	    j++, i++;
    305 	}
    306     }
    307 
    308   return err;
    309 }
    310 
    311 
    312 struct formatstring_parser formatstring_sh =
    313 {
    314   format_parse,
    315   format_free,
    316   format_get_number_of_directives,
    317   NULL,
    318   format_check
    319 };
    320 
    321 
    322 #ifdef TEST
    323 
    324 /* Test program: Print the argument list specification returned by
    325    format_parse for strings read from standard input.  */
    326 
    327 #include <stdio.h>
    328 #include "getline.h"
    329 
    330 static void
    331 format_print (void *descr)
    332 {
    333   struct spec *spec = (struct spec *) descr;
    334   unsigned int i;
    335 
    336   if (spec == NULL)
    337     {
    338       printf ("INVALID");
    339       return;
    340     }
    341 
    342   printf ("{");
    343   for (i = 0; i < spec->named_arg_count; i++)
    344     {
    345       if (i > 0)
    346 	printf (", ");
    347       printf ("'%s'", spec->named[i].name);
    348     }
    349   printf ("}");
    350 }
    351 
    352 int
    353 main ()
    354 {
    355   for (;;)
    356     {
    357       char *line = NULL;
    358       size_t line_size = 0;
    359       int line_len;
    360       char *invalid_reason;
    361       void *descr;
    362 
    363       line_len = getline (&line, &line_size, stdin);
    364       if (line_len < 0)
    365 	break;
    366       if (line_len > 0 && line[line_len - 1] == '\n')
    367 	line[--line_len] = '\0';
    368 
    369       invalid_reason = NULL;
    370       descr = format_parse (line, false, &invalid_reason);
    371 
    372       format_print (descr);
    373       printf ("\n");
    374       if (descr == NULL)
    375 	printf ("%s\n", invalid_reason);
    376 
    377       free (invalid_reason);
    378       free (line);
    379     }
    380 
    381   return 0;
    382 }
    383 
    384 /*
    385  * For Emacs M-x compile
    386  * Local Variables:
    387  * compile-command: "/bin/sh ../libtool --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../lib -I../intl -DHAVE_CONFIG_H -DTEST format-sh.c ../lib/libgettextlib.la"
    388  * End:
    389  */
    390 
    391 #endif /* TEST */
    392