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