Don't warn about deprecation on Win32. Code written for GLib 1.2 doesn't
[platform/upstream/glib.git] / gshell.c
1 /* gshell.c - Shell-related utilities
2  *
3  *  Copyright 2000 Red Hat, Inc.
4  *  g_execvpe implementation based on GNU libc execvp:
5  *   Copyright 1991, 92, 95, 96, 97, 98, 99 Free Software Foundation, Inc.
6  *
7  * GLib is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * GLib is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with GLib; see the file COPYING.LIB.  If not, write
19  * to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "glib.h"
24 #include <string.h>
25
26 #ifdef _
27 #warning "FIXME remove gettext hack"
28 #endif
29
30 #define _(x) x
31
32 GQuark
33 g_shell_error_quark (void)
34 {
35   static GQuark quark = 0;
36   if (quark == 0)
37     quark = g_quark_from_static_string ("g-shell-error-quark");
38   return quark;
39 }
40
41 /* Single quotes preserve the literal string exactly. escape
42  * sequences are not allowed; not even \' - if you want a '
43  * in the quoted text, you have to do something like 'foo'\''bar'
44  *
45  * Double quotes allow $ ` " \ and newline to be escaped with backslash.
46  * Otherwise double quotes preserve things literally.
47  */
48
49 gboolean 
50 unquote_string_inplace (gchar* str, gchar** end, GError** err)
51 {
52   gchar* dest;
53   gchar* s;
54   gchar quote_char;
55   
56   g_return_val_if_fail(end != NULL, FALSE);
57   g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
58   g_return_val_if_fail(str != NULL, FALSE);
59   
60   dest = s = str;
61
62   quote_char = *s;
63   
64   if (!(*s == '"' || *s == '\''))
65     {
66       if (err)
67         *err = g_error_new(G_SHELL_ERROR,
68                            G_SHELL_ERROR_BAD_QUOTING,
69                            _("Quoted text doesn't begin with a quotation mark"));
70       *end = str;
71       return FALSE;
72     }
73
74   /* Skip the initial quote mark */
75   ++s;
76
77   if (quote_char == '"')
78     {
79       while (*s)
80         {
81           g_assert(s > dest); /* loop invariant */
82       
83           switch (*s)
84             {
85             case '"':
86               /* End of the string, return now */
87               *dest = '\0';
88               ++s;
89               *end = s;
90               return TRUE;
91               break;
92
93             case '\\':
94               /* Possible escaped quote or \ */
95               ++s;
96               switch (*s)
97                 {
98                 case '"':
99                 case '\\':
100                 case '`':
101                 case '$':
102                 case '\n':
103                   *dest = *s;
104                   ++s;
105                   ++dest;
106                   break;
107
108                 default:
109                   /* not an escaped char */
110                   *dest = '\\';
111                   ++dest;
112                   /* ++s already done. */
113                   break;
114                 }
115               break;
116
117             default:
118               *dest = *s;
119               ++dest;
120               ++s;
121               break;
122             }
123
124           g_assert(s > dest); /* loop invariant */
125         }
126     }
127   else
128     {
129       while (*s)
130         {
131           g_assert(s > dest); /* loop invariant */
132           
133           if (*s == '\'')
134             {
135               /* End of the string, return now */
136               *dest = '\0';
137               ++s;
138               *end = s;
139               return TRUE;
140             }
141           else
142             {
143               *dest = *s;
144               ++dest;
145               ++s;
146             }
147
148           g_assert(s > dest); /* loop invariant */
149         }
150     }
151   
152   /* If we reach here this means the close quote was never encountered */
153
154   *dest = '\0';
155   
156   if (err)
157     *err = g_error_new(G_SHELL_ERROR,
158                        G_SHELL_ERROR_BAD_QUOTING,
159                        _("Unmatched quotation mark in command line or other shell-quoted text"));
160   *end = s;
161   return FALSE;
162 }
163
164 /**
165  * g_shell_quote:
166  * @unquoted_string: a literal string
167  * 
168  * Quotes a string so that the shell (/bin/sh) will interpret the
169  * quoted string to mean @unquoted_string. If you pass a filename to
170  * the shell, for example, you should first quote it with this
171  * function.  The return value must be freed with g_free(). The
172  * quoting style used is undefined (single or double quotes may be
173  * used).
174  * 
175  * Return value: quoted string
176  **/
177 gchar*
178 g_shell_quote (const gchar *unquoted_string)
179 {
180   /* We always use single quotes, because the algorithm is cheesier.
181    * We could use double if we felt like it, that might be more
182    * human-readable.
183    */
184
185   const gchar *p;
186   GString *dest;
187
188   g_return_val_if_fail (unquoted_string != NULL, NULL);
189   
190   dest = g_string_new ("'");
191
192   p = unquoted_string;
193
194   /* could speed this up a lot by appending chunks of text at a
195    * time.
196    */
197   while (*p)
198     {
199       /* Replace literal ' with a close ', a \', and a open ' */
200       if (*p == '\'')
201         g_string_append (dest, "'\''");
202       else
203         g_string_append_c (dest, *p);
204
205       ++p;
206     }
207
208   /* close the quote */
209   g_string_append_c (dest, '\'');
210   
211   return g_string_free (dest, FALSE);
212 }
213
214 /**
215  * g_shell_unquote:
216  * @quoted_string: shell-quoted string
217  * @error: error return location or NULL
218  * 
219  * Unquotes a string as the shell (/bin/sh) would. Only handles
220  * quotes; if a string contains file globs, arithmetic operators,
221  * variables, backticks, redirections, or other special-to-the-shell
222  * features, the result will be different from the result a real shell
223  * would produce (the variables, backticks, etc. will be passed
224  * through literally instead of being expanded). This function is
225  * guaranteed to succeed if applied to the result of
226  * g_shell_quote(). If it fails, it returns NULL and sets the
227  * error. The @quoted_string need not actually contain quoted or
228  * escaped text; g_shell_unquote() simply goes through the string and
229  * unquotes/unescapes anything that the shell would. Both single and
230  * double quotes are handled, as are escapes including escaped
231  * newlines. The return value must be freed with g_free(). Possible
232  * errors are in the #G_SHELL_ERROR domain.
233  * 
234  * Return value: an unquoted string
235  **/
236 gchar*
237 g_shell_unquote (const gchar *quoted_string,
238                  GError     **error)
239 {
240   gchar *unquoted;
241   gchar *end;
242   gchar *start;
243   GString *retval;
244   
245   g_return_val_if_fail (quoted_string != NULL, NULL);
246   
247   unquoted = g_strdup (quoted_string);
248
249   start = unquoted;
250   end = unquoted;
251   retval = g_string_new ("");
252
253   /* The loop allows cases such as
254    * "foo"blah blah'bar'woo foo"baz"la la la\'\''foo'
255    */
256   while (*start)
257     {
258       /* Append all non-quoted chars, honoring backslash escape
259        */
260       
261       while (*start && !(*start == '"' || *start == '\''))
262         {
263           if (*start == '\\')
264             {
265               /* all characters can get escaped by backslash,
266                * except newline, which is removed if it follows
267                * a backslash outside of quotes
268                */
269               
270               ++start;
271               if (*start)
272                 {
273                   if (*start != '\n')
274                     g_string_append_c (retval, *start);
275                   ++start;
276                 }
277             }
278           else
279             {
280               g_string_append_c (retval, *start);
281               ++start;
282             }
283         }
284
285       if (*start)
286         {
287           if (!unquote_string_inplace (start, &end, error))
288             {
289               goto error;
290             }
291           else
292             {
293               g_string_append (retval, start);
294               start = end;
295             }
296         }
297     }
298
299   return g_string_free (retval, FALSE);
300   
301  error:
302   g_assert (error == NULL || *error != NULL);
303   
304   g_free (unquoted);
305   g_string_free (retval, TRUE);
306   return NULL;
307 }
308
309 /* g_parse_argv() does a semi-arbitrary weird subset of the way
310  * the shell parses a command line. We don't do variable expansion,
311  * don't understand that operators are tokens, don't do tilde expansion,
312  * don't do command substitution, no arithmetic expansion, IFS gets ignored,
313  * don't do filename globs, don't remove redirection stuff, etc.
314  *
315  * READ THE UNIX98 SPEC on "Shell Command Language" before changing
316  * the behavior of this code.
317  *
318  * Steps to parsing the argv string:
319  *
320  *  - tokenize the string (but since we ignore operators,
321  *    our tokenization may diverge from what the shell would do)
322  *    note that tokenization ignores the internals of a quoted
323  *    word and it always splits on spaces, not on IFS even
324  *    if we used IFS. We also ignore "end of input indicator"
325  *    (I guess this is control-D?)
326  *
327  *    Tokenization steps, from UNIX98 with operator stuff removed,
328  *    are:
329  * 
330  *    1) "If the current character is backslash, single-quote or
331  *        double-quote (\, ' or ") and it is not quoted, it will affect
332  *        quoting for subsequent characters up to the end of the quoted
333  *        text. The rules for quoting are as described in Quoting
334  *        . During token recognition no substitutions will be actually
335  *        performed, and the result token will contain exactly the
336  *        characters that appear in the input (except for newline
337  *        character joining), unmodified, including any embedded or
338  *        enclosing quotes or substitution operators, between the quote
339  *        mark and the end of the quoted text. The token will not be
340  *        delimited by the end of the quoted field."
341  *
342  *    2) "If the current character is an unquoted newline character,
343  *        the current token will be delimited."
344  *
345  *    3) "If the current character is an unquoted blank character, any
346  *        token containing the previous character is delimited and the
347  *        current character will be discarded."
348  *
349  *    4) "If the previous character was part of a word, the current
350  *        character will be appended to that word."
351  *
352  *    5) "If the current character is a "#", it and all subsequent
353  *        characters up to, but excluding, the next newline character
354  *        will be discarded as a comment. The newline character that
355  *        ends the line is not considered part of the comment. The
356  *        "#" starts a comment only when it is at the beginning of a
357  *        token. Since the search for the end-of-comment does not
358  *        consider an escaped newline character specially, a comment
359  *        cannot be continued to the next line."
360  *
361  *    6) "The current character will be used as the start of a new word."
362  *
363  *
364  *  - for each token (word), perform portions of word expansion, namely
365  *    field splitting (using default whitespace IFS) and quote
366  *    removal.  Field splitting may increase the number of words.
367  *    Quote removal does not increase the number of words.
368  *
369  *   "If the complete expansion appropriate for a word results in an
370  *   empty field, that empty field will be deleted from the list of
371  *   fields that form the completely expanded command, unless the
372  *   original word contained single-quote or double-quote characters."
373  *    - UNIX98 spec
374  *
375  *
376  */
377
378 static inline void
379 ensure_token (GString **token)
380 {
381   if (*token == NULL)
382     *token = g_string_new ("");
383 }
384
385 static void
386 delimit_token (GString **token,
387                GSList **retval)
388 {
389   if (*token == NULL)
390     return;
391
392   *retval = g_slist_prepend (*retval, g_string_free (*token, FALSE));
393
394   *token = NULL;
395 }
396
397 static GSList*
398 tokenize_command_line (const gchar *command_line,
399                        GError **error)
400 {
401   gchar current_quote;
402   const gchar *p;
403   GString *current_token = NULL;
404   GSList *retval = NULL;
405   
406   current_quote = '\0';
407   p = command_line;
408
409   while (*p)
410     {
411       if (current_quote == '\\')
412         {
413           if (*p == '\n')
414             {
415               /* we append nothing; backslash-newline become nothing */
416             }
417           else
418             {
419               /* we append the backslash and the current char,
420                * to be interpreted later after tokenization
421                */
422               ensure_token (&current_token);
423               g_string_append_c (current_token, '\\');
424               g_string_append_c (current_token, *p);
425             }
426
427           current_quote = '\0';
428         }
429       else if (current_quote == '#')
430         {
431           /* Discard up to and including next newline */
432           while (*p && *p != '\n')
433             ++p;
434
435           current_quote = '\0';
436           
437           if (*p == '\0')
438             break;
439         }
440       else if (current_quote)
441         {
442           if (*p == current_quote &&
443               /* check that it isn't an escaped double quote */
444               !(current_quote == '"' && p != command_line && *(p - 1) == '\\'))
445             {
446               /* close the quote */
447               current_quote = '\0';
448             }
449
450           /* Everything inside quotes, and the close quote,
451            * gets appended literally.
452            */
453
454           ensure_token (&current_token);
455           g_string_append_c (current_token, *p);
456         }
457       else
458         {
459           switch (*p)
460             {
461             case '\n':
462               delimit_token (&current_token, &retval);
463               break;
464
465             case ' ':
466             case '\t':
467               /* If the current token contains the previous char, delimit
468                * the current token. A nonzero length
469                * token should always contain the previous char.
470                */
471               if (current_token &&
472                   current_token->len > 0)
473                 {
474                   delimit_token (&current_token, &retval);
475                 }
476               
477               /* discard all unquoted blanks (don't add them to a token) */
478               break;
479
480
481               /* single/double quotes are appended to the token,
482                * escapes are maybe appended next time through the loop,
483                * comment chars are never appended.
484                */
485               
486             case '\'':
487             case '"':
488               ensure_token (&current_token);
489               g_string_append_c (current_token, *p);
490
491               /* FALL THRU */
492               
493             case '#':
494             case '\\':
495               current_quote = *p;
496               break;
497
498             default:
499               /* Combines rules 4) and 6) - if we have a token, append to it,
500                * otherwise create a new token.
501                */
502               ensure_token (&current_token);
503               g_string_append_c (current_token, *p);
504               break;
505             }
506         }
507
508       ++p;
509     }
510
511   delimit_token (&current_token, &retval);
512
513   if (current_quote)
514     {
515       if (current_quote == '\\')
516         g_set_error (error,
517                      G_SHELL_ERROR,
518                      G_SHELL_ERROR_BAD_QUOTING,
519                      _("Text ended just after a '\' character."
520                        " (The text was '%s')"),
521                      command_line);
522       else
523         g_set_error (error,
524                      G_SHELL_ERROR,
525                      G_SHELL_ERROR_BAD_QUOTING,
526                      _("Text ended before matching quote was found for %c."
527                        " (The text was '%s')"),
528                      current_quote, command_line);
529       
530       goto error;
531     }
532
533   if (retval == NULL)
534     {
535       g_set_error (error,
536                    G_SHELL_ERROR,
537                    G_SHELL_ERROR_EMPTY_STRING,
538                    _("Text was empty (or contained only whitespace)"));
539
540       goto error;
541     }
542   
543   /* we appended backward */
544   retval = g_slist_reverse (retval);
545
546   return retval;
547
548  error:
549   g_assert (error == NULL || *error != NULL);
550   
551   if (retval)
552     {
553       g_slist_foreach (retval, (GFunc)g_free, NULL);
554       g_slist_free (retval);
555     }
556
557   return NULL;
558 }
559
560 /**
561  * g_shell_parse_argv:
562  * @command_line: command line to parse
563  * @argcp: return location for number of args
564  * @argvp: return location for array of args
565  * @error: return location for error
566  * 
567  * Parses a command line into an argument vector, in much the same way
568  * the shell would, but without many of the expansions the shell would
569  * perform (variable expansion, globs, operators, filename expansion,
570  * etc. are not supported). The results are defined to be the same as
571  * those you would get from a UNIX98 /bin/sh, as long as the input
572  * contains none of the unsupported shell expansions. If the input
573  * does contain such expansions, they are passed through
574  * literally. Possible errors are those from the #G_SHELL_ERROR
575  * domain.
576  * 
577  * Return value: TRUE on success, FALSE if error set
578  **/
579 gboolean
580 g_shell_parse_argv (const gchar *command_line,
581                     gint        *argcp,
582                     gchar     ***argvp,
583                     GError     **error)
584 {
585   /* Code based on poptParseArgvString() from libpopt */
586   gint argc = 0;
587   gchar **argv = NULL;
588   GSList *tokens = NULL;
589   gint i;
590   GSList *tmp_list;
591   
592   g_return_val_if_fail (command_line != NULL, FALSE);
593
594   tokens = tokenize_command_line (command_line, error);
595   if (tokens == NULL)
596     return FALSE;
597
598   /* Because we can't have introduced any new blank space into the
599    * tokens (we didn't do any new expansions), we don't need to
600    * perform field splitting. If we were going to honor IFS or do any
601    * expansions, we would have to do field splitting on each word
602    * here. Also, if we were going to do any expansion we would need to
603    * remove any zero-length words that didn't contain quotes
604    * originally; but since there's no expansion we know all words have
605    * nonzero length, unless they contain quotes.
606    * 
607    * So, we simply remove quotes, and don't do any field splitting or
608    * empty word removal, since we know there was no way to introduce
609    * such things.
610    */
611
612   argc = g_slist_length (tokens);
613   argv = g_new0 (gchar*, argc + 1);
614   i = 0;
615   tmp_list = tokens;
616   while (tmp_list)
617     {
618       argv[i] = g_shell_unquote (tmp_list->data, error);
619
620       /* Since we already checked that quotes matched up in the
621        * tokenizer, this shouldn't be possible to reach I guess.
622        */
623       if (argv[i] == NULL)
624         goto failed;
625
626       tmp_list = g_slist_next (tmp_list);
627       ++i;
628     }
629   
630   g_slist_foreach (tokens, (GFunc)g_free, NULL);
631   g_slist_free (tokens);
632   
633   if (argcp)
634     *argcp = argc;
635
636   if (argvp)
637     *argvp = argv;
638   else
639     g_strfreev (argv);
640
641   return TRUE;
642
643  failed:
644
645   g_assert (error == NULL || *error != NULL);
646   g_strfreev (argv);
647   g_slist_foreach (tokens, (GFunc) g_free, NULL);
648   g_slist_free (tokens);
649   
650   return FALSE;
651 }