]> git.sesse.net Git - ffmpeg/blob - libavformat/ftp.c
lavf/http: Implement server side network code.
[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
30 typedef enum {
31     UNKNOWN,
32     READY,
33     DOWNLOADING,
34     UPLOADING,
35     DISCONNECTED
36 } FTPState;
37
38 typedef struct {
39     const AVClass *class;
40     URLContext *conn_control;                    /**< Control connection */
41     URLContext *conn_data;                       /**< Data connection, NULL when not connected */
42     uint8_t control_buffer[CONTROL_BUFFER_SIZE]; /**< Control connection buffer */
43     uint8_t *control_buf_ptr, *control_buf_end;
44     int server_data_port;                        /**< Data connection port opened by server, -1 on error. */
45     int server_control_port;                     /**< Control connection port, default is 21 */
46     char *hostname;                              /**< Server address. */
47     char *user;                                  /**< Server user */
48     char *password;                              /**< Server user's password */
49     char *path;                                  /**< 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_close(URLContext *h);
76
77 static int ftp_getc(FTPContext *s)
78 {
79     int len;
80     if (s->control_buf_ptr >= s->control_buf_end) {
81         len = ffurl_read(s->conn_control, s->control_buffer, CONTROL_BUFFER_SIZE);
82         if (len < 0) {
83             return len;
84         } else if (!len) {
85             return -1;
86         } else {
87             s->control_buf_ptr = s->control_buffer;
88             s->control_buf_end = s->control_buffer + len;
89         }
90     }
91     return *s->control_buf_ptr++;
92 }
93
94 static int ftp_get_line(FTPContext *s, char *line, int line_size)
95 {
96     int ch;
97     char *q = line;
98
99     for (;;) {
100         ch = ftp_getc(s);
101         if (ch < 0) {
102             return ch;
103         }
104         if (ch == '\n') {
105             /* process line */
106             if (q > line && q[-1] == '\r')
107                 q--;
108             *q = '\0';
109             return 0;
110         } else {
111             if ((q - line) < line_size - 1)
112                 *q++ = ch;
113         }
114     }
115 }
116
117 /*
118  * This routine returns ftp server response code.
119  * Server may send more than one response for a certain command.
120  * First expected code is returned.
121  */
122 static int ftp_status(FTPContext *s, char **line, const int response_codes[])
123 {
124     int err, i, dash = 0, result = 0, code_found = 0, linesize;
125     char buf[CONTROL_BUFFER_SIZE];
126     AVBPrint line_buffer;
127
128     if (line)
129         av_bprint_init(&line_buffer, 0, AV_BPRINT_SIZE_AUTOMATIC);
130
131     while (!code_found || dash) {
132         if ((err = ftp_get_line(s, buf, sizeof(buf))) < 0) {
133             if (line)
134                 av_bprint_finalize(&line_buffer, NULL);
135             return err;
136         }
137
138         av_log(s, AV_LOG_DEBUG, "%s\n", buf);
139
140         linesize = strlen(buf);
141         err = 0;
142         if (linesize >= 3) {
143             for (i = 0; i < 3; ++i) {
144                 if (buf[i] < '0' || buf[i] > '9') {
145                     err = 0;
146                     break;
147                 }
148                 err *= 10;
149                 err += buf[i] - '0';
150             }
151         }
152
153         if (!code_found) {
154             if (err >= 500) {
155                 code_found = 1;
156                 result = err;
157             } else
158                 for (i = 0; response_codes[i]; ++i) {
159                     if (err == response_codes[i]) {
160                         code_found = 1;
161                         result = err;
162                         break;
163                     }
164                 }
165         }
166         if (code_found) {
167             if (line)
168                 av_bprintf(&line_buffer, "%s\r\n", buf);
169             if (linesize >= 4) {
170                 if (!dash && buf[3] == '-')
171                     dash = err;
172                 else if (err == dash && buf[3] == ' ')
173                     dash = 0;
174             }
175         }
176     }
177
178     if (line)
179         av_bprint_finalize(&line_buffer, line);
180     return result;
181 }
182
183 static int ftp_send_command(FTPContext *s, const char *command,
184                             const int response_codes[], char **response)
185 {
186     int err;
187
188     if (response)
189         *response = NULL;
190
191     if ((err = ffurl_write(s->conn_control, command, strlen(command))) < 0)
192         return err;
193     if (!err)
194         return -1;
195
196     /* return status */
197     if (response_codes) {
198         return ftp_status(s, response, response_codes);
199     }
200     return 0;
201 }
202
203 static void ftp_close_data_connection(FTPContext *s)
204 {
205     ffurl_closep(&s->conn_data);
206     s->position = 0;
207     s->state = DISCONNECTED;
208 }
209
210 static void ftp_close_both_connections(FTPContext *s)
211 {
212     ffurl_closep(&s->conn_control);
213     ftp_close_data_connection(s);
214 }
215
216 static int ftp_auth(FTPContext *s)
217 {
218     char buf[CONTROL_BUFFER_SIZE];
219     int err;
220     static const int user_codes[] = {331, 230, 0};
221     static const int pass_codes[] = {230, 0};
222
223     snprintf(buf, sizeof(buf), "USER %s\r\n", s->user);
224     err = ftp_send_command(s, buf, user_codes, NULL);
225     if (err == 331) {
226         if (s->password) {
227             snprintf(buf, sizeof(buf), "PASS %s\r\n", s->password);
228             err = ftp_send_command(s, buf, pass_codes, NULL);
229         } else
230             return AVERROR(EACCES);
231     }
232     if (err != 230)
233         return AVERROR(EACCES);
234
235     return 0;
236 }
237
238 static int ftp_passive_mode_epsv(FTPContext *s)
239 {
240     char *res = NULL, *start = NULL, *end = NULL;
241     int i;
242     static const char d = '|';
243     static const char *command = "EPSV\r\n";
244     static const int epsv_codes[] = {229, 0};
245
246     if (ftp_send_command(s, command, epsv_codes, &res) != 229 || !res)
247         goto fail;
248
249     for (i = 0; res[i]; ++i) {
250         if (res[i] == '(') {
251             start = res + i + 1;
252         } else if (res[i] == ')') {
253             end = res + i;
254             break;
255         }
256     }
257     if (!start || !end)
258         goto fail;
259
260     *end = '\0';
261     if (strlen(start) < 5)
262         goto fail;
263     if (start[0] != d || start[1] != d || start[2] != d || end[-1] != d)
264         goto fail;
265     start += 3;
266     end[-1] = '\0';
267
268     s->server_data_port = atoi(start);
269     av_dlog(s, "Server data port: %d\n", s->server_data_port);
270
271     av_free(res);
272     return 0;
273
274   fail:
275     av_free(res);
276     s->server_data_port = -1;
277     return AVERROR(ENOSYS);
278 }
279
280 static int ftp_passive_mode(FTPContext *s)
281 {
282     char *res = NULL, *start = NULL, *end = NULL;
283     int i;
284     static const char *command = "PASV\r\n";
285     static const int pasv_codes[] = {227, 0};
286
287     if (ftp_send_command(s, command, pasv_codes, &res) != 227 || !res)
288         goto fail;
289
290     for (i = 0; res[i]; ++i) {
291         if (res[i] == '(') {
292             start = res + i + 1;
293         } else if (res[i] == ')') {
294             end = res + i;
295             break;
296         }
297     }
298     if (!start || !end)
299         goto fail;
300
301     *end  = '\0';
302     /* skip ip */
303     if (!av_strtok(start, ",", &end)) goto fail;
304     if (!av_strtok(end, ",", &end)) goto fail;
305     if (!av_strtok(end, ",", &end)) goto fail;
306     if (!av_strtok(end, ",", &end)) goto fail;
307
308     /* parse port number */
309     start = av_strtok(end, ",", &end);
310     if (!start) goto fail;
311     s->server_data_port = atoi(start) * 256;
312     start = av_strtok(end, ",", &end);
313     if (!start) goto fail;
314     s->server_data_port += atoi(start);
315     av_dlog(s, "Server data port: %d\n", s->server_data_port);
316
317     av_free(res);
318     return 0;
319
320   fail:
321     av_free(res);
322     s->server_data_port = -1;
323     return AVERROR(EIO);
324 }
325
326 static int ftp_current_dir(FTPContext *s)
327 {
328     char *res = NULL, *start = NULL, *end = NULL;
329     int i;
330     static const char *command = "PWD\r\n";
331     static const int pwd_codes[] = {257, 0};
332
333     if (ftp_send_command(s, command, pwd_codes, &res) != 257 || !res)
334         goto fail;
335
336     for (i = 0; res[i]; ++i) {
337         if (res[i] == '"') {
338             if (!start) {
339                 start = res + i + 1;
340                 continue;
341             }
342             end = res + i;
343             break;
344         }
345     }
346
347     if (!end)
348         goto fail;
349
350     if (end > res && end[-1] == '/') {
351         end[-1] = '\0';
352     } else
353         *end = '\0';
354     s->path = av_strdup(start);
355
356     av_free(res);
357
358     if (!s->path)
359         return AVERROR(ENOMEM);
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, 0};
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, 0};
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, 0};
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, 0};
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_features(FTPContext *s)
438 {
439     static const char *feat_command        = "FEAT\r\n";
440     static const char *enable_utf8_command = "OPTS UTF8 ON\r\n";
441     static const int feat_codes[] = {211, 0};
442     static const int opts_codes[] = {200, 451};
443     char *feat = NULL;
444
445     if (ftp_send_command(s, feat_command, feat_codes, &feat) == 211) {
446         if (av_stristr(feat, "UTF8"))
447             ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
448     }
449     av_freep(&feat);
450
451     return 0;
452 }
453
454 static int ftp_connect_control_connection(URLContext *h)
455 {
456     char buf[CONTROL_BUFFER_SIZE], *response = NULL;
457     int err;
458     AVDictionary *opts = NULL;
459     FTPContext *s = h->priv_data;
460     static const int connect_codes[] = {220, 0};
461
462     if (!s->conn_control) {
463         ff_url_join(buf, sizeof(buf), "tcp", NULL,
464                     s->hostname, s->server_control_port, NULL);
465         if (s->rw_timeout != -1) {
466             av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
467         } /* if option is not given, don't pass it and let tcp use its own default */
468         err = ffurl_open(&s->conn_control, buf, AVIO_FLAG_READ_WRITE,
469                          &h->interrupt_callback, &opts);
470         av_dict_free(&opts);
471         if (err < 0) {
472             av_log(h, AV_LOG_ERROR, "Cannot open control connection\n");
473             return err;
474         }
475
476         /* check if server is ready */
477         if (ftp_status(s, ((h->flags & AVIO_FLAG_WRITE) ? &response : NULL), connect_codes) != 220) {
478             av_log(h, AV_LOG_ERROR, "FTP server not ready for new users\n");
479             return AVERROR(EACCES);
480         }
481
482         if ((h->flags & AVIO_FLAG_WRITE) && av_stristr(response, "pure-ftpd")) {
483             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.");
484         }
485         av_free(response);
486
487         if ((err = ftp_auth(s)) < 0) {
488             av_log(h, AV_LOG_ERROR, "FTP authentication failed\n");
489             return err;
490         }
491
492         if ((err = ftp_type(s)) < 0) {
493             av_log(h, AV_LOG_ERROR, "Set content type failed\n");
494             return err;
495         }
496
497         ftp_features(s);
498     }
499     return 0;
500 }
501
502 static int ftp_connect_data_connection(URLContext *h)
503 {
504     int err;
505     char buf[CONTROL_BUFFER_SIZE];
506     AVDictionary *opts = NULL;
507     FTPContext *s = h->priv_data;
508
509     if (!s->conn_data) {
510         /* Enter passive mode */
511         if (ftp_passive_mode_epsv(s) < 0) {
512             /* Use PASV as fallback */
513             if ((err = ftp_passive_mode(s)) < 0)
514                 return err;
515         }
516         /* Open data connection */
517         ff_url_join(buf, sizeof(buf), "tcp", NULL, s->hostname, s->server_data_port, NULL);
518         if (s->rw_timeout != -1) {
519             av_dict_set_int(&opts, "timeout", s->rw_timeout, 0);
520         } /* if option is not given, don't pass it and let tcp use its own default */
521         err = ffurl_open(&s->conn_data, buf, h->flags,
522                          &h->interrupt_callback, &opts);
523         av_dict_free(&opts);
524         if (err < 0)
525             return err;
526
527         if (s->position)
528             if ((err = ftp_restart(s, s->position)) < 0)
529                 return err;
530     }
531     s->state = READY;
532     return 0;
533 }
534
535 static int ftp_abort(URLContext *h)
536 {
537     static const char *command = "ABOR\r\n";
538     int err;
539     static const int abor_codes[] = {225, 226, 0};
540     FTPContext *s = h->priv_data;
541
542     /* According to RCF 959:
543        "ABOR command tells the server to abort the previous FTP
544        service command and any associated transfer of data."
545
546        There are FTP server implementations that don't response
547        to any commands during data transfer in passive mode (including ABOR).
548
549        This implementation closes data connection by force.
550     */
551
552     if (ftp_send_command(s, command, NULL, NULL) < 0) {
553         ftp_close_both_connections(s);
554         if ((err = ftp_connect_control_connection(h)) < 0) {
555             av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
556             return err;
557         }
558     } else {
559         ftp_close_data_connection(s);
560         if (ftp_status(s, NULL, abor_codes) < 225) {
561             /* wu-ftpd also closes control connection after data connection closing */
562             ffurl_closep(&s->conn_control);
563             if ((err = ftp_connect_control_connection(h)) < 0) {
564                 av_log(h, AV_LOG_ERROR, "Reconnect failed.\n");
565                 return err;
566             }
567         }
568     }
569
570     return 0;
571 }
572
573 static int ftp_open(URLContext *h, const char *url, int flags)
574 {
575     char proto[10], path[MAX_URL_SIZE], credencials[MAX_URL_SIZE], hostname[MAX_URL_SIZE];
576     const char *tok_user = NULL, *tok_pass = NULL;
577     char *end = NULL;
578     int err;
579     size_t pathlen;
580     FTPContext *s = h->priv_data;
581
582     av_dlog(h, "ftp protocol open\n");
583
584     s->state = DISCONNECTED;
585     s->filesize = -1;
586     s->position = 0;
587
588     av_url_split(proto, sizeof(proto),
589                  credencials, sizeof(credencials),
590                  hostname, sizeof(hostname),
591                  &s->server_control_port,
592                  path, sizeof(path),
593                  url);
594
595     tok_user = av_strtok(credencials, ":", &end);
596     tok_pass = av_strtok(end, ":", &end);
597     if (!tok_user) {
598         tok_user = "anonymous";
599         tok_pass = av_x_if_null(s->anonymous_password, "nopassword");
600     }
601     s->user = av_strdup(tok_user);
602     s->password = av_strdup(tok_pass);
603     s->hostname = av_strdup(hostname);
604     if (!s->hostname || !s->user || (tok_pass && !s->password)) {
605         err = AVERROR(ENOMEM);
606         goto fail;
607     }
608
609     if (s->server_control_port < 0 || s->server_control_port > 65535)
610         s->server_control_port = 21;
611
612     if ((err = ftp_connect_control_connection(h)) < 0)
613         goto fail;
614
615     if ((err = ftp_current_dir(s)) < 0)
616         goto fail;
617     pathlen = strlen(s->path) + strlen(path) + 1;
618     if ((err = av_reallocp(&s->path, pathlen)) < 0)
619         goto fail;
620     av_strlcat(s->path + strlen(s->path), path, pathlen);
621
622     if (ftp_restart(s, 0) < 0) {
623         h->is_streamed = 1;
624     } else {
625         if (ftp_file_size(s) < 0 && flags & AVIO_FLAG_READ)
626             h->is_streamed = 1;
627         if (s->write_seekable != 1 && flags & AVIO_FLAG_WRITE)
628             h->is_streamed = 1;
629     }
630
631     return 0;
632
633   fail:
634     av_log(h, AV_LOG_ERROR, "FTP open failed\n");
635     ftp_close(h);
636     return err;
637 }
638
639 static int64_t ftp_seek(URLContext *h, int64_t pos, int whence)
640 {
641     FTPContext *s = h->priv_data;
642     int err;
643     int64_t new_pos, fake_pos;
644
645     av_dlog(h, "ftp protocol seek %"PRId64" %d\n", pos, whence);
646
647     switch(whence) {
648     case AVSEEK_SIZE:
649         return s->filesize;
650     case SEEK_SET:
651         new_pos = pos;
652         break;
653     case SEEK_CUR:
654         new_pos = s->position + pos;
655         break;
656     case SEEK_END:
657         if (s->filesize < 0)
658             return AVERROR(EIO);
659         new_pos = s->filesize + pos;
660         break;
661     default:
662         return AVERROR(EINVAL);
663     }
664
665     if (h->is_streamed)
666         return AVERROR(EIO);
667
668     if (new_pos < 0) {
669         av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
670         return AVERROR(EINVAL);
671     }
672
673     fake_pos = s->filesize != -1 ? FFMIN(new_pos, s->filesize) : new_pos;
674     if (fake_pos != s->position) {
675         if ((err = ftp_abort(h)) < 0)
676             return err;
677         s->position = fake_pos;
678     }
679     return new_pos;
680 }
681
682 static int ftp_read(URLContext *h, unsigned char *buf, int size)
683 {
684     FTPContext *s = h->priv_data;
685     int read, err, retry_done = 0;
686
687     av_dlog(h, "ftp protocol read %d bytes\n", size);
688   retry:
689     if (s->state == DISCONNECTED) {
690         /* optimization */
691         if (s->position >= s->filesize)
692             return 0;
693         if ((err = ftp_connect_data_connection(h)) < 0)
694             return err;
695     }
696     if (s->state == READY) {
697         if (s->position >= s->filesize)
698             return 0;
699         if ((err = ftp_retrieve(s)) < 0)
700             return err;
701     }
702     if (s->conn_data && s->state == DOWNLOADING) {
703         read = ffurl_read(s->conn_data, buf, size);
704         if (read >= 0) {
705             s->position += read;
706             if (s->position >= s->filesize) {
707                 /* server will terminate, but keep current position to avoid madness */
708                 /* save position to restart from it */
709                 int64_t pos = s->position;
710                 if (ftp_abort(h) < 0) {
711                     s->position = pos;
712                     return AVERROR(EIO);
713                 }
714                 s->position = pos;
715             }
716         }
717         if (read <= 0 && s->position < s->filesize && !h->is_streamed) {
718             /* Server closed connection. Probably due to inactivity */
719             int64_t pos = s->position;
720             av_log(h, AV_LOG_INFO, "Reconnect to FTP server.\n");
721             if ((err = ftp_abort(h)) < 0)
722                 return err;
723             if ((err = ftp_seek(h, pos, SEEK_SET)) < 0) {
724                 av_log(h, AV_LOG_ERROR, "Position cannot be restored.\n");
725                 return err;
726             }
727             if (!retry_done) {
728                 retry_done = 1;
729                 goto retry;
730             }
731         }
732         return read;
733     }
734
735     av_log(h, AV_LOG_DEBUG, "FTP read failed\n");
736     return AVERROR(EIO);
737 }
738
739 static int ftp_write(URLContext *h, const unsigned char *buf, int size)
740 {
741     int err;
742     FTPContext *s = h->priv_data;
743     int written;
744
745     av_dlog(h, "ftp protocol write %d bytes\n", size);
746
747     if (s->state == DISCONNECTED) {
748         if ((err = ftp_connect_data_connection(h)) < 0)
749             return err;
750     }
751     if (s->state == READY) {
752         if ((err = ftp_store(s)) < 0)
753             return err;
754     }
755     if (s->conn_data && s->state == UPLOADING) {
756         written = ffurl_write(s->conn_data, buf, size);
757         if (written > 0) {
758             s->position += written;
759             s->filesize = FFMAX(s->filesize, s->position);
760         }
761         return written;
762     }
763
764     av_log(h, AV_LOG_ERROR, "FTP write failed\n");
765     return AVERROR(EIO);
766 }
767
768 static int ftp_close(URLContext *h)
769 {
770     FTPContext *s = h->priv_data;
771
772     av_dlog(h, "ftp protocol close\n");
773
774     ftp_close_both_connections(s);
775     av_freep(&s->user);
776     av_freep(&s->password);
777     av_freep(&s->hostname);
778     av_freep(&s->path);
779
780     return 0;
781 }
782
783 static int ftp_get_file_handle(URLContext *h)
784 {
785     FTPContext *s = h->priv_data;
786
787     av_dlog(h, "ftp protocol get_file_handle\n");
788
789     if (s->conn_data)
790         return ffurl_get_file_handle(s->conn_data);
791
792     return AVERROR(EIO);
793 }
794
795 static int ftp_shutdown(URLContext *h, int flags)
796 {
797     FTPContext *s = h->priv_data;
798
799     av_dlog(h, "ftp protocol shutdown\n");
800
801     if (s->conn_data)
802         return ffurl_shutdown(s->conn_data, flags);
803
804     return AVERROR(EIO);
805 }
806
807 URLProtocol ff_ftp_protocol = {
808     .name                = "ftp",
809     .url_open            = ftp_open,
810     .url_read            = ftp_read,
811     .url_write           = ftp_write,
812     .url_seek            = ftp_seek,
813     .url_close           = ftp_close,
814     .url_get_file_handle = ftp_get_file_handle,
815     .url_shutdown        = ftp_shutdown,
816     .priv_data_size      = sizeof(FTPContext),
817     .priv_data_class     = &ftp_context_class,
818     .flags               = URL_PROTOCOL_FLAG_NETWORK,
819 };