Improved config_chain parsing by using escape for \ " and ' (close #1952)
authorLaurent Aimar <fenrir@videolan.org>
Fri, 29 Aug 2008 10:14:48 +0000 (12:14 +0200)
committerLaurent Aimar <fenrir@videolan.org>
Fri, 29 Aug 2008 10:14:48 +0000 (12:14 +0200)
It also add checks against failed malloc.

The option value should be escaped by \ for the mentionned characters and
only for them (and only one time).

For example
 dst="test \"ok\".mp3"
will assign the value
 test "ok".mp3
to the option dst.

The following one
 dst="c:\test\\'bla'bla.txt"
will assign the value
 c:\test\'bla'bla.txt

You can use the functions
- config_StringEscape (allocates memory)
- config_StringUnescape (does not allocate memory).

include/vlc_configuration.h
src/config/chain.c
src/libvlccore.sym

index 36d46cb..65b3357 100644 (file)
@@ -243,17 +243,63 @@ VLC_EXPORT( bool, __config_ExistIntf,  ( vlc_object_t *, const char * ) );
  ****************************************************************************/
 struct config_chain_t
 {
-    config_chain_t *p_next;
+    config_chain_t *p_next;     /**< Pointer on the next config_chain_t element */
 
-    char        *psz_name;
-    char        *psz_value;
+    char        *psz_name;      /**< Option name */
+    char        *psz_value;     /**< Option value */
 };
 
+/**
+ * This function will
+ * - create all options in the array ppsz_options (var_Create).
+ * - parse the given linked list of config_chain_t and set the value (var_Set).
+ *
+ * The option names will be created by adding the psz_prefix prefix.
+ */
 #define config_ChainParse( a, b, c, d ) __config_ChainParse( VLC_OBJECT(a), b, c, d )
 VLC_EXPORT( void,   __config_ChainParse, ( vlc_object_t *, const char *psz_prefix, const char *const *ppsz_options, config_chain_t * ) );
-VLC_EXPORT( char *, config_ChainCreate, ( char **, config_chain_t **, const char * ) );
+
+/**
+ * This function will parse a configuration string (psz_string) and
+ * - set the module name (*ppsz_name)
+ * - set all options for this module in a chained list (*pp_cfg)
+ * - returns a pointer on the next module if any.
+ *
+ * The string format is
+ *   module{option=*,option=*}[:modulenext{option=*,...}]
+ *
+ * The options values are unescaped using config_StringUnescape.
+ */
+VLC_EXPORT( char *, config_ChainCreate, ( char **ppsz_name, config_chain_t **pp_cfg, const char *psz_string ) );
+
+/**
+ * This function will release a linked list of config_chain_t
+ * (Including the head)
+ */
 VLC_EXPORT( void, config_ChainDestroy, ( config_chain_t * ) );
 
+/**
+ * This function will unescape a string in place and will return a pointer on
+ * the given string.
+ * No memory is allocated by it (unlike config_StringEscape).
+ * If NULL is given as parameter nothing will be done (NULL will be returned).
+ *
+ * The following sequences will be unescaped (only one time):
+ * \\ \' and \"
+ */
+VLC_EXPORT( char *, config_StringUnescape, ( char *psz_string ) );
+
+/**
+ * This function will escape a string that can be unescaped by
+ * config_StringUnescape.
+ * The returned value is allocated by it. You have to free it once you
+ * do not need it anymore (unlike config_StringUnescape).
+ * If NULL is given as parameter nothing will be done (NULL will be returned).
+ *
+ * The escaped characters are ' " and \
+ */
+VLC_EXPORT( char *, config_StringEscape, ( const char *psz_string ) );
+
 # ifdef __cplusplus
 }
 # endif
index db78ab7..5423fb7 100644 (file)
 /*****************************************************************************
  * Local prototypes
  *****************************************************************************/
+static bool IsEscapeNeeded( char c )
+{
+    return c == '\'' || c == '"' || c == '\\';
+}
+static bool IsEscape( const char *psz )
+{
+    if( !psz )
+        return false;
+    return psz[0] == '\\' && IsEscapeNeeded( psz[1] );
+}
+static bool IsSpace( char c  )
+{
+    return c == ' ' || c == '\t';
+}
 
-/* chain format:
-    module{option=*:option=*}[:module{option=*:...}]
- */
-#define SKIPSPACE( p ) { while( *p && ( *p == ' ' || *p == '\t' ) ) p++; }
+#define SKIPSPACE( p ) do { while( *p && IsSpace( *p ) ) p++; } while(0)
 #define SKIPTRAILINGSPACE( p, e ) \
-    { while( e > p && ( *(e-1) == ' ' || *(e-1) == '\t' ) ) e--; }
-
-/* go accross " " and { } */
-static const char *_get_chain_end( const char *str )
+    do { while( e > p && IsSpace( *(e-1) ) ) e--; } while(0)
+
+/**
+ * This function will return a pointer after the end of a string element.
+ * It will search the closing element which is
+ * } for { (it will handle nested { ... })
+ * " for "
+ * ' for '
+ */
+static const char *ChainGetEnd( const char *psz_string )
 {
+    const char *p = psz_string;
     char c;
-    const char *p = str;
 
+    if( !psz_string )
+        return NULL;
+
+    /* Look for a opening character */
     SKIPSPACE( p );
 
-    for( ;; )
+    for( ;; p++)
     {
-        if( !*p || *p == ',' || *p == '}' ) return p;
+        if( *p == '\0' || *p == ',' || *p == '}' )
+            return p;
 
-        if( *p != '{' && *p != '"' && *p != '\'' )
-        {
+        if( *p == '{' || *p == '"' || *p == '\'' )
+            break;
+    }
+
+    /* Set c to the closing character */
+    if( *p == '{' )
+        c = '}';
+    else
+        c = *p;
+    p++;
+
+    /* Search the closing character, handle nested {..} */
+    for( ;; )
+    {
+        if( *p == '\0')
+            return p;
+
+        if( IsEscape( p ) )
+            p += 2;
+        else if( *p == c )
+            return ++p;
+        else if( *p == '{' && c == '}' )
+            p = ChainGetEnd( p );
+        else
             p++;
-            continue;
-        }
+    }
+}
+
+/**
+ * It will extract an option value (=... or {...}).
+ * It will remove the initial = if present but keep the {}
+ */
+static char *ChainGetValue( const char **ppsz_string )
+{
+    const char *p = *ppsz_string;
 
-        if( *p == '{' ) c = '}';
-        else c = *p;
+    char *psz_value = NULL;
+    const char *end;
+    bool b_keep_brackets = (*p == '{');
+
+    if( *p == '=' )
         p++;
 
-        for( ;; )
+    end = ChainGetEnd( p );
+    if( end <= p )
+    {
+        psz_value = NULL;
+    }
+    else
+    {
+        /* Skip heading and trailing spaces.
+         * This ain't necessary but will avoid simple
+         * user mistakes. */
+        SKIPSPACE( p );
+    }
+
+    if( end <= p )
+    {
+        psz_value = NULL;
+    }
+    else
+    {
+        if( *p == '\'' || *p == '"' || ( !b_keep_brackets && *p == '{' ) )
         {
-            if( !*p ) return p;
+            p++;
+
+            if( *(end-1) != '\'' && *(end-1) == '"' )
+                SKIPTRAILINGSPACE( p, end );
 
-            if( *p == c ) return ++p;
-            else if( *p == '{' && c == '}' ) p = _get_chain_end( p );
-            else p++;
+            if( end - 1 <= p )
+                psz_value = NULL;
+            else
+                psz_value = strndup( p, end -1 - p );
+        }
+        else
+        {
+            SKIPTRAILINGSPACE( p, end );
+            if( end <= p )
+                psz_value = NULL;
+            else
+                psz_value = strndup( p, end - p );
         }
     }
+
+    /* */
+    if( psz_value )
+        config_StringUnescape( psz_value );
+
+    /* */
+    *ppsz_string = end;
+    return psz_value;
 }
 
 char *config_ChainCreate( char **ppsz_name, config_chain_t **pp_cfg, const char *psz_chain )
@@ -88,116 +182,88 @@ char *config_ChainCreate( char **ppsz_name, config_chain_t **pp_cfg, const char
     *ppsz_name = NULL;
     *pp_cfg    = NULL;
 
-    if( !p ) return NULL;
+    if( !p )
+        return NULL;
 
+    /* Look for parameter(either a {...} or :...) or the end of name (space or nul) */
     SKIPSPACE( p );
 
-    while( *p && *p != '{' && *p != ':' && *p != ' ' && *p != '\t' ) p++;
+    while( *p && *p != '{' && *p != ':' && !IsSpace( *p ) )
+        p++;
 
-    if( p == psz_chain ) return NULL;
+    if( p == psz_chain )
+        return NULL;
 
+    /* Extract the name */
     *ppsz_name = strndup( psz_chain, p - psz_chain );
 
+    /* Parse the parameters */
     SKIPSPACE( p );
 
     if( *p == '{' )
     {
         const char *psz_name;
 
+        /* Skip the opening '{' */
         p++;
 
+        /* parse all name=value[,] elements */
         for( ;; )
         {
-            config_chain_t cfg;
-
             SKIPSPACE( p );
 
             psz_name = p;
 
-            while( *p && *p != '=' && *p != ',' && *p != '{' && *p != '}' &&
-                   *p != ' ' && *p != '\t' ) p++;
+            /* Look for the end of the name (,={}_space_) */
+            while( *p && *p != '=' && *p != ',' && *p != '{' && *p != '}' && !IsSpace( *p ) )
+                p++;
 
-            /* fprintf( stderr, "name=%s - rest=%s\n", psz_name, p ); */
+            // fprintf( stderr, "name=%s - rest=%s\n", psz_name, p );
             if( p == psz_name )
             {
                 fprintf( stderr, "config_ChainCreate: invalid options (empty) \n" );
                 break;
             }
 
+            /* */
+            config_chain_t cfg;
             cfg.psz_name = strndup( psz_name, p - psz_name );
+            cfg.psz_value = NULL;
+            cfg.p_next = NULL;
 
+            /* Parse the option name parameter */
             SKIPSPACE( p );
 
             if( *p == '=' || *p == '{' )
             {
-                const char *end;
-                bool b_keep_brackets = (*p == '{');
-
-                if( *p == '=' ) p++;
-
-                end = _get_chain_end( p );
-                if( end <= p )
-                {
-                    cfg.psz_value = NULL;
-                }
-                else
-                {
-                    /* Skip heading and trailing spaces.
-                     * This ain't necessary but will avoid simple
-                     * user mistakes. */
-                    SKIPSPACE( p );
-                }
-
-                if( end <= p )
-                {
-                    cfg.psz_value = NULL;
-                }
-                else
-                {
-                    if( *p == '\'' || *p == '"' ||
-                        ( !b_keep_brackets && *p == '{' ) )
-                    {
-                        p++;
-
-                        if( *(end-1) != '\'' && *(end-1) == '"' )
-                            SKIPTRAILINGSPACE( p, end );
-
-                        if( end - 1 <= p ) cfg.psz_value = NULL;
-                        else cfg.psz_value = strndup( p, end -1 - p );
-                    }
-                    else
-                    {
-                        SKIPTRAILINGSPACE( p, end );
-                        if( end <= p ) cfg.psz_value = NULL;
-                        else cfg.psz_value = strndup( p, end - p );
-                    }
-                }
-
-                p = end;
+                cfg.psz_value = ChainGetValue( &p );
+
                 SKIPSPACE( p );
             }
-            else
+
+            /* Append the new option */
+            config_chain_t *p_new = malloc( sizeof(*p_new) );
+            if( !p_new )
             {
-                cfg.psz_value = NULL;
+                free( cfg.psz_name );
+                free( cfg.psz_value );
+                break;
             }
+            *p_new = cfg;
 
-            cfg.p_next = NULL;
             if( p_cfg )
             {
-                p_cfg->p_next = malloc( sizeof( config_chain_t ) );
-                memcpy( p_cfg->p_next, &cfg, sizeof( config_chain_t ) );
-
+                p_cfg->p_next = p_new;
                 p_cfg = p_cfg->p_next;
             }
             else
             {
-                p_cfg = malloc( sizeof( config_chain_t ) );
-                memcpy( p_cfg, &cfg, sizeof( config_chain_t ) );
-
-                *pp_cfg = p_cfg;
+                *pp_cfg = p_cfg = p_new;
             }
 
-            if( *p == ',' ) p++;
+            /* */
+            if( *p == ',' )
+                p++;
 
             if( *p == '}' )
             {
@@ -207,7 +273,8 @@ char *config_ChainCreate( char **ppsz_name, config_chain_t **pp_cfg, const char
         }
     }
 
-    if( *p == ':' ) return( strdup( p + 1 ) );
+    if( *p == ':' )
+        return strdup( &p[1] );
 
     return NULL;
 }
@@ -384,3 +451,51 @@ void __config_ChainParse( vlc_object_t *p_this, const char *psz_prefix,
                  cfg->psz_value ? cfg->psz_value : "(null)" );
     }
 }
+
+char *config_StringUnescape( char *psz_string )
+{
+    char *psz_src = psz_string;
+    char *psz_dst = psz_string;
+    if( !psz_src )
+        return NULL;
+
+    while( *psz_src )
+    {
+        if( IsEscape( psz_src ) )
+            psz_src++;
+        *psz_dst++ = *psz_src++;
+    }
+    *psz_dst = '\0';
+
+    return psz_string;
+}
+
+char *config_StringEscape( const char *psz_string )
+{
+    char *psz_return;
+    char *psz_dst;
+    int i_escape;
+
+    if( !psz_string )
+        return NULL;
+
+    i_escape = 0;
+    for( const char *p = psz_string; *p; p++ )
+    {
+        if( IsEscapeNeeded( *p ) )
+            i_escape++;
+    }
+
+    psz_return = psz_dst = malloc( strlen( psz_string ) + i_escape + 1 );
+    for( const char *p = psz_string; *p; p++ )
+    {
+        if( IsEscapeNeeded( *p ) )
+            *psz_dst++ = '\\';
+        *psz_dst++ = *p;
+    }
+    *psz_dst = '\0';
+
+    return psz_return;
+}
+
+
index 78aaf52..f2bb726 100644 (file)
@@ -68,6 +68,8 @@ __config_PutPsz
 __config_RemoveIntf
 __config_ResetAll
 __config_SaveConfigFile
+config_StringEscape
+config_StringUnescape
 convert_xml_special_chars
 date_Change
 date_Get