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