Commit ce1a1d96 authored by Laurent Aimar's avatar Laurent Aimar

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).
parent 07c44496
...@@ -243,17 +243,63 @@ VLC_EXPORT( bool, __config_ExistIntf, ( vlc_object_t *, const char * ) ); ...@@ -243,17 +243,63 @@ VLC_EXPORT( bool, __config_ExistIntf, ( vlc_object_t *, const char * ) );
****************************************************************************/ ****************************************************************************/
struct config_chain_t 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_name; /**< Option name */
char *psz_value; 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 ) #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( 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 * ) ); 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 # ifdef __cplusplus
} }
# endif # endif
......
...@@ -39,45 +39,139 @@ ...@@ -39,45 +39,139 @@
/***************************************************************************** /*****************************************************************************
* Local prototypes * 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: #define SKIPSPACE( p ) do { while( *p && IsSpace( *p ) ) p++; } while(0)
module{option=*:option=*}[:module{option=*:...}]
*/
#define SKIPSPACE( p ) { while( *p && ( *p == ' ' || *p == '\t' ) ) p++; }
#define SKIPTRAILINGSPACE( p, e ) \ #define SKIPTRAILINGSPACE( p, e ) \
{ while( e > p && ( *(e-1) == ' ' || *(e-1) == '\t' ) ) e--; } do { while( e > p && IsSpace( *(e-1) ) ) e--; } while(0)
/* go accross " " and { } */ /**
static const char *_get_chain_end( const char *str ) * 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; char c;
const char *p = str;
if( !psz_string )
return NULL;
/* Look for a opening character */
SKIPSPACE( p ); 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++; 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 = '}'; char *psz_value = NULL;
else c = *p; const char *end;
bool b_keep_brackets = (*p == '{');
if( *p == '=' )
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; if( end - 1 <= p )
else if( *p == '{' && c == '}' ) p = _get_chain_end( p ); psz_value = NULL;
else p++; 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 ) 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 ...@@ -88,116 +182,88 @@ char *config_ChainCreate( char **ppsz_name, config_chain_t **pp_cfg, const char
*ppsz_name = NULL; *ppsz_name = NULL;
*pp_cfg = 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 ); 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 ); *ppsz_name = strndup( psz_chain, p - psz_chain );
/* Parse the parameters */
SKIPSPACE( p ); SKIPSPACE( p );
if( *p == '{' ) if( *p == '{' )
{ {
const char *psz_name; const char *psz_name;
/* Skip the opening '{' */
p++; p++;
/* parse all name=value[,] elements */
for( ;; ) for( ;; )
{ {
config_chain_t cfg;
SKIPSPACE( p ); SKIPSPACE( p );
psz_name = p; psz_name = p;
while( *p && *p != '=' && *p != ',' && *p != '{' && *p != '}' && /* Look for the end of the name (,={}_space_) */
*p != ' ' && *p != '\t' ) p++; 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 ) if( p == psz_name )
{ {
fprintf( stderr, "config_ChainCreate: invalid options (empty) \n" ); fprintf( stderr, "config_ChainCreate: invalid options (empty) \n" );
break; break;
} }
/* */
config_chain_t cfg;
cfg.psz_name = strndup( psz_name, p - psz_name ); cfg.psz_name = strndup( psz_name, p - psz_name );
cfg.psz_value = NULL;
cfg.p_next = NULL;
/* Parse the option name parameter */
SKIPSPACE( p ); SKIPSPACE( p );
if( *p == '=' || *p == '{' ) if( *p == '=' || *p == '{' )
{ {
const char *end; cfg.psz_value = ChainGetValue( &p );
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;
SKIPSPACE( 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 ) if( p_cfg )
{ {
p_cfg->p_next = malloc( sizeof( config_chain_t ) ); p_cfg->p_next = p_new;
memcpy( p_cfg->p_next, &cfg, sizeof( config_chain_t ) );
p_cfg = p_cfg->p_next; p_cfg = p_cfg->p_next;
} }
else else
{ {
p_cfg = malloc( sizeof( config_chain_t ) ); *pp_cfg = p_cfg = p_new;
memcpy( p_cfg, &cfg, sizeof( config_chain_t ) );
*pp_cfg = p_cfg;
} }
if( *p == ',' ) p++; /* */
if( *p == ',' )
p++;
if( *p == '}' ) if( *p == '}' )
{ {
...@@ -207,7 +273,8 @@ char *config_ChainCreate( char **ppsz_name, config_chain_t **pp_cfg, const char ...@@ -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; return NULL;
} }
...@@ -384,3 +451,51 @@ void __config_ChainParse( vlc_object_t *p_this, const char *psz_prefix, ...@@ -384,3 +451,51 @@ void __config_ChainParse( vlc_object_t *p_this, const char *psz_prefix,
cfg->psz_value ? cfg->psz_value : "(null)" ); 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;
}
...@@ -68,6 +68,8 @@ __config_PutPsz ...@@ -68,6 +68,8 @@ __config_PutPsz
__config_RemoveIntf __config_RemoveIntf
__config_ResetAll __config_ResetAll
__config_SaveConfigFile __config_SaveConfigFile
config_StringEscape
config_StringUnescape
convert_xml_special_chars convert_xml_special_chars
date_Change date_Change
date_Get date_Get
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment