FFmpeg coverage


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