From: Laurent Aimar Date: Fri, 29 Aug 2008 10:14:48 +0000 (+0200) Subject: Improved config_chain parsing by using escape for \ " and ' (close #1952) X-Git-Tag: 1.0.0-pre1~3700 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=ce1a1d9677d22a87bbec1fa9cbba42eca549b5c3;p=vlc Improved config_chain parsing by using escape for \ " and ' (close #1952) 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). --- diff --git a/include/vlc_configuration.h b/include/vlc_configuration.h index 36d46cb28a..65b33570d3 100644 --- a/include/vlc_configuration.h +++ b/include/vlc_configuration.h @@ -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 diff --git a/src/config/chain.c b/src/config/chain.c index db78ab718b..5423fb754d 100644 --- a/src/config/chain.c +++ b/src/config/chain.c @@ -39,45 +39,139 @@ /***************************************************************************** * 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; +} + + diff --git a/src/libvlccore.sym b/src/libvlccore.sym index 78aaf52d51..f2bb726973 100644 --- a/src/libvlccore.sym +++ b/src/libvlccore.sym @@ -68,6 +68,8 @@ __config_PutPsz __config_RemoveIntf __config_ResetAll __config_SaveConfigFile +config_StringEscape +config_StringUnescape convert_xml_special_chars date_Change date_Get