]> git.sesse.net Git - vlc/blob - modules/control/http/util.c
7f21ebc93a683ef798e97d7266fa4ff5b792e4e7
[vlc] / modules / control / http / util.c
1 /*****************************************************************************
2  * util.c : Utility functions for HTTP interface
3  *****************************************************************************
4  * Copyright (C) 2001-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@netcourrier.com>
8  *          Laurent Aimar <fenrir@via.ecp.fr>
9  *          Christophe Massiot <massiot@via.ecp.fr>
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 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <vlc_common.h>
31 #include "http.h"
32 #include <vlc_strings.h>
33 #include <errno.h>
34
35 /****************************************************************************
36  * File and directory functions
37  ****************************************************************************/
38
39 /* ToUrl: create a good name for an url from filename */
40 char *FileToUrl( char *name, bool *pb_index )
41 {
42     char *url, *p;
43
44     url = p = malloc( strlen( name ) + 1 );
45
46     *pb_index = false;
47     if( !url || !p )
48     {
49         return NULL;
50     }
51
52 #ifdef WIN32
53     while( *name == '\\' || *name == '/' )
54 #else
55     while( *name == '/' )
56 #endif
57     {
58         name++;
59     }
60
61     *p++ = '/';
62     strcpy( p, name );
63
64 #ifdef WIN32
65     /* convert '\\' into '/' */
66     name = p;
67     while( *name )
68     {
69         if( *name == '\\' )
70             *name = '/';
71         name++;
72     }
73 #endif
74
75     /* index.* -> / */
76     if( ( p = strrchr( url, '/' ) ) != NULL )
77     {
78         if( !strncmp( p, "/index.", 7 ) )
79         {
80             p[1] = '\0';
81             *pb_index = true;
82         }
83     }
84     return url;
85 }
86
87 /* Load a file */
88 int FileLoad( FILE *f, char **pp_data, int *pi_data )
89 {
90     int i_read;
91
92     /* just load the file */
93     *pi_data = 0;
94     *pp_data = xmalloc( 1025 );  /* +1 for \0 */
95
96     while( ( i_read = fread( &(*pp_data)[*pi_data], 1, 1024, f ) ) == 1024 )
97     {
98         *pi_data += 1024;
99         *pp_data = xrealloc( *pp_data, *pi_data  + 1025 );
100     }
101     if( i_read > 0 )
102     {
103         *pi_data += i_read;
104     }
105     (*pp_data)[*pi_data] = '\0';
106
107     return VLC_SUCCESS;
108 }
109
110 /* Parse a directory and recursively add files */
111 int ParseDirectory( intf_thread_t *p_intf, char *psz_root,
112                         char *psz_dir )
113 {
114     intf_sys_t     *p_sys = p_intf->p_sys;
115     char           dir[MAX_DIR_SIZE];
116     DIR           *p_dir;
117     vlc_acl_t     *p_acl;
118     FILE          *file;
119
120     char          *user = NULL;
121     char          *password = NULL;
122
123     int           i_dirlen;
124
125     if( ( p_dir = utf8_opendir( psz_dir ) ) == NULL )
126     {
127         if( errno != ENOENT && errno != ENOTDIR )
128             msg_Err( p_intf, "cannot open directory (%s)", psz_dir );
129         return VLC_EGENERIC;
130     }
131
132     i_dirlen = strlen( psz_dir );
133     if( i_dirlen + 10 > MAX_DIR_SIZE )
134     {
135         msg_Warn( p_intf, "skipping too deep directory (%s)", psz_dir );
136         closedir( p_dir );
137         return 0;
138     }
139
140     msg_Dbg( p_intf, "dir=%s", psz_dir );
141
142     snprintf( dir, sizeof( dir ), "%s"DIR_SEP".access", psz_dir );
143     if( ( file = utf8_fopen( dir, "r" ) ) != NULL )
144     {
145         char line[1024];
146         int  i_size;
147
148         msg_Dbg( p_intf, "find .access in dir=%s", psz_dir );
149
150         i_size = fread( line, 1, 1023, file );
151         if( i_size > 0 )
152         {
153             char *p;
154             while( i_size > 0 && ( line[i_size-1] == '\n' ||
155                    line[i_size-1] == '\r' ) )
156             {
157                 i_size--;
158             }
159
160             line[i_size] = '\0';
161
162             p = strchr( line, ':' );
163             if( p )
164             {
165                 *p++ = '\0';
166                 user = strdup( line );
167                 password = strdup( p );
168             }
169         }
170         msg_Dbg( p_intf, "using user=%s (read=%d)", user, i_size );
171
172         fclose( file );
173     }
174
175     snprintf( dir, sizeof( dir ), "%s"DIR_SEP".hosts", psz_dir );
176     p_acl = ACL_Create( p_intf, false );
177     if( ACL_LoadFile( p_acl, dir ) )
178     {
179         ACL_Destroy( p_acl );
180
181         struct stat st;
182         if( utf8_stat( dir, &st ) == 0 )
183         {
184             free( user );
185             free( password );
186             closedir( p_dir );
187             return VLC_EGENERIC;
188         }
189         p_acl = NULL;
190     }
191
192     for( ;; )
193     {
194         char *psz_filename;
195         /* parse psz_src dir */
196         if( ( psz_filename = utf8_readdir( p_dir ) ) == NULL )
197         {
198             break;
199         }
200
201         if( ( psz_filename[0] == '.' )
202          || ( i_dirlen + strlen( psz_filename ) > MAX_DIR_SIZE ) )
203         {
204             free( psz_filename );
205             continue;
206         }
207
208         snprintf( dir, sizeof( dir ), "%s"DIR_SEP"%s", psz_dir, psz_filename );
209         free( psz_filename );
210
211         if( ParseDirectory( p_intf, psz_root, dir ) )
212         {
213             httpd_file_sys_t *f = NULL;
214             httpd_handler_sys_t *h = NULL;
215             bool b_index;
216             char *psz_name, *psz_ext;
217
218             psz_name = FileToUrl( &dir[strlen( psz_root )], &b_index );
219             psz_ext = strrchr( dir, '.' );
220             if( psz_ext != NULL )
221             {
222                 int i;
223                 psz_ext++;
224                 for( i = 0; i < p_sys->i_handlers; i++ )
225                     if( !strcmp( p_sys->pp_handlers[i]->psz_ext, psz_ext ) )
226                         break;
227                 if( i < p_sys->i_handlers )
228                 {
229                     f = malloc( sizeof( httpd_handler_sys_t ) );
230                     h = (httpd_handler_sys_t *)f;
231                     f->b_handler = true;
232                     h->p_association = p_sys->pp_handlers[i];
233                 }
234             }
235             if( f == NULL )
236             {
237                 f = xmalloc( sizeof( httpd_file_sys_t ) );
238                 f->b_handler = false;
239             }
240
241             f->p_intf  = p_intf;
242             f->p_file = NULL;
243             f->p_redir = NULL;
244             f->p_redir2 = NULL;
245             f->file = strdup (dir);
246             f->name = psz_name;
247             f->b_html = strstr( &dir[strlen( psz_root )], ".htm" ) || strstr( &dir[strlen( psz_root )], ".xml" ) ? true : false;
248
249             if( !f->name )
250             {
251                 msg_Err( p_intf , "unable to parse directory" );
252                 closedir( p_dir );
253                 free( f );
254                 return( VLC_ENOMEM );
255             }
256             msg_Dbg( p_intf, "file=%s (url=%s)",
257                      f->file, f->name );
258
259             if( !f->b_handler )
260             {
261                 char *psz_type = strdup( "text/html; charset=UTF-8" );
262                 if( strstr( &dir[strlen( psz_root )], ".xml" ) )
263                 {
264                     char *psz = strstr( psz_type, "html;" );
265                     if( psz )
266                     {
267                         psz[0] = 'x';
268                         psz[1] = 'm';
269                         psz[2] = 'l';
270                         psz[3] = ';';
271                         psz[4] = ' ';
272                     }
273                 }
274                 f->p_file = httpd_FileNew( p_sys->p_httpd_host,
275                                            f->name,
276                                            f->b_html ? psz_type : NULL,
277                                            user, password, p_acl,
278                                            HttpCallback, f );
279                 free( psz_type );
280                 if( f->p_file != NULL )
281                 {
282                     TAB_APPEND( p_sys->i_files, p_sys->pp_files, f );
283                 }
284             }
285             else
286             {
287                 h->p_handler = httpd_HandlerNew( p_sys->p_httpd_host,
288                                                  f->name,
289                                                  user, password, p_acl,
290                                                  HandlerCallback, h );
291                 if( h->p_handler != NULL )
292                 {
293                     TAB_APPEND( p_sys->i_files, p_sys->pp_files,
294                                 (httpd_file_sys_t *)h );
295                 }
296             }
297
298             /* for url that ends by / add
299              *  - a redirect from rep to rep/
300              *  - in case of index.* rep/index.html to rep/ */
301             if( f && f->name[strlen(f->name) - 1] == '/' )
302             {
303                 char *psz_redir = strdup( f->name );
304                 char *p;
305                 psz_redir[strlen( psz_redir ) - 1] = '\0';
306
307                 msg_Dbg( p_intf, "redir=%s -> %s", psz_redir, f->name );
308                 f->p_redir = httpd_RedirectNew( p_sys->p_httpd_host, f->name, psz_redir );
309                 free( psz_redir );
310
311                 if( b_index && ( p = strstr( f->file, "index." ) ) )
312                 {
313                     if( asprintf( &psz_redir, "%s%s", f->name, p ) != -1 )
314                     {
315                         msg_Dbg( p_intf, "redir=%s -> %s", psz_redir, f->name );
316                         f->p_redir2 = httpd_RedirectNew( p_sys->p_httpd_host,
317                                                          f->name, psz_redir );
318
319                         free( psz_redir );
320                     }
321                 }
322             }
323         }
324     }
325
326     free( user );
327     free( password );
328
329     ACL_Destroy( p_acl );
330     closedir( p_dir );
331
332     return VLC_SUCCESS;
333 }
334
335
336 /*************************************************************************
337  * Playlist stuff
338  *************************************************************************/
339 void PlaylistListNode( intf_thread_t *p_intf, playlist_t *p_pl,
340                            playlist_item_t *p_node, char *name, mvar_t *s,
341                            int i_depth )
342 {
343     if( !p_node || !p_node->p_input )
344         return;
345
346     if( p_node->i_children == -1 )
347     {
348         char value[512];
349         char *psz;
350         playlist_item_t * p_item = playlist_CurrentPlayingItem( p_pl );
351         if( !p_item || !p_item->p_input )
352             return;
353
354         mvar_t *itm = mvar_New( name, "set" );
355         if( p_item->p_input == p_node->p_input )
356             mvar_AppendNewVar( itm, "current", "1" );
357         else
358             mvar_AppendNewVar( itm, "current", "0" );
359
360         sprintf( value, "%d", p_node->i_id );
361         mvar_AppendNewVar( itm, "index", value );
362
363         psz = input_item_GetName( p_node->p_input );
364         mvar_AppendNewVar( itm, "name", psz );
365         free( psz );
366
367         psz = input_item_GetURI( p_node->p_input );
368         mvar_AppendNewVar( itm, "uri", psz );
369         free( psz );
370
371         mvar_AppendNewVar( itm, "type", "Item" );
372
373         sprintf( value, "%d", i_depth );
374         mvar_AppendNewVar( itm, "depth", value );
375
376         if( p_node->i_flags & PLAYLIST_RO_FLAG )
377             mvar_AppendNewVar( itm, "ro", "ro" );
378         else
379             mvar_AppendNewVar( itm, "ro", "rw" );
380
381         sprintf( value, "%"PRId64, input_item_GetDuration( p_node->p_input ) );
382         mvar_AppendNewVar( itm, "duration", value );
383
384         //Adding extra meta-information to each playlist item
385
386         psz = input_item_GetTitle( p_node->p_input );
387         mvar_AppendNewVar( itm, "title", psz );
388         free( psz );
389
390         psz = input_item_GetArtist( p_node->p_input );
391         mvar_AppendNewVar( itm, "artist", psz );
392         free( psz );
393
394         psz = input_item_GetGenre( p_node->p_input );
395         mvar_AppendNewVar( itm, "genre", psz );
396         free( psz );
397
398         psz = input_item_GetCopyright( p_node->p_input );
399         mvar_AppendNewVar( itm, "copyright", psz );
400         free( psz );
401
402         psz = input_item_GetAlbum( p_node->p_input );
403         mvar_AppendNewVar( itm, "album", psz );
404         free( psz );
405
406         psz = input_item_GetTrackNum( p_node->p_input );
407         mvar_AppendNewVar( itm, "track", psz );
408         free( psz );
409
410         psz = input_item_GetDescription( p_node->p_input );
411         mvar_AppendNewVar( itm, "description", psz );
412         free( psz );
413
414         psz = input_item_GetRating( p_node->p_input );
415         mvar_AppendNewVar( itm, "rating", psz );
416         free( psz );
417
418         psz = input_item_GetDate( p_node->p_input );
419         mvar_AppendNewVar( itm, "date", psz );
420         free( psz );
421
422         psz = input_item_GetURL( p_node->p_input );
423         mvar_AppendNewVar( itm, "url", psz );
424         free( psz );
425
426         psz = input_item_GetLanguage( p_node->p_input );
427         mvar_AppendNewVar( itm, "language", psz );
428         free( psz );
429
430         psz = input_item_GetNowPlaying( p_node->p_input );
431         mvar_AppendNewVar( itm, "now_playing", psz );
432         free( psz );
433
434         psz = input_item_GetPublisher( p_node->p_input );
435         mvar_AppendNewVar( itm, "publisher", psz );
436         free( psz );
437
438         psz = input_item_GetEncodedBy( p_node->p_input );
439         mvar_AppendNewVar( itm, "encoded_by", psz );
440         free( psz );
441
442         psz = input_item_GetArtURL( p_node->p_input );
443         mvar_AppendNewVar( itm, "art_url", psz );
444         free( psz );
445
446         psz = input_item_GetTrackID( p_node->p_input );
447         mvar_AppendNewVar( itm, "track_id", psz );
448         free( psz );
449
450         mvar_AppendVar( s, itm );
451     }
452     else
453     {
454         char value[512];
455         int i_child;
456         mvar_t *itm = mvar_New( name, "set" );
457
458         mvar_AppendNewVar( itm, "name", p_node->p_input->psz_name );
459         mvar_AppendNewVar( itm, "uri", p_node->p_input->psz_name );
460
461         mvar_AppendNewVar( itm, "type", "Node" );
462
463         sprintf( value, "%d", p_node->i_id );
464         mvar_AppendNewVar( itm, "index", value );
465
466         sprintf( value, "%d", p_node->i_children);
467         mvar_AppendNewVar( itm, "i_children", value );
468
469         sprintf( value, "%d", i_depth );
470         mvar_AppendNewVar( itm, "depth", value );
471
472         if( p_node->i_flags & PLAYLIST_RO_FLAG )
473             mvar_AppendNewVar( itm, "ro", "ro" );
474         else
475             mvar_AppendNewVar( itm, "ro", "rw" );
476
477         mvar_AppendVar( s, itm );
478
479         for( i_child = 0 ; i_child < p_node->i_children ; i_child++ )
480              PlaylistListNode( p_intf, p_pl, p_node->pp_children[i_child],
481                                name, s, i_depth + 1);
482     }
483 }
484
485 /****************************************************************************
486  * Seek command parsing handling
487  ****************************************************************************/
488 void HandleSeek( intf_thread_t *p_intf, char *p_value )
489 {
490     intf_sys_t     *p_sys = p_intf->p_sys;
491     vlc_value_t val;
492     int i_stock = 0;
493     uint64_t i_length;
494     int i_value = 0;
495     int i_relative = 0;
496 #define POSITION_ABSOLUTE 12
497 #define POSITION_REL_FOR 13
498 #define POSITION_REL_BACK 11
499 #define VL_TIME_ABSOLUTE 0
500 #define VL_TIME_REL_FOR 1
501 #define VL_TIME_REL_BACK -1
502     if( p_sys->p_input )
503     {
504         var_Get( p_sys->p_input, "length", &val );
505         i_length = val.i_time;
506
507         while( p_value[0] != '\0' )
508         {
509             switch(p_value[0])
510             {
511                 case '+':
512                 {
513                     i_relative = VL_TIME_REL_FOR;
514                     p_value++;
515                     break;
516                 }
517                 case '-':
518                 {
519                     i_relative = VL_TIME_REL_BACK;
520                     p_value++;
521                     break;
522                 }
523                 case '0': case '1': case '2': case '3': case '4':
524                 case '5': case '6': case '7': case '8': case '9':
525                 {
526                     i_stock = strtol( p_value , &p_value , 10 );
527                     break;
528                 }
529                 case '%': /* for percentage ie position */
530                 {
531                     i_relative += POSITION_ABSOLUTE;
532                     i_value = i_stock;
533                     i_stock = 0;
534                     p_value[0] = '\0';
535                     break;
536                 }
537                 case ':':
538                 {
539                     i_value = 60 * (i_value + i_stock) ;
540                     i_stock = 0;
541                     p_value++;
542                     break;
543                 }
544                 case 'h': case 'H': /* hours */
545                 {
546                     i_value += 3600 * i_stock;
547                     i_stock = 0;
548                     /* other characters which are not numbers are not
549                      * important */
550                     while( ((p_value[0] < '0') || (p_value[0] > '9'))
551                            && (p_value[0] != '\0') )
552                     {
553                         p_value++;
554                     }
555                     break;
556                 }
557                 case 'm': case 'M': case '\'': /* minutes */
558                 {
559                     i_value += 60 * i_stock;
560                     i_stock = 0;
561                     p_value++;
562                     while( ((p_value[0] < '0') || (p_value[0] > '9'))
563                            && (p_value[0] != '\0') )
564                     {
565                         p_value++;
566                     }
567                     break;
568                 }
569                 case 's': case 'S': case '"':  /* seconds */
570                 {
571                     i_value += i_stock;
572                     i_stock = 0;
573                     while( ((p_value[0] < '0') || (p_value[0] > '9'))
574                            && (p_value[0] != '\0') )
575                     {
576                         p_value++;
577                     }
578                     break;
579                 }
580                 default:
581                 {
582                     p_value++;
583                     break;
584                 }
585             }
586         }
587
588         /* if there is no known symbol, I consider it as seconds.
589          * Otherwise, i_stock = 0 */
590         i_value += i_stock;
591
592         switch(i_relative)
593         {
594             case VL_TIME_ABSOLUTE:
595             {
596                 if( (uint64_t)( i_value ) * 1000000 <= i_length )
597                     val.i_time = (uint64_t)( i_value ) * 1000000;
598                 else
599                     val.i_time = i_length;
600
601                 var_Set( p_sys->p_input, "time", val );
602                 msg_Dbg( p_intf, "requested seek position: %dsec", i_value );
603                 break;
604             }
605             case VL_TIME_REL_FOR:
606             {
607                 var_Get( p_sys->p_input, "time", &val );
608                 if( (uint64_t)( i_value ) * 1000000 + val.i_time <= i_length )
609                 {
610                     val.i_time = ((uint64_t)( i_value ) * 1000000) + val.i_time;
611                 } else
612                 {
613                     val.i_time = i_length;
614                 }
615                 var_Set( p_sys->p_input, "time", val );
616                 msg_Dbg( p_intf, "requested seek position forward: %dsec", i_value );
617                 break;
618             }
619             case VL_TIME_REL_BACK:
620             {
621                 var_Get( p_sys->p_input, "time", &val );
622                 if( (int64_t)( i_value ) * 1000000 > val.i_time )
623                 {
624                     val.i_time = 0;
625                 } else
626                 {
627                     val.i_time = val.i_time - ((uint64_t)( i_value ) * 1000000);
628                 }
629                 var_Set( p_sys->p_input, "time", val );
630                 msg_Dbg( p_intf, "requested seek position backward: %dsec", i_value );
631                 break;
632             }
633             case POSITION_ABSOLUTE:
634             {
635                 val.f_float = __MIN( __MAX( ((float) i_value ) / 100.0 ,
636                                             0.0 ), 100.0 );
637                 var_Set( p_sys->p_input, "position", val );
638                 msg_Dbg( p_intf, "requested seek percent: %d%%", i_value );
639                 break;
640             }
641             case POSITION_REL_FOR:
642             {
643                 var_Get( p_sys->p_input, "position", &val );
644                 val.f_float += __MIN( __MAX( ((float) i_value ) / 100.0,
645                                              0.0 ) , 100.0 );
646                 var_Set( p_sys->p_input, "position", val );
647                 msg_Dbg( p_intf, "requested seek percent forward: %d%%",
648                          i_value );
649                 break;
650             }
651             case POSITION_REL_BACK:
652             {
653                 var_Get( p_sys->p_input, "position", &val );
654                 val.f_float -= __MIN( __MAX( ((float) i_value ) / 100.0,
655                                              0.0 ) , 100.0 );
656                 var_Set( p_sys->p_input, "position", val );
657                 msg_Dbg( p_intf, "requested seek percent backward: %d%%",
658                          i_value );
659                 break;
660             }
661             default:
662             {
663                 msg_Dbg( p_intf, "invalid seek request" );
664                 break;
665             }
666         }
667     }
668 #undef POSITION_ABSOLUTE
669 #undef POSITION_REL_FOR
670 #undef POSITION_REL_BACK
671 #undef VL_TIME_ABSOLUTE
672 #undef VL_TIME_REL_FOR
673 #undef VL_TIME_REL_BACK
674 }
675
676
677 /****************************************************************************
678  * URI Parsing functions
679  ****************************************************************************/
680 int TestURIParam( char *psz_uri, const char *psz_name )
681 {
682     char *p = psz_uri;
683
684     while( (p = strstr( p, psz_name )) )
685     {
686         /* Verify that we are dealing with a post/get argument */
687         if( (p == psz_uri || *(p - 1) == '&' || *(p - 1) == '\n')
688               && p[strlen(psz_name)] == '=' )
689         {
690             return true;
691         }
692         p++;
693     }
694
695     return false;
696 }
697
698 static const char *FindURIValue( const char *psz_uri, const char *restrict psz_name,
699                            size_t *restrict p_len )
700 {
701     const char *p = psz_uri, *end;
702     size_t len;
703
704     while( (p = strstr( p, psz_name )) )
705     {
706         /* Verify that we are dealing with a post/get argument */
707         if( (p == psz_uri || *(p - 1) == '&' || *(p - 1) == '\n')
708               && p[strlen(psz_name)] == '=' )
709             break;
710         p++;
711     }
712
713     if( p == NULL )
714     {
715         *p_len = 0;
716         return NULL;
717     }
718
719     p += strlen( psz_name );
720     if( *p == '=' ) p++;
721
722     if( ( end = strchr( p, '\n' ) ) != NULL )
723     {
724         /* POST method */
725         if( ( end > p ) && ( end[-1] == '\r' ) )
726             end--;
727
728         len = end - p;
729     }
730     else
731     {
732         /* GET method */
733         if( ( end = strchr( p, '&' ) ) != NULL )
734             len = end - p;
735         else
736             len = strlen( p );
737     }
738
739     *p_len = len;
740     return p;
741 }
742
743 const char *ExtractURIValue( const char *restrict psz_uri,
744                            const char *restrict psz_name,
745                            char *restrict psz_buf, size_t bufsize )
746 {
747     size_t len;
748     const char *psz_value = FindURIValue( psz_uri, psz_name, &len );
749     const char *psz_next;
750
751     if( psz_value == NULL )
752     {
753         if( bufsize > 0 )
754             *psz_buf = '\0';
755         return NULL;
756     }
757
758     psz_next = psz_value + len;
759
760     if( len >= bufsize )
761         len = bufsize - 1;
762
763     if( len > 0 )
764         strncpy( psz_buf, psz_value, len );
765     if( bufsize > 0 )
766         psz_buf[len] = '\0';
767
768     return psz_next;
769 }
770
771 char *ExtractURIString( const char *restrict psz_uri,
772                             const char *restrict psz_name )
773 {
774     size_t len;
775     const char *psz_value = FindURIValue( psz_uri, psz_name, &len );
776
777     if( psz_value == NULL )
778         return NULL;
779
780     char *res = malloc( len + 1 );
781     if( res == NULL )
782         return NULL;
783
784     memcpy( res, psz_value, len );
785     res[len] = '\0';
786
787     return res;
788 }
789
790 /* Since the resulting string is smaller we can work in place, so it is
791  * permitted to have psz == new. new points to the first word of the
792  * string, the function returns the remaining string. */
793 char *FirstWord( char *psz, char *new )
794 {
795     bool b_end;
796
797     while( *psz == ' ' )
798         psz++;
799
800     while( *psz != '\0' && *psz != ' ' )
801     {
802         if( *psz == '\'' )
803         {
804             char c = *psz++;
805             while( *psz != '\0' && *psz != c )
806             {
807                 if( *psz == '\\' && psz[1] != '\0' )
808                     psz++;
809                 *new++ = *psz++;
810             }
811             if( *psz == c )
812                 psz++;
813         }
814         else
815         {
816             if( *psz == '\\' && psz[1] != '\0' )
817                 psz++;
818             *new++ = *psz++;
819         }
820     }
821     b_end = !*psz;
822
823     *new++ = '\0';
824     if( !b_end )
825         return psz + 1;
826     else
827         return NULL;
828 }
829
830 /**********************************************************************
831  * MRLParse: parse the MRL, find the MRL string and the options,
832  * create an item with all information in it, and return the item.
833  * return NULL if there is an error.
834  **********************************************************************/
835
836 /* Function analog to FirstWord except that it relies on colon instead
837  * of space to delimit option boundaries. */
838 static char *FirstOption( char *psz, char *new )
839 {
840     bool b_end, b_start = true;
841
842     while( *psz == ' ' )
843         psz++;
844
845     while( *psz != '\0' && (*psz != ' ' || psz[1] != ':') )
846     {
847         if( *psz == '\'' )
848         {
849             char c = *psz++;
850             while( *psz != '\0' && *psz != c )
851             {
852                 if( *psz == '\\' && psz[1] != '\0' )
853                     psz++;
854                 *new++ = *psz++;
855                 b_start = false;
856             }
857             if( *psz == c )
858                 psz++;
859         }
860         else
861         {
862             if( *psz == '\\' && psz[1] != '\0' )
863                 psz++;
864             *new++ = *psz++;
865             b_start = false;
866         }
867     }
868     b_end = !*psz;
869
870     if ( !b_start )
871         while (new[-1] == ' ')
872             new--;
873
874     *new++ = '\0';
875     if( !b_end )
876         return psz + 1;
877     else
878         return NULL;
879 }
880
881 input_item_t *MRLParse( intf_thread_t *p_intf, const char *mrl,
882                                    char *psz_name )
883 {
884     char *psz = strdup( mrl ), *s_mrl = psz, *s_temp;
885     if( psz == NULL )
886         return NULL;
887     /* extract the mrl */
888     s_temp = FirstOption( s_mrl, s_mrl );
889     if( s_temp == NULL )
890     {
891         s_temp = s_mrl + strlen( s_mrl );
892     }
893
894     input_item_t *p_input = input_item_New( p_intf, s_mrl, psz_name );
895     if( p_input == NULL )
896         return NULL;
897     s_mrl = s_temp;
898
899     /* now we can take care of the options */
900     while ( *s_mrl != '\0' )
901     {
902         s_temp = FirstOption( s_mrl, s_mrl );
903         if( s_mrl == '\0' )
904             break;
905         if( s_temp == NULL )
906         {
907             s_temp = s_mrl + strlen( s_mrl );
908         }
909         input_item_AddOption( p_input, s_mrl, VLC_INPUT_OPTION_TRUSTED );
910         s_mrl = s_temp;
911     }
912
913     return p_input;
914 }
915
916 /**********************************************************************
917  * RealPath: parse ../, ~ and path stuff
918  **********************************************************************/
919 char *RealPath( const char *psz_src )
920 {
921     char *psz_dir;
922     char *p;
923     int i_len = strlen(psz_src);
924
925     psz_dir = xmalloc( i_len + 2 );
926     strcpy( psz_dir, psz_src );
927
928     /* Add a trailing sep to ease the .. step */
929     psz_dir[i_len] = DIR_SEP_CHAR;
930     psz_dir[i_len + 1] = '\0';
931
932 #if (DIR_SEP_CHAR != '/')
933     /* Convert all / to native separator */
934     p = psz_dir;
935     while( (p = strchr( p, '/' )) != NULL )
936     {
937         *p = DIR_SEP_CHAR;
938     }
939 #endif
940
941     /* FIXME: this could be O(N) rather than O(N²)... */
942     /* Remove multiple separators and /./ */
943     p = psz_dir;
944     while( (p = strchr( p, DIR_SEP_CHAR )) != NULL )
945     {
946         if( p[1] == DIR_SEP_CHAR )
947             memmove( &p[1], &p[2], strlen(&p[2]) + 1 );
948         else if( p[1] == '.' && p[2] == DIR_SEP_CHAR )
949             memmove( &p[1], &p[3], strlen(&p[3]) + 1 );
950         else
951             p++;
952     }
953
954     if( psz_dir[0] == '~' )
955     {
956         char *home = config_GetUserDir( VLC_HOME_DIR ), *dir;
957         if( asprintf( &dir, "%s%s", home, psz_dir + 1 ) != -1 )
958         {
959             free( psz_dir );
960             psz_dir = dir;
961         }
962         free( home );
963     }
964
965     if( strlen(psz_dir) > 2 )
966     {
967         /* Fix all .. dir */
968         p = psz_dir + 3;
969         while( (p = strchr( p, DIR_SEP_CHAR )) != NULL )
970         {
971             if( p[-1] == '.' && p[-2] == '.' && p[-3] == DIR_SEP_CHAR )
972             {
973                 char *q;
974                 p[-3] = '\0';
975                 if( (q = strrchr( psz_dir, DIR_SEP_CHAR )) != NULL )
976                 {
977                     memmove( q + 1, p + 1, strlen(p + 1) + 1 );
978                     p = q + 1;
979                 }
980                 else
981                 {
982                     memmove( psz_dir, p, strlen(p) + 1 );
983                     p = psz_dir + 3;
984                 }
985             }
986             else
987                 p++;
988         }
989     }
990
991     /* Remove trailing sep if there are at least 2 sep in the string
992      * (handles the C:\ stuff) */
993     p = strrchr( psz_dir, DIR_SEP_CHAR );
994     if( p != NULL && p[1] == '\0' && p != strchr( psz_dir, DIR_SEP_CHAR ) )
995         *p = '\0';
996
997     return psz_dir;
998 }