]> git.sesse.net Git - ffmpeg/blob - libavformat/ftp.c
Merge commit '512f3ffe9b4bb86767c2b1176554407c75fe1a5c'
[ffmpeg] / libavformat / ftp.c
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
31 typedef enum {
32     UNKNOWN,
33     READY,
34     DOWNLOADING,
35     UPLOADING,
36     DISCONNECTED
37 } FTPState;
38
39 typedef 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
61 static 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
68 static 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
75 static 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
92 static 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  */
120 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
121 {
122     int err, i, dash = 0, result = 0, code_found = 0;
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         if (strlen(buf) < 4)
139             continue;
140
141         err = 0;
142         for (i = 0; i < 3; ++i) {
143             if (buf[i] < '0' || buf[i] > '9')
144                 continue;
145             err *= 10;
146             err += buf[i] - '0';
147         }
148         dash = !!(buf[3] == '-');
149
150         for (i = 0; response_codes[i]; ++i) {
151             if (err == response_codes[i]) {
152                 if (line)
153                     av_bprintf(&line_buffer, "%s", buf);
154                 code_found = 1;
155                 result = err;
156                 break;
157             }
158         }
159     }
160
161     if (line)
162         av_bprint_finalize(&line_buffer, line);
163     return result;
164 }
165
166 static int ftp_send_command(FTPContext *s, const char *command,
167                             const int response_codes[], char **response)
168 {
169     int err;
170
171     if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
172         return err;
173     if (!err)
174         return -1;
175
176     /* return status */
177     if (response_codes) {
178         return ftp_status(s, response, response_codes);
179     }
180     return 0;
181 }
182
183 static void ftp_close_data_connection(FTPContext *s)
184 {
185     ffurl_closep(&s->conn_data);
186     s->position = 0;
187     s->state = DISCONNECTED;
188 }
189
190 static void ftp_close_both_connections(FTPContext *s)
191 {
192     ffurl_closep(&s->conn_control);
193     ftp_close_data_connection(s);
194 }
195
196 static int ftp_auth(FTPContext *s)
197 {
198     const char *user = NULL, *pass = NULL;
199     char *end = NULL, buf[CONTROL_BUFFER_SIZE], credencials[CREDENTIALS_BUFFER_SIZE];
200     int err;
201     static const int user_codes[] = {331, 230, 500, 530, 0}; /* 500, 530 are incorrect codes */
202     static const int pass_codes[] = {230, 503, 530, 0}; /* 503, 530 are incorrect codes */
203
204     /* Authentication may be repeated, original string has to be saved */
205     av_strlcpy(credencials, s->credencials, sizeof(credencials));
206
207     user = av_strtok(credencials, ":", &end);
208     pass = av_strtok(end, ":", &end);
209
210     if (!user) {
211         user = "anonymous";
212         pass = s->anonymous_password ? s->anonymous_password : "nopassword";
213     }
214
215     snprintf(buf, sizeof(buf), "USER %s\r\n", user);
216     err = ftp_send_command(s, buf, user_codes, NULL);
217     if (err == 331) {
218         if (pass) {
219             snprintf(buf, sizeof(buf), "PASS %s\r\n", pass);
220             err = ftp_send_command(s, buf, pass_codes, NULL);
221         } else
222             return AVERROR(EACCES);
223     }
224     if (err != 230)
225         return AVERROR(EACCES);
226
227     return 0;
228 }
229
230 static int ftp_passive_mode_epsv(FTPContext *s)
231 {
232     char *res = NULL, *start = NULL, *end = NULL;
233     int i;
234     static const char d = '|';
235     static const char *command = "EPSV\r\n";
236     static const int epsv_codes[] = {229, 500, 501, 0}; /* 500, 501 are incorrect codes */
237
238     if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
239         goto fail;
240
241     for (i = 0; res[i]; ++i) {
242         if (res[i] == '(') {
243             start = res + i + 1;
244         } else if (res[i] == ')') {
245             end = res + i;
246             break;
247         }
248     }
249     if (!start || !end)
250         goto fail;
251
252     *end = '\0';
253     if (strlen(start) < 5)
254         goto fail;
255     if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
256         goto fail;
257     start += 3;
258     end[-1] = '\0';
259
260     s->server_data_port = atoi(start);
261     av_dlog(s, "Server data port: %d\n", s->server_data_port);
262
263     av_free(res);
264     return 0;
265
266   fail:
267     av_free(res);
268     s->server_data_port = -1;
269     return AVERROR(ENOSYS);
270 }
271
272 static int ftp_passive_mode(FTPContext *s)
273 {
274     char *res = NULL, *start = NULL, *end = NULL;
275     int i;
276     static const char *command = "PASV\r\n";
277     static const int pasv_codes[] = {227, 501, 0}; /* 501 is incorrect code */
278
279     if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
280         goto fail;
281
282     for (i = 0; res[i]; ++i) {
283         if (res[i] == '(') {
284             start = res + i + 1;
285         } else if (res[i] == ')') {
286             end = res + i;
287             break;
288         }
289     }
290     if (!start || !end)
291         goto fail;
292
293     *end  = '\0';
294     /* skip ip */
295     if (!av_strtok(start, ",", &end)) goto fail;
296     if (!av_strtok(end, ",", &end)) goto fail;
297     if (!av_strtok(end, ",", &end)) goto fail;
298     if (!av_strtok(end, ",", &end)) goto fail;
299
300     /* parse port number */
301     start = av_strtok(end, ",", &end);
302     if (!start) goto fail;
303     s->server_data_port = atoi(start) * 256;
304     start = av_strtok(end, ",", &end);
305     if (!start) goto fail;
306     s->server_data_port += atoi(start);
307     av_dlog(s, "Server data port: %d\n", s->server_data_port);
308
309     av_free(res);
310     return 0;
311
312   fail:
313     av_free(res);
314     s->server_data_port = -1;
315     return AVERROR(EIO);
316 }
317
318 static int ftp_current_dir(FTPContext *s)
319 {
320     char *res = NULL, *start = NULL, *end = NULL;
321     int i;
322     static const char *command = "PWD\r\n";
323     static const int pwd_codes[] = {257, 0};
324
325     if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
326         goto fail;
327
328     for (i = 0; res[i]; ++i) {
329         if (res[i] == '"') {
330             if (!start) {
331                 start = res + i + 1;
332                 continue;
333             }
334             end = res + i;
335             break;
336         }
337     }
338
339     if (!end)
340         goto fail;
341
342     if (end > res && end[-1] == '/') {
343         end[-1] = '\0';
344     } else
345         *end = '\0';
346     av_strlcpy(s->path, start, sizeof(s->path));
347
348     av_free(res);
349     return 0;
350
351   fail:
352     av_free(res);
353     return AVERROR(EIO);
354 }
355
356 static int ftp_file_size(FTPContext *s)
357 {
358     char command[CONTROL_BUFFER_SIZE];
359     char *res = NULL;
360     static const int size_codes[] = {213, 501, 550, 0}; /* 501, 550 are incorrect codes */
361
362     snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
363     if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
364         s->filesize = strtoll(&res[4], NULL, 10);
365     } else {
366         s->filesize = -1;
367         av_free(res);
368         return AVERROR(EIO);
369     }
370
371     av_free(res);
372     return 0;
373 }
374
375 static int ftp_retrieve(FTPContext *s)
376 {
377     char command[CONTROL_BUFFER_SIZE];
378     static const int retr_codes[] = {150, 550, 554, 0}; /* 550, 554 are incorrect codes */
379
380     snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
381     if (ftp_send_command(s, command, retr_codes, NULL) != 150)
382         return AVERROR(EIO);
383
384     s->state = DOWNLOADING;
385
386     return 0;
387 }
388
389 static int ftp_store(FTPContext *s)
390 {
391     char command[CONTROL_BUFFER_SIZE];
392     static const int stor_codes[] = {150, 0};
393
394     snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
395     if (ftp_send_command(s, command, stor_codes, NULL) != 150)
396         return AVERROR(EIO);
397
398     s->state = UPLOADING;
399
400     return 0;
401 }
402
403 static int ftp_type(FTPContext *s)
404 {
405     static const char *command = "TYPE I\r\n";
406     static const int type_codes[] = {200, 500, 504, 0}; /* 500, 504 are incorrect codes */
407
408     if (ftp_send_command(s, command, type_codes, NULL) != 200)
409         return AVERROR(EIO);
410
411     return 0;
412 }
413
414 static int ftp_restart(FTPContext *s, int64_t pos)
415 {
416     char command[CONTROL_BUFFER_SIZE];
417     static const int rest_codes[] = {350, 500, 501, 0}; /* 500, 501 are incorrect codes */
418
419     snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
420     if (ftp_send_command(s, command, rest_codes, NULL) != 350)
421         return AVERROR(EIO);
422
423     return 0;
424 }
425
426 static int ftp_connect_control_connection(URLContext *h)
427 {
428     char buf[CONTROL_BUFFER_SIZE], opts_format[20], *response = NULL;
429     int err;
430     AVDictionary *opts = NULL;
431     FTPContext *s = h->priv_data;
432     static const int connect_codes[] = {220, 0};
433
434     if (!s->conn_control) {
435         ff_url_join(buf, sizeof(buf), "tcp", NULL,
436                     s->hostname, s->server_control_port, NULL);
437         if (s->rw_timeout != -1) {
438             snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
439             av_dict_set(&opts, "timeout", opts_format, 0);
440         } /* if option is not given, don't pass it and let tcp use its own default */
441         err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
442                          &h->interrupt_callback, &opts);
443         av_dict_free(&opts);
444         if (err < 0) {
445             av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
446             return err;
447         }
448
449         /* check if server is ready */
450         if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
451             av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
452             return AVERROR(EACCES);
453         }
454
455         if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
456             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.");
457         }
458         av_free(response);
459
460         if ((err = ftp_auth(s)) < 0) {
461             av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
462             return err;
463         }
464
465         if ((err = ftp_type(s)) < 0) {
466             av_log(h, AV_LOG_ERROR, "Set content type failed\n");
467             return err;
468         }
469     }
470     return 0;
471 }
472
473 static int ftp_connect_data_connection(URLContext *h)
474 {
475     int err;
476     char buf[CONTROL_BUFFER_SIZE], opts_format[20];
477     AVDictionary *opts = NULL;
478     FTPContext *s = h->priv_data;
479
480     if (!s->conn_data) {
481         /* Enter passive mode */
482         if (ftp_passive_mode_epsv(s) < 0) {
483             /* Use PASV as fallback */
484             if ((err = ftp_passive_mode(s)) < 0)
485                 return err;
486         }
487         /* Open data connection */
488         ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
489         if (s->rw_timeout != -1) {
490             snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
491             av_dict_set(&opts, "timeout", opts_format, 0);
492         } /* if option is not given, don't pass it and let tcp use its own default */
493         err = ffurl_open(&s->conn_data, buf, h->flags,
494                          &h->interrupt_callback, &opts);
495         av_dict_free(&opts);
496         if (err < 0)
497             return err;
498
499         if (s->position)
500             if ((err = ftp_restart(s, s->position)) < 0)
501                 return err;
502     }
503     s->state = READY;
504     return 0;
505 }
506
507 static int ftp_abort(URLContext *h)
508 {
509     static const char *command = "ABOR\r\n";
510     int err;
511     static const int abor_codes[] = {225, 226, 0};
512     FTPContext *s = h->priv_data;
513
514     /* According to RCF 959:
515        "ABOR command tells the server to abort the previous FTP
516        service command and any associated transfer of data."
517
518        There are FTP server implementations that don't response
519        to any commands during data transfer in passive mode (including ABOR).
520
521        This implementation closes data connection by force.
522     */
523
524     if (ftp_send_command(s, command, NULL, NULL) < 0) {
525         ftp_close_both_connections(s);
526         if ((err = ftp_connect_control_connection(h)) < 0) {
527             av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
528             return err;
529         }
530     } else {
531         ftp_close_data_connection(s);
532         if (ftp_status(s, NULL, abor_codes) < 225) {
533             /* wu-ftpd also closes control connection after data connection closing */
534             ffurl_closep(&s->conn_control);
535             if ((err = ftp_connect_control_connection(h)) < 0) {
536                 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
537                 return err;
538             }
539         }
540     }
541
542     return 0;
543 }
544
545 static int ftp_open(URLContext *h, const char *url, int flags)
546 {
547     char proto[10], path[MAX_URL_SIZE];
548     int err;
549     FTPContext *s = h->priv_data;
550
551     av_dlog(h, "ftp protocol open\n");
552
553     s->state = DISCONNECTED;
554     s->filesize = -1;
555     s->position = 0;
556
557     av_url_split(proto, sizeof(proto),
558                  s->credencials, sizeof(s->credencials),
559                  s->hostname, sizeof(s->hostname),
560                  &s->server_control_port,
561                  path, sizeof(path),
562                  url);
563
564     if (s->server_control_port < 0 || s->server_control_port > 65535)
565         s->server_control_port = 21;
566
567     if ((err = ftp_connect_control_connection(h)) < 0)
568         goto fail;
569
570     if ((err = ftp_current_dir(s)) < 0)
571         goto fail;
572     av_strlcat(s->path, path, sizeof(s->path));
573
574     if (ftp_restart(s, 0) < 0) {
575         h->is_streamed = 1;
576     } else {
577         if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
578             h->is_streamed = 1;
579         if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
580             h->is_streamed = 1;
581     }
582
583     return 0;
584
585   fail:
586     av_log(h, AV_LOG_ERROR, "FTP open failed\n");
587     ffurl_closep(&s->conn_control);
588     ffurl_closep(&s->conn_data);
589     return err;
590 }
591
592 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
593 {
594     FTPContext *s = h->priv_data;
595     int err;
596     int64_t new_pos, fake_pos;
597
598     av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
599
600     switch(whence) {
601     case AVSEEK_SIZE:
602         return s->filesize;
603     case SEEK_SET:
604         new_pos = pos;
605         break;
606     case SEEK_CUR:
607         new_pos = s->position + pos;
608         break;
609     case SEEK_END:
610         if (s->filesize < 0)
611             return AVERROR(EIO);
612         new_pos = s->filesize + pos;
613         break;
614     default:
615         return AVERROR(EINVAL);
616     }
617
618     if (h->is_streamed)
619         return AVERROR(EIO);
620
621     if (new_pos < 0) {
622         av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
623         return AVERROR(EINVAL);
624     }
625
626     fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
627     if (fake_pos != s->position) {
628         if ((err = ftp_abort(h)) < 0)
629             return err;
630         s->position = fake_pos;
631     }
632     return new_pos;
633 }
634
635 static int ftp_read(URLContext *h, unsigned char *buf, int size)
636 {
637     FTPContext *s = h->priv_data;
638     int read, err, retry_done = 0;
639
640     av_dlog(h, "ftp protocol read %d bytes\n", size);
641   retry:
642     if (s->state == DISCONNECTED) {
643         /* optimization */
644         if (s->position >= s->filesize)
645             return 0;
646         if ((err = ftp_connect_data_connection(h)) < 0)
647             return err;
648     }
649     if (s->state == READY) {
650         if (s->position >= s->filesize)
651             return 0;
652         if ((err = ftp_retrieve(s)) < 0)
653             return err;
654     }
655     if (s->conn_data && s->state == DOWNLOADING) {
656         read = ffurl_read(s->conn_data, buf, size);
657         if (read >= 0) {
658             s->position += read;
659             if (s->position >= s->filesize) {
660                 /* server will terminate, but keep current position to avoid madness */
661                 /* save position to restart from it */
662                 int64_t pos = s->position;
663                 if (ftp_abort(h) < 0) {
664                     s->position = pos;
665                     return AVERROR(EIO);
666                 }
667                 s->position = pos;
668             }
669         }
670         if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
671             /* Server closed connection. Probably due to inactivity */
672             int64_t pos = s->position;
673             av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
674             if ((err = ftp_abort(h)) < 0)
675                 return err;
676             if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
677                 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
678                 return err;
679             }
680             if (!retry_done) {
681                 retry_done = 1;
682                 goto retry;
683             }
684         }
685         return read;
686     }
687
688     av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
689     return AVERROR(EIO);
690 }
691
692 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
693 {
694     int err;
695     FTPContext *s = h->priv_data;
696     int written;
697
698     av_dlog(h, "ftp protocol write %d bytes\n", size);
699
700     if (s->state == DISCONNECTED) {
701         if ((err = ftp_connect_data_connection(h)) < 0)
702             return err;
703     }
704     if (s->state == READY) {
705         if ((err = ftp_store(s)) < 0)
706             return err;
707     }
708     if (s->conn_data && s->state == UPLOADING) {
709         written = ffurl_write(s->conn_data, buf, size);
710         if (written > 0) {
711             s->position += written;
712             s->filesize = FFMAX(s->filesize, s->position);
713         }
714         return written;
715     }
716
717     av_log(h, AV_LOG_ERROR, "FTP write failed\n");
718     return AVERROR(EIO);
719 }
720
721 static int ftp_close(URLContext *h)
722 {
723     av_dlog(h, "ftp protocol close\n");
724
725     ftp_close_both_connections(h->priv_data);
726
727     return 0;
728 }
729
730 static int ftp_get_file_handle(URLContext *h)
731 {
732     FTPContext *s = h->priv_data;
733
734     av_dlog(h, "ftp protocol get_file_handle\n");
735
736     if (s->conn_data)
737         return ffurl_get_file_handle(s->conn_data);
738
739     return AVERROR(EIO);
740 }
741
742 static int ftp_shutdown(URLContext *h, int flags)
743 {
744     FTPContext *s = h->priv_data;
745
746     av_dlog(h, "ftp protocol shutdown\n");
747
748     if (s->conn_data)
749         return ffurl_shutdown(s->conn_data, flags);
750
751     return AVERROR(EIO);
752 }
753
754 URLProtocol ff_ftp_protocol = {
755     .name                = "ftp",
756     .url_open            = ftp_open,
757     .url_read            = ftp_read,
758     .url_write           = ftp_write,
759     .url_seek            = ftp_seek,
760     .url_close           = ftp_close,
761     .url_get_file_handle = ftp_get_file_handle,
762     .url_shutdown        = ftp_shutdown,
763     .priv_data_size      = sizeof(FTPContext),
764     .priv_data_class     = &ftp_context_class,
765     .flags               = URL_PROTOCOL_FLAG_NETWORK,
766 };