FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/ftp.c
Date: 2026-10-31 18:41:55
Exec Total Coverage
Lines: 0 637 0.0%
Functions: 0 42 0.0%
Branches: 0 440 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, 125, 500, 0}; /* 500 is incorrect code */
517 ✗ int resp_code = ftp_send_command(s, command, mlsd_codes, NULL);
518
519 ✗ if (resp_code != 150 && resp_code != 125)
520 ✗ return AVERROR(ENOSYS);
521 ✗ s->listing_method = MLSD;
522 ✗ return 0;
523 }
524
525 ✗ static int ftp_list_nlst(FTPContext *s)
526 {
527 static const char *command = "NLST\r\n";
528 static const int nlst_codes[] = {226, 425, 426, 451, 450, 550, 0};
529
530 ✗ if (ftp_send_command(s, command, nlst_codes, NULL) != 226)
531 ✗ return AVERROR(ENOSYS);
532 ✗ s->listing_method = NLST;
533 ✗ return 0;
534 }
535
536 ✗ static int ftp_list(FTPContext *s)
537 {
538 int ret;
539 ✗ s->state = LISTING_DIR;
540
541 ✗ if ((ret = ftp_list_mlsd(s)) < 0)
542 ✗ ret = ftp_list_nlst(s);
543
544 ✗ return ret;
545 }
546
547 ✗ static int ftp_has_feature(FTPContext *s, const char *feature_name)
548 {
549 ✗ if (!s->features)
550 ✗ return 0;
551
552 ✗ return av_stristr(s->features, feature_name) != NULL;
553 }
554
555 ✗ static int ftp_features(FTPContext *s)
556 {
557 static const char *feat_command = "FEAT\r\n";
558 static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
559 static const int feat_codes[] = {211, 0};
560 static const int opts_codes[] = {200, 202, 451, 0};
561
562 ✗ av_freep(&s->features);
563 ✗ if (ftp_send_command(s, feat_command, feat_codes, &s->features) != 211) {
564 ✗ av_freep(&s->features);
565 }
566
567 ✗ if (ftp_has_feature(s, "UTF8")) {
568 ✗ int ret = ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
569 ✗ if (ret == 200 || ret == 202)
570 ✗ s->utf8 = 1;
571 }
572
573 ✗ return 0;
574 }
575
576 ✗ static int ftp_connect_control_connection(URLContext *h)
577 {
578 ✗ char buf[CONTROL_BUFFER_SIZE], *response = NULL;
579 int err;
580 ✗ AVDictionary *opts = NULL;
581 ✗ FTPContext *s = h->priv_data;
582 static const int connect_codes[] = {220, 0};
583
584 ✗ if (!s->conn_control) {
585 ✗ ff_url_join(buf, sizeof(buf), "tcp", NULL,
586 ✗ s->hostname, s->server_control_port, NULL);
587 ✗ if (s->rw_timeout != -1) {
588 ✗ av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
589 } /* if option is not given, don't pass it and let tcp use its own default */
590 ✗ err = ffurl_open_whitelist(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
591 ✗ &h->interrupt_callback, &opts,
592 h->protocol_whitelist, h->protocol_blacklist, h);
593 ✗ av_dict_free(&opts);
594 ✗ if (err < 0) {
595 ✗ av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
596 ✗ return err;
597 }
598
599 /* check if server is ready */
600 ✗ if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
601 ✗ av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
602 ✗ return AVERROR(EACCES);
603 }
604
605 ✗ if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
606 ✗ 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.");
607 }
608 ✗ av_free(response);
609
610 ✗ if ((err = ftp_auth(s)) < 0) {
611 ✗ av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
612 ✗ return err;
613 }
614
615 ✗ if ((err = ftp_type(s)) < 0) {
616 ✗ av_log(h, AV_LOG_ERROR, "Set content type failed\n");
617 ✗ return err;
618 }
619
620 ✗ ftp_features(s);
621 }
622 ✗ return 0;
623 }
624
625 ✗ static int ftp_connect_data_connection(URLContext *h)
626 {
627 int err;
628 char buf[CONTROL_BUFFER_SIZE];
629 ✗ AVDictionary *opts = NULL;
630 ✗ FTPContext *s = h->priv_data;
631
632 ✗ if (!s->conn_data) {
633 /* Enter passive mode */
634 ✗ if (ftp_passive_mode_epsv(s) < 0) {
635 /* Use PASV as fallback */
636 ✗ if ((err = ftp_passive_mode(s)) < 0)
637 ✗ return err;
638 }
639 /* Open data connection */
640 ✗ ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
641 ✗ if (s->rw_timeout != -1) {
642 ✗ av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
643 } /* if option is not given, don't pass it and let tcp use its own default */
644 ✗ err = ffurl_open_whitelist(&s->conn_data, buf, h->flags,
645 ✗ &h->interrupt_callback, &opts,
646 h->protocol_whitelist, h->protocol_blacklist, h);
647 ✗ av_dict_free(&opts);
648 ✗ if (err < 0)
649 ✗ return err;
650
651 ✗ if (s->position)
652 ✗ if ((err = ftp_restart(s, s->position)) < 0)
653 ✗ return err;
654 }
655 ✗ s->state = READY;
656 ✗ return 0;
657 }
658
659 ✗ static int ftp_abort(URLContext *h)
660 {
661 static const char *command = "ABOR\r\n";
662 int err;
663 static const int abor_codes[] = {225, 226, 0};
664 ✗ FTPContext *s = h->priv_data;
665
666 /* According to RCF 959:
667 "ABOR command tells the server to abort the previous FTP
668 service command and any associated transfer of data."
669
670 There are FTP server implementations that don't response
671 to any commands during data transfer in passive mode (including ABOR).
672
673 This implementation closes data connection by force.
674 */
675
676 ✗ if (ftp_send_command(s, command, NULL, NULL) < 0) {
677 ✗ ftp_close_both_connections(s);
678 ✗ if ((err = ftp_connect_control_connection(h)) < 0) {
679 ✗ av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
680 ✗ return err;
681 }
682 } else {
683 ✗ ftp_close_data_connection(s);
684 ✗ if (ftp_status(s, NULL, abor_codes) < 225) {
685 /* wu-ftpd also closes control connection after data connection closing */
686 ✗ ffurl_closep(&s->conn_control);
687 ✗ if ((err = ftp_connect_control_connection(h)) < 0) {
688 ✗ av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
689 ✗ return err;
690 }
691 }
692 }
693
694 ✗ return 0;
695 }
696
697 ✗ static int ftp_connect(URLContext *h, const char *url)
698 {
699 char proto[10], path[MAX_URL_SIZE], credentials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
700 ✗ const char *tok_user = NULL, *tok_pass = NULL;
701 ✗ char *newpath = NULL;
702 int err;
703 ✗ FTPContext *s = h->priv_data;
704
705 ✗ s->state = DISCONNECTED;
706 ✗ s->listing_method = UNKNOWN_METHOD;
707 ✗ s->filesize = -1;
708 ✗ s->position = 0;
709 ✗ s->features = NULL;
710
711 ✗ av_url_split(proto, sizeof(proto),
712 credentials, sizeof(credentials),
713 hostname, sizeof(hostname),
714 &s->server_control_port,
715 path, sizeof(path),
716 url);
717
718 ✗ if (!*credentials) {
719 ✗ if (!s->option_user) {
720 ✗ tok_user = "anonymous";
721 ✗ tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
722 } else {
723 ✗ tok_user = s->option_user;
724 ✗ tok_pass = s->option_password;
725 }
726 ✗ s->user = av_strdup(tok_user);
727 ✗ s->password = av_strdup(tok_pass);
728 } else {
729 ✗ char *pass = strchr(credentials, ':');
730 ✗ if (pass) {
731 ✗ *pass++ = '\0';
732 ✗ tok_pass = pass;
733 ✗ s->password = ff_urldecode(pass, 0);
734 } else {
735 ✗ tok_pass = s->option_password;
736 ✗ s->password = av_strdup(tok_pass);
737 }
738 ✗ s->user = ff_urldecode(credentials, 0);
739 }
740 ✗ s->hostname = av_strdup(hostname);
741 ✗ if (!s->hostname || !s->user || (tok_pass && !s->password)) {
742 ✗ return AVERROR(ENOMEM);
743 }
744
745 ✗ if (s->server_control_port < 0 || s->server_control_port > 65535)
746 ✗ s->server_control_port = 21;
747
748 ✗ if ((err = ftp_connect_control_connection(h)) < 0)
749 ✗ return err;
750
751 ✗ if ((err = ftp_current_dir(s)) < 0)
752 ✗ return err;
753
754 ✗ newpath = av_append_path_component(s->path, path);
755 ✗ if (!newpath)
756 ✗ return AVERROR(ENOMEM);
757 ✗ av_free(s->path);
758 ✗ s->path = newpath;
759
760 ✗ if (is_bad_string(s->path)) {
761 ✗ av_log(h, AV_LOG_ERROR, "Path contains non-printable characters\n");
762 ✗ return AVERROR(EINVAL);
763 }
764
765 ✗ return 0;
766 }
767
768 ✗ static int ftp_open(URLContext *h, const char *url, int flags)
769 {
770 ✗ FTPContext *s = h->priv_data;
771 int err;
772
773 ff_dlog(h, "ftp protocol open\n");
774
775 ✗ if ((err = ftp_connect(h, url)) < 0)
776 ✗ goto fail;
777
778 ✗ if (ftp_restart(s, 0) < 0) {
779 ✗ h->is_streamed = 1;
780 } else {
781 ✗ ftp_file_size(s);
782 ✗ if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
783 ✗ h->is_streamed = 1;
784 }
785
786 ✗ return 0;
787
788 ✗ fail:
789 ✗ av_log(h, AV_LOG_ERROR, "FTP open failed\n");
790 ✗ ftp_close(h);
791 ✗ return err;
792 }
793
794 ✗ static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
795 {
796 ✗ FTPContext *s = h->priv_data;
797 int err;
798 int64_t new_pos;
799
800 ff_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
801
802 ✗ switch(whence) {
803 ✗ case AVSEEK_SIZE:
804 ✗ return s->filesize;
805 ✗ case SEEK_SET:
806 ✗ new_pos = pos;
807 ✗ break;
808 ✗ case SEEK_CUR:
809 ✗ new_pos = s->position + pos;
810 ✗ break;
811 ✗ case SEEK_END:
812 ✗ if (s->filesize < 0)
813 ✗ return AVERROR(EIO);
814 ✗ new_pos = s->filesize + pos;
815 ✗ break;
816 ✗ default:
817 ✗ return AVERROR(EINVAL);
818 }
819
820 ✗ if (h->is_streamed)
821 ✗ return AVERROR(EIO);
822
823 ✗ if (new_pos < 0) {
824 ✗ av_log(h, AV_LOG_ERROR, "Seeking to negative position.\n");
825 ✗ return AVERROR(EINVAL);
826 }
827
828 ✗ if (new_pos != s->position) {
829 ✗ if ((err = ftp_abort(h)) < 0)
830 ✗ return err;
831 ✗ s->position = new_pos;
832 }
833 ✗ return new_pos;
834 }
835
836 ✗ static int ftp_read(URLContext *h, unsigned char *buf, int size)
837 {
838 ✗ FTPContext *s = h->priv_data;
839 ✗ int read, err, retry_done = 0;
840
841 ff_dlog(h, "ftp protocol read %d bytes\n", size);
842 ✗ retry:
843 ✗ if (s->state == ENDOFFILE)
844 ✗ return AVERROR_EOF;
845 ✗ if (s->state == DISCONNECTED) {
846 ✗ if ((err = ftp_connect_data_connection(h)) < 0)
847 ✗ return err;
848 }
849 ✗ if (s->state == READY) {
850 ✗ if ((err = ftp_retrieve(s)) < 0)
851 ✗ return err;
852 }
853 ✗ if (s->conn_data && s->state == DOWNLOADING) {
854 ✗ read = ffurl_read(s->conn_data, buf, size);
855 ✗ if (read >= 0) {
856 ✗ s->position += read;
857 ✗ s->filesize = FFMAX(s->filesize, s->position);
858 }
859 ✗ if (read == AVERROR_EOF) {
860 static const int retr_codes[] = {226, 250, 425, 426, 451, 0};
861 ✗ char *response = NULL;
862 ✗ err = ftp_status(s, &response, retr_codes);
863 ✗ if (err == 226) {
864 ✗ ftp_close_data_connection(s);
865 ✗ av_freep(&response);
866 ✗ s->state = ENDOFFILE;
867 ✗ return AVERROR_EOF;
868 }
869 /* 250 is not allowed, any other status means some kind of error */
870 ✗ av_log(h, AV_LOG_ERROR, "FTP transfer failed: %s\n", response ? response : (err < 0 ? av_err2str(err) : "?"));
871 ✗ av_freep(&response);
872 ✗ read = AVERROR(EIO);
873 }
874 ✗ if (read <= 0 && !h->is_streamed) {
875 /* Server closed connection. Probably due to inactivity */
876 ✗ av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
877 ✗ if ((err = ftp_abort(h)) < 0)
878 ✗ return err;
879 ✗ if (!retry_done) {
880 ✗ retry_done = 1;
881 ✗ goto retry;
882 }
883 }
884 ✗ return read;
885 }
886
887 ✗ av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
888 ✗ return AVERROR(EIO);
889 }
890
891 ✗ static int ftp_write(URLContext *h, const unsigned char *buf, int size)
892 {
893 int err;
894 ✗ FTPContext *s = h->priv_data;
895 int written;
896
897 ff_dlog(h, "ftp protocol write %d bytes\n", size);
898
899 ✗ if (s->state == DISCONNECTED) {
900 ✗ if ((err = ftp_connect_data_connection(h)) < 0)
901 ✗ return err;
902 }
903 ✗ if (s->state == READY) {
904 ✗ if ((err = ftp_store(s)) < 0)
905 ✗ return err;
906 }
907 ✗ if (s->conn_data && s->state == UPLOADING) {
908 ✗ written = ffurl_write(s->conn_data, buf, size);
909 ✗ if (written > 0) {
910 ✗ s->position += written;
911 ✗ s->filesize = FFMAX(s->filesize, s->position);
912 }
913 ✗ return written;
914 }
915
916 ✗ av_log(h, AV_LOG_ERROR, "FTP write failed\n");
917 ✗ return AVERROR(EIO);
918 }
919
920 ✗ static int ftp_close(URLContext *h)
921 {
922 ✗ FTPContext *s = h->priv_data;
923
924 ff_dlog(h, "ftp protocol close\n");
925
926 ✗ ftp_close_both_connections(s);
927 ✗ av_freep(&s->user);
928 ✗ av_freep(&s->password);
929 ✗ av_freep(&s->hostname);
930 ✗ av_freep(&s->path);
931 ✗ av_freep(&s->features);
932
933 ✗ return 0;
934 }
935
936 ✗ static int ftp_get_file_handle(URLContext *h)
937 {
938 ✗ FTPContext *s = h->priv_data;
939
940 ff_dlog(h, "ftp protocol get_file_handle\n");
941
942 ✗ if (s->conn_data)
943 ✗ return ffurl_get_file_handle(s->conn_data);
944
945 ✗ return AVERROR(EIO);
946 }
947
948 ✗ static int ftp_shutdown(URLContext *h, int flags)
949 {
950 ✗ FTPContext *s = h->priv_data;
951
952 ff_dlog(h, "ftp protocol shutdown\n");
953
954 ✗ if (s->conn_data)
955 ✗ return ffurl_shutdown(s->conn_data, flags);
956
957 ✗ return AVERROR(EIO);
958 }
959
960 ✗ static int ftp_open_dir(URLContext *h)
961 {
962 ✗ FTPContext *s = h->priv_data;
963 int ret;
964
965 ✗ if ((ret = ftp_connect(h, h->filename)) < 0)
966 ✗ goto fail;
967 ✗ if ((ret = ftp_set_dir(s)) < 0)
968 ✗ goto fail;
969 ✗ if ((ret = ftp_connect_data_connection(h)) < 0)
970 ✗ goto fail;
971 ✗ if ((ret = ftp_list(s)) < 0)
972 ✗ goto fail;
973 ✗ s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
974 ✗ if (!s->dir_buffer) {
975 ✗ ret = AVERROR(ENOMEM);
976 ✗ goto fail;
977 }
978 ✗ s->dir_buffer[0] = 0;
979 ✗ if (s->conn_data && s->state == LISTING_DIR)
980 ✗ return 0;
981 ✗ fail:
982 ✗ ffurl_closep(&s->conn_control);
983 ✗ ffurl_closep(&s->conn_data);
984 ✗ return ret;
985 }
986
987 ✗ static int64_t ftp_parse_date(const char *date)
988 {
989 struct tm tv;
990 ✗ memset(&tv, 0, sizeof(struct tm));
991 ✗ av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
992 ✗ return INT64_C(1000000) * av_timegm(&tv);
993 }
994
995 ✗ static int ftp_parse_entry_nlst(char *line, AVIODirEntry *next)
996 {
997 ✗ next->name = av_strdup(line);
998 ✗ return next->name ? 0 : AVERROR(ENOMEM);
999 }
1000
1001 ✗ static int ftp_parse_entry_mlsd(char *mlsd, AVIODirEntry *next)
1002 {
1003 char *fact, *value;
1004 ✗ char *saveptr = NULL, *p = mlsd;
1005 ff_dlog(NULL, "%s\n", mlsd);
1006 ✗ while(fact = av_strtok(p, ";", &saveptr)) {
1007 ✗ p = NULL;
1008 ✗ if (fact[0] == ' ') {
1009 ✗ if (!(next->name = av_strdup(&fact[1])))
1010 ✗ return AVERROR(ENOMEM);
1011 ✗ continue;
1012 }
1013 ✗ fact = av_strtok(fact, "=", &value);
1014 ✗ if (!fact)
1015 ✗ continue;
1016 ✗ if (!av_strcasecmp(fact, "type")) {
1017 ✗ if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
1018 ✗ return 1;
1019 ✗ if (!av_strcasecmp(value, "dir"))
1020 ✗ next->type = AVIO_ENTRY_DIRECTORY;
1021 ✗ else if (!av_strcasecmp(value, "file"))
1022 ✗ next->type = AVIO_ENTRY_FILE;
1023 ✗ else if (!av_strcasecmp(value, "OS.unix=slink:"))
1024 ✗ next->type = AVIO_ENTRY_SYMBOLIC_LINK;
1025 ✗ } else if (!av_strcasecmp(fact, "modify")) {
1026 ✗ next->modification_timestamp = ftp_parse_date(value);
1027 ✗ } else if (!av_strcasecmp(fact, "UNIX.mode")) {
1028 ✗ next->filemode = strtoumax(value, NULL, 8);
1029 ✗ } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
1030 ✗ next->user_id = strtoumax(value, NULL, 10);
1031 ✗ else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
1032 ✗ next->group_id = strtoumax(value, NULL, 10);
1033 ✗ else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
1034 ✗ next->size = strtoll(value, NULL, 10);
1035 }
1036 ✗ return 0;
1037 }
1038
1039 /**
1040 * @return 0 on success, negative on error, positive on entry to discard.
1041 */
1042 ✗ static int ftp_parse_entry(URLContext *h, char *line, AVIODirEntry *next)
1043 {
1044 ✗ FTPContext *s = h->priv_data;
1045
1046 ✗ switch (s->listing_method) {
1047 ✗ case MLSD:
1048 ✗ return ftp_parse_entry_mlsd(line, next);
1049 ✗ case NLST:
1050 ✗ return ftp_parse_entry_nlst(line, next);
1051 ✗ case UNKNOWN_METHOD:
1052 default:
1053 ✗ return -1;
1054 }
1055 }
1056
1057 ✗ static int ftp_read_dir(URLContext *h, AVIODirEntry **next)
1058 {
1059 ✗ FTPContext *s = h->priv_data;
1060 char *start, *found;
1061 int ret, retried;
1062
1063 do {
1064 ✗ retried = 0;
1065 ✗ start = s->dir_buffer + s->dir_buffer_offset;
1066 ✗ while (!(found = strstr(start, "\n"))) {
1067 ✗ if (retried)
1068 ✗ return AVERROR(EIO);
1069 ✗ s->dir_buffer_size -= s->dir_buffer_offset;
1070 ✗ s->dir_buffer_offset = 0;
1071 ✗ if (s->dir_buffer_size)
1072 ✗ memmove(s->dir_buffer, start, s->dir_buffer_size);
1073 ✗ ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
1074 ✗ if (ret < 0 && ret != AVERROR_EOF)
1075 ✗ return ret;
1076 ✗ if (ret <= 0) {
1077 ✗ *next = NULL;
1078 ✗ return 0;
1079 }
1080 ✗ s->dir_buffer_size += ret;
1081 ✗ s->dir_buffer[s->dir_buffer_size] = 0;
1082 ✗ start = s->dir_buffer;
1083 ✗ retried = 1;
1084 }
1085 ✗ s->dir_buffer_offset += (found + 1 - start);
1086 ✗ found[0] = 0;
1087 ✗ if (found > start && found[-1] == '\r')
1088 ✗ found[-1] = 0;
1089
1090 ✗ *next = ff_alloc_dir_entry();
1091 ✗ if (!*next)
1092 ✗ return AVERROR(ENOMEM);
1093 ✗ (*next)->utf8 = s->utf8;
1094 ✗ ret = ftp_parse_entry(h, start, *next);
1095 ✗ if (ret) {
1096 ✗ avio_free_directory_entry(next);
1097 ✗ if (ret < 0)
1098 ✗ return ret;
1099 }
1100 ✗ } while (ret > 0);
1101 ✗ return 0;
1102 }
1103
1104 ✗ static int ftp_close_dir(URLContext *h)
1105 {
1106 ✗ FTPContext *s = h->priv_data;
1107 ✗ av_freep(&s->dir_buffer);
1108 ✗ ffurl_closep(&s->conn_control);
1109 ✗ ffurl_closep(&s->conn_data);
1110 ✗ return 0;
1111 }
1112
1113 ✗ static int ftp_delete(URLContext *h)
1114 {
1115 ✗ FTPContext *s = h->priv_data;
1116 char command[MAX_URL_SIZE];
1117 static const int del_codes[] = {250, 421, 450, 500, 501, 502, 530, 550, 0};
1118 static const int rmd_codes[] = {250, 421, 500, 501, 502, 530, 550, 0};
1119 int ret;
1120
1121 ✗ if ((ret = ftp_connect(h, h->filename)) < 0)
1122 ✗ goto cleanup;
1123
1124 ✗ ret = snprintf(command, sizeof(command), "DELE %s\r\n", s->path);
1125 ✗ if (ret >= sizeof(command)) {
1126 ✗ ret = AVERROR(ENOSYS);
1127 ✗ goto cleanup;
1128 }
1129
1130 ✗ if (ftp_send_command(s, command, del_codes, NULL) == 250) {
1131 ✗ ret = 0;
1132 ✗ goto cleanup;
1133 }
1134
1135 ✗ ret = snprintf(command, sizeof(command), "RMD %s\r\n", s->path);
1136 ✗ if (ret >= sizeof(command)) {
1137 ✗ ret = AVERROR(ENOSYS);
1138 ✗ goto cleanup;
1139 }
1140
1141 ✗ if (ftp_send_command(s, command, rmd_codes, NULL) == 250)
1142 ✗ ret = 0;
1143 else
1144 ✗ ret = AVERROR(EIO);
1145
1146 ✗ cleanup:
1147 ✗ ftp_close(h);
1148 ✗ return ret;
1149 }
1150
1151 ✗ static int ftp_move(URLContext *h_src, URLContext *h_dst)
1152 {
1153 ✗ FTPContext *s = h_src->priv_data;
1154 char command[MAX_URL_SIZE], path[MAX_URL_SIZE];
1155 static const int rnfr_codes[] = {350, 421, 450, 500, 501, 502, 503, 530, 0};
1156 static const int rnto_codes[] = {250, 421, 500, 501, 502, 503, 530, 532, 553, 0};
1157 int ret;
1158
1159 ✗ if ((ret = ftp_connect(h_src, h_src->filename)) < 0)
1160 ✗ goto cleanup;
1161
1162 ✗ ret = snprintf(command, sizeof(command), "RNFR %s\r\n", s->path);
1163 ✗ if (ret >= sizeof(command)) {
1164 ✗ ret = AVERROR(ENOSYS);
1165 ✗ goto cleanup;
1166 }
1167
1168 ✗ if (ftp_send_command(s, command, rnfr_codes, NULL) != 350) {
1169 ✗ ret = AVERROR(EIO);
1170 ✗ goto cleanup;
1171 }
1172
1173 ✗ av_url_split(0, 0, 0, 0, 0, 0, 0,
1174 path, sizeof(path),
1175 ✗ h_dst->filename);
1176 ✗ if (is_bad_string(path)) {
1177 ✗ ret = AVERROR(EINVAL);
1178 ✗ goto cleanup;
1179 }
1180 ✗ ret = snprintf(command, sizeof(command), "RNTO %s\r\n", path);
1181 ✗ if (ret >= sizeof(command)) {
1182 ✗ ret = AVERROR(ENOSYS);
1183 ✗ goto cleanup;
1184 }
1185
1186 ✗ if (ftp_send_command(s, command, rnto_codes, NULL) == 250)
1187 ✗ ret = 0;
1188 else
1189 ✗ ret = AVERROR(EIO);
1190
1191 ✗ cleanup:
1192 ✗ ftp_close(h_src);
1193 ✗ return ret;
1194 }
1195
1196 const URLProtocol ff_ftp_protocol = {
1197 .name = "ftp",
1198 .url_open = ftp_open,
1199 .url_read = ftp_read,
1200 .url_write = ftp_write,
1201 .url_seek = ftp_seek,
1202 .url_close = ftp_close,
1203 .url_get_file_handle = ftp_get_file_handle,
1204 .url_shutdown = ftp_shutdown,
1205 .priv_data_size = sizeof(FTPContext),
1206 .priv_data_class = &ftp_context_class,
1207 .url_open_dir = ftp_open_dir,
1208 .url_read_dir = ftp_read_dir,
1209 .url_close_dir = ftp_close_dir,
1210 .url_delete = ftp_delete,
1211 .url_move = ftp_move,
1212 .flags = URL_PROTOCOL_FLAG_NETWORK,
1213 .default_whitelist = "tcp",
1214 };
1215