1 /*****************************************************************************
2  * chain.c : configuration module chain parsing stuff
3  *****************************************************************************
4  * Copyright (C) 2002-2007 VLC authors and VideoLAN
5  * $Id: 0ac550cec08cf5b0324ee42e972e73b705e1d729 $
6  *
7  * Authors: Christophe Massiot <massiot@via.ecp.fr>
8  *          Laurent Aimar <fenrir@via.ecp.fr>
9  *          Eric Petit <titer@videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25 
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33 
34 #include <vlc_common.h>
35 #include "libvlc.h"
36 #include <vlc_charset.h>
37 #include <vlc_plugin.h>
38 
39 #include "vlc_interface.h"
40 #include "configuration.h"
41 
42 /*****************************************************************************
43  * Local prototypes
44  *****************************************************************************/
IsEscapeNeeded(char c)45 static bool IsEscapeNeeded( char c )
46 {
47     return c == '\'' || c == '"' || c == '\\';
48 }
IsEscape(const char * psz)49 static bool IsEscape( const char *psz )
50 {
51     if( !psz )
52         return false;
53     return psz[0] == '\\' && IsEscapeNeeded( psz[1] );
54 }
IsSpace(char c)55 static bool IsSpace( char c  )
56 {
57     return c == ' ' || c == '\t';
58 }
59 
60 #define SKIPSPACE( p ) p += strspn( p, " \t" )
61 #define SKIPTRAILINGSPACE( p, e ) \
62     do { while( e > p && IsSpace( *(e-1) ) ) e--; } while(0)
63 
64 /**
65  * This function will return a pointer after the end of a string element.
66  * It will search the closing element which is
67  * } for { (it will handle nested { ... })
68  * " for "
69  * ' for '
70  */
ChainGetEnd(const char * psz_string)71 static const char *ChainGetEnd( const char *psz_string )
72 {
73     const char *p = psz_string;
74     char c;
75 
76     if( !psz_string )
77         return NULL;
78 
79     /* Look for a opening character */
80     SKIPSPACE( p );
81 
82     for( ;; p++)
83     {
84         if( *p == '\0' || *p == ',' || *p == '}' )
85             return p;
86 
87         if( *p == '{' || *p == '"' || *p == '\'' )
88             break;
89     }
90 
91     /* Set c to the closing character */
92     if( *p == '{' )
93         c = '}';
94     else
95         c = *p;
96     p++;
97 
98     /* Search the closing character, handle nested {..} */
99     for( ;; )
100     {
101         if( *p == '\0')
102             return p;
103 
104         if( IsEscape( p ) )
105             p += 2;
106         else if( *p == c )
107             return ++p;
108         else if( *p == '{' && c == '}' )
109             p = ChainGetEnd( p );
110         else
111             p++;
112     }
113 }
114 
115 /**
116  * It will extract an option value (=... or {...}).
117  * It will remove the initial = if present but keep the {}
118  */
ChainGetValue(const char ** ppsz_string)119 static char *ChainGetValue( const char **ppsz_string )
120 {
121     const char *p = *ppsz_string;
122 
123     char *psz_value = NULL;
124     const char *end;
125     bool b_keep_brackets = (*p == '{');
126 
127     if( *p == '=' )
128         p++;
129 
130     end = ChainGetEnd( p );
131     if( end <= p )
132     {
133         psz_value = NULL;
134     }
135     else
136     {
137         /* Skip heading and trailing spaces.
138          * This ain't necessary but will avoid simple
139          * user mistakes. */
140         SKIPSPACE( p );
141     }
142 
143     if( end <= p )
144     {
145         psz_value = NULL;
146     }
147     else
148     {
149         if( *p == '\'' || *p == '"' || ( !b_keep_brackets && *p == '{' ) )
150         {
151             p++;
152 
153             if( *(end-1) != '\'' && *(end-1) == '"' )
154                 SKIPTRAILINGSPACE( p, end );
155 
156             if( end - 1 <= p )
157                 psz_value = NULL;
158             else
159                 psz_value = strndup( p, end -1 - p );
160         }
161         else
162         {
163             SKIPTRAILINGSPACE( p, end );
164             if( end <= p )
165                 psz_value = NULL;
166             else
167                 psz_value = strndup( p, end - p );
168         }
169     }
170 
171     /* */
172     if( psz_value )
173         config_StringUnescape( psz_value );
174 
175     /* */
176     *ppsz_string = end;
177     return psz_value;
178 }
179 
180 /* Parse all name=value[,] elements */
config_ChainParseOptions(config_chain_t ** pp_cfg,const char * psz_opts)181 const char *config_ChainParseOptions( config_chain_t **pp_cfg, const char *psz_opts )
182 {
183     config_chain_t **pp_next = pp_cfg;
184     bool first = true;
185     do
186     {
187         if (!first)
188             psz_opts++; /* skip previous delimiter */
189         SKIPSPACE( psz_opts );
190 
191         first = false;
192 
193         /* Look for the end of the name (,={}_space_) */
194         size_t len = strcspn( psz_opts, "=,{} \t" );
195         if( len == 0 )
196             continue; /* ignore empty parameter */
197 
198         /* Append the new parameter */
199         config_chain_t *p_cfg = malloc( sizeof(*p_cfg) );
200         if( !p_cfg )
201             break;
202         p_cfg->psz_name = strndup( psz_opts, len );
203         psz_opts += len;
204         p_cfg->psz_value = NULL;
205         p_cfg->p_next = NULL;
206 
207         *pp_next = p_cfg;
208         pp_next = &p_cfg->p_next;
209 
210         /* Extract the option value */
211         SKIPSPACE( psz_opts );
212         if( strchr( "={", *psz_opts ) )
213         {
214             p_cfg->psz_value = ChainGetValue( &psz_opts );
215             SKIPSPACE( psz_opts );
216         }
217     }
218     while( !memchr( "}", *psz_opts, 2 ) );
219 
220     if( *psz_opts ) psz_opts++; /* skip '}' */;
221     SKIPSPACE( psz_opts );
222 
223     return psz_opts;
224 }
225 
config_ChainCreate(char ** ppsz_name,config_chain_t ** pp_cfg,const char * psz_chain)226 char *config_ChainCreate( char **ppsz_name, config_chain_t **pp_cfg,
227                           const char *psz_chain )
228 {
229     size_t len;
230 
231     *ppsz_name = NULL;
232     *pp_cfg    = NULL;
233 
234     if( !psz_chain )
235         return NULL;
236     SKIPSPACE( psz_chain );
237 
238     /* Look for parameter (a {...} or :...) or end of name (space or nul) */
239     len = strcspn( psz_chain, "{: \t" );
240     *ppsz_name = strndup( psz_chain, len );
241     psz_chain += len;
242 
243     /* Parse the parameters */
244     SKIPSPACE( psz_chain );
245     if( *psz_chain == '{' )
246         psz_chain = config_ChainParseOptions( pp_cfg, psz_chain );
247 
248     if( *psz_chain == ':' )
249         return strdup( psz_chain + 1 );
250 
251     return NULL;
252 }
253 
config_ChainDestroy(config_chain_t * p_cfg)254 void config_ChainDestroy( config_chain_t *p_cfg )
255 {
256     while( p_cfg != NULL )
257     {
258         config_chain_t *p_next;
259 
260         p_next = p_cfg->p_next;
261 
262         FREENULL( p_cfg->psz_name );
263         FREENULL( p_cfg->psz_value );
264         free( p_cfg );
265 
266         p_cfg = p_next;
267     }
268 }
269 
270 #undef config_ChainParse
config_ChainParse(vlc_object_t * p_this,const char * psz_prefix,const char * const * ppsz_options,config_chain_t * cfg)271 void config_ChainParse( vlc_object_t *p_this, const char *psz_prefix,
272                         const char *const *ppsz_options, config_chain_t *cfg )
273 {
274     if( psz_prefix == NULL ) psz_prefix = "";
275     size_t plen = 1 + strlen( psz_prefix );
276 
277     /* First, var_Create all variables */
278     for( size_t i = 0; ppsz_options[i] != NULL; i++ )
279     {
280         const char *optname = ppsz_options[i];
281         if (optname[0] == '*')
282             optname++;
283 
284         char name[plen + strlen( optname )];
285         snprintf( name, sizeof (name), "%s%s", psz_prefix, optname );
286         if( var_Create( p_this, name,
287                         config_GetType( name ) | VLC_VAR_DOINHERIT ) )
288             return /* VLC_xxx */;
289 
290         module_config_t* p_conf = config_FindConfig( name );
291         if( p_conf )
292         {
293             switch( CONFIG_CLASS( p_conf->i_type ) )
294             {
295                 case CONFIG_ITEM_INTEGER:
296                     var_Change( p_this, name, VLC_VAR_SETMINMAX,
297                         &(vlc_value_t){ .i_int = p_conf->min.i },
298                         &(vlc_value_t){ .i_int = p_conf->max.i } );
299                     break;
300                 case CONFIG_ITEM_FLOAT:
301                     var_Change( p_this, name, VLC_VAR_SETMINMAX,
302                         &(vlc_value_t){ .f_float = p_conf->min.f },
303                         &(vlc_value_t){ .f_float = p_conf->max.f } );
304                     break;
305             }
306         }
307     }
308 
309     /* Now parse options and set value */
310     for(; cfg; cfg = cfg->p_next )
311     {
312         vlc_value_t val;
313         bool b_yes = true;
314         bool b_once = false;
315         module_config_t *p_conf;
316         int i_type;
317         size_t i;
318 
319         if( cfg->psz_name == NULL || *cfg->psz_name == '\0' )
320             continue;
321 
322         for( i = 0; ppsz_options[i] != NULL; i++ )
323         {
324             if( !strcmp( ppsz_options[i], cfg->psz_name ) )
325             {
326                 break;
327             }
328             if( ( !strncmp( cfg->psz_name, "no-", 3 ) &&
329                   !strcmp( ppsz_options[i], cfg->psz_name + 3 ) ) ||
330                 ( !strncmp( cfg->psz_name, "no", 2 ) &&
331                   !strcmp( ppsz_options[i], cfg->psz_name + 2 ) ) )
332             {
333                 b_yes = false;
334                 break;
335             }
336 
337             if( *ppsz_options[i] == '*' &&
338                 !strcmp( &ppsz_options[i][1], cfg->psz_name ) )
339             {
340                 b_once = true;
341                 break;
342             }
343 
344         }
345 
346         if( ppsz_options[i] == NULL )
347         {
348             msg_Warn( p_this, "option %s is unknown", cfg->psz_name );
349             continue;
350         }
351 
352         /* create name */
353         char name[plen + strlen( ppsz_options[i] )];
354         const char *psz_name = name;
355         snprintf( name, sizeof (name), "%s%s", psz_prefix,
356                   b_once ? (ppsz_options[i] + 1) : ppsz_options[i] );
357 
358         /* Check if the option is deprecated */
359         p_conf = config_FindConfig( name );
360 
361         /* This is basically cut and paste from src/misc/configuration.c
362          * with slight changes */
363         if( p_conf )
364         {
365             if( p_conf->b_removed )
366             {
367                 msg_Err( p_this, "Option %s is not supported anymore.",
368                          name );
369                 /* TODO: this should return an error and end option parsing
370                  * ... but doing this would change the VLC API and all the
371                  * modules so i'll do it later */
372                 continue;
373             }
374         }
375         /* </Check if the option is deprecated> */
376 
377         /* get the type of the variable */
378         i_type = config_GetType( psz_name );
379         if( !i_type )
380         {
381             msg_Warn( p_this, "unknown option %s (value=%s)",
382                       cfg->psz_name, cfg->psz_value );
383             continue;
384         }
385 
386         if( i_type != VLC_VAR_BOOL && cfg->psz_value == NULL )
387         {
388             msg_Warn( p_this, "missing value for option %s", cfg->psz_name );
389             continue;
390         }
391         if( i_type != VLC_VAR_STRING && b_once )
392         {
393             msg_Warn( p_this, "*option_name need to be a string option" );
394             continue;
395         }
396 
397         switch( i_type )
398         {
399             case VLC_VAR_BOOL:
400                 val.b_bool = b_yes;
401                 break;
402             case VLC_VAR_INTEGER:
403                 val.i_int = strtoll( cfg->psz_value ? cfg->psz_value : "0",
404                                      NULL, 0 );
405                 break;
406             case VLC_VAR_FLOAT:
407                 val.f_float = us_atof( cfg->psz_value ? cfg->psz_value : "0" );
408                 break;
409             case VLC_VAR_STRING:
410                 val.psz_string = cfg->psz_value;
411                 break;
412             default:
413                 msg_Warn( p_this, "unhandled config var type (%d)", i_type );
414                 memset( &val, 0, sizeof( vlc_value_t ) );
415                 break;
416         }
417         if( b_once )
418         {
419             vlc_value_t val2;
420 
421             var_Get( p_this, psz_name, &val2 );
422             if( *val2.psz_string )
423             {
424                 free( val2.psz_string );
425                 msg_Dbg( p_this, "ignoring option %s (not first occurrence)", psz_name );
426                 continue;
427             }
428             free( val2.psz_string );
429         }
430         var_Set( p_this, psz_name, val );
431         msg_Dbg( p_this, "set config option: %s to %s", psz_name,
432                  cfg->psz_value ? cfg->psz_value : "(null)" );
433     }
434 }
435 
config_ChainDuplicate(const config_chain_t * p_src)436 config_chain_t *config_ChainDuplicate( const config_chain_t *p_src )
437 {
438     config_chain_t *p_dst = NULL;
439     config_chain_t **pp_last = &p_dst;
440 
441     for( ; p_src != NULL; p_src = p_src->p_next )
442     {
443         config_chain_t *p = malloc( sizeof(*p) );
444         if( !p )
445             break;
446         p->p_next    = NULL;
447         p->psz_name  = p_src->psz_name  ? strdup( p_src->psz_name )  : NULL;
448         p->psz_value = p_src->psz_value ? strdup( p_src->psz_value ) : NULL;
449 
450         *pp_last = p;
451         pp_last = &p->p_next;
452     }
453     return p_dst;
454 }
455 
config_StringUnescape(char * psz_string)456 char *config_StringUnescape( char *psz_string )
457 {
458     char *psz_src = psz_string;
459     char *psz_dst = psz_string;
460     if( !psz_src )
461         return NULL;
462 
463     while( *psz_src )
464     {
465         if( IsEscape( psz_src ) )
466             psz_src++;
467         *psz_dst++ = *psz_src++;
468     }
469     *psz_dst = '\0';
470 
471     return psz_string;
472 }
473 
config_StringEscape(const char * str)474 char *config_StringEscape( const char *str )
475 {
476     size_t length = 0;
477 
478     if( str == NULL )
479         return NULL;
480 
481     for( const char *p = str; *p; p++ )
482         length += IsEscapeNeeded( *p ) ? 2 : 1;
483 
484     char *ret = malloc( length + 1 ), *dst = ret;
485 
486     if( unlikely( !ret ) )
487         return NULL;
488 
489     for( const char *p = str; *p; p++ )
490     {
491         if( IsEscapeNeeded( *p ) )
492             *dst++ = '\\';
493         *dst++ = *p;
494     }
495     *dst = '\0';;
496     return ret;
497 }
498