|
|
|
@ -38,6 +38,12 @@ typedef enum { |
|
|
|
|
DISCONNECTED |
|
|
|
|
} FTPState; |
|
|
|
|
|
|
|
|
|
typedef enum { |
|
|
|
|
UNKNOWN_METHOD, |
|
|
|
|
NLST, |
|
|
|
|
MLSD |
|
|
|
|
} FTPListingMethod; |
|
|
|
|
|
|
|
|
|
typedef struct { |
|
|
|
|
const AVClass *class; |
|
|
|
|
URLContext *conn_control; /**< Control connection */ |
|
|
|
@ -56,6 +62,8 @@ 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 */ |
|
|
|
|
FTPListingMethod listing_method; /**< Called listing method */ |
|
|
|
|
char *features; /**< List of server's features represented as raw response */ |
|
|
|
|
char *dir_buffer; |
|
|
|
|
size_t dir_buffer_size; |
|
|
|
|
size_t dir_buffer_offset; |
|
|
|
@ -192,6 +200,8 @@ static int ftp_send_command(FTPContext *s, const char *command, |
|
|
|
|
{ |
|
|
|
|
int err; |
|
|
|
|
|
|
|
|
|
av_dlog(s, "%s", command); |
|
|
|
|
|
|
|
|
|
if (response) |
|
|
|
|
*response = NULL; |
|
|
|
|
|
|
|
|
@ -449,32 +459,65 @@ static int ftp_set_dir(FTPContext *s) |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int ftp_list(FTPContext *s) |
|
|
|
|
static int ftp_list_mlsd(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; |
|
|
|
|
s->listing_method = MLSD; |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int ftp_list_nlst(FTPContext *s) |
|
|
|
|
{ |
|
|
|
|
static const char *command = "NLST\r\n"; |
|
|
|
|
static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0}; |
|
|
|
|
|
|
|
|
|
if (ftp_send_command(s, command, nlst_codes, NULL) != 226) |
|
|
|
|
return AVERROR(ENOSYS); |
|
|
|
|
s->listing_method = NLST; |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int ftp_has_feature(FTPContext *s, const char *feature_name); |
|
|
|
|
|
|
|
|
|
static int ftp_list(FTPContext *s) |
|
|
|
|
{ |
|
|
|
|
int ret; |
|
|
|
|
s->state = LISTING_DIR; |
|
|
|
|
|
|
|
|
|
if ((ret = ftp_list_mlsd(s)) < 0) |
|
|
|
|
ret = ftp_list_nlst(s); |
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int ftp_has_feature(FTPContext *s, const char *feature_name) |
|
|
|
|
{ |
|
|
|
|
if (!s->features) |
|
|
|
|
return 0; |
|
|
|
|
|
|
|
|
|
return av_stristr(s->features, feature_name) != NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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, 0}; |
|
|
|
|
char *feat = NULL; |
|
|
|
|
|
|
|
|
|
if (ftp_send_command(s, feat_command, feat_codes, &feat) == 211) { |
|
|
|
|
if (av_stristr(feat, "UTF8")) { |
|
|
|
|
if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200) |
|
|
|
|
s->utf8 = 1; |
|
|
|
|
} |
|
|
|
|
av_freep(&s->features); |
|
|
|
|
if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) { |
|
|
|
|
av_freep(&s->features); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (ftp_has_feature(s, "UTF8")) { |
|
|
|
|
if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200) |
|
|
|
|
s->utf8 = 1; |
|
|
|
|
} |
|
|
|
|
av_freep(&feat); |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
@ -607,8 +650,10 @@ static int ftp_connect(URLContext *h, const char *url) |
|
|
|
|
FTPContext *s = h->priv_data; |
|
|
|
|
|
|
|
|
|
s->state = DISCONNECTED; |
|
|
|
|
s->listing_method = UNKNOWN; |
|
|
|
|
s->filesize = -1; |
|
|
|
|
s->position = 0; |
|
|
|
|
s->features = NULL; |
|
|
|
|
|
|
|
|
|
av_url_split(proto, sizeof(proto), |
|
|
|
|
credencials, sizeof(credencials), |
|
|
|
@ -815,6 +860,7 @@ static int ftp_close(URLContext *h) |
|
|
|
|
av_freep(&s->password); |
|
|
|
|
av_freep(&s->hostname); |
|
|
|
|
av_freep(&s->path); |
|
|
|
|
av_freep(&s->features); |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
@ -878,10 +924,13 @@ static int64_t ftp_parse_date(const char *date) |
|
|
|
|
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) |
|
|
|
|
static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next) |
|
|
|
|
{ |
|
|
|
|
next->name = av_strdup(line); |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next) |
|
|
|
|
{ |
|
|
|
|
char *fact, *value; |
|
|
|
|
av_dlog(NULL, "%s\n", mlsd); |
|
|
|
@ -914,6 +963,24 @@ static int ftp_parse_entry(char *mlsd, AVIODirEntry *next) |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return 0 on success, negative on error, positive on entry to discard. |
|
|
|
|
*/ |
|
|
|
|
static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next) |
|
|
|
|
{ |
|
|
|
|
FTPContext *s = h->priv_data; |
|
|
|
|
|
|
|
|
|
switch (s->listing_method) { |
|
|
|
|
case MLSD: |
|
|
|
|
return ftp_parse_entry_mlsd(line, next); |
|
|
|
|
case NLST: |
|
|
|
|
return ftp_parse_entry_nlst(line, next); |
|
|
|
|
case UNKNOWN_METHOD: |
|
|
|
|
default: |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int ftp_read_dir(URLContext *h, AVIODirEntry **next) |
|
|
|
|
{ |
|
|
|
|
FTPContext *s = h->priv_data; |
|
|
|
@ -951,7 +1018,7 @@ static int ftp_read_dir(URLContext *h, AVIODirEntry **next) |
|
|
|
|
if (!*next) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
(*next)->utf8 = s->utf8; |
|
|
|
|
ret = ftp_parse_entry(start, *next); |
|
|
|
|
ret = ftp_parse_entry(h, start, *next); |
|
|
|
|
if (ret) { |
|
|
|
|
avio_free_directory_entry(next); |
|
|
|
|
if (ret < 0) |
|
|
|
|