FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/ftp.c
Date: 2026-09-28 11:37:26
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