Commit | Line | Data |
---|---|---|
2ba45a60 DM |
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 | }; |