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