Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
4349 Serge 1
/*
2
 * Copyright (c) 2013 Lukasz Marek 
3
 *
4
 * This file is part of FFmpeg.
5
 *
6
 * FFmpeg is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * FFmpeg is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with FFmpeg; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
 
21
#include "libavutil/avstring.h"
22
#include "avformat.h"
23
#include "internal.h"
24
#include "url.h"
25
#include "libavutil/opt.h"
26
#include "libavutil/bprint.h"
27
 
28
#define CONTROL_BUFFER_SIZE 1024
29
#define CREDENTIALS_BUFFER_SIZE 128
30
 
31
typedef enum {
32
    UNKNOWN,
33
    READY,
34
    DOWNLOADING,
35
    UPLOADING,
36
    DISCONNECTED
37
} FTPState;
38
 
39
typedef struct {
40
    const AVClass *class;
41
    URLContext *conn_control;                    /**< Control connection */
42
    URLContext *conn_data;                       /**< Data connection, NULL when not connected */
43
    uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
44
    uint8_t *control_buf_ptr, *control_buf_end;
45
    int server_data_port;                        /**< Data connection port opened by server, -1 on error. */
46
    int server_control_port;                     /**< Control connection port, default is 21 */
47
    char hostname[512];                          /**< Server address. */
48
    char credencials[CREDENTIALS_BUFFER_SIZE];   /**< Authentication data */
49
    char path[MAX_URL_SIZE];                     /**< Path to resource on server. */
50
    int64_t filesize;                            /**< Size of file on server, -1 on error. */
51
    int64_t position;                            /**< Current position, calculated. */
52
    int rw_timeout;                              /**< Network timeout. */
53
    const char *anonymous_password;              /**< Password to be used for anonymous user. An email should be used. */
54
    int write_seekable;                          /**< Control seekability, 0 = disable, 1 = enable. */
55
    FTPState state;                              /**< State of data connection */
56
} FTPContext;
57
 
58
#define OFFSET(x) offsetof(FTPContext, x)
59
#define D AV_OPT_FLAG_DECODING_PARAM
60
#define E AV_OPT_FLAG_ENCODING_PARAM
61
static const AVOption options[] = {
62
    {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
63
    {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
64
    {"ftp-anonymous-password", "password for anonymous login. E-mail address should be used.", OFFSET(anonymous_password), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
65
    {NULL}
66
};
67
 
68
static const AVClass ftp_context_class = {
69
    .class_name     = "ftp",
70
    .item_name      = av_default_item_name,
71
    .option         = options,
72
    .version        = LIBAVUTIL_VERSION_INT,
73
};
74
 
75
static int ftp_getc(FTPContext *s)
76
{
77
    int len;
78
    if (s->control_buf_ptr >= s->control_buf_end) {
79
        len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
80
        if (len < 0) {
81
            return len;
82
        } else if (!len) {
83
            return -1;
84
        } else {
85
            s->control_buf_ptr = s->control_buffer;
86
            s->control_buf_end = s->control_buffer + len;
87
        }
88
    }
89
    return *s->control_buf_ptr++;
90
}
91
 
92
static int ftp_get_line(FTPContext *s, char *line, int line_size)
93
{
94
    int ch;
95
    char *q = line;
96
 
97
    for (;;) {
98
        ch = ftp_getc(s);
99
        if (ch < 0) {
100
            return ch;
101
        }
102
        if (ch == '\n') {
103
            /* process line */
104
            if (q > line && q[-1] == '\r')
105
                q--;
106
            *q = '\0';
107
            return 0;
108
        } else {
109
            if ((q - line) < line_size - 1)
110
                *q++ = ch;
111
        }
112
    }
113
}
114
 
115
/*
116
 * This routine returns ftp server response code.
117
 * Server may send more than one response for a certain command.
118
 * First expected code is returned.
119
 */
120
static int ftp_status(FTPContext *s, char **line, const int response_codes[])
121
{
122
    int err, i, dash = 0, result = 0, code_found = 0;
123
    char buf[CONTROL_BUFFER_SIZE];
124
    AVBPrint line_buffer;
125
 
126
    if (line)
127
        av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
128
 
129
    while (!code_found || dash) {
130
        if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
131
            if (line)
132
                av_bprint_finalize(&line_buffer, NULL);
133
            return err;
134
        }
135
 
136
        av_log(s, AV_LOG_DEBUG, "%s\n", buf);
137
 
138
        if (strlen(buf) < 4)
139
            continue;
140
 
141
        err = 0;
142
        for (i = 0; i < 3; ++i) {
143
            if (buf[i] < '0' || buf[i] > '9')
144
                continue;
145
            err *= 10;
146
            err += buf[i] - '0';
147
        }
148
        dash = !!(buf[3] == '-');
149
 
150
        for (i = 0; response_codes[i]; ++i) {
151
            if (err == response_codes[i]) {
152
                if (line)
153
                    av_bprintf(&line_buffer, "%s", buf);
154
                code_found = 1;
155
                result = err;
156
                break;
157
            }
158
        }
159
    }
160
 
161
    if (line)
162
        av_bprint_finalize(&line_buffer, line);
163
    return result;
164
}
165
 
166
static int ftp_send_command(FTPContext *s, const char *command,
167
                            const int response_codes[], char **response)
168
{
169
    int err;
170
 
171
    if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
172
        return err;
173
    if (!err)
174
        return -1;
175
 
176
    /* return status */
177
    if (response_codes) {
178
        return ftp_status(s, response, response_codes);
179
    }
180
    return 0;
181
}
182
 
183
static void ftp_close_data_connection(FTPContext *s)
184
{
185
    ffurl_closep(&s->conn_data);
186
    s->position = 0;
187
    s->state = DISCONNECTED;
188
}
189
 
190
static void ftp_close_both_connections(FTPContext *s)
191
{
192
    ffurl_closep(&s->conn_control);
193
    ftp_close_data_connection(s);
194
}
195
 
196
static int ftp_auth(FTPContext *s)
197
{
198
    const char *user = NULL, *pass = NULL;
199
    char *end = NULL, buf[CONTROL_BUFFER_SIZE], credencials[CREDENTIALS_BUFFER_SIZE];
200
    int err;
201
    static const int user_codes[] = {331, 230, 500, 530, 0}; /* 500, 530 are incorrect codes */
202
    static const int pass_codes[] = {230, 503, 530, 0}; /* 503, 530 are incorrect codes */
203
 
204
    /* Authentication may be repeated, original string has to be saved */
205
    av_strlcpy(credencials, s->credencials, sizeof(credencials));
206
 
207
    user = av_strtok(credencials, ":", &end);
208
    pass = av_strtok(end, ":", &end);
209
 
210
    if (!user) {
211
        user = "anonymous";
212
        pass = s->anonymous_password ? s->anonymous_password : "nopassword";
213
    }
214
 
215
    snprintf(buf, sizeof(buf), "USER %s\r\n", user);
216
    err = ftp_send_command(s, buf, user_codes, NULL);
217
    if (err == 331) {
218
        if (pass) {
219
            snprintf(buf, sizeof(buf), "PASS %s\r\n", pass);
220
            err = ftp_send_command(s, buf, pass_codes, NULL);
221
        } else
222
            return AVERROR(EACCES);
223
    }
224
    if (err != 230)
225
        return AVERROR(EACCES);
226
 
227
    return 0;
228
}
229
 
230
static int ftp_passive_mode(FTPContext *s)
231
{
232
    char *res = NULL, *start = NULL, *end = NULL;
233
    int i;
234
    static const char *command = "PASV\r\n";
235
    static const int pasv_codes[] = {227, 501, 0}; /* 501 is incorrect code */
236
 
237
    if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
238
        goto fail;
239
 
240
    for (i = 0; res[i]; ++i) {
241
        if (res[i] == '(') {
242
            start = res + i + 1;
243
        } else if (res[i] == ')') {
244
            end = res + i;
245
            break;
246
        }
247
    }
248
    if (!start || !end)
249
        goto fail;
250
 
251
    *end  = '\0';
252
    /* skip ip */
253
    if (!av_strtok(start, ",", &end)) goto fail;
254
    if (!av_strtok(end, ",", &end)) goto fail;
255
    if (!av_strtok(end, ",", &end)) goto fail;
256
    if (!av_strtok(end, ",", &end)) goto fail;
257
 
258
    /* parse port number */
259
    start = av_strtok(end, ",", &end);
260
    if (!start) goto fail;
261
    s->server_data_port = atoi(start) * 256;
262
    start = av_strtok(end, ",", &end);
263
    if (!start) goto fail;
264
    s->server_data_port += atoi(start);
265
    av_dlog(s, "Server data port: %d\n", s->server_data_port);
266
 
267
    av_free(res);
268
    return 0;
269
 
270
  fail:
271
    av_free(res);
272
    s->server_data_port = -1;
273
    av_log(s, AV_LOG_ERROR, "Set passive mode failed\n"
274
                            "Your FTP server may use IPv6 which is not supported yet.\n");
275
    return AVERROR(EIO);
276
}
277
 
278
static int ftp_current_dir(FTPContext *s)
279
{
280
    char *res = NULL, *start = NULL, *end = NULL;
281
    int i;
282
    static const char *command = "PWD\r\n";
283
    static const int pwd_codes[] = {257, 0};
284
 
285
    if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
286
        goto fail;
287
 
288
    for (i = 0; res[i]; ++i) {
289
        if (res[i] == '"') {
290
            if (!start) {
291
                start = res + i + 1;
292
                continue;
293
            }
294
            end = res + i;
295
            break;
296
        }
297
    }
298
 
299
    if (!end)
300
        goto fail;
301
 
302
    if (end > res && end[-1] == '/') {
303
        end[-1] = '\0';
304
    } else
305
        *end = '\0';
306
    av_strlcpy(s->path, start, sizeof(s->path));
307
 
308
    av_free(res);
309
    return 0;
310
 
311
  fail:
312
    av_free(res);
313
    return AVERROR(EIO);
314
}
315
 
316
static int ftp_file_size(FTPContext *s)
317
{
318
    char command[CONTROL_BUFFER_SIZE];
319
    char *res = NULL;
320
    static const int size_codes[] = {213, 501, 550, 0}; /* 501, 550 are incorrect codes */
321
 
322
    snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
323
    if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
324
        s->filesize = strtoll(&res[4], NULL, 10);
325
    } else {
326
        s->filesize = -1;
327
        av_free(res);
328
        return AVERROR(EIO);
329
    }
330
 
331
    av_free(res);
332
    return 0;
333
}
334
 
335
static int ftp_retrieve(FTPContext *s)
336
{
337
    char command[CONTROL_BUFFER_SIZE];
338
    static const int retr_codes[] = {150, 550, 554, 0}; /* 550, 554 are incorrect codes */
339
 
340
    snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
341
    if (ftp_send_command(s, command, retr_codes, NULL) != 150)
342
        return AVERROR(EIO);
343
 
344
    s->state = DOWNLOADING;
345
 
346
    return 0;
347
}
348
 
349
static int ftp_store(FTPContext *s)
350
{
351
    char command[CONTROL_BUFFER_SIZE];
352
    static const int stor_codes[] = {150, 0};
353
 
354
    snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
355
    if (ftp_send_command(s, command, stor_codes, NULL) != 150)
356
        return AVERROR(EIO);
357
 
358
    s->state = UPLOADING;
359
 
360
    return 0;
361
}
362
 
363
static int ftp_type(FTPContext *s)
364
{
365
    static const char *command = "TYPE I\r\n";
366
    static const int type_codes[] = {200, 500, 504, 0}; /* 500, 504 are incorrect codes */
367
 
368
    if (ftp_send_command(s, command, type_codes, NULL) != 200)
369
        return AVERROR(EIO);
370
 
371
    return 0;
372
}
373
 
374
static int ftp_restart(FTPContext *s, int64_t pos)
375
{
376
    char command[CONTROL_BUFFER_SIZE];
377
    static const int rest_codes[] = {350, 500, 501, 0}; /* 500, 501 are incorrect codes */
378
 
379
    snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
380
    if (ftp_send_command(s, command, rest_codes, NULL) != 350)
381
        return AVERROR(EIO);
382
 
383
    return 0;
384
}
385
 
386
static int ftp_connect_control_connection(URLContext *h)
387
{
388
    char buf[CONTROL_BUFFER_SIZE], opts_format[20], *response = NULL;
389
    int err;
390
    AVDictionary *opts = NULL;
391
    FTPContext *s = h->priv_data;
392
    static const int connect_codes[] = {220, 0};
393
 
394
    if (!s->conn_control) {
395
        ff_url_join(buf, sizeof(buf), "tcp", NULL,
396
                    s->hostname, s->server_control_port, NULL);
397
        if (s->rw_timeout != -1) {
398
            snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
399
            av_dict_set(&opts, "timeout", opts_format, 0);
400
        } /* if option is not given, don't pass it and let tcp use its own default */
401
        err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
402
                         &h->interrupt_callback, &opts);
403
        av_dict_free(&opts);
404
        if (err < 0) {
405
            av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
406
            return err;
407
        }
408
 
409
        /* check if server is ready */
410
        if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
411
            av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
412
            return AVERROR(EACCES);
413
        }
414
 
415
        if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
416
            av_log(h, AV_LOG_WARNING, "Pure-FTPd server is used as an output protocol. It is known issue this implementation may produce incorrect content and it cannot be fixed at this moment.");
417
        }
418
        av_free(response);
419
 
420
        if ((err = ftp_auth(s)) < 0) {
421
            av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
422
            return err;
423
        }
424
 
425
        if ((err = ftp_type(s)) < 0) {
426
            av_log(h, AV_LOG_ERROR, "Set content type failed\n");
427
            return err;
428
        }
429
    }
430
    return 0;
431
}
432
 
433
static int ftp_connect_data_connection(URLContext *h)
434
{
435
    int err;
436
    char buf[CONTROL_BUFFER_SIZE], opts_format[20];
437
    AVDictionary *opts = NULL;
438
    FTPContext *s = h->priv_data;
439
 
440
    if (!s->conn_data) {
441
        /* Enter passive mode */
442
        if ((err = ftp_passive_mode(s)) < 0)
443
            return err;
444
        /* Open data connection */
445
        ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
446
        if (s->rw_timeout != -1) {
447
            snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
448
            av_dict_set(&opts, "timeout", opts_format, 0);
449
        } /* if option is not given, don't pass it and let tcp use its own default */
450
        err = ffurl_open(&s->conn_data, buf, h->flags,
451
                         &h->interrupt_callback, &opts);
452
        av_dict_free(&opts);
453
        if (err < 0)
454
            return err;
455
 
456
        if (s->position)
457
            if ((err = ftp_restart(s, s->position)) < 0)
458
                return err;
459
    }
460
    s->state = READY;
461
    return 0;
462
}
463
 
464
static int ftp_abort(URLContext *h)
465
{
466
    static const char *command = "ABOR\r\n";
467
    int err;
468
    static const int abor_codes[] = {225, 226, 0};
469
    FTPContext *s = h->priv_data;
470
 
471
    /* According to RCF 959:
472
       "ABOR command tells the server to abort the previous FTP
473
       service command and any associated transfer of data."
474
 
475
       There are FTP server implementations that don't response
476
       to any commands during data transfer in passive mode (including ABOR).
477
 
478
       This implementation closes data connection by force.
479
    */
480
 
481
    if (ftp_send_command(s, command, NULL, NULL) < 0) {
482
        ftp_close_both_connections(s);
483
        if ((err = ftp_connect_control_connection(h)) < 0) {
484
            av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
485
            return err;
486
        }
487
    } else {
488
        ftp_close_data_connection(s);
489
        if (ftp_status(s, NULL, abor_codes) < 225) {
490
            /* wu-ftpd also closes control connection after data connection closing */
491
            ffurl_closep(&s->conn_control);
492
            if ((err = ftp_connect_control_connection(h)) < 0) {
493
                av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
494
                return err;
495
            }
496
        }
497
    }
498
 
499
    return 0;
500
}
501
 
502
static int ftp_open(URLContext *h, const char *url, int flags)
503
{
504
    char proto[10], path[MAX_URL_SIZE];
505
    int err;
506
    FTPContext *s = h->priv_data;
507
 
508
    av_dlog(h, "ftp protocol open\n");
509
 
510
    s->state = DISCONNECTED;
511
    s->filesize = -1;
512
    s->position = 0;
513
 
514
    av_url_split(proto, sizeof(proto),
515
                 s->credencials, sizeof(s->credencials),
516
                 s->hostname, sizeof(s->hostname),
517
                 &s->server_control_port,
518
                 path, sizeof(path),
519
                 url);
520
 
521
    if (s->server_control_port < 0 || s->server_control_port > 65535)
522
        s->server_control_port = 21;
523
 
524
    if ((err = ftp_connect_control_connection(h)) < 0)
525
        goto fail;
526
 
527
    if ((err = ftp_current_dir(s)) < 0)
528
        goto fail;
529
    av_strlcat(s->path, path, sizeof(s->path));
530
 
531
    if (ftp_restart(s, 0) < 0) {
532
        h->is_streamed = 1;
533
    } else {
534
        if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
535
            h->is_streamed = 1;
536
        if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
537
            h->is_streamed = 1;
538
    }
539
 
540
    return 0;
541
 
542
  fail:
543
    av_log(h, AV_LOG_ERROR, "FTP open failed\n");
544
    ffurl_closep(&s->conn_control);
545
    ffurl_closep(&s->conn_data);
546
    return err;
547
}
548
 
549
static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
550
{
551
    FTPContext *s = h->priv_data;
552
    int err;
553
    int64_t new_pos, fake_pos;
554
 
555
    av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
556
 
557
    switch(whence) {
558
    case AVSEEK_SIZE:
559
        return s->filesize;
560
    case SEEK_SET:
561
        new_pos = pos;
562
        break;
563
    case SEEK_CUR:
564
        new_pos = s->position + pos;
565
        break;
566
    case SEEK_END:
567
        if (s->filesize < 0)
568
            return AVERROR(EIO);
569
        new_pos = s->filesize + pos;
570
        break;
571
    default:
572
        return AVERROR(EINVAL);
573
    }
574
 
575
    if  (h->is_streamed)
576
        return AVERROR(EIO);
577
 
578
    /* XXX: Simulate behaviour of lseek in file protocol, which could be treated as a reference */
579
    new_pos = FFMAX(0, new_pos);
580
    fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
581
 
582
    if (fake_pos != s->position) {
583
        if ((err = ftp_abort(h)) < 0)
584
            return err;
585
        s->position = fake_pos;
586
    }
587
    return new_pos;
588
}
589
 
590
static int ftp_read(URLContext *h, unsigned char *buf, int size)
591
{
592
    FTPContext *s = h->priv_data;
593
    int read, err, retry_done = 0;
594
 
595
    av_dlog(h, "ftp protocol read %d bytes\n", size);
596
  retry:
597
    if (s->state == DISCONNECTED) {
598
        /* optimization */
599
        if (s->position >= s->filesize)
600
            return 0;
601
        if ((err = ftp_connect_data_connection(h)) < 0)
602
            return err;
603
    }
604
    if (s->state == READY) {
605
        if (s->position >= s->filesize)
606
            return 0;
607
        if ((err = ftp_retrieve(s)) < 0)
608
            return err;
609
    }
610
    if (s->conn_data && s->state == DOWNLOADING) {
611
        read = ffurl_read(s->conn_data, buf, size);
612
        if (read >= 0) {
613
            s->position += read;
614
            if (s->position >= s->filesize) {
615
                /* server will terminate, but keep current position to avoid madness */
616
                /* save position to restart from it */
617
                int64_t pos = s->position;
618
                if (ftp_abort(h) < 0) {
619
                    s->position = pos;
620
                    return AVERROR(EIO);
621
                }
622
                s->position = pos;
623
            }
624
        }
625
        if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
626
            /* Server closed connection. Probably due to inactivity */
627
            int64_t pos = s->position;
628
            av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
629
            if ((err = ftp_abort(h)) < 0)
630
                return err;
631
            if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
632
                av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
633
                return err;
634
            }
635
            if (!retry_done) {
636
                retry_done = 1;
637
                goto retry;
638
            }
639
        }
640
        return read;
641
    }
642
 
643
    av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
644
    return AVERROR(EIO);
645
}
646
 
647
static int ftp_write(URLContext *h, const unsigned char *buf, int size)
648
{
649
    int err;
650
    FTPContext *s = h->priv_data;
651
    int written;
652
 
653
    av_dlog(h, "ftp protocol write %d bytes\n", size);
654
 
655
    if (s->state == DISCONNECTED) {
656
        if ((err = ftp_connect_data_connection(h)) < 0)
657
            return err;
658
    }
659
    if (s->state == READY) {
660
        if ((err = ftp_store(s)) < 0)
661
            return err;
662
    }
663
    if (s->conn_data && s->state == UPLOADING) {
664
        written = ffurl_write(s->conn_data, buf, size);
665
        if (written > 0) {
666
            s->position += written;
667
            s->filesize = FFMAX(s->filesize, s->position);
668
        }
669
        return written;
670
    }
671
 
672
    av_log(h, AV_LOG_ERROR, "FTP write failed\n");
673
    return AVERROR(EIO);
674
}
675
 
676
static int ftp_close(URLContext *h)
677
{
678
    av_dlog(h, "ftp protocol close\n");
679
 
680
    ftp_close_both_connections(h->priv_data);
681
 
682
    return 0;
683
}
684
 
685
static int ftp_get_file_handle(URLContext *h)
686
{
687
    FTPContext *s = h->priv_data;
688
 
689
    av_dlog(h, "ftp protocol get_file_handle\n");
690
 
691
    if (s->conn_data)
692
        return ffurl_get_file_handle(s->conn_data);
693
 
694
    return AVERROR(EIO);
695
}
696
 
697
static int ftp_shutdown(URLContext *h, int flags)
698
{
699
    FTPContext *s = h->priv_data;
700
 
701
    av_dlog(h, "ftp protocol shutdown\n");
702
 
703
    if (s->conn_data)
704
        return ffurl_shutdown(s->conn_data, flags);
705
 
706
    return AVERROR(EIO);
707
}
708
 
709
URLProtocol ff_ftp_protocol = {
710
    .name                = "ftp",
711
    .url_open            = ftp_open,
712
    .url_read            = ftp_read,
713
    .url_write           = ftp_write,
714
    .url_seek            = ftp_seek,
715
    .url_close           = ftp_close,
716
    .url_get_file_handle = ftp_get_file_handle,
717
    .url_shutdown        = ftp_shutdown,
718
    .priv_data_size      = sizeof(FTPContext),
719
    .priv_data_class     = &ftp_context_class,
720
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
721
};