]> git.sesse.net Git - ffmpeg/blob - libavformat/ftp.c
Merge remote-tracking branch 'qatar/master'
[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 <stdlib.h>
22 #include "libavutil/avstring.h"
23 #include "libavutil/time.h"
24 #include "avformat.h"
25 #include "internal.h"
26 #include "network.h"
27 #include "os_support.h"
28 #include "url.h"
29 #include "libavutil/opt.h"
30
31 #define CONTROL_BUFFER_SIZE 1024
32 #define CREDENTIALS_BUFFER_SIZE 128
33
34 typedef enum {
35     UNKNOWN,
36     READY,
37     DOWNLOADING,
38     UPLOADING,
39     DISCONNECTED
40 } FTPState;
41
42 typedef struct {
43     const AVClass *class;
44     URLContext *conn_control;                    /**< Control connection */
45     int conn_control_block_flag;                 /**< Controls block/unblock mode of data connection */
46     AVIOInterruptCB conn_control_interrupt_cb;   /**< Controls block/unblock mode of data connection */
47     URLContext *conn_data;                       /**< Data connection, NULL when not connected */
48     uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
49     uint8_t *control_buf_ptr, *control_buf_end;
50     int server_data_port;                        /**< Data connection port opened by server, -1 on error. */
51     int server_control_port;                     /**< Control connection port, default is 21 */
52     char hostname[512];                          /**< Server address. */
53     char credencials[CREDENTIALS_BUFFER_SIZE];   /**< Authentication data */
54     char path[MAX_URL_SIZE];                     /**< Path to resource on server. */
55     int64_t filesize;                            /**< Size of file on server, -1 on error. */
56     int64_t position;                            /**< Current position, calculated. */
57     int rw_timeout;                              /**< Network timeout. */
58     const char *anonymous_password;              /**< Password to be used for anonymous user. An email should be used. */
59     int write_seekable;                          /**< Control seekability, 0 = disable, 1 = enable. */
60     FTPState state;                              /**< State of data connection */
61 } FTPContext;
62
63 #define OFFSET(x) offsetof(FTPContext, x)
64 #define D AV_OPT_FLAG_DECODING_PARAM
65 #define E AV_OPT_FLAG_ENCODING_PARAM
66 static const AVOption options[] = {
67     {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
68     {"ftp-write-seekable", "control seekability of connection during encoding", OFFSET(write_seekable), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
69     {"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 },
70     {NULL}
71 };
72
73 static const AVClass ftp_context_class = {
74     .class_name     = "ftp",
75     .item_name      = av_default_item_name,
76     .option         = options,
77     .version        = LIBAVUTIL_VERSION_INT,
78 };
79
80 static int ftp_conn_control_block_control(void *data)
81 {
82     FTPContext *s = data;
83     return s->conn_control_block_flag;
84 }
85
86 static int ftp_getc(FTPContext *s)
87 {
88     int len;
89     if (s->control_buf_ptr >= s->control_buf_end) {
90         if (s->conn_control_block_flag)
91             return AVERROR_EXIT;
92         len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
93         if (len < 0) {
94             return len;
95         } else if (!len) {
96             return -1;
97         } else {
98             s->control_buf_ptr = s->control_buffer;
99             s->control_buf_end = s->control_buffer + len;
100         }
101     }
102     return *s->control_buf_ptr++;
103 }
104
105 static int ftp_get_line(FTPContext *s, char *line, int line_size)
106 {
107     int ch;
108     char *q = line;
109     int ori_block_flag = s->conn_control_block_flag;
110
111     for (;;) {
112         ch = ftp_getc(s);
113         if (ch < 0) {
114             s->conn_control_block_flag = ori_block_flag;
115             return ch;
116         }
117         if (ch == '\n') {
118             /* process line */
119             if (q > line && q[-1] == '\r')
120                 q--;
121             *q = '\0';
122
123             s->conn_control_block_flag = ori_block_flag;
124             return 0;
125         } else {
126             s->conn_control_block_flag = 0; /* line need to be finished */
127             if ((q - line) < line_size - 1)
128                 *q++ = ch;
129         }
130     }
131 }
132
133 static int ftp_flush_control_input(FTPContext *s)
134 {
135     char buf[CONTROL_BUFFER_SIZE];
136     int err, ori_block_flag = s->conn_control_block_flag;
137
138     s->conn_control_block_flag = 1;
139     do {
140         err = ftp_get_line(s, buf, sizeof(buf));
141     } while (!err);
142
143     s->conn_control_block_flag = ori_block_flag;
144
145     if (err < 0 && err != AVERROR_EXIT)
146         return err;
147
148     return 0;
149 }
150
151 /*
152  * This routine returns ftp server response code.
153  * Server may send more than one response for a certain command, following priorities are used:
154  *   - When pref_codes are set then pref_code is return if occurred. (expected result)
155  *   - 0 is returned when no pref_codes or not occurred
156  */
157 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
158 {
159     int err, i, result = 0, pref_code_found = 0, wait_count = 100;
160     char buf[CONTROL_BUFFER_SIZE];
161
162     /* Set blocking mode */
163     s->conn_control_block_flag = 0;
164     for (;;) {
165         if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
166             if (err == AVERROR_EXIT) {
167                 if (!pref_code_found && wait_count--) {
168                     av_usleep(10000);
169                     continue;
170                 }
171             }
172             return result;
173         }
174
175         av_log(s, AV_LOG_DEBUG, "%s\n", buf);
176
177         if (!pref_code_found) {
178             if (strlen(buf) < 3)
179                 continue;
180
181             err = 0;
182             for (i = 0; i < 3; ++i) {
183                 if (buf[i] < '0' || buf[i] > '9')
184                     continue;
185                 err *= 10;
186                 err += buf[i] - '0';
187             }
188
189             for (i = 0; response_codes[i]; ++i) {
190                 if (err == response_codes[i]) {
191                     /* first code received. Now get all lines in non blocking mode */
192                     s->conn_control_block_flag = 1;
193                     pref_code_found = 1;
194                     result = err;
195                     if (line)
196                         *line = av_strdup(buf);
197                     break;
198                 }
199             }
200         }
201     }
202     return result;
203 }
204
205 static int ftp_send_command(FTPContext *s, const char *command,
206                             const int response_codes[], char **response)
207 {
208     int err;
209
210     /* Flush control connection input to get rid of non relevant responses if any */
211     if ((err = ftp_flush_control_input(s)) < 0)
212         return err;
213
214     /* send command in blocking mode */
215     s->conn_control_block_flag = 0;
216     if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
217         return err;
218     if (!err)
219         return -1;
220
221     /* return status */
222     if (response_codes) {
223         return ftp_status(s, response, response_codes);
224     }
225     return 0;
226 }
227
228 static void ftp_close_data_connection(FTPContext *s)
229 {
230     ffurl_closep(&s->conn_data);
231     s->position = 0;
232     s->state = DISCONNECTED;
233 }
234
235 static void ftp_close_both_connections(FTPContext *s)
236 {
237     ffurl_closep(&s->conn_control);
238     ftp_close_data_connection(s);
239 }
240
241 static int ftp_auth(FTPContext *s)
242 {
243     const char *user = NULL, *pass = NULL;
244     char *end = NULL, buf[CONTROL_BUFFER_SIZE], credencials[CREDENTIALS_BUFFER_SIZE];
245     int err;
246     const int user_codes[] = {331, 230, 500, 530, 0}; /* 500, 530 are incorrect codes */
247     const int pass_codes[] = {230, 503, 530, 0}; /* 503, 530 are incorrect codes */
248
249     /* Authentication may be repeated, original string has to be saved */
250     av_strlcpy(credencials, s->credencials, sizeof(credencials));
251
252     user = av_strtok(credencials, ":", &end);
253     pass = av_strtok(end, ":", &end);
254
255     if (!user) {
256         user = "anonymous";
257         pass = s->anonymous_password ? s->anonymous_password : "nopassword";
258     }
259
260     snprintf(buf, sizeof(buf), "USER %s\r\n", user);
261     err = ftp_send_command(s, buf, user_codes, NULL);
262     if (err == 331) {
263         if (pass) {
264             snprintf(buf, sizeof(buf), "PASS %s\r\n", pass);
265             err = ftp_send_command(s, buf, pass_codes, NULL);
266         } else
267             return AVERROR(EACCES);
268     }
269     if (err != 230)
270         return AVERROR(EACCES);
271
272     return 0;
273 }
274
275 static int ftp_passive_mode(FTPContext *s)
276 {
277     char *res = NULL, *start = NULL, *end = NULL;
278     int i;
279     const char *command = "PASV\r\n";
280     const int pasv_codes[] = {227, 501, 0}; /* 501 is incorrect code */
281
282     if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
283         goto fail;
284
285     for (i = 0; res[i]; ++i) {
286         if (res[i] == '(') {
287             start = res + i + 1;
288         } else if (res[i] == ')') {
289             end = res + i;
290             break;
291         }
292     }
293     if (!start || !end)
294         goto fail;
295
296     *end  = '\0';
297     /* skip ip */
298     if (!av_strtok(start, ",", &end)) goto fail;
299     if (!av_strtok(end, ",", &end)) goto fail;
300     if (!av_strtok(end, ",", &end)) goto fail;
301     if (!av_strtok(end, ",", &end)) goto fail;
302
303     /* parse port number */
304     start = av_strtok(end, ",", &end);
305     if (!start) goto fail;
306     s->server_data_port = atoi(start) * 256;
307     start = av_strtok(end, ",", &end);
308     if (!start) goto fail;
309     s->server_data_port += atoi(start);
310     av_dlog(s, "Server data port: %d\n", s->server_data_port);
311
312     av_free(res);
313     return 0;
314
315   fail:
316     av_free(res);
317     s->server_data_port = -1;
318     return AVERROR(EIO);
319 }
320
321 static int ftp_current_dir(FTPContext *s)
322 {
323     char *res = NULL, *start = NULL, *end = NULL;
324     int i;
325     const char *command = "PWD\r\n";
326     const int pwd_codes[] = {257, 0};
327
328     if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
329         goto fail;
330
331     for (i = 0; res[i]; ++i) {
332         if (res[i] == '"') {
333             if (!start) {
334                 start = res + i + 1;
335                 continue;
336             }
337             end = res + i;
338             break;
339         }
340     }
341
342     if (!end)
343         goto fail;
344
345     if (end > res && end[-1] == '/') {
346         end[-1] = '\0';
347     } else
348         *end = '\0';
349     av_strlcpy(s->path, start, sizeof(s->path));
350
351     av_free(res);
352     return 0;
353
354   fail:
355     av_free(res);
356     return AVERROR(EIO);
357 }
358
359 static int ftp_file_size(FTPContext *s)
360 {
361     char command[CONTROL_BUFFER_SIZE];
362     char *res = NULL;
363     const int size_codes[] = {213, 501, 550, 0}; /* 501, 550 are incorrect codes */
364
365     snprintf(command, sizeof(command), "SIZE %s\r\n", s->path);
366     if (ftp_send_command(s, command, size_codes, &res) == 213 && res) {
367         s->filesize = strtoll(&res[4], NULL, 10);
368     } else {
369         s->filesize = -1;
370         av_free(res);
371         return AVERROR(EIO);
372     }
373
374     av_free(res);
375     return 0;
376 }
377
378 static int ftp_retrieve(FTPContext *s)
379 {
380     char command[CONTROL_BUFFER_SIZE];
381     const int retr_codes[] = {150, 550, 554, 0}; /* 550, 554 are incorrect codes */
382
383     snprintf(command, sizeof(command), "RETR %s\r\n", s->path);
384     if (ftp_send_command(s, command, retr_codes, NULL) != 150)
385         return AVERROR(EIO);
386
387     s->state = DOWNLOADING;
388
389     return 0;
390 }
391
392 static int ftp_store(FTPContext *s)
393 {
394     char command[CONTROL_BUFFER_SIZE];
395     const int stor_codes[] = {150, 0};
396
397     snprintf(command, sizeof(command), "STOR %s\r\n", s->path);
398     if (ftp_send_command(s, command, stor_codes, NULL) != 150)
399         return AVERROR(EIO);
400
401     s->state = UPLOADING;
402
403     return 0;
404 }
405
406 static int ftp_type(FTPContext *s)
407 {
408     const char *command = "TYPE I\r\n";
409     const int type_codes[] = {200, 500, 504, 0}; /* 500, 504 are incorrect codes */
410
411     if (ftp_send_command(s, command, type_codes, NULL) != 200)
412         return AVERROR(EIO);
413
414     return 0;
415 }
416
417 static int ftp_restart(FTPContext *s, int64_t pos)
418 {
419     char command[CONTROL_BUFFER_SIZE];
420     const int rest_codes[] = {350, 500, 501, 0}; /* 500, 501 are incorrect codes */
421
422     snprintf(command, sizeof(command), "REST %"PRId64"\r\n", pos);
423     if (ftp_send_command(s, command, rest_codes, NULL) != 350)
424         return AVERROR(EIO);
425
426     return 0;
427 }
428
429 static int ftp_connect_control_connection(URLContext *h)
430 {
431     char buf[CONTROL_BUFFER_SIZE], opts_format[20];
432     int err;
433     AVDictionary *opts = NULL;
434     FTPContext *s = h->priv_data;
435     const int connect_codes[] = {220, 0};
436
437     s->conn_control_block_flag = 0;
438
439     if (!s->conn_control) {
440         ff_url_join(buf, sizeof(buf), "tcp", NULL,
441                     s->hostname, s->server_control_port, NULL);
442         if (s->rw_timeout != -1) {
443             snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
444             av_dict_set(&opts, "timeout", opts_format, 0);
445         } /* if option is not given, don't pass it and let tcp use its own default */
446         err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
447                          &s->conn_control_interrupt_cb, &opts);
448         av_dict_free(&opts);
449         if (err < 0) {
450             av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
451             return err;
452         }
453
454         /* consume all messages from server */
455         if (ftp_status(s, NULL, connect_codes) != 220) {
456             av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
457             return AVERROR(EACCES);
458         }
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_dlog(h, "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 ((err = ftp_passive_mode(s)) < 0) {
483             av_dlog(h, "Set passive mode failed\n");
484             return err;
485         }
486         /* Open data connection */
487         ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
488         if (s->rw_timeout != -1) {
489             snprintf(opts_format, sizeof(opts_format), "%d", s->rw_timeout);
490             av_dict_set(&opts, "timeout", opts_format, 0);
491         } /* if option is not given, don't pass it and let tcp use its own default */
492         err = ffurl_open(&s->conn_data, buf, AVIO_FLAG_READ_WRITE,
493                          &h->interrupt_callback, &opts);
494         av_dict_free(&opts);
495         if (err < 0)
496             return err;
497
498         if (s->position)
499             if ((err = ftp_restart(s, s->position)) < 0)
500                 return err;
501     }
502     s->state = READY;
503     return 0;
504 }
505
506 static int ftp_abort(URLContext *h)
507 {
508     const char *command = "ABOR\r\n";
509     int err;
510     const int abor_codes[] = {225, 226, 0};
511     FTPContext *s = h->priv_data;
512
513     /* According to RCF 959:
514        "ABOR command tells the server to abort the previous FTP
515        service command and any associated transfer of data."
516
517        There are FTP server implementations that don't response
518        to any commands during data transfer in passive mode (including ABOR).
519
520        This implementation closes data connection by force.
521     */
522
523     if (ftp_send_command(s, command, NULL, NULL) < 0) {
524         ftp_close_both_connections(s);
525         if ((err = ftp_connect_control_connection(h)) < 0) {
526             av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
527             return err;
528         }
529     } else {
530         ftp_close_data_connection(s);
531     }
532
533     if (ftp_status(s, NULL, abor_codes) < 225) {
534         /* wu-ftpd also closes control connection after data connection closing */
535         ffurl_closep(&s->conn_control);
536         if ((err = ftp_connect_control_connection(h)) < 0) {
537             av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
538             return err;
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     s->conn_control_interrupt_cb.opaque = s;
557     s->conn_control_interrupt_cb.callback = ftp_conn_control_block_control;
558
559     av_url_split(proto, sizeof(proto),
560                  s->credencials, sizeof(s->credencials),
561                  s->hostname, sizeof(s->hostname),
562                  &s->server_control_port,
563                  path, sizeof(path),
564                  url);
565
566     if (s->server_control_port < 0 || s->server_control_port > 65535)
567         s->server_control_port = 21;
568
569     if ((err = ftp_connect_control_connection(h)) < 0)
570         goto fail;
571
572     if ((err = ftp_current_dir(s)) < 0)
573         goto fail;
574     av_strlcat(s->path, path, sizeof(s->path));
575
576     if (ftp_restart(s, 0) < 0) {
577         h->is_streamed = 1;
578     } else {
579         if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
580             h->is_streamed = 1;
581         if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
582             h->is_streamed = 1;
583     }
584
585     return 0;
586
587   fail:
588     av_log(h, AV_LOG_ERROR, "FTP open failed\n");
589     ffurl_closep(&s->conn_control);
590     ffurl_closep(&s->conn_data);
591     return err;
592 }
593
594 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
595 {
596     FTPContext *s = h->priv_data;
597     int err;
598     int64_t new_pos, fake_pos;
599
600     av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
601
602     switch(whence) {
603     case AVSEEK_SIZE:
604         return s->filesize;
605     case SEEK_SET:
606         new_pos = pos;
607         break;
608     case SEEK_CUR:
609         new_pos = s->position + pos;
610         break;
611     case SEEK_END:
612         if (s->filesize < 0)
613             return AVERROR(EIO);
614         new_pos = s->filesize + pos;
615         break;
616     default:
617         return AVERROR(EINVAL);
618     }
619
620     if  (h->is_streamed)
621         return AVERROR(EIO);
622
623     new_pos = FFMAX(0, new_pos);
624     fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
625
626     if (fake_pos != s->position) {
627         if ((err = ftp_abort(h)) < 0)
628             return err;
629         s->position = fake_pos;
630     }
631     return new_pos;
632 }
633
634 static int ftp_read(URLContext *h, unsigned char *buf, int size)
635 {
636     FTPContext *s = h->priv_data;
637     int read, err, retry_done = 0;
638
639     av_dlog(h, "ftp protocol read %d bytes\n", size);
640   retry:
641     if (s->state == DISCONNECTED) {
642         if (s->position >= s->filesize)
643             return 0;
644         if ((err = ftp_connect_data_connection(h)) < 0)
645             return err;
646     }
647     if (s->state == READY) {
648         if (s->position >= s->filesize)
649             return 0;
650         if ((err = ftp_retrieve(s)) < 0)
651             return err;
652     }
653     if (s->conn_data && s->state == DOWNLOADING) {
654         read = ffurl_read(s->conn_data, buf, size);
655         if (read >= 0) {
656             s->position += read;
657             if (s->position >= s->filesize) {
658                 /* server will terminate, but keep current position to avoid madness */
659                 int64_t pos = s->position;
660                 if (ftp_abort(h) < 0) {
661                     s->position = pos;
662                     return AVERROR(EIO);
663                 }
664                 s->position = pos;
665             }
666         }
667         if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
668             /* Server closed connection. Probably due to inactivity */
669             int64_t pos = s->position;
670             av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
671             if ((err = ftp_abort(h)) < 0)
672                 return err;
673             if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
674                 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
675                 return err;
676             }
677             if (!retry_done) {
678                 retry_done = 1;
679                 goto retry;
680             }
681         }
682         return read;
683     }
684
685     av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
686     return AVERROR(EIO);
687 }
688
689 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
690 {
691     int err;
692     FTPContext *s = h->priv_data;
693     int written;
694
695     av_dlog(h, "ftp protocol write %d bytes\n", size);
696
697     if (s->state == DISCONNECTED) {
698         if ((err = ftp_connect_data_connection(h)) < 0)
699             return err;
700     }
701     if (s->state == READY) {
702         if ((err = ftp_store(s)) < 0)
703             return err;
704     }
705     if (s->conn_data && s->state == UPLOADING) {
706         written = ffurl_write(s->conn_data, buf, size);
707         if (written > 0) {
708             s->position += written;
709             s->filesize = FFMAX(s->filesize, s->position);
710         }
711         return written;
712     }
713
714     av_log(h, AV_LOG_ERROR, "FTP write failed\n");
715     return AVERROR(EIO);
716 }
717
718 static int ftp_close(URLContext *h)
719 {
720     av_dlog(h, "ftp protocol close\n");
721
722     ftp_close_both_connections(h->priv_data);
723
724     return 0;
725 }
726
727 static int ftp_get_file_handle(URLContext *h)
728 {
729     FTPContext *s = h->priv_data;
730
731     av_dlog(h, "ftp protocol get_file_handle\n");
732
733     if (s->conn_data)
734         return ffurl_get_file_handle(s->conn_data);
735
736     return AVERROR(EIO);
737 }
738
739 static int ftp_shutdown(URLContext *h, int flags)
740 {
741     FTPContext *s = h->priv_data;
742
743     av_dlog(h, "ftp protocol shutdown\n");
744
745     if (s->conn_data)
746         return ffurl_shutdown(s->conn_data, flags);
747
748     return AVERROR(EIO);
749 }
750
751 URLProtocol ff_ftp_protocol = {
752     .name                = "ftp",
753     .url_open            = ftp_open,
754     .url_read            = ftp_read,
755     .url_write           = ftp_write,
756     .url_seek            = ftp_seek,
757     .url_close           = ftp_close,
758     .url_get_file_handle = ftp_get_file_handle,
759     .url_shutdown        = ftp_shutdown,
760     .priv_data_size      = sizeof(FTPContext),
761     .priv_data_class     = &ftp_context_class,
762     .flags               = URL_PROTOCOL_FLAG_NETWORK,
763 };