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