From bc9305316142a4af22593698c728db2d5db59ab8 Mon Sep 17 00:00:00 2001 From: Lukasz Marek Date: Sun, 9 Aug 2015 02:38:10 +0200 Subject: lavf/ftp: implement directory listing callbacks Signed-off-by: Lukasz Marek Signed-off-by: Michael Niedermayer --- libavformat/ftp.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 182 insertions(+), 11 deletions(-) (limited to 'libavformat/ftp.c') diff --git a/libavformat/ftp.c b/libavformat/ftp.c index 27a172e8ad..c92ed11d86 100644 --- a/libavformat/ftp.c +++ b/libavformat/ftp.c @@ -19,6 +19,7 @@ */ #include "libavutil/avstring.h" +#include "libavutil/parseutils.h" #include "avformat.h" #include "internal.h" #include "url.h" @@ -26,12 +27,14 @@ #include "libavutil/bprint.h" #define CONTROL_BUFFER_SIZE 1024 +#define DIR_BUFFER_SIZE 4096 typedef enum { UNKNOWN, READY, DOWNLOADING, UPLOADING, + LISTING_DIR, DISCONNECTED } FTPState; @@ -53,6 +56,10 @@ typedef struct { const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */ int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */ FTPState state; /**< State of data connection */ + char *dir_buffer; + size_t dir_buffer_size; + size_t dir_buffer_offset; + int utf8; } FTPContext; #define OFFSET(x) offsetof(FTPContext, x) @@ -434,17 +441,41 @@ static int ftp_restart(FTPContext *s, int64_t pos) return 0; } +static int ftp_set_dir(FTPContext *s) +{ + static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */ + char command[MAX_URL_SIZE]; + + snprintf(command, sizeof(command), "CWD %s\r\n", s->path); + if (ftp_send_command(s, command, cwd_codes, NULL) != 250) + return AVERROR(EIO); + return 0; +} + +static int ftp_list(FTPContext *s) +{ + static const char *command = "MLSD\r\n"; + static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */ + + if (ftp_send_command(s, command, mlsd_codes, NULL) != 150) + return AVERROR(ENOSYS); + s->state = LISTING_DIR; + return 0; +} + static int ftp_features(FTPContext *s) { static const char *feat_command = "FEAT\r\n"; static const char *enable_utf8_command = "OPTS UTF8 ON\r\n"; static const int feat_codes[] = {211, 0}; - static const int opts_codes[] = {200, 451}; + static const int opts_codes[] = {200, 451, 0}; char *feat = NULL; if (ftp_send_command(s, feat_command, feat_codes, &feat) == 211) { - if (av_stristr(feat, "UTF8")) - ftp_send_command(s, enable_utf8_command, opts_codes, NULL); + if (av_stristr(feat, "UTF8")) { + if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200) + s->utf8 = 1; + } } av_freep(&feat); @@ -570,7 +601,7 @@ static int ftp_abort(URLContext *h) return 0; } -static int ftp_open(URLContext *h, const char *url, int flags) +static int ftp_connect(URLContext *h, const char *url) { char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE]; const char *tok_user = NULL, *tok_pass = NULL; @@ -579,8 +610,6 @@ static int ftp_open(URLContext *h, const char *url, int flags) size_t pathlen; FTPContext *s = h->priv_data; - av_dlog(h, "ftp protocol open\n"); - s->state = DISCONNECTED; s->filesize = -1; s->position = 0; @@ -602,23 +631,35 @@ static int ftp_open(URLContext *h, const char *url, int flags) s->password = av_strdup(tok_pass); s->hostname = av_strdup(hostname); if (!s->hostname || !s->user || (tok_pass && !s->password)) { - err = AVERROR(ENOMEM); - goto fail; + return AVERROR(ENOMEM); } if (s->server_control_port < 0 || s->server_control_port > 65535) s->server_control_port = 21; if ((err = ftp_connect_control_connection(h)) < 0) - goto fail; + return err; if ((err = ftp_current_dir(s)) < 0) - goto fail; + return err; pathlen = strlen(s->path) + strlen(path) + 1; if ((err = av_reallocp(&s->path, pathlen)) < 0) - goto fail; + return err; av_strlcat(s->path + strlen(s->path), path, pathlen); + return 0; +} + +static int ftp_open(URLContext *h, const char *url, int flags) +{ + FTPContext *s = h->priv_data; + int err; + + av_dlog(h, "ftp protocol open\n"); + + if ((err = ftp_connect(h, url)) < 0) + goto fail; + if (ftp_restart(s, 0) < 0) { h->is_streamed = 1; } else { @@ -804,6 +845,133 @@ static int ftp_shutdown(URLContext *h, int flags) return AVERROR(EIO); } +static int ftp_open_dir(URLContext *h) +{ + FTPContext *s = h->priv_data; + int ret; + + if ((ret = ftp_connect(h, h->filename)) < 0) + goto fail; + if ((ret = ftp_set_dir(s)) < 0) + goto fail; + if ((ret = ftp_connect_data_connection(h)) < 0) + goto fail; + if ((ret = ftp_list(s)) < 0) + goto fail; + s->dir_buffer = av_malloc(DIR_BUFFER_SIZE); + if (!s->dir_buffer) { + ret = AVERROR(ENOMEM); + goto fail; + } + s->dir_buffer[0] = 0; + if (s->conn_data && s->state == LISTING_DIR) + return 0; + fail: + ffurl_closep(&s->conn_control); + ffurl_closep(&s->conn_data); + return ret; +} + +static int64_t ftp_parse_date(const char *date) +{ + struct tm tv; + memset(&tv, 0, sizeof(struct tm)); + av_small_strptime(date, "%Y%m%d%H%M%S", &tv); + return INT64_C(1000000) * av_timegm(&tv); +} + +/** + * @return 0 on success, negative on error, positive on entry to discard. + */ +static int ftp_parse_entry(char *mlsd, AVIODirEntry *next) +{ + char *fact, *value; + av_dlog(NULL, "%s\n", mlsd); + while(fact = av_strtok(mlsd, ";", &mlsd)) { + if (fact[0] == ' ') { + next->name = av_strdup(&fact[1]); + continue; + } + fact = av_strtok(fact, "=", &value); + if (!av_strcasecmp(fact, "type")) { + if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir")) + return 1; + if (!av_strcasecmp(value, "dir")) + next->type = AVIO_ENTRY_DIRECTORY; + else if (!av_strcasecmp(value, "file")) + next->type = AVIO_ENTRY_FILE; + else if (!av_strcasecmp(value, "OS.unix=slink:")) + next->type = AVIO_ENTRY_SYMBOLIC_LINK; + } else if (!av_strcasecmp(fact, "modify")) { + next->modification_timestamp = ftp_parse_date(value); + } else if (!av_strcasecmp(fact, "UNIX.mode")) { + next->filemode = strtoumax(value, NULL, 8); + } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner")) + next->user_id = strtoumax(value, NULL, 10); + else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group")) + next->group_id = strtoumax(value, NULL, 10); + else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd")) + next->size = strtoll(value, NULL, 10); + } + return 0; +} + +static int ftp_read_dir(URLContext *h, AVIODirEntry **next) +{ + FTPContext *s = h->priv_data; + char *start, *found; + int ret, retried; + + do { + retried = 0; + start = s->dir_buffer + s->dir_buffer_offset; + while (!(found = strstr(start, "\n"))) { + if (retried) + return AVERROR(EIO); + s->dir_buffer_size -= s->dir_buffer_offset; + s->dir_buffer_offset = 0; + if (s->dir_buffer_size) + memmove(s->dir_buffer, start, s->dir_buffer_size); + ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1)); + if (ret < 0) + return ret; + if (!ret) { + *next = NULL; + return 0; + } + s->dir_buffer_size += ret; + s->dir_buffer[s->dir_buffer_size] = 0; + start = s->dir_buffer; + retried = 1; + } + s->dir_buffer_offset += (found + 1 - start); + found[0] = 0; + if (found > start && found[-1] == '\r') + found[-1] = 0; + + *next = ff_alloc_dir_entry(); + if (!*next) + return AVERROR(ENOMEM); + (*next)->utf8 = s->utf8; + ret = ftp_parse_entry(start, *next); + if (ret) { + avio_free_directory_entry(next); + if (ret < 0) + return ret; + } + } while (ret > 0); + return 0; +} + +static int ftp_close_dir(URLContext *h) +{ + FTPContext *s = h->priv_data; + av_free(s->dir_buffer); + ffurl_closep(&s->conn_control); + ffurl_closep(&s->conn_data); + return 0; +} + URLProtocol ff_ftp_protocol = { .name = "ftp", .url_open = ftp_open, @@ -815,5 +983,8 @@ URLProtocol ff_ftp_protocol = { .url_shutdown = ftp_shutdown, .priv_data_size = sizeof(FTPContext), .priv_data_class = &ftp_context_class, + .url_open_dir = ftp_open_dir, + .url_read_dir = ftp_read_dir, + .url_close_dir = ftp_close_dir, .flags = URL_PROTOCOL_FLAG_NETWORK, }; -- cgit v1.2.3