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