ftp: reconnect on read
[ffmpeg.git] / 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 <stdlib.h>
22 #include "libavutil/avstring.h"
23 #include "avformat.h"
24 #include "internal.h"
25 #include "network.h"
26 #include "os_support.h"
27 #include "url.h"
28 #include "libavutil/opt.h"
29
30 #define CONTROL_BUFFER_SIZE 1024
31 #define CREDENTIALS_BUFFER_SIZE 128
32
33 typedef enum {
34     UNKNOWN,
35     READY,
36     DOWNLOADING,
37     UPLOADING,
38     DISCONNECTED
39 } FTPState;
40
41 typedef struct {
42     const AVClass *class;
43     URLContext *conn_control;                    /**< Control connection */
44     int conn_control_block_flag;                 /**< Controls block/unblock mode of data connection */
45     AVIOInterruptCB conn_control_interrupt_cb;   /**< Controls block/unblock mode of data connection */
46     URLContext *conn_data;                       /**< Data connection, NULL when not connected */
47     uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
48     uint8_t *control_buf_ptr, *control_buf_end;
49     int server_data_port;                        /**< Data connection port opened by server, -1 on error. */
50     int server_control_port;                     /**< Control connection port, default is 21 */
51     char hostname[512];                          /**< Server address. */
52     char credencials[CREDENTIALS_BUFFER_SIZE];   /**< Authentication data */
53     char path[MAX_URL_SIZE];                     /**< Path to resource on server. */
54     int64_t filesize;                            /**< Size of file on server, -1 on error. */
55     int64_t position;                            /**< Current position, calculated. */
56     int rw_timeout;                              /**< Network timeout. */
57     const char *anonymous_password;              /**< Password to be used for anonymous user. An email should be used. */
58     int write_seekable;                          /**< Control seekability, 0 = disable, 1 = enable. */
59     FTPState state;                              /**< State of data connection */
60 } FTPContext;
61
62 #define OFFSET(x) offsetof(FTPContext, x)
63 #define D AV_OPT_FLAG_DECODING_PARAM
64 #define E AV_OPT_FLAG_ENCODING_PARAM
65 static const AVOption options[] = {
66     {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
67     {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
68     {"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 },
69     {NULL}
70 };
71
72 static const AVClass ftp_context_class = {
73     .class_name     = "ftp",
74     .item_name      = av_default_item_name,
75     .option         = options,
76     .version        = LIBAVUTIL_VERSION_INT,
77 };
78
79 static int ftp_conn_control_block_control(void *data)
80 {
81     FTPContext *s = data;
82     return s->conn_control_block_flag;
83 }
84
85 static int ftp_getc(FTPContext *s)
86 {
87     int len;
88     if (s->control_buf_ptr >= s->control_buf_end) {
89         if (s->conn_control_block_flag)
90             return AVERROR_EXIT;
91         len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
92         if (len < 0) {
93             return len;
94         } else if (!len) {
95             return -1;
96         } else {
97             s->control_buf_ptr = s->control_buffer;
98             s->control_buf_end = s->control_buffer + len;
99         }
100     }
101     return *s->control_buf_ptr++;
102 }
103
104 static int ftp_get_line(FTPContext *s, char *line, int line_size)
105 {
106     int ch;
107     char *q = line;
108     int ori_block_flag = s->conn_control_block_flag;
109
110     for (;;) {
111         ch = ftp_getc(s);
112         if (ch < 0) {
113             s->conn_control_block_flag = ori_block_flag;
114             return ch;
115         }
116         if (ch == '\n') {
117             /* process line */
118             if (q > line && q[-1] == '\r')
119                 q--;
120             *q = '\0';
121
122             s->conn_control_block_flag = ori_block_flag;
123             return 0;
124         } else {
125             s->conn_control_block_flag = 0; /* line need to be finished */
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, following priorities are used:
135  *   - 5xx code is returned if occurred. (means error)
136  *   - When pref_code is set then pref_code is return if occurred. (expected result)
137  *   - The lowest code is returned. (means success)
138  */
139 static int ftp_status(FTPContext *s, int *major, int *minor, int *extra, char **line, int pref_code)
140 {
141     int err, result = -1, pref_code_found = 0;
142     char buf[CONTROL_BUFFER_SIZE];
143     unsigned char d_major, d_minor, d_extra;
144
145     /* Set blocking mode */
146     s->conn_control_block_flag = 0;
147     for (;;) {
148         if ((err = ftp_get_line(s, buf, CONTROL_BUFFER_SIZE)) < 0) {
149             if (err == AVERROR_EXIT)
150                 return result;
151             return err;
152         }
153
154         if (strlen(buf) < 3)
155             continue;
156         d_major = buf[0];
157         if (d_major < '1' || d_major > '6' || d_major == '4')
158             continue;
159         d_minor = buf[1];
160         if (d_minor < '0' || d_minor > '9')
161             continue;
162         d_extra = buf[2];
163         if (d_extra < '0' || d_extra > '9')
164             continue;
165
166         av_log(s, AV_LOG_DEBUG, "%s\n", buf);
167
168         err = d_major * 100 + d_minor * 10 + d_extra - 111 * '0';
169
170         if ((result < 0 || err < result || pref_code == err) && !pref_code_found || d_major == '5') {
171             if (pref_code == err || d_major == '5')
172                 pref_code_found = 1;
173             result = err;
174             if (major)
175                 *major = d_major - '0';
176             if (minor)
177                 *minor = d_minor - '0';
178             if (extra)
179                 *extra = d_extra - '0';
180             if (line)
181                 *line = av_strdup(buf);
182         }
183
184         /* first code received. Now get all lines in non blocking mode */
185         if (pref_code < 0 || pref_code_found)
186             s->conn_control_block_flag = 1;
187     }
188     return result;
189 }
190
191 static int ftp_auth(FTPContext *s)
192 {
193     const char *user = NULL, *pass = NULL;
194     char *end = NULL, buf[CONTROL_BUFFER_SIZE], credencials[CREDENTIALS_BUFFER_SIZE];
195     int err;
196
197     /* Authentication may be repeated, original string has to be saved */
198     av_strlcpy(credencials, s->credencials, sizeof(credencials));
199
200     user = av_strtok(credencials, ":", &end);
201     pass = av_strtok(end, ":", &end);
202
203     if (!user) {
204         user = "anonymous";
205         pass = s->anonymous_password ? s->anonymous_password : "nopassword";
206     }
207
208     snprintf(buf, sizeof(buf), "USER %s\r\n", user);
209     if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
210         return err;
211     ftp_status(s, &err, NULL, NULL, NULL, -1);
212     if (err == 3) {
213         if (pass) {
214             snprintf(buf, sizeof(buf), "PASS %s\r\n", pass);
215             if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
216                 return err;
217             ftp_status(s, &err, NULL, NULL, NULL, -1);
218         } else
219             return AVERROR(EACCES);
220     }
221     if (err != 2) {
222         return AVERROR(EACCES);
223     }
224
225     return 0;
226 }
227
228 static int ftp_passive_mode(FTPContext *s)
229 {
230     char *res = NULL, *start, *end;
231     int err, i;
232     const char *command = "PASV\r\n";
233
234     if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
235         return err;
236     if (ftp_status(s, NULL, NULL, NULL, &res, 227) != 227)
237         goto fail;
238
239     start = NULL;
240     for (i = 0; i < strlen(res); ++i) {
241         if (res[i] == '(') {
242             start = res + i + 1;
243         } else if (res[i] == ')') {
244             end = res + i;
245             break;
246         }
247     }
248     if (!start || !end)
249         goto fail;
250
251     *end  = '\0';
252     /* skip ip */
253     if (!av_strtok(start, ",", &end)) goto fail;
254     if (!av_strtok(end, ",", &end)) goto fail;
255     if (!av_strtok(end, ",", &end)) goto fail;
256     if (!av_strtok(end, ",", &end)) goto fail;
257
258     /* parse port number */
259     start = av_strtok(end, ",", &end);
260     if (!start) goto fail;
261     s->server_data_port = atoi(start) * 256;
262     start = av_strtok(end, ",", &end);
263     if (!start) goto fail;
264     s->server_data_port += atoi(start);
265     av_dlog(s, "Server data port: %d\n", s->server_data_port);
266
267     av_free(res);
268     return 0;
269
270   fail:
271     av_free(res);
272     s->server_data_port = -1;
273     return AVERROR(EIO);
274 }
275
276 static int ftp_current_dir(FTPContext *s)
277 {
278     char *res = NULL, *start = NULL, *end = NULL;
279     int err, i;
280     const char *command = "PWD\r\n";
281
282     if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
283         return err;
284     if (ftp_status(s, NULL, NULL, NULL, &res, 257) != 257)
285         goto fail;
286
287     for (i = 0; res[i]; ++i) {
288         if (res[i] == '"') {
289             if (!start) {
290                 start = res + i + 1;
291                 continue;
292             }
293             end = res + i;
294             break;
295         }
296     }
297
298     if (!end)
299         goto fail;
300
301     if (end > res && end[-1] == '/') {
302         end[-1] = '\0';
303     } else
304         *end = '\0';
305     av_strlcpy(s->path, start, sizeof(s->path));
306
307     av_free(res);
308     return 0;
309
310   fail:
311     av_free(res);
312     return AVERROR(EIO);
313 }
314
315 static int ftp_file_size(FTPContext *s)
316 {
317     char buf[CONTROL_BUFFER_SIZE];
318     int err;
319     char *res = NULL;
320
321     snprintf(buf, sizeof(buf), "SIZE %s\r\n", s->path);
322     if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
323         return err;
324     if (ftp_status(s, NULL, NULL, NULL, &res, 213) == 213) {
325         s->filesize = strtoll(&res[4], NULL, 10);
326     } else {
327         s->filesize = -1;
328         av_free(res);
329         return AVERROR(EIO);
330     }
331
332     av_free(res);
333     return 0;
334 }
335
336 static int ftp_retrieve(FTPContext *s)
337 {
338     char buf[CONTROL_BUFFER_SIZE];
339     int err;
340
341     snprintf(buf, sizeof(buf), "RETR %s\r\n", s->path);
342     if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
343         return err;
344     if (ftp_status(s, NULL, NULL, NULL, NULL, 150) != 150)
345         return AVERROR(EIO);
346
347     s->state = DOWNLOADING;
348
349     return 0;
350 }
351
352 static int ftp_store(FTPContext *s)
353 {
354     char buf[CONTROL_BUFFER_SIZE];
355     int err;
356
357     snprintf(buf, sizeof(buf), "STOR %s\r\n", s->path);
358     if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
359         return err;
360     if (ftp_status(s, NULL, NULL, NULL, NULL, 150) != 150)
361         return AVERROR(EIO);
362
363     s->state = UPLOADING;
364
365     return 0;
366 }
367
368 static int ftp_type(FTPContext *s)
369 {
370     int err;
371     const char *command = "TYPE I\r\n";
372
373     if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
374         return err;
375     if (ftp_status(s, NULL, NULL, NULL, NULL, 200) != 200)
376         return AVERROR(EIO);
377
378     return 0;
379 }
380
381 static int ftp_connect_control_connection(URLContext *h)
382 {
383     char buf[CONTROL_BUFFER_SIZE], opts_format[20];
384     int err;
385     AVDictionary *opts = NULL;
386     FTPContext *s = h->priv_data;
387
388     s->conn_control_block_flag = 0;
389
390     if (!s->conn_control) {
391         ff_url_join(buf, sizeof(buf), "tcp", NULL,
392                     s->hostname, s->server_control_port, NULL);
393         if (s->rw_timeout != -1) {
394             snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
395             av_dict_set(&opts, "timeout", opts_format, 0);
396         } /* if option is not given, don't pass it and let tcp use its own default */
397         err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
398                          &s->conn_control_interrupt_cb, &opts);
399         av_dict_free(&opts);
400         if (err < 0) {
401             av_dlog(h, "Cannot open control connection, error %d\n", err);
402             return err;
403         }
404
405         /* consume all messages from server */
406         if (ftp_status(s, NULL, NULL, NULL, NULL, 220) != 220) {
407             av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
408             err = AVERROR(EACCES);
409             return err;
410         }
411
412         if ((err = ftp_auth(s)) < 0) {
413             av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
414             return err;
415         }
416
417         if ((err = ftp_type(s)) < 0) {
418             av_dlog(h, "Set content type failed\n");
419             return err;
420         }
421     }
422     return 0;
423 }
424
425 static int ftp_connect_data_connection(URLContext *h)
426 {
427     int err;
428     char buf[CONTROL_BUFFER_SIZE], opts_format[20];
429     AVDictionary *opts = NULL;
430     FTPContext *s = h->priv_data;
431
432     if (!s->conn_data) {
433         /* Enter passive mode */
434         if ((err = ftp_passive_mode(s)) < 0) {
435             av_dlog(h, "Set passive mode failed\n");
436             return err;
437         }
438         /* Open data connection */
439         ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
440         if (s->rw_timeout != -1) {
441             snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
442             av_dict_set(&opts, "timeout", opts_format, 0);
443         } /* if option is not given, don't pass it and let tcp use its own default */
444         err = ffurl_open(&s->conn_data, buf, AVIO_FLAG_READ_WRITE,
445                          &h->interrupt_callback, &opts);
446         av_dict_free(&opts);
447         if (err < 0)
448             return err;
449     }
450     s->state = READY;
451     s->position = 0;
452     return 0;
453 }
454
455 static int ftp_open(URLContext *h, const char *url, int flags)
456 {
457     char proto[10], path[MAX_URL_SIZE];
458     int err;
459     FTPContext *s = h->priv_data;
460
461     av_dlog(h, "ftp protocol open\n");
462
463     s->state = DISCONNECTED;
464     s->filesize = -1;
465     s->conn_control_interrupt_cb.opaque = s;
466     s->conn_control_interrupt_cb.callback = ftp_conn_control_block_control;
467
468     av_url_split(proto, sizeof(proto),
469                  s->credencials, sizeof(s->credencials),
470                  s->hostname, sizeof(s->hostname),
471                  &s->server_control_port,
472                  path, sizeof(path),
473                  url);
474
475     if (s->server_control_port < 0 || s->server_control_port > 65535)
476         s->server_control_port = 21;
477
478     if ((err = ftp_connect_control_connection(h)) < 0)
479         goto fail;
480
481     if ((err = ftp_current_dir(s)) < 0)
482         goto fail;
483     av_strlcat(s->path, path, sizeof(s->path));
484
485     if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
486         h->is_streamed = 1;
487     if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
488         h->is_streamed = 1;
489
490     if ((err = ftp_connect_data_connection(h)) < 0)
491         goto fail;
492
493     return 0;
494   fail:
495     av_log(h, AV_LOG_ERROR, "FTP open failed\n");
496     ffurl_closep(&s->conn_control);
497     ffurl_closep(&s->conn_data);
498     return err;
499 }
500
501 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
502 {
503     FTPContext *s = h->priv_data;
504     char buf[CONTROL_BUFFER_SIZE];
505     int err;
506     int64_t new_pos;
507
508     av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
509
510     switch(whence) {
511     case AVSEEK_SIZE:
512         return s->filesize;
513     case SEEK_SET:
514         new_pos = pos;
515         break;
516     case SEEK_CUR:
517         new_pos = s->position + pos;
518         break;
519     case SEEK_END:
520         if (s->filesize < 0)
521             return AVERROR(EIO);
522         new_pos = s->filesize + pos;
523         break;
524     default:
525         return AVERROR(EINVAL);
526     }
527
528     if  (h->is_streamed)
529         return AVERROR(EIO);
530
531     if (new_pos < 0 || (s->filesize >= 0 && new_pos > s->filesize))
532         return AVERROR(EINVAL);
533
534     if (new_pos != s->position) {
535         /* close existing data connection */
536         if (s->state != READY) {
537             if (s->conn_data) {
538                 /* abort existing transfer */
539                 if (s->state == DOWNLOADING) {
540                     snprintf(buf, sizeof(buf), "ABOR\r\n");
541                     if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
542                         return err;
543                 }
544                 ffurl_closep(&s->conn_data);
545                 s->state = DISCONNECTED;
546                 /* Servers return 225 or 226 */
547                 ftp_status(s, &err, NULL, NULL, NULL, -1);
548                 if (err != 2)
549                     return AVERROR(EIO);
550             }
551
552             /* open new data connection */
553             if ((err = ftp_connect_data_connection(h)) < 0)
554                 return err;
555         }
556
557         /* resume from pos position */
558         snprintf(buf, sizeof(buf), "REST %"PRId64"\r\n", pos);
559         if ((err = ffurl_write(s->conn_control, buf, strlen(buf))) < 0)
560             return err;
561         if (ftp_status(s, NULL, NULL, NULL, NULL, 350) != 350)
562             return AVERROR(EIO);
563
564         s->position = pos;
565     }
566     return new_pos;
567 }
568
569 static int ftp_read(URLContext *h, unsigned char *buf, int size)
570 {
571     FTPContext *s = h->priv_data;
572     int read, err, retry_done = 0;
573
574     av_dlog(h, "ftp protocol read %d bytes\n", size);
575   retry:
576     if (s->state == READY) {
577         ftp_retrieve(s);
578     }
579     if (s->conn_data && s->state == DOWNLOADING) {
580         read = ffurl_read(s->conn_data, buf, size);
581         if (read >= 0) {
582             s->position += read;
583             if (s->position >= s->filesize) {
584                 ffurl_closep(&s->conn_data);
585                 s->state = DISCONNECTED;
586                 if (ftp_status(s, NULL, NULL, NULL,NULL, 226) != 226)
587                     return AVERROR(EIO);
588             }
589         }
590         if (!read && s->position < s->filesize && !h->is_streamed) {
591             /* Server closed connection. Probably due to inactivity */
592             /* TODO: Consider retry before reconnect */
593             int64_t pos = s->position;
594             av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
595             ffurl_closep(&s->conn_control);
596             ffurl_closep(&s->conn_data);
597             s->position = 0;
598             s->state = DISCONNECTED;
599             if ((err = ftp_connect_control_connection(h)) < 0) {
600                 av_log(h, AV_LOG_ERROR, "Reconnect failed\n");
601                 return err;
602             }
603             if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
604                 av_dlog(h, "Seek failed after reconnect\n");
605                 return err;
606             }
607             if (!retry_done) {
608                 retry_done = 1;
609                 goto retry;
610             }
611         }
612         return read;
613     }
614
615     av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
616     return AVERROR(EIO);
617 }
618
619 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
620 {
621     FTPContext *s = h->priv_data;
622     int written;
623
624     av_dlog(h, "ftp protocol write %d bytes\n", size);
625
626     if (s->state == READY) {
627         ftp_store(s);
628     }
629     if (s->conn_data && s->state == UPLOADING) {
630         written = ffurl_write(s->conn_data, buf, size);
631         if (written > 0) {
632             s->position += written;
633             s->filesize = FFMAX(s->filesize, s->position);
634         }
635         return written;
636     }
637
638     av_log(h, AV_LOG_ERROR, "FTP write failed\n");
639     return AVERROR(EIO);
640 }
641
642 static int ftp_close(URLContext *h)
643 {
644     FTPContext *s = h->priv_data;
645
646     av_dlog(h, "ftp protocol close\n");
647
648     ffurl_closep(&s->conn_control);
649     ffurl_closep(&s->conn_data);
650
651     return 0;
652 }
653
654 static int ftp_get_file_handle(URLContext *h)
655 {
656     FTPContext *s = h->priv_data;
657
658     av_dlog(h, "ftp protocol get_file_handle\n");
659
660     if (s->conn_data)
661         return ffurl_get_file_handle(s->conn_data);
662
663     return AVERROR(EIO);
664 }
665
666 static int ftp_shutdown(URLContext *h, int flags)
667 {
668     FTPContext *s = h->priv_data;
669
670     av_dlog(h, "ftp protocol shutdown\n");
671
672     if (s->conn_data)
673         return ffurl_shutdown(s->conn_data, flags);
674
675     return AVERROR(EIO);
676 }
677
678 URLProtocol ff_ftp_protocol = {
679     .name                = "ftp",
680     .url_open            = ftp_open,
681     .url_read            = ftp_read,
682     .url_write           = ftp_write,
683     .url_seek            = ftp_seek,
684     .url_close           = ftp_close,
685     .url_get_file_handle = ftp_get_file_handle,
686     .url_shutdown        = ftp_shutdown,
687     .priv_data_size      = sizeof(FTPContext),
688     .priv_data_class     = &ftp_context_class,
689     .flags               = URL_PROTOCOL_FLAG_NETWORK,
690 };