ftp.c 23.3 KB
Newer Older
1
/*****************************************************************************
gbazin's avatar
   
gbazin committed
2
 * ftp.c: FTP input module
3
 *****************************************************************************
4
 * Copyright (C) 2001-2006 the VideoLAN team
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
5
 * Copyright © 2006 Rémi Denis-Courmont
6
 * $Id$
7
 *
8
 * Authors: Laurent Aimar <fenrir@via.ecp.fr> - original code
9
 *          Rémi Denis-Courmont <rem # videolan.org> - EPSV support
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
26
27
28
29
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>
30
#include <assert.h>
31
32
33

#include <vlc/vlc.h>
#include <vlc/input.h>
34
#include <vlc_interaction.h>
35
36

#include "network.h"
37
#include "vlc_url.h"
38
#include "stream_output.h"
39
40

/*****************************************************************************
41
 * Module descriptor
42
 *****************************************************************************/
43
44
45
46
static int   InOpen ( vlc_object_t * );
static void  InClose( vlc_object_t * );
static int  OutOpen ( vlc_object_t * );
static void OutClose( vlc_object_t * );
47

Christophe Massiot's avatar
Christophe Massiot committed
48
#define CACHING_TEXT N_("Caching value in ms")
49
#define CACHING_LONGTEXT N_( \
50
    "Caching value for FTP streams. This " \
zorglub's avatar
zorglub committed
51
    "value should be set in milliseconds." )
gbazin's avatar
   
gbazin committed
52
#define USER_TEXT N_("FTP user name")
zorglub's avatar
zorglub committed
53
#define USER_LONGTEXT N_("User name that will " \
gbazin's avatar
   
gbazin committed
54
55
    "be used for the connection.")
#define PASS_TEXT N_("FTP password")
zorglub's avatar
zorglub committed
56
#define PASS_LONGTEXT N_("Password that will be " \
gbazin's avatar
   
gbazin committed
57
58
    "used for the connection.")
#define ACCOUNT_TEXT N_("FTP account")
zorglub's avatar
zorglub committed
59
#define ACCOUNT_LONGTEXT N_("Account that will be " \
gbazin's avatar
   
gbazin committed
60
    "used for the connection.")
61
62

vlc_module_begin();
zorglub's avatar
zorglub committed
63
    set_shortname( "FTP" );
gbazin's avatar
   
gbazin committed
64
    set_description( _("FTP input") );
Laurent Aimar's avatar
Laurent Aimar committed
65
    set_capability( "access2", 0 );
zorglub's avatar
zorglub committed
66
67
    set_category( CAT_INPUT );
    set_subcategory( SUBCAT_INPUT_ACCESS );
gbazin's avatar
   
gbazin committed
68
69
70
71
    add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
                 CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
    add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
                VLC_FALSE );
72
    add_string( "ftp-pwd", "anonymous@example.com", NULL, PASS_TEXT,
gbazin's avatar
   
gbazin committed
73
74
75
                PASS_LONGTEXT, VLC_FALSE );
    add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
                ACCOUNT_LONGTEXT, VLC_FALSE );
76
    add_shortcut( "ftp" );
77
78
79
80
81
82
83
84
85
86
    set_callbacks( InOpen, InClose );

    add_submodule();
    set_shortname( "FTP" );
    set_description( _("FTP upload output") );
    set_capability( "sout access", 0 );
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_ACO );
    add_shortcut( "ftp" );
    set_callbacks( OutOpen, OutClose );
87
88
vlc_module_end();

89
90
91
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
92
static int Read( access_t *, uint8_t *, int );
93
static int Write( sout_access_out_t *, block_t * );
Laurent Aimar's avatar
Laurent Aimar committed
94
static int Seek( access_t *, int64_t );
95
static int OutSeek( sout_access_out_t *, int64_t );
Laurent Aimar's avatar
Laurent Aimar committed
96
static int Control( access_t *, int, va_list );
97

98
struct access_sys_t
99
{
100
    vlc_url_t  url;
101

102
103
    int        fd_cmd;
    int        fd_data;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
104

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
105
    char       sz_epsv_ip[NI_MAXNUMERICHOST];
106
};
107
108
#define GET_OUT_SYS( p_this ) \
    ((access_sys_t *)(((sout_access_out_t *)(p_this))->p_sys))
109

110
111
112
113
static int ftp_SendCommand( vlc_object_t *, access_sys_t *, const char *, ... );
static int ftp_ReadCommand( vlc_object_t *, access_sys_t *, int *, char ** );
static int ftp_StartStream( vlc_object_t *, access_sys_t *, int64_t );
static int ftp_StopStream ( vlc_object_t *, access_sys_t * );
114

115
static int Login( vlc_object_t *p_access, access_sys_t *p_sys )
116
{
117
    int i_answer;
118
    char *psz;
119

120
    /* *** Open a TCP connection with server *** */
121
122
    int fd = p_sys->fd_cmd = net_ConnectTCP( p_access, p_sys->url.psz_host,
                                             p_sys->url.i_port );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
123
    if( fd == -1 )
124
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
125
        msg_Err( p_access, "connection failed" );
126
127
        intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"), 
                        _("VLC could not connect with the given server.") );
128
        return -1;
129
    }
Laurent Aimar's avatar
Laurent Aimar committed
130

131
    while( ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) == 1 );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
132

133
134
    if( i_answer / 100 != 2 )
    {
Laurent Aimar's avatar
Laurent Aimar committed
135
        msg_Err( p_access, "connection rejected" );
136
137
        intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"), 
                        _("VLC's connection to the given server was rejected.") );
138
        return -1;
139
140
    }

Laurent Aimar's avatar
Laurent Aimar committed
141
    msg_Dbg( p_access, "connection accepted (%d)", i_answer );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
142

143
144
145
146
    if( p_sys->url.psz_username && *p_sys->url.psz_username )
        psz = strdup( p_sys->url.psz_username );
    else
        psz = var_CreateGetString( p_access, "ftp-user" );
147

148
149
    if( ftp_SendCommand( p_access, p_sys, "USER %s", psz ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
150
    {
151
        free( psz );
152
        return -1;
153
    }
154
    free( psz );
155

156
157
158
    switch( i_answer / 100 )
    {
        case 2:
Laurent Aimar's avatar
Laurent Aimar committed
159
            msg_Dbg( p_access, "user accepted" );
160
161
            break;
        case 3:
Laurent Aimar's avatar
Laurent Aimar committed
162
            msg_Dbg( p_access, "password needed" );
163
164
            if( p_sys->url.psz_password && *p_sys->url.psz_password )
                psz = strdup( p_sys->url.psz_password );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
165
            else
166
167
                psz = var_CreateGetString( p_access, "ftp-pwd" );

168
169
            if( ftp_SendCommand( p_access, p_sys, "PASS %s", psz ) < 0 ||
                ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
170
            {
171
                free( psz );
172
                return -1;
173
            }
174
            free( psz );
175

176
177
178
            switch( i_answer / 100 )
            {
                case 2:
Laurent Aimar's avatar
Laurent Aimar committed
179
                    msg_Dbg( p_access, "password accepted" );
180
181
                    break;
                case 3:
Laurent Aimar's avatar
Laurent Aimar committed
182
                    msg_Dbg( p_access, "account needed" );
183
                    psz = var_CreateGetString( p_access, "ftp-account" );
184
                    if( ftp_SendCommand( p_access, p_sys, "ACCT %s",
185
                                         psz ) < 0 ||
186
                        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
187
                    {
188
                        free( psz );
189
                        return -1;
190
                    }
191
                    free( psz );
192

193
194
                    if( i_answer / 100 != 2 )
                    {
Laurent Aimar's avatar
Laurent Aimar committed
195
                        msg_Err( p_access, "account rejected" );
196
197
198
                        intf_UserFatal( p_access, VLC_FALSE, 
                                        _("Network interaction failed"), 
                                        _("Your account was rejected.") );
199
                        return -1;
200
                    }
Laurent Aimar's avatar
Laurent Aimar committed
201
                    msg_Dbg( p_access, "account accepted" );
202
                    break;
203

204
                default:
Laurent Aimar's avatar
Laurent Aimar committed
205
                    msg_Err( p_access, "password rejected" );
206
207
208
                    intf_UserFatal( p_access, VLC_FALSE, 
                                    _("Network interaction failed"), 
                                    _("Your password was rejected.") );
209
                    return -1;
210
211
212
            }
            break;
        default:
Laurent Aimar's avatar
Laurent Aimar committed
213
            msg_Err( p_access, "user rejected" );
214
215
216
            intf_UserFatal( p_access, VLC_FALSE, 
                        _("Network interaction failed"), 
                        _("Your connection attemp to the server was rejected.") );
217
            return -1;
218
219
    }

220
221
222
    return 0;
}

223
static int Connect( vlc_object_t *p_access, access_sys_t *p_sys )
224
{
225
226
    if( Login( p_access, p_sys ) < 0 )
        return -1;
227

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
228
    /* Extended passive mode */
229
    if( ftp_SendCommand( p_access, p_sys, "EPSV ALL" ) < 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
230
231
    {
        msg_Err( p_access, "cannot request extended passive mode" );
232
        net_Close( p_sys->fd_cmd );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
233
234
235
        return -1;
    }

236
    if( ftp_ReadCommand( p_access, p_sys, NULL, NULL ) == 2 )
237
    {
238
        if( net_GetPeerAddress( p_sys->fd_cmd, p_sys->sz_epsv_ip, NULL ) )
239
240
241
242
        {
            net_Close( p_sys->fd_cmd );
            return -1;
        }
243
    }
244
245
246
247
248
249
250
    else
    {
        /* If ESPV ALL fails, we fallback to PASV.
         * We have to restart the connection in case there is a NAT that
         * understands EPSV ALL in the way, and hence won't allow PASV on
         * the initial connection.
         */
251
        msg_Info( p_access, "FTP Extended passive mode disabled" );
252
        net_Close( p_sys->fd_cmd );
253

Rémi Denis-Courmont's avatar
Oups    
Rémi Denis-Courmont committed
254
        if( Login( p_access, p_sys ) )
255
256
257
258
        {
            net_Close( p_sys->fd_cmd );
            return -1;
        }
259
    }
260

261
262
263
    /* check binary mode support */
    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, NULL, NULL ) != 2 )
264
    {
hartman's avatar
hartman committed
265
        msg_Err( p_access, "cannot set binary transfer mode" );
266
267
        net_Close( p_sys->fd_cmd );
        return -1;
268
269
    }

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    return 0;
}


static int parseURL( vlc_url_t *url, const char *path )
{
    if( path == NULL )
        return -1;

    /* *** Parse URL and get server addr/port and path *** */
    while( *path == '/' )
        path++;

    vlc_UrlParse( url, path, 0 );

    if( url->psz_host == NULL || *url->psz_host == '\0' )
        return -1;

    if( url->i_port <= 0 )
        url->i_port = IPPORT_FTP; /* default port */

    /* FTP URLs are relative to user's default directory (RFC1738)
    For absolute path use ftp://foo.bar//usr/local/etc/filename */

    if( *url->psz_path == '/' )
        url->psz_path++;

    return 0;
}


/****************************************************************************
 * Open: connect to ftp server and ask for file
 ****************************************************************************/
static int InOpen( vlc_object_t *p_this )
{
    access_t     *p_access = (access_t*)p_this;
    access_sys_t *p_sys;
    char         *psz_arg;

    /* Init p_access */
    STANDARD_READ_ACCESS_INIT
    p_sys->fd_data = -1;

    if( parseURL( &p_sys->url, p_access->psz_path ) )
        goto exit_error;

    if( Connect( p_this, p_sys ) )
        goto exit_error;

320
    /* get size */
321
322
    if( ftp_SendCommand( p_this, p_sys, "SIZE %s", p_sys->url.psz_path ) < 0 ||
        ftp_ReadCommand( p_this, p_sys, NULL, &psz_arg ) != 2 )
323
    {
Laurent Aimar's avatar
Laurent Aimar committed
324
        msg_Err( p_access, "cannot get file size" );
325
        net_Close( p_sys->fd_cmd );
326
327
        goto exit_error;
    }
328
    p_access->info.i_size = atoll( &psz_arg[4] );
329
    free( psz_arg );
330
    msg_Dbg( p_access, "file size: "I64Fd, p_access->info.i_size );
331

332
    /* Start the 'stream' */
333
    if( ftp_StartStream( p_this, p_sys, 0 ) < 0 )
334
    {
Laurent Aimar's avatar
Laurent Aimar committed
335
        msg_Err( p_access, "cannot retrieve file" );
336
        net_Close( p_sys->fd_cmd );
337
338
339
340
        goto exit_error;
    }

    /* Update default_pts to a suitable value for ftp access */
Laurent Aimar's avatar
Laurent Aimar committed
341
    var_Create( p_access, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
342

343
    return VLC_SUCCESS;
344
345

exit_error:
346
347
348
    vlc_UrlClean( &p_sys->url );
    free( p_sys );
    return VLC_EGENERIC;
349
350
}

351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
static int OutOpen( vlc_object_t *p_this )
{
    sout_access_out_t *p_access = (sout_access_out_t *)p_this;
    access_sys_t      *p_sys;

    p_sys = malloc( sizeof( *p_sys ) );
    if( p_sys == NULL )
        return VLC_ENOMEM;
    memset( p_sys, 0, sizeof( *p_sys ) );

    /* Init p_access */
    p_sys->fd_data = -1;

    if( parseURL( &p_sys->url, p_access->psz_name ) )
        goto exit_error;

    if( Connect( p_this, p_sys ) )
        goto exit_error;

    /* Start the 'stream' */
    if( ftp_StartStream( p_this, p_sys, 0 ) < 0 )
    {
        msg_Err( p_access, "cannot store file" );
        net_Close( p_sys->fd_cmd );
        goto exit_error;
    }

    p_access->pf_seek = OutSeek;
    p_access->pf_write = Write;

    return VLC_SUCCESS;

exit_error:
Rémi Denis-Courmont's avatar
Oups    
Rémi Denis-Courmont committed
384
    vlc_UrlClean( &p_sys->url );
385
386
387
388
    free( p_sys );
    return VLC_EGENERIC;
}

389
390
391
/*****************************************************************************
 * Close: free unused data structures
 *****************************************************************************/
392
static void Close( vlc_object_t *p_access, access_sys_t *p_sys )
393
{
Laurent Aimar's avatar
Laurent Aimar committed
394
    msg_Dbg( p_access, "stopping stream" );
395
    ftp_StopStream( p_access, p_sys );
396

397
    if( ftp_SendCommand( p_access, p_sys, "QUIT" ) < 0 )
398
    {
Laurent Aimar's avatar
Laurent Aimar committed
399
        msg_Warn( p_access, "cannot quit" );
400
401
402
    }
    else
    {
403
        ftp_ReadCommand( p_access, p_sys, NULL, NULL );
404
    }
405
    net_Close( p_sys->fd_cmd );
406
407

    /* free memory */
408
409
    vlc_UrlClean( &p_sys->url );
    free( p_sys );
410
411
}

412
413
414
415
416
417
418
419
420
421
422
static void InClose( vlc_object_t *p_this )
{
    Close( p_this, ((access_t *)p_this)->p_sys);
}

static void OutClose( vlc_object_t *p_this )
{
    Close( p_this, GET_OUT_SYS(p_this));
}


423
424
425
/*****************************************************************************
 * Seek: try to go at the right place
 *****************************************************************************/
426
static int _Seek( vlc_object_t *p_access, access_sys_t *p_sys, int64_t i_pos )
427
428
{
    if( i_pos < 0 )
Laurent Aimar's avatar
Laurent Aimar committed
429
        return VLC_EGENERIC;
430

Laurent Aimar's avatar
Laurent Aimar committed
431
    msg_Dbg( p_access, "seeking to "I64Fd, i_pos );
432

433
434
    ftp_StopStream( (vlc_object_t *)p_access, p_sys );
    if( ftp_StartStream( (vlc_object_t *)p_access, p_sys, i_pos ) < 0 )
Laurent Aimar's avatar
Laurent Aimar committed
435
        return VLC_EGENERIC;
436
437
438
439
440
441
442
443
444

    return VLC_SUCCESS;
}

static int Seek( access_t *p_access, int64_t i_pos )
{
    int val = _Seek( (vlc_object_t *)p_access, p_access->p_sys, i_pos );
    if( val )
        return val;
445

446
447
    p_access->info.b_eof = VLC_FALSE;
    p_access->info.i_pos = i_pos;
448

Laurent Aimar's avatar
Laurent Aimar committed
449
    return VLC_SUCCESS;
450
451
}

452
453
454
455
456
static int OutSeek( sout_access_out_t *p_access, off_t i_pos )
{
    return _Seek( (vlc_object_t *)p_access, GET_OUT_SYS( p_access ), i_pos);
}

457
458
459
/*****************************************************************************
 * Read:
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
460
static int Read( access_t *p_access, uint8_t *p_buffer, int i_len )
461
{
Laurent Aimar's avatar
Laurent Aimar committed
462
463
464
    access_sys_t *p_sys = p_access->p_sys;
    int i_read;

465
466
467
    assert( p_sys->fd_data != -1 );
    assert( p_access->i_object_type == VLC_OBJECT_ACCESS );

468
    if( p_access->info.b_eof )
Laurent Aimar's avatar
Laurent Aimar committed
469
470
        return 0;

471
472
    i_read = net_Read( p_access, p_sys->fd_data, NULL, p_buffer, i_len,
                       VLC_FALSE );
Laurent Aimar's avatar
Laurent Aimar committed
473
    if( i_read == 0 )
474
        p_access->info.b_eof = VLC_TRUE;
Laurent Aimar's avatar
Laurent Aimar committed
475
    else if( i_read > 0 )
476
        p_access->info.i_pos += i_read;
Laurent Aimar's avatar
Laurent Aimar committed
477
478
479
480

    return i_read;
}

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/*****************************************************************************
 * Write:
 *****************************************************************************/
static int Write( sout_access_out_t *p_access, block_t *p_buffer )
{
    access_sys_t *p_sys = GET_OUT_SYS(p_access);
    size_t i_write = 0;

    assert( p_sys->fd_data != -1 );

    while( p_buffer != NULL )
    {
        block_t *p_next = p_buffer->p_next;;

        i_write += net_Write( p_access, p_sys->fd_data, NULL,
                              p_buffer->p_buffer, p_buffer->i_buffer );
        block_Release( p_buffer );

        p_buffer = p_next;
    }

    return i_write;
}

Laurent Aimar's avatar
Laurent Aimar committed
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
/*****************************************************************************
 * Control:
 *****************************************************************************/
static int Control( access_t *p_access, int i_query, va_list args )
{
    vlc_bool_t   *pb_bool;
    int          *pi_int;
    int64_t      *pi_64;
    vlc_value_t  val;

    switch( i_query )
    {
        /* */
        case ACCESS_CAN_SEEK:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_TRUE;
            break;
        case ACCESS_CAN_FASTSEEK:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_FALSE;
            break;
        case ACCESS_CAN_PAUSE:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_TRUE;    /* FIXME */
            break;
        case ACCESS_CAN_CONTROL_PACE:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_TRUE;    /* FIXME */
            break;

        /* */
        case ACCESS_GET_MTU:
            pi_int = (int*)va_arg( args, int * );
            *pi_int = 0;
            break;

        case ACCESS_GET_PTS_DELAY:
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            var_Get( p_access, "ftp-caching", &val );
544
            *pi_64 = (int64_t)var_GetInteger( p_access, "ftp-caching" ) * I64C(1000);
Laurent Aimar's avatar
Laurent Aimar committed
545
            break;
546

Laurent Aimar's avatar
Laurent Aimar committed
547
548
549
550
551
        /* */
        case ACCESS_SET_PAUSE_STATE:
            /* Nothing to do */
            break;

552
553
554
        case ACCESS_GET_TITLE_INFO:
        case ACCESS_SET_TITLE:
        case ACCESS_SET_SEEKPOINT:
555
        case ACCESS_SET_PRIVATE_ID_STATE:
556
557
            return VLC_EGENERIC;

Laurent Aimar's avatar
Laurent Aimar committed
558
        default:
559
            msg_Warn( p_access, "unimplemented query in control" );
Laurent Aimar's avatar
Laurent Aimar committed
560
561
562
563
            return VLC_EGENERIC;

    }
    return VLC_SUCCESS;
564
565
}

566
567
568
/*****************************************************************************
 * ftp_*:
 *****************************************************************************/
569
570
static int ftp_SendCommand( vlc_object_t *p_access, access_sys_t *p_sys,
                            const char *psz_fmt, ... )
571
{
572
573
    va_list      args;
    char         *psz_cmd;
574
575

    va_start( args, psz_fmt );
576
577
    vasprintf( &psz_cmd, psz_fmt, args );
    va_end( args );
578

Laurent Aimar's avatar
Laurent Aimar committed
579
    msg_Dbg( p_access, "ftp_SendCommand:\"%s\"", psz_cmd);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
580
581
    if( net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL, "%s\r\n",
                    psz_cmd ) < 0 )
582
    {
Laurent Aimar's avatar
Laurent Aimar committed
583
        msg_Err( p_access, "failed to send command" );
584
        return VLC_EGENERIC;
585
    }
586
    return VLC_SUCCESS;
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
}

/* TODO support this s**t :
 RFC 959 allows the client to send certain TELNET strings at any moment,
 even in the middle of a request:

 * \377\377.
 * \377\376x where x is one byte.
 * \377\375x where x is one byte. The server is obliged to send \377\374x
 *                                immediately after reading x.
 * \377\374x where x is one byte.
 * \377\373x where x is one byte. The server is obliged to send \377\376x
 *                                immediately after reading x.
 * \377x for any other byte x.

 These strings are not part of the requests, except in the case \377\377,
 where the request contains one \377. */
604
static int ftp_ReadCommand( vlc_object_t *p_access, access_sys_t *p_sys,
gbazin's avatar
   
gbazin committed
605
                            int *pi_answer, char **ppsz_answer )
606
{
607
608
609
    char         *psz_line;
    int          i_answer;

610
    psz_line = net_Gets( p_access, p_sys->fd_cmd, NULL );
Laurent Aimar's avatar
Laurent Aimar committed
611
    msg_Dbg( p_access, "answer=%s", psz_line );
612
613
    if( psz_line == NULL || strlen( psz_line ) < 3 )
    {
Laurent Aimar's avatar
Laurent Aimar committed
614
        msg_Err( p_access, "cannot get answer" );
615
616
617
618
        if( psz_line ) free( psz_line );
        if( pi_answer ) *pi_answer    = 500;
        if( ppsz_answer ) *ppsz_answer  = NULL;
        return -1;
619
620
    }

621
622
623
624
625
626
627
628
629
    if( psz_line[3] == '-' )    /* Multiple response */
    {
        char end[4];

        memcpy( end, psz_line, 3 );
        end[3] = ' ';

        for( ;; )
        {
630
            char *psz_tmp = net_Gets( p_access, p_sys->fd_cmd, NULL );
631
632
633
634
635
636
637
638
639
640
641
642
643

            if( psz_tmp == NULL )   /* Error */
                break;

            if( !strncmp( psz_tmp, end, 4 ) )
            {
                free( psz_tmp );
                break;
            }
            free( psz_tmp );
        }
    }

644
    i_answer = atoi( psz_line );
645
646
647
648

    if( pi_answer ) *pi_answer = i_answer;
    if( ppsz_answer )
    {
649
        *ppsz_answer = psz_line;
650
651
652
    }
    else
    {
653
        free( psz_line );
654
655
656
657
    }
    return( i_answer / 100 );
}

658
659
static int ftp_StartStream( vlc_object_t *p_access, access_sys_t *p_sys,
                            off_t i_start )
660
{
661
    char psz_ipv4[16], *psz_ip = p_sys->sz_epsv_ip;
662
663
664
665
    int  i_answer;
    char *psz_arg, *psz_parser;
    int  i_port;

666
    assert( p_sys->fd_data == -1 );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
667

668
669
    if( ( ftp_SendCommand( p_access, p_sys, *psz_ip ? "EPSV" : "PASV" ) < 0 )
     || ( ftp_ReadCommand( p_access, p_sys, &i_answer, &psz_arg ) != 2 ) )
670
    {
671
        msg_Err( p_access, "cannot set passive mode" );
672
        return VLC_EGENERIC;
673
    }
674

675
    psz_parser = strchr( psz_arg, '(' );
676
    if( psz_parser == NULL )
677
    {
678
        free( psz_arg );
679
        msg_Err( p_access, "cannot parse passive mode response" );
680
        return VLC_EGENERIC;
681
    }
682

683
    if( psz_ip != NULL )
684
685
686
687
688
689
690
691
692
693
694
695
696
    {
        char psz_fmt[7] = "(|||%u";
        psz_fmt[1] = psz_fmt[2] = psz_fmt[3] = psz_parser[1];

        if( sscanf( psz_parser, psz_fmt, &i_port ) < 1 )
        {
            free( psz_arg );
            msg_Err( p_access, "cannot parse passive mode response" );
            return VLC_EGENERIC;
        }
    }
    else
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
697
698
        unsigned  a1, a2, a3, a4, p1, p2;

699
700
701
702
703
704
705
706
707
708
709
710
711
        if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
                      &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
         || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
        {
            free( psz_arg );
            msg_Err( p_access, "cannot parse passive mode response" );
            return VLC_EGENERIC;
        }

        sprintf( psz_ipv4, "%u.%u.%u.%u", a1, a2, a3, a4 );
        psz_ip = psz_ipv4;
        i_port = (p1 << 8) | p2;
    }
712
    free( psz_arg );
713

Laurent Aimar's avatar
Laurent Aimar committed
714
    msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
715

716
717
    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) != 2 )
718
    {
hartman's avatar
hartman committed
719
        msg_Err( p_access, "cannot set binary transfer mode" );
720
        return VLC_EGENERIC;
721
722
723
724
    }

    if( i_start > 0 )
    {
725
726
        if( ftp_SendCommand( p_access, p_sys, "REST "I64Fu, i_start ) < 0 ||
            ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) > 3 )
727
        {
728
            msg_Err( p_access, "cannot set restart offset" );
729
            return VLC_EGENERIC;
730
731
732
        }
    }

Laurent Aimar's avatar
Laurent Aimar committed
733
    msg_Dbg( p_access, "waiting for data connection..." );
734
    p_sys->fd_data = net_ConnectTCP( p_access, psz_ip, i_port );
735
    if( p_sys->fd_data < 0 )
736
    {
Laurent Aimar's avatar
Laurent Aimar committed
737
        msg_Err( p_access, "failed to connect with server" );
738
        return VLC_EGENERIC;
739
    }
Laurent Aimar's avatar
Laurent Aimar committed
740
    msg_Dbg( p_access, "connection with \"%s:%d\" successful",
741
742
743
             psz_ip, i_port );

    /* "1xx" message */
744
745
746
747
748
    if( ftp_SendCommand( p_access, p_sys, "%s %s",
                         (p_access->i_object_type == VLC_OBJECT_ACCESS)
                                 ? "RETR" : "STOR",
                         p_sys->url.psz_path ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) > 2 )
749
    {
Laurent Aimar's avatar
Laurent Aimar committed
750
        msg_Err( p_access, "cannot retreive file" );
751
        return VLC_EGENERIC;
752
    }
753
    return VLC_SUCCESS;
754
755
}

756
static int ftp_StopStream ( vlc_object_t *p_access, access_sys_t *p_sys )
757
{
758
    if( ftp_SendCommand( p_access, p_sys, "ABOR" ) < 0 )
759
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
760
        msg_Warn( p_access, "cannot abort file" );
Laurent Aimar's avatar
Laurent Aimar committed
761
762
763
        if(  p_sys->fd_data > 0 )
            net_Close( p_sys->fd_data );
        p_sys->fd_data = -1;
764
        return VLC_EGENERIC;
765
    }
766
767

    if( p_sys->fd_data != -1 )
Laurent Aimar's avatar
Laurent Aimar committed
768
769
770
    {
        net_Close( p_sys->fd_data );
        p_sys->fd_data = -1;
771
        ftp_ReadCommand( p_access, p_sys, NULL, NULL );
Laurent Aimar's avatar
Laurent Aimar committed
772
    }
773
    ftp_ReadCommand( p_access, p_sys, NULL, NULL );
774

775
    return VLC_SUCCESS;
776
}