2 * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com>
4 * This file is part of FFmpeg.
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.
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.
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
21 #include "libavutil/avstring.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/bprint.h"
28 #define CONTROL_BUFFER_SIZE 1024
40 URLContext *conn_control; /**< Control connection */
41 URLContext *conn_data; /**< Data connection, NULL when not connected */
42 uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
43 uint8_t *control_buf_ptr, *control_buf_end;
44 int server_data_port; /**< Data connection port opened by server, -1 on error. */
45 int server_control_port; /**< Control connection port, default is 21 */
46 char *hostname; /**< Server address. */
47 char *user; /**< Server user */
48 char *password; /**< Server user's password */
49 char *path; /**< Path to resource on server. */
50 int64_t filesize; /**< Size of file on server, -1 on error. */
51 int64_t position; /**< Current position, calculated. */
52 int rw_timeout; /**< Network timeout. */
53 const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */
54 int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */
55 FTPState state; /**< State of data connection */
58 #define OFFSET(x) offsetof(FTPContext, x)
59 #define D AV_OPT_FLAG_DECODING_PARAM
60 #define E AV_OPT_FLAG_ENCODING_PARAM
61 static const AVOption options[] = {
62 {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
63 {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
64 {"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 },
68 static const AVClass ftp_context_class = {
70 .item_name = av_default_item_name,
72 .version = LIBAVUTIL_VERSION_INT,
75 static int ftp_close(URLContext *h);
77 static int ftp_getc(FTPContext *s)
80 if (s->control_buf_ptr >= s->control_buf_end) {
81 len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
87 s->control_buf_ptr = s->control_buffer;
88 s->control_buf_end = s->control_buffer + len;
91 return *s->control_buf_ptr++;
94 static int ftp_get_line(FTPContext *s, char *line, int line_size)
106 if (q > line && q[-1] == '\r')
111 if ((q - line) < line_size - 1)
118 * This routine returns ftp server response code.
119 * Server may send more than one response for a certain command.
120 * First expected code is returned.
122 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
124 int err, i, dash = 0, result = 0, code_found = 0, linesize;
125 char buf[CONTROL_BUFFER_SIZE];
126 AVBPrint line_buffer;
129 av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
131 while (!code_found || dash) {
132 if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
134 av_bprint_finalize(&line_buffer, NULL);
138 av_log(s, AV_LOG_DEBUG, "%s\n", buf);
140 linesize = strlen(buf);
143 for (i = 0; i < 3; ++i) {
144 if (buf[i] < '0' || buf[i] > '9') {
158 for (i = 0; response_codes[i]; ++i) {
159 if (err == response_codes[i]) {
168 av_bprintf(&line_buffer, "%s\r\n", buf);
170 if (!dash && buf[3] == '-')
172 else if (err == dash && buf[3] == ' ')
179 av_bprint_finalize(&line_buffer, line);
183 static int ftp_send_command(FTPContext *s, const char *command,
184 const int response_codes[], char **response)
191 if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
197 if (response_codes) {
198 return ftp_status(s, response, response_codes);
203 static void ftp_close_data_connection(FTPContext *s)
205 ffurl_closep(&s->conn_data);
207 s->state = DISCONNECTED;
210 static void ftp_close_both_connections(FTPContext *s)
212 ffurl_closep(&s->conn_control);
213 ftp_close_data_connection(s);
216 static int ftp_auth(FTPContext *s)
218 char buf[CONTROL_BUFFER_SIZE];
220 static const int user_codes[] = {331, 230, 0};
221 static const int pass_codes[] = {230, 0};
223 snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
224 err = ftp_send_command(s, buf, user_codes, NULL);
227 snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
228 err = ftp_send_command(s, buf, pass_codes, NULL);
230 return AVERROR(EACCES);
233 return AVERROR(EACCES);
238 static int ftp_passive_mode_epsv(FTPContext *s)
240 char *res = NULL, *start = NULL, *end = NULL;
242 static const char d = '|';
243 static const char *command = "EPSV\r\n";
244 static const int epsv_codes[] = {229, 0};
246 if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
249 for (i = 0; res[i]; ++i) {
252 } else if (res[i] == ')') {
261 if (strlen(start) < 5)
263 if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
268 s->server_data_port = atoi(start);
269 av_dlog(s, "Server data port: %d\n", s->server_data_port);
276 s->server_data_port = -1;
277 return AVERROR(ENOSYS);
280 static int ftp_passive_mode(FTPContext *s)
282 char *res = NULL, *start = NULL, *end = NULL;
284 static const char *command = "PASV\r\n";
285 static const int pasv_codes[] = {227, 0};
287 if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
290 for (i = 0; res[i]; ++i) {
293 } else if (res[i] == ')') {
303 if (!av_strtok(start, ",", &end)) goto fail;
304 if (!av_strtok(end, ",", &end)) goto fail;
305 if (!av_strtok(end, ",", &end)) goto fail;
306 if (!av_strtok(end, ",", &end)) goto fail;
308 /* parse port number */
309 start = av_strtok(end, ",", &end);
310 if (!start) goto fail;
311 s->server_data_port = atoi(start) * 256;
312 start = av_strtok(end, ",", &end);
313 if (!start) goto fail;
314 s->server_data_port += atoi(start);
315 av_dlog(s, "Server data port: %d\n", s->server_data_port);
322 s->server_data_port = -1;
326 static int ftp_current_dir(FTPContext *s)
328 char *res = NULL, *start = NULL, *end = NULL;
330 static const char *command = "PWD\r\n";
331 static const int pwd_codes[] = {257, 0};
333 if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
336 for (i = 0; res[i]; ++i) {
350 if (end > res && end[-1] == '/') {
354 s->path = av_strdup(start);
359 return AVERROR(ENOMEM);
367 static int ftp_file_size(FTPContext *s)
369 char command[CONTROL_BUFFER_SIZE];
371 static const int size_codes[] = {213, 0};
373 snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
374 if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
375 s->filesize = strtoll(&res[4], NULL, 10);
386 static int ftp_retrieve(FTPContext *s)
388 char command[CONTROL_BUFFER_SIZE];
389 static const int retr_codes[] = {150, 0};
391 snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
392 if (ftp_send_command(s, command, retr_codes, NULL) != 150)
395 s->state = DOWNLOADING;
400 static int ftp_store(FTPContext *s)
402 char command[CONTROL_BUFFER_SIZE];
403 static const int stor_codes[] = {150, 0};
405 snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
406 if (ftp_send_command(s, command, stor_codes, NULL) != 150)
409 s->state = UPLOADING;
414 static int ftp_type(FTPContext *s)
416 static const char *command = "TYPE I\r\n";
417 static const int type_codes[] = {200, 0};
419 if (ftp_send_command(s, command, type_codes, NULL) != 200)
425 static int ftp_restart(FTPContext *s, int64_t pos)
427 char command[CONTROL_BUFFER_SIZE];
428 static const int rest_codes[] = {350, 0};
430 snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
431 if (ftp_send_command(s, command, rest_codes, NULL) != 350)
437 static int ftp_features(FTPContext *s)
439 static const char *feat_command = "FEAT\r\n";
440 static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
441 static const int feat_codes[] = {211, 0};
442 static const int opts_codes[] = {200, 451};
445 if (ftp_send_command(s, feat_command, feat_codes, &feat) == 211) {
446 if (av_stristr(feat, "UTF8"))
447 ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
454 static int ftp_connect_control_connection(URLContext *h)
456 char buf[CONTROL_BUFFER_SIZE], *response = NULL;
458 AVDictionary *opts = NULL;
459 FTPContext *s = h->priv_data;
460 static const int connect_codes[] = {220, 0};
462 if (!s->conn_control) {
463 ff_url_join(buf, sizeof(buf), "tcp", NULL,
464 s->hostname, s->server_control_port, NULL);
465 if (s->rw_timeout != -1) {
466 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
467 } /* if option is not given, don't pass it and let tcp use its own default */
468 err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
469 &h->interrupt_callback, &opts);
472 av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
476 /* check if server is ready */
477 if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
478 av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
479 return AVERROR(EACCES);
482 if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
483 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.");
487 if ((err = ftp_auth(s)) < 0) {
488 av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
492 if ((err = ftp_type(s)) < 0) {
493 av_log(h, AV_LOG_ERROR, "Set content type failed\n");
502 static int ftp_connect_data_connection(URLContext *h)
505 char buf[CONTROL_BUFFER_SIZE];
506 AVDictionary *opts = NULL;
507 FTPContext *s = h->priv_data;
510 /* Enter passive mode */
511 if (ftp_passive_mode_epsv(s) < 0) {
512 /* Use PASV as fallback */
513 if ((err = ftp_passive_mode(s)) < 0)
516 /* Open data connection */
517 ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
518 if (s->rw_timeout != -1) {
519 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
520 } /* if option is not given, don't pass it and let tcp use its own default */
521 err = ffurl_open(&s->conn_data, buf, h->flags,
522 &h->interrupt_callback, &opts);
528 if ((err = ftp_restart(s, s->position)) < 0)
535 static int ftp_abort(URLContext *h)
537 static const char *command = "ABOR\r\n";
539 static const int abor_codes[] = {225, 226, 0};
540 FTPContext *s = h->priv_data;
542 /* According to RCF 959:
543 "ABOR command tells the server to abort the previous FTP
544 service command and any associated transfer of data."
546 There are FTP server implementations that don't response
547 to any commands during data transfer in passive mode (including ABOR).
549 This implementation closes data connection by force.
552 if (ftp_send_command(s, command, NULL, NULL) < 0) {
553 ftp_close_both_connections(s);
554 if ((err = ftp_connect_control_connection(h)) < 0) {
555 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
559 ftp_close_data_connection(s);
560 if (ftp_status(s, NULL, abor_codes) < 225) {
561 /* wu-ftpd also closes control connection after data connection closing */
562 ffurl_closep(&s->conn_control);
563 if ((err = ftp_connect_control_connection(h)) < 0) {
564 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
573 static int ftp_open(URLContext *h, const char *url, int flags)
575 char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
576 const char *tok_user = NULL, *tok_pass = NULL;
580 FTPContext *s = h->priv_data;
582 av_dlog(h, "ftp protocol open\n");
584 s->state = DISCONNECTED;
588 av_url_split(proto, sizeof(proto),
589 credencials, sizeof(credencials),
590 hostname, sizeof(hostname),
591 &s->server_control_port,
595 tok_user = av_strtok(credencials, ":", &end);
596 tok_pass = av_strtok(end, ":", &end);
598 tok_user = "anonymous";
599 tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
601 s->user = av_strdup(tok_user);
602 s->password = av_strdup(tok_pass);
603 s->hostname = av_strdup(hostname);
604 if (!s->hostname || !s->user || (tok_pass && !s->password)) {
605 err = AVERROR(ENOMEM);
609 if (s->server_control_port < 0 || s->server_control_port > 65535)
610 s->server_control_port = 21;
612 if ((err = ftp_connect_control_connection(h)) < 0)
615 if ((err = ftp_current_dir(s)) < 0)
617 pathlen = strlen(s->path) + strlen(path) + 1;
618 if ((err = av_reallocp(&s->path, pathlen)) < 0)
620 av_strlcat(s->path + strlen(s->path), path, pathlen);
622 if (ftp_restart(s, 0) < 0) {
625 if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
627 if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
634 av_log(h, AV_LOG_ERROR, "FTP open failed\n");
639 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
641 FTPContext *s = h->priv_data;
643 int64_t new_pos, fake_pos;
645 av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
654 new_pos = s->position + pos;
659 new_pos = s->filesize + pos;
662 return AVERROR(EINVAL);
669 av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
670 return AVERROR(EINVAL);
673 fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
674 if (fake_pos != s->position) {
675 if ((err = ftp_abort(h)) < 0)
677 s->position = fake_pos;
682 static int ftp_read(URLContext *h, unsigned char *buf, int size)
684 FTPContext *s = h->priv_data;
685 int read, err, retry_done = 0;
687 av_dlog(h, "ftp protocol read %d bytes\n", size);
689 if (s->state == DISCONNECTED) {
691 if (s->position >= s->filesize)
693 if ((err = ftp_connect_data_connection(h)) < 0)
696 if (s->state == READY) {
697 if (s->position >= s->filesize)
699 if ((err = ftp_retrieve(s)) < 0)
702 if (s->conn_data && s->state == DOWNLOADING) {
703 read = ffurl_read(s->conn_data, buf, size);
706 if (s->position >= s->filesize) {
707 /* server will terminate, but keep current position to avoid madness */
708 /* save position to restart from it */
709 int64_t pos = s->position;
710 if (ftp_abort(h) < 0) {
717 if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
718 /* Server closed connection. Probably due to inactivity */
719 int64_t pos = s->position;
720 av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
721 if ((err = ftp_abort(h)) < 0)
723 if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
724 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
735 av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
739 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
742 FTPContext *s = h->priv_data;
745 av_dlog(h, "ftp protocol write %d bytes\n", size);
747 if (s->state == DISCONNECTED) {
748 if ((err = ftp_connect_data_connection(h)) < 0)
751 if (s->state == READY) {
752 if ((err = ftp_store(s)) < 0)
755 if (s->conn_data && s->state == UPLOADING) {
756 written = ffurl_write(s->conn_data, buf, size);
758 s->position += written;
759 s->filesize = FFMAX(s->filesize, s->position);
764 av_log(h, AV_LOG_ERROR, "FTP write failed\n");
768 static int ftp_close(URLContext *h)
770 FTPContext *s = h->priv_data;
772 av_dlog(h, "ftp protocol close\n");
774 ftp_close_both_connections(s);
776 av_freep(&s->password);
777 av_freep(&s->hostname);
783 static int ftp_get_file_handle(URLContext *h)
785 FTPContext *s = h->priv_data;
787 av_dlog(h, "ftp protocol get_file_handle\n");
790 return ffurl_get_file_handle(s->conn_data);
795 static int ftp_shutdown(URLContext *h, int flags)
797 FTPContext *s = h->priv_data;
799 av_dlog(h, "ftp protocol shutdown\n");
802 return ffurl_shutdown(s->conn_data, flags);
807 URLProtocol ff_ftp_protocol = {
809 .url_open = ftp_open,
810 .url_read = ftp_read,
811 .url_write = ftp_write,
812 .url_seek = ftp_seek,
813 .url_close = ftp_close,
814 .url_get_file_handle = ftp_get_file_handle,
815 .url_shutdown = ftp_shutdown,
816 .priv_data_size = sizeof(FTPContext),
817 .priv_data_class = &ftp_context_class,
818 .flags = URL_PROTOCOL_FLAG_NETWORK,