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_list(FTPContext *s)
495 s->state = LISTING_DIR;
497 if ((ret = ftp_list_mlsd(s)) < 0)
498 ret = ftp_list_nlst(s);
503 static int ftp_has_feature(FTPContext *s, const char *feature_name)
508 return av_stristr(s->features, feature_name) != NULL;
511 static int ftp_features(FTPContext *s)
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, 451, 0};
518 av_freep(&s->features);
519 if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
520 av_freep(&s->features);
523 if (ftp_has_feature(s, "UTF8")) {
524 if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200)
531 static int ftp_connect_control_connection(URLContext *h)
533 char buf[CONTROL_BUFFER_SIZE], *response = NULL;
535 AVDictionary *opts = NULL;
536 FTPContext *s = h->priv_data;
537 static const int connect_codes[] = {220, 0};
539 if (!s->conn_control) {
540 ff_url_join(buf, sizeof(buf), "tcp", NULL,
541 s->hostname, s->server_control_port, NULL);
542 if (s->rw_timeout != -1) {
543 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
544 } /* if option is not given, don't pass it and let tcp use its own default */
545 err = ffurl_open_whitelist(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
546 &h->interrupt_callback, &opts,
547 h->protocol_whitelist, h->protocol_blacklist, h);
550 av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
554 /* check if server is ready */
555 if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
556 av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
557 return AVERROR(EACCES);
560 if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
561 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.");
565 if ((err = ftp_auth(s)) < 0) {
566 av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
570 if ((err = ftp_type(s)) < 0) {
571 av_log(h, AV_LOG_ERROR, "Set content type failed\n");
580 static int ftp_connect_data_connection(URLContext *h)
583 char buf[CONTROL_BUFFER_SIZE];
584 AVDictionary *opts = NULL;
585 FTPContext *s = h->priv_data;
588 /* Enter passive mode */
589 if (ftp_passive_mode_epsv(s) < 0) {
590 /* Use PASV as fallback */
591 if ((err = ftp_passive_mode(s)) < 0)
594 /* Open data connection */
595 ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
596 if (s->rw_timeout != -1) {
597 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
598 } /* if option is not given, don't pass it and let tcp use its own default */
599 err = ffurl_open_whitelist(&s->conn_data, buf, h->flags,
600 &h->interrupt_callback, &opts,
601 h->protocol_whitelist, h->protocol_blacklist, h);
607 if ((err = ftp_restart(s, s->position)) < 0)
614 static int ftp_abort(URLContext *h)
616 static const char *command = "ABOR\r\n";
618 static const int abor_codes[] = {225, 226, 0};
619 FTPContext *s = h->priv_data;
621 /* According to RCF 959:
622 "ABOR command tells the server to abort the previous FTP
623 service command and any associated transfer of data."
625 There are FTP server implementations that don't response
626 to any commands during data transfer in passive mode (including ABOR).
628 This implementation closes data connection by force.
631 if (ftp_send_command(s, command, NULL, NULL) < 0) {
632 ftp_close_both_connections(s);
633 if ((err = ftp_connect_control_connection(h)) < 0) {
634 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
638 ftp_close_data_connection(s);
639 if (ftp_status(s, NULL, abor_codes) < 225) {
640 /* wu-ftpd also closes control connection after data connection closing */
641 ffurl_closep(&s->conn_control);
642 if ((err = ftp_connect_control_connection(h)) < 0) {
643 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
652 static int ftp_connect(URLContext *h, const char *url)
654 char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
655 const char *tok_user = NULL, *tok_pass = NULL;
656 char *end = NULL, *newpath = NULL;
658 FTPContext *s = h->priv_data;
660 s->state = DISCONNECTED;
661 s->listing_method = UNKNOWN_METHOD;
666 av_url_split(proto, sizeof(proto),
667 credencials, sizeof(credencials),
668 hostname, sizeof(hostname),
669 &s->server_control_port,
673 tok_user = av_strtok(credencials, ":", &end);
674 tok_pass = av_strtok(end, ":", &end);
676 tok_user = "anonymous";
677 tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
679 s->user = av_strdup(tok_user);
680 s->password = av_strdup(tok_pass);
681 s->hostname = av_strdup(hostname);
682 if (!s->hostname || !s->user || (tok_pass && !s->password)) {
683 return AVERROR(ENOMEM);
686 if (s->server_control_port < 0 || s->server_control_port > 65535)
687 s->server_control_port = 21;
689 if ((err = ftp_connect_control_connection(h)) < 0)
692 if ((err = ftp_current_dir(s)) < 0)
695 newpath = av_append_path_component(s->path, path);
697 return AVERROR(ENOMEM);
704 static int ftp_open(URLContext *h, const char *url, int flags)
706 FTPContext *s = h->priv_data;
709 ff_dlog(h, "ftp protocol open\n");
711 if ((err = ftp_connect(h, url)) < 0)
714 if (ftp_restart(s, 0) < 0) {
717 if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
719 if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
726 av_log(h, AV_LOG_ERROR, "FTP open failed\n");
731 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
733 FTPContext *s = h->priv_data;
735 int64_t new_pos, fake_pos;
737 ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
746 new_pos = s->position + pos;
751 new_pos = s->filesize + pos;
754 return AVERROR(EINVAL);
761 av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
762 return AVERROR(EINVAL);
765 fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
766 if (fake_pos != s->position) {
767 if ((err = ftp_abort(h)) < 0)
769 s->position = fake_pos;
774 static int ftp_read(URLContext *h, unsigned char *buf, int size)
776 FTPContext *s = h->priv_data;
777 int read, err, retry_done = 0;
779 ff_dlog(h, "ftp protocol read %d bytes\n", size);
781 if (s->state == DISCONNECTED) {
783 if (s->position >= s->filesize)
785 if ((err = ftp_connect_data_connection(h)) < 0)
788 if (s->state == READY) {
789 if (s->position >= s->filesize)
791 if ((err = ftp_retrieve(s)) < 0)
794 if (s->conn_data && s->state == DOWNLOADING) {
795 read = ffurl_read(s->conn_data, buf, size);
798 if (s->position >= s->filesize) {
799 /* server will terminate, but keep current position to avoid madness */
800 /* save position to restart from it */
801 int64_t pos = s->position;
802 if (ftp_abort(h) < 0) {
809 if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
810 /* Server closed connection. Probably due to inactivity */
811 int64_t pos = s->position;
812 av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
813 if ((err = ftp_abort(h)) < 0)
815 if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
816 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
827 av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
831 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
834 FTPContext *s = h->priv_data;
837 ff_dlog(h, "ftp protocol write %d bytes\n", size);
839 if (s->state == DISCONNECTED) {
840 if ((err = ftp_connect_data_connection(h)) < 0)
843 if (s->state == READY) {
844 if ((err = ftp_store(s)) < 0)
847 if (s->conn_data && s->state == UPLOADING) {
848 written = ffurl_write(s->conn_data, buf, size);
850 s->position += written;
851 s->filesize = FFMAX(s->filesize, s->position);
856 av_log(h, AV_LOG_ERROR, "FTP write failed\n");
860 static int ftp_close(URLContext *h)
862 FTPContext *s = h->priv_data;
864 ff_dlog(h, "ftp protocol close\n");
866 ftp_close_both_connections(s);
868 av_freep(&s->password);
869 av_freep(&s->hostname);
871 av_freep(&s->features);
876 static int ftp_get_file_handle(URLContext *h)
878 FTPContext *s = h->priv_data;
880 ff_dlog(h, "ftp protocol get_file_handle\n");
883 return ffurl_get_file_handle(s->conn_data);
888 static int ftp_shutdown(URLContext *h, int flags)
890 FTPContext *s = h->priv_data;
892 ff_dlog(h, "ftp protocol shutdown\n");
895 return ffurl_shutdown(s->conn_data, flags);
900 static int ftp_open_dir(URLContext *h)
902 FTPContext *s = h->priv_data;
905 if ((ret = ftp_connect(h, h->filename)) < 0)
907 if ((ret = ftp_set_dir(s)) < 0)
909 if ((ret = ftp_connect_data_connection(h)) < 0)
911 if ((ret = ftp_list(s)) < 0)
913 s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
914 if (!s->dir_buffer) {
915 ret = AVERROR(ENOMEM);
918 s->dir_buffer[0] = 0;
919 if (s->conn_data && s->state == LISTING_DIR)
922 ffurl_closep(&s->conn_control);
923 ffurl_closep(&s->conn_data);
927 static int64_t ftp_parse_date(const char *date)
930 memset(&tv, 0, sizeof(struct tm));
931 av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
932 return INT64_C(1000000) * av_timegm(&tv);
935 static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
937 next->name = av_strdup(line);
941 static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
944 ff_dlog(NULL, "%s\n", mlsd);
945 while(fact = av_strtok(mlsd, ";", &mlsd)) {
946 if (fact[0] == ' ') {
947 next->name = av_strdup(&fact[1]);
950 fact = av_strtok(fact, "=", &value);
951 if (!av_strcasecmp(fact, "type")) {
952 if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
954 if (!av_strcasecmp(value, "dir"))
955 next->type = AVIO_ENTRY_DIRECTORY;
956 else if (!av_strcasecmp(value, "file"))
957 next->type = AVIO_ENTRY_FILE;
958 else if (!av_strcasecmp(value, "OS.unix=slink:"))
959 next->type = AVIO_ENTRY_SYMBOLIC_LINK;
960 } else if (!av_strcasecmp(fact, "modify")) {
961 next->modification_timestamp = ftp_parse_date(value);
962 } else if (!av_strcasecmp(fact, "UNIX.mode")) {
963 next->filemode = strtoumax(value, NULL, 8);
964 } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
965 next->user_id = strtoumax(value, NULL, 10);
966 else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
967 next->group_id = strtoumax(value, NULL, 10);
968 else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
969 next->size = strtoll(value, NULL, 10);
975 * @return 0 on success, negative on error, positive on entry to discard.
977 static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
979 FTPContext *s = h->priv_data;
981 switch (s->listing_method) {
983 return ftp_parse_entry_mlsd(line, next);
985 return ftp_parse_entry_nlst(line, next);
992 static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
994 FTPContext *s = h->priv_data;
1000 start = s->dir_buffer + s->dir_buffer_offset;
1001 while (!(found = strstr(start, "\n"))) {
1003 return AVERROR(EIO);
1004 s->dir_buffer_size -= s->dir_buffer_offset;
1005 s->dir_buffer_offset = 0;
1006 if (s->dir_buffer_size)
1007 memmove(s->dir_buffer, start, s->dir_buffer_size);
1008 ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
1015 s->dir_buffer_size += ret;
1016 s->dir_buffer[s->dir_buffer_size] = 0;
1017 start = s->dir_buffer;
1020 s->dir_buffer_offset += (found + 1 - start);
1022 if (found > start && found[-1] == '\r')
1025 *next = ff_alloc_dir_entry();
1027 return AVERROR(ENOMEM);
1028 (*next)->utf8 = s->utf8;
1029 ret = ftp_parse_entry(h, start, *next);
1031 avio_free_directory_entry(next);
1039 static int ftp_close_dir(URLContext *h)
1041 FTPContext *s = h->priv_data;
1042 av_freep(&s->dir_buffer);
1043 ffurl_closep(&s->conn_control);
1044 ffurl_closep(&s->conn_data);
1048 static int ftp_delete(URLContext *h)
1050 FTPContext *s = h->priv_data;
1051 char command[MAX_URL_SIZE];
1052 static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
1053 static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
1056 if ((ret = ftp_connect(h, h->filename)) < 0)
1059 snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
1060 if (ftp_send_command(s, command, del_codes, NULL) == 250) {
1065 snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
1066 if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
1076 static int ftp_move(URLContext *h_src, URLContext *h_dst)
1078 FTPContext *s = h_src->priv_data;
1079 char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
1080 static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
1081 static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
1084 if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
1087 snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
1088 if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
1093 av_url_split(0, 0, 0, 0, 0, 0, 0,
1096 snprintf(command, sizeof(command), "RNTO %s\r\n", path);
1097 if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
1107 const URLProtocol ff_ftp_protocol = {
1109 .url_open = ftp_open,
1110 .url_read = ftp_read,
1111 .url_write = ftp_write,
1112 .url_seek = ftp_seek,
1113 .url_close = ftp_close,
1114 .url_get_file_handle = ftp_get_file_handle,
1115 .url_shutdown = ftp_shutdown,
1116 .priv_data_size = sizeof(FTPContext),
1117 .priv_data_class = &ftp_context_class,
1118 .url_open_dir = ftp_open_dir,
1119 .url_read_dir = ftp_read_dir,
1120 .url_close_dir = ftp_close_dir,
1121 .url_delete = ftp_delete,
1122 .url_move = ftp_move,
1123 .flags = URL_PROTOCOL_FLAG_NETWORK,
1124 .default_whitelist = "tcp",