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