git-instaweb: Extract configuring web server into configure_httpd
[git] / sh-i18n--envsubst.c
1 /*
2  * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1)
3  *
4  * Copyright (C) 2010 Ævar Arnfjörð Bjarmason
5  *
6  * This is a modified version of
7  * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git
8  * repository. It has been stripped down to only implement the
9  * envsubst(1) features that we need in the git-sh-i18n fallbacks.
10  *
11  * The "Close standard error" part in main() is from
12  * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for
13  * both files are reproduced immediately below.
14  */
15
16 #include "git-compat-util.h"
17
18 /* Substitution of environment variables in shell format strings.
19    Copyright (C) 2003-2007 Free Software Foundation, Inc.
20    Written by Bruno Haible <bruno@clisp.org>, 2003.
21
22    This program is free software; you can redistribute it and/or modify
23    it under the terms of the GNU General Public License as published by
24    the Free Software Foundation; either version 2, or (at your option)
25    any later version.
26
27    This program is distributed in the hope that it will be useful,
28    but WITHOUT ANY WARRANTY; without even the implied warranty of
29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30    GNU General Public License for more details.
31
32    You should have received a copy of the GNU General Public License
33    along with this program; if not, write to the Free Software Foundation,
34    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
35
36 /* closeout.c - close standard output and standard error
37    Copyright (C) 1998-2007 Free Software Foundation, Inc.
38
39    This program is free software; you can redistribute it and/or modify
40    it under the terms of the GNU General Public License as published by
41    the Free Software Foundation; either version 2, or (at your option)
42    any later version.
43
44    This program is distributed in the hope that it will be useful,
45    but WITHOUT ANY WARRANTY; without even the implied warranty of
46    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
47    GNU General Public License for more details.
48
49    You should have received a copy of the GNU General Public License
50    along with this program; if not, write to the Free Software Foundation,
51    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
52
53 #include <errno.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57
58 /* If true, substitution shall be performed on all variables.  */
59 static unsigned short int all_variables;
60
61 /* Forward declaration of local functions.  */
62 static void print_variables (const char *string);
63 static void note_variables (const char *string);
64 static void subst_from_stdin (void);
65
66 int
67 main (int argc, char *argv[])
68 {
69   /* Default values for command line options.  */
70   /* unsigned short int show_variables = 0; */
71
72   switch (argc)
73         {
74         case 1:
75           error ("we won't substitute all variables on stdin for you");
76           /*
77           all_variables = 1;
78       subst_from_stdin ();
79           */
80         case 2:
81           /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */
82           all_variables = 0;
83           note_variables (argv[1]);
84       subst_from_stdin ();
85           break;
86         case 3:
87           /* git sh-i18n--envsubst --variables '$foo and $bar' */
88           if (strcmp(argv[1], "--variables"))
89                 error ("first argument must be --variables when two are given");
90           /* show_variables = 1; */
91       print_variables (argv[2]);
92           break;
93         default:
94           error ("too many arguments");
95           break;
96         }
97
98   /* Close standard error.  This is simpler than fwriteerror_no_ebadf, because
99      upon failure we don't need an errno - all we can do at this point is to
100      set an exit status.  */
101   errno = 0;
102   if (ferror (stderr) || fflush (stderr))
103     {
104       fclose (stderr);
105       exit (EXIT_FAILURE);
106     }
107   if (fclose (stderr) && errno != EBADF)
108     exit (EXIT_FAILURE);
109
110   exit (EXIT_SUCCESS);
111 }
112
113 /* Parse the string and invoke the callback each time a $VARIABLE or
114    ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence
115    of ASCII alphanumeric/underscore characters, starting with an ASCII
116    alphabetic/underscore character.
117    We allow only ASCII characters, to avoid dependencies w.r.t. the current
118    encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
119    encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
120    SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
121    encodings.  */
122 static void
123 find_variables (const char *string,
124                 void (*callback) (const char *var_ptr, size_t var_len))
125 {
126   for (; *string != '\0';)
127     if (*string++ == '$')
128       {
129         const char *variable_start;
130         const char *variable_end;
131         unsigned short int valid;
132         char c;
133
134         if (*string == '{')
135           string++;
136
137         variable_start = string;
138         c = *string;
139         if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
140           {
141             do
142               c = *++string;
143             while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
144                    || (c >= '0' && c <= '9') || c == '_');
145             variable_end = string;
146
147             if (variable_start[-1] == '{')
148               {
149                 if (*string == '}')
150                   {
151                     string++;
152                     valid = 1;
153                   }
154                 else
155                   valid = 0;
156               }
157             else
158               valid = 1;
159
160             if (valid)
161               callback (variable_start, variable_end - variable_start);
162           }
163       }
164 }
165
166
167 /* Print a variable to stdout, followed by a newline.  */
168 static void
169 print_variable (const char *var_ptr, size_t var_len)
170 {
171   fwrite (var_ptr, var_len, 1, stdout);
172   putchar ('\n');
173 }
174
175 /* Print the variables contained in STRING to stdout, each one followed by a
176    newline.  */
177 static void
178 print_variables (const char *string)
179 {
180   find_variables (string, &print_variable);
181 }
182
183
184 /* Type describing list of immutable strings,
185    implemented using a dynamic array.  */
186 typedef struct string_list_ty string_list_ty;
187 struct string_list_ty
188 {
189   const char **item;
190   size_t nitems;
191   size_t nitems_max;
192 };
193
194 /* Initialize an empty list of strings.  */
195 static inline void
196 string_list_init (string_list_ty *slp)
197 {
198   slp->item = NULL;
199   slp->nitems = 0;
200   slp->nitems_max = 0;
201 }
202
203 /* Append a single string to the end of a list of strings.  */
204 static inline void
205 string_list_append (string_list_ty *slp, const char *s)
206 {
207   /* Grow the list.  */
208   if (slp->nitems >= slp->nitems_max)
209     {
210       size_t nbytes;
211
212       slp->nitems_max = slp->nitems_max * 2 + 4;
213       nbytes = slp->nitems_max * sizeof (slp->item[0]);
214       slp->item = (const char **) xrealloc (slp->item, nbytes);
215     }
216
217   /* Add the string to the end of the list.  */
218   slp->item[slp->nitems++] = s;
219 }
220
221 /* Compare two strings given by reference.  */
222 static int
223 cmp_string (const void *pstr1, const void *pstr2)
224 {
225   const char *str1 = *(const char **)pstr1;
226   const char *str2 = *(const char **)pstr2;
227
228   return strcmp (str1, str2);
229 }
230
231 /* Sort a list of strings.  */
232 static inline void
233 string_list_sort (string_list_ty *slp)
234 {
235   if (slp->nitems > 0)
236     qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);
237 }
238
239 /* Test whether a string list contains a given string.  */
240 static inline int
241 string_list_member (const string_list_ty *slp, const char *s)
242 {
243   size_t j;
244
245   for (j = 0; j < slp->nitems; ++j)
246     if (strcmp (slp->item[j], s) == 0)
247       return 1;
248   return 0;
249 }
250
251 /* Test whether a sorted string list contains a given string.  */
252 static int
253 sorted_string_list_member (const string_list_ty *slp, const char *s)
254 {
255   size_t j1, j2;
256
257   j1 = 0;
258   j2 = slp->nitems;
259   if (j2 > 0)
260     {
261       /* Binary search.  */
262       while (j2 - j1 > 1)
263         {
264           /* Here we know that if s is in the list, it is at an index j
265              with j1 <= j < j2.  */
266           size_t j = (j1 + j2) >> 1;
267           int result = strcmp (slp->item[j], s);
268
269           if (result > 0)
270             j2 = j;
271           else if (result == 0)
272             return 1;
273           else
274             j1 = j + 1;
275         }
276       if (j2 > j1)
277         if (strcmp (slp->item[j1], s) == 0)
278           return 1;
279     }
280   return 0;
281 }
282
283
284 /* Set of variables on which to perform substitution.
285    Used only if !all_variables.  */
286 static string_list_ty variables_set;
287
288 /* Adds a variable to variables_set.  */
289 static void
290 note_variable (const char *var_ptr, size_t var_len)
291 {
292   char *string = xmalloc (var_len + 1);
293   memcpy (string, var_ptr, var_len);
294   string[var_len] = '\0';
295
296   string_list_append (&variables_set, string);
297 }
298
299 /* Stores the variables occurring in the string in variables_set.  */
300 static void
301 note_variables (const char *string)
302 {
303   string_list_init (&variables_set);
304   find_variables (string, &note_variable);
305   string_list_sort (&variables_set);
306 }
307
308
309 static int
310 do_getc (void)
311 {
312   int c = getc (stdin);
313
314   if (c == EOF)
315     {
316       if (ferror (stdin))
317         error ("error while reading standard input");
318     }
319
320   return c;
321 }
322
323 static inline void
324 do_ungetc (int c)
325 {
326   if (c != EOF)
327     ungetc (c, stdin);
328 }
329
330 /* Copies stdin to stdout, performing substitutions.  */
331 static void
332 subst_from_stdin (void)
333 {
334   static char *buffer;
335   static size_t bufmax;
336   static size_t buflen;
337   int c;
338
339   for (;;)
340     {
341       c = do_getc ();
342       if (c == EOF)
343         break;
344       /* Look for $VARIABLE or ${VARIABLE}.  */
345       if (c == '$')
346         {
347           unsigned short int opening_brace = 0;
348           unsigned short int closing_brace = 0;
349
350           c = do_getc ();
351           if (c == '{')
352             {
353               opening_brace = 1;
354               c = do_getc ();
355             }
356           if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
357             {
358               unsigned short int valid;
359
360               /* Accumulate the VARIABLE in buffer.  */
361               buflen = 0;
362               do
363                 {
364                   if (buflen >= bufmax)
365                     {
366                       bufmax = 2 * bufmax + 10;
367                       buffer = xrealloc (buffer, bufmax);
368                     }
369                   buffer[buflen++] = c;
370
371                   c = do_getc ();
372                 }
373               while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
374                      || (c >= '0' && c <= '9') || c == '_');
375
376               if (opening_brace)
377                 {
378                   if (c == '}')
379                     {
380                       closing_brace = 1;
381                       valid = 1;
382                     }
383                   else
384                     {
385                       valid = 0;
386                       do_ungetc (c);
387                     }
388                 }
389               else
390                 {
391                   valid = 1;
392                   do_ungetc (c);
393                 }
394
395               if (valid)
396                 {
397                   /* Terminate the variable in the buffer.  */
398                   if (buflen >= bufmax)
399                     {
400                       bufmax = 2 * bufmax + 10;
401                       buffer = xrealloc (buffer, bufmax);
402                     }
403                   buffer[buflen] = '\0';
404
405                   /* Test whether the variable shall be substituted.  */
406                   if (!all_variables
407                       && !sorted_string_list_member (&variables_set, buffer))
408                     valid = 0;
409                 }
410
411               if (valid)
412                 {
413                   /* Substitute the variable's value from the environment.  */
414                   const char *env_value = getenv (buffer);
415
416                   if (env_value != NULL)
417                     fputs (env_value, stdout);
418                 }
419               else
420                 {
421                   /* Perform no substitution at all.  Since the buffered input
422                      contains no other '$' than at the start, we can just
423                      output all the buffered contents.  */
424                   putchar ('$');
425                   if (opening_brace)
426                     putchar ('{');
427                   fwrite (buffer, buflen, 1, stdout);
428                   if (closing_brace)
429                     putchar ('}');
430                 }
431             }
432           else
433             {
434               do_ungetc (c);
435               putchar ('$');
436               if (opening_brace)
437                 putchar ('{');
438             }
439         }
440       else
441         putchar (c);
442     }
443 }