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