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/parseutils.h"
26 #include "libavutil/opt.h"
27 #include "libavutil/bprint.h"
29 #define CONTROL_BUFFER_SIZE 1024
30 #define DIR_BUFFER_SIZE 4096
49 URLContext *conn_control; /**< Control connection */
50 URLContext *conn_data; /**< Data connection, NULL when not connected */
51 uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
52 uint8_t *control_buf_ptr, *control_buf_end;
53 int server_data_port; /**< Data connection port opened by server, -1 on error. */
54 int server_control_port; /**< Control connection port, default is 21 */
55 char *hostname; /**< Server address. */
56 char *user; /**< Server user */
57 char *password; /**< Server user's password */
58 char *path; /**< Path to resource on server. */
59 int64_t filesize; /**< Size of file on server, -1 on error. */
60 int64_t position; /**< Current position, calculated. */
61 int rw_timeout; /**< Network timeout. */
62 const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */
63 int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */
64 FTPState state; /**< State of data connection */
65 FTPListingMethod listing_method; /**< Called listing method */
66 char *features; /**< List of server's features represented as raw response */
68 size_t dir_buffer_size;
69 size_t dir_buffer_offset;
73 #define OFFSET(x) offsetof(FTPContext, x)
74 #define D AV_OPT_FLAG_DECODING_PARAM
75 #define E AV_OPT_FLAG_ENCODING_PARAM
76 static const AVOption options[] = {
77 {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
78 {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
79 {"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 },
83 static const AVClass ftp_context_class = {
85 .item_name = av_default_item_name,
87 .version = LIBAVUTIL_VERSION_INT,
90 static int ftp_close(URLContext *h);
92 static int ftp_getc(FTPContext *s)
95 if (s->control_buf_ptr >= s->control_buf_end) {
96 len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
102 s->control_buf_ptr = s->control_buffer;
103 s->control_buf_end = s->control_buffer + len;
106 return *s->control_buf_ptr++;
109 static int ftp_get_line(FTPContext *s, char *line, int line_size)
121 if (q > line && q[-1] == '\r')
126 if ((q - line) < line_size - 1)
133 * This routine returns ftp server response code.
134 * Server may send more than one response for a certain command.
135 * First expected code is returned.
137 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
139 int err, i, dash = 0, result = 0, code_found = 0, linesize;
140 char buf[CONTROL_BUFFER_SIZE];
141 AVBPrint line_buffer;
144 av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
146 while (!code_found || dash) {
147 if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
149 av_bprint_finalize(&line_buffer, NULL);
153 av_log(s, AV_LOG_DEBUG, "%s\n", buf);
155 linesize = strlen(buf);
158 for (i = 0; i < 3; ++i) {
159 if (buf[i] < '0' || buf[i] > '9') {
173 for (i = 0; response_codes[i]; ++i) {
174 if (err == response_codes[i]) {
183 av_bprintf(&line_buffer, "%s\r\n", buf);
185 if (!dash && buf[3] == '-')
187 else if (err == dash && buf[3] == ' ')
194 av_bprint_finalize(&line_buffer, line);
198 static int ftp_send_command(FTPContext *s, const char *command,
199 const int response_codes[], char **response)
203 av_dlog(s, "%s", command);
208 if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
214 if (response_codes) {
215 return ftp_status(s, response, response_codes);
220 static void ftp_close_data_connection(FTPContext *s)
222 ffurl_closep(&s->conn_data);
224 s->state = DISCONNECTED;
227 static void ftp_close_both_connections(FTPContext *s)
229 ffurl_closep(&s->conn_control);
230 ftp_close_data_connection(s);
233 static int ftp_auth(FTPContext *s)
235 char buf[CONTROL_BUFFER_SIZE];
237 static const int user_codes[] = {331, 230, 0};
238 static const int pass_codes[] = {230, 0};
240 snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
241 err = ftp_send_command(s, buf, user_codes, NULL);
244 snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
245 err = ftp_send_command(s, buf, pass_codes, NULL);
247 return AVERROR(EACCES);
250 return AVERROR(EACCES);
255 static int ftp_passive_mode_epsv(FTPContext *s)
257 char *res = NULL, *start = NULL, *end = NULL;
259 static const char d = '|';
260 static const char *command = "EPSV\r\n";
261 static const int epsv_codes[] = {229, 0};
263 if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
266 for (i = 0; res[i]; ++i) {
269 } else if (res[i] == ')') {
278 if (strlen(start) < 5)
280 if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
285 s->server_data_port = atoi(start);
286 av_dlog(s, "Server data port: %d\n", s->server_data_port);
293 s->server_data_port = -1;
294 return AVERROR(ENOSYS);
297 static int ftp_passive_mode(FTPContext *s)
299 char *res = NULL, *start = NULL, *end = NULL;
301 static const char *command = "PASV\r\n";
302 static const int pasv_codes[] = {227, 0};
304 if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
307 for (i = 0; res[i]; ++i) {
310 } else if (res[i] == ')') {
320 if (!av_strtok(start, ",", &end)) goto fail;
321 if (!av_strtok(end, ",", &end)) goto fail;
322 if (!av_strtok(end, ",", &end)) goto fail;
323 if (!av_strtok(end, ",", &end)) goto fail;
325 /* parse port number */
326 start = av_strtok(end, ",", &end);
327 if (!start) goto fail;
328 s->server_data_port = atoi(start) * 256;
329 start = av_strtok(end, ",", &end);
330 if (!start) goto fail;
331 s->server_data_port += atoi(start);
332 av_dlog(s, "Server data port: %d\n", s->server_data_port);
339 s->server_data_port = -1;
343 static int ftp_current_dir(FTPContext *s)
345 char *res = NULL, *start = NULL, *end = NULL;
347 static const char *command = "PWD\r\n";
348 static const int pwd_codes[] = {257, 0};
350 if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
353 for (i = 0; res[i]; ++i) {
368 s->path = av_strdup(start);
373 return AVERROR(ENOMEM);
381 static int ftp_file_size(FTPContext *s)
383 char command[CONTROL_BUFFER_SIZE];
385 static const int size_codes[] = {213, 0};
387 snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
388 if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
389 s->filesize = strtoll(&res[4], NULL, 10);
400 static int ftp_retrieve(FTPContext *s)
402 char command[CONTROL_BUFFER_SIZE];
403 static const int retr_codes[] = {150, 0};
405 snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
406 if (ftp_send_command(s, command, retr_codes, NULL) != 150)
409 s->state = DOWNLOADING;
414 static int ftp_store(FTPContext *s)
416 char command[CONTROL_BUFFER_SIZE];
417 static const int stor_codes[] = {150, 0};
419 snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
420 if (ftp_send_command(s, command, stor_codes, NULL) != 150)
423 s->state = UPLOADING;
428 static int ftp_type(FTPContext *s)
430 static const char *command = "TYPE I\r\n";
431 static const int type_codes[] = {200, 0};
433 if (ftp_send_command(s, command, type_codes, NULL) != 200)
439 static int ftp_restart(FTPContext *s, int64_t pos)
441 char command[CONTROL_BUFFER_SIZE];
442 static const int rest_codes[] = {350, 0};
444 snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
445 if (ftp_send_command(s, command, rest_codes, NULL) != 350)
451 static int ftp_set_dir(FTPContext *s)
453 static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */
454 char command[MAX_URL_SIZE];
456 snprintf(command, sizeof(command), "CWD %s\r\n", s->path);
457 if (ftp_send_command(s, command, cwd_codes, NULL) != 250)
462 static int ftp_list_mlsd(FTPContext *s)
464 static const char *command = "MLSD\r\n";
465 static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */
467 if (ftp_send_command(s, command, mlsd_codes, NULL) != 150)
468 return AVERROR(ENOSYS);
469 s->listing_method = MLSD;
473 static int ftp_list_nlst(FTPContext *s)
475 static const char *command = "NLST\r\n";
476 static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0};
478 if (ftp_send_command(s, command, nlst_codes, NULL) != 226)
479 return AVERROR(ENOSYS);
480 s->listing_method = NLST;
484 static int ftp_has_feature(FTPContext *s, const char *feature_name);
486 static int ftp_list(FTPContext *s)
489 s->state = LISTING_DIR;
491 if ((ret = ftp_list_mlsd(s)) < 0)
492 ret = ftp_list_nlst(s);
497 static int ftp_has_feature(FTPContext *s, const char *feature_name)
502 return av_stristr(s->features, feature_name) != NULL;
505 static int ftp_features(FTPContext *s)
507 static const char *feat_command = "FEAT\r\n";
508 static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
509 static const int feat_codes[] = {211, 0};
510 static const int opts_codes[] = {200, 451, 0};
512 av_freep(&s->features);
513 if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
514 av_freep(&s->features);
517 if (ftp_has_feature(s, "UTF8")) {
518 if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200)
525 static int ftp_connect_control_connection(URLContext *h)
527 char buf[CONTROL_BUFFER_SIZE], *response = NULL;
529 AVDictionary *opts = NULL;
530 FTPContext *s = h->priv_data;
531 static const int connect_codes[] = {220, 0};
533 if (!s->conn_control) {
534 ff_url_join(buf, sizeof(buf), "tcp", NULL,
535 s->hostname, s->server_control_port, NULL);
536 if (s->rw_timeout != -1) {
537 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
538 } /* if option is not given, don't pass it and let tcp use its own default */
539 err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
540 &h->interrupt_callback, &opts);
543 av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
547 /* check if server is ready */
548 if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
549 av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
550 return AVERROR(EACCES);
553 if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
554 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.");
558 if ((err = ftp_auth(s)) < 0) {
559 av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
563 if ((err = ftp_type(s)) < 0) {
564 av_log(h, AV_LOG_ERROR, "Set content type failed\n");
573 static int ftp_connect_data_connection(URLContext *h)
576 char buf[CONTROL_BUFFER_SIZE];
577 AVDictionary *opts = NULL;
578 FTPContext *s = h->priv_data;
581 /* Enter passive mode */
582 if (ftp_passive_mode_epsv(s) < 0) {
583 /* Use PASV as fallback */
584 if ((err = ftp_passive_mode(s)) < 0)
587 /* Open data connection */
588 ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
589 if (s->rw_timeout != -1) {
590 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
591 } /* if option is not given, don't pass it and let tcp use its own default */
592 err = ffurl_open(&s->conn_data, buf, h->flags,
593 &h->interrupt_callback, &opts);
599 if ((err = ftp_restart(s, s->position)) < 0)
606 static int ftp_abort(URLContext *h)
608 static const char *command = "ABOR\r\n";
610 static const int abor_codes[] = {225, 226, 0};
611 FTPContext *s = h->priv_data;
613 /* According to RCF 959:
614 "ABOR command tells the server to abort the previous FTP
615 service command and any associated transfer of data."
617 There are FTP server implementations that don't response
618 to any commands during data transfer in passive mode (including ABOR).
620 This implementation closes data connection by force.
623 if (ftp_send_command(s, command, NULL, NULL) < 0) {
624 ftp_close_both_connections(s);
625 if ((err = ftp_connect_control_connection(h)) < 0) {
626 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
630 ftp_close_data_connection(s);
631 if (ftp_status(s, NULL, abor_codes) < 225) {
632 /* wu-ftpd also closes control connection after data connection closing */
633 ffurl_closep(&s->conn_control);
634 if ((err = ftp_connect_control_connection(h)) < 0) {
635 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
644 static int ftp_connect(URLContext *h, const char *url)
646 char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
647 const char *tok_user = NULL, *tok_pass = NULL;
648 char *end = NULL, *newpath = NULL;
650 FTPContext *s = h->priv_data;
652 s->state = DISCONNECTED;
653 s->listing_method = UNKNOWN;
658 av_url_split(proto, sizeof(proto),
659 credencials, sizeof(credencials),
660 hostname, sizeof(hostname),
661 &s->server_control_port,
665 tok_user = av_strtok(credencials, ":", &end);
666 tok_pass = av_strtok(end, ":", &end);
668 tok_user = "anonymous";
669 tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
671 s->user = av_strdup(tok_user);
672 s->password = av_strdup(tok_pass);
673 s->hostname = av_strdup(hostname);
674 if (!s->hostname || !s->user || (tok_pass && !s->password)) {
675 return AVERROR(ENOMEM);
678 if (s->server_control_port < 0 || s->server_control_port > 65535)
679 s->server_control_port = 21;
681 if ((err = ftp_connect_control_connection(h)) < 0)
684 if ((err = ftp_current_dir(s)) < 0)
687 newpath = av_append_path_component(s->path, path);
689 return AVERROR(ENOMEM);
696 static int ftp_open(URLContext *h, const char *url, int flags)
698 FTPContext *s = h->priv_data;
701 av_dlog(h, "ftp protocol open\n");
703 if ((err = ftp_connect(h, url)) < 0)
706 if (ftp_restart(s, 0) < 0) {
709 if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
711 if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
718 av_log(h, AV_LOG_ERROR, "FTP open failed\n");
723 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
725 FTPContext *s = h->priv_data;
727 int64_t new_pos, fake_pos;
729 av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
738 new_pos = s->position + pos;
743 new_pos = s->filesize + pos;
746 return AVERROR(EINVAL);
753 av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
754 return AVERROR(EINVAL);
757 fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
758 if (fake_pos != s->position) {
759 if ((err = ftp_abort(h)) < 0)
761 s->position = fake_pos;
766 static int ftp_read(URLContext *h, unsigned char *buf, int size)
768 FTPContext *s = h->priv_data;
769 int read, err, retry_done = 0;
771 av_dlog(h, "ftp protocol read %d bytes\n", size);
773 if (s->state == DISCONNECTED) {
775 if (s->position >= s->filesize)
777 if ((err = ftp_connect_data_connection(h)) < 0)
780 if (s->state == READY) {
781 if (s->position >= s->filesize)
783 if ((err = ftp_retrieve(s)) < 0)
786 if (s->conn_data && s->state == DOWNLOADING) {
787 read = ffurl_read(s->conn_data, buf, size);
790 if (s->position >= s->filesize) {
791 /* server will terminate, but keep current position to avoid madness */
792 /* save position to restart from it */
793 int64_t pos = s->position;
794 if (ftp_abort(h) < 0) {
801 if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
802 /* Server closed connection. Probably due to inactivity */
803 int64_t pos = s->position;
804 av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
805 if ((err = ftp_abort(h)) < 0)
807 if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
808 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
819 av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
823 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
826 FTPContext *s = h->priv_data;
829 av_dlog(h, "ftp protocol write %d bytes\n", size);
831 if (s->state == DISCONNECTED) {
832 if ((err = ftp_connect_data_connection(h)) < 0)
835 if (s->state == READY) {
836 if ((err = ftp_store(s)) < 0)
839 if (s->conn_data && s->state == UPLOADING) {
840 written = ffurl_write(s->conn_data, buf, size);
842 s->position += written;
843 s->filesize = FFMAX(s->filesize, s->position);
848 av_log(h, AV_LOG_ERROR, "FTP write failed\n");
852 static int ftp_close(URLContext *h)
854 FTPContext *s = h->priv_data;
856 av_dlog(h, "ftp protocol close\n");
858 ftp_close_both_connections(s);
860 av_freep(&s->password);
861 av_freep(&s->hostname);
863 av_freep(&s->features);
868 static int ftp_get_file_handle(URLContext *h)
870 FTPContext *s = h->priv_data;
872 av_dlog(h, "ftp protocol get_file_handle\n");
875 return ffurl_get_file_handle(s->conn_data);
880 static int ftp_shutdown(URLContext *h, int flags)
882 FTPContext *s = h->priv_data;
884 av_dlog(h, "ftp protocol shutdown\n");
887 return ffurl_shutdown(s->conn_data, flags);
892 static int ftp_open_dir(URLContext *h)
894 FTPContext *s = h->priv_data;
897 if ((ret = ftp_connect(h, h->filename)) < 0)
899 if ((ret = ftp_set_dir(s)) < 0)
901 if ((ret = ftp_connect_data_connection(h)) < 0)
903 if ((ret = ftp_list(s)) < 0)
905 s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
906 if (!s->dir_buffer) {
907 ret = AVERROR(ENOMEM);
910 s->dir_buffer[0] = 0;
911 if (s->conn_data && s->state == LISTING_DIR)
914 ffurl_closep(&s->conn_control);
915 ffurl_closep(&s->conn_data);
919 static int64_t ftp_parse_date(const char *date)
922 memset(&tv, 0, sizeof(struct tm));
923 av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
924 return INT64_C(1000000) * av_timegm(&tv);
927 static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
929 next->name = av_strdup(line);
933 static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
936 av_dlog(NULL, "%s\n", mlsd);
937 while(fact = av_strtok(mlsd, ";", &mlsd)) {
938 if (fact[0] == ' ') {
939 next->name = av_strdup(&fact[1]);
942 fact = av_strtok(fact, "=", &value);
943 if (!av_strcasecmp(fact, "type")) {
944 if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
946 if (!av_strcasecmp(value, "dir"))
947 next->type = AVIO_ENTRY_DIRECTORY;
948 else if (!av_strcasecmp(value, "file"))
949 next->type = AVIO_ENTRY_FILE;
950 else if (!av_strcasecmp(value, "OS.unix=slink:"))
951 next->type = AVIO_ENTRY_SYMBOLIC_LINK;
952 } else if (!av_strcasecmp(fact, "modify")) {
953 next->modification_timestamp = ftp_parse_date(value);
954 } else if (!av_strcasecmp(fact, "UNIX.mode")) {
955 next->filemode = strtoumax(value, NULL, 8);
956 } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
957 next->user_id = strtoumax(value, NULL, 10);
958 else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
959 next->group_id = strtoumax(value, NULL, 10);
960 else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
961 next->size = strtoll(value, NULL, 10);
967 * @return 0 on success, negative on error, positive on entry to discard.
969 static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
971 FTPContext *s = h->priv_data;
973 switch (s->listing_method) {
975 return ftp_parse_entry_mlsd(line, next);
977 return ftp_parse_entry_nlst(line, next);
984 static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
986 FTPContext *s = h->priv_data;
992 start = s->dir_buffer + s->dir_buffer_offset;
993 while (!(found = strstr(start, "\n"))) {
996 s->dir_buffer_size -= s->dir_buffer_offset;
997 s->dir_buffer_offset = 0;
998 if (s->dir_buffer_size)
999 memmove(s->dir_buffer, start, s->dir_buffer_size);
1000 ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
1007 s->dir_buffer_size += ret;
1008 s->dir_buffer[s->dir_buffer_size] = 0;
1009 start = s->dir_buffer;
1012 s->dir_buffer_offset += (found + 1 - start);
1014 if (found > start && found[-1] == '\r')
1017 *next = ff_alloc_dir_entry();
1019 return AVERROR(ENOMEM);
1020 (*next)->utf8 = s->utf8;
1021 ret = ftp_parse_entry(h, start, *next);
1023 avio_free_directory_entry(next);
1031 static int ftp_close_dir(URLContext *h)
1033 FTPContext *s = h->priv_data;
1034 av_freep(&s->dir_buffer);
1035 ffurl_closep(&s->conn_control);
1036 ffurl_closep(&s->conn_data);
1040 URLProtocol ff_ftp_protocol = {
1042 .url_open = ftp_open,
1043 .url_read = ftp_read,
1044 .url_write = ftp_write,
1045 .url_seek = ftp_seek,
1046 .url_close = ftp_close,
1047 .url_get_file_handle = ftp_get_file_handle,
1048 .url_shutdown = ftp_shutdown,
1049 .priv_data_size = sizeof(FTPContext),
1050 .priv_data_class = &ftp_context_class,
1051 .url_open_dir = ftp_open_dir,
1052 .url_read_dir = ftp_read_dir,
1053 .url_close_dir = ftp_close_dir,
1054 .flags = URL_PROTOCOL_FLAG_NETWORK,