]> git.sesse.net Git - vlc/blob - src/text/strings.c
De-inline base64 decoder
[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 #include <vlc/vlc.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <assert.h>
34
35 /* Needed by str_format_time */
36 #include <time.h>
37
38 /* Needed by str_format_meta */
39 #include <vlc_input.h>
40 #include <vlc_meta.h>
41 #include <vlc_playlist.h>
42 #include <vlc_aout.h>
43
44 #include <vlc_strings.h>
45 #include <vlc_url.h>
46 #include <vlc_charset.h>
47
48 /**
49  * Unescape URI encoded string
50  * \return decoded duplicated string
51  */
52 char *unescape_URI_duplicate( const char *psz )
53 {
54     char *psz_dup = strdup( psz );
55     unescape_URI( psz_dup );
56     return psz_dup;
57 }
58
59 /**
60  * Unescape URI encoded string in place
61  * \return nothing
62  */
63 void unescape_URI( char *psz )
64 {
65     unsigned char *in = (unsigned char *)psz, *out = in, c;
66
67     while( ( c = *in++ ) != '\0' )
68     {
69         switch( c )
70         {
71             case '%':
72             {
73                 char val[5], *pval = val;
74                 unsigned long cp;
75
76                 switch( c = *in++ )
77                 {
78                     case '\0':
79                         return;
80
81                     case 'u':
82                     case 'U':
83                         if( ( *pval++ = *in++ ) == '\0' )
84                             return;
85                         if( ( *pval++ = *in++ ) == '\0' )
86                             return;
87                         c = *in++;
88
89                     default:
90                         *pval++ = c;
91                         if( ( *pval++ = *in++ ) == '\0' )
92                             return;
93                         *pval = '\0';
94                 }
95
96                 cp = strtoul( val, NULL, 0x10 );
97                 if( cp < 0x80 )
98                     *out++ = cp;
99                 else
100                 if( cp < 0x800 )
101                 {
102                     *out++ = (( cp >>  6)         | 0xc0);
103                     *out++ = (( cp        & 0x3f) | 0x80);
104                 }
105                 else
106                 {
107                     assert( cp < 0x10000 );
108                     *out++ = (( cp >> 12)         | 0xe0);
109                     *out++ = (((cp >>  6) & 0x3f) | 0x80);
110                     *out++ = (( cp        & 0x3f) | 0x80);
111                 }
112                 break;
113             }
114
115             /* + is not a special case - it means plus, not space. */
116
117             default:
118                 /* Inserting non-ASCII or non-printable characters is unsafe,
119                  * and no sane browser will send these unencoded */
120                 if( ( c < 32 ) || ( c > 127 ) )
121                     *out++ = '?';
122                 else
123                     *out++ = c;
124         }
125     }
126     *out = '\0';
127 }
128
129 /**
130  * Decode encoded URI string
131  * \return decoded duplicated string
132  */
133 char *decode_URI_duplicate( const char *psz )
134 {
135     char *psz_dup = strdup( psz );
136     decode_URI( psz_dup );
137     return psz_dup;
138 }
139
140 /**
141  * Decode encoded URI string in place
142  * \return nothing
143  */
144 void decode_URI( char *psz )
145 {
146     unsigned char *in = (unsigned char *)psz, *out = in, c;
147
148     while( ( c = *in++ ) != '\0' )
149     {
150         switch( c )
151         {
152             case '%':
153             {
154                 char hex[3];
155
156                 if( ( ( hex[0] = *in++ ) == 0 )
157                  || ( ( hex[1] = *in++ ) == 0 ) )
158                     return;
159
160                 hex[2] = '\0';
161                 *out++ = (unsigned char)strtoul( hex, NULL, 0x10 );
162                 break;
163             }
164
165             case '+':
166                 *out++ = ' ';
167                 break;
168
169             default:
170                 /* Inserting non-ASCII or non-printable characters is unsafe,
171                  * and no sane browser will send these unencoded */
172                 if( ( c < 32 ) || ( c > 127 ) )
173                     *out++ = '?';
174                 else
175                     *out++ = c;
176         }
177     }
178     *out = '\0';
179     EnsureUTF8( psz );
180 }
181
182 static inline int isurlsafe( int c )
183 {
184     return ( (unsigned char)( c - 'a' ) < 26 )
185             || ( (unsigned char)( c - 'A' ) < 26 )
186             || ( (unsigned char)( c - '0' ) < 10 )
187         /* Hmm, we should not encode character that are allowed in URLs
188          * (even if they are not URL-safe), nor URL-safe characters.
189          * We still encode some of them because of Microsoft's crap browser.
190          */
191             || ( strchr( "-_.", c ) != NULL );
192 }
193
194 static inline char url_hexchar( int c )
195 {
196     return ( c < 10 ) ? c + '0' : c + 'A' - 10;
197 }
198
199 /**
200  * encode_URI_component
201  * Encodes an URI component.
202  *
203  * @param psz_url nul-terminated UTF-8 representation of the component.
204  * Obviously, you can't pass an URI containing a nul character, but you don't
205  * want to do that, do you?
206  *
207  * @return encoded string (must be free()'d)
208  */
209 char *encode_URI_component( const char *psz_url )
210 {
211     char psz_enc[3 * strlen( psz_url ) + 1], *out = psz_enc;
212     const uint8_t *in;
213
214     for( in = (const uint8_t *)psz_url; *in; in++ )
215     {
216         uint8_t c = *in;
217
218         if( isurlsafe( c ) )
219             *out++ = (char)c;
220         else
221         if ( c == ' ')
222             *out++ = '+';
223         else
224         {
225             *out++ = '%';
226             *out++ = url_hexchar( c >> 4 );
227             *out++ = url_hexchar( c & 0xf );
228         }
229     }
230     *out++ = '\0';
231
232     return strdup( psz_enc );
233 }
234
235 /**
236  * Converts "&lt;", "&gt;" and "&amp;" to "<", ">" and "&"
237  * \param string to convert
238  */
239 void resolve_xml_special_chars( char *psz_value )
240 {
241     char *p_pos = psz_value;
242
243     while ( *psz_value )
244     {
245         if( !strncmp( psz_value, "&lt;", 4 ) )
246         {
247             *p_pos = '<';
248             psz_value += 4;
249         }
250         else if( !strncmp( psz_value, "&gt;", 4 ) )
251         {
252             *p_pos = '>';
253             psz_value += 4;
254         }
255         else if( !strncmp( psz_value, "&amp;", 5 ) )
256         {
257             *p_pos = '&';
258             psz_value += 5;
259         }
260         else if( !strncmp( psz_value, "&quot;", 6 ) )
261         {
262             *p_pos = '\"';
263             psz_value += 6;
264         }
265         else if( !strncmp( psz_value, "&#039;", 6 ) )
266         {
267             *p_pos = '\'';
268             psz_value += 6;
269         }
270         else
271         {
272             *p_pos = *psz_value;
273             psz_value++;
274         }
275
276         p_pos++;
277     }
278
279     *p_pos = '\0';
280 }
281
282 /**
283  * Converts '<', '>', '\"', '\'' and '&' to their html entities
284  * \param psz_content simple element content that is to be converted
285  */
286 char *convert_xml_special_chars( const char *psz_content )
287 {
288     char *psz_temp = malloc( 6 * strlen( psz_content ) + 1 );
289     const char *p_from = psz_content;
290     char *p_to   = psz_temp;
291
292     while ( *p_from )
293     {
294         if ( *p_from == '<' )
295         {
296             strcpy( p_to, "&lt;" );
297             p_to += 4;
298         }
299         else if ( *p_from == '>' )
300         {
301             strcpy( p_to, "&gt;" );
302             p_to += 4;
303         }
304         else if ( *p_from == '&' )
305         {
306             strcpy( p_to, "&amp;" );
307             p_to += 5;
308         }
309         else if( *p_from == '\"' )
310         {
311             strcpy( p_to, "&quot;" );
312             p_to += 6;
313         }
314         else if( *p_from == '\'' )
315         {
316             strcpy( p_to, "&#039;" );
317             p_to += 6;
318         }
319         else
320         {
321             *p_to = *p_from;
322             p_to++;
323         }
324         p_from++;
325     }
326     *p_to = '\0';
327
328     return psz_temp;
329 }
330
331 /* Base64 encoding */
332 char *vlc_b64_encode( const char *src )
333 {
334     static const char b64[] =
335            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
336     size_t len = strlen( src );
337     const uint8_t *in = (const uint8_t *)src;
338
339     char *ret;
340     char *dst = (char *)malloc( ( len + 4 ) * 4 / 3 );
341     if( dst == NULL )
342         return NULL;
343
344     ret = dst;
345
346     while( len > 0 )
347     {
348         /* pops (up to) 3 bytes of input, push 4 bytes */
349         uint32_t v = *in++ << 24; // 1/3
350         *dst++ = b64[v >> 26]; // 1/4
351         v = v << 6;
352
353         if( len >= 2 )
354             v |= *in++ << 22; // 2/3
355         *dst++ = b64[v >> 26]; // 2/4
356         v = v << 6;
357
358         if( len >= 3 )
359             v |= *in++ << 20; // 3/3
360         *dst++ = ( len >= 2 ) ? b64[v >> 26] : '='; // 3/4
361         v = v << 6;
362
363         *dst++ = ( len >= 3 ) ? b64[v >> 26] : '='; // 4/4
364
365         len--;
366         if( len > 0 )
367         {
368             len--;
369             if( len > 0 )
370                 len--;
371         }
372     }
373
374     *dst = '\0';
375
376     return ret;
377 }
378
379 /****************************************************************************
380  * String formating functions
381  ****************************************************************************/
382 char *str_format_time( const char *tformat )
383 {
384     char buffer[255];
385     time_t curtime;
386 #if defined(HAVE_LOCALTIME_R)
387     struct tm loctime;
388 #else
389     struct tm *loctime;
390 #endif
391
392     /* Get the current time.  */
393     curtime = time( NULL );
394
395     /* Convert it to local time representation.  */
396 #if defined(HAVE_LOCALTIME_R)
397     localtime_r( &curtime, &loctime );
398     strftime( buffer, 255, tformat, &loctime );
399 #else
400     loctime = localtime( &curtime );
401     strftime( buffer, 255, tformat, loctime );
402 #endif
403     return strdup( buffer );
404 }
405
406 #define INSERT_STRING( check, string )                              \
407                     if( check && string )                           \
408                     {                                               \
409                         int len = strlen( string );                 \
410                         dst = realloc( dst,                         \
411                                        i_size = i_size + len + 1 ); \
412                         strncpy( d, string, len+1 );                \
413                         d += len;                                   \
414                     }                                               \
415                     else                                            \
416                     {                                               \
417                         *d = '-';                                   \
418                         d++;                                        \
419                     }
420 char *__str_format_meta( vlc_object_t *p_object, const char *string )
421 {
422     const char *s = string;
423     char *dst = malloc( 1000 );
424     char *d = dst;
425     int b_is_format = 0;
426     char buf[10];
427     int i_size = strlen( string );
428
429     playlist_t *p_playlist = pl_Yield( p_object );
430     input_thread_t *p_input = p_playlist->p_input;
431     input_item_t *p_item = NULL;
432     pl_Release( p_object );
433     if( p_input )
434     {
435         vlc_object_yield( p_input );
436         p_item = input_GetItem(p_input);
437         if( p_item )
438             vlc_mutex_lock( &p_item->lock );
439     }
440
441     sprintf( dst, string );
442
443     while( *s )
444     {
445         if( b_is_format )
446         {
447             switch( *s )
448             {
449                 case 'a':
450                     INSERT_STRING( p_item && p_item->p_meta,
451                                    p_item->p_meta->psz_artist );
452                     break;
453                 case 'b':
454                     INSERT_STRING( p_item && p_item->p_meta,
455                                    p_item->p_meta->psz_album );
456                     break;
457                 case 'c':
458                     INSERT_STRING( p_item && p_item->p_meta,
459                                    p_item->p_meta->psz_copyright );
460                     break;
461                 case 'd':
462                     INSERT_STRING( p_item && p_item->p_meta,
463                                    p_item->p_meta->psz_description );
464                     break;
465                 case 'e':
466                     INSERT_STRING( p_item && p_item->p_meta,
467                                    p_item->p_meta->psz_encodedby );
468                     break;
469                 case 'g':
470                     INSERT_STRING( p_item && p_item->p_meta,
471                                    p_item->p_meta->psz_genre );
472                     break;
473                 case 'l':
474                     INSERT_STRING( p_item && p_item->p_meta,
475                                    p_item->p_meta->psz_language );
476                     break;
477                 case 'n':
478                     INSERT_STRING( p_item && p_item->p_meta,
479                                    p_item->p_meta->psz_tracknum );
480                     break;
481                 case 'p':
482                     INSERT_STRING( p_item && p_item->p_meta,
483                                    p_item->p_meta->psz_nowplaying );
484                     break;
485                 case 'r':
486                     INSERT_STRING( p_item && p_item->p_meta,
487                                    p_item->p_meta->psz_rating );
488                     break;
489                 case 's':
490                 {
491                     char *lang;
492                     if( p_input )
493                     {
494                         lang = var_GetString( p_input, "sub-language" );
495                     }
496                     else
497                     {
498                         lang = strdup( "-" );
499                     }
500                     INSERT_STRING( 1, lang );
501                     free( lang );
502                     break;
503                 }
504                 case 't':
505                     INSERT_STRING( p_item && p_item->p_meta,
506                                    p_item->p_meta->psz_title );
507                     break;
508                 case 'u':
509                     INSERT_STRING( p_item && p_item->p_meta,
510                                    p_item->p_meta->psz_url );
511                     break;
512                 case 'A':
513                     INSERT_STRING( p_item && p_item->p_meta,
514                                    p_item->p_meta->psz_date );
515                     break;
516                 case 'B':
517                     if( p_input )
518                     {
519                         snprintf( buf, 10, "%d",
520                                   var_GetInteger( p_input, "bit-rate" )/1000 );
521                     }
522                     else
523                     {
524                         sprintf( buf, "-" );
525                     }
526                     INSERT_STRING( 1, buf );
527                     break;
528                 case 'C':
529                     if( p_input )
530                     {
531                         snprintf( buf, 10, "%d",
532                                   var_GetInteger( p_input, "chapter" ) );
533                     }
534                     else
535                     {
536                         sprintf( buf, "-" );
537                     }
538                     INSERT_STRING( 1, buf );
539                     break;
540                 case 'D':
541                     if( p_item )
542                     {
543                         sprintf( buf, "%02d:%02d:%02d",
544                                  (int)(p_item->i_duration/(3600000000)),
545                                  (int)((p_item->i_duration/(60000000))%60),
546                                  (int)((p_item->i_duration/1000000)%60) );
547                     }
548                     else
549                     {
550                         sprintf( buf, "--:--:--" );
551                     }
552                     INSERT_STRING( 1, buf );
553                     break;
554                 case 'F':
555                     INSERT_STRING( p_item, p_item->psz_uri );
556                     break;
557                 case 'I':
558                     if( p_input )
559                     {
560                         snprintf( buf, 10, "%d",
561                                   var_GetInteger( p_input, "title" ) );
562                     }
563                     else
564                     {
565                         sprintf( buf, "-" );
566                     }
567                     INSERT_STRING( 1, buf );
568                     break;
569                 case 'L':
570                     if( p_item && p_input )
571                     {
572                         sprintf( buf, "%02d:%02d:%02d",
573                      (int)((p_item->i_duration-p_input->i_time)/(3600000000)),
574                      (int)(((p_item->i_duration-p_input->i_time)/(60000000))%60),
575                      (int)(((p_item->i_duration-p_input->i_time)/1000000)%60) );
576                     }
577                     else
578                     {
579                         sprintf( buf, "--:--:--" );
580                     }
581                     INSERT_STRING( 1, buf );
582                     break;
583                 case 'N':
584                     INSERT_STRING( p_item, p_item->psz_name );
585                     break;
586                 case 'O':
587                 {
588                     char *lang;
589                     if( p_input )
590                     {
591                         lang = var_GetString( p_input, "audio-language" );
592                     }
593                     else
594                     {
595                         lang = strdup( "-" );
596                     }
597                     INSERT_STRING( 1, lang );
598                     free( lang );
599                     break;
600                 }
601                 case 'P':
602                     if( p_input )
603                     {
604                         snprintf( buf, 10, "%2.1lf",
605                                   var_GetFloat( p_input, "position" ) * 100. );
606                     }
607                     else
608                     {
609                         sprintf( buf, "--.-%%" );
610                     }
611                     INSERT_STRING( 1, buf );
612                     break;
613                 case 'R':
614                     if( p_input )
615                     {
616                         int r = var_GetInteger( p_input, "rate" );
617                         snprintf( buf, 10, "%d.%d", r/1000, r%1000 );
618                     }
619                     else
620                     {
621                         sprintf( buf, "-" );
622                     }
623                     INSERT_STRING( 1, buf );
624                     break;
625                 case 'S':
626                     if( p_input )
627                     {
628                         int r = var_GetInteger( p_input, "sample-rate" );
629                         snprintf( buf, 10, "%d.%d", r/1000, (r/100)%10 );
630                     }
631                     else
632                     {
633                         sprintf( buf, "-" );
634                     }
635                     INSERT_STRING( 1, buf );
636                     break;
637                 case 'T':
638                     if( p_input )
639                     {
640                         sprintf( buf, "%02d:%02d:%02d",
641                                  (int)(p_input->i_time/(3600000000)),
642                                  (int)((p_input->i_time/(60000000))%60),
643                                  (int)((p_input->i_time/1000000)%60) );
644                     }
645                     else
646                     {
647                         sprintf( buf, "--:--:--" );
648                     }
649                     INSERT_STRING( 1, buf );
650                     break;
651                 case 'U':
652                     INSERT_STRING( p_item && p_item->p_meta,
653                                    p_item->p_meta->psz_publisher );
654                     break;
655                 case 'V':
656                 {
657                     audio_volume_t volume;
658                     aout_VolumeGet( p_object, &volume );
659                     snprintf( buf, 10, "%d", volume );
660                     INSERT_STRING( 1, buf );
661                     break;
662                 }
663                 case '_':
664                     *d = '\n';
665                     d++;
666                     break;
667
668                 default:
669                     *d = *s;
670                     d++;
671                     break;
672             }
673             b_is_format = 0;
674         }
675         else if( *s == '$' )
676         {
677             b_is_format = 1;
678         }
679         else
680         {
681             *d = *s;
682             d++;
683         }
684         s++;
685     }
686     *d = '\0';
687
688     if( p_input )
689     {
690         vlc_object_release( p_input );
691         if( p_item )
692             vlc_mutex_unlock( &p_item->lock );
693     }
694
695     return dst;
696 }
697
698 /**
699  * Apply str format time and str format meta
700  */
701 char *__str_format( vlc_object_t *p_this, const char *psz_src )
702 {
703     char *psz_buf1, *psz_buf2;
704     psz_buf1 = str_format_time( psz_src );
705     psz_buf2 = str_format_meta( p_this, psz_buf1 );
706     free( psz_buf1 );
707     return psz_buf2;
708 }
709
710 /**
711  * Remove forbidden characters from filenames (including slashes)
712  */
713 void filename_sanitize( char *str )
714 {
715     while( *str )
716     {
717         switch( *str )
718         {
719             case '/':
720 #ifdef WIN32
721             case '*':
722             case '.':
723             case '"':
724             case '\\':
725             case '[':
726             case ']':
727             case ':':
728             case ';':
729             case '|':
730             case '=':
731 #endif
732                 *str = '_';
733         }
734         str++;
735     }
736 }
737
738 /**
739  * Remove forbidden characters from full paths (leaves slashes)
740  */
741 void path_sanitize( char *str )
742 {
743     while( *str )
744     {
745         switch( *str )
746         {
747 #ifdef WIN32
748             case '*':
749             case '.':
750             case '"':
751             case '[':
752             case ']':
753             case ':':
754             case ';':
755             case '|':
756             case '=':
757 #endif
758                 *str = '_';
759         }
760         str++;
761     }
762 }