]> git.sesse.net Git - vlc/blob - src/text/strings.c
Use var_Inherit* instead of var_CreateGet*.
[vlc] / src / text / strings.c
1 /*****************************************************************************
2  * strings.c: String related functions
3  *****************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * Copyright (C) 2008-2009 Rémi Denis-Courmont
6  * $Id$
7  *
8  * Authors: Antoine Cellerier <dionoea at videolan dot org>
9  *          Daniel Stranger <vlc at schmaller dot de>
10  *          Rémi Denis-Courmont <rem # videolan org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35 #include <assert.h>
36
37 /* Needed by str_format_time */
38 #include <time.h>
39 #include <limits.h>
40
41 /* Needed by str_format_meta */
42 #include <vlc_input.h>
43 #include <vlc_meta.h>
44 #include <vlc_playlist.h>
45 #include <vlc_aout.h>
46
47 #include <vlc_strings.h>
48 #include <vlc_url.h>
49 #include <vlc_charset.h>
50
51 /**
52  * Decode encoded URI component. See also decode_URI().
53  * \return decoded duplicated string
54  */
55 char *decode_URI_duplicate( const char *psz )
56 {
57     char *psz_dup = strdup( psz );
58     decode_URI( psz_dup );
59     return psz_dup;
60 }
61
62 /**
63  * Decode an encoded URI component in place.
64  * <b>This function does NOT decode entire URIs.</b>
65  * It decodes components (e.g. host name, directory, file name).
66  * Decoded URIs do not exist in the real world (see RFC3986 §2.4).
67  * Complete URIs are always "encoded" (or they are syntaxically invalid).
68  *
69  * Note that URI encoding is different from Javascript escaping. Especially,
70  * white spaces and Unicode non-ASCII code points are encoded differently.
71  *
72  * \return psz on success, NULL if it was not properly encoded
73  */
74 char *decode_URI( char *psz )
75 {
76     unsigned char *in = (unsigned char *)psz, *out = in, c;
77
78     if( psz == NULL )
79         return NULL;
80
81     while( ( c = *in++ ) != '\0' )
82     {
83         switch( c )
84         {
85             case '%':
86             {
87                 char hex[3];
88
89                 if( ( ( hex[0] = *in++ ) == 0 )
90                  || ( ( hex[1] = *in++ ) == 0 ) )
91                     return NULL;
92
93                 hex[2] = '\0';
94                 *out++ = (unsigned char)strtoul( hex, NULL, 0x10 );
95                 break;
96             }
97
98             default:
99                 /* Inserting non-ASCII or non-printable characters is unsafe,
100                  * and no sane browser will send these unencoded */
101                 if( ( c < 32 ) || ( c > 127 ) )
102                     *out++ = '?';
103                 else
104                     *out++ = c;
105         }
106     }
107     *out = '\0';
108     return psz;
109 }
110
111 static inline bool isurisafe( int c )
112 {
113     /* These are the _unreserved_ URI characters (RFC3986 §2.3) */
114     return ( (unsigned char)( c - 'a' ) < 26 )
115             || ( (unsigned char)( c - 'A' ) < 26 )
116             || ( (unsigned char)( c - '0' ) < 10 )
117             || ( strchr( "-._~", c ) != NULL );
118 }
119
120 static char *encode_URI_bytes (const char *psz_uri, size_t len)
121 {
122     char *psz_enc = malloc (3 * len + 1), *out = psz_enc;
123     if (psz_enc == NULL)
124         return NULL;
125
126     for (size_t i = 0; i < len; i++)
127     {
128         static const char hex[16] = "0123456789ABCDEF";
129         uint8_t c = *psz_uri;
130
131         if( isurisafe( c ) )
132             *out++ = c;
133         /* This is URI encoding, not HTTP forms:
134          * Space is encoded as '%20', not '+'. */
135         else
136         {
137             *out++ = '%';
138             *out++ = hex[c >> 4];
139             *out++ = hex[c & 0xf];
140         }
141         psz_uri++;
142     }
143     *out++ = '\0';
144
145     out = realloc (psz_enc, out - psz_enc);
146     return out ? out : psz_enc; /* realloc() can fail (safe) */
147 }
148
149 /**
150  * Encodes an URI component (RFC3986 §2).
151  *
152  * @param psz_uri nul-terminated UTF-8 representation of the component.
153  * Obviously, you can't pass an URI containing a nul character, but you don't
154  * want to do that, do you?
155  *
156  * @return encoded string (must be free()'d), or NULL for ENOMEM.
157  */
158 char *encode_URI_component( const char *psz_uri )
159 {
160     return encode_URI_bytes (psz_uri, strlen (psz_uri));
161 }
162
163
164 static const struct xml_entity_s
165 {
166     char    psz_entity[8];
167     char    psz_char[4];
168 } xml_entities[] = {
169     /* Important: this list has to be in alphabetical order (psz_entity-wise) */
170     { "AElig;",  "Æ" },
171     { "Aacute;", "Á" },
172     { "Acirc;",  "Â" },
173     { "Agrave;", "À" },
174     { "Aring;",  "Å" },
175     { "Atilde;", "Ã" },
176     { "Auml;",   "Ä" },
177     { "Ccedil;", "Ç" },
178     { "Dagger;", "‡" },
179     { "ETH;",    "Ð" },
180     { "Eacute;", "É" },
181     { "Ecirc;",  "Ê" },
182     { "Egrave;", "È" },
183     { "Euml;",   "Ë" },
184     { "Iacute;", "Í" },
185     { "Icirc;",  "Î" },
186     { "Igrave;", "Ì" },
187     { "Iuml;",   "Ï" },
188     { "Ntilde;", "Ñ" },
189     { "OElig;",  "Œ" },
190     { "Oacute;", "Ó" },
191     { "Ocirc;",  "Ô" },
192     { "Ograve;", "Ò" },
193     { "Oslash;", "Ø" },
194     { "Otilde;", "Õ" },
195     { "Ouml;",   "Ö" },
196     { "Scaron;", "Š" },
197     { "THORN;",  "Þ" },
198     { "Uacute;", "Ú" },
199     { "Ucirc;",  "Û" },
200     { "Ugrave;", "Ù" },
201     { "Uuml;",   "Ü" },
202     { "Yacute;", "Ý" },
203     { "Yuml;",   "Ÿ" },
204     { "aacute;", "á" },
205     { "acirc;",  "â" },
206     { "acute;",  "´" },
207     { "aelig;",  "æ" },
208     { "agrave;", "à" },
209     { "amp;",    "&" },
210     { "apos;",   "'" },
211     { "aring;",  "å" },
212     { "atilde;", "ã" },
213     { "auml;",   "ä" },
214     { "bdquo;",  "„" },
215     { "brvbar;", "¦" },
216     { "ccedil;", "ç" },
217     { "cedil;",  "¸" },
218     { "cent;",   "¢" },
219     { "circ;",   "ˆ" },
220     { "copy;",   "©" },
221     { "curren;", "¤" },
222     { "dagger;", "†" },
223     { "deg;",    "°" },
224     { "divide;", "÷" },
225     { "eacute;", "é" },
226     { "ecirc;",  "ê" },
227     { "egrave;", "è" },
228     { "eth;",    "ð" },
229     { "euml;",   "ë" },
230     { "euro;",   "€" },
231     { "frac12;", "½" },
232     { "frac14;", "¼" },
233     { "frac34;", "¾" },
234     { "gt;",     ">" },
235     { "hellip;", "…" },
236     { "iacute;", "í" },
237     { "icirc;",  "î" },
238     { "iexcl;",  "¡" },
239     { "igrave;", "ì" },
240     { "iquest;", "¿" },
241     { "iuml;",   "ï" },
242     { "laquo;",  "«" },
243     { "ldquo;",  "“" },
244     { "lsaquo;", "‹" },
245     { "lsquo;",  "‘" },
246     { "lt;",     "<" },
247     { "macr;",   "¯" },
248     { "mdash;",  "—" },
249     { "micro;",  "µ" },
250     { "middot;", "·" },
251     { "nbsp;",   "\xc2\xa0" },
252     { "ndash;",  "–" },
253     { "not;",    "¬" },
254     { "ntilde;", "ñ" },
255     { "oacute;", "ó" },
256     { "ocirc;",  "ô" },
257     { "oelig;",  "œ" },
258     { "ograve;", "ò" },
259     { "ordf;",   "ª" },
260     { "ordm;",   "º" },
261     { "oslash;", "ø" },
262     { "otilde;", "õ" },
263     { "ouml;",   "ö" },
264     { "para;",   "¶" },
265     { "permil;", "‰" },
266     { "plusmn;", "±" },
267     { "pound;",  "£" },
268     { "quot;",   "\"" },
269     { "raquo;",  "»" },
270     { "rdquo;",  "”" },
271     { "reg;",    "®" },
272     { "rsaquo;", "›" },
273     { "rsquo;",  "’" },
274     { "sbquo;",  "‚" },
275     { "scaron;", "š" },
276     { "sect;",   "§" },
277     { "shy;",    "­" },
278     { "sup1;",   "¹" },
279     { "sup2;",   "²" },
280     { "sup3;",   "³" },
281     { "szlig;",  "ß" },
282     { "thorn;",  "þ" },
283     { "tilde;",  "˜" },
284     { "times;",  "×" },
285     { "trade;",  "™" },
286     { "uacute;", "ú" },
287     { "ucirc;",  "û" },
288     { "ugrave;", "ù" },
289     { "uml;",    "¨" },
290     { "uuml;",   "ü" },
291     { "yacute;", "ý" },
292     { "yen;",    "¥" },
293     { "yuml;",   "ÿ" },
294 };
295
296 static int cmp_entity (const void *key, const void *elem)
297 {
298     const struct xml_entity_s *ent = elem;
299     const char *name = key;
300
301     return strncmp (name, ent->psz_entity, strlen (ent->psz_entity));
302 }
303
304 /**
305  * Converts "&lt;", "&gt;" and "&amp;" to "<", ">" and "&"
306  * \param string to convert
307  */
308 void resolve_xml_special_chars( char *psz_value )
309 {
310     char *p_pos = psz_value;
311
312     while ( *psz_value )
313     {
314         if( *psz_value == '&' )
315         {
316             if( psz_value[1] == '#' )
317             {   /* &#xxx; Unicode code point */
318                 char *psz_end;
319                 unsigned long cp = strtoul( psz_value+2, &psz_end, 10 );
320                 if( *psz_end == ';' )
321                 {
322                     psz_value = psz_end + 1;
323                     if( cp == 0 )
324                         (void)0; /* skip nuls */
325                     else
326                     if( cp <= 0x7F )
327                     {
328                         *p_pos =            cp;
329                     }
330                     else
331                     /* Unicode code point outside ASCII.
332                      * &#xxx; representation is longer than UTF-8 :) */
333                     if( cp <= 0x7FF )
334                     {
335                         *p_pos++ = 0xC0 |  (cp >>  6);
336                         *p_pos   = 0x80 |  (cp        & 0x3F);
337                     }
338                     else
339                     if( cp <= 0xFFFF )
340                     {
341                         *p_pos++ = 0xE0 |  (cp >> 12);
342                         *p_pos++ = 0x80 | ((cp >>  6) & 0x3F);
343                         *p_pos   = 0x80 |  (cp        & 0x3F);
344                     }
345                     else
346                     if( cp <= 0x1FFFFF ) /* Outside the BMP */
347                     {   /* Unicode stops at 10FFFF, but who cares? */
348                         *p_pos++ = 0xF0 |  (cp >> 18);
349                         *p_pos++ = 0x80 | ((cp >> 12) & 0x3F);
350                         *p_pos++ = 0x80 | ((cp >>  6) & 0x3F);
351                         *p_pos   = 0x80 |  (cp        & 0x3F);
352                     }
353                 }
354                 else
355                 {
356                     /* Invalid entity number */
357                     *p_pos = *psz_value;
358                     psz_value++;
359                 }
360             }
361             else
362             {   /* Well-known XML entity */
363                 const struct xml_entity_s *ent;
364
365                 ent = bsearch (psz_value + 1, xml_entities,
366                                sizeof (xml_entities) / sizeof (*ent),
367                                sizeof (*ent), cmp_entity);
368                 if (ent != NULL)
369                 {
370                     size_t olen = strlen (ent->psz_char);
371                     memcpy (p_pos, ent->psz_char, olen);
372                     p_pos += olen - 1;
373                     psz_value += strlen (ent->psz_entity) + 1;
374                 }
375                 else
376                 {   /* No match */
377                     *p_pos = *psz_value;
378                     psz_value++;
379                 }
380             }
381         }
382         else
383         {
384             *p_pos = *psz_value;
385             psz_value++;
386         }
387
388         p_pos++;
389     }
390
391     *p_pos = '\0';
392 }
393
394 /**
395  * Converts '<', '>', '\"', '\'' and '&' to their html entities
396  * \param psz_content simple element content that is to be converted
397  */
398 char *convert_xml_special_chars( const char *psz_content )
399 {
400     assert( psz_content );
401
402     const size_t len = strlen( psz_content );
403     char *const psz_temp = malloc( 6 * len + 1 );
404     char *p_to   = psz_temp;
405
406     if( psz_temp == NULL )
407         return NULL;
408     for( size_t i = 0; i < len; i++ )
409     {
410         const char *str;
411         char c = psz_content[i];
412
413         switch ( c )
414         {
415             case '\"': str = "quot"; break;
416             case '&':  str = "amp";  break;
417             case '\'': str = "#39";  break;
418             case '<':  str = "lt";   break;
419             case '>':  str = "gt";   break;
420             default:
421                 *(p_to++) = c;
422                 continue;
423         }
424         p_to += sprintf( p_to, "&%s;", str );
425     }
426     *(p_to++) = '\0';
427
428     p_to = realloc( psz_temp, p_to - psz_temp );
429     return p_to ? p_to : psz_temp; /* cannot fail */
430 }
431
432 /* Base64 encoding */
433 char *vlc_b64_encode_binary( const uint8_t *src, size_t i_src )
434 {
435     static const char b64[] =
436            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
437
438     char *ret = malloc( ( i_src + 4 ) * 4 / 3 );
439     char *dst = ret;
440
441     if( dst == NULL )
442         return NULL;
443
444     while( i_src > 0 )
445     {
446         /* pops (up to) 3 bytes of input, push 4 bytes */
447         uint32_t v;
448
449         /* 1/3 -> 1/4 */
450         v = *src++ << 24;
451         *dst++ = b64[v >> 26];
452         v = v << 6;
453
454         /* 2/3 -> 2/4 */
455         if( i_src >= 2 )
456             v |= *src++ << 22;
457         *dst++ = b64[v >> 26];
458         v = v << 6;
459
460         /* 3/3 -> 3/4 */
461         if( i_src >= 3 )
462             v |= *src++ << 20; // 3/3
463         *dst++ = ( i_src >= 2 ) ? b64[v >> 26] : '='; // 3/4
464         v = v << 6;
465
466         /* -> 4/4 */
467         *dst++ = ( i_src >= 3 ) ? b64[v >> 26] : '='; // 4/4
468
469         if( i_src <= 3 )
470             break;
471         i_src -= 3;
472     }
473
474     *dst = '\0';
475
476     return ret;
477 }
478
479 char *vlc_b64_encode( const char *src )
480 {
481     if( src )
482         return vlc_b64_encode_binary( (const uint8_t*)src, strlen(src) );
483     else
484         return vlc_b64_encode_binary( (const uint8_t*)"", 0 );
485 }
486
487 /* Base64 decoding */
488 size_t vlc_b64_decode_binary_to_buffer( uint8_t *p_dst, size_t i_dst, const char *p_src )
489 {
490     static const int b64[256] = {
491         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 00-0F */
492         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 10-1F */
493         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,  /* 20-2F */
494         52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,  /* 30-3F */
495         -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,  /* 40-4F */
496         15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,  /* 50-5F */
497         -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,  /* 60-6F */
498         41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,  /* 70-7F */
499         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 80-8F */
500         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 90-9F */
501         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* A0-AF */
502         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* B0-BF */
503         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* C0-CF */
504         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* D0-DF */
505         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* E0-EF */
506         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1   /* F0-FF */
507     };
508     uint8_t *p_start = p_dst;
509     uint8_t *p = (uint8_t *)p_src;
510
511     int i_level;
512     int i_last;
513
514     for( i_level = 0, i_last = 0; (size_t)( p_dst - p_start ) < i_dst && *p != '\0'; p++ )
515     {
516         const int c = b64[(unsigned int)*p];
517         if( c == -1 )
518             continue;
519
520         switch( i_level )
521         {
522             case 0:
523                 i_level++;
524                 break;
525             case 1:
526                 *p_dst++ = ( i_last << 2 ) | ( ( c >> 4)&0x03 );
527                 i_level++;
528                 break;
529             case 2:
530                 *p_dst++ = ( ( i_last << 4 )&0xf0 ) | ( ( c >> 2 )&0x0f );
531                 i_level++;
532                 break;
533             case 3:
534                 *p_dst++ = ( ( i_last &0x03 ) << 6 ) | c;
535                 i_level = 0;
536         }
537         i_last = c;
538     }
539
540     return p_dst - p_start;
541 }
542 size_t vlc_b64_decode_binary( uint8_t **pp_dst, const char *psz_src )
543 {
544     const int i_src = strlen( psz_src );
545     uint8_t   *p_dst;
546
547     *pp_dst = p_dst = malloc( i_src );
548     if( !p_dst )
549         return 0;
550     return  vlc_b64_decode_binary_to_buffer( p_dst, i_src, psz_src );
551 }
552 char *vlc_b64_decode( const char *psz_src )
553 {
554     const int i_src = strlen( psz_src );
555     char *p_dst = malloc( i_src + 1 );
556     size_t i_dst;
557     if( !p_dst )
558         return NULL;
559
560     i_dst = vlc_b64_decode_binary_to_buffer( (uint8_t*)p_dst, i_src, psz_src );
561     p_dst[i_dst] = '\0';
562
563     return p_dst;
564 }
565
566 /**
567  * Formats current time into a heap-allocated string.
568  * @param tformat time format (as with C strftime())
569  * @return an allocated string (must be free()'d), or NULL on memory error.
570  */
571 char *str_format_time( const char *tformat )
572 {
573     time_t curtime;
574     struct tm loctime;
575
576     if (strcmp (tformat, "") == 0)
577         return strdup (""); /* corner case w.r.t. strftime() return value */
578
579     /* Get the current time.  */
580     time( &curtime );
581
582     /* Convert it to local time representation.  */
583     localtime_r( &curtime, &loctime );
584     for (size_t buflen = strlen (tformat) + 32;; buflen += 32)
585     {
586         char *str = malloc (buflen);
587         if (str == NULL)
588             return NULL;
589
590         size_t len = strftime (str, buflen, tformat, &loctime);
591         if (len > 0)
592         {
593             char *ret = realloc (str, len + 1);
594             return ret ? ret : str; /* <- this cannot fail */
595         }
596     }
597     assert (0);
598 }
599
600 static void format_duration (char *buf, size_t len, int64_t duration)
601 {
602     lldiv_t d;
603     int sec;
604
605     duration /= CLOCK_FREQ;
606     d = lldiv (duration, 60);
607     sec = d.rem;
608     d = lldiv (d.quot, 60);
609     snprintf (buf, len, "%02lld:%02d:%02d", d.quot, (int)d.rem, sec);
610 }
611
612 #define INSERT_STRING( string )                                     \
613                     if( string != NULL )                            \
614                     {                                               \
615                         int len = strlen( string );                 \
616                         dst = xrealloc( dst, i_size = i_size + len );\
617                         memcpy( (dst+d), string, len );             \
618                         d += len;                                   \
619                         free( string );                             \
620                     }                                               \
621                     else if( !b_empty_if_na )                       \
622                     {                                               \
623                         *(dst+d) = '-';                             \
624                         d++;                                        \
625                     }                                               \
626
627 /* same than INSERT_STRING, except that string won't be freed */
628 #define INSERT_STRING_NO_FREE( string )                             \
629                     {                                               \
630                         int len = strlen( string );                 \
631                         dst = xrealloc( dst, i_size = i_size + len );\
632                         memcpy( dst+d, string, len );               \
633                         d += len;                                   \
634                     }
635 #undef str_format_meta
636 char *str_format_meta( vlc_object_t *p_object, const char *string )
637 {
638     const char *s = string;
639     bool b_is_format = false;
640     bool b_empty_if_na = false;
641     char buf[10];
642     int i_size = strlen( string ) + 1; /* +1 to store '\0' */
643     char *dst = strdup( string );
644     if( !dst ) return NULL;
645     int d = 0;
646
647     input_thread_t *p_input = playlist_CurrentInput( pl_Get(p_object) );
648     input_item_t *p_item = NULL;
649     if( p_input )
650     {
651         p_item = input_GetItem(p_input);
652     }
653
654     while( *s )
655     {
656         if( b_is_format )
657         {
658             switch( *s )
659             {
660                 case 'a':
661                     if( p_item )
662                     {
663                         INSERT_STRING( input_item_GetArtist( p_item ) );
664                     }
665                     break;
666                 case 'b':
667                     if( p_item )
668                     {
669                         INSERT_STRING( input_item_GetAlbum( p_item ) );
670                     }
671                     break;
672                 case 'c':
673                     if( p_item )
674                     {
675                         INSERT_STRING( input_item_GetCopyright( p_item ) );
676                     }
677                     break;
678                 case 'd':
679                     if( p_item )
680                     {
681                         INSERT_STRING( input_item_GetDescription( p_item ) );
682                     }
683                     break;
684                 case 'e':
685                     if( p_item )
686                     {
687                         INSERT_STRING( input_item_GetEncodedBy( p_item ) );
688                     }
689                     break;
690                 case 'f':
691                     if( p_item && p_item->p_stats )
692                     {
693                         vlc_mutex_lock( &p_item->p_stats->lock );
694                         snprintf( buf, 10, "%"PRIi64,
695                                   p_item->p_stats->i_displayed_pictures );
696                         vlc_mutex_unlock( &p_item->p_stats->lock );
697                     }
698                     else
699                         strcpy( buf, b_empty_if_na ? "" : "-" );
700                     INSERT_STRING_NO_FREE( buf );
701                     break;
702                 case 'g':
703                     if( p_item )
704                     {
705                         INSERT_STRING( input_item_GetGenre( p_item ) );
706                     }
707                     break;
708                 case 'l':
709                     if( p_item )
710                     {
711                         INSERT_STRING( input_item_GetLanguage( p_item ) );
712                     }
713                     break;
714                 case 'n':
715                     if( p_item )
716                     {
717                         INSERT_STRING( input_item_GetTrackNum( p_item ) );
718                     }
719                     break;
720                 case 'p':
721                     if( p_item )
722                     {
723                         INSERT_STRING( input_item_GetNowPlaying( p_item ) );
724                     }
725                     break;
726                 case 'r':
727                     if( p_item )
728                     {
729                         INSERT_STRING( input_item_GetRating( p_item ) );
730                     }
731                     break;
732                 case 's':
733                 {
734                     char *lang = NULL;
735                     if( p_input )
736                         lang = var_GetNonEmptyString( p_input, "sub-language" );
737                     if( lang == NULL )
738                         lang = strdup( b_empty_if_na ? "" : "-" );
739                     INSERT_STRING( lang );
740                     break;
741                 }
742                 case 't':
743                     if( p_item )
744                     {
745                         INSERT_STRING( input_item_GetTitle( p_item ) );
746                     }
747                     break;
748                 case 'u':
749                     if( p_item )
750                     {
751                         INSERT_STRING( input_item_GetURL( p_item ) );
752                     }
753                     break;
754                 case 'A':
755                     if( p_item )
756                     {
757                         INSERT_STRING( input_item_GetDate( p_item ) );
758                     }
759                     break;
760                 case 'B':
761                     if( p_input )
762                     {
763                         snprintf( buf, 10, "%"PRId64,
764                                   var_GetInteger( p_input, "bit-rate" )/1000 );
765                     }
766                     else
767                         strcpy( buf, b_empty_if_na ? "" : "-" );
768                     INSERT_STRING_NO_FREE( buf );
769                     break;
770                 case 'C':
771                     if( p_input )
772                     {
773                         snprintf( buf, 10, "%"PRId64,
774                                   var_GetInteger( p_input, "chapter" ) );
775                     }
776                     else
777                         strcpy( buf, b_empty_if_na ? "" : "-" );
778                     INSERT_STRING_NO_FREE( buf );
779                     break;
780                 case 'D':
781                     if( p_item )
782                     {
783                         mtime_t i_duration = input_item_GetDuration( p_item );
784                         format_duration (buf, sizeof (buf), i_duration);
785                     }
786                     else
787                         strcpy( buf, b_empty_if_na ? "" : "--:--:--" );
788                     INSERT_STRING_NO_FREE( buf );
789                     break;
790                 case 'F':
791                     if( p_item )
792                     {
793                         INSERT_STRING( input_item_GetURI( p_item ) );
794                     }
795                     break;
796                 case 'I':
797                     if( p_input )
798                     {
799                         snprintf( buf, 10, "%"PRId64,
800                                   var_GetInteger( p_input, "title" ) );
801                     }
802                     else
803                         strcpy( buf, b_empty_if_na ? "" : "-" );
804                     INSERT_STRING_NO_FREE( buf );
805                     break;
806                 case 'L':
807                     if( p_item && p_input )
808                     {
809                         mtime_t i_duration = input_item_GetDuration( p_item );
810                         int64_t i_time = var_GetTime( p_input, "time" );
811                         format_duration( buf, sizeof(buf),
812                                          i_duration - i_time );
813                     }
814                     else
815                         strcpy( buf, b_empty_if_na ? "" : "--:--:--" );
816                     INSERT_STRING_NO_FREE( buf );
817                     break;
818                 case 'N':
819                     if( p_item )
820                     {
821                         INSERT_STRING( input_item_GetName( p_item ) );
822                     }
823                     break;
824                 case 'O':
825                 {
826                     char *lang = NULL;
827                     if( p_input )
828                         lang = var_GetNonEmptyString( p_input,
829                                                       "audio-language" );
830                     if( lang == NULL )
831                         lang = strdup( b_empty_if_na ? "" : "-" );
832                     INSERT_STRING( lang );
833                     break;
834                 }
835                 case 'P':
836                     if( p_input )
837                     {
838                         snprintf( buf, 10, "%2.1lf",
839                                   var_GetFloat( p_input, "position" ) * 100. );
840                     }
841                     else
842                     {
843                         snprintf( buf, 10, b_empty_if_na ? "" : "--.-%%" );
844                     }
845                     INSERT_STRING_NO_FREE( buf );
846                     break;
847                 case 'R':
848                     if( p_input )
849                     {
850                         float f = var_GetFloat( p_input, "rate" );
851                         snprintf( buf, 10, "%.3f", f );
852                     }
853                     else
854                         strcpy( buf, b_empty_if_na ? "" : "-" );
855                     INSERT_STRING_NO_FREE( buf );
856                     break;
857                 case 'S':
858                     if( p_input )
859                     {
860                         int r = var_GetInteger( p_input, "sample-rate" );
861                         snprintf( buf, 10, "%d.%d", r/1000, (r/100)%10 );
862                     }
863                     else
864                         strcpy( buf, b_empty_if_na ? "" : "-" );
865                     INSERT_STRING_NO_FREE( buf );
866                     break;
867                 case 'T':
868                     if( p_input )
869                     {
870                         int64_t i_time = var_GetTime( p_input, "time" );
871                         format_duration( buf, sizeof(buf), i_time );
872                     }
873                     else
874                         strcpy( buf, b_empty_if_na ? "" :  "--:--:--" );
875                     INSERT_STRING_NO_FREE( buf );
876                     break;
877                 case 'U':
878                     if( p_item )
879                     {
880                         INSERT_STRING( input_item_GetPublisher( p_item ) );
881                     }
882                     break;
883                 case 'V':
884                 {
885                     audio_volume_t volume;
886                     aout_VolumeGet( p_object, &volume );
887                     snprintf( buf, 10, "%d", volume );
888                     INSERT_STRING_NO_FREE( buf );
889                     break;
890                 }
891                 case '_':
892                     *(dst+d) = '\n';
893                     d++;
894                     break;
895
896                 case ' ':
897                     b_empty_if_na = true;
898                     break;
899
900                 default:
901                     *(dst+d) = *s;
902                     d++;
903                     break;
904             }
905             if( *s != ' ' )
906                 b_is_format = false;
907         }
908         else if( *s == '$' )
909         {
910             b_is_format = true;
911             b_empty_if_na = false;
912         }
913         else
914         {
915             *(dst+d) = *s;
916             d++;
917         }
918         s++;
919     }
920     *(dst+d) = '\0';
921
922     if( p_input )
923         vlc_object_release( p_input );
924
925     return dst;
926 }
927 #undef INSERT_STRING
928 #undef INSERT_STRING_NO_FREE
929
930 #undef str_format
931 /**
932  * Apply str format time and str format meta
933  */
934 char *str_format( vlc_object_t *p_this, const char *psz_src )
935 {
936     char *psz_buf1, *psz_buf2;
937     psz_buf1 = str_format_time( psz_src );
938     psz_buf2 = str_format_meta( p_this, psz_buf1 );
939     free( psz_buf1 );
940     return psz_buf2;
941 }
942
943 /**
944  * Remove forbidden characters from filenames (including slashes)
945  */
946 void filename_sanitize( char *str )
947 {
948 #if defined( WIN32 )
949     char *str_base = str;
950 #endif
951
952     if( *str == '.' && (str[1] == '\0' || (str[1] == '.' && str[2] == '\0' ) ) )
953     {
954         while( *str )
955         {
956             *str = '_';
957             str++;
958         }
959         return;
960     }
961
962 #if defined( WIN32 )
963     // Change leading spaces into underscores
964     while( *str && *str == ' ' )
965         *str++ = '_';
966 #endif
967
968     while( *str )
969     {
970         switch( *str )
971         {
972             case '/':
973 #if defined( __APPLE__ )
974             case ':':
975 #elif defined( WIN32 )
976             case '\\':
977             case '*':
978             case '"':
979             case '?':
980             case ':':
981             case '|':
982             case '<':
983             case '>':
984 #endif
985                 *str = '_';
986         }
987         str++;
988     }
989
990 #if defined( WIN32 )
991     // Change trailing spaces into underscores
992     str--;
993     while( str != str_base )
994     {
995         if( *str != ' ' )
996             break;
997         *str-- = '_';
998     }
999 #endif
1000 }
1001
1002 /**
1003  * Remove forbidden characters from full paths (leaves slashes)
1004  */
1005 void path_sanitize( char *str )
1006 {
1007 #ifdef WIN32
1008     /* check drive prefix if path is absolute */
1009     if( (((unsigned char)(str[0] - 'A') < 26)
1010       || ((unsigned char)(str[0] - 'a') < 26)) && (':' == str[1]) )
1011         str += 2;
1012 #endif
1013     while( *str )
1014     {
1015 #if defined( __APPLE__ )
1016         if( *str == ':' )
1017             *str = '_';
1018 #elif defined( WIN32 )
1019         if( strchr( "*\"?:|<>", *str ) )
1020             *str = '_';
1021         if( *str == '/' )
1022             *str = DIR_SEP_CHAR;
1023 #endif
1024         str++;
1025     }
1026 }
1027
1028 #include <vlc_url.h>
1029 #ifdef WIN32
1030 # include <io.h>
1031 #endif
1032
1033 /**
1034  * Convert a file path to an URI.
1035  * If already an URI, return a copy of the string.
1036  * @param path path to convert (or URI to copy)
1037  * @param scheme URI scheme to use (default is auto: "file", "fd" or "smb")
1038  * @return a nul-terminated URI string (use free() to release it),
1039  * or NULL in case of error
1040  */
1041 char *make_URI (const char *path, const char *scheme)
1042 {
1043     if (path == NULL)
1044         return NULL;
1045     if (scheme == NULL && !strcmp (path, "-"))
1046         return strdup ("fd://0"); // standard input
1047     if (strstr (path, "://") != NULL)
1048         return strdup (path); /* Already an URI */
1049     /* Note: VLC cannot handle URI schemes without double slash after the
1050      * scheme name (such as mailto: or news:). */
1051
1052     char *buf;
1053 #ifdef WIN32
1054     /* Drive letter */
1055     if (isalpha (path[0]) && (path[1] == ':'))
1056     {
1057         if (asprintf (&buf, "%s:///%c:", scheme ? scheme : "file",
1058                       path[0]) == -1)
1059             buf = NULL;
1060         path += 2;
1061 # warning Drive letter-relative path not implemented!
1062         if (path[0] != DIR_SEP_CHAR)
1063             return NULL;
1064     }
1065     else
1066 #endif
1067     if (!strncmp (path, "\\\\", 2))
1068     {   /* Windows UNC paths */
1069 #ifndef WIN32
1070         if (scheme != NULL)
1071             return NULL; /* remote files not supported */
1072
1073         /* \\host\share\path -> smb://host/share/path */
1074         if (strchr (path + 2, '\\') != NULL)
1075         {   /* Convert backslashes to slashes */
1076             char *dup = strdup (path);
1077             if (dup == NULL)
1078                 return NULL;
1079             for (size_t i = 2; dup[i]; i++)
1080                 if (dup[i] == '\\')
1081                     dup[i] = DIR_SEP_CHAR;
1082
1083             char *ret = make_URI (dup, scheme);
1084             free (dup);
1085             return ret;
1086         }
1087 # define SMB_SCHEME "smb"
1088 #else
1089         /* \\host\share\path -> file://host/share/path */
1090 # define SMB_SCHEME "file"
1091 #endif
1092         size_t hostlen = strcspn (path + 2, DIR_SEP);
1093
1094         buf = malloc (sizeof (SMB_SCHEME) + 3 + hostlen);
1095         if (buf != NULL)
1096             snprintf (buf, sizeof (SMB_SCHEME) + 3 + hostlen,
1097                       SMB_SCHEME"://%s", path + 2);
1098         path += 2 + hostlen;
1099
1100         if (path[0] == '\0')
1101             return buf; /* Hostname without path */
1102     }
1103     else
1104     if (path[0] != DIR_SEP_CHAR)
1105     {   /* Relative path: prepend the current working directory */
1106         char cwd[PATH_MAX];
1107
1108         if (getcwd (cwd, sizeof (cwd)) == NULL) /* FIXME: UTF8? */
1109             return NULL;
1110         if (asprintf (&buf, "%s/%s", cwd, path) == -1)
1111             return NULL;
1112         char *ret = make_URI (buf, scheme);
1113         free (buf);
1114         return ret;
1115     }
1116     else
1117     if (asprintf (&buf, "%s://", scheme ? scheme : "file") == -1)
1118         buf = NULL;
1119     if (buf == NULL)
1120         return NULL;
1121
1122     assert (path[0] == DIR_SEP_CHAR);
1123
1124     /* Absolute file path */
1125     for (const char *ptr = path + 1;; ptr++)
1126     {
1127         size_t len = strcspn (ptr, DIR_SEP);
1128         char *component = encode_URI_bytes (ptr, len);
1129         if (component == NULL)
1130         {
1131             free (buf);
1132             return NULL;
1133         }
1134         char *uri;
1135         int val = asprintf (&uri, "%s/%s", buf, component);
1136         free (component);
1137         free (buf);
1138         if (val == -1)
1139             return NULL;
1140         buf = uri;
1141         ptr += len;
1142         if (*ptr == '\0')
1143             return buf;
1144     }
1145 }
1146
1147 /**
1148  * Tries to convert an URI to a local (UTF-8-encoded) file path.
1149  * @param url URI to convert
1150  * @return NULL on error, a nul-terminated string otherwise
1151  * (use free() to release it)
1152  */
1153 char *make_path (const char *url)
1154 {
1155     char *ret = NULL;
1156     char *end;
1157
1158     char *path = strstr (url, "://");
1159     if (path == NULL)
1160         return NULL; /* unsupported scheme or invalid syntax */
1161
1162     end = memchr (url, '/', path - url);
1163     size_t schemelen = ((end != NULL) ? end : path) - url;
1164     path += 3; /* skip "://" */
1165
1166     /* Remove HTML anchor if present */
1167     end = strchr (path, '#');
1168     if (end)
1169         path = strndup (path, end - path);
1170     else
1171         path = strdup (path);
1172     if (unlikely(path == NULL))
1173         return NULL; /* boom! */
1174
1175     /* Decode path */
1176     decode_URI (path);
1177
1178     if (schemelen == 4 && !strncasecmp (url, "file", 4))
1179     {
1180 #if (DIR_SEP_CHAR != '/')
1181         for (char *p = strchr (path, '/'); p; p = strchr (p + 1, '/'))
1182             *p = DIR_SEP_CHAR;
1183 #endif
1184         /* Leading slash => local path */
1185         if (*path == DIR_SEP_CHAR)
1186 #if !defined (WIN32) || defined (UNDER_CE)
1187             return path;
1188 #else
1189             return memmove (path, path + 1, strlen (path + 1) + 1);
1190 #endif
1191
1192         /* Local path disguised as a remote one (MacOS X) */
1193         if (!strncasecmp (path, "localhost"DIR_SEP, 10))
1194             return memmove (path, path + 9, strlen (path + 9) + 1);
1195
1196 #ifdef WIN32
1197         if (*path && asprintf (&ret, "\\\\%s", path) == -1)
1198             ret = NULL;
1199 #endif
1200         /* non-local path :-( */
1201     }
1202     else
1203     if (schemelen == 2 && !strncasecmp (url, "fd", 2))
1204     {
1205         int fd = strtol (path, &end, 0);
1206
1207         if (*end)
1208             goto out;
1209
1210 #ifndef WIN32
1211         switch (fd)
1212         {
1213             case 0:
1214                 ret = strdup ("/dev/stdin");
1215                 break;
1216             case 1:
1217                 ret = strdup ("/dev/stdout");
1218                 break;
1219             case 2:
1220                 ret = strdup ("/dev/stderr");
1221                 break;
1222             default:
1223                 if (asprintf (&ret, "/dev/fd/%d", fd) == -1)
1224                     ret = NULL;
1225         }
1226 #else
1227         /* XXX: Does this work on WinCE? */
1228         if (fd < 2)
1229             ret = strdup ("CON");
1230         else
1231             ret = NULL;
1232 #endif
1233     }
1234
1235 out:
1236     free (path);
1237     return ret; /* unknown scheme */
1238 }