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