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