]> git.sesse.net Git - vlc/blob - modules/access/ftp.c
* Massive spelling corrections.
[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_description( _("FTP input") );
56     set_capability( "access", 0 );
57     add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
58                  CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
59     add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
60                 VLC_FALSE );
61     add_string( "ftp-pwd", "anonymous@dummy.org", NULL, PASS_TEXT,
62                 PASS_LONGTEXT, VLC_FALSE );
63     add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
64                 ACCOUNT_LONGTEXT, VLC_FALSE );
65     add_shortcut( "ftp" );
66     set_callbacks( Open, Close );
67 vlc_module_end();
68
69 /*****************************************************************************
70  * Local prototypes
71  *****************************************************************************/
72 static ssize_t Read     ( input_thread_t *, byte_t *,  size_t );
73 static void    Seek     ( input_thread_t *, off_t );
74
75 struct access_sys_t
76 {
77     vlc_url_t url;
78
79     int       fd_cmd;
80     int       fd_data;
81
82     int64_t   i_size;
83 };
84
85 static int  ftp_SendCommand( input_thread_t *, char *, ... );
86 static int  ftp_ReadCommand( input_thread_t *, int *, char ** );
87 static int  ftp_StartStream( input_thread_t *, off_t );
88 static int  ftp_StopStream ( input_thread_t *);
89
90 /****************************************************************************
91  * Open: connect to ftp server and ask for file
92  ****************************************************************************/
93 static int Open( vlc_object_t *p_this )
94 {
95     input_thread_t  *p_input = (input_thread_t*)p_this;
96     access_sys_t    *p_sys;
97     char            *psz;
98     vlc_value_t     val;
99
100     int             i_answer;
101     char            *psz_arg;
102
103     /* *** allocate access_sys_t *** */
104     p_sys = p_input->p_access_data = malloc( sizeof( access_sys_t ) );
105     memset( p_sys, 0, sizeof( access_sys_t ) );
106     p_sys->fd_cmd = -1;
107     p_sys->fd_data = -1;
108
109     /* *** Parse URL and get server addr/port and path *** */
110     psz = p_input->psz_name;
111     while( *psz == '/' )
112     {
113         psz++;
114     }
115     vlc_UrlParse( &p_sys->url, psz, 0 );
116
117     if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' )
118     {
119         msg_Err( p_input, "invalid server name" );
120         goto exit_error;
121     }
122     if( p_sys->url.i_port <= 0 )
123     {
124         p_sys->url.i_port = 21; /* default port */
125     }
126
127     /* *** Open a TCP connection with server *** */
128     msg_Dbg( p_input, "waiting for connection..." );
129     p_sys->fd_cmd = net_OpenTCP( p_input, p_sys->url.psz_host,
130                                  p_sys->url.i_port );
131     if( p_sys->fd_cmd < 0 )
132     {
133         msg_Err( p_input, "failed to connect with server" );
134         goto exit_error;
135     }
136     p_input->i_mtu = 0;
137
138     for( ;; )
139     {
140         if( ftp_ReadCommand( p_input, &i_answer, NULL ) != 1 )
141         {
142             break;
143         }
144     }
145     if( i_answer / 100 != 2 )
146     {
147         msg_Err( p_input, "connection rejected" );
148         goto exit_error;
149     }
150
151     msg_Dbg( p_input, "connection accepted (%d)", i_answer );
152
153     var_Create( p_input, "ftp-user", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
154     var_Get( p_input, "ftp-user", &val );
155     if( ftp_SendCommand( p_input, "USER %s", val.psz_string ) < 0 ||
156         ftp_ReadCommand( p_input, &i_answer, NULL ) < 0 )
157     {
158         if( val.psz_string ) free( val.psz_string );
159         goto exit_error;
160     }
161     if( val.psz_string ) free( val.psz_string );
162
163     switch( i_answer / 100 )
164     {
165         case 2:
166             msg_Dbg( p_input, "user accepted" );
167             break;
168         case 3:
169             msg_Dbg( p_input, "password needed" );
170             var_Create( p_input, "ftp-pwd", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
171             var_Get( p_input, "ftp-pwd", &val );
172             if( ftp_SendCommand( p_input, "PASS %s", val.psz_string ) < 0 ||
173                 ftp_ReadCommand( p_input, &i_answer, NULL ) < 0 )
174             {
175                 if( val.psz_string ) free( val.psz_string );
176                 goto exit_error;
177             }
178             if( val.psz_string ) free( val.psz_string );
179
180             switch( i_answer / 100 )
181             {
182                 case 2:
183                     msg_Dbg( p_input, "password accepted" );
184                     break;
185                 case 3:
186                     msg_Dbg( p_input, "account needed" );
187                     var_Create( p_input, "ftp-account",
188                                 VLC_VAR_STRING | VLC_VAR_DOINHERIT );
189                     var_Get( p_input, "ftp-account", &val );
190                     if( ftp_SendCommand( p_input, "ACCT %s",
191                                          val.psz_string ) < 0 ||
192                         ftp_ReadCommand( p_input, &i_answer, NULL ) < 0 )
193                     {
194                         if( val.psz_string ) free( val.psz_string );
195                         goto exit_error;
196                     }
197                     if( val.psz_string ) free( val.psz_string );
198
199                     if( i_answer / 100 != 2 )
200                     {
201                         msg_Err( p_input, "account rejected" );
202                         goto exit_error;
203                     }
204                     msg_Dbg( p_input, "account accepted" );
205                     break;
206
207                 default:
208                     msg_Err( p_input, "password rejected" );
209                     goto exit_error;
210             }
211             break;
212         default:
213             msg_Err( p_input, "user rejected" );
214             goto exit_error;
215     }
216
217     /* binary mode */
218     if( ftp_SendCommand( p_input, "TYPE I" ) < 0 ||
219         ftp_ReadCommand( p_input, &i_answer, NULL ) != 2 )
220     {
221         msg_Err( p_input, "cannot set binary transfert mode" );
222         goto exit_error;
223     }
224
225     /* get size */
226     if( ftp_SendCommand( p_input, "SIZE %s", p_sys->url.psz_path ) < 0 ||
227         ftp_ReadCommand( p_input, &i_answer, &psz_arg ) != 2 )
228     {
229         msg_Err( p_input, "cannot get file size" );
230         goto exit_error;
231     }
232     p_sys->i_size = atoll( &psz_arg[4] );
233     free( psz_arg );
234     msg_Dbg( p_input, "file size: "I64Fd, p_sys->i_size );
235
236     /* Start the 'stream' */
237     if( ftp_StartStream( p_input, 0 ) < 0 )
238     {
239         msg_Err( p_input, "cannot retrieve file" );
240         goto exit_error;
241     }
242     /* *** set exported functions *** */
243     p_input->pf_read = Read;
244     p_input->pf_seek = Seek;
245     p_input->pf_set_program = input_SetProgram;
246     p_input->pf_set_area = NULL;
247
248     p_input->p_private = NULL;
249
250     /* *** finished to set some variable *** */
251     vlc_mutex_lock( &p_input->stream.stream_lock );
252     p_input->stream.b_pace_control = VLC_TRUE;
253     p_input->stream.p_selected_area->i_tell = 0;
254     p_input->stream.b_seekable = VLC_TRUE;
255     p_input->stream.p_selected_area->i_size = p_sys->i_size;
256     p_input->stream.i_method = INPUT_METHOD_NETWORK;
257     vlc_mutex_unlock( &p_input->stream.stream_lock );
258
259     /* Update default_pts to a suitable value for ftp access */
260     var_Create( p_input, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
261     var_Get( p_input, "ftp-caching", &val );
262     p_input->i_pts_delay = val.i_int * 1000;
263
264     return VLC_SUCCESS;
265
266 exit_error:
267     if( p_sys->fd_cmd > 0 )
268     {
269         net_Close( p_sys->fd_cmd );
270     }
271     vlc_UrlClean( &p_sys->url );
272     free( p_sys );
273     return VLC_EGENERIC;
274 }
275
276 /*****************************************************************************
277  * Close: free unused data structures
278  *****************************************************************************/
279 static void Close( vlc_object_t *p_this )
280 {
281     input_thread_t  *p_input = (input_thread_t *)p_this;
282     access_sys_t    *p_sys = p_input->p_access_data;
283
284     msg_Dbg( p_input, "stopping stream" );
285     ftp_StopStream( p_input );
286
287     if( ftp_SendCommand( p_input, "QUIT" ) < 0 )
288     {
289         msg_Warn( p_input, "cannot quit" );
290     }
291     else
292     {
293         ftp_ReadCommand( p_input, NULL, NULL );
294     }
295     net_Close( p_sys->fd_cmd );
296
297     /* free memory */
298     vlc_UrlClean( &p_sys->url );
299     free( p_sys );
300 }
301
302 /*****************************************************************************
303  * Seek: try to go at the right place
304  *****************************************************************************/
305 static void Seek( input_thread_t * p_input, off_t i_pos )
306 {
307     if( i_pos < 0 )
308     {
309         return;
310     }
311     vlc_mutex_lock( &p_input->stream.stream_lock );
312
313     msg_Dbg( p_input, "seeking to "I64Fd, i_pos );
314
315     ftp_StopStream( p_input );
316     ftp_StartStream( p_input, i_pos );
317
318     p_input->stream.p_selected_area->i_tell = i_pos;
319     vlc_mutex_unlock( &p_input->stream.stream_lock );
320 }
321
322 /*****************************************************************************
323  * Read:
324  *****************************************************************************/
325 static ssize_t Read( input_thread_t * p_input, byte_t * p_buffer,
326                      size_t i_len )
327 {
328     access_sys_t *p_sys = p_input->p_access_data;
329
330     return net_Read( p_input, p_sys->fd_data, p_buffer, i_len, VLC_FALSE );
331 }
332
333 /*****************************************************************************
334  * ftp_*:
335  *****************************************************************************/
336 static int ftp_SendCommand( input_thread_t *p_input, char *psz_fmt, ... )
337 {
338     access_sys_t *p_sys = p_input->p_access_data;
339     va_list      args;
340     char         *psz_cmd;
341     int          i_ret;
342
343     va_start( args, psz_fmt );
344     vasprintf( &psz_cmd, psz_fmt, args );
345     va_end( args );
346
347     msg_Dbg( p_input, "ftp_SendCommand:\"%s\"", psz_cmd);
348     if( ( i_ret = net_Printf( VLC_OBJECT(p_input), p_sys->fd_cmd,
349                               "%s", psz_cmd ) ) > 0 )
350     {
351         i_ret = net_Printf( VLC_OBJECT(p_input), p_sys->fd_cmd, "\n" );
352     }
353
354     if( i_ret < 0 )
355     {
356         msg_Err( p_input, "failed to send command" );
357         return VLC_EGENERIC;
358     }
359     return VLC_SUCCESS;
360 }
361
362 /* TODO support this s**t :
363  RFC 959 allows the client to send certain TELNET strings at any moment,
364  even in the middle of a request:
365
366  * \377\377.
367  * \377\376x where x is one byte.
368  * \377\375x where x is one byte. The server is obliged to send \377\374x
369  *                                immediately after reading x.
370  * \377\374x where x is one byte.
371  * \377\373x where x is one byte. The server is obliged to send \377\376x
372  *                                immediately after reading x.
373  * \377x for any other byte x.
374
375  These strings are not part of the requests, except in the case \377\377,
376  where the request contains one \377. */
377 static int ftp_ReadCommand( input_thread_t *p_input,
378                             int *pi_answer, char **ppsz_answer )
379 {
380     access_sys_t *p_sys = p_input->p_access_data;
381     char         *psz_line;
382     int          i_answer;
383
384     psz_line = net_Gets( p_input, p_sys->fd_cmd );
385     msg_Dbg( p_input, "answer=%s", psz_line );
386     if( psz_line == NULL || strlen( psz_line ) < 3 )
387     {
388         msg_Err( p_input, "cannot get answer" );
389         if( psz_line ) free( psz_line );
390         if( pi_answer ) *pi_answer    = 500;
391         if( ppsz_answer ) *ppsz_answer  = NULL;
392         return -1;
393     }
394
395     i_answer = atoi( psz_line );
396
397     if( pi_answer ) *pi_answer = i_answer;
398     if( ppsz_answer )
399     {
400         *ppsz_answer = psz_line;
401     }
402     else
403     {
404         free( psz_line );
405     }
406     return( i_answer / 100 );
407 }
408
409 static int ftp_StartStream( input_thread_t *p_input, off_t i_start )
410 {
411     access_sys_t *p_sys = p_input->p_access_data;
412
413     char psz_ip[1000];
414     int  i_answer;
415     char *psz_arg, *psz_parser;
416     int  a1,a2,a3,a4;
417     int  p1,p2;
418     int  i_port;
419
420     if( ftp_SendCommand( p_input, "PASV" ) < 0 ||
421         ftp_ReadCommand( p_input, &i_answer, &psz_arg ) != 2 )
422     {
423         msg_Err( p_input, "cannot set passive transfert mode" );
424         return VLC_EGENERIC;
425     }
426
427     psz_parser = strchr( psz_arg, '(' );
428     if( !psz_parser ||
429         sscanf( psz_parser, "(%d,%d,%d,%d,%d,%d", &a1, &a2, &a3,
430                 &a4, &p1, &p2 ) < 6 )
431     {
432         free( psz_arg );
433         msg_Err( p_input, "cannot get ip/port for passive transfert mode" );
434         return VLC_EGENERIC;
435     }
436     free( psz_arg );
437
438     sprintf( psz_ip, "%d.%d.%d.%d", a1, a2, a3, a4 );
439     i_port = p1 * 256 + p2;
440     msg_Dbg( p_input, "ip:%s port:%d", psz_ip, i_port );
441
442     if( ftp_SendCommand( p_input, "TYPE I" ) < 0 ||
443         ftp_ReadCommand( p_input, &i_answer, NULL ) != 2 )
444     {
445         msg_Err( p_input, "cannot set binary transfert mode" );
446         return VLC_EGENERIC;
447     }
448
449     if( i_start > 0 )
450     {
451         if( ftp_SendCommand( p_input, "REST "I64Fu, i_start ) < 0 ||
452             ftp_ReadCommand( p_input, &i_answer, NULL ) > 3 )
453         {
454             msg_Err( p_input, "cannot set restart point" );
455             return VLC_EGENERIC;
456         }
457     }
458
459     msg_Dbg( p_input, "waiting for data connection..." );
460     p_sys->fd_data = net_OpenTCP( p_input, psz_ip, i_port );
461     if( p_sys->fd_data < 0 )
462     {
463         msg_Err( p_input, "failed to connect with server" );
464         return VLC_EGENERIC;
465     }
466     msg_Dbg( p_input, "connection with \"%s:%d\" successful",
467              psz_ip, i_port );
468
469     /* "1xx" message */
470     if( ftp_SendCommand( p_input, "RETR %s", p_sys->url.psz_path ) < 0 ||
471         ftp_ReadCommand( p_input, &i_answer, NULL ) > 2 )
472     {
473         msg_Err( p_input, "cannot retreive file" );
474         return VLC_EGENERIC;
475     }
476     return VLC_SUCCESS;
477 }
478
479 static int ftp_StopStream ( input_thread_t *p_input)
480 {
481     access_sys_t *p_sys = p_input->p_access_data;
482
483     int i_answer;
484
485     if( ftp_SendCommand( p_input, "ABOR" ) < 0 )
486     {
487         msg_Warn( p_input, "cannot abord file" );
488         net_Close( p_sys->fd_data ); p_sys->fd_data = -1;
489         return VLC_EGENERIC;
490     }
491     net_Close( p_sys->fd_data ); p_sys->fd_data = -1;
492     ftp_ReadCommand( p_input, &i_answer, NULL );
493     ftp_ReadCommand( p_input, &i_answer, NULL );
494
495     return VLC_SUCCESS;
496 }