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