|
|
@@ -18,15 +18,12 @@ |
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
|
*/ |
|
|
*/ |
|
|
|
|
|
|
|
|
#include <stdlib.h> |
|
|
|
|
|
#include "libavutil/avstring.h" |
|
|
#include "libavutil/avstring.h" |
|
|
#include "libavutil/time.h" |
|
|
|
|
|
#include "avformat.h" |
|
|
#include "avformat.h" |
|
|
#include "internal.h" |
|
|
#include "internal.h" |
|
|
#include "network.h" |
|
|
|
|
|
#include "os_support.h" |
|
|
|
|
|
#include "url.h" |
|
|
#include "url.h" |
|
|
#include "libavutil/opt.h" |
|
|
#include "libavutil/opt.h" |
|
|
|
|
|
#include "libavutil/bprint.h" |
|
|
|
|
|
|
|
|
#define CONTROL_BUFFER_SIZE 1024 |
|
|
#define CONTROL_BUFFER_SIZE 1024 |
|
|
#define CREDENTIALS_BUFFER_SIZE 128 |
|
|
#define CREDENTIALS_BUFFER_SIZE 128 |
|
|
@@ -42,8 +39,6 @@ typedef enum { |
|
|
typedef struct { |
|
|
typedef struct { |
|
|
const AVClass *class; |
|
|
const AVClass *class; |
|
|
URLContext *conn_control; /**< Control connection */ |
|
|
URLContext *conn_control; /**< Control connection */ |
|
|
int conn_control_block_flag; /**< Controls block/unblock mode of data connection */ |
|
|
|
|
|
AVIOInterruptCB conn_control_interrupt_cb; /**< Controls block/unblock mode of data connection */ |
|
|
|
|
|
URLContext *conn_data; /**< Data connection, NULL when not connected */ |
|
|
URLContext *conn_data; /**< Data connection, NULL when not connected */ |
|
|
uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */ |
|
|
uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */ |
|
|
uint8_t *control_buf_ptr, *control_buf_end; |
|
|
uint8_t *control_buf_ptr, *control_buf_end; |
|
|
@@ -77,18 +72,10 @@ static const AVClass ftp_context_class = { |
|
|
.version = LIBAVUTIL_VERSION_INT, |
|
|
.version = LIBAVUTIL_VERSION_INT, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
static int ftp_conn_control_block_control(void *data) |
|
|
|
|
|
{ |
|
|
|
|
|
FTPContext *s = data; |
|
|
|
|
|
return s->conn_control_block_flag; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int ftp_getc(FTPContext *s) |
|
|
static int ftp_getc(FTPContext *s) |
|
|
{ |
|
|
{ |
|
|
int len; |
|
|
int len; |
|
|
if (s->control_buf_ptr >= s->control_buf_end) { |
|
|
if (s->control_buf_ptr >= s->control_buf_end) { |
|
|
if (s->conn_control_block_flag) |
|
|
|
|
|
return AVERROR_EXIT; |
|
|
|
|
|
len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE); |
|
|
len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE); |
|
|
if (len < 0) { |
|
|
if (len < 0) { |
|
|
return len; |
|
|
return len; |
|
|
@@ -106,12 +93,10 @@ static int ftp_get_line(FTPContext *s, char *line, int line_size) |
|
|
{ |
|
|
{ |
|
|
int ch; |
|
|
int ch; |
|
|
char *q = line; |
|
|
char *q = line; |
|
|
int ori_block_flag = s->conn_control_block_flag; |
|
|
|
|
|
|
|
|
|
|
|
for (;;) { |
|
|
for (;;) { |
|
|
ch = ftp_getc(s); |
|
|
ch = ftp_getc(s); |
|
|
if (ch < 0) { |
|
|
if (ch < 0) { |
|
|
s->conn_control_block_flag = ori_block_flag; |
|
|
|
|
|
return ch; |
|
|
return ch; |
|
|
} |
|
|
} |
|
|
if (ch == '\n') { |
|
|
if (ch == '\n') { |
|
|
@@ -119,86 +104,61 @@ static int ftp_get_line(FTPContext *s, char *line, int line_size) |
|
|
if (q > line && q[-1] == '\r') |
|
|
if (q > line && q[-1] == '\r') |
|
|
q--; |
|
|
q--; |
|
|
*q = '\0'; |
|
|
*q = '\0'; |
|
|
|
|
|
|
|
|
s->conn_control_block_flag = ori_block_flag; |
|
|
|
|
|
return 0; |
|
|
return 0; |
|
|
} else { |
|
|
} else { |
|
|
s->conn_control_block_flag = 0; /* line need to be finished */ |
|
|
|
|
|
if ((q - line) < line_size - 1) |
|
|
if ((q - line) < line_size - 1) |
|
|
*q++ = ch; |
|
|
*q++ = ch; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static int ftp_flush_control_input(FTPContext *s) |
|
|
|
|
|
{ |
|
|
|
|
|
char buf[CONTROL_BUFFER_SIZE]; |
|
|
|
|
|
int err, ori_block_flag = s->conn_control_block_flag; |
|
|
|
|
|
|
|
|
|
|
|
s->conn_control_block_flag = 1; |
|
|
|
|
|
do { |
|
|
|
|
|
err = ftp_get_line(s, buf, sizeof(buf)); |
|
|
|
|
|
} while (!err); |
|
|
|
|
|
|
|
|
|
|
|
s->conn_control_block_flag = ori_block_flag; |
|
|
|
|
|
|
|
|
|
|
|
if (err < 0 && err != AVERROR_EXIT) |
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
/* |
|
|
* This routine returns ftp server response code. |
|
|
* This routine returns ftp server response code. |
|
|
* Server may send more than one response for a certain command, following priorities are used: |
|
|
|
|
|
* - When pref_codes are set then pref_code is return if occurred. (expected result) |
|
|
|
|
|
* - 0 is returned when no pref_codes or not occurred |
|
|
|
|
|
|
|
|
* Server may send more than one response for a certain command. |
|
|
|
|
|
* First expected code is returned. |
|
|
*/ |
|
|
*/ |
|
|
static int ftp_status(FTPContext *s, char **line, const int response_codes[]) |
|
|
static int ftp_status(FTPContext *s, char **line, const int response_codes[]) |
|
|
{ |
|
|
{ |
|
|
int err, i, result = 0, pref_code_found = 0, wait_count = 100; |
|
|
|
|
|
|
|
|
int err, i, dash = 0, result = 0, code_found = 0; |
|
|
char buf[CONTROL_BUFFER_SIZE]; |
|
|
char buf[CONTROL_BUFFER_SIZE]; |
|
|
|
|
|
AVBPrint line_buffer; |
|
|
|
|
|
|
|
|
/* Set blocking mode */ |
|
|
|
|
|
s->conn_control_block_flag = 0; |
|
|
|
|
|
for (;;) { |
|
|
|
|
|
|
|
|
if (line) |
|
|
|
|
|
av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC); |
|
|
|
|
|
|
|
|
|
|
|
while (!code_found || dash) { |
|
|
if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) { |
|
|
if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) { |
|
|
if (err == AVERROR_EXIT) { |
|
|
|
|
|
if (!pref_code_found && wait_count--) { |
|
|
|
|
|
av_usleep(10000); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
|
av_bprint_finalize(&line_buffer, NULL); |
|
|
|
|
|
return err; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
av_log(s, AV_LOG_DEBUG, "%s\n", buf); |
|
|
av_log(s, AV_LOG_DEBUG, "%s\n", buf); |
|
|
|
|
|
|
|
|
if (!pref_code_found) { |
|
|
|
|
|
if (strlen(buf) < 3) |
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
err = 0; |
|
|
|
|
|
for (i = 0; i < 3; ++i) { |
|
|
|
|
|
if (buf[i] < '0' || buf[i] > '9') |
|
|
|
|
|
continue; |
|
|
|
|
|
err *= 10; |
|
|
|
|
|
err += buf[i] - '0'; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (strlen(buf) < 4) |
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
for (i = 0; response_codes[i]; ++i) { |
|
|
|
|
|
if (err == response_codes[i]) { |
|
|
|
|
|
/* first code received. Now get all lines in non blocking mode */ |
|
|
|
|
|
s->conn_control_block_flag = 1; |
|
|
|
|
|
pref_code_found = 1; |
|
|
|
|
|
result = err; |
|
|
|
|
|
if (line) |
|
|
|
|
|
*line = av_strdup(buf); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
err = 0; |
|
|
|
|
|
for (i = 0; i < 3; ++i) { |
|
|
|
|
|
if (buf[i] < '0' || buf[i] > '9') |
|
|
|
|
|
continue; |
|
|
|
|
|
err *= 10; |
|
|
|
|
|
err += buf[i] - '0'; |
|
|
|
|
|
} |
|
|
|
|
|
dash = !!(buf[3] == '-'); |
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; response_codes[i]; ++i) { |
|
|
|
|
|
if (err == response_codes[i]) { |
|
|
|
|
|
if (line) |
|
|
|
|
|
av_bprintf(&line_buffer, "%s", buf); |
|
|
|
|
|
code_found = 1; |
|
|
|
|
|
result = err; |
|
|
|
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (line) |
|
|
|
|
|
av_bprint_finalize(&line_buffer, line); |
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@@ -207,12 +167,6 @@ static int ftp_send_command(FTPContext *s, const char *command, |
|
|
{ |
|
|
{ |
|
|
int err; |
|
|
int err; |
|
|
|
|
|
|
|
|
/* Flush control connection input to get rid of non relevant responses if any */ |
|
|
|
|
|
if ((err = ftp_flush_control_input(s)) < 0) |
|
|
|
|
|
return err; |
|
|
|
|
|
|
|
|
|
|
|
/* send command in blocking mode */ |
|
|
|
|
|
s->conn_control_block_flag = 0; |
|
|
|
|
|
if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0) |
|
|
if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0) |
|
|
return err; |
|
|
return err; |
|
|
if (!err) |
|
|
if (!err) |
|
|
@@ -428,14 +382,12 @@ static int ftp_restart(FTPContext *s, int64_t pos) |
|
|
|
|
|
|
|
|
static int ftp_connect_control_connection(URLContext *h) |
|
|
static int ftp_connect_control_connection(URLContext *h) |
|
|
{ |
|
|
{ |
|
|
char buf[CONTROL_BUFFER_SIZE], opts_format[20]; |
|
|
|
|
|
|
|
|
char buf[CONTROL_BUFFER_SIZE], opts_format[20], *response = NULL; |
|
|
int err; |
|
|
int err; |
|
|
AVDictionary *opts = NULL; |
|
|
AVDictionary *opts = NULL; |
|
|
FTPContext *s = h->priv_data; |
|
|
FTPContext *s = h->priv_data; |
|
|
const int connect_codes[] = {220, 0}; |
|
|
const int connect_codes[] = {220, 0}; |
|
|
|
|
|
|
|
|
s->conn_control_block_flag = 0; |
|
|
|
|
|
|
|
|
|
|
|
if (!s->conn_control) { |
|
|
if (!s->conn_control) { |
|
|
ff_url_join(buf, sizeof(buf), "tcp", NULL, |
|
|
ff_url_join(buf, sizeof(buf), "tcp", NULL, |
|
|
s->hostname, s->server_control_port, NULL); |
|
|
s->hostname, s->server_control_port, NULL); |
|
|
@@ -444,19 +396,24 @@ static int ftp_connect_control_connection(URLContext *h) |
|
|
av_dict_set(&opts, "timeout", opts_format, 0); |
|
|
av_dict_set(&opts, "timeout", opts_format, 0); |
|
|
} /* if option is not given, don't pass it and let tcp use its own default */ |
|
|
} /* if option is not given, don't pass it and let tcp use its own default */ |
|
|
err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE, |
|
|
err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE, |
|
|
&s->conn_control_interrupt_cb, &opts); |
|
|
|
|
|
|
|
|
&h->interrupt_callback, &opts); |
|
|
av_dict_free(&opts); |
|
|
av_dict_free(&opts); |
|
|
if (err < 0) { |
|
|
if (err < 0) { |
|
|
av_log(h, AV_LOG_ERROR, "Cannot open control connection\n"); |
|
|
av_log(h, AV_LOG_ERROR, "Cannot open control connection\n"); |
|
|
return err; |
|
|
return err; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* consume all messages from server */ |
|
|
|
|
|
if (ftp_status(s, NULL, connect_codes) != 220) { |
|
|
|
|
|
|
|
|
/* check if server is ready */ |
|
|
|
|
|
if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) { |
|
|
av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n"); |
|
|
av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n"); |
|
|
return AVERROR(EACCES); |
|
|
return AVERROR(EACCES); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) { |
|
|
|
|
|
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."); |
|
|
|
|
|
} |
|
|
|
|
|
av_free(response); |
|
|
|
|
|
|
|
|
if ((err = ftp_auth(s)) < 0) { |
|
|
if ((err = ftp_auth(s)) < 0) { |
|
|
av_log(h, AV_LOG_ERROR, "FTP authentication failed\n"); |
|
|
av_log(h, AV_LOG_ERROR, "FTP authentication failed\n"); |
|
|
return err; |
|
|
return err; |
|
|
@@ -489,7 +446,7 @@ static int ftp_connect_data_connection(URLContext *h) |
|
|
snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout); |
|
|
snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout); |
|
|
av_dict_set(&opts, "timeout", opts_format, 0); |
|
|
av_dict_set(&opts, "timeout", opts_format, 0); |
|
|
} /* if option is not given, don't pass it and let tcp use its own default */ |
|
|
} /* if option is not given, don't pass it and let tcp use its own default */ |
|
|
err = ffurl_open(&s->conn_data, buf, AVIO_FLAG_READ_WRITE, |
|
|
|
|
|
|
|
|
err = ffurl_open(&s->conn_data, buf, h->flags, |
|
|
&h->interrupt_callback, &opts); |
|
|
&h->interrupt_callback, &opts); |
|
|
av_dict_free(&opts); |
|
|
av_dict_free(&opts); |
|
|
if (err < 0) |
|
|
if (err < 0) |
|
|
@@ -553,8 +510,6 @@ static int ftp_open(URLContext *h, const char *url, int flags) |
|
|
s->state = DISCONNECTED; |
|
|
s->state = DISCONNECTED; |
|
|
s->filesize = -1; |
|
|
s->filesize = -1; |
|
|
s->position = 0; |
|
|
s->position = 0; |
|
|
s->conn_control_interrupt_cb.opaque = s; |
|
|
|
|
|
s->conn_control_interrupt_cb.callback = ftp_conn_control_block_control; |
|
|
|
|
|
|
|
|
|
|
|
av_url_split(proto, sizeof(proto), |
|
|
av_url_split(proto, sizeof(proto), |
|
|
s->credencials, sizeof(s->credencials), |
|
|
s->credencials, sizeof(s->credencials), |
|
|
@@ -620,6 +575,7 @@ static int64_t ftp_seek(URLContext *h, int64_t pos, int whence) |
|
|
if (h->is_streamed) |
|
|
if (h->is_streamed) |
|
|
return AVERROR(EIO); |
|
|
return AVERROR(EIO); |
|
|
|
|
|
|
|
|
|
|
|
/* XXX: Simulate behaviour of lseek in file protocol, which could be treated as a reference */ |
|
|
new_pos = FFMAX(0, new_pos); |
|
|
new_pos = FFMAX(0, new_pos); |
|
|
fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos; |
|
|
fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos; |
|
|
|
|
|
|
|
|
@@ -639,6 +595,7 @@ static int ftp_read(URLContext *h, unsigned char *buf, int size) |
|
|
av_dlog(h, "ftp protocol read %d bytes\n", size); |
|
|
av_dlog(h, "ftp protocol read %d bytes\n", size); |
|
|
retry: |
|
|
retry: |
|
|
if (s->state == DISCONNECTED) { |
|
|
if (s->state == DISCONNECTED) { |
|
|
|
|
|
/* optimization */ |
|
|
if (s->position >= s->filesize) |
|
|
if (s->position >= s->filesize) |
|
|
return 0; |
|
|
return 0; |
|
|
if ((err = ftp_connect_data_connection(h)) < 0) |
|
|
if ((err = ftp_connect_data_connection(h)) < 0) |
|
|
@@ -656,6 +613,7 @@ static int ftp_read(URLContext *h, unsigned char *buf, int size) |
|
|
s->position += read; |
|
|
s->position += read; |
|
|
if (s->position >= s->filesize) { |
|
|
if (s->position >= s->filesize) { |
|
|
/* server will terminate, but keep current position to avoid madness */ |
|
|
/* server will terminate, but keep current position to avoid madness */ |
|
|
|
|
|
/* save position to restart from it */ |
|
|
int64_t pos = s->position; |
|
|
int64_t pos = s->position; |
|
|
if (ftp_abort(h) < 0) { |
|
|
if (ftp_abort(h) < 0) { |
|
|
s->position = pos; |
|
|
s->position = pos; |
|
|
|