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