shell quoting



Hi,

Code to quote/unquote strings for passing the string to a shell as an
argument. Mostly useful for filenames.

I had to write this for g_exec_*, so if we include g_exec_* this code
is in GLib; we may as well expose it publically.

I haven't tested it yet, I'll submit it as part of the gexec patch
later, this post is just so we can discuss whether to include
shell-style quoting in the public API.

Exposed API is:
 
  gchar* g_shell_quote   (const gchar  *unquoted_string);
  gchar* g_shell_unquote (const gchar  *quoted_string,
                          GError      **error);


Havoc


/* Single quotes preserve the literal string exactly. escape
 * sequences are not allowed; not even \' - if you want a '
 * in the quoted text, you have to do something like 'foo'\''bar'
 *
 * Double quotes allow $ ` " \ and newline to be escaped with backslash.
 * Otherwise double quotes preserve things literally.
 */

gboolean 
unquote_string_inplace (gchar* str, gchar** end, GError** err)
{
  gchar* dest;
  gchar* s;
  gchar quote_char;
  
  g_return_val_if_fail(end != NULL, FALSE);
  g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
  g_return_val_if_fail(str != NULL, FALSE);
  
  dest = s = str;

  quote_char = *s;
  
  if (!(*s == '"' || *s == '\''))
    {
      if (err)
        *err = g_error_new(G_EXEC_ERROR,
                           G_EXEC_ERROR_PARSE,
                           _("Quoted text doesn't begin with a quotation mark"));
      *end = str;
      return FALSE;
    }

  /* Skip the initial quote mark */
  ++s;

  if (quote_char == '"')
    {
      while (*s)
        {
          g_assert(s > dest); /* loop invariant */
      
          switch (*s)
            {
            case '"':
              /* End of the string, return now */
              *dest = '\0';
              ++s;
              *end = s;
              return TRUE;
              break;

            case '\\':
              /* Possible escaped quote or \ */
              ++s;
              switch (*s)
                {
                case '"':
                case '\\':
                case '`':
                case '$':
                  *dest = *s;
                  ++s;
                  ++dest;
                  break;

                default:
                  /* not an escaped char */
                  *dest = '\\';
                  ++dest;
                  /* ++s already done. */
                  break;
                }
              break;

            default:
              *dest = *s;
              ++dest;
              ++s;
              break;
            }

          g_assert(s > dest); /* loop invariant */
        }
    }
  else
    {
      while (*s)
        {
          g_assert(s > dest); /* loop invariant */
          
          if (*s == '\'')
            {
              /* End of the string, return now */
              *dest = '\0';
              ++s;
              *end = s;
              return TRUE;
            }
          else
            {
              *dest = *s;
              ++dest;
              ++s;
            }

          g_assert(s > dest); /* loop invariant */
        }
    }
  
  /* If we reach here this means the close quote was never encountered */

  *dest = '\0';
  
  if (err)
    *err = g_error_new(G_EXEC_ERROR,
                       G_EXEC_ERROR_PARSE,
                       _("Unmatched quotation mark in command line"));
  *end = s;
  return FALSE;
}

gchar*
g_shell_quote (const gchar *unquoted_string)
{
  /* We always use single quotes, because the algorithm is cheesier.
   * We could use double if we felt like it.
   */

  const gchar *p;
  GString *dest;

  g_return_val_if_fail (unquoted_string != NULL, NULL);
  
  dest = g_string_new ("'");

  p = unquoted_string;

  /* could speed this up a lot by appending chunks of text at a
   * time.
   */
  while (*p)
    {
      /* Replace literal ' with a close ', a \', and a open ' */
      if (*p == '\'')
        g_string_append (dest, "'\''");
      else
        g_string_append_c (dest, *p);

      ++p;
    }

  /* close the quote */
  g_string_append_c (dest, '\'');
  
  return g_string_free (dest, FALSE);
}

gchar*
g_shell_unquote (const gchar *quoted_string,
                 GError **error)
{
  gchar *unquoted;
  gchar *end;
  
  g_return_val_if_fail (quoted_string != NULL, NULL);
  
  unquoted = g_strdup (quoted_string);

  if (!unquote_string_inplace (unquoted, &end, error))
    {
      g_free (unquoted);
      return NULL;
    }
  else
    return unquoted;
}




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]