]> git.sesse.net Git - vlc/blob - modules/access/directory.c
Use vlc_openat, fix support for non-UTF-8 systems
[vlc] / modules / access / directory.c
1 /*****************************************************************************
2  * directory.c: expands a directory (directory: access plug-in)
3  *****************************************************************************
4  * Copyright (C) 2002-2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8  *          RĂ©mi Denis-Courmont
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include "fs.h"
35 #include <vlc_access.h>
36
37 #include <sys/types.h>
38 #ifdef HAVE_SYS_STAT_H
39 #   include <sys/stat.h>
40 #endif
41
42 #ifdef HAVE_UNISTD_H
43 #   include <unistd.h>
44 #   include <fcntl.h>
45 #elif defined( WIN32 ) && !defined( UNDER_CE )
46 #   include <io.h>
47 #endif
48
49 #ifdef HAVE_DIRENT_H
50 #   include <dirent.h>
51 #endif
52 #ifdef __sun__
53 static inline int dirfd (DIR *dir)
54 {
55     return dir->dd_fd;
56 }
57 #endif
58
59 #include <vlc_fs.h>
60 #include <vlc_url.h>
61 #include <vlc_strings.h>
62
63 enum
64 {
65     MODE_NONE,
66     MODE_COLLAPSE,
67     MODE_EXPAND,
68 };
69
70 typedef struct directory_t directory_t;
71 struct directory_t
72 {
73     directory_t *parent;
74     DIR         *handle;
75     char        *uri;
76 #ifndef WIN32
77     struct stat  st;
78 #endif
79 #ifndef HAVE_FDOPENDIR
80     char         *path;
81 #endif
82 };
83
84 struct access_sys_t
85 {
86     directory_t *current;
87     DIR *handle;
88     char *uri;
89     char *ignored_exts;
90     int mode;
91     int i_item_count;
92     char *psz_xspf_extension;
93 };
94
95 /*****************************************************************************
96  * Open: open the directory
97  *****************************************************************************/
98 int DirOpen( vlc_object_t *p_this )
99 {
100     access_t *p_access = (access_t*)p_this;
101
102     if( !p_access->psz_path )
103         return VLC_EGENERIC;
104
105     DIR *handle = vlc_opendir (p_access->psz_path);
106     if (handle == NULL)
107         return VLC_EGENERIC;
108
109     return DirInit (p_access, handle);
110 }
111
112 int DirInit (access_t *p_access, DIR *handle)
113 {
114     access_sys_t *p_sys = malloc (sizeof (*p_sys));
115     if (unlikely(p_sys == NULL))
116         goto error;
117
118     char *uri;
119     if (!strcmp (p_access->psz_access, "fd"))
120     {
121         if (asprintf (&uri, "fd://%s", p_access->psz_path) == -1)
122             uri = NULL;
123     }
124     else
125         uri = make_URI (p_access->psz_path);
126     if (unlikely(uri == NULL))
127         goto error;
128
129     p_access->p_sys = p_sys;
130     p_sys->current = NULL;
131     p_sys->handle = handle;
132     p_sys->uri = uri;
133     p_sys->ignored_exts = var_InheritString (p_access, "ignore-filetypes");
134     p_sys->i_item_count = 0;
135     p_sys->psz_xspf_extension = strdup( "" );
136
137     /* Handle mode */
138     char *psz = var_InheritString (p_access, "recursive");
139     if (psz == NULL || !strcasecmp (psz, "none"))
140         p_sys->mode = MODE_NONE;
141     else if( !strcasecmp( psz, "collapse" )  )
142         p_sys->mode = MODE_COLLAPSE;
143     else
144         p_sys->mode = MODE_EXPAND;
145     free( psz );
146
147     access_InitFields(p_access);
148     p_access->pf_read  = NULL;
149     p_access->pf_block = DirBlock;
150     p_access->pf_seek  = NULL;
151     p_access->pf_control= DirControl;
152     free (p_access->psz_demux);
153     p_access->psz_demux = strdup ("xspf-open");
154
155     return VLC_SUCCESS;
156
157 error:
158     closedir (handle);
159     free (p_sys);
160     return VLC_EGENERIC;
161 }
162
163 /*****************************************************************************
164  * Close: close the target
165  *****************************************************************************/
166 void DirClose( vlc_object_t * p_this )
167 {
168     access_t *p_access = (access_t*)p_this;
169     access_sys_t *p_sys = p_access->p_sys;
170
171     while (p_sys->current)
172     {
173         directory_t *current = p_sys->current;
174
175         p_sys->current = current->parent;
176         closedir (current->handle);
177         free (current->uri);
178 #ifndef HAVE_FDOPENDIR
179         free (current->path);
180 #endif
181         free (current);
182     }
183
184     /* corner case: Block() not called ever */
185     if (p_sys->handle != NULL)
186         closedir (p_sys->handle);
187     free (p_sys->uri);
188
189     free (p_sys->psz_xspf_extension);
190     free (p_sys->ignored_exts);
191     free (p_sys);
192 }
193
194 /* Detect directories that recurse into themselves. */
195 static bool has_inode_loop (const directory_t *dir)
196 {
197 #ifndef WIN32
198     dev_t dev = dir->st.st_dev;
199     ino_t inode = dir->st.st_ino;
200
201     while ((dir = dir->parent) != NULL)
202         if ((dir->st.st_dev == dev) && (dir->st.st_ino == inode))
203             return true;
204 #else
205 # undef fstat
206 # define fstat( fd, st ) (0)
207     VLC_UNUSED( dir );
208 #endif
209     return false;
210 }
211
212 block_t *DirBlock (access_t *p_access)
213 {
214     access_sys_t *p_sys = p_access->p_sys;
215     directory_t *current = p_sys->current;
216
217     if (p_access->info.b_eof)
218         return NULL;
219
220     if (current == NULL)
221     {   /* Startup: send the XSPF header */
222         static const char header[] =
223             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
224             "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\" xmlns:vlc=\"http://www.videolan.org/vlc/playlist/ns/0/\">\n"
225             " <trackList>\n";
226         block_t *block = block_Alloc (sizeof (header) - 1);
227         if (!block)
228             goto fatal;
229         memcpy (block->p_buffer, header, sizeof (header) - 1);
230
231         /* "Open" the base directory */
232         current = malloc (sizeof (*current));
233         if (current == NULL)
234         {
235             block_Release (block);
236             goto fatal;
237         }
238         current->parent = NULL;
239         current->handle = p_sys->handle;
240 #ifndef HAVE_FDOPENDIR
241         current->path = strdup (p_access->psz_path);
242 #endif
243         current->uri = p_sys->uri;
244         if (fstat (dirfd (current->handle), &current->st))
245         {
246             free (current);
247             block_Release (block);
248             goto fatal;
249         }
250
251         p_sys->handle = NULL;
252         p_sys->uri = NULL;
253         p_sys->current = current;
254         return block;
255     }
256
257     char *entry = vlc_readdir (current->handle);
258     if (entry == NULL)
259     {   /* End of directory, go back to parent */
260         closedir (current->handle);
261         p_sys->current = current->parent;
262         free (current->uri);
263 #ifndef HAVE_FDOPENDIR
264         free (current->path);
265 #endif
266         free (current);
267
268         if (p_sys->current == NULL)
269         {   /* End of XSPF playlist */
270             char *footer;
271             int len = asprintf( &footer, " </trackList>\n" \
272                 " <extension application=\"http://www.videolan.org/vlc/playlist/0\">\n" \
273                 "%s" \
274                 " </extension>\n" \
275                 "</playlist>\n", p_sys->psz_xspf_extension );
276             if (unlikely(len == -1))
277                 goto fatal;
278
279             block_t *block = block_heap_Alloc (footer, footer, len);
280             if (unlikely(block == NULL))
281                 free (footer);
282             p_access->info.b_eof = true;
283             return block;
284         }
285         else
286         {
287             /* This was the end of a "subnode" */
288             /* Write the ID to the extension */
289             char *old_xspf_extension = p_sys->psz_xspf_extension;
290             if (old_xspf_extension == NULL)
291                 goto fatal;
292
293             int len2 = asprintf( &p_sys->psz_xspf_extension, "%s  </vlc:node>\n", old_xspf_extension );
294             if (len2 == -1)
295                 goto fatal;
296             free( old_xspf_extension );
297         }
298         return NULL;
299     }
300
301     /* Skip current, parent and hidden directories */
302     if (entry[0] == '.')
303     {
304         free (entry);
305         return NULL;
306     }
307     /* Handle recursion */
308     if (p_sys->mode != MODE_COLLAPSE)
309     {
310         directory_t *sub = malloc (sizeof (*sub));
311         if (sub == NULL)
312         {
313             free (entry);
314             return NULL;
315         }
316
317         DIR *handle;
318 #ifdef HAVE_FDOPENDIR
319         int fd = vlc_openat (dirfd (current->handle), entry, O_RDONLY);
320         if (fd != -1)
321         {
322             handle = fdopendir (fd);
323             if (handle == NULL)
324                 close (fd);
325         }
326         else
327             handle = NULL;
328 #else
329         if (asprintf (&sub->path, "%s/%s", current->path, entry) != -1)
330             handle = vlc_opendir (sub->path);
331         else
332             handle = NULL;
333 #endif
334         if (handle != NULL)
335         {
336             sub->parent = current;
337             sub->handle = handle;
338
339             char *encoded = encode_URI_component (entry);
340             if ((encoded == NULL)
341              || (asprintf (&sub->uri, "%s/%s", current->uri, encoded) == -1))
342                  sub->uri = NULL;
343             free (encoded);
344
345             if ((p_sys->mode == MODE_NONE)
346              || fstat (dirfd (handle), &sub->st)
347              || has_inode_loop (sub)
348              || (sub->uri == NULL))
349             {
350                 free (entry);
351                 closedir (handle);
352                 free (sub->uri);
353                 free (sub);
354                 return NULL;
355             }
356             p_sys->current = sub;
357
358             /* Add node to xspf extension */
359             char *old_xspf_extension = p_sys->psz_xspf_extension;
360             if (old_xspf_extension == NULL)
361             {
362                 free (entry);
363                 goto fatal;
364             }
365
366             char *title = convert_xml_special_chars (entry);
367             free (entry);
368             if (title == NULL
369              || asprintf (&p_sys->psz_xspf_extension, "%s"
370                           "  <vlc:node title=\"%s\">\n", old_xspf_extension,
371                           title) == -1)
372             {
373                 free (title);
374                 goto fatal;
375             }
376             free (title);
377             free (old_xspf_extension);
378             return NULL;
379         }
380         else
381             free (sub);
382     }
383
384     /* Skip files with ignored extensions */
385     if (p_sys->ignored_exts != NULL)
386     {
387         const char *ext = strrchr (entry, '.');
388         if (ext != NULL)
389         {
390             size_t extlen = strlen (++ext);
391             for (const char *type = p_sys->ignored_exts, *end;
392                  type[0]; type = end + 1)
393             {
394                 end = strchr (type, ',');
395                 if (end == NULL)
396                     end = type + strlen (type);
397
398                 if (type + extlen == end
399                  && !strncasecmp (ext, type, extlen))
400                 {
401                     free (entry);
402                     return NULL;
403                 }
404
405                 if (*end == '\0')
406                     break;
407             }
408         }
409     }
410
411     char *encoded = encode_URI_component (entry);
412     free (entry);
413     if (encoded == NULL)
414         goto fatal;
415     int len = asprintf (&entry,
416                         "  <track><location>%s/%s</location>\n" \
417                         "   <extension application=\"http://www.videolan.org/vlc/playlist/0\">\n" \
418                         "    <vlc:id>%d</vlc:id>\n" \
419                         "   </extension>\n" \
420                         "  </track>\n",
421                         current->uri, encoded, p_sys->i_item_count++);
422     free (encoded);
423     if (len == -1)
424         goto fatal;
425
426     /* Write the ID to the extension */
427     char *old_xspf_extension = p_sys->psz_xspf_extension;
428     if (old_xspf_extension == NULL)
429         goto fatal;
430
431     int len2 = asprintf( &p_sys->psz_xspf_extension, "%s   <vlc:item tid=\"%i\" />\n",
432                             old_xspf_extension, p_sys->i_item_count-1 );
433     if (len2 == -1)
434         goto fatal;
435     free( old_xspf_extension );
436
437     block_t *block = block_heap_Alloc (entry, entry, len);
438     if (unlikely(block == NULL))
439     {
440         free (entry);
441         goto fatal;
442     }
443     return block;
444
445 fatal:
446     p_access->info.b_eof = true;
447     return NULL;
448 }
449
450 /*****************************************************************************
451  * Control:
452  *****************************************************************************/
453 int DirControl( access_t *p_access, int i_query, va_list args )
454 {
455     switch( i_query )
456     {
457         /* */
458         case ACCESS_CAN_SEEK:
459         case ACCESS_CAN_FASTSEEK:
460             *va_arg( args, bool* ) = false;
461             break;
462
463         case ACCESS_CAN_PAUSE:
464         case ACCESS_CAN_CONTROL_PACE:
465             *va_arg( args, bool* ) = true;
466             break;
467
468         /* */
469         case ACCESS_GET_PTS_DELAY:
470             *va_arg( args, int64_t * ) = DEFAULT_PTS_DELAY * 1000;
471             break;
472
473         /* */
474         case ACCESS_SET_PAUSE_STATE:
475         case ACCESS_GET_TITLE_INFO:
476         case ACCESS_SET_TITLE:
477         case ACCESS_SET_SEEKPOINT:
478         case ACCESS_SET_PRIVATE_ID_STATE:
479         case ACCESS_GET_CONTENT_TYPE:
480         case ACCESS_GET_META:
481             return VLC_EGENERIC;
482
483         default:
484             msg_Warn( p_access, "unimplemented query in control" );
485             return VLC_EGENERIC;
486     }
487     return VLC_SUCCESS;
488 }