| 1 | /* |
| 2 | * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com> |
| 3 | * |
| 4 | * This file is part of FFmpeg. |
| 5 | * |
| 6 | * FFmpeg is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU Lesser General Public |
| 8 | * License as published by the Free Software Foundation; either |
| 9 | * version 2.1 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * FFmpeg is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | * Lesser General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU Lesser General Public |
| 17 | * License along with FFmpeg; if not, write to the Free Software |
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 19 | */ |
| 20 | |
| 21 | #include "libavutil/avstring.h" |
| 22 | #include "avformat.h" |
| 23 | #include "internal.h" |
| 24 | #include "url.h" |
| 25 | #include "libavutil/opt.h" |
| 26 | #include "libavutil/bprint.h" |
| 27 | |
| 28 | #define CONTROL_BUFFER_SIZE 1024 |
| 29 | #define CREDENTIALS_BUFFER_SIZE 128 |
| 30 | |
| 31 | typedef enum { |
| 32 | UNKNOWN, |
| 33 | READY, |
| 34 | DOWNLOADING, |
| 35 | UPLOADING, |
| 36 | DISCONNECTED |
| 37 | } FTPState; |
| 38 | |
| 39 | typedef struct { |
| 40 | const AVClass *class; |
| 41 | URLContext *conn_control; /**< Control connection */ |
| 42 | URLContext *conn_data; /**< Data connection, NULL when not connected */ |
| 43 | uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */ |
| 44 | uint8_t *control_buf_ptr, *control_buf_end; |
| 45 | int server_data_port; /**< Data connection port opened by server, -1 on error. */ |
| 46 | int server_control_port; /**< Control connection port, default is 21 */ |
| 47 | char hostname[512]; /**< Server address. */ |
| 48 | char credencials[CREDENTIALS_BUFFER_SIZE]; /**< Authentication data */ |
| 49 | char path[MAX_URL_SIZE]; /**< Path to resource on server. */ |
| 50 | int64_t filesize; /**< Size of file on server, -1 on error. */ |
| 51 | int64_t position; /**< Current position, calculated. */ |
| 52 | int rw_timeout; /**< Network timeout. */ |
| 53 | const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */ |
| 54 | int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */ |
| 55 | FTPState state; /**< State of data connection */ |
| 56 | } FTPContext; |
| 57 | |
| 58 | #define OFFSET(x) offsetof(FTPContext, x) |
| 59 | #define D AV_OPT_FLAG_DECODING_PARAM |
| 60 | #define E AV_OPT_FLAG_ENCODING_PARAM |
| 61 | static const AVOption options[] = { |
| 62 | {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E }, |
| 63 | {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, |
| 64 | {"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 }, |
| 65 | {NULL} |
| 66 | }; |
| 67 | |
| 68 | static const AVClass ftp_context_class = { |
| 69 | .class_name = "ftp", |
| 70 | .item_name = av_default_item_name, |
| 71 | .option = options, |
| 72 | .version = LIBAVUTIL_VERSION_INT, |
| 73 | }; |
| 74 | |
| 75 | static int ftp_getc(FTPContext *s) |
| 76 | { |
| 77 | int len; |
| 78 | if (s->control_buf_ptr >= s->control_buf_end) { |
| 79 | len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE); |
| 80 | if (len < 0) { |
| 81 | return len; |
| 82 | } else if (!len) { |
| 83 | return -1; |
| 84 | } else { |
| 85 | s->control_buf_ptr = s->control_buffer; |
| 86 | s->control_buf_end = s->control_buffer + len; |
| 87 | } |
| 88 | } |
| 89 | return *s->control_buf_ptr++; |
| 90 | } |
| 91 | |
| 92 | static int ftp_get_line(FTPContext *s, char *line, int line_size) |
| 93 | { |
| 94 | int ch; |
| 95 | char *q = line; |
| 96 | |
| 97 | for (;;) { |
| 98 | ch = ftp_getc(s); |
| 99 | if (ch < 0) { |
| 100 | return ch; |
| 101 | } |
| 102 | if (ch == '\n') { |
| 103 | /* process line */ |
| 104 | if (q > line && q[-1] == '\r') |
| 105 | q--; |
| 106 | *q = '\0'; |
| 107 | return 0; |
| 108 | } else { |
| 109 | if ((q - line) < line_size - 1) |
| 110 | *q++ = ch; |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /* |
| 116 | * This routine returns ftp server response code. |
| 117 | * Server may send more than one response for a certain command. |
| 118 | * First expected code is returned. |
| 119 | */ |
| 120 | static int ftp_status(FTPContext *s, char **line, const int response_codes[]) |
| 121 | { |
| 122 | int err, i, dash = 0, result = 0, code_found = 0, linesize; |
| 123 | char buf[CONTROL_BUFFER_SIZE]; |
| 124 | AVBPrint line_buffer; |
| 125 | |
| 126 | if (line) |
| 127 | av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC); |
| 128 | |
| 129 | while (!code_found || dash) { |
| 130 | if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) { |
| 131 | if (line) |
| 132 | av_bprint_finalize(&line_buffer, NULL); |
| 133 | return err; |
| 134 | } |
| 135 | |
| 136 | av_log(s, AV_LOG_DEBUG, "%s\n", buf); |
| 137 | |
| 138 | linesize = strlen(buf); |
| 139 | err = 0; |
| 140 | if (linesize >= 3) { |
| 141 | for (i = 0; i < 3; ++i) { |
| 142 | if (buf[i] < '0' || buf[i] > '9') { |
| 143 | err = 0; |
| 144 | break; |
| 145 | } |
| 146 | err *= 10; |
| 147 | err += buf[i] - '0'; |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | if (!code_found) { |
| 152 | if (err >= 500) { |
| 153 | code_found = 1; |
| 154 | result = err; |
| 155 | } else |
| 156 | for (i = 0; response_codes[i]; ++i) { |
| 157 | if (err == response_codes[i]) { |
| 158 | code_found = 1; |
| 159 | result = err; |
| 160 | break; |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | if (code_found) { |
| 165 | if (line) |
| 166 | av_bprintf(&line_buffer, "%s\r\n", buf); |
| 167 | if (linesize >= 4) { |
| 168 | if (!dash && buf[3] == '-') |
| 169 | dash = err; |
| 170 | else if (err == dash && buf[3] == ' ') |
| 171 | dash = 0; |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | if (line) |
| 177 | av_bprint_finalize(&line_buffer, line); |
| 178 | return result; |
| 179 | } |
| 180 | |
| 181 | static int ftp_send_command(FTPContext *s, const char *command, |
| 182 | const int response_codes[], char **response) |
| 183 | { |
| 184 | int err; |
| 185 | |
| 186 | if (response) |
| 187 | *response = NULL; |
| 188 | |
| 189 | if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0) |
| 190 | return err; |
| 191 | if (!err) |
| 192 | return -1; |
| 193 | |
| 194 | /* return status */ |
| 195 | if (response_codes) { |
| 196 | return ftp_status(s, response, response_codes); |
| 197 | } |
| 198 | return 0; |
| 199 | } |
| 200 | |
| 201 | static void ftp_close_data_connection(FTPContext *s) |
| 202 | { |
| 203 | ffurl_closep(&s->conn_data); |
| 204 | s->position = 0; |
| 205 | s->state = DISCONNECTED; |
| 206 | } |
| 207 | |
| 208 | static void ftp_close_both_connections(FTPContext *s) |
| 209 | { |
| 210 | ffurl_closep(&s->conn_control); |
| 211 | ftp_close_data_connection(s); |
| 212 | } |
| 213 | |
| 214 | static int ftp_auth(FTPContext *s) |
| 215 | { |
| 216 | const char *user = NULL, *pass = NULL; |
| 217 | char *end = NULL, buf[CONTROL_BUFFER_SIZE], credencials[CREDENTIALS_BUFFER_SIZE]; |
| 218 | int err; |
| 219 | static const int user_codes[] = {331, 230, 0}; |
| 220 | static const int pass_codes[] = {230, 0}; |
| 221 | |
| 222 | /* Authentication may be repeated, original string has to be saved */ |
| 223 | av_strlcpy(credencials, s->credencials, sizeof(credencials)); |
| 224 | |
| 225 | user = av_strtok(credencials, ":", &end); |
| 226 | pass = av_strtok(end, ":", &end); |
| 227 | |
| 228 | if (!user) { |
| 229 | user = "anonymous"; |
| 230 | pass = s->anonymous_password ? s->anonymous_password : "nopassword"; |
| 231 | } |
| 232 | |
| 233 | snprintf(buf, sizeof(buf), "USER %s\r\n", user); |
| 234 | err = ftp_send_command(s, buf, user_codes, NULL); |
| 235 | if (err == 331) { |
| 236 | if (pass) { |
| 237 | snprintf(buf, sizeof(buf), "PASS %s\r\n", pass); |
| 238 | err = ftp_send_command(s, buf, pass_codes, NULL); |
| 239 | } else |
| 240 | return AVERROR(EACCES); |
| 241 | } |
| 242 | if (err != 230) |
| 243 | return AVERROR(EACCES); |
| 244 | |
| 245 | return 0; |
| 246 | } |
| 247 | |
| 248 | static int ftp_passive_mode_epsv(FTPContext *s) |
| 249 | { |
| 250 | char *res = NULL, *start = NULL, *end = NULL; |
| 251 | int i; |
| 252 | static const char d = '|'; |
| 253 | static const char *command = "EPSV\r\n"; |
| 254 | static const int epsv_codes[] = {229, 0}; |
| 255 | |
| 256 | if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res) |
| 257 | goto fail; |
| 258 | |
| 259 | for (i = 0; res[i]; ++i) { |
| 260 | if (res[i] == '(') { |
| 261 | start = res + i + 1; |
| 262 | } else if (res[i] == ')') { |
| 263 | end = res + i; |
| 264 | break; |
| 265 | } |
| 266 | } |
| 267 | if (!start || !end) |
| 268 | goto fail; |
| 269 | |
| 270 | *end = '\0'; |
| 271 | if (strlen(start) < 5) |
| 272 | goto fail; |
| 273 | if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d) |
| 274 | goto fail; |
| 275 | start += 3; |
| 276 | end[-1] = '\0'; |
| 277 | |
| 278 | s->server_data_port = atoi(start); |
| 279 | av_dlog(s, "Server data port: %d\n", s->server_data_port); |
| 280 | |
| 281 | av_free(res); |
| 282 | return 0; |
| 283 | |
| 284 | fail: |
| 285 | av_free(res); |
| 286 | s->server_data_port = -1; |
| 287 | return AVERROR(ENOSYS); |
| 288 | } |
| 289 | |
| 290 | static int ftp_passive_mode(FTPContext *s) |
| 291 | { |
| 292 | char *res = NULL, *start = NULL, *end = NULL; |
| 293 | int i; |
| 294 | static const char *command = "PASV\r\n"; |
| 295 | static const int pasv_codes[] = {227, 0}; |
| 296 | |
| 297 | if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res) |
| 298 | goto fail; |
| 299 | |
| 300 | for (i = 0; res[i]; ++i) { |
| 301 | if (res[i] == '(') { |
| 302 | start = res + i + 1; |
| 303 | } else if (res[i] == ')') { |
| 304 | end = res + i; |
| 305 | break; |
| 306 | } |
| 307 | } |
| 308 | if (!start || !end) |
| 309 | goto fail; |
| 310 | |
| 311 | *end = '\0'; |
| 312 | /* skip ip */ |
| 313 | if (!av_strtok(start, ",", &end)) goto fail; |
| 314 | if (!av_strtok(end, ",", &end)) goto fail; |
| 315 | if (!av_strtok(end, ",", &end)) goto fail; |
| 316 | if (!av_strtok(end, ",", &end)) goto fail; |
| 317 | |
| 318 | /* parse port number */ |
| 319 | start = av_strtok(end, ",", &end); |
| 320 | if (!start) goto fail; |
| 321 | s->server_data_port = atoi(start) * 256; |
| 322 | start = av_strtok(end, ",", &end); |
| 323 | if (!start) goto fail; |
| 324 | s->server_data_port += atoi(start); |
| 325 | av_dlog(s, "Server data port: %d\n", s->server_data_port); |
| 326 | |
| 327 | av_free(res); |
| 328 | return 0; |
| 329 | |
| 330 | fail: |
| 331 | av_free(res); |
| 332 | s->server_data_port = -1; |
| 333 | return AVERROR(EIO); |
| 334 | } |
| 335 | |
| 336 | static int ftp_current_dir(FTPContext *s) |
| 337 | { |
| 338 | char *res = NULL, *start = NULL, *end = NULL; |
| 339 | int i; |
| 340 | static const char *command = "PWD\r\n"; |
| 341 | static const int pwd_codes[] = {257, 0}; |
| 342 | |
| 343 | if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res) |
| 344 | goto fail; |
| 345 | |
| 346 | for (i = 0; res[i]; ++i) { |
| 347 | if (res[i] == '"') { |
| 348 | if (!start) { |
| 349 | start = res + i + 1; |
| 350 | continue; |
| 351 | } |
| 352 | end = res + i; |
| 353 | break; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | if (!end) |
| 358 | goto fail; |
| 359 | |
| 360 | if (end > res && end[-1] == '/') { |
| 361 | end[-1] = '\0'; |
| 362 | } else |
| 363 | *end = '\0'; |
| 364 | av_strlcpy(s->path, start, sizeof(s->path)); |
| 365 | |
| 366 | av_free(res); |
| 367 | return 0; |
| 368 | |
| 369 | fail: |
| 370 | av_free(res); |
| 371 | return AVERROR(EIO); |
| 372 | } |
| 373 | |
| 374 | static int ftp_file_size(FTPContext *s) |
| 375 | { |
| 376 | char command[CONTROL_BUFFER_SIZE]; |
| 377 | char *res = NULL; |
| 378 | static const int size_codes[] = {213, 0}; |
| 379 | |
| 380 | snprintf(command, sizeof(command), "SIZE %s\r\n", s->path); |
| 381 | if (ftp_send_command(s, command, size_codes, &res) == 213 && res) { |
| 382 | s->filesize = strtoll(&res[4], NULL, 10); |
| 383 | } else { |
| 384 | s->filesize = -1; |
| 385 | av_free(res); |
| 386 | return AVERROR(EIO); |
| 387 | } |
| 388 | |
| 389 | av_free(res); |
| 390 | return 0; |
| 391 | } |
| 392 | |
| 393 | static int ftp_retrieve(FTPContext *s) |
| 394 | { |
| 395 | char command[CONTROL_BUFFER_SIZE]; |
| 396 | static const int retr_codes[] = {150, 0}; |
| 397 | |
| 398 | snprintf(command, sizeof(command), "RETR %s\r\n", s->path); |
| 399 | if (ftp_send_command(s, command, retr_codes, NULL) != 150) |
| 400 | return AVERROR(EIO); |
| 401 | |
| 402 | s->state = DOWNLOADING; |
| 403 | |
| 404 | return 0; |
| 405 | } |
| 406 | |
| 407 | static int ftp_store(FTPContext *s) |
| 408 | { |
| 409 | char command[CONTROL_BUFFER_SIZE]; |
| 410 | static const int stor_codes[] = {150, 0}; |
| 411 | |
| 412 | snprintf(command, sizeof(command), "STOR %s\r\n", s->path); |
| 413 | if (ftp_send_command(s, command, stor_codes, NULL) != 150) |
| 414 | return AVERROR(EIO); |
| 415 | |
| 416 | s->state = UPLOADING; |
| 417 | |
| 418 | return 0; |
| 419 | } |
| 420 | |
| 421 | static int ftp_type(FTPContext *s) |
| 422 | { |
| 423 | static const char *command = "TYPE I\r\n"; |
| 424 | static const int type_codes[] = {200, 0}; |
| 425 | |
| 426 | if (ftp_send_command(s, command, type_codes, NULL) != 200) |
| 427 | return AVERROR(EIO); |
| 428 | |
| 429 | return 0; |
| 430 | } |
| 431 | |
| 432 | static int ftp_restart(FTPContext *s, int64_t pos) |
| 433 | { |
| 434 | char command[CONTROL_BUFFER_SIZE]; |
| 435 | static const int rest_codes[] = {350, 0}; |
| 436 | |
| 437 | snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos); |
| 438 | if (ftp_send_command(s, command, rest_codes, NULL) != 350) |
| 439 | return AVERROR(EIO); |
| 440 | |
| 441 | return 0; |
| 442 | } |
| 443 | |
| 444 | static int ftp_features(FTPContext *s) |
| 445 | { |
| 446 | static const char *feat_command = "FEAT\r\n"; |
| 447 | static const char *enable_utf8_command = "OPTS UTF8 ON\r\n"; |
| 448 | static const int feat_codes[] = {211, 0}; |
| 449 | static const int opts_codes[] = {200, 451}; |
| 450 | char *feat = NULL; |
| 451 | |
| 452 | if (ftp_send_command(s, feat_command, feat_codes, &feat) == 211) { |
| 453 | if (av_stristr(feat, "UTF8")) |
| 454 | ftp_send_command(s, enable_utf8_command, opts_codes, NULL); |
| 455 | } |
| 456 | av_freep(&feat); |
| 457 | |
| 458 | return 0; |
| 459 | } |
| 460 | |
| 461 | static int ftp_connect_control_connection(URLContext *h) |
| 462 | { |
| 463 | char buf[CONTROL_BUFFER_SIZE], *response = NULL; |
| 464 | int err; |
| 465 | AVDictionary *opts = NULL; |
| 466 | FTPContext *s = h->priv_data; |
| 467 | static const int connect_codes[] = {220, 0}; |
| 468 | |
| 469 | if (!s->conn_control) { |
| 470 | ff_url_join(buf, sizeof(buf), "tcp", NULL, |
| 471 | s->hostname, s->server_control_port, NULL); |
| 472 | if (s->rw_timeout != -1) { |
| 473 | av_dict_set_int(&opts, "timeout", s->rw_timeout, 0); |
| 474 | } /* if option is not given, don't pass it and let tcp use its own default */ |
| 475 | err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE, |
| 476 | &h->interrupt_callback, &opts); |
| 477 | av_dict_free(&opts); |
| 478 | if (err < 0) { |
| 479 | av_log(h, AV_LOG_ERROR, "Cannot open control connection\n"); |
| 480 | return err; |
| 481 | } |
| 482 | |
| 483 | /* check if server is ready */ |
| 484 | if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) { |
| 485 | av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n"); |
| 486 | return AVERROR(EACCES); |
| 487 | } |
| 488 | |
| 489 | if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) { |
| 490 | 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."); |
| 491 | } |
| 492 | av_free(response); |
| 493 | |
| 494 | if ((err = ftp_auth(s)) < 0) { |
| 495 | av_log(h, AV_LOG_ERROR, "FTP authentication failed\n"); |
| 496 | return err; |
| 497 | } |
| 498 | |
| 499 | if ((err = ftp_type(s)) < 0) { |
| 500 | av_log(h, AV_LOG_ERROR, "Set content type failed\n"); |
| 501 | return err; |
| 502 | } |
| 503 | |
| 504 | ftp_features(s); |
| 505 | } |
| 506 | return 0; |
| 507 | } |
| 508 | |
| 509 | static int ftp_connect_data_connection(URLContext *h) |
| 510 | { |
| 511 | int err; |
| 512 | char buf[CONTROL_BUFFER_SIZE]; |
| 513 | AVDictionary *opts = NULL; |
| 514 | FTPContext *s = h->priv_data; |
| 515 | |
| 516 | if (!s->conn_data) { |
| 517 | /* Enter passive mode */ |
| 518 | if (ftp_passive_mode_epsv(s) < 0) { |
| 519 | /* Use PASV as fallback */ |
| 520 | if ((err = ftp_passive_mode(s)) < 0) |
| 521 | return err; |
| 522 | } |
| 523 | /* Open data connection */ |
| 524 | ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL); |
| 525 | if (s->rw_timeout != -1) { |
| 526 | av_dict_set_int(&opts, "timeout", s->rw_timeout, 0); |
| 527 | } /* if option is not given, don't pass it and let tcp use its own default */ |
| 528 | err = ffurl_open(&s->conn_data, buf, h->flags, |
| 529 | &h->interrupt_callback, &opts); |
| 530 | av_dict_free(&opts); |
| 531 | if (err < 0) |
| 532 | return err; |
| 533 | |
| 534 | if (s->position) |
| 535 | if ((err = ftp_restart(s, s->position)) < 0) |
| 536 | return err; |
| 537 | } |
| 538 | s->state = READY; |
| 539 | return 0; |
| 540 | } |
| 541 | |
| 542 | static int ftp_abort(URLContext *h) |
| 543 | { |
| 544 | static const char *command = "ABOR\r\n"; |
| 545 | int err; |
| 546 | static const int abor_codes[] = {225, 226, 0}; |
| 547 | FTPContext *s = h->priv_data; |
| 548 | |
| 549 | /* According to RCF 959: |
| 550 | "ABOR command tells the server to abort the previous FTP |
| 551 | service command and any associated transfer of data." |
| 552 | |
| 553 | There are FTP server implementations that don't response |
| 554 | to any commands during data transfer in passive mode (including ABOR). |
| 555 | |
| 556 | This implementation closes data connection by force. |
| 557 | */ |
| 558 | |
| 559 | if (ftp_send_command(s, command, NULL, NULL) < 0) { |
| 560 | ftp_close_both_connections(s); |
| 561 | if ((err = ftp_connect_control_connection(h)) < 0) { |
| 562 | av_log(h, AV_LOG_ERROR, "Reconnect failed.\n"); |
| 563 | return err; |
| 564 | } |
| 565 | } else { |
| 566 | ftp_close_data_connection(s); |
| 567 | if (ftp_status(s, NULL, abor_codes) < 225) { |
| 568 | /* wu-ftpd also closes control connection after data connection closing */ |
| 569 | ffurl_closep(&s->conn_control); |
| 570 | if ((err = ftp_connect_control_connection(h)) < 0) { |
| 571 | av_log(h, AV_LOG_ERROR, "Reconnect failed.\n"); |
| 572 | return err; |
| 573 | } |
| 574 | } |
| 575 | } |
| 576 | |
| 577 | return 0; |
| 578 | } |
| 579 | |
| 580 | static int ftp_open(URLContext *h, const char *url, int flags) |
| 581 | { |
| 582 | char proto[10], path[MAX_URL_SIZE]; |
| 583 | int err; |
| 584 | FTPContext *s = h->priv_data; |
| 585 | |
| 586 | av_dlog(h, "ftp protocol open\n"); |
| 587 | |
| 588 | s->state = DISCONNECTED; |
| 589 | s->filesize = -1; |
| 590 | s->position = 0; |
| 591 | |
| 592 | av_url_split(proto, sizeof(proto), |
| 593 | s->credencials, sizeof(s->credencials), |
| 594 | s->hostname, sizeof(s->hostname), |
| 595 | &s->server_control_port, |
| 596 | path, sizeof(path), |
| 597 | url); |
| 598 | |
| 599 | if (s->server_control_port < 0 || s->server_control_port > 65535) |
| 600 | s->server_control_port = 21; |
| 601 | |
| 602 | if ((err = ftp_connect_control_connection(h)) < 0) |
| 603 | goto fail; |
| 604 | |
| 605 | if ((err = ftp_current_dir(s)) < 0) |
| 606 | goto fail; |
| 607 | av_strlcat(s->path, path, sizeof(s->path)); |
| 608 | |
| 609 | if (ftp_restart(s, 0) < 0) { |
| 610 | h->is_streamed = 1; |
| 611 | } else { |
| 612 | if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ) |
| 613 | h->is_streamed = 1; |
| 614 | if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE) |
| 615 | h->is_streamed = 1; |
| 616 | } |
| 617 | |
| 618 | return 0; |
| 619 | |
| 620 | fail: |
| 621 | av_log(h, AV_LOG_ERROR, "FTP open failed\n"); |
| 622 | ffurl_closep(&s->conn_control); |
| 623 | ffurl_closep(&s->conn_data); |
| 624 | return err; |
| 625 | } |
| 626 | |
| 627 | static int64_t ftp_seek(URLContext *h, int64_t pos, int whence) |
| 628 | { |
| 629 | FTPContext *s = h->priv_data; |
| 630 | int err; |
| 631 | int64_t new_pos, fake_pos; |
| 632 | |
| 633 | av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence); |
| 634 | |
| 635 | switch(whence) { |
| 636 | case AVSEEK_SIZE: |
| 637 | return s->filesize; |
| 638 | case SEEK_SET: |
| 639 | new_pos = pos; |
| 640 | break; |
| 641 | case SEEK_CUR: |
| 642 | new_pos = s->position + pos; |
| 643 | break; |
| 644 | case SEEK_END: |
| 645 | if (s->filesize < 0) |
| 646 | return AVERROR(EIO); |
| 647 | new_pos = s->filesize + pos; |
| 648 | break; |
| 649 | default: |
| 650 | return AVERROR(EINVAL); |
| 651 | } |
| 652 | |
| 653 | if (h->is_streamed) |
| 654 | return AVERROR(EIO); |
| 655 | |
| 656 | if (new_pos < 0) { |
| 657 | av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n"); |
| 658 | return AVERROR(EINVAL); |
| 659 | } |
| 660 | |
| 661 | fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos; |
| 662 | if (fake_pos != s->position) { |
| 663 | if ((err = ftp_abort(h)) < 0) |
| 664 | return err; |
| 665 | s->position = fake_pos; |
| 666 | } |
| 667 | return new_pos; |
| 668 | } |
| 669 | |
| 670 | static int ftp_read(URLContext *h, unsigned char *buf, int size) |
| 671 | { |
| 672 | FTPContext *s = h->priv_data; |
| 673 | int read, err, retry_done = 0; |
| 674 | |
| 675 | av_dlog(h, "ftp protocol read %d bytes\n", size); |
| 676 | retry: |
| 677 | if (s->state == DISCONNECTED) { |
| 678 | /* optimization */ |
| 679 | if (s->position >= s->filesize) |
| 680 | return 0; |
| 681 | if ((err = ftp_connect_data_connection(h)) < 0) |
| 682 | return err; |
| 683 | } |
| 684 | if (s->state == READY) { |
| 685 | if (s->position >= s->filesize) |
| 686 | return 0; |
| 687 | if ((err = ftp_retrieve(s)) < 0) |
| 688 | return err; |
| 689 | } |
| 690 | if (s->conn_data && s->state == DOWNLOADING) { |
| 691 | read = ffurl_read(s->conn_data, buf, size); |
| 692 | if (read >= 0) { |
| 693 | s->position += read; |
| 694 | if (s->position >= s->filesize) { |
| 695 | /* server will terminate, but keep current position to avoid madness */ |
| 696 | /* save position to restart from it */ |
| 697 | int64_t pos = s->position; |
| 698 | if (ftp_abort(h) < 0) { |
| 699 | s->position = pos; |
| 700 | return AVERROR(EIO); |
| 701 | } |
| 702 | s->position = pos; |
| 703 | } |
| 704 | } |
| 705 | if (read <= 0 && s->position < s->filesize && !h->is_streamed) { |
| 706 | /* Server closed connection. Probably due to inactivity */ |
| 707 | int64_t pos = s->position; |
| 708 | av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n"); |
| 709 | if ((err = ftp_abort(h)) < 0) |
| 710 | return err; |
| 711 | if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) { |
| 712 | av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n"); |
| 713 | return err; |
| 714 | } |
| 715 | if (!retry_done) { |
| 716 | retry_done = 1; |
| 717 | goto retry; |
| 718 | } |
| 719 | } |
| 720 | return read; |
| 721 | } |
| 722 | |
| 723 | av_log(h, AV_LOG_DEBUG, "FTP read failed\n"); |
| 724 | return AVERROR(EIO); |
| 725 | } |
| 726 | |
| 727 | static int ftp_write(URLContext *h, const unsigned char *buf, int size) |
| 728 | { |
| 729 | int err; |
| 730 | FTPContext *s = h->priv_data; |
| 731 | int written; |
| 732 | |
| 733 | av_dlog(h, "ftp protocol write %d bytes\n", size); |
| 734 | |
| 735 | if (s->state == DISCONNECTED) { |
| 736 | if ((err = ftp_connect_data_connection(h)) < 0) |
| 737 | return err; |
| 738 | } |
| 739 | if (s->state == READY) { |
| 740 | if ((err = ftp_store(s)) < 0) |
| 741 | return err; |
| 742 | } |
| 743 | if (s->conn_data && s->state == UPLOADING) { |
| 744 | written = ffurl_write(s->conn_data, buf, size); |
| 745 | if (written > 0) { |
| 746 | s->position += written; |
| 747 | s->filesize = FFMAX(s->filesize, s->position); |
| 748 | } |
| 749 | return written; |
| 750 | } |
| 751 | |
| 752 | av_log(h, AV_LOG_ERROR, "FTP write failed\n"); |
| 753 | return AVERROR(EIO); |
| 754 | } |
| 755 | |
| 756 | static int ftp_close(URLContext *h) |
| 757 | { |
| 758 | av_dlog(h, "ftp protocol close\n"); |
| 759 | |
| 760 | ftp_close_both_connections(h->priv_data); |
| 761 | |
| 762 | return 0; |
| 763 | } |
| 764 | |
| 765 | static int ftp_get_file_handle(URLContext *h) |
| 766 | { |
| 767 | FTPContext *s = h->priv_data; |
| 768 | |
| 769 | av_dlog(h, "ftp protocol get_file_handle\n"); |
| 770 | |
| 771 | if (s->conn_data) |
| 772 | return ffurl_get_file_handle(s->conn_data); |
| 773 | |
| 774 | return AVERROR(EIO); |
| 775 | } |
| 776 | |
| 777 | static int ftp_shutdown(URLContext *h, int flags) |
| 778 | { |
| 779 | FTPContext *s = h->priv_data; |
| 780 | |
| 781 | av_dlog(h, "ftp protocol shutdown\n"); |
| 782 | |
| 783 | if (s->conn_data) |
| 784 | return ffurl_shutdown(s->conn_data, flags); |
| 785 | |
| 786 | return AVERROR(EIO); |
| 787 | } |
| 788 | |
| 789 | URLProtocol ff_ftp_protocol = { |
| 790 | .name = "ftp", |
| 791 | .url_open = ftp_open, |
| 792 | .url_read = ftp_read, |
| 793 | .url_write = ftp_write, |
| 794 | .url_seek = ftp_seek, |
| 795 | .url_close = ftp_close, |
| 796 | .url_get_file_handle = ftp_get_file_handle, |
| 797 | .url_shutdown = ftp_shutdown, |
| 798 | .priv_data_size = sizeof(FTPContext), |
| 799 | .priv_data_class = &ftp_context_class, |
| 800 | .flags = URL_PROTOCOL_FLAG_NETWORK, |
| 801 | }; |