1 /*****************************************************************************
2 * strings.c: String related functions
3 *****************************************************************************
4 * Copyright (C) 2006 the VideoLAN team
5 * Copyright (C) 2008-2009 Rémi Denis-Courmont
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>
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.
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.
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 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
34 #include <vlc_common.h>
37 /* Needed by str_format_time */
41 /* Needed by str_format_meta */
42 #include <vlc_input.h>
44 #include <vlc_playlist.h>
47 #include <vlc_strings.h>
49 #include <vlc_charset.h>
52 * Decode encoded URI component. See also decode_URI().
53 * \return decoded duplicated string
55 char *decode_URI_duplicate( const char *psz )
57 char *psz_dup = strdup( psz );
58 decode_URI( psz_dup );
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).
69 * Note that URI encoding is different from Javascript escaping. Especially,
70 * white spaces and Unicode non-ASCII code points are encoded differently.
72 * \return psz on success, NULL if it was not properly encoded
74 char *decode_URI( char *psz )
76 unsigned char *in = (unsigned char *)psz, *out = in, c;
81 while( ( c = *in++ ) != '\0' )
89 if( ( ( hex[0] = *in++ ) == 0 )
90 || ( ( hex[1] = *in++ ) == 0 ) )
94 *out++ = (unsigned char)strtoul( hex, NULL, 0x10 );
98 case '+': /* This is HTTP forms, not URI decoding... */
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 ) )
115 static inline bool isurisafe( int c )
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 );
124 static char *encode_URI_bytes (const char *psz_uri, size_t len)
126 char *psz_enc = malloc (3 * len + 1), *out = psz_enc;
130 for (size_t i = 0; i < len; i++)
132 static const char hex[16] = "0123456789ABCDEF";
133 uint8_t c = *psz_uri;
137 /* This is URI encoding, not HTTP forms:
138 * Space is encoded as '%20', not '+'. */
142 *out++ = hex[c >> 4];
143 *out++ = hex[c & 0xf];
149 out = realloc (psz_enc, out - psz_enc);
150 return out ? out : psz_enc; /* realloc() can fail (safe) */
154 * Encodes an URI component (RFC3986 §2).
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?
160 * @return encoded string (must be free()'d), or NULL for ENOMEM.
162 char *encode_URI_component( const char *psz_uri )
164 return encode_URI_bytes (psz_uri, strlen (psz_uri));
168 static const struct xml_entity_s
173 /* Important: this list has to be in alphabetical order (psz_entity-wise) */
255 { "nbsp;", "\xc2\xa0" },
300 static int cmp_entity (const void *key, const void *elem)
302 const struct xml_entity_s *ent = elem;
303 const char *name = key;
305 return strncmp (name, ent->psz_entity, strlen (ent->psz_entity));
309 * Converts "<", ">" and "&" to "<", ">" and "&"
310 * \param string to convert
312 void resolve_xml_special_chars( char *psz_value )
314 char *p_pos = psz_value;
318 if( *psz_value == '&' )
320 if( psz_value[1] == '#' )
321 { /* &#xxx; Unicode code point */
323 unsigned long cp = strtoul( psz_value+2, &psz_end, 10 );
324 if( *psz_end == ';' )
326 psz_value = psz_end + 1;
328 (void)0; /* skip nuls */
335 /* Unicode code point outside ASCII.
336 * &#xxx; representation is longer than UTF-8 :) */
339 *p_pos++ = 0xC0 | (cp >> 6);
340 *p_pos = 0x80 | (cp & 0x3F);
345 *p_pos++ = 0xE0 | (cp >> 12);
346 *p_pos++ = 0x80 | ((cp >> 6) & 0x3F);
347 *p_pos = 0x80 | (cp & 0x3F);
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);
360 /* Invalid entity number */
366 { /* Well-known XML entity */
367 const struct xml_entity_s *ent;
369 ent = bsearch (psz_value + 1, xml_entities,
370 sizeof (xml_entities) / sizeof (*ent),
371 sizeof (*ent), cmp_entity);
374 size_t olen = strlen (ent->psz_char);
375 memcpy (p_pos, ent->psz_char, olen);
377 psz_value += strlen (ent->psz_entity) + 1;
399 * Converts '<', '>', '\"', '\'' and '&' to their html entities
400 * \param psz_content simple element content that is to be converted
402 char *convert_xml_special_chars( const char *psz_content )
404 assert( psz_content );
406 const size_t len = strlen( psz_content );
407 char *const psz_temp = malloc( 6 * len + 1 );
408 char *p_to = psz_temp;
410 if( psz_temp == NULL )
412 for( size_t i = 0; i < len; i++ )
415 char c = psz_content[i];
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;
428 p_to += sprintf( p_to, "&%s;", str );
432 p_to = realloc( psz_temp, p_to - psz_temp );
433 return p_to ? p_to : psz_temp; /* cannot fail */
436 /* Base64 encoding */
437 char *vlc_b64_encode_binary( const uint8_t *src, size_t i_src )
439 static const char b64[] =
440 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
442 char *ret = malloc( ( i_src + 4 ) * 4 / 3 );
450 /* pops (up to) 3 bytes of input, push 4 bytes */
455 *dst++ = b64[v >> 26];
461 *dst++ = b64[v >> 26];
466 v |= *src++ << 20; // 3/3
467 *dst++ = ( i_src >= 2 ) ? b64[v >> 26] : '='; // 3/4
471 *dst++ = ( i_src >= 3 ) ? b64[v >> 26] : '='; // 4/4
483 char *vlc_b64_encode( const char *src )
486 return vlc_b64_encode_binary( (const uint8_t*)src, strlen(src) );
488 return vlc_b64_encode_binary( (const uint8_t*)"", 0 );
491 /* Base64 decoding */
492 size_t vlc_b64_decode_binary_to_buffer( uint8_t *p_dst, size_t i_dst, const char *p_src )
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 */
512 uint8_t *p_start = p_dst;
513 uint8_t *p = (uint8_t *)p_src;
518 for( i_level = 0, i_last = 0; (size_t)( p_dst - p_start ) < i_dst && *p != '\0'; p++ )
520 const int c = b64[(unsigned int)*p];
530 *p_dst++ = ( i_last << 2 ) | ( ( c >> 4)&0x03 );
534 *p_dst++ = ( ( i_last << 4 )&0xf0 ) | ( ( c >> 2 )&0x0f );
538 *p_dst++ = ( ( i_last &0x03 ) << 6 ) | c;
544 return p_dst - p_start;
546 size_t vlc_b64_decode_binary( uint8_t **pp_dst, const char *psz_src )
548 const int i_src = strlen( psz_src );
551 *pp_dst = p_dst = malloc( i_src );
554 return vlc_b64_decode_binary_to_buffer( p_dst, i_src, psz_src );
556 char *vlc_b64_decode( const char *psz_src )
558 const int i_src = strlen( psz_src );
559 char *p_dst = malloc( i_src + 1 );
564 i_dst = vlc_b64_decode_binary_to_buffer( (uint8_t*)p_dst, i_src, psz_src );
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.
575 char *str_format_time( const char *tformat )
580 if (strcmp (tformat, "") == 0)
581 return strdup (""); /* corner case w.r.t. strftime() return value */
583 /* Get the current time. */
586 /* Convert it to local time representation. */
587 localtime_r( &curtime, &loctime );
588 for (size_t buflen = strlen (tformat) + 32;; buflen += 32)
590 char *str = malloc (buflen);
594 size_t len = strftime (str, buflen, tformat, &loctime);
597 char *ret = realloc (str, len + 1);
598 return ret ? ret : str; /* <- this cannot fail */
604 static void format_duration (char *buf, size_t len, int64_t duration)
609 duration /= CLOCK_FREQ;
610 d = lldiv (duration, 60);
612 d = lldiv (d.quot, 60);
613 snprintf (buf, len, "%02lld:%02d:%02d", d.quot, (int)d.rem, sec);
616 #define INSERT_STRING( string ) \
617 if( string != NULL ) \
619 int len = strlen( string ); \
620 dst = xrealloc( dst, i_size = i_size + len );\
621 memcpy( (dst+d), string, len ); \
625 else if( !b_empty_if_na ) \
631 /* same than INSERT_STRING, except that string won't be freed */
632 #define INSERT_STRING_NO_FREE( string ) \
634 int len = strlen( string ); \
635 dst = xrealloc( dst, i_size = i_size + len );\
636 memcpy( dst+d, string, len ); \
639 #undef str_format_meta
640 char *str_format_meta( vlc_object_t *p_object, const char *string )
642 const char *s = string;
643 bool b_is_format = false;
644 bool b_empty_if_na = false;
646 int i_size = strlen( string ) + 1; /* +1 to store '\0' */
647 char *dst = strdup( string );
648 if( !dst ) return NULL;
651 input_thread_t *p_input = playlist_CurrentInput( pl_Get(p_object) );
652 input_item_t *p_item = NULL;
655 p_item = input_GetItem(p_input);
667 INSERT_STRING( input_item_GetArtist( p_item ) );
673 INSERT_STRING( input_item_GetAlbum( p_item ) );
679 INSERT_STRING( input_item_GetCopyright( p_item ) );
685 INSERT_STRING( input_item_GetDescription( p_item ) );
691 INSERT_STRING( input_item_GetEncodedBy( p_item ) );
695 if( p_item && p_item->p_stats )
697 vlc_mutex_lock( &p_item->p_stats->lock );
698 snprintf( buf, 10, "%d",
699 p_item->p_stats->i_displayed_pictures );
700 vlc_mutex_unlock( &p_item->p_stats->lock );
703 strcpy( buf, b_empty_if_na ? "" : "-" );
704 INSERT_STRING_NO_FREE( buf );
709 INSERT_STRING( input_item_GetGenre( p_item ) );
715 INSERT_STRING( input_item_GetLanguage( p_item ) );
721 INSERT_STRING( input_item_GetTrackNum( p_item ) );
727 INSERT_STRING( input_item_GetNowPlaying( p_item ) );
733 INSERT_STRING( input_item_GetRating( p_item ) );
740 lang = var_GetNonEmptyString( p_input, "sub-language" );
742 lang = strdup( b_empty_if_na ? "" : "-" );
743 INSERT_STRING( lang );
749 INSERT_STRING( input_item_GetTitle( p_item ) );
755 INSERT_STRING( input_item_GetURL( p_item ) );
761 INSERT_STRING( input_item_GetDate( p_item ) );
767 snprintf( buf, 10, "%"PRId64,
768 var_GetInteger( p_input, "bit-rate" )/1000 );
771 strcpy( buf, b_empty_if_na ? "" : "-" );
772 INSERT_STRING_NO_FREE( buf );
777 snprintf( buf, 10, "%"PRId64,
778 var_GetInteger( p_input, "chapter" ) );
781 strcpy( buf, b_empty_if_na ? "" : "-" );
782 INSERT_STRING_NO_FREE( buf );
787 mtime_t i_duration = input_item_GetDuration( p_item );
788 format_duration (buf, sizeof (buf), i_duration);
791 strcpy( buf, b_empty_if_na ? "" : "--:--:--" );
792 INSERT_STRING_NO_FREE( buf );
797 INSERT_STRING( input_item_GetURI( p_item ) );
803 snprintf( buf, 10, "%"PRId64,
804 var_GetInteger( p_input, "title" ) );
807 strcpy( buf, b_empty_if_na ? "" : "-" );
808 INSERT_STRING_NO_FREE( buf );
811 if( p_item && p_input )
813 mtime_t i_duration = input_item_GetDuration( p_item );
814 int64_t i_time = var_GetTime( p_input, "time" );
815 format_duration( buf, sizeof(buf),
816 i_duration - i_time );
819 strcpy( buf, b_empty_if_na ? "" : "--:--:--" );
820 INSERT_STRING_NO_FREE( buf );
825 INSERT_STRING( input_item_GetName( p_item ) );
832 lang = var_GetNonEmptyString( p_input,
835 lang = strdup( b_empty_if_na ? "" : "-" );
836 INSERT_STRING( lang );
842 snprintf( buf, 10, "%2.1lf",
843 var_GetFloat( p_input, "position" ) * 100. );
847 snprintf( buf, 10, b_empty_if_na ? "" : "--.-%%" );
849 INSERT_STRING_NO_FREE( buf );
854 float f = var_GetFloat( p_input, "rate" );
855 snprintf( buf, 10, "%.3f", f );
858 strcpy( buf, b_empty_if_na ? "" : "-" );
859 INSERT_STRING_NO_FREE( buf );
864 int r = var_GetInteger( p_input, "sample-rate" );
865 snprintf( buf, 10, "%d.%d", r/1000, (r/100)%10 );
868 strcpy( buf, b_empty_if_na ? "" : "-" );
869 INSERT_STRING_NO_FREE( buf );
874 int64_t i_time = var_GetTime( p_input, "time" );
875 format_duration( buf, sizeof(buf), i_time );
878 strcpy( buf, b_empty_if_na ? "" : "--:--:--" );
879 INSERT_STRING_NO_FREE( buf );
884 INSERT_STRING( input_item_GetPublisher( p_item ) );
889 audio_volume_t volume;
890 aout_VolumeGet( p_object, &volume );
891 snprintf( buf, 10, "%d", volume );
892 INSERT_STRING_NO_FREE( buf );
901 b_empty_if_na = true;
915 b_empty_if_na = false;
927 vlc_object_release( p_input );
932 #undef INSERT_STRING_NO_FREE
936 * Apply str format time and str format meta
938 char *str_format( vlc_object_t *p_this, const char *psz_src )
940 char *psz_buf1, *psz_buf2;
941 psz_buf1 = str_format_time( psz_src );
942 psz_buf2 = str_format_meta( p_this, psz_buf1 );
948 * Remove forbidden characters from filenames (including slashes)
950 void filename_sanitize( char *str )
953 char *str_base = str;
956 if( *str == '.' && (str[1] == '\0' || (str[1] == '.' && str[2] == '\0' ) ) )
967 // Change leading spaces into underscores
968 while( *str && *str == ' ' )
977 #if defined( __APPLE__ )
979 #elif defined( WIN32 )
995 // Change trailing spaces into underscores
997 while( str != str_base )
1007 * Remove forbidden characters from full paths (leaves slashes)
1009 void path_sanitize( char *str )
1012 /* check drive prefix if path is absolute */
1013 if( (((unsigned char)(str[0] - 'A') < 26)
1014 || ((unsigned char)(str[0] - 'a') < 26)) && (':' == str[1]) )
1019 #if defined( __APPLE__ )
1022 #elif defined( WIN32 )
1023 if( strchr( "*\"?:|<>", *str ) )
1026 *str = DIR_SEP_CHAR;
1032 #include <vlc_url.h>
1035 * Convert a file path to an URI.
1036 * If already an URI, return a copy of the string.
1037 * @param path path to convert (or URI to copy)
1038 * @param scheme URI scheme to use (default is auto: "file", "fd" or "smb")
1039 * @return a nul-terminated URI string (use free() to release it),
1040 * or NULL in case of error
1042 char *make_URI (const char *path, const char *scheme)
1046 if (scheme == NULL && !strcmp (path, "-"))
1047 return strdup ("fd://0"); // standard input
1048 if (strstr (path, "://") != NULL)
1049 return strdup (path); /* Already an URI */
1050 /* Note: VLC cannot handle URI schemes without double slash after the
1051 * scheme name (such as mailto: or news:). */
1055 if (isalpha (path[0]) && (path[1] == ':'))
1057 if (asprintf (&buf, "%s:///%c:", scheme, path[0]) == -1)
1063 if (!strncmp (path, "\\\\", 2))
1064 { /* Windows UNC paths */
1067 return NULL; /* remote files not supported */
1069 /* \\host\share\path -> smb://host/share/path */
1070 if (strchr (path + 2, '\\') != NULL)
1071 { /* Convert backslashes to slashes */
1072 char *dup = strdup (path);
1075 for (size_t i = 2; dup[i]; i++)
1077 dup[i] = DIR_SEP_CHAR;
1079 char *ret = make_URI (dup, scheme);
1083 # define SMB_SCHEME "smb"
1085 /* \\host\share\path -> file://host/share/path */
1086 # define SMB_SCHEME "file"
1088 size_t hostlen = strcspn (path + 2, DIR_SEP);
1090 buf = malloc (sizeof (SMB_SCHEME) + 3 + hostlen);
1092 snprintf (buf, sizeof (SMB_SCHEME) + 3 + hostlen,
1093 SMB_SCHEME"://%s", path + 2);
1094 path += 2 + hostlen;
1097 if (path[0] != DIR_SEP_CHAR)
1098 { /* Relative path: prepend the current working directory */
1101 if (getcwd (cwd, sizeof (cwd)) == NULL) /* FIXME: UTF8? */
1103 if (asprintf (&buf, "%s/%s", cwd, path) == -1)
1105 char *ret = make_URI (buf, scheme);
1110 if (asprintf (&buf, "%s://", scheme ? scheme : "file") == -1)
1115 assert (path[0] == DIR_SEP_CHAR);
1117 /* Absolute file path */
1118 for (const char *ptr = path + 1;; ptr++)
1120 size_t len = strcspn (ptr, DIR_SEP);
1121 char *component = encode_URI_bytes (ptr, len);
1122 if (component == NULL)
1128 int val = asprintf (&uri, "%s/%s", buf, component);
1141 * Tries to convert an URI to a local (UTF-8-encoded) file path.
1142 * @param url URI to convert
1143 * @return NULL on error, a nul-terminated string otherwise
1144 * (use free() to release it)
1146 char *make_path (const char *url)
1151 char *path = strstr (url, "://");
1153 return NULL; /* unsupported scheme or invalid syntax */
1155 end = memchr (url, '/', path - url);
1156 size_t schemelen = ((end != NULL) ? end : path) - url;
1157 path += 3; /* skip "://" */
1159 /* Remove HTML anchor if present */
1160 end = strchr (path, '#');
1162 path = strndup (path, end - path);
1164 path = strdup (path);
1165 if (unlikely(path == NULL))
1166 return NULL; /* boom! */
1171 if (schemelen == 4 && !strncasecmp (url, "file", 4))
1173 #if (DIR_SEP_CHAR != '/')
1174 for (char *p = strchr (path, '/'); p; p = strchr (p + 1, '/'))
1177 /* Leading slash => local path */
1178 if (*path == DIR_SEP_CHAR)
1179 #if !defined (WIN32) || defined (UNDER_CE)
1182 return memmove (path, path + 1, strlen (path + 1) + 1);
1185 /* Local path disguised as a remote one (MacOS X) */
1186 if (!strncasecmp (path, "localhost"DIR_SEP, 10))
1187 return memmove (path, path + 9, strlen (path + 9) + 1);
1190 if (*path && asprintf (&ret, "\\\\%s", path) == -1)
1193 /* non-local path :-( */
1196 if (schemelen == 2 && !strncasecmp (url, "fd", 2))
1198 int fd = strtol (path, &end, 0);
1207 ret = strdup ("/dev/stdin");
1210 ret = strdup ("/dev/stdout");
1213 ret = strdup ("/dev/stderr");
1216 if (asprintf (&ret, "/dev/fd/%d", fd) == -1)
1220 /* XXX: Does this work on WinCE? */
1222 ret = strdup ("CON");
1230 return ret; /* unknown scheme */