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"
22 #include "libavutil/internal.h"
23 #include "libavutil/parseutils.h"
27 #include "libavutil/opt.h"
28 #include "libavutil/bprint.h"
30 #define CONTROL_BUFFER_SIZE 1024
31 #define DIR_BUFFER_SIZE 4096
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 */
69 size_t dir_buffer_size;
70 size_t dir_buffer_offset;
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 },
84 static const AVClass ftp_context_class = {
86 .item_name = av_default_item_name,
88 .version = LIBAVUTIL_VERSION_INT,
91 static int ftp_close(URLContext *h);
93 static int ftp_getc(FTPContext *s)
96 if (s->control_buf_ptr >= s->control_buf_end) {
97 len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
103 s->control_buf_ptr = s->control_buffer;
104 s->control_buf_end = s->control_buffer + len;
107 return *s->control_buf_ptr++;
110 static int ftp_get_line(FTPContext *s, char *line, int line_size)
122 if (q > line && q[-1] == '\r')
127 if ((q - line) < line_size - 1)
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.
138 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
140 int err, i, dash = 0, result = 0, code_found = 0, linesize;
141 char buf[CONTROL_BUFFER_SIZE];
142 AVBPrint line_buffer;
145 av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
147 while (!code_found || dash) {
148 if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
150 av_bprint_finalize(&line_buffer, NULL);
154 av_log(s, AV_LOG_DEBUG, "%s\n", buf);
156 linesize = strlen(buf);
159 for (i = 0; i < 3; ++i) {
160 if (buf[i] < '0' || buf[i] > '9') {
174 for (i = 0; response_codes[i]; ++i) {
175 if (err == response_codes[i]) {
184 av_bprintf(&line_buffer, "%s\r\n", buf);
186 if (!dash && buf[3] == '-')
188 else if (err == dash && buf[3] == ' ')
195 av_bprint_finalize(&line_buffer, line);
199 static int ftp_send_command(FTPContext *s, const char *command,
200 const int response_codes[], char **response)
204 ff_dlog(s, "%s", command);
209 if (!s->conn_control)
212 if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
218 if (response_codes) {
219 return ftp_status(s, response, response_codes);
224 static void ftp_close_data_connection(FTPContext *s)
226 ffurl_closep(&s->conn_data);
228 s->state = DISCONNECTED;
231 static void ftp_close_both_connections(FTPContext *s)
233 ffurl_closep(&s->conn_control);
234 ftp_close_data_connection(s);
237 static int ftp_auth(FTPContext *s)
239 char buf[CONTROL_BUFFER_SIZE];
241 static const int user_codes[] = {331, 230, 0};
242 static const int pass_codes[] = {230, 0};
244 snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
245 err = ftp_send_command(s, buf, user_codes, NULL);
248 snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
249 err = ftp_send_command(s, buf, pass_codes, NULL);
251 return AVERROR(EACCES);
254 return AVERROR(EACCES);
259 static int ftp_passive_mode_epsv(FTPContext *s)
261 char *res = NULL, *start = NULL, *end = NULL;
263 static const char d = '|';
264 static const char *command = "EPSV\r\n";
265 static const int epsv_codes[] = {229, 0};
267 if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
270 for (i = 0; res[i]; ++i) {
273 } else if (res[i] == ')') {
282 if (strlen(start) < 5)
284 if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
289 s->server_data_port = atoi(start);
290 ff_dlog(s, "Server data port: %d\n", s->server_data_port);
297 s->server_data_port = -1;
298 return AVERROR(ENOSYS);
301 static int ftp_passive_mode(FTPContext *s)
303 char *res = NULL, *start = NULL, *end = NULL;
305 static const char *command = "PASV\r\n";
306 static const int pasv_codes[] = {227, 0};
308 if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
311 for (i = 0; res[i]; ++i) {
314 } else if (res[i] == ')') {
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;
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);
343 s->server_data_port = -1;
347 static int ftp_current_dir(FTPContext *s)
349 char *res = NULL, *start = NULL, *end = NULL;
351 static const char *command = "PWD\r\n";
352 static const int pwd_codes[] = {257, 0};
354 if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
357 for (i = 0; res[i]; ++i) {
372 s->path = av_strdup(start);
377 return AVERROR(ENOMEM);
385 static int ftp_file_size(FTPContext *s)
387 char command[CONTROL_BUFFER_SIZE];
389 static const int size_codes[] = {213, 0};
391 snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
392 if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
393 s->filesize = strtoll(&res[4], NULL, 10);
404 static int ftp_retrieve(FTPContext *s)
406 char command[CONTROL_BUFFER_SIZE];
407 static const int retr_codes[] = {150, 125, 0};
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)
415 s->state = DOWNLOADING;
420 static int ftp_store(FTPContext *s)
422 char command[CONTROL_BUFFER_SIZE];
423 static const int stor_codes[] = {150, 125, 0};
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)
431 s->state = UPLOADING;
436 static int ftp_type(FTPContext *s)
438 static const char *command = "TYPE I\r\n";
439 static const int type_codes[] = {200, 0};
441 if (ftp_send_command(s, command, type_codes, NULL) != 200)
447 static int ftp_restart(FTPContext *s, int64_t pos)
449 char command[CONTROL_BUFFER_SIZE];
450 static const int rest_codes[] = {350, 0};
452 snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
453 if (ftp_send_command(s, command, rest_codes, NULL) != 350)
459 static int ftp_set_dir(FTPContext *s)
461 static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */
462 char command[MAX_URL_SIZE];
464 snprintf(command, sizeof(command), "CWD %s\r\n", s->path);
465 if (ftp_send_command(s, command, cwd_codes, NULL) != 250)
470 static int ftp_list_mlsd(FTPContext *s)
472 static const char *command = "MLSD\r\n";
473 static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */
475 if (ftp_send_command(s, command, mlsd_codes, NULL) != 150)
476 return AVERROR(ENOSYS);
477 s->listing_method = MLSD;
481 static int ftp_list_nlst(FTPContext *s)
483 static const char *command = "NLST\r\n";
484 static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0};
486 if (ftp_send_command(s, command, nlst_codes, NULL) != 226)
487 return AVERROR(ENOSYS);
488 s->listing_method = NLST;
492 static int ftp_has_feature(FTPContext *s, const char *feature_name);
494 static int ftp_list(FTPContext *s)
497 s->state = LISTING_DIR;
499 if ((ret = ftp_list_mlsd(s)) < 0)
500 ret = ftp_list_nlst(s);
505 static int ftp_has_feature(FTPContext *s, const char *feature_name)
510 return av_stristr(s->features, feature_name) != NULL;
513 static int ftp_features(FTPContext *s)
515 static const char *feat_command = "FEAT\r\n";
516 static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
517 static const int feat_codes[] = {211, 0};
518 static const int opts_codes[] = {200, 451, 0};
520 av_freep(&s->features);
521 if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
522 av_freep(&s->features);
525 if (ftp_has_feature(s, "UTF8")) {
526 if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200)
533 static int ftp_connect_control_connection(URLContext *h)
535 char buf[CONTROL_BUFFER_SIZE], *response = NULL;
537 AVDictionary *opts = NULL;
538 FTPContext *s = h->priv_data;
539 static const int connect_codes[] = {220, 0};
541 if (!s->conn_control) {
542 ff_url_join(buf, sizeof(buf), "tcp", NULL,
543 s->hostname, s->server_control_port, NULL);
544 if (s->rw_timeout != -1) {
545 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
546 } /* if option is not given, don't pass it and let tcp use its own default */
547 err = ffurl_open_whitelist(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
548 &h->interrupt_callback, &opts,
549 h->protocol_whitelist, h->protocol_blacklist, h);
552 av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
556 /* check if server is ready */
557 if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
558 av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
559 return AVERROR(EACCES);
562 if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
563 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.");
567 if ((err = ftp_auth(s)) < 0) {
568 av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
572 if ((err = ftp_type(s)) < 0) {
573 av_log(h, AV_LOG_ERROR, "Set content type failed\n");
582 static int ftp_connect_data_connection(URLContext *h)
585 char buf[CONTROL_BUFFER_SIZE];
586 AVDictionary *opts = NULL;
587 FTPContext *s = h->priv_data;
590 /* Enter passive mode */
591 if (ftp_passive_mode_epsv(s) < 0) {
592 /* Use PASV as fallback */
593 if ((err = ftp_passive_mode(s)) < 0)
596 /* Open data connection */
597 ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
598 if (s->rw_timeout != -1) {
599 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
600 } /* if option is not given, don't pass it and let tcp use its own default */
601 err = ffurl_open_whitelist(&s->conn_data, buf, h->flags,
602 &h->interrupt_callback, &opts,
603 h->protocol_whitelist, h->protocol_blacklist, h);
609 if ((err = ftp_restart(s, s->position)) < 0)
616 static int ftp_abort(URLContext *h)
618 static const char *command = "ABOR\r\n";
620 static const int abor_codes[] = {225, 226, 0};
621 FTPContext *s = h->priv_data;
623 /* According to RCF 959:
624 "ABOR command tells the server to abort the previous FTP
625 service command and any associated transfer of data."
627 There are FTP server implementations that don't response
628 to any commands during data transfer in passive mode (including ABOR).
630 This implementation closes data connection by force.
633 if (ftp_send_command(s, command, NULL, NULL) < 0) {
634 ftp_close_both_connections(s);
635 if ((err = ftp_connect_control_connection(h)) < 0) {
636 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
640 ftp_close_data_connection(s);
641 if (ftp_status(s, NULL, abor_codes) < 225) {
642 /* wu-ftpd also closes control connection after data connection closing */
643 ffurl_closep(&s->conn_control);
644 if ((err = ftp_connect_control_connection(h)) < 0) {
645 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
654 static int ftp_connect(URLContext *h, const char *url)
656 char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
657 const char *tok_user = NULL, *tok_pass = NULL;
658 char *end = NULL, *newpath = NULL;
660 FTPContext *s = h->priv_data;
662 s->state = DISCONNECTED;
663 s->listing_method = UNKNOWN_METHOD;
668 av_url_split(proto, sizeof(proto),
669 credencials, sizeof(credencials),
670 hostname, sizeof(hostname),
671 &s->server_control_port,
675 tok_user = av_strtok(credencials, ":", &end);
676 tok_pass = av_strtok(end, ":", &end);
678 tok_user = "anonymous";
679 tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
681 s->user = av_strdup(tok_user);
682 s->password = av_strdup(tok_pass);
683 s->hostname = av_strdup(hostname);
684 if (!s->hostname || !s->user || (tok_pass && !s->password)) {
685 return AVERROR(ENOMEM);
688 if (s->server_control_port < 0 || s->server_control_port > 65535)
689 s->server_control_port = 21;
691 if ((err = ftp_connect_control_connection(h)) < 0)
694 if ((err = ftp_current_dir(s)) < 0)
697 newpath = av_append_path_component(s->path, path);
699 return AVERROR(ENOMEM);
706 static int ftp_open(URLContext *h, const char *url, int flags)
708 FTPContext *s = h->priv_data;
711 ff_dlog(h, "ftp protocol open\n");
713 if ((err = ftp_connect(h, url)) < 0)
716 if (ftp_restart(s, 0) < 0) {
719 if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
721 if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
728 av_log(h, AV_LOG_ERROR, "FTP open failed\n");
733 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
735 FTPContext *s = h->priv_data;
737 int64_t new_pos, fake_pos;
739 ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
748 new_pos = s->position + pos;
753 new_pos = s->filesize + pos;
756 return AVERROR(EINVAL);
763 av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
764 return AVERROR(EINVAL);
767 fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
768 if (fake_pos != s->position) {
769 if ((err = ftp_abort(h)) < 0)
771 s->position = fake_pos;
776 static int ftp_read(URLContext *h, unsigned char *buf, int size)
778 FTPContext *s = h->priv_data;
779 int read, err, retry_done = 0;
781 ff_dlog(h, "ftp protocol read %d bytes\n", size);
783 if (s->state == DISCONNECTED) {
785 if (s->position >= s->filesize)
787 if ((err = ftp_connect_data_connection(h)) < 0)
790 if (s->state == READY) {
791 if (s->position >= s->filesize)
793 if ((err = ftp_retrieve(s)) < 0)
796 if (s->conn_data && s->state == DOWNLOADING) {
797 read = ffurl_read(s->conn_data, buf, size);
800 if (s->position >= s->filesize) {
801 /* server will terminate, but keep current position to avoid madness */
802 /* save position to restart from it */
803 int64_t pos = s->position;
804 if (ftp_abort(h) < 0) {
811 if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
812 /* Server closed connection. Probably due to inactivity */
813 int64_t pos = s->position;
814 av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
815 if ((err = ftp_abort(h)) < 0)
817 if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
818 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
829 av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
833 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
836 FTPContext *s = h->priv_data;
839 ff_dlog(h, "ftp protocol write %d bytes\n", size);
841 if (s->state == DISCONNECTED) {
842 if ((err = ftp_connect_data_connection(h)) < 0)
845 if (s->state == READY) {
846 if ((err = ftp_store(s)) < 0)
849 if (s->conn_data && s->state == UPLOADING) {
850 written = ffurl_write(s->conn_data, buf, size);
852 s->position += written;
853 s->filesize = FFMAX(s->filesize, s->position);
858 av_log(h, AV_LOG_ERROR, "FTP write failed\n");
862 static int ftp_close(URLContext *h)
864 FTPContext *s = h->priv_data;
866 ff_dlog(h, "ftp protocol close\n");
868 ftp_close_both_connections(s);
870 av_freep(&s->password);
871 av_freep(&s->hostname);
873 av_freep(&s->features);
878 static int ftp_get_file_handle(URLContext *h)
880 FTPContext *s = h->priv_data;
882 ff_dlog(h, "ftp protocol get_file_handle\n");
885 return ffurl_get_file_handle(s->conn_data);
890 static int ftp_shutdown(URLContext *h, int flags)
892 FTPContext *s = h->priv_data;
894 ff_dlog(h, "ftp protocol shutdown\n");
897 return ffurl_shutdown(s->conn_data, flags);
902 static int ftp_open_dir(URLContext *h)
904 FTPContext *s = h->priv_data;
907 if ((ret = ftp_connect(h, h->filename)) < 0)
909 if ((ret = ftp_set_dir(s)) < 0)
911 if ((ret = ftp_connect_data_connection(h)) < 0)
913 if ((ret = ftp_list(s)) < 0)
915 s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
916 if (!s->dir_buffer) {
917 ret = AVERROR(ENOMEM);
920 s->dir_buffer[0] = 0;
921 if (s->conn_data && s->state == LISTING_DIR)
924 ffurl_closep(&s->conn_control);
925 ffurl_closep(&s->conn_data);
929 static int64_t ftp_parse_date(const char *date)
932 memset(&tv, 0, sizeof(struct tm));
933 av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
934 return INT64_C(1000000) * av_timegm(&tv);
937 static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
939 next->name = av_strdup(line);
943 static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
946 ff_dlog(NULL, "%s\n", mlsd);
947 while(fact = av_strtok(mlsd, ";", &mlsd)) {
948 if (fact[0] == ' ') {
949 next->name = av_strdup(&fact[1]);
952 fact = av_strtok(fact, "=", &value);
953 if (!av_strcasecmp(fact, "type")) {
954 if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
956 if (!av_strcasecmp(value, "dir"))
957 next->type = AVIO_ENTRY_DIRECTORY;
958 else if (!av_strcasecmp(value, "file"))
959 next->type = AVIO_ENTRY_FILE;
960 else if (!av_strcasecmp(value, "OS.unix=slink:"))
961 next->type = AVIO_ENTRY_SYMBOLIC_LINK;
962 } else if (!av_strcasecmp(fact, "modify")) {
963 next->modification_timestamp = ftp_parse_date(value);
964 } else if (!av_strcasecmp(fact, "UNIX.mode")) {
965 next->filemode = strtoumax(value, NULL, 8);
966 } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
967 next->user_id = strtoumax(value, NULL, 10);
968 else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
969 next->group_id = strtoumax(value, NULL, 10);
970 else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
971 next->size = strtoll(value, NULL, 10);
977 * @return 0 on success, negative on error, positive on entry to discard.
979 static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
981 FTPContext *s = h->priv_data;
983 switch (s->listing_method) {
985 return ftp_parse_entry_mlsd(line, next);
987 return ftp_parse_entry_nlst(line, next);
994 static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
996 FTPContext *s = h->priv_data;
1002 start = s->dir_buffer + s->dir_buffer_offset;
1003 while (!(found = strstr(start, "\n"))) {
1005 return AVERROR(EIO);
1006 s->dir_buffer_size -= s->dir_buffer_offset;
1007 s->dir_buffer_offset = 0;
1008 if (s->dir_buffer_size)
1009 memmove(s->dir_buffer, start, s->dir_buffer_size);
1010 ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
1017 s->dir_buffer_size += ret;
1018 s->dir_buffer[s->dir_buffer_size] = 0;
1019 start = s->dir_buffer;
1022 s->dir_buffer_offset += (found + 1 - start);
1024 if (found > start && found[-1] == '\r')
1027 *next = ff_alloc_dir_entry();
1029 return AVERROR(ENOMEM);
1030 (*next)->utf8 = s->utf8;
1031 ret = ftp_parse_entry(h, start, *next);
1033 avio_free_directory_entry(next);
1041 static int ftp_close_dir(URLContext *h)
1043 FTPContext *s = h->priv_data;
1044 av_freep(&s->dir_buffer);
1045 ffurl_closep(&s->conn_control);
1046 ffurl_closep(&s->conn_data);
1050 static int ftp_delete(URLContext *h)
1052 FTPContext *s = h->priv_data;
1053 char command[MAX_URL_SIZE];
1054 static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
1055 static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
1058 if ((ret = ftp_connect(h, h->filename)) < 0)
1061 snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
1062 if (ftp_send_command(s, command, del_codes, NULL) == 250) {
1067 snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
1068 if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
1078 static int ftp_move(URLContext *h_src, URLContext *h_dst)
1080 FTPContext *s = h_src->priv_data;
1081 char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
1082 static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
1083 static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
1086 if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
1089 snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
1090 if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
1095 av_url_split(0, 0, 0, 0, 0, 0, 0,
1098 snprintf(command, sizeof(command), "RNTO %s\r\n", path);
1099 if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
1109 const URLProtocol ff_ftp_protocol = {
1111 .url_open = ftp_open,
1112 .url_read = ftp_read,
1113 .url_write = ftp_write,
1114 .url_seek = ftp_seek,
1115 .url_close = ftp_close,
1116 .url_get_file_handle = ftp_get_file_handle,
1117 .url_shutdown = ftp_shutdown,
1118 .priv_data_size = sizeof(FTPContext),
1119 .priv_data_class = &ftp_context_class,
1120 .url_open_dir = ftp_open_dir,
1121 .url_read_dir = ftp_read_dir,
1122 .url_close_dir = ftp_close_dir,
1123 .url_delete = ftp_delete,
1124 .url_move = ftp_move,
1125 .flags = URL_PROTOCOL_FLAG_NETWORK,
1126 .default_whitelist = "tcp",