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