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