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 && strlen(res) > 4) {
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, 202, 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 int ret = ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
525 if (ret == 200 || ret == 202)
532 static int ftp_connect_control_connection(URLContext *h)
534 char buf[CONTROL_BUFFER_SIZE], *response = NULL;
536 AVDictionary *opts = NULL;
537 FTPContext *s = h->priv_data;
538 static const int connect_codes[] = {220, 0};
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);
551 av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
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);
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.");
566 if ((err = ftp_auth(s)) < 0) {
567 av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
571 if ((err = ftp_type(s)) < 0) {
572 av_log(h, AV_LOG_ERROR, "Set content type failed\n");
581 static int ftp_connect_data_connection(URLContext *h)
584 char buf[CONTROL_BUFFER_SIZE];
585 AVDictionary *opts = NULL;
586 FTPContext *s = h->priv_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)
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);
608 if ((err = ftp_restart(s, s->position)) < 0)
615 static int ftp_abort(URLContext *h)
617 static const char *command = "ABOR\r\n";
619 static const int abor_codes[] = {225, 226, 0};
620 FTPContext *s = h->priv_data;
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."
626 There are FTP server implementations that don't response
627 to any commands during data transfer in passive mode (including ABOR).
629 This implementation closes data connection by force.
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");
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");
653 static int ftp_connect(URLContext *h, const char *url)
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;
659 FTPContext *s = h->priv_data;
661 s->state = DISCONNECTED;
662 s->listing_method = UNKNOWN_METHOD;
667 av_url_split(proto, sizeof(proto),
668 credencials, sizeof(credencials),
669 hostname, sizeof(hostname),
670 &s->server_control_port,
674 tok_user = av_strtok(credencials, ":", &end);
675 tok_pass = av_strtok(end, ":", &end);
677 tok_user = "anonymous";
678 tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
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);
687 if (s->server_control_port < 0 || s->server_control_port > 65535)
688 s->server_control_port = 21;
690 if ((err = ftp_connect_control_connection(h)) < 0)
693 if ((err = ftp_current_dir(s)) < 0)
696 newpath = av_append_path_component(s->path, path);
698 return AVERROR(ENOMEM);
705 static int ftp_open(URLContext *h, const char *url, int flags)
707 FTPContext *s = h->priv_data;
710 ff_dlog(h, "ftp protocol open\n");
712 if ((err = ftp_connect(h, url)) < 0)
715 if (ftp_restart(s, 0) < 0) {
718 if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
720 if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
727 av_log(h, AV_LOG_ERROR, "FTP open failed\n");
732 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
734 FTPContext *s = h->priv_data;
736 int64_t new_pos, fake_pos;
738 ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
747 new_pos = s->position + pos;
752 new_pos = s->filesize + pos;
755 return AVERROR(EINVAL);
762 av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
763 return AVERROR(EINVAL);
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)
770 s->position = fake_pos;
775 static int ftp_read(URLContext *h, unsigned char *buf, int size)
777 FTPContext *s = h->priv_data;
778 int read, err, retry_done = 0;
780 ff_dlog(h, "ftp protocol read %d bytes\n", size);
782 if (s->state == DISCONNECTED) {
784 if (s->position >= s->filesize)
786 if ((err = ftp_connect_data_connection(h)) < 0)
789 if (s->state == READY) {
790 if (s->position >= s->filesize)
792 if ((err = ftp_retrieve(s)) < 0)
795 if (s->conn_data && s->state == DOWNLOADING) {
796 read = ffurl_read(s->conn_data, buf, size);
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) {
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)
816 if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
817 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
828 av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
832 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
835 FTPContext *s = h->priv_data;
838 ff_dlog(h, "ftp protocol write %d bytes\n", size);
840 if (s->state == DISCONNECTED) {
841 if ((err = ftp_connect_data_connection(h)) < 0)
844 if (s->state == READY) {
845 if ((err = ftp_store(s)) < 0)
848 if (s->conn_data && s->state == UPLOADING) {
849 written = ffurl_write(s->conn_data, buf, size);
851 s->position += written;
852 s->filesize = FFMAX(s->filesize, s->position);
857 av_log(h, AV_LOG_ERROR, "FTP write failed\n");
861 static int ftp_close(URLContext *h)
863 FTPContext *s = h->priv_data;
865 ff_dlog(h, "ftp protocol close\n");
867 ftp_close_both_connections(s);
869 av_freep(&s->password);
870 av_freep(&s->hostname);
872 av_freep(&s->features);
877 static int ftp_get_file_handle(URLContext *h)
879 FTPContext *s = h->priv_data;
881 ff_dlog(h, "ftp protocol get_file_handle\n");
884 return ffurl_get_file_handle(s->conn_data);
889 static int ftp_shutdown(URLContext *h, int flags)
891 FTPContext *s = h->priv_data;
893 ff_dlog(h, "ftp protocol shutdown\n");
896 return ffurl_shutdown(s->conn_data, flags);
901 static int ftp_open_dir(URLContext *h)
903 FTPContext *s = h->priv_data;
906 if ((ret = ftp_connect(h, h->filename)) < 0)
908 if ((ret = ftp_set_dir(s)) < 0)
910 if ((ret = ftp_connect_data_connection(h)) < 0)
912 if ((ret = ftp_list(s)) < 0)
914 s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
915 if (!s->dir_buffer) {
916 ret = AVERROR(ENOMEM);
919 s->dir_buffer[0] = 0;
920 if (s->conn_data && s->state == LISTING_DIR)
923 ffurl_closep(&s->conn_control);
924 ffurl_closep(&s->conn_data);
928 static int64_t ftp_parse_date(const char *date)
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);
936 static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
938 next->name = av_strdup(line);
942 static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
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]);
951 fact = av_strtok(fact, "=", &value);
952 if (!av_strcasecmp(fact, "type")) {
953 if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
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);
976 * @return 0 on success, negative on error, positive on entry to discard.
978 static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
980 FTPContext *s = h->priv_data;
982 switch (s->listing_method) {
984 return ftp_parse_entry_mlsd(line, next);
986 return ftp_parse_entry_nlst(line, next);
993 static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
995 FTPContext *s = h->priv_data;
1001 start = s->dir_buffer + s->dir_buffer_offset;
1002 while (!(found = strstr(start, "\n"))) {
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));
1016 s->dir_buffer_size += ret;
1017 s->dir_buffer[s->dir_buffer_size] = 0;
1018 start = s->dir_buffer;
1021 s->dir_buffer_offset += (found + 1 - start);
1023 if (found > start && found[-1] == '\r')
1026 *next = ff_alloc_dir_entry();
1028 return AVERROR(ENOMEM);
1029 (*next)->utf8 = s->utf8;
1030 ret = ftp_parse_entry(h, start, *next);
1032 avio_free_directory_entry(next);
1040 static int ftp_close_dir(URLContext *h)
1042 FTPContext *s = h->priv_data;
1043 av_freep(&s->dir_buffer);
1044 ffurl_closep(&s->conn_control);
1045 ffurl_closep(&s->conn_data);
1049 static int ftp_delete(URLContext *h)
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};
1057 if ((ret = ftp_connect(h, h->filename)) < 0)
1060 snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
1061 if (ftp_send_command(s, command, del_codes, NULL) == 250) {
1066 snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
1067 if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
1077 static int ftp_move(URLContext *h_src, URLContext *h_dst)
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};
1085 if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
1088 snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
1089 if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
1094 av_url_split(0, 0, 0, 0, 0, 0, 0,
1097 snprintf(command, sizeof(command), "RNTO %s\r\n", path);
1098 if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
1108 const URLProtocol ff_ftp_protocol = {
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",