FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/ftp.c
Date: 2022-12-09 07:38:14
Exec Total Coverage
Lines: 0 621 0.0%
Functions: 0 41 0.0%
Branches: 0 420 0.0%

Line Branch Exec Source
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 <string.h>
22
23 #include "libavutil/avstring.h"
24 #include "libavutil/internal.h"
25 #include "libavutil/parseutils.h"
26 #include "avformat.h"
27 #include "internal.h"
28 #include "url.h"
29 #include "urldecode.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/bprint.h"
32
33 #define CONTROL_BUFFER_SIZE 1024
34 #define DIR_BUFFER_SIZE 4096
35
36 typedef enum {
37 UNKNOWN,
38 READY,
39 DOWNLOADING,
40 UPLOADING,
41 LISTING_DIR,
42 DISCONNECTED,
43 ENDOFFILE,
44 } FTPState;
45
46 typedef enum {
47 UNKNOWN_METHOD,
48 NLST,
49 MLSD
50 } FTPListingMethod;
51
52 typedef struct {
53 const AVClass *class;
54 URLContext *conn_control; /**< Control connection */
55 URLContext *conn_data; /**< Data connection, NULL when not connected */
56 uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
57 uint8_t *control_buf_ptr, *control_buf_end;
58 int server_data_port; /**< Data connection port opened by server, -1 on error. */
59 int server_control_port; /**< Control connection port, default is 21 */
60 char *hostname; /**< Server address. */
61 char *user; /**< Server user */
62 char *password; /**< Server user's password */
63 char *path; /**< Path to resource on server. */
64 int64_t filesize; /**< Size of file on server, -1 on error. */
65 int64_t position; /**< Current position, calculated. */
66 int rw_timeout; /**< Network timeout. */
67 const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */
68 int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */
69 FTPState state; /**< State of data connection */
70 FTPListingMethod listing_method; /**< Called listing method */
71 char *features; /**< List of server's features represented as raw response */
72 char *dir_buffer;
73 size_t dir_buffer_size;
74 size_t dir_buffer_offset;
75 int utf8;
76 const char *option_user; /**< User to be used if none given in the URL */
77 const char *option_password; /**< Password to be used if none given in the URL */
78 } FTPContext;
79
80 #define OFFSET(x) offsetof(FTPContext, x)
81 #define D AV_OPT_FLAG_DECODING_PARAM
82 #define E AV_OPT_FLAG_ENCODING_PARAM
83 static const AVOption options[] = {
84 {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
85 {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
86 {"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 },
87 {"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 },
88 {"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 },
89 {NULL}
90 };
91
92 static const AVClass ftp_context_class = {
93 .class_name = "ftp",
94 .item_name = av_default_item_name,
95 .option = options,
96 .version = LIBAVUTIL_VERSION_INT,
97 };
98
99 static int ftp_close(URLContext *h);
100
101 static int ftp_getc(FTPContext *s)
102 {
103 int len;
104 if (s->control_buf_ptr >= s->control_buf_end) {
105 len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
106 if (len < 0) {
107 return len;
108 } else if (!len) {
109 return -1;
110 } else {
111 s->control_buf_ptr = s->control_buffer;
112 s->control_buf_end = s->control_buffer + len;
113 }
114 }
115 return *s->control_buf_ptr++;
116 }
117
118 static int ftp_get_line(FTPContext *s, char *line, int line_size)
119 {
120 int ch;
121 char *q = line;
122
123 for (;;) {
124 ch = ftp_getc(s);
125 if (ch < 0) {
126 return ch;
127 }
128 if (ch == '\n') {
129 /* process line */
130 if (q > line && q[-1] == '\r')
131 q--;
132 *q = '\0';
133 return 0;
134 } else {
135 if ((q - line) < line_size - 1)
136 *q++ = ch;
137 }
138 }
139 }
140
141 /*
142 * This routine returns ftp server response code.
143 * Server may send more than one response for a certain command.
144 * First expected code is returned.
145 */
146 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
147 {
148 int err, i, dash = 0, result = 0, code_found = 0, linesize;
149 char buf[CONTROL_BUFFER_SIZE];
150 AVBPrint line_buffer;
151
152 if (line)
153 av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
154
155 while (!code_found || dash) {
156 if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
157 if (line)
158 av_bprint_finalize(&line_buffer, NULL);
159 return err;
160 }
161
162 av_log(s, AV_LOG_DEBUG, "%s\n", buf);
163
164 linesize = strlen(buf);
165 err = 0;
166 if (linesize >= 3) {
167 for (i = 0; i < 3; ++i) {
168 if (buf[i] < '0' || buf[i] > '9') {
169 err = 0;
170 break;
171 }
172 err *= 10;
173 err += buf[i] - '0';
174 }
175 }
176
177 if (!code_found) {
178 if (err >= 500) {
179 code_found = 1;
180 result = err;
181 } else
182 for (i = 0; response_codes[i]; ++i) {
183 if (err == response_codes[i]) {
184 code_found = 1;
185 result = err;
186 break;
187 }
188 }
189 }
190 if (code_found) {
191 if (line)
192 av_bprintf(&line_buffer, "%s\r\n", buf);
193 if (linesize >= 4) {
194 if (!dash && buf[3] == '-')
195 dash = err;
196 else if (err == dash && buf[3] == ' ')
197 dash = 0;
198 }
199 }
200 }
201
202 if (line)
203 av_bprint_finalize(&line_buffer, line);
204 return result;
205 }
206
207 static int ftp_send_command(FTPContext *s, const char *command,
208 const int response_codes[], char **response)
209 {
210 int err;
211
212 ff_dlog(s, "%s", command);
213
214 if (response)
215 *response = NULL;
216
217 if (!s->conn_control)
218 return AVERROR(EIO);
219
220 if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
221 return err;
222 if (!err)
223 return -1;
224
225 /* return status */
226 if (response_codes) {
227 return ftp_status(s, response, response_codes);
228 }
229 return 0;
230 }
231
232 static void ftp_close_data_connection(FTPContext *s)
233 {
234 ffurl_closep(&s->conn_data);
235 s->state = DISCONNECTED;
236 }
237
238 static void ftp_close_both_connections(FTPContext *s)
239 {
240 ffurl_closep(&s->conn_control);
241 ftp_close_data_connection(s);
242 }
243
244 static int ftp_auth(FTPContext *s)
245 {
246 char buf[CONTROL_BUFFER_SIZE];
247 int err;
248 static const int user_codes[] = {331, 230, 0};
249 static const int pass_codes[] = {230, 0};
250
251 if (strpbrk(s->user, "\r\n"))
252 return AVERROR(EINVAL);
253 err = snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
254 if (err >= sizeof(buf))
255 return AVERROR(ENOSYS);
256
257 err = ftp_send_command(s, buf, user_codes, NULL);
258 if (err == 331) {
259 if (s->password) {
260 if (strpbrk(s->password, "\r\n"))
261 return AVERROR(EINVAL);
262 err = snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
263 if (err >= sizeof(buf))
264 return AVERROR(ENOSYS);
265
266 err = ftp_send_command(s, buf, pass_codes, NULL);
267 } else
268 return AVERROR(EACCES);
269 }
270 if (err != 230)
271 return AVERROR(EACCES);
272
273 return 0;
274 }
275
276 static int ftp_passive_mode_epsv(FTPContext *s)
277 {
278 char *res = NULL, *start = NULL, *end = NULL;
279 int i;
280 static const char d = '|';
281 static const char *command = "EPSV\r\n";
282 static const int epsv_codes[] = {229, 0};
283
284 if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
285 goto fail;
286
287 for (i = 0; res[i]; ++i) {
288 if (res[i] == '(') {
289 start = res + i + 1;
290 } else if (res[i] == ')') {
291 end = res + i;
292 break;
293 }
294 }
295 if (!start || !end)
296 goto fail;
297
298 *end = '\0';
299 if (strlen(start) < 5)
300 goto fail;
301 if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
302 goto fail;
303 start += 3;
304 end[-1] = '\0';
305
306 s->server_data_port = atoi(start);
307 ff_dlog(s, "Server data port: %d\n", s->server_data_port);
308
309 av_free(res);
310 return 0;
311
312 fail:
313 av_free(res);
314 s->server_data_port = -1;
315 return AVERROR(ENOSYS);
316 }
317
318 static int ftp_passive_mode(FTPContext *s)
319 {
320 char *res = NULL, *start = NULL, *end = NULL;
321 int i;
322 static const char *command = "PASV\r\n";
323 static const int pasv_codes[] = {227, 0};
324
325 if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
326 goto fail;
327
328 for (i = 0; res[i]; ++i) {
329 if (res[i] == '(') {
330 start = res + i + 1;
331 } else if (res[i] == ')') {
332 end = res + i;
333 break;
334 }
335 }
336 if (!start || !end)
337 goto fail;
338
339 *end = '\0';
340 /* skip ip */
341 if (!av_strtok(start, ",", &end)) goto fail;
342 if (!av_strtok(NULL, ",", &end)) goto fail;
343 if (!av_strtok(NULL, ",", &end)) goto fail;
344 if (!av_strtok(NULL, ",", &end)) goto fail;
345
346 /* parse port number */
347 start = av_strtok(NULL, ",", &end);
348 if (!start) goto fail;
349 s->server_data_port = atoi(start) * 256;
350 start = av_strtok(NULL, ",", &end);
351 if (!start) goto fail;
352 s->server_data_port += atoi(start);
353 ff_dlog(s, "Server data port: %d\n", s->server_data_port);
354
355 av_free(res);
356 return 0;
357
358 fail:
359 av_free(res);
360 s->server_data_port = -1;
361 return AVERROR(EIO);
362 }
363
364 static int ftp_current_dir(FTPContext *s)
365 {
366 char *res = NULL, *start = NULL, *end = NULL;
367 int i;
368 static const char *command = "PWD\r\n";
369 static const int pwd_codes[] = {257, 0};
370
371 if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
372 goto fail;
373
374 for (i = 0; res[i]; ++i) {
375 if (res[i] == '"') {
376 if (!start) {
377 start = res + i + 1;
378 continue;
379 }
380 end = res + i;
381 break;
382 }
383 }
384
385 if (!end)
386 goto fail;
387
388 *end = '\0';
389 s->path = av_strdup(start);
390
391 av_free(res);
392
393 if (!s->path)
394 return AVERROR(ENOMEM);
395 return 0;
396
397 fail:
398 av_free(res);
399 return AVERROR(EIO);
400 }
401
402 static int ftp_file_size(FTPContext *s)
403 {
404 char command[CONTROL_BUFFER_SIZE];
405 char *res = NULL;
406 int ret;
407 static const int size_codes[] = {213, 0};
408
409 ret = snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
410 if (ret >= sizeof(command))
411 return AVERROR(ENOSYS);
412
413 if (ftp_send_command(s, command, size_codes, &res) == 213 && res && strlen(res) > 4) {
414 s->filesize = strtoll(&res[4], NULL, 10);
415 } else {
416 s->filesize = -1;
417 av_free(res);
418 return AVERROR(EIO);
419 }
420
421 av_free(res);
422 return 0;
423 }
424
425 static int ftp_retrieve(FTPContext *s)
426 {
427 char command[CONTROL_BUFFER_SIZE];
428 static const int retr_codes[] = {150, 125, 0};
429 int resp_code, ret;
430
431 ret = snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
432 if (ret >= sizeof(command))
433 return AVERROR(ENOSYS);
434
435 resp_code = ftp_send_command(s, command, retr_codes, NULL);
436 if (resp_code != 125 && resp_code != 150)
437 return AVERROR(EIO);
438
439 s->state = DOWNLOADING;
440
441 return 0;
442 }
443
444 static int ftp_store(FTPContext *s)
445 {
446 char command[CONTROL_BUFFER_SIZE];
447 static const int stor_codes[] = {150, 125, 0};
448 int resp_code, ret;
449
450 ret = snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
451 if (ret >= sizeof(command))
452 return AVERROR(ENOSYS);
453
454 resp_code = ftp_send_command(s, command, stor_codes, NULL);
455 if (resp_code != 125 && resp_code != 150)
456 return AVERROR(EIO);
457
458 s->state = UPLOADING;
459
460 return 0;
461 }
462
463 static int ftp_type(FTPContext *s)
464 {
465 static const char *command = "TYPE I\r\n";
466 static const int type_codes[] = {200, 0};
467
468 if (ftp_send_command(s, command, type_codes, NULL) != 200)
469 return AVERROR(EIO);
470
471 return 0;
472 }
473
474 static int ftp_restart(FTPContext *s, int64_t pos)
475 {
476 char command[CONTROL_BUFFER_SIZE];
477 static const int rest_codes[] = {350, 0};
478
479 snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
480 if (ftp_send_command(s, command, rest_codes, NULL) != 350)
481 return AVERROR(EIO);
482
483 return 0;
484 }
485
486 static int ftp_set_dir(FTPContext *s)
487 {
488 static const int cwd_codes[] = {250, 550, 0}; /* 550 is incorrect code */
489 char command[MAX_URL_SIZE];
490 int ret;
491
492 ret = snprintf(command, sizeof(command), "CWD %s\r\n", s->path);
493 if (ret >= sizeof(command))
494 return AVERROR(ENOSYS);
495
496 if (ftp_send_command(s, command, cwd_codes, NULL) != 250)
497 return AVERROR(EIO);
498 return 0;
499 }
500
501 static int ftp_list_mlsd(FTPContext *s)
502 {
503 static const char *command = "MLSD\r\n";
504 static const int mlsd_codes[] = {150, 500, 0}; /* 500 is incorrect code */
505
506 if (ftp_send_command(s, command, mlsd_codes, NULL) != 150)
507 return AVERROR(ENOSYS);
508 s->listing_method = MLSD;
509 return 0;
510 }
511
512 static int ftp_list_nlst(FTPContext *s)
513 {
514 static const char *command = "NLST\r\n";
515 static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0};
516
517 if (ftp_send_command(s, command, nlst_codes, NULL) != 226)
518 return AVERROR(ENOSYS);
519 s->listing_method = NLST;
520 return 0;
521 }
522
523 static int ftp_list(FTPContext *s)
524 {
525 int ret;
526 s->state = LISTING_DIR;
527
528 if ((ret = ftp_list_mlsd(s)) < 0)
529 ret = ftp_list_nlst(s);
530
531 return ret;
532 }
533
534 static int ftp_has_feature(FTPContext *s, const char *feature_name)
535 {
536 if (!s->features)
537 return 0;
538
539 return av_stristr(s->features, feature_name) != NULL;
540 }
541
542 static int ftp_features(FTPContext *s)
543 {
544 static const char *feat_command = "FEAT\r\n";
545 static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
546 static const int feat_codes[] = {211, 0};
547 static const int opts_codes[] = {200, 202, 451, 0};
548
549 av_freep(&s->features);
550 if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
551 av_freep(&s->features);
552 }
553
554 if (ftp_has_feature(s, "UTF8")) {
555 int ret = ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
556 if (ret == 200 || ret == 202)
557 s->utf8 = 1;
558 }
559
560 return 0;
561 }
562
563 static int ftp_connect_control_connection(URLContext *h)
564 {
565 char buf[CONTROL_BUFFER_SIZE], *response = NULL;
566 int err;
567 AVDictionary *opts = NULL;
568 FTPContext *s = h->priv_data;
569 static const int connect_codes[] = {220, 0};
570
571 if (!s->conn_control) {
572 ff_url_join(buf, sizeof(buf), "tcp", NULL,
573 s->hostname, s->server_control_port, NULL);
574 if (s->rw_timeout != -1) {
575 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
576 } /* if option is not given, don't pass it and let tcp use its own default */
577 err = ffurl_open_whitelist(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
578 &h->interrupt_callback, &opts,
579 h->protocol_whitelist, h->protocol_blacklist, h);
580 av_dict_free(&opts);
581 if (err < 0) {
582 av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
583 return err;
584 }
585
586 /* check if server is ready */
587 if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
588 av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
589 return AVERROR(EACCES);
590 }
591
592 if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
593 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.");
594 }
595 av_free(response);
596
597 if ((err = ftp_auth(s)) < 0) {
598 av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
599 return err;
600 }
601
602 if ((err = ftp_type(s)) < 0) {
603 av_log(h, AV_LOG_ERROR, "Set content type failed\n");
604 return err;
605 }
606
607 ftp_features(s);
608 }
609 return 0;
610 }
611
612 static int ftp_connect_data_connection(URLContext *h)
613 {
614 int err;
615 char buf[CONTROL_BUFFER_SIZE];
616 AVDictionary *opts = NULL;
617 FTPContext *s = h->priv_data;
618
619 if (!s->conn_data) {
620 /* Enter passive mode */
621 if (ftp_passive_mode_epsv(s) < 0) {
622 /* Use PASV as fallback */
623 if ((err = ftp_passive_mode(s)) < 0)
624 return err;
625 }
626 /* Open data connection */
627 ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
628 if (s->rw_timeout != -1) {
629 av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
630 } /* if option is not given, don't pass it and let tcp use its own default */
631 err = ffurl_open_whitelist(&s->conn_data, buf, h->flags,
632 &h->interrupt_callback, &opts,
633 h->protocol_whitelist, h->protocol_blacklist, h);
634 av_dict_free(&opts);
635 if (err < 0)
636 return err;
637
638 if (s->position)
639 if ((err = ftp_restart(s, s->position)) < 0)
640 return err;
641 }
642 s->state = READY;
643 return 0;
644 }
645
646 static int ftp_abort(URLContext *h)
647 {
648 static const char *command = "ABOR\r\n";
649 int err;
650 static const int abor_codes[] = {225, 226, 0};
651 FTPContext *s = h->priv_data;
652
653 /* According to RCF 959:
654 "ABOR command tells the server to abort the previous FTP
655 service command and any associated transfer of data."
656
657 There are FTP server implementations that don't response
658 to any commands during data transfer in passive mode (including ABOR).
659
660 This implementation closes data connection by force.
661 */
662
663 if (ftp_send_command(s, command, NULL, NULL) < 0) {
664 ftp_close_both_connections(s);
665 if ((err = ftp_connect_control_connection(h)) < 0) {
666 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
667 return err;
668 }
669 } else {
670 ftp_close_data_connection(s);
671 if (ftp_status(s, NULL, abor_codes) < 225) {
672 /* wu-ftpd also closes control connection after data connection closing */
673 ffurl_closep(&s->conn_control);
674 if ((err = ftp_connect_control_connection(h)) < 0) {
675 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
676 return err;
677 }
678 }
679 }
680
681 return 0;
682 }
683
684 static int ftp_connect(URLContext *h, const char *url)
685 {
686 char proto[10], path[MAX_URL_SIZE], credentials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
687 const char *tok_user = NULL, *tok_pass = NULL;
688 char *newpath = NULL;
689 int err;
690 FTPContext *s = h->priv_data;
691
692 s->state = DISCONNECTED;
693 s->listing_method = UNKNOWN_METHOD;
694 s->filesize = -1;
695 s->position = 0;
696 s->features = NULL;
697
698 av_url_split(proto, sizeof(proto),
699 credentials, sizeof(credentials),
700 hostname, sizeof(hostname),
701 &s->server_control_port,
702 path, sizeof(path),
703 url);
704
705 if (!*credentials) {
706 if (!s->option_user) {
707 tok_user = "anonymous";
708 tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
709 } else {
710 tok_user = s->option_user;
711 tok_pass = s->option_password;
712 }
713 s->user = av_strdup(tok_user);
714 s->password = av_strdup(tok_pass);
715 } else {
716 char *pass = strchr(credentials, ':');
717 if (pass) {
718 *pass++ = '\0';
719 tok_pass = pass;
720 s->password = ff_urldecode(pass, 0);
721 } else {
722 tok_pass = s->option_password;
723 s->password = av_strdup(tok_pass);
724 }
725 s->user = ff_urldecode(credentials, 0);
726 }
727 s->hostname = av_strdup(hostname);
728 if (!s->hostname || !s->user || (tok_pass && !s->password)) {
729 return AVERROR(ENOMEM);
730 }
731
732 if (s->server_control_port < 0 || s->server_control_port > 65535)
733 s->server_control_port = 21;
734
735 if ((err = ftp_connect_control_connection(h)) < 0)
736 return err;
737
738 if ((err = ftp_current_dir(s)) < 0)
739 return err;
740
741 newpath = av_append_path_component(s->path, path);
742 if (!newpath)
743 return AVERROR(ENOMEM);
744 av_free(s->path);
745 s->path = newpath;
746
747 return 0;
748 }
749
750 static int ftp_open(URLContext *h, const char *url, int flags)
751 {
752 FTPContext *s = h->priv_data;
753 int err;
754
755 ff_dlog(h, "ftp protocol open\n");
756
757 if ((err = ftp_connect(h, url)) < 0)
758 goto fail;
759
760 if (ftp_restart(s, 0) < 0) {
761 h->is_streamed = 1;
762 } else {
763 ftp_file_size(s);
764 if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
765 h->is_streamed = 1;
766 }
767
768 return 0;
769
770 fail:
771 av_log(h, AV_LOG_ERROR, "FTP open failed\n");
772 ftp_close(h);
773 return err;
774 }
775
776 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
777 {
778 FTPContext *s = h->priv_data;
779 int err;
780 int64_t new_pos;
781
782 ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
783
784 switch(whence) {
785 case AVSEEK_SIZE:
786 return s->filesize;
787 case SEEK_SET:
788 new_pos = pos;
789 break;
790 case SEEK_CUR:
791 new_pos = s->position + pos;
792 break;
793 case SEEK_END:
794 if (s->filesize < 0)
795 return AVERROR(EIO);
796 new_pos = s->filesize + pos;
797 break;
798 default:
799 return AVERROR(EINVAL);
800 }
801
802 if (h->is_streamed)
803 return AVERROR(EIO);
804
805 if (new_pos < 0) {
806 av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
807 return AVERROR(EINVAL);
808 }
809
810 if (new_pos != s->position) {
811 if ((err = ftp_abort(h)) < 0)
812 return err;
813 s->position = new_pos;
814 }
815 return new_pos;
816 }
817
818 static int ftp_read(URLContext *h, unsigned char *buf, int size)
819 {
820 FTPContext *s = h->priv_data;
821 int read, err, retry_done = 0;
822
823 ff_dlog(h, "ftp protocol read %d bytes\n", size);
824 retry:
825 if (s->state == ENDOFFILE)
826 return AVERROR_EOF;
827 if (s->state == DISCONNECTED) {
828 if ((err = ftp_connect_data_connection(h)) < 0)
829 return err;
830 }
831 if (s->state == READY) {
832 if ((err = ftp_retrieve(s)) < 0)
833 return err;
834 }
835 if (s->conn_data && s->state == DOWNLOADING) {
836 read = ffurl_read(s->conn_data, buf, size);
837 if (read >= 0) {
838 s->position += read;
839 s->filesize = FFMAX(s->filesize, s->position);
840 }
841 if (read == AVERROR_EOF) {
842 static const int retr_codes[] = {226, 250, 425, 426, 451, 0};
843 char *response = NULL;
844 err = ftp_status(s, &response, retr_codes);
845 if (err == 226) {
846 ftp_close_data_connection(s);
847 av_freep(&response);
848 s->state = ENDOFFILE;
849 return AVERROR_EOF;
850 }
851 /* 250 is not allowed, any other status means some kind of error */
852 av_log(h, AV_LOG_ERROR, "FTP transfer failed: %s\n", response ? response : (err < 0 ? av_err2str(err) : "?"));
853 av_freep(&response);
854 read = AVERROR(EIO);
855 }
856 if (read <= 0 && !h->is_streamed) {
857 /* Server closed connection. Probably due to inactivity */
858 av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
859 if ((err = ftp_abort(h)) < 0)
860 return err;
861 if (!retry_done) {
862 retry_done = 1;
863 goto retry;
864 }
865 }
866 return read;
867 }
868
869 av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
870 return AVERROR(EIO);
871 }
872
873 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
874 {
875 int err;
876 FTPContext *s = h->priv_data;
877 int written;
878
879 ff_dlog(h, "ftp protocol write %d bytes\n", size);
880
881 if (s->state == DISCONNECTED) {
882 if ((err = ftp_connect_data_connection(h)) < 0)
883 return err;
884 }
885 if (s->state == READY) {
886 if ((err = ftp_store(s)) < 0)
887 return err;
888 }
889 if (s->conn_data && s->state == UPLOADING) {
890 written = ffurl_write(s->conn_data, buf, size);
891 if (written > 0) {
892 s->position += written;
893 s->filesize = FFMAX(s->filesize, s->position);
894 }
895 return written;
896 }
897
898 av_log(h, AV_LOG_ERROR, "FTP write failed\n");
899 return AVERROR(EIO);
900 }
901
902 static int ftp_close(URLContext *h)
903 {
904 FTPContext *s = h->priv_data;
905
906 ff_dlog(h, "ftp protocol close\n");
907
908 ftp_close_both_connections(s);
909 av_freep(&s->user);
910 av_freep(&s->password);
911 av_freep(&s->hostname);
912 av_freep(&s->path);
913 av_freep(&s->features);
914
915 return 0;
916 }
917
918 static int ftp_get_file_handle(URLContext *h)
919 {
920 FTPContext *s = h->priv_data;
921
922 ff_dlog(h, "ftp protocol get_file_handle\n");
923
924 if (s->conn_data)
925 return ffurl_get_file_handle(s->conn_data);
926
927 return AVERROR(EIO);
928 }
929
930 static int ftp_shutdown(URLContext *h, int flags)
931 {
932 FTPContext *s = h->priv_data;
933
934 ff_dlog(h, "ftp protocol shutdown\n");
935
936 if (s->conn_data)
937 return ffurl_shutdown(s->conn_data, flags);
938
939 return AVERROR(EIO);
940 }
941
942 static int ftp_open_dir(URLContext *h)
943 {
944 FTPContext *s = h->priv_data;
945 int ret;
946
947 if ((ret = ftp_connect(h, h->filename)) < 0)
948 goto fail;
949 if ((ret = ftp_set_dir(s)) < 0)
950 goto fail;
951 if ((ret = ftp_connect_data_connection(h)) < 0)
952 goto fail;
953 if ((ret = ftp_list(s)) < 0)
954 goto fail;
955 s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
956 if (!s->dir_buffer) {
957 ret = AVERROR(ENOMEM);
958 goto fail;
959 }
960 s->dir_buffer[0] = 0;
961 if (s->conn_data && s->state == LISTING_DIR)
962 return 0;
963 fail:
964 ffurl_closep(&s->conn_control);
965 ffurl_closep(&s->conn_data);
966 return ret;
967 }
968
969 static int64_t ftp_parse_date(const char *date)
970 {
971 struct tm tv;
972 memset(&tv, 0, sizeof(struct tm));
973 av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
974 return INT64_C(1000000) * av_timegm(&tv);
975 }
976
977 static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
978 {
979 next->name = av_strdup(line);
980 return 0;
981 }
982
983 static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
984 {
985 char *fact, *value;
986 char *saveptr = NULL, *p = mlsd;
987 ff_dlog(NULL, "%s\n", mlsd);
988 while(fact = av_strtok(p, ";", &saveptr)) {
989 p = NULL;
990 if (fact[0] == ' ') {
991 next->name = av_strdup(&fact[1]);
992 continue;
993 }
994 fact = av_strtok(fact, "=", &value);
995 if (!fact)
996 continue;
997 if (!av_strcasecmp(fact, "type")) {
998 if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
999 return 1;
1000 if (!av_strcasecmp(value, "dir"))
1001 next->type = AVIO_ENTRY_DIRECTORY;
1002 else if (!av_strcasecmp(value, "file"))
1003 next->type = AVIO_ENTRY_FILE;
1004 else if (!av_strcasecmp(value, "OS.unix=slink:"))
1005 next->type = AVIO_ENTRY_SYMBOLIC_LINK;
1006 } else if (!av_strcasecmp(fact, "modify")) {
1007 next->modification_timestamp = ftp_parse_date(value);
1008 } else if (!av_strcasecmp(fact, "UNIX.mode")) {
1009 next->filemode = strtoumax(value, NULL, 8);
1010 } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
1011 next->user_id = strtoumax(value, NULL, 10);
1012 else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
1013 next->group_id = strtoumax(value, NULL, 10);
1014 else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
1015 next->size = strtoll(value, NULL, 10);
1016 }
1017 return 0;
1018 }
1019
1020 /**
1021 * @return 0 on success, negative on error, positive on entry to discard.
1022 */
1023 static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
1024 {
1025 FTPContext *s = h->priv_data;
1026
1027 switch (s->listing_method) {
1028 case MLSD:
1029 return ftp_parse_entry_mlsd(line, next);
1030 case NLST:
1031 return ftp_parse_entry_nlst(line, next);
1032 case UNKNOWN_METHOD:
1033 default:
1034 return -1;
1035 }
1036 }
1037
1038 static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
1039 {
1040 FTPContext *s = h->priv_data;
1041 char *start, *found;
1042 int ret, retried;
1043
1044 do {
1045 retried = 0;
1046 start = s->dir_buffer + s->dir_buffer_offset;
1047 while (!(found = strstr(start, "\n"))) {
1048 if (retried)
1049 return AVERROR(EIO);
1050 s->dir_buffer_size -= s->dir_buffer_offset;
1051 s->dir_buffer_offset = 0;
1052 if (s->dir_buffer_size)
1053 memmove(s->dir_buffer, start, s->dir_buffer_size);
1054 ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
1055 if (ret < 0)
1056 return ret;
1057 if (!ret) {
1058 *next = NULL;
1059 return 0;
1060 }
1061 s->dir_buffer_size += ret;
1062 s->dir_buffer[s->dir_buffer_size] = 0;
1063 start = s->dir_buffer;
1064 retried = 1;
1065 }
1066 s->dir_buffer_offset += (found + 1 - start);
1067 found[0] = 0;
1068 if (found > start && found[-1] == '\r')
1069 found[-1] = 0;
1070
1071 *next = ff_alloc_dir_entry();
1072 if (!*next)
1073 return AVERROR(ENOMEM);
1074 (*next)->utf8 = s->utf8;
1075 ret = ftp_parse_entry(h, start, *next);
1076 if (ret) {
1077 avio_free_directory_entry(next);
1078 if (ret < 0)
1079 return ret;
1080 }
1081 } while (ret > 0);
1082 return 0;
1083 }
1084
1085 static int ftp_close_dir(URLContext *h)
1086 {
1087 FTPContext *s = h->priv_data;
1088 av_freep(&s->dir_buffer);
1089 ffurl_closep(&s->conn_control);
1090 ffurl_closep(&s->conn_data);
1091 return 0;
1092 }
1093
1094 static int ftp_delete(URLContext *h)
1095 {
1096 FTPContext *s = h->priv_data;
1097 char command[MAX_URL_SIZE];
1098 static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
1099 static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
1100 int ret;
1101
1102 if ((ret = ftp_connect(h, h->filename)) < 0)
1103 goto cleanup;
1104
1105 ret = snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
1106 if (ret >= sizeof(command)) {
1107 ret = AVERROR(ENOSYS);
1108 goto cleanup;
1109 }
1110
1111 if (ftp_send_command(s, command, del_codes, NULL) == 250) {
1112 ret = 0;
1113 goto cleanup;
1114 }
1115
1116 ret = snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
1117 if (ret >= sizeof(command)) {
1118 ret = AVERROR(ENOSYS);
1119 goto cleanup;
1120 }
1121
1122 if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
1123 ret = 0;
1124 else
1125 ret = AVERROR(EIO);
1126
1127 cleanup:
1128 ftp_close(h);
1129 return ret;
1130 }
1131
1132 static int ftp_move(URLContext *h_src, URLContext *h_dst)
1133 {
1134 FTPContext *s = h_src->priv_data;
1135 char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
1136 static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
1137 static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
1138 int ret;
1139
1140 if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
1141 goto cleanup;
1142
1143 ret = snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
1144 if (ret >= sizeof(command)) {
1145 ret = AVERROR(ENOSYS);
1146 goto cleanup;
1147 }
1148
1149 if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
1150 ret = AVERROR(EIO);
1151 goto cleanup;
1152 }
1153
1154 av_url_split(0, 0, 0, 0, 0, 0, 0,
1155 path, sizeof(path),
1156 h_dst->filename);
1157 ret = snprintf(command, sizeof(command), "RNTO %s\r\n", path);
1158 if (ret >= sizeof(command)) {
1159 ret = AVERROR(ENOSYS);
1160 goto cleanup;
1161 }
1162
1163 if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
1164 ret = 0;
1165 else
1166 ret = AVERROR(EIO);
1167
1168 cleanup:
1169 ftp_close(h_src);
1170 return ret;
1171 }
1172
1173 const URLProtocol ff_ftp_protocol = {
1174 .name = "ftp",
1175 .url_open = ftp_open,
1176 .url_read = ftp_read,
1177 .url_write = ftp_write,
1178 .url_seek = ftp_seek,
1179 .url_close = ftp_close,
1180 .url_get_file_handle = ftp_get_file_handle,
1181 .url_shutdown = ftp_shutdown,
1182 .priv_data_size = sizeof(FTPContext),
1183 .priv_data_class = &ftp_context_class,
1184 .url_open_dir = ftp_open_dir,
1185 .url_read_dir = ftp_read_dir,
1186 .url_close_dir = ftp_close_dir,
1187 .url_delete = ftp_delete,
1188 .url_move = ftp_move,
1189 .flags = URL_PROTOCOL_FLAG_NETWORK,
1190 .default_whitelist = "tcp",
1191 };
1192