]> git.sesse.net Git - vlc/blob - modules/access/ftp.c
Improvements to preferences
[vlc] / modules / access / ftp.c
1 /*****************************************************************************
2  * ftp.c: FTP input module
3  *****************************************************************************
4  * Copyright (C) 2001-2004 VideoLAN
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>
28
29 #include <vlc/vlc.h>
30 #include <vlc/input.h>
31
32 #include "network.h"
33
34 /*****************************************************************************
35  * Module descriptor
36  *****************************************************************************/
37 static int     Open ( vlc_object_t * );
38 static void    Close( vlc_object_t * );
39
40 #define CACHING_TEXT N_("Caching value in ms")
41 #define CACHING_LONGTEXT N_( \
42     "Allows you to modify the default caching value for FTP streams. This " \
43     "value should be set in millisecond units." )
44 #define USER_TEXT N_("FTP user name")
45 #define USER_LONGTEXT N_("Allows you to modify the user name that will " \
46     "be used for the connection.")
47 #define PASS_TEXT N_("FTP password")
48 #define PASS_LONGTEXT N_("Allows you to modify the password that will be " \
49     "used for the connection.")
50 #define ACCOUNT_TEXT N_("FTP account")
51 #define ACCOUNT_LONGTEXT N_("Allows you to modify the account that will be " \
52     "used for the connection.")
53
54 vlc_module_begin();
55     set_name( "FTP" );
56     set_description( _("FTP input") );
57     set_capability( "access2", 0 );
58     set_category( CAT_INPUT );
59     set_subcategory( SUBCAT_INPUT_ACCESS );
60     add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
61                  CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
62     add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
63                 VLC_FALSE );
64     add_string( "ftp-pwd", "anonymous@dummy.org", NULL, PASS_TEXT,
65                 PASS_LONGTEXT, VLC_FALSE );
66     add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
67                 ACCOUNT_LONGTEXT, VLC_FALSE );
68     add_shortcut( "ftp" );
69     set_callbacks( Open, Close );
70 vlc_module_end();
71
72 /*****************************************************************************
73  * Local prototypes
74  *****************************************************************************/
75 static int Read( access_t *, uint8_t *, int );
76 static int Seek( access_t *, int64_t );
77 static int Control( access_t *, int, va_list );
78
79 struct access_sys_t
80 {
81     vlc_url_t url;
82
83     int       fd_cmd;
84     int       fd_data;
85 };
86
87 static int  ftp_SendCommand( access_t *, char *, ... );
88 static int  ftp_ReadCommand( access_t *, int *, char ** );
89 static int  ftp_StartStream( access_t *, int64_t );
90 static int  ftp_StopStream ( access_t *);
91
92 /****************************************************************************
93  * Open: connect to ftp server and ask for file
94  ****************************************************************************/
95 static int Open( vlc_object_t *p_this )
96 {
97     access_t     *p_access = (access_t*)p_this;
98     access_sys_t *p_sys;
99     char         *psz;
100
101     int          i_answer;
102     char         *psz_arg;
103
104     /* Init p_access */
105     p_access->pf_read = Read;
106     p_access->pf_block = NULL;
107     p_access->pf_seek = Seek;
108     p_access->pf_control = Control;
109     p_access->info.i_update = 0;
110     p_access->info.i_size = 0;
111     p_access->info.i_pos = 0;
112     p_access->info.b_eof = VLC_FALSE;
113     p_access->info.i_title = 0;
114     p_access->info.i_seekpoint = 0;
115     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
116     memset( p_sys, 0, sizeof( access_sys_t ) );
117     p_sys->fd_cmd = -1;
118     p_sys->fd_data = -1;
119
120     /* *** Parse URL and get server addr/port and path *** */
121     psz = p_access->psz_path;
122     while( *psz == '/' )
123     {
124         psz++;
125     }
126     vlc_UrlParse( &p_sys->url, psz, 0 );
127
128     if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' )
129     {
130         msg_Err( p_access, "invalid server name" );
131         goto exit_error;
132     }
133     if( p_sys->url.i_port <= 0 )
134     {
135         p_sys->url.i_port = 21; /* default port */
136     }
137
138     /* *** Open a TCP connection with server *** */
139     msg_Dbg( p_access, "waiting for connection..." );
140     p_sys->fd_cmd = net_OpenTCP( p_access, p_sys->url.psz_host,
141                                  p_sys->url.i_port );
142     if( p_sys->fd_cmd < 0 )
143     {
144         msg_Err( p_access, "failed to connect with server" );
145         goto exit_error;
146     }
147
148     for( ;; )
149     {
150         if( ftp_ReadCommand( p_access, &i_answer, NULL ) != 1 )
151         {
152             break;
153         }
154     }
155     if( i_answer / 100 != 2 )
156     {
157         msg_Err( p_access, "connection rejected" );
158         goto exit_error;
159     }
160
161     msg_Dbg( p_access, "connection accepted (%d)", i_answer );
162
163     psz = var_CreateGetString( p_access, "ftp-user" );
164     if( ftp_SendCommand( p_access, "USER %s", psz ) < 0 ||
165         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
166     {
167         free( psz );
168         goto exit_error;
169     }
170     free( psz );
171
172     switch( i_answer / 100 )
173     {
174         case 2:
175             msg_Dbg( p_access, "user accepted" );
176             break;
177         case 3:
178             msg_Dbg( p_access, "password needed" );
179             psz = var_CreateGetString( p_access, "ftp-pwd" );
180             if( ftp_SendCommand( p_access, "PASS %s", psz ) < 0 ||
181                 ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
182             {
183                 free( psz );
184                 goto exit_error;
185             }
186             free( psz );
187
188             switch( i_answer / 100 )
189             {
190                 case 2:
191                     msg_Dbg( p_access, "password accepted" );
192                     break;
193                 case 3:
194                     msg_Dbg( p_access, "account needed" );
195                     psz = var_CreateGetString( p_access, "ftp-account" );
196                     if( ftp_SendCommand( p_access, "ACCT %s",
197                                          psz ) < 0 ||
198                         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
199                     {
200                         free( psz );
201                         goto exit_error;
202                     }
203                     free( psz );
204
205                     if( i_answer / 100 != 2 )
206                     {
207                         msg_Err( p_access, "account rejected" );
208                         goto exit_error;
209                     }
210                     msg_Dbg( p_access, "account accepted" );
211                     break;
212
213                 default:
214                     msg_Err( p_access, "password rejected" );
215                     goto exit_error;
216             }
217             break;
218         default:
219             msg_Err( p_access, "user rejected" );
220             goto exit_error;
221     }
222
223     /* binary mode */
224     if( ftp_SendCommand( p_access, "TYPE I" ) < 0 ||
225         ftp_ReadCommand( p_access, &i_answer, NULL ) != 2 )
226     {
227         msg_Err( p_access, "cannot set binary transfer mode" );
228         goto exit_error;
229     }
230
231     /* get size */
232     if( ftp_SendCommand( p_access, "SIZE %s", p_sys->url.psz_path ) < 0 ||
233         ftp_ReadCommand( p_access, &i_answer, &psz_arg ) != 2 )
234     {
235         msg_Err( p_access, "cannot get file size" );
236         goto exit_error;
237     }
238     p_access->info.i_size = atoll( &psz_arg[4] );
239     free( psz_arg );
240     msg_Dbg( p_access, "file size: "I64Fd, p_access->info.i_size );
241
242     /* Start the 'stream' */
243     if( ftp_StartStream( p_access, 0 ) < 0 )
244     {
245         msg_Err( p_access, "cannot retrieve file" );
246         goto exit_error;
247     }
248
249     /* Update default_pts to a suitable value for ftp access */
250     var_Create( p_access, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
251
252     return VLC_SUCCESS;
253
254 exit_error:
255     if( p_sys->fd_cmd > 0 )
256     {
257         net_Close( p_sys->fd_cmd );
258     }
259     vlc_UrlClean( &p_sys->url );
260     free( p_sys );
261     return VLC_EGENERIC;
262 }
263
264 /*****************************************************************************
265  * Close: free unused data structures
266  *****************************************************************************/
267 static void Close( vlc_object_t *p_this )
268 {
269     access_t      *p_access = (access_t*)p_this;
270     access_sys_t  *p_sys = p_access->p_sys;
271
272     msg_Dbg( p_access, "stopping stream" );
273     ftp_StopStream( p_access );
274
275     if( ftp_SendCommand( p_access, "QUIT" ) < 0 )
276     {
277         msg_Warn( p_access, "cannot quit" );
278     }
279     else
280     {
281         ftp_ReadCommand( p_access, NULL, NULL );
282     }
283     net_Close( p_sys->fd_cmd );
284
285     /* free memory */
286     vlc_UrlClean( &p_sys->url );
287     free( p_sys );
288 }
289
290 /*****************************************************************************
291  * Seek: try to go at the right place
292  *****************************************************************************/
293 static int Seek( access_t *p_access, int64_t i_pos )
294 {
295     if( i_pos < 0 )
296     {
297         return VLC_EGENERIC;
298     }
299     msg_Dbg( p_access, "seeking to "I64Fd, i_pos );
300
301     ftp_StopStream( p_access );
302     if( ftp_StartStream( p_access, i_pos ) < 0 )
303     {
304         p_access->info.b_eof = VLC_TRUE;
305         return VLC_EGENERIC;
306     }
307
308     p_access->info.b_eof = VLC_FALSE;
309     p_access->info.i_pos = i_pos;
310
311     return VLC_SUCCESS;
312 }
313
314 /*****************************************************************************
315  * Read:
316  *****************************************************************************/
317 static int Read( access_t *p_access, uint8_t *p_buffer, int i_len )
318 {
319     access_sys_t *p_sys = p_access->p_sys;
320     int i_read;
321
322     if( p_access->info.b_eof )
323         return 0;
324
325     i_read = net_Read( p_access, p_sys->fd_data, NULL, p_buffer, i_len,
326                        VLC_FALSE );
327     if( i_read == 0 )
328         p_access->info.b_eof = VLC_TRUE;
329     else if( i_read > 0 )
330         p_access->info.i_pos += i_read;
331
332     return i_read;
333 }
334
335 /*****************************************************************************
336  * Control:
337  *****************************************************************************/
338 static int Control( access_t *p_access, int i_query, va_list args )
339 {
340     vlc_bool_t   *pb_bool;
341     int          *pi_int;
342     int64_t      *pi_64;
343     vlc_value_t  val;
344
345     switch( i_query )
346     {
347         /* */
348         case ACCESS_CAN_SEEK:
349             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
350             *pb_bool = VLC_TRUE;
351             break;
352         case ACCESS_CAN_FASTSEEK:
353             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
354             *pb_bool = VLC_FALSE;
355             break;
356         case ACCESS_CAN_PAUSE:
357             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
358             *pb_bool = VLC_TRUE;    /* FIXME */
359             break;
360         case ACCESS_CAN_CONTROL_PACE:
361             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
362             *pb_bool = VLC_TRUE;    /* FIXME */
363             break;
364
365         /* */
366         case ACCESS_GET_MTU:
367             pi_int = (int*)va_arg( args, int * );
368             *pi_int = 0;
369             break;
370
371         case ACCESS_GET_PTS_DELAY:
372             pi_64 = (int64_t*)va_arg( args, int64_t * );
373             var_Get( p_access, "ftp-caching", &val );
374             *pi_64 = (int64_t)var_GetInteger( p_access, "ftp-caching" ) * I64C(1000);
375             break;
376
377         /* */
378         case ACCESS_SET_PAUSE_STATE:
379             /* Nothing to do */
380             break;
381
382         case ACCESS_GET_TITLE_INFO:
383         case ACCESS_SET_TITLE:
384         case ACCESS_SET_SEEKPOINT:
385         case ACCESS_SET_PRIVATE_ID_STATE:
386             return VLC_EGENERIC;
387
388         default:
389             msg_Warn( p_access, "unimplemented query in control" );
390             return VLC_EGENERIC;
391
392     }
393     return VLC_SUCCESS;
394 }
395
396 /*****************************************************************************
397  * ftp_*:
398  *****************************************************************************/
399 static int ftp_SendCommand( access_t *p_access, char *psz_fmt, ... )
400 {
401     access_sys_t *p_sys = p_access->p_sys;
402     va_list      args;
403     char         *psz_cmd;
404     int          i_ret;
405
406     va_start( args, psz_fmt );
407     vasprintf( &psz_cmd, psz_fmt, args );
408     va_end( args );
409
410     msg_Dbg( p_access, "ftp_SendCommand:\"%s\"", psz_cmd);
411     if( ( i_ret = net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL,
412                               "%s", psz_cmd ) ) > 0 )
413     {
414         i_ret = net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL,
415                             "\r\n" );
416     }
417
418     if( i_ret < 0 )
419     {
420         msg_Err( p_access, "failed to send command" );
421         return VLC_EGENERIC;
422     }
423     return VLC_SUCCESS;
424 }
425
426 /* TODO support this s**t :
427  RFC 959 allows the client to send certain TELNET strings at any moment,
428  even in the middle of a request:
429
430  * \377\377.
431  * \377\376x where x is one byte.
432  * \377\375x where x is one byte. The server is obliged to send \377\374x
433  *                                immediately after reading x.
434  * \377\374x where x is one byte.
435  * \377\373x where x is one byte. The server is obliged to send \377\376x
436  *                                immediately after reading x.
437  * \377x for any other byte x.
438
439  These strings are not part of the requests, except in the case \377\377,
440  where the request contains one \377. */
441 static int ftp_ReadCommand( access_t *p_access,
442                             int *pi_answer, char **ppsz_answer )
443 {
444     access_sys_t *p_sys = p_access->p_sys;
445     char         *psz_line;
446     int          i_answer;
447
448     psz_line = net_Gets( p_access, p_sys->fd_cmd, NULL );
449     msg_Dbg( p_access, "answer=%s", psz_line );
450     if( psz_line == NULL || strlen( psz_line ) < 3 )
451     {
452         msg_Err( p_access, "cannot get answer" );
453         if( psz_line ) free( psz_line );
454         if( pi_answer ) *pi_answer    = 500;
455         if( ppsz_answer ) *ppsz_answer  = NULL;
456         return -1;
457     }
458
459     if( psz_line[3] == '-' )    /* Multiple response */
460     {
461         char end[4];
462
463         memcpy( end, psz_line, 3 );
464         end[3] = ' ';
465
466         for( ;; )
467         {
468             char *psz_tmp = net_Gets( p_access, p_sys->fd_cmd, NULL );
469
470             if( psz_tmp == NULL )   /* Error */
471                 break;
472
473             if( !strncmp( psz_tmp, end, 4 ) )
474             {
475                 free( psz_tmp );
476                 break;
477             }
478             free( psz_tmp );
479         }
480     }
481
482     i_answer = atoi( psz_line );
483
484     if( pi_answer ) *pi_answer = i_answer;
485     if( ppsz_answer )
486     {
487         *ppsz_answer = psz_line;
488     }
489     else
490     {
491         free( psz_line );
492     }
493     return( i_answer / 100 );
494 }
495
496 static int ftp_StartStream( access_t *p_access, off_t i_start )
497 {
498     access_sys_t *p_sys = p_access->p_sys;
499
500     char psz_ip[1000];
501     int  i_answer;
502     char *psz_arg, *psz_parser;
503     int  a1,a2,a3,a4;
504     int  p1,p2;
505     int  i_port;
506
507     if( ftp_SendCommand( p_access, "PASV" ) < 0 ||
508         ftp_ReadCommand( p_access, &i_answer, &psz_arg ) != 2 )
509     {
510         msg_Err( p_access, "cannot set passive transfer mode" );
511         return VLC_EGENERIC;
512     }
513
514     psz_parser = strchr( psz_arg, '(' );
515     if( !psz_parser ||
516         sscanf( psz_parser, "(%d,%d,%d,%d,%d,%d", &a1, &a2, &a3,
517                 &a4, &p1, &p2 ) < 6 )
518     {
519         free( psz_arg );
520         msg_Err( p_access, "cannot get ip/port for passive transfer mode" );
521         return VLC_EGENERIC;
522     }
523     free( psz_arg );
524
525     sprintf( psz_ip, "%d.%d.%d.%d", a1, a2, a3, a4 );
526     i_port = p1 * 256 + p2;
527     msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
528
529     if( ftp_SendCommand( p_access, "TYPE I" ) < 0 ||
530         ftp_ReadCommand( p_access, &i_answer, NULL ) != 2 )
531     {
532         msg_Err( p_access, "cannot set binary transfer mode" );
533         return VLC_EGENERIC;
534     }
535
536     if( i_start > 0 )
537     {
538         if( ftp_SendCommand( p_access, "REST "I64Fu, i_start ) < 0 ||
539             ftp_ReadCommand( p_access, &i_answer, NULL ) > 3 )
540         {
541             msg_Err( p_access, "cannot set restart point" );
542             return VLC_EGENERIC;
543         }
544     }
545
546     msg_Dbg( p_access, "waiting for data connection..." );
547     p_sys->fd_data = net_OpenTCP( p_access, psz_ip, i_port );
548     if( p_sys->fd_data < 0 )
549     {
550         msg_Err( p_access, "failed to connect with server" );
551         return VLC_EGENERIC;
552     }
553     msg_Dbg( p_access, "connection with \"%s:%d\" successful",
554              psz_ip, i_port );
555
556     /* "1xx" message */
557     if( ftp_SendCommand( p_access, "RETR %s", p_sys->url.psz_path ) < 0 ||
558         ftp_ReadCommand( p_access, &i_answer, NULL ) > 2 )
559     {
560         msg_Err( p_access, "cannot retreive file" );
561         return VLC_EGENERIC;
562     }
563     return VLC_SUCCESS;
564 }
565
566 static int ftp_StopStream ( access_t *p_access )
567 {
568     access_sys_t *p_sys = p_access->p_sys;
569
570     int i_answer;
571
572     if( ftp_SendCommand( p_access, "ABOR" ) < 0 )
573     {
574         msg_Warn( p_access, "cannot abord file" );
575         if(  p_sys->fd_data > 0 )
576             net_Close( p_sys->fd_data );
577         p_sys->fd_data = -1;
578         return VLC_EGENERIC;
579     }
580     if(  p_sys->fd_data > 0 )
581     {
582         net_Close( p_sys->fd_data );
583         p_sys->fd_data = -1;
584         ftp_ReadCommand( p_access, &i_answer, NULL );
585     }
586     ftp_ReadCommand( p_access, &i_answer, NULL );
587
588     return VLC_SUCCESS;
589 }
590