1 /* JavaScript format strings.
2    Copyright (C) 2001-2004, 2006-2010, 2013, 2016, 2019-2020 Free Software Foundation, Inc.
3    Written by Andreas Stricker <andy@knitter.ch>, 2010.
4    It's based on python format module from Bruno Haible.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
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 "xvasprintf.h"
31 #include "format-invalid.h"
32 #include "gettext.h"
33 
34 #define _(str) gettext (str)
35 
36 /* JavaScript format strings are not in the language specification,
37    but there are several implementations which provide the printf-like
38    feature.  Here, we provide a permissive parser which at least accepts
39    format strings supported by Gjs version 1.40, where:
40 
41    A directive
42    - starts with '%' or '%m$' where m is a positive integer,
43    - is optionally followed by any of the characters '0', '-', ' ',
44      or 'I', each of which acts as a flag,
45    - is optionally followed by a width specification: a nonempty digit
46      sequence,
47    - is optionally followed by '.' and a precision specification: a nonempty
48      digit sequence,
49    - is finished by a specifier
50        - 's', that needs a string argument,
51        - 'b', 'd', 'u', 'o', 'x', 'X', that need an integer argument,
52        - 'f', that need a floating-point argument,
53        - 'c', that needs a character argument.
54        - 'j', that needs an argument of any type.
55    Additionally there is the directive '%%', which takes no argument.  */
56 
57 enum format_arg_type
58 {
59   FAT_NONE,
60   FAT_ANY,
61   FAT_CHARACTER,
62   FAT_STRING,
63   FAT_INTEGER,
64   FAT_FLOAT
65 };
66 
67 struct numbered_arg
68 {
69   unsigned int number;
70   enum format_arg_type type;
71 };
72 
73 struct spec
74 {
75   unsigned int directives;
76   unsigned int numbered_arg_count;
77   struct numbered_arg *numbered;
78 };
79 
80 /* Locale independent test for a decimal digit.
81    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
82    <ctype.h> isdigit must be an 'unsigned char'.)  */
83 #undef isdigit
84 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
85 
86 
87 static int
numbered_arg_compare(const void * p1,const void * p2)88 numbered_arg_compare (const void *p1, const void *p2)
89 {
90   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
91   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
92 
93   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
94 }
95 
96 static void *
format_parse(const char * format,bool translated,char * fdi,char ** invalid_reason)97 format_parse (const char *format, bool translated, char *fdi,
98               char **invalid_reason)
99 {
100   const char *const format_start = format;
101   struct spec spec;
102   unsigned int numbered_allocated;
103   unsigned int unnumbered_arg_count;
104   struct spec *result;
105 
106   spec.directives = 0;
107   spec.numbered_arg_count = 0;
108   spec.numbered = NULL;
109   numbered_allocated = 0;
110   unnumbered_arg_count = 0;
111 
112   for (; *format != '\0';)
113     if (*format++ == '%')
114       {
115         /* A directive.  */
116         unsigned int number = 0;
117         enum format_arg_type type;
118 
119         FDI_SET (format - 1, FMTDIR_START);
120         spec.directives++;
121 
122         if (isdigit (*format))
123           {
124             const char *f = format;
125             unsigned int m = 0;
126 
127             do
128               {
129                 m = 10 * m + (*f - '0');
130                 f++;
131               }
132             while (isdigit (*f));
133 
134             if (*f == '$')
135               {
136                 if (m == 0)
137                   {
138                     *invalid_reason = INVALID_ARGNO_0 (spec.directives);
139                     FDI_SET (f, FMTDIR_ERROR);
140                     goto bad_format;
141                   }
142                 number = m;
143                 format = ++f;
144               }
145           }
146 
147         /* Parse flags.  */
148         while (*format == '-' || *format == '+' || *format == ' '
149                || *format == '0' || *format == 'I')
150           format++;
151 
152         /* Parse width.  */
153         while (isdigit (*format))
154           format++;
155 
156         if (*format == '.')
157           {
158             format++;
159 
160             while (isdigit (*format))
161               format++;
162           }
163 
164         switch (*format)
165           {
166           case '%':
167             type = FAT_NONE;
168             break;
169           case 'c':
170             type = FAT_CHARACTER;
171             break;
172           case 's':
173             type = FAT_STRING;
174             break;
175           case 'b': case 'd': case 'o': case 'x': case 'X':
176             type = FAT_INTEGER;
177             break;
178           case 'f':
179             type = FAT_FLOAT;
180             break;
181           case 'j':
182             type = FAT_ANY;
183             break;
184           default:
185             if (*format == '\0')
186               {
187                 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
188                 FDI_SET (format - 1, FMTDIR_ERROR);
189               }
190             else
191               {
192                 *invalid_reason =
193                   INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
194                 FDI_SET (format, FMTDIR_ERROR);
195               }
196             goto bad_format;
197           }
198 
199         if (type != FAT_NONE)
200           {
201             if (number)
202               {
203                 /* Numbered argument.  */
204 
205                 /* Numbered and unnumbered specifications are exclusive.  */
206                 if (unnumbered_arg_count > 0)
207                   {
208                     *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
209                     FDI_SET (format, FMTDIR_ERROR);
210                     goto bad_format;
211                   }
212 
213                 if (numbered_allocated == spec.numbered_arg_count)
214                   {
215                     numbered_allocated = 2 * numbered_allocated + 1;
216                     spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
217                   }
218                 spec.numbered[spec.numbered_arg_count].number = number;
219                 spec.numbered[spec.numbered_arg_count].type = type;
220                 spec.numbered_arg_count++;
221               }
222             else
223               {
224                 /* Unnumbered argument.  */
225 
226                 /* Numbered and unnumbered specifications are exclusive.  */
227                 if (spec.numbered_arg_count > 0)
228                   {
229                     *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
230                     FDI_SET (format, FMTDIR_ERROR);
231                     goto bad_format;
232                   }
233 
234                 if (numbered_allocated == unnumbered_arg_count)
235                   {
236                     numbered_allocated = 2 * numbered_allocated + 1;
237                     spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
238                   }
239                 spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
240                 spec.numbered[unnumbered_arg_count].type = type;
241                 unnumbered_arg_count++;
242               }
243           }
244 
245         FDI_SET (format, FMTDIR_END);
246 
247         format++;
248       }
249 
250   /* Convert the unnumbered argument array to numbered arguments.  */
251   if (unnumbered_arg_count > 0)
252     spec.numbered_arg_count = unnumbered_arg_count;
253   /* Sort the numbered argument array, and eliminate duplicates.  */
254   else if (spec.numbered_arg_count > 1)
255     {
256       unsigned int i, j;
257       bool err;
258 
259       qsort (spec.numbered, spec.numbered_arg_count,
260              sizeof (struct numbered_arg), numbered_arg_compare);
261 
262       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
263       err = false;
264       for (i = j = 0; i < spec.numbered_arg_count; i++)
265         if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
266           {
267             enum format_arg_type type1 = spec.numbered[i].type;
268             enum format_arg_type type2 = spec.numbered[j-1].type;
269             enum format_arg_type type_both;
270 
271             if (type1 == type2)
272               type_both = type1;
273             else
274               {
275                 /* Incompatible types.  */
276                 type_both = FAT_NONE;
277                 if (!err)
278                   *invalid_reason =
279                     INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
280                 err = true;
281               }
282 
283             spec.numbered[j-1].type = type_both;
284           }
285         else
286           {
287             if (j < i)
288               {
289                 spec.numbered[j].number = spec.numbered[i].number;
290                 spec.numbered[j].type = spec.numbered[i].type;
291               }
292             j++;
293           }
294       spec.numbered_arg_count = j;
295       if (err)
296         /* *invalid_reason has already been set above.  */
297         goto bad_format;
298     }
299 
300   result = XMALLOC (struct spec);
301   *result = spec;
302   return result;
303 
304  bad_format:
305   if (spec.numbered != NULL)
306     free (spec.numbered);
307   return NULL;
308 }
309 
310 static void
format_free(void * descr)311 format_free (void *descr)
312 {
313   struct spec *spec = (struct spec *) descr;
314 
315   if (spec->numbered != NULL)
316     free (spec->numbered);
317   free (spec);
318 }
319 
320 static int
format_get_number_of_directives(void * descr)321 format_get_number_of_directives (void *descr)
322 {
323   struct spec *spec = (struct spec *) descr;
324 
325   return spec->directives;
326 }
327 
328 static bool
format_check(void * msgid_descr,void * msgstr_descr,bool equality,formatstring_error_logger_t error_logger,const char * pretty_msgid,const char * pretty_msgstr)329 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
330               formatstring_error_logger_t error_logger,
331               const char *pretty_msgid, const char *pretty_msgstr)
332 {
333   struct spec *spec1 = (struct spec *) msgid_descr;
334   struct spec *spec2 = (struct spec *) msgstr_descr;
335   bool err = false;
336 
337   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
338     {
339       unsigned int i, j;
340       unsigned int n1 = spec1->numbered_arg_count;
341       unsigned int n2 = spec2->numbered_arg_count;
342 
343       /* Check the argument names are the same.
344          Both arrays are sorted.  We search for the first difference.  */
345       for (i = 0, j = 0; i < n1 || j < n2; )
346         {
347           int cmp = (i >= n1 ? 1 :
348                      j >= n2 ? -1 :
349                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
350                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
351                      0);
352           if (cmp > 0)
353             {
354               if (error_logger)
355                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
356                               spec2->numbered[j].number, pretty_msgstr,
357                               pretty_msgid);
358               err = true;
359               break;
360             }
361           else if (cmp < 0)
362             {
363               if (equality)
364                 {
365                   if (error_logger)
366                     error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
367                                   spec1->numbered[i].number, pretty_msgstr);
368                   err = true;
369                   break;
370                 }
371               else
372                 i++;
373             }
374           else
375             j++, i++;
376         }
377       /* Check the argument types are the same.  */
378       if (!err)
379         for (i = 0, j = 0; j < n2; )
380           {
381             if (spec1->numbered[i].number == spec2->numbered[j].number)
382               {
383                 if (!(spec1->numbered[i].type == spec2->numbered[j].type
384                       || (!equality
385                           && (spec1->numbered[i].type == FAT_ANY
386                               || spec2->numbered[i].type == FAT_ANY))))
387                   {
388                     if (error_logger)
389                       error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
390                                     pretty_msgid, pretty_msgstr,
391                                     spec2->numbered[j].number);
392                     err = true;
393                     break;
394                   }
395                 j++, i++;
396               }
397             else
398               i++;
399           }
400     }
401 
402   return err;
403 }
404 
405 
406 struct formatstring_parser formatstring_javascript =
407 {
408   format_parse,
409   format_free,
410   format_get_number_of_directives,
411   NULL,
412   format_check
413 };
414 
415 
416 #ifdef TEST
417 
418 /* Test program: Print the argument list specification returned by
419    format_parse for strings read from standard input.  */
420 
421 #include <stdio.h>
422 
423 static void
format_print(void * descr)424 format_print (void *descr)
425 {
426   struct spec *spec = (struct spec *) descr;
427   unsigned int i;
428 
429   if (spec == NULL)
430     {
431       printf ("INVALID");
432       return;
433     }
434 
435       printf ("(");
436       for (i = 0; i < spec->numbered_arg_count; i++)
437         {
438           if (i > 0)
439             printf (" ");
440           switch (spec->numbered[i].type)
441             {
442             case FAT_ANY:
443               printf ("*");
444               break;
445             case FAT_CHARACTER:
446               printf ("c");
447               break;
448             case FAT_STRING:
449               printf ("s");
450               break;
451             case FAT_INTEGER:
452               printf ("i");
453               break;
454             case FAT_FLOAT:
455               printf ("f");
456               break;
457             default:
458               abort ();
459             }
460         }
461       printf (")");
462 }
463 
464 int
main()465 main ()
466 {
467   for (;;)
468     {
469       char *line = NULL;
470       size_t line_size = 0;
471       int line_len;
472       char *invalid_reason;
473       void *descr;
474 
475       line_len = getline (&line, &line_size, stdin);
476       if (line_len < 0)
477         break;
478       if (line_len > 0 && line[line_len - 1] == '\n')
479         line[--line_len] = '\0';
480 
481       invalid_reason = NULL;
482       descr = format_parse (line, false, NULL, &invalid_reason);
483 
484       format_print (descr);
485       printf ("\n");
486       if (descr == NULL)
487         printf ("%s\n", invalid_reason);
488 
489       free (invalid_reason);
490       free (line);
491     }
492 
493   return 0;
494 }
495 
496 /*
497  * For Emacs M-x compile
498  * Local Variables:
499  * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-javascript.c ../gnulib-lib/libgettextlib.la"
500  * End:
501  */
502 #endif /* TEST */
503