* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <string.h>
+
#include "libavutil/avstring.h"
#include "libavutil/internal.h"
#include "libavutil/parseutils.h"
#include "avformat.h"
#include "internal.h"
#include "url.h"
+#include "urldecode.h"
#include "libavutil/opt.h"
#include "libavutil/bprint.h"
DOWNLOADING,
UPLOADING,
LISTING_DIR,
- DISCONNECTED
+ DISCONNECTED,
+ ENDOFFILE,
} FTPState;
typedef enum {
size_t dir_buffer_size;
size_t dir_buffer_offset;
int utf8;
+ const char *option_user; /**< User to be used if none given in the URL */
+ const char *option_password; /**< Password to be used if none given in the URL */
} FTPContext;
#define OFFSET(x) offsetof(FTPContext, x)
{"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
{"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
{"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 },
+ {"ftp-user", "user for FTP login. Overridden by whatever is in the URL.", OFFSET(option_user), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
+ {"ftp-password", "password for FTP login. Overridden by whatever is in the URL.", OFFSET(option_password), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E },
{NULL}
};
static void ftp_close_data_connection(FTPContext *s)
{
ffurl_closep(&s->conn_data);
- s->position = 0;
s->state = DISCONNECTED;
}
static const int user_codes[] = {331, 230, 0};
static const int pass_codes[] = {230, 0};
+ if (strpbrk(s->user, "\r\n"))
+ return AVERROR(EINVAL);
snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
err = ftp_send_command(s, buf, user_codes, NULL);
if (err == 331) {
if (s->password) {
+ if (strpbrk(s->password, "\r\n"))
+ return AVERROR(EINVAL);
snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
err = ftp_send_command(s, buf, pass_codes, NULL);
} else
*end = '\0';
/* skip ip */
if (!av_strtok(start, ",", &end)) goto fail;
- if (!av_strtok(end, ",", &end)) goto fail;
- if (!av_strtok(end, ",", &end)) goto fail;
- if (!av_strtok(end, ",", &end)) goto fail;
+ if (!av_strtok(NULL, ",", &end)) goto fail;
+ if (!av_strtok(NULL, ",", &end)) goto fail;
+ if (!av_strtok(NULL, ",", &end)) goto fail;
/* parse port number */
- start = av_strtok(end, ",", &end);
+ start = av_strtok(NULL, ",", &end);
if (!start) goto fail;
s->server_data_port = atoi(start) * 256;
- start = av_strtok(end, ",", &end);
+ start = av_strtok(NULL, ",", &end);
if (!start) goto fail;
s->server_data_port += atoi(start);
ff_dlog(s, "Server data port: %d\n", s->server_data_port);
static const int size_codes[] = {213, 0};
snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
- if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
+ if (ftp_send_command(s, command, size_codes, &res) == 213 && res && strlen(res) > 4) {
s->filesize = strtoll(&res[4], NULL, 10);
} else {
s->filesize = -1;
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};
+ static const int opts_codes[] = {200, 202, 451, 0};
av_freep(&s->features);
if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
}
if (ftp_has_feature(s, "UTF8")) {
- if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200)
+ int ret = ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
+ if (ret == 200 || ret == 202)
s->utf8 = 1;
}
static int ftp_connect(URLContext *h, const char *url)
{
- char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
+ char proto[10], path[MAX_URL_SIZE], credentials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
const char *tok_user = NULL, *tok_pass = NULL;
- char *end = NULL, *newpath = NULL;
+ char *newpath = NULL;
int err;
FTPContext *s = h->priv_data;
s->features = NULL;
av_url_split(proto, sizeof(proto),
- credencials, sizeof(credencials),
+ credentials, sizeof(credentials),
hostname, sizeof(hostname),
&s->server_control_port,
path, sizeof(path),
url);
- tok_user = av_strtok(credencials, ":", &end);
- tok_pass = av_strtok(end, ":", &end);
- if (!tok_user) {
- tok_user = "anonymous";
- tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
+ if (!*credentials) {
+ if (!s->option_user) {
+ tok_user = "anonymous";
+ tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
+ } else {
+ tok_user = s->option_user;
+ tok_pass = s->option_password;
+ }
+ s->user = av_strdup(tok_user);
+ s->password = av_strdup(tok_pass);
+ } else {
+ char *pass = strchr(credentials, ':');
+ if (pass) {
+ *pass++ = '\0';
+ tok_pass = pass;
+ s->password = ff_urldecode(pass, 0);
+ } else {
+ tok_pass = s->option_password;
+ s->password = av_strdup(tok_pass);
+ }
+ s->user = ff_urldecode(credentials, 0);
}
- s->user = av_strdup(tok_user);
- s->password = av_strdup(tok_pass);
s->hostname = av_strdup(hostname);
if (!s->hostname || !s->user || (tok_pass && !s->password)) {
return AVERROR(ENOMEM);
if (ftp_restart(s, 0) < 0) {
h->is_streamed = 1;
} else {
- if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
- h->is_streamed = 1;
+ ftp_file_size(s);
if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
h->is_streamed = 1;
}
{
FTPContext *s = h->priv_data;
int err;
- int64_t new_pos, fake_pos;
+ int64_t new_pos;
ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
return AVERROR(EINVAL);
}
- fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
- if (fake_pos != s->position) {
+ if (new_pos != s->position) {
if ((err = ftp_abort(h)) < 0)
return err;
- s->position = fake_pos;
+ s->position = new_pos;
}
return new_pos;
}
ff_dlog(h, "ftp protocol read %d bytes\n", size);
retry:
+ if (s->state == ENDOFFILE)
+ return AVERROR_EOF;
if (s->state == DISCONNECTED) {
- /* optimization */
- if (s->position >= s->filesize)
- return AVERROR_EOF;
if ((err = ftp_connect_data_connection(h)) < 0)
return err;
}
if (s->state == READY) {
- if (s->position >= s->filesize)
- return AVERROR_EOF;
if ((err = ftp_retrieve(s)) < 0)
return err;
}
read = ffurl_read(s->conn_data, buf, size);
if (read >= 0) {
s->position += read;
- if (s->position >= s->filesize) {
- /* server will terminate, but keep current position to avoid madness */
- /* save position to restart from it */
- int64_t pos = s->position;
- if (ftp_abort(h) < 0) {
- s->position = pos;
- return AVERROR(EIO);
- }
- s->position = pos;
- }
+ s->filesize = FFMAX(s->filesize, s->position);
}
- if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
+ if (read == AVERROR_EOF) {
+ static const int retr_codes[] = {226, 250, 425, 426, 451, 0};
+ char *response = NULL;
+ err = ftp_status(s, &response, retr_codes);
+ if (err == 226) {
+ ftp_close_data_connection(s);
+ av_freep(&response);
+ s->state = ENDOFFILE;
+ return AVERROR_EOF;
+ }
+ /* 250 is not allowed, any other status means some kind of error */
+ av_log(h, AV_LOG_ERROR, "FTP transfer failed: %s\n", response ? response : (err < 0 ? av_err2str(err) : "?"));
+ av_freep(&response);
+ read = AVERROR(EIO);
+ }
+ if (read <= 0 && !h->is_streamed) {
/* Server closed connection. Probably due to inactivity */
- int64_t pos = s->position;
av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
if ((err = ftp_abort(h)) < 0)
return err;
- if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
- av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
- return err;
- }
if (!retry_done) {
retry_done = 1;
goto retry;
static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
{
char *fact, *value;
+ char *saveptr = NULL, *p = mlsd;
ff_dlog(NULL, "%s\n", mlsd);
- while(fact = av_strtok(mlsd, ";", &mlsd)) {
+ while(fact = av_strtok(p, ";", &saveptr)) {
+ p = NULL;
if (fact[0] == ' ') {
next->name = av_strdup(&fact[1]);
continue;