]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/ftp.c
avfilter: Constify all AVFilters
[ffmpeg] / libavformat / ftp.c
index 9aa7a45629ff6e67e6d65672bc7a6394bf2034a9..caeea429209657c44e70b13ad868699a19fbe8a0 100644 (file)
  * 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"
 
@@ -36,7 +39,8 @@ typedef enum {
     DOWNLOADING,
     UPLOADING,
     LISTING_DIR,
-    DISCONNECTED
+    DISCONNECTED,
+    ENDOFFILE,
 } FTPState;
 
 typedef enum {
@@ -69,6 +73,8 @@ typedef struct {
     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)
@@ -78,6 +84,8 @@ static const AVOption options[] = {
     {"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}
 };
 
@@ -224,7 +232,6 @@ static int ftp_send_command(FTPContext *s, const char *command,
 static void ftp_close_data_connection(FTPContext *s)
 {
     ffurl_closep(&s->conn_data);
-    s->position = 0;
     s->state = DISCONNECTED;
 }
 
@@ -241,10 +248,14 @@ static int ftp_auth(FTPContext *s)
     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
@@ -322,15 +333,15 @@ static int ftp_passive_mode(FTPContext *s)
     *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);
@@ -389,7 +400,7 @@ static int ftp_file_size(FTPContext *s)
     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;
@@ -489,8 +500,6 @@ static int ftp_list_nlst(FTPContext *s)
     return 0;
 }
 
-static int ftp_has_feature(FTPContext *s, const char *feature_name);
-
 static int ftp_list(FTPContext *s)
 {
     int ret;
@@ -515,7 +524,7 @@ 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};
+    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) {
@@ -523,7 +532,8 @@ static int ftp_features(FTPContext *s)
     }
 
     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;
     }
 
@@ -653,9 +663,9 @@ static int ftp_abort(URLContext *h)
 
 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;
 
@@ -666,20 +676,34 @@ static int ftp_connect(URLContext *h, const char *url)
     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);
@@ -716,8 +740,7 @@ static int ftp_open(URLContext *h, const char *url, int flags)
     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;
     }
@@ -734,7 +757,7 @@ static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
 {
     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);
 
@@ -764,11 +787,10 @@ static int64_t ftp_seek(URLContext *h, int64_t pos, int 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;
 }
@@ -780,16 +802,13 @@ static int ftp_read(URLContext *h, unsigned char *buf, int size)
 
     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 0;
         if ((err = ftp_connect_data_connection(h)) < 0)
             return err;
     }
     if (s->state == READY) {
-        if (s->position >= s->filesize)
-            return 0;
         if ((err = ftp_retrieve(s)) < 0)
             return err;
     }
@@ -797,27 +816,28 @@ static int ftp_read(URLContext *h, unsigned char *buf, int size)
         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 == 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 && s->position < s->filesize && !h->is_streamed) {
+        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;
@@ -943,8 +963,10 @@ static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
 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;