xref: /third_party/ffmpeg/libavformat/ftp.c (revision cabdff1a)
1/*
2 * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com>
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 <string.h>
22
23#include "libavutil/avstring.h"
24#include "libavutil/internal.h"
25#include "libavutil/parseutils.h"
26#include "avformat.h"
27#include "internal.h"
28#include "url.h"
29#include "urldecode.h"
30#include "libavutil/opt.h"
31#include "libavutil/bprint.h"
32
33#define CONTROL_BUFFER_SIZE 1024
34#define DIR_BUFFER_SIZE 4096
35
36typedef enum {
37    UNKNOWN,
38    READY,
39    DOWNLOADING,
40    UPLOADING,
41    LISTING_DIR,
42    DISCONNECTED,
43    ENDOFFILE,
44} FTPState;
45
46typedef enum {
47    UNKNOWN_METHOD,
48    NLST,
49    MLSD
50} FTPListingMethod;
51
52typedef struct {
53    const AVClass *class;
54    URLContext *conn_control;                    /**< Control connection */
55    URLContext *conn_data;                       /**< Data connection, NULL when not connected */
56    uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
57    uint8_t *control_buf_ptr, *control_buf_end;
58    int server_data_port;                        /**< Data connection port opened by server, -1 on error. */
59    int server_control_port;                     /**< Control connection port, default is 21 */
60    char *hostname;                              /**< Server address. */
61    char *user;                                  /**< Server user */
62    char *password;                              /**< Server user's password */
63    char *path;                                  /**< Path to resource on server. */
64    int64_t filesize;                            /**< Size of file on server, -1 on error. */
65    int64_t position;                            /**< Current position, calculated. */
66    int rw_timeout;                              /**< Network timeout. */
67    const char *anonymous_password;              /**< Password to be used for anonymous user. An email should be used. */
68    int write_seekable;                          /**< Control seekability, 0 = disable, 1 = enable. */
69    FTPState state;                              /**< State of data connection */
70    FTPListingMethod listing_method;             /**< Called listing method */
71    char *features;                              /**< List of server's features represented as raw response */
72    char *dir_buffer;
73    size_t dir_buffer_size;
74    size_t dir_buffer_offset;
75    int utf8;
76    const char *option_user;                     /**< User to be used if none given in the URL */
77    const char *option_password;                 /**< Password to be used if none given in the URL */
78} FTPContext;
79
80#define OFFSET(x) offsetof(FTPContext, x)
81#define D AV_OPT_FLAG_DECODING_PARAM
82#define E AV_OPT_FLAG_ENCODING_PARAM
83static const AVOption options[] = {
84    {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
85    {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
86    {"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 },
87    {"ftp-user", "user for FTP login. Overridden by whatever is in the URL.", OFFSET(option_user), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
88    {"ftp-password", "password for FTP login. Overridden by whatever is in the URL.", OFFSET(option_password), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
89    {NULL}
90};
91
92static const AVClass ftp_context_class = {
93    .class_name     = "ftp",
94    .item_name      = av_default_item_name,
95    .option         = options,
96    .version        = LIBAVUTIL_VERSION_INT,
97};
98
99static int ftp_close(URLContext *h);
100
101static int ftp_getc(FTPContext *s)
102{
103    int len;
104    if (s->control_buf_ptr >= s->control_buf_end) {
105        len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
106        if (len < 0) {
107            return len;
108        } else if (!len) {
109            return -1;
110        } else {
111            s->control_buf_ptr = s->control_buffer;
112            s->control_buf_end = s->control_buffer + len;
113        }
114    }
115    return *s->control_buf_ptr++;
116}
117
118static int ftp_get_line(FTPContext *s, char *line, int line_size)
119{
120    int ch;
121    char *q = line;
122
123    for (;;) {
124        ch = ftp_getc(s);
125        if (ch < 0) {
126            return ch;
127        }
128        if (ch == '\n') {
129            /* process line */
130            if (q > line && q[-1] == '\r')
131                q--;
132            *q = '\0';
133            return 0;
134        } else {
135            if ((q - line) < line_size - 1)
136                *q++ = ch;
137        }
138    }
139}
140
141/*
142 * This routine returns ftp server response code.
143 * Server may send more than one response for a certain command.
144 * First expected code is returned.
145 */
146static int ftp_status(FTPContext *s, char **line, const int response_codes[])
147{
148    int err, i, dash = 0, result = 0, code_found = 0, linesize;
149    char buf[CONTROL_BUFFER_SIZE];
150    AVBPrint line_buffer;
151
152    if (line)
153        av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
154
155    while (!code_found || dash) {
156        if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
157            if (line)
158                av_bprint_finalize(&line_buffer, NULL);
159            return err;
160        }
161
162        av_log(s, AV_LOG_DEBUG, "%s\n", buf);
163
164        linesize = strlen(buf);
165        err = 0;
166        if (linesize >= 3) {
167            for (i = 0; i < 3; ++i) {
168                if (buf[i] < '0' || buf[i] > '9') {
169                    err = 0;
170                    break;
171                }
172                err *= 10;
173                err += buf[i] - '0';
174            }
175        }
176
177        if (!code_found) {
178            if (err >= 500) {
179                code_found = 1;
180                result = err;
181            } else
182                for (i = 0; response_codes[i]; ++i) {
183                    if (err == response_codes[i]) {
184                        code_found = 1;
185                        result = err;
186                        break;
187                    }
188                }
189        }
190        if (code_found) {
191            if (line)
192                av_bprintf(&line_buffer, "%s\r\n", buf);
193            if (linesize >= 4) {
194                if (!dash && buf[3] == '-')
195                    dash = err;
196                else if (err == dash && buf[3] == ' ')
197                    dash = 0;
198            }
199        }
200    }
201
202    if (line)
203        av_bprint_finalize(&line_buffer, line);
204    return result;
205}
206
207static int ftp_send_command(FTPContext *s, const char *command,
208                            const int response_codes[], char **response)
209{
210    int err;
211
212    ff_dlog(s, "%s", command);
213
214    if (response)
215        *response = NULL;
216
217    if (!s->conn_control)
218        return AVERROR(EIO);
219
220    if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
221        return err;
222    if (!err)
223        return -1;
224
225    /* return status */
226    if (response_codes) {
227        return ftp_status(s, response, response_codes);
228    }
229    return 0;
230}
231
232static void ftp_close_data_connection(FTPContext *s)
233{
234    ffurl_closep(&s->conn_data);
235    s->state = DISCONNECTED;
236}
237
238static void ftp_close_both_connections(FTPContext *s)
239{
240    ffurl_closep(&s->conn_control);
241    ftp_close_data_connection(s);
242}
243
244static int ftp_auth(FTPContext *s)
245{
246    char buf[CONTROL_BUFFER_SIZE];
247    int err;
248    static const int user_codes[] = {331, 230, 0};
249    static const int pass_codes[] = {230, 0};
250
251    if (strpbrk(s->user, "\r\n"))
252        return AVERROR(EINVAL);
253    err = snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
254    if (err >= sizeof(buf))
255        return AVERROR(ENOSYS);
256
257    err = ftp_send_command(s, buf, user_codes, NULL);
258    if (err == 331) {
259        if (s->password) {
260            if (strpbrk(s->password, "\r\n"))
261                return AVERROR(EINVAL);
262            err = snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
263            if (err >= sizeof(buf))
264                return AVERROR(ENOSYS);
265
266            err = ftp_send_command(s, buf, pass_codes, NULL);
267        } else
268            return AVERROR(EACCES);
269    }
270    if (err != 230)
271        return AVERROR(EACCES);
272
273    return 0;
274}
275
276static int ftp_passive_mode_epsv(FTPContext *s)
277{
278    char *res = NULL, *start = NULL, *end = NULL;
279    int i;
280    static const char d = '|';
281    static const char *command = "EPSV\r\n";
282    static const int epsv_codes[] = {229, 0};
283
284    if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
285        goto fail;
286
287    for (i = 0; res[i]; ++i) {
288        if (res[i] == '(') {
289            start = res + i + 1;
290        } else if (res[i] == ')') {
291            end = res + i;
292            break;
293        }
294    }
295    if (!start || !end)
296        goto fail;
297
298    *end = '\0';
299    if (strlen(start) < 5)
300        goto fail;
301    if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
302        goto fail;
303    start += 3;
304    end[-1] = '\0';
305
306    s->server_data_port = atoi(start);
307    ff_dlog(s, "Server data port: %d\n", s->server_data_port);
308
309    av_free(res);
310    return 0;
311
312  fail:
313    av_free(res);
314    s->server_data_port = -1;
315    return AVERROR(ENOSYS);
316}
317
318static int ftp_passive_mode(FTPContext *s)
319{
320    char *res = NULL, *start = NULL, *end = NULL;
321    int i;
322    static const char *command = "PASV\r\n";
323    static const int pasv_codes[] = {227, 0};
324
325    if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
326        goto fail;
327
328    for (i = 0; res[i]; ++i) {
329        if (res[i] == '(') {
330            start = res + i + 1;
331        } else if (res[i] == ')') {
332            end = res + i;
333            break;
334        }
335    }
336    if (!start || !end)
337        goto fail;
338
339    *end  = '\0';
340    /* skip ip */
341    if (!av_strtok(start, ",", &end)) goto fail;
342    if (!av_strtok(NULL, ",", &end)) goto fail;
343    if (!av_strtok(NULL, ",", &end)) goto fail;
344    if (!av_strtok(NULL, ",", &end)) goto fail;
345
346    /* parse port number */
347    start = av_strtok(NULL, ",", &end);
348    if (!start) goto fail;
349    s->server_data_port = atoi(start) * 256;
350    start = av_strtok(NULL, ",", &end);
351    if (!start) goto fail;
352    s->server_data_port += atoi(start);
353    ff_dlog(s, "Server data port: %d\n", s->server_data_port);
354
355    av_free(res);
356    return 0;
357
358  fail:
359    av_free(res);
360    s->server_data_port = -1;
361    return AVERROR(EIO);
362}
363
364static int ftp_current_dir(FTPContext *s)
365{
366    char *res = NULL, *start = NULL, *end = NULL;
367    int i;
368    static const char *command = "PWD\r\n";
369    static const int pwd_codes[] = {257, 0};
370
371    if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
372        goto fail;
373
374    for (i = 0; res[i]; ++i) {
375        if (res[i] == '"') {
376            if (!start) {
377                start = res + i + 1;
378                continue;
379            }
380            end = res + i;
381            break;
382        }
383    }
384
385    if (!end)
386        goto fail;
387
388    *end = '\0';
389    s->path = av_strdup(start);
390
391    av_free(res);
392
393    if (!s->path)
394        return AVERROR(ENOMEM);
395    return 0;
396
397  fail:
398    av_free(res);
399    return AVERROR(EIO);
400}
401
402static int ftp_file_size(FTPContext *s)
403{
404    char command[CONTROL_BUFFER_SIZE];
405    char *res = NULL;
406    int ret;
407    static const int size_codes[] = {213, 0};
408
409    ret = snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
410    if (ret >= sizeof(command))
411        return AVERROR(ENOSYS);
412
413    if (ftp_send_command(s, command, size_codes, &res) == 213 && res && strlen(res) > 4) {
414        s->filesize = strtoll(&res[4], NULL, 10);
415    } else {
416        s->filesize = -1;
417        av_free(res);
418        return AVERROR(EIO);
419    }
420
421    av_free(res);
422    return 0;
423}
424
425static int ftp_retrieve(FTPContext *s)
426{
427    char command[CONTROL_BUFFER_SIZE];
428    static const int retr_codes[] = {150, 125, 0};
429    int resp_code, ret;
430
431    ret = snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
432    if (ret >= sizeof(command))
433        return AVERROR(ENOSYS);
434
435    resp_code = ftp_send_command(s, command, retr_codes, NULL);
436    if (resp_code != 125 && resp_code != 150)
437        return AVERROR(EIO);
438
439    s->state = DOWNLOADING;
440
441    return 0;
442}
443
444static int ftp_store(FTPContext *s)
445{
446    char command[CONTROL_BUFFER_SIZE];
447    static const int stor_codes[] = {150, 125, 0};
448    int resp_code, ret;
449
450    ret = snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
451    if (ret >= sizeof(command))
452        return AVERROR(ENOSYS);
453
454    resp_code = ftp_send_command(s, command, stor_codes, NULL);
455    if (resp_code != 125 && resp_code != 150)
456        return AVERROR(EIO);
457
458    s->state = UPLOADING;
459
460    return 0;
461}
462
463static int ftp_type(FTPContext *s)
464{
465    static const char *command = "TYPE I\r\n";
466    static const int type_codes[] = {200, 0};
467
468    if (ftp_send_command(s, command, type_codes, NULL) != 200)
469        return AVERROR(EIO);
470
471    return 0;
472}
473
474static int ftp_restart(FTPContext *s, int64_t pos)
475{
476    char command[CONTROL_BUFFER_SIZE];
477    static const int rest_codes[] = {350, 0};
478
479    snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
480    if (ftp_send_command(s, command, rest_codes, NULL) != 350)
481        return AVERROR(EIO);
482
483    return 0;
484}
485
486static int ftp_set_dir(FTPContext *s)
487{
488    static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */
489    char command[MAX_URL_SIZE];
490    int ret;
491
492    ret = snprintf(command, sizeof(command), "CWD %s\r\n", s->path);
493    if (ret >= sizeof(command))
494        return AVERROR(ENOSYS);
495
496    if (ftp_send_command(s, command, cwd_codes, NULL) != 250)
497        return AVERROR(EIO);
498    return 0;
499}
500
501static int ftp_list_mlsd(FTPContext *s)
502{
503    static const char *command = "MLSD\r\n";
504    static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */
505
506    if (ftp_send_command(s, command, mlsd_codes, NULL) != 150)
507        return AVERROR(ENOSYS);
508    s->listing_method = MLSD;
509    return 0;
510}
511
512static int ftp_list_nlst(FTPContext *s)
513{
514    static const char *command = "NLST\r\n";
515    static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0};
516
517    if (ftp_send_command(s, command, nlst_codes, NULL) != 226)
518        return AVERROR(ENOSYS);
519    s->listing_method = NLST;
520    return 0;
521}
522
523static int ftp_list(FTPContext *s)
524{
525    int ret;
526    s->state = LISTING_DIR;
527
528    if ((ret = ftp_list_mlsd(s)) < 0)
529        ret = ftp_list_nlst(s);
530
531    return ret;
532}
533
534static int ftp_has_feature(FTPContext *s, const char *feature_name)
535{
536    if (!s->features)
537        return 0;
538
539    return av_stristr(s->features, feature_name) != NULL;
540}
541
542static int ftp_features(FTPContext *s)
543{
544    static const char *feat_command        = "FEAT\r\n";
545    static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
546    static const int feat_codes[] = {211, 0};
547    static const int opts_codes[] = {200, 202, 451, 0};
548
549    av_freep(&s->features);
550    if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
551        av_freep(&s->features);
552    }
553
554    if (ftp_has_feature(s, "UTF8")) {
555        int ret = ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
556        if (ret == 200 || ret == 202)
557            s->utf8 = 1;
558    }
559
560    return 0;
561}
562
563static int ftp_connect_control_connection(URLContext *h)
564{
565    char buf[CONTROL_BUFFER_SIZE], *response = NULL;
566    int err;
567    AVDictionary *opts = NULL;
568    FTPContext *s = h->priv_data;
569    static const int connect_codes[] = {220, 0};
570
571    if (!s->conn_control) {
572        ff_url_join(buf, sizeof(buf), "tcp", NULL,
573                    s->hostname, s->server_control_port, NULL);
574        if (s->rw_timeout != -1) {
575            av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
576        } /* if option is not given, don't pass it and let tcp use its own default */
577        err = ffurl_open_whitelist(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
578                                   &h->interrupt_callback, &opts,
579                                   h->protocol_whitelist, h->protocol_blacklist, h);
580        av_dict_free(&opts);
581        if (err < 0) {
582            av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
583            return err;
584        }
585
586        /* check if server is ready */
587        if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
588            av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
589            return AVERROR(EACCES);
590        }
591
592        if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
593            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.");
594        }
595        av_free(response);
596
597        if ((err = ftp_auth(s)) < 0) {
598            av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
599            return err;
600        }
601
602        if ((err = ftp_type(s)) < 0) {
603            av_log(h, AV_LOG_ERROR, "Set content type failed\n");
604            return err;
605        }
606
607        ftp_features(s);
608    }
609    return 0;
610}
611
612static int ftp_connect_data_connection(URLContext *h)
613{
614    int err;
615    char buf[CONTROL_BUFFER_SIZE];
616    AVDictionary *opts = NULL;
617    FTPContext *s = h->priv_data;
618
619    if (!s->conn_data) {
620        /* Enter passive mode */
621        if (ftp_passive_mode_epsv(s) < 0) {
622            /* Use PASV as fallback */
623            if ((err = ftp_passive_mode(s)) < 0)
624                return err;
625        }
626        /* Open data connection */
627        ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
628        if (s->rw_timeout != -1) {
629            av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
630        } /* if option is not given, don't pass it and let tcp use its own default */
631        err = ffurl_open_whitelist(&s->conn_data, buf, h->flags,
632                                   &h->interrupt_callback, &opts,
633                                   h->protocol_whitelist, h->protocol_blacklist, h);
634        av_dict_free(&opts);
635        if (err < 0)
636            return err;
637
638        if (s->position)
639            if ((err = ftp_restart(s, s->position)) < 0)
640                return err;
641    }
642    s->state = READY;
643    return 0;
644}
645
646static int ftp_abort(URLContext *h)
647{
648    static const char *command = "ABOR\r\n";
649    int err;
650    static const int abor_codes[] = {225, 226, 0};
651    FTPContext *s = h->priv_data;
652
653    /* According to RCF 959:
654       "ABOR command tells the server to abort the previous FTP
655       service command and any associated transfer of data."
656
657       There are FTP server implementations that don't response
658       to any commands during data transfer in passive mode (including ABOR).
659
660       This implementation closes data connection by force.
661    */
662
663    if (ftp_send_command(s, command, NULL, NULL) < 0) {
664        ftp_close_both_connections(s);
665        if ((err = ftp_connect_control_connection(h)) < 0) {
666            av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
667            return err;
668        }
669    } else {
670        ftp_close_data_connection(s);
671        if (ftp_status(s, NULL, abor_codes) < 225) {
672            /* wu-ftpd also closes control connection after data connection closing */
673            ffurl_closep(&s->conn_control);
674            if ((err = ftp_connect_control_connection(h)) < 0) {
675                av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
676                return err;
677            }
678        }
679    }
680
681    return 0;
682}
683
684static int ftp_connect(URLContext *h, const char *url)
685{
686    char proto[10], path[MAX_URL_SIZE], credentials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
687    const char *tok_user = NULL, *tok_pass = NULL;
688    char *newpath = NULL;
689    int err;
690    FTPContext *s = h->priv_data;
691
692    s->state = DISCONNECTED;
693    s->listing_method = UNKNOWN_METHOD;
694    s->filesize = -1;
695    s->position = 0;
696    s->features = NULL;
697
698    av_url_split(proto, sizeof(proto),
699                 credentials, sizeof(credentials),
700                 hostname, sizeof(hostname),
701                 &s->server_control_port,
702                 path, sizeof(path),
703                 url);
704
705    if (!*credentials) {
706        if (!s->option_user) {
707            tok_user = "anonymous";
708            tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
709        } else {
710            tok_user = s->option_user;
711            tok_pass = s->option_password;
712        }
713        s->user = av_strdup(tok_user);
714        s->password = av_strdup(tok_pass);
715    } else {
716        char *pass = strchr(credentials, ':');
717        if (pass) {
718            *pass++ = '\0';
719            tok_pass = pass;
720            s->password = ff_urldecode(pass, 0);
721        } else {
722            tok_pass = s->option_password;
723            s->password = av_strdup(tok_pass);
724        }
725        s->user = ff_urldecode(credentials, 0);
726    }
727    s->hostname = av_strdup(hostname);
728    if (!s->hostname || !s->user || (tok_pass && !s->password)) {
729        return AVERROR(ENOMEM);
730    }
731
732    if (s->server_control_port < 0 || s->server_control_port > 65535)
733        s->server_control_port = 21;
734
735    if ((err = ftp_connect_control_connection(h)) < 0)
736        return err;
737
738    if ((err = ftp_current_dir(s)) < 0)
739        return err;
740
741    newpath = av_append_path_component(s->path, path);
742    if (!newpath)
743        return AVERROR(ENOMEM);
744    av_free(s->path);
745    s->path = newpath;
746
747    return 0;
748}
749
750static int ftp_open(URLContext *h, const char *url, int flags)
751{
752    FTPContext *s = h->priv_data;
753    int err;
754
755    ff_dlog(h, "ftp protocol open\n");
756
757    if ((err = ftp_connect(h, url)) < 0)
758        goto fail;
759
760    if (ftp_restart(s, 0) < 0) {
761        h->is_streamed = 1;
762    } else {
763        ftp_file_size(s);
764        if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
765            h->is_streamed = 1;
766    }
767
768    return 0;
769
770  fail:
771    av_log(h, AV_LOG_ERROR, "FTP open failed\n");
772    ftp_close(h);
773    return err;
774}
775
776static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
777{
778    FTPContext *s = h->priv_data;
779    int err;
780    int64_t new_pos;
781
782    ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
783
784    switch(whence) {
785    case AVSEEK_SIZE:
786        return s->filesize;
787    case SEEK_SET:
788        new_pos = pos;
789        break;
790    case SEEK_CUR:
791        new_pos = s->position + pos;
792        break;
793    case SEEK_END:
794        if (s->filesize < 0)
795            return AVERROR(EIO);
796        new_pos = s->filesize + pos;
797        break;
798    default:
799        return AVERROR(EINVAL);
800    }
801
802    if (h->is_streamed)
803        return AVERROR(EIO);
804
805    if (new_pos < 0) {
806        av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
807        return AVERROR(EINVAL);
808    }
809
810    if (new_pos != s->position) {
811        if ((err = ftp_abort(h)) < 0)
812            return err;
813        s->position = new_pos;
814    }
815    return new_pos;
816}
817
818static int ftp_read(URLContext *h, unsigned char *buf, int size)
819{
820    FTPContext *s = h->priv_data;
821    int read, err, retry_done = 0;
822
823    ff_dlog(h, "ftp protocol read %d bytes\n", size);
824  retry:
825    if (s->state == ENDOFFILE)
826        return AVERROR_EOF;
827    if (s->state == DISCONNECTED) {
828        if ((err = ftp_connect_data_connection(h)) < 0)
829            return err;
830    }
831    if (s->state == READY) {
832        if ((err = ftp_retrieve(s)) < 0)
833            return err;
834    }
835    if (s->conn_data && s->state == DOWNLOADING) {
836        read = ffurl_read(s->conn_data, buf, size);
837        if (read >= 0) {
838            s->position += read;
839            s->filesize = FFMAX(s->filesize, s->position);
840        }
841        if (read == AVERROR_EOF) {
842           static const int retr_codes[] = {226, 250, 425, 426, 451, 0};
843           char *response = NULL;
844           err = ftp_status(s, &response, retr_codes);
845           if (err == 226) {
846               ftp_close_data_connection(s);
847               av_freep(&response);
848               s->state = ENDOFFILE;
849               return AVERROR_EOF;
850           }
851           /* 250 is not allowed, any other status means some kind of error */
852           av_log(h, AV_LOG_ERROR, "FTP transfer failed: %s\n", response ? response : (err < 0 ? av_err2str(err) : "?"));
853           av_freep(&response);
854           read = AVERROR(EIO);
855        }
856        if (read <= 0 && !h->is_streamed) {
857            /* Server closed connection. Probably due to inactivity */
858            av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
859            if ((err = ftp_abort(h)) < 0)
860                return err;
861            if (!retry_done) {
862                retry_done = 1;
863                goto retry;
864            }
865        }
866        return read;
867    }
868
869    av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
870    return AVERROR(EIO);
871}
872
873static int ftp_write(URLContext *h, const unsigned char *buf, int size)
874{
875    int err;
876    FTPContext *s = h->priv_data;
877    int written;
878
879    ff_dlog(h, "ftp protocol write %d bytes\n", size);
880
881    if (s->state == DISCONNECTED) {
882        if ((err = ftp_connect_data_connection(h)) < 0)
883            return err;
884    }
885    if (s->state == READY) {
886        if ((err = ftp_store(s)) < 0)
887            return err;
888    }
889    if (s->conn_data && s->state == UPLOADING) {
890        written = ffurl_write(s->conn_data, buf, size);
891        if (written > 0) {
892            s->position += written;
893            s->filesize = FFMAX(s->filesize, s->position);
894        }
895        return written;
896    }
897
898    av_log(h, AV_LOG_ERROR, "FTP write failed\n");
899    return AVERROR(EIO);
900}
901
902static int ftp_close(URLContext *h)
903{
904    FTPContext *s = h->priv_data;
905
906    ff_dlog(h, "ftp protocol close\n");
907
908    ftp_close_both_connections(s);
909    av_freep(&s->user);
910    av_freep(&s->password);
911    av_freep(&s->hostname);
912    av_freep(&s->path);
913    av_freep(&s->features);
914
915    return 0;
916}
917
918static int ftp_get_file_handle(URLContext *h)
919{
920    FTPContext *s = h->priv_data;
921
922    ff_dlog(h, "ftp protocol get_file_handle\n");
923
924    if (s->conn_data)
925        return ffurl_get_file_handle(s->conn_data);
926
927    return AVERROR(EIO);
928}
929
930static int ftp_shutdown(URLContext *h, int flags)
931{
932    FTPContext *s = h->priv_data;
933
934    ff_dlog(h, "ftp protocol shutdown\n");
935
936    if (s->conn_data)
937        return ffurl_shutdown(s->conn_data, flags);
938
939    return AVERROR(EIO);
940}
941
942static int ftp_open_dir(URLContext *h)
943{
944    FTPContext *s = h->priv_data;
945    int ret;
946
947    if ((ret = ftp_connect(h, h->filename)) < 0)
948        goto fail;
949    if ((ret = ftp_set_dir(s)) < 0)
950        goto fail;
951    if ((ret = ftp_connect_data_connection(h)) < 0)
952        goto fail;
953    if ((ret = ftp_list(s)) < 0)
954        goto fail;
955    s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
956    if (!s->dir_buffer) {
957        ret = AVERROR(ENOMEM);
958        goto fail;
959    }
960    s->dir_buffer[0] = 0;
961    if (s->conn_data && s->state == LISTING_DIR)
962        return 0;
963  fail:
964    ffurl_closep(&s->conn_control);
965    ffurl_closep(&s->conn_data);
966    return ret;
967}
968
969static int64_t ftp_parse_date(const char *date)
970{
971    struct tm tv;
972    memset(&tv, 0, sizeof(struct tm));
973    av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
974    return INT64_C(1000000) * av_timegm(&tv);
975}
976
977static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
978{
979    next->name = av_strdup(line);
980    return 0;
981}
982
983static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
984{
985    char *fact, *value;
986    char *saveptr = NULL, *p = mlsd;
987    ff_dlog(NULL, "%s\n", mlsd);
988    while(fact = av_strtok(p, ";", &saveptr)) {
989        p = NULL;
990        if (fact[0] == ' ') {
991            next->name = av_strdup(&fact[1]);
992            continue;
993        }
994        fact = av_strtok(fact, "=", &value);
995        if (!fact)
996            continue;
997        if (!av_strcasecmp(fact, "type")) {
998            if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
999                return 1;
1000            if (!av_strcasecmp(value, "dir"))
1001                next->type = AVIO_ENTRY_DIRECTORY;
1002            else if (!av_strcasecmp(value, "file"))
1003                next->type = AVIO_ENTRY_FILE;
1004            else if (!av_strcasecmp(value, "OS.unix=slink:"))
1005                next->type = AVIO_ENTRY_SYMBOLIC_LINK;
1006        } else if (!av_strcasecmp(fact, "modify")) {
1007            next->modification_timestamp = ftp_parse_date(value);
1008        } else if (!av_strcasecmp(fact, "UNIX.mode")) {
1009            next->filemode = strtoumax(value, NULL, 8);
1010        } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
1011            next->user_id = strtoumax(value, NULL, 10);
1012        else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
1013            next->group_id = strtoumax(value, NULL, 10);
1014        else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
1015            next->size = strtoll(value, NULL, 10);
1016    }
1017    return 0;
1018}
1019
1020/**
1021 * @return 0 on success, negative on error, positive on entry to discard.
1022 */
1023static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
1024{
1025    FTPContext *s = h->priv_data;
1026
1027    switch (s->listing_method) {
1028    case MLSD:
1029        return ftp_parse_entry_mlsd(line, next);
1030    case NLST:
1031        return ftp_parse_entry_nlst(line, next);
1032    case UNKNOWN_METHOD:
1033    default:
1034        return -1;
1035    }
1036}
1037
1038static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
1039{
1040    FTPContext *s = h->priv_data;
1041    char *start, *found;
1042    int ret, retried;
1043
1044    do {
1045        retried = 0;
1046        start = s->dir_buffer + s->dir_buffer_offset;
1047        while (!(found = strstr(start, "\n"))) {
1048            if (retried)
1049                return AVERROR(EIO);
1050            s->dir_buffer_size -= s->dir_buffer_offset;
1051            s->dir_buffer_offset = 0;
1052            if (s->dir_buffer_size)
1053                memmove(s->dir_buffer, start, s->dir_buffer_size);
1054            ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
1055            if (ret < 0)
1056                return ret;
1057            if (!ret) {
1058                *next = NULL;
1059                return 0;
1060            }
1061            s->dir_buffer_size += ret;
1062            s->dir_buffer[s->dir_buffer_size] = 0;
1063            start = s->dir_buffer;
1064            retried = 1;
1065        }
1066        s->dir_buffer_offset += (found + 1 - start);
1067        found[0] = 0;
1068        if (found > start && found[-1] == '\r')
1069            found[-1] = 0;
1070
1071        *next = ff_alloc_dir_entry();
1072        if (!*next)
1073            return AVERROR(ENOMEM);
1074        (*next)->utf8 = s->utf8;
1075        ret = ftp_parse_entry(h, start, *next);
1076        if (ret) {
1077            avio_free_directory_entry(next);
1078            if (ret < 0)
1079                return ret;
1080        }
1081    } while (ret > 0);
1082    return 0;
1083}
1084
1085static int ftp_close_dir(URLContext *h)
1086{
1087    FTPContext *s = h->priv_data;
1088    av_freep(&s->dir_buffer);
1089    ffurl_closep(&s->conn_control);
1090    ffurl_closep(&s->conn_data);
1091    return 0;
1092}
1093
1094static int ftp_delete(URLContext *h)
1095{
1096    FTPContext *s = h->priv_data;
1097    char command[MAX_URL_SIZE];
1098    static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
1099    static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
1100    int ret;
1101
1102    if ((ret = ftp_connect(h, h->filename)) < 0)
1103        goto cleanup;
1104
1105    ret = snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
1106    if (ret >= sizeof(command)) {
1107        ret = AVERROR(ENOSYS);
1108        goto cleanup;
1109    }
1110
1111    if (ftp_send_command(s, command, del_codes, NULL) == 250) {
1112        ret = 0;
1113        goto cleanup;
1114    }
1115
1116    ret = snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
1117    if (ret >= sizeof(command)) {
1118        ret = AVERROR(ENOSYS);
1119        goto cleanup;
1120    }
1121
1122    if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
1123        ret = 0;
1124    else
1125        ret = AVERROR(EIO);
1126
1127cleanup:
1128    ftp_close(h);
1129    return ret;
1130}
1131
1132static int ftp_move(URLContext *h_src, URLContext *h_dst)
1133{
1134    FTPContext *s = h_src->priv_data;
1135    char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
1136    static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
1137    static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
1138    int ret;
1139
1140    if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
1141        goto cleanup;
1142
1143    ret = snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
1144    if (ret >= sizeof(command)) {
1145        ret = AVERROR(ENOSYS);
1146        goto cleanup;
1147    }
1148
1149    if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
1150        ret = AVERROR(EIO);
1151        goto cleanup;
1152    }
1153
1154    av_url_split(0, 0, 0, 0, 0, 0, 0,
1155                 path, sizeof(path),
1156                 h_dst->filename);
1157    ret = snprintf(command, sizeof(command), "RNTO %s\r\n", path);
1158    if (ret >= sizeof(command)) {
1159        ret = AVERROR(ENOSYS);
1160        goto cleanup;
1161    }
1162
1163    if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
1164        ret = 0;
1165    else
1166        ret = AVERROR(EIO);
1167
1168cleanup:
1169    ftp_close(h_src);
1170    return ret;
1171}
1172
1173const URLProtocol ff_ftp_protocol = {
1174    .name                = "ftp",
1175    .url_open            = ftp_open,
1176    .url_read            = ftp_read,
1177    .url_write           = ftp_write,
1178    .url_seek            = ftp_seek,
1179    .url_close           = ftp_close,
1180    .url_get_file_handle = ftp_get_file_handle,
1181    .url_shutdown        = ftp_shutdown,
1182    .priv_data_size      = sizeof(FTPContext),
1183    .priv_data_class     = &ftp_context_class,
1184    .url_open_dir        = ftp_open_dir,
1185    .url_read_dir        = ftp_read_dir,
1186    .url_close_dir       = ftp_close_dir,
1187    .url_delete          = ftp_delete,
1188    .url_move            = ftp_move,
1189    .flags               = URL_PROTOCOL_FLAG_NETWORK,
1190    .default_whitelist   = "tcp",
1191};
1192