1 /*
2 * Copyright (C) 2000-2019 the xine project
3 * Copyright (C) 2018 Petri Hintukainen <phintuka@users.sourceforge.net>
4 *
5 * This file is part of xine, a free video player.
6 *
7 * xine is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * xine is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
20 *
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <stdint.h>
30 #include <unistd.h>
31 #include <errno.h>
32
33 #define LOG_MODULE "input_ftp"
34 #define LOG_VERBOSE
35 /*
36 #define LOG
37 */
38
39 #include <xine/xine_internal.h>
40 #include <xine/xineutils.h>
41 #include <xine/input_plugin.h>
42
43 #include "group_network.h"
44 #include "http_helper.h"
45 #include "input_helper.h"
46 #include "net_buf_ctrl.h"
47 #include "tls/xine_tls.h"
48
49 #define DEFAULT_FTP_PORT 21
50
51 typedef struct {
52 input_plugin_t input_plugin;
53
54 xine_t *xine;
55 xine_stream_t *stream;
56 nbc_t *nbc;
57
58 char *mrl;
59 char *mrl_private;
60 char *uri;
61 off_t curpos;
62 off_t file_size;
63 int cap_rest;
64
65 xine_tls_t *tls;
66 int fd_data;
67 char buf[1024];
68
69 off_t preview_size;
70 char preview[MAX_PREVIEW_SIZE];
71
72 } ftp_input_plugin_t;
73
74 typedef struct {
75 input_class_t input_class;
76 xine_t *xine;
77 xine_mrl_t **mrls;
78 } ftp_input_class_t;
79
80 /*
81 * FTP protocol
82 */
83
_read_response(ftp_input_plugin_t * this)84 static int _read_response(ftp_input_plugin_t *this)
85 {
86 int rc;
87
88 do {
89 rc = _x_tls_read_line(this->tls, this->buf, sizeof(this->buf));
90 if (rc < 4)
91 return -1;
92 lprintf("<<< '%s'\n", this->buf);
93 } while (this->buf[3] == '-');
94
95 rc = -1;
96 if (this->buf[3] == ' ') {
97 rc = atoi(this->buf);
98 }
99 return rc;
100 }
101
_write_command(ftp_input_plugin_t * this,const char * cmd)102 static int _write_command(ftp_input_plugin_t *this, const char *cmd)
103 {
104 size_t len;
105 int rc = -1;
106
107 lprintf(">>> %s\n", cmd);
108 this->buf[0] = 0;
109
110 len = strlen(cmd);
111 rc = _x_tls_write(this->tls, cmd, len);
112 if ((size_t)rc != len) {
113 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
114 "send failed\n");
115 return -1;
116 }
117
118 rc = _x_tls_write(this->tls, "\r\n", 2);
119 if (rc != 2) {
120 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
121 "send CRLF failed\n");
122 return -1;
123 }
124
125 return 0;
126 }
127
_send_command(ftp_input_plugin_t * this,const char * cmd)128 static int _send_command(ftp_input_plugin_t *this, const char *cmd)
129 {
130 int rc;
131
132 rc = _write_command(this, cmd);
133 if (rc < 0)
134 return rc;
135
136 rc = _read_response(this);
137 return rc;
138 }
139
_auth_tls(ftp_input_plugin_t * this,const char * host)140 static int _auth_tls(ftp_input_plugin_t *this, const char *host)
141 {
142 int rc;
143
144 rc = _send_command(this, "AUTH TLS");
145 if (rc < 0 || (rc/100) > 3) {
146 return -1;
147 }
148
149 rc = _x_tls_handshake(this->tls, host, -1);
150 return rc;
151 }
152
_connect(ftp_input_plugin_t * this,int * fd,const char * host,int port)153 static int _connect(ftp_input_plugin_t *this, int *fd, const char *host, int port)
154 {
155 _x_assert(*fd < 0);
156
157 if (!port)
158 port = DEFAULT_FTP_PORT;
159
160 *fd = _x_io_tcp_connect (this->stream, host, port);
161 if (*fd < 0) {
162 return -1;
163 }
164
165 do {
166 int rc = _x_io_tcp_connect_finish(this->stream, *fd, 1000);
167 if (rc == XIO_READY)
168 break;
169 if (rc != XIO_TIMEOUT)
170 return -1;
171 } while (1);
172
173 return 0;
174 }
175
_login(ftp_input_plugin_t * this,const char * user,const char * pass)176 static int _login(ftp_input_plugin_t *this,
177 const char *user, const char *pass)
178 {
179 char *s;
180 int rc;
181
182 s = _x_asprintf("USER %s", user);
183 if (!s)
184 return -1;
185 rc = _send_command(this, s);
186 free(s);
187
188 if (rc / 100 == 2) {
189 return 0;
190 }
191 if (rc / 100 != 3) {
192 return -1;
193 }
194
195 s = _x_asprintf("PASS %s", pass);
196 if (!s)
197 return -1;
198 rc = _send_command(this, s);
199 _x_freep_wipe_string(&s);
200
201 if (rc / 100 == 2) {
202 return 0;
203 }
204 return -1;
205 }
206
_ftp_connect(ftp_input_plugin_t * this,xine_url_t * url)207 static int _ftp_connect(ftp_input_plugin_t *this, xine_url_t *url)
208 {
209 const char *user, *pass;
210 int rc, fd = -1;
211
212 rc = _connect(this, &fd, url->host, url->port);
213 if (rc < 0) {
214 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
215 "Connect to %s failed\n", this->mrl);
216 if (fd >= 0)
217 _x_io_tcp_close(this->stream, fd);
218 return -1;
219 }
220
221 this->tls = _x_tls_init(this->xine, this->stream, fd);
222 if (!this->tls) {
223 if (fd >= 0)
224 _x_io_tcp_close(this->stream, fd);
225 return -1;
226 }
227
228 /* check prompt */
229 rc = _read_response(this);
230 if (rc / 100 != 2) {
231 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
232 "FTP connect failed: %s\n", this->buf);
233 return -1;
234 }
235
236 if (!strcasecmp(url->proto, "ftpes")) {
237 if (_auth_tls(this, url->host) < 0) {
238 const char *help = NULL;
239 if (_x_tls_get_verify_tls_cert(this->xine->config)) {
240 help = "Disabling \'media.network.verify_tls_certificate\' may help.";
241 }
242 _x_message(this->stream, XINE_MSG_SECURITY,
243 this->mrl, "TLS handshake failed. ", help,
244 NULL);
245 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
246 "TLS handshake failed but TLS was requested for '%s'. %s\n",
247 this->mrl, help ? help : "");
248 return -1;
249 }
250 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
251 "AUTH TLS succeed. Control connection is now encrypted.\n");
252 }
253
254 if (!url->user) {
255 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
256 "No username in mrl, logging in as anonymous\n");
257 }
258
259 user = url->user ? url->user : "anonymous";
260 pass = url->password ? url->password : "anonymous@anonymous.org";
261
262 rc = _login(this, user, pass);
263 if (rc < 0) {
264 if (!url->user || !url->password) {
265 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
266 "Authentication required for '%s'\n", this->mrl);
267 } else {
268 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
269 "Authentication by password failed: %s\n", this->buf);
270 }
271 if (this->stream)
272 _x_message(this->stream, XINE_MSG_AUTHENTICATION_NEEDED,
273 this->mrl, "Authentication required", NULL);
274 return -1;
275 }
276
277 /* check passive mode support */
278 rc = _send_command(this, "PASV");
279 if (rc / 100 != 2) {
280 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
281 "Failed to set passive mode: %s\n", this->buf);
282 return -1;
283 }
284
285 return 0;
286 }
287
_connect_data(ftp_input_plugin_t * this,char type)288 static int _connect_data(ftp_input_plugin_t *this, char type)
289 {
290 char ip[16];
291 char *pt, *cmd;
292 unsigned a1, a2, a3, a4, p1, p2;
293 int rc;
294
295 _x_assert(this->fd_data < 0);
296
297 /* request passive mode */
298 rc = _send_command(this, "PASV");
299 if (rc / 100 != 2) {
300 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
301 "Failed to set passive mode: %s\n", this->buf);
302 return -1;
303 }
304
305 /* parse address */
306 pt = strchr(this->buf, '(');
307 if (!pt) {
308 return -1;
309 }
310 rc = sscanf(pt, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4, &p1, &p2);
311 if (rc != 6 ||
312 a1 > 255 || a2 > 255 || a3 > 255 || a4 > 255 ||
313 p1 > 255 || p2 > 255) {
314 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
315 "Address parsing failed (%s)\n", this->buf);
316 return -1;
317 }
318 sprintf(ip, "%u.%u.%u.%u", a1, a2, a3, a4);
319
320 /* set transfer type */
321 cmd = _x_asprintf("TYPE %c", type);
322 if (!cmd)
323 return -1;
324 rc = _send_command(this, cmd);
325 free(cmd);
326 if (rc / 100 != 2) {
327 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
328 "Failed to set '%c' mode: %s\n", type, this->buf);
329 return -1;
330 }
331
332 /* connect data stream */
333 rc = _connect(this, &this->fd_data, ip, (p1 << 8) | p2);
334 if (rc < 0) {
335 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
336 "Failed to connect data stream.\n");
337 return -1;
338 }
339
340 return 0;
341 }
342
_cwd(ftp_input_plugin_t * this,const char * dir)343 static int _cwd(ftp_input_plugin_t *this, const char *dir)
344 {
345 int rc;
346 char *cmd;
347
348 cmd = _x_asprintf("CWD %s", dir);
349 if (!cmd)
350 return -1;
351 rc = _send_command(this, cmd);
352 free(cmd);
353
354 if (rc / 100 != 2) {
355 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
356 "Error changing current directory to %s: %s\n", dir, this->buf);
357 return -1;
358 }
359
360 return 0;
361 }
362
_list(ftp_input_plugin_t * this)363 static int _list(ftp_input_plugin_t *this)
364 {
365 int rc;
366
367 rc = _connect_data(this, 'A');
368 if (rc < 0)
369 return -1;
370
371 rc = _send_command(this, "LIST");
372 if (rc / 100 != 1) {
373 xprintf(this->xine, XINE_VERBOSITY_DEBUG, LOG_MODULE ": "
374 "Error listing files in verbose mode: %s\n", this->buf);
375 rc = _send_command(this, "NLST");
376 if (rc / 100 != 1) {
377 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
378 "Failed to list files: %s\n", this->buf);
379 return -1;
380 }
381 }
382
383 return 0;
384 }
385
_parse_off_t(const char * pt)386 static off_t _parse_off_t(const char *pt)
387 {
388 off_t off = 0;
389 while (*pt >= '0' && *pt <= '9') {
390 off = 10 * off + (*pt - '0');
391 pt++;
392 }
393 return off;
394 }
395
_ftp_size(ftp_input_plugin_t * this,const char * uri)396 static int _ftp_size(ftp_input_plugin_t *this, const char *uri)
397 {
398 int rc;
399 char *cmd;
400
401 cmd = _x_asprintf("SIZE %s", uri);
402 if (!cmd)
403 return -1;
404 rc = _send_command(this, cmd);
405 free(cmd);
406
407 if (rc / 100 != 2)
408 return -1;
409
410 this->file_size = _parse_off_t(this->buf + 4);
411
412 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
413 "File size is %" PRId64 " bytes\n", (int64_t)this->file_size);
414
415 return 0;
416 }
417
_abor(ftp_input_plugin_t * this)418 static int _abor(ftp_input_plugin_t *this)
419 {
420 int rc;
421
422 rc = _write_command(this, "ABOR");
423 if (rc < 0)
424 return rc;
425
426 if (this->fd_data >= 0) {
427 _x_io_tcp_close(this->stream, this->fd_data);
428 this->fd_data = -1;
429 /* this should return response code for initial RETR (426 or 226) if transferring */
430 rc = _read_response(this);
431 }
432
433 if (rc >= 0) {
434 /* read ABRT response code */
435 rc = _read_response(this);
436 }
437
438 return rc;
439 }
440
_rest(ftp_input_plugin_t * this,off_t offset)441 static int _rest(ftp_input_plugin_t *this, off_t offset)
442 {
443 char *cmd;
444 int rc;
445
446 cmd = _x_asprintf("REST %" PRIu64, (uint64_t)offset);
447 if (!cmd)
448 return -1;
449 rc = _send_command(this, cmd);
450 free(cmd);
451
452 if (rc < 0 || (rc/100) > 3) {
453 return -1;
454 }
455
456 this->curpos = offset;
457 this->cap_rest = 1;
458 return 0;
459 }
460
_retr(ftp_input_plugin_t * this,const char * uri,off_t offset)461 static int _retr(ftp_input_plugin_t *this, const char *uri, off_t offset)
462 {
463 int rc;
464 char *cmd;
465
466 /* issue REST command even if starting from offset 0.
467 * (to test REST support) */
468 _rest(this, offset);
469
470 rc = _connect_data(this, 'I');
471 if (rc < 0)
472 return -1;
473
474 cmd = _x_asprintf("RETR %s", uri);
475 if (!cmd)
476 return -1;
477 rc = _send_command(this, cmd);
478 free(cmd);
479
480 if (rc / 100 != 1) {
481 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
482 "Failed to retrive file %s: %s\n", uri, this->buf);
483 return -1;
484 }
485
486 /* if SIZE failed, try to parse it from reply */
487 if (this->file_size < 1) {
488 char *pt = strrchr(this->buf, '(');
489 if (pt) {
490 this->file_size = _parse_off_t(pt + 1);
491 }
492 }
493
494 return 0;
495 }
496
497 /*
498 * xine plugin
499 */
500
_ftp_read(input_plugin_t * this_gen,void * buf_gen,off_t len)501 static off_t _ftp_read (input_plugin_t *this_gen, void *buf_gen, off_t len)
502 {
503 ftp_input_plugin_t *this = (ftp_input_plugin_t *) this_gen;
504 uint8_t *buf = buf_gen;
505 off_t got = 0;
506 int rc;
507
508 /* read from preview ? */
509 if (this->curpos < this->preview_size) {
510 if (len > (this->preview_size - this->curpos))
511 got = this->preview_size - this->curpos;
512 else
513 got = len;
514
515 memcpy (buf, &this->preview[this->curpos], got);
516 }
517
518 while (got < len) {
519 rc = _x_io_tcp_read(this->stream, this->fd_data, buf + got, len - got);
520 if (rc <= 0) {
521 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
522 "FTP read failed\n");
523 if (got)
524 break;
525 return rc;
526 }
527
528 got += rc;
529
530 if (_x_action_pending(this->stream)) {
531 errno = EINTR;
532 if (got)
533 break;
534 return -1;
535 }
536 }
537
538 this->curpos += got;
539 return got;
540 }
541
_ftp_get_length(input_plugin_t * this_gen)542 static off_t _ftp_get_length (input_plugin_t *this_gen)
543 {
544 ftp_input_plugin_t *this = (ftp_input_plugin_t *) this_gen;
545
546 return this->file_size;
547 }
548
_ftp_get_current_pos(input_plugin_t * this_gen)549 static off_t _ftp_get_current_pos (input_plugin_t *this_gen)
550 {
551 ftp_input_plugin_t *this = (ftp_input_plugin_t *) this_gen;
552
553 return this->curpos;
554 }
555
_ftp_seek(input_plugin_t * this_gen,off_t offset,int origin)556 static off_t _ftp_seek (input_plugin_t *this_gen, off_t offset, int origin)
557 {
558 ftp_input_plugin_t *this = (ftp_input_plugin_t *) this_gen;
559
560 off_t r = _x_input_seek_preview(this_gen, offset, origin,
561 &this->curpos, this->file_size, this->preview_size);
562
563 if (r < 0 && this->cap_rest) {
564 offset = _x_input_translate_seek(offset, origin, this->curpos, this->file_size);
565 if (offset < 0)
566 return -1;
567
568 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
569 "restarting transfer (seek from %" PRIu64 " to %" PRIu64 "\n",
570 (uint64_t)this->curpos, (uint64_t)offset);
571
572 if (_abor(this) < 0)
573 return -1;
574 if (_retr(this, this->uri, offset) < 0)
575 return 0;
576 this->preview_size = 0;
577 return this->curpos;
578 }
579
580 return r;
581 }
582
_ftp_get_mrl(input_plugin_t * this_gen)583 static const char *_ftp_get_mrl (input_plugin_t *this_gen)
584 {
585 ftp_input_plugin_t *this = (ftp_input_plugin_t *) this_gen;
586
587 return this->mrl;
588 }
589
_ftp_get_optional_data(input_plugin_t * this_gen,void * data,int data_type)590 static int _ftp_get_optional_data (input_plugin_t *this_gen, void *data, int data_type)
591 {
592 ftp_input_plugin_t *this = (ftp_input_plugin_t *)this_gen;
593
594 switch (data_type) {
595 case INPUT_OPTIONAL_DATA_PREVIEW:
596 if (!data || (this->preview_size <= 0))
597 break;
598 memcpy (data, this->preview, this->preview_size);
599 return this->preview_size;
600 case INPUT_OPTIONAL_DATA_SIZED_PREVIEW:
601 if (!data || (this->preview_size <= 0))
602 break;
603 {
604 int want;
605 memcpy (&want, data, sizeof (want));
606 want = want < 0 ? 0
607 : want > this->preview_size ? this->preview_size
608 : want;
609 memcpy (data, this->preview, want);
610 return want;
611 }
612 }
613
614 return INPUT_OPTIONAL_UNSUPPORTED;
615 }
616
_ftp_dispose(input_plugin_t * this_gen)617 static void _ftp_dispose (input_plugin_t *this_gen)
618 {
619 ftp_input_plugin_t *this = (ftp_input_plugin_t *) this_gen;
620
621 if (this->fd_data >= 0) {
622 _x_io_tcp_close(this->stream, this->fd_data);
623 this->fd_data = -1;
624 }
625
626 _x_tls_close(&this->tls);
627
628 if (this->nbc) {
629 nbc_close(this->nbc);
630 this->nbc = NULL;
631 }
632
633 _x_freep (&this->mrl);
634 _x_freep (&this->uri);
635 _x_freep_wipe_string(&this->mrl_private);
636
637 free (this_gen);
638 }
639
_fill_preview(ftp_input_plugin_t * this)640 static int _fill_preview(ftp_input_plugin_t *this)
641 {
642 off_t got;
643
644 got = _ftp_read (&this->input_plugin, this->preview, sizeof(this->preview));
645 if (got < 1 || got > (off_t)sizeof (this->preview)) {
646 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
647 "Unable to read preview data\n");
648 return -1;
649 }
650
651 this->preview_size = got;
652 return 0;
653 }
654
_ftp_open(input_plugin_t * this_gen)655 static int _ftp_open (input_plugin_t *this_gen)
656 {
657 ftp_input_plugin_t *this = (ftp_input_plugin_t *)this_gen;
658 xine_url_t url;
659 int rc, result = 0;
660
661 /* parse mrl */
662 rc = _x_url_parse2(this->mrl_private, &url);
663 _x_freep_wipe_string(&this->mrl_private);
664 if (!rc) {
665 _x_message(this->stream, XINE_MSG_GENERAL_WARNING, "malformed url", NULL);
666 return 0;
667 }
668
669 this->curpos = 0;
670
671 rc = _ftp_connect(this, &url);
672 if (rc < 0)
673 goto out;
674
675 _ftp_size(this, url.uri);
676
677 rc = _retr(this, url.uri, 0);
678 if (rc < 0)
679 goto out;
680
681 rc = _fill_preview(this);
682 if (rc < 0)
683 goto out;
684
685 /* save URI for seeking (= ABRT + REST + RETR) */
686 this->uri = strdup(url.uri);
687 if (!this->uri)
688 goto out;
689
690 result = 1;
691
692 out:
693 _x_url_cleanup(&url);
694 return result;
695 }
696
_ftp_get_capabilities(input_plugin_t * this_gen)697 static uint32_t _ftp_get_capabilities (input_plugin_t *this_gen)
698 {
699 ftp_input_plugin_t *this = (ftp_input_plugin_t *)this_gen;
700
701 return INPUT_CAP_PREVIEW | INPUT_CAP_SIZED_PREVIEW | (this->cap_rest ? INPUT_CAP_SLOW_SEEKABLE : 0);
702 }
703
_get_instance(input_class_t * cls_gen,xine_stream_t * stream,const char * mrl)704 static input_plugin_t *_get_instance (input_class_t *cls_gen, xine_stream_t *stream, const char *mrl)
705 {
706 ftp_input_class_t *class = (ftp_input_class_t *)cls_gen;
707 ftp_input_plugin_t *this;
708
709 if (strncasecmp (mrl, "ftp://", 6) &&
710 strncasecmp (mrl, "ftpes://", 8)) {
711 return NULL;
712 }
713
714 this = calloc(1, sizeof(*this));
715 if (!this) {
716 return NULL;
717 }
718
719 this->mrl_private = strdup(mrl);
720 this->mrl = _x_mrl_remove_auth(mrl);
721 this->stream = stream;
722 this->xine = class->xine;
723 this->curpos = 0;
724 this->tls = NULL;
725 this->fd_data = -1;
726
727 this->input_plugin.open = _ftp_open;
728 this->input_plugin.get_capabilities = _ftp_get_capabilities;
729 this->input_plugin.read = _ftp_read;
730 this->input_plugin.read_block = _x_input_default_read_block;
731 this->input_plugin.seek = _ftp_seek;
732 this->input_plugin.get_current_pos = _ftp_get_current_pos;
733 this->input_plugin.get_length = _ftp_get_length;
734 this->input_plugin.get_blocksize = _x_input_default_get_blocksize;
735 this->input_plugin.get_mrl = _ftp_get_mrl;
736 this->input_plugin.get_optional_data = _ftp_get_optional_data;
737 this->input_plugin.dispose = _ftp_dispose;
738 this->input_plugin.input_class = cls_gen;
739
740 if (stream) {
741 /* not needed for directory browsing */
742 this->nbc = nbc_init (stream);
743 }
744
745 return &this->input_plugin;
746 }
747
748 /*
749 * plugin class
750 */
751
_get_files(ftp_input_plugin_t * this,const char * uri,int * nFiles)752 static xine_mrl_t **_get_files(ftp_input_plugin_t *this, const char *uri, int *nFiles)
753 {
754 xine_mrl_t **mrls;
755 size_t n = 0, mrls_size = 64;
756 int show_hidden_files;
757 int rc;
758
759 /* change working directory */
760 if (uri[0] && (uri[0] != '/' || uri[1])) {
761 rc = _cwd(this, uri[0] == '/' ? uri+1 : uri);
762 if (rc < 0)
763 return NULL;
764 }
765
766 /* open directory */
767 if (_list(this) < 0)
768 return NULL;
769
770 mrls = _x_input_alloc_mrls(mrls_size);
771 if (!mrls)
772 return NULL;
773
774 /* add ".." entry */
775 mrls[n]->type = mrl_net | mrl_file | mrl_file_directory;
776 mrls[n]->origin = strdup(this->mrl);
777 mrls[n]->mrl = _x_asprintf("%s/..", this->mrl);
778 n++;
779
780 show_hidden_files = _x_input_get_show_hidden_files(this->xine->config);
781
782 while (1) {
783 char buf[1024], *file;
784 int is_dir = 0;
785
786 rc = _x_io_tcp_read_line(this->stream, this->fd_data, buf, sizeof(buf));
787 if (rc <= 0) {
788 if (rc < 0)
789 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
790 "FTP directory read failed\n");
791 break;
792 }
793 lprintf("<<< '%s'\n", buf);
794
795 /* strip file info, try to detect directories */
796 file = strrchr(buf, ' ');
797 if (!file) {
798 file = buf;
799 } else {
800 *file++ = 0;
801 if (buf[0] == 'd' || strstr(buf, "<DIR>"))
802 is_dir = 1;
803 }
804
805 if (!show_hidden_files && file[0] == '.')
806 continue;
807
808 if (n >= mrls_size) {
809 mrls_size = mrls_size ? 2*mrls_size : 100;
810 if (!(_x_input_realloc_mrls(&mrls, mrls_size))) {
811 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
812 "out of memory while listing directory '%s'\n",
813 uri);
814 break;
815 }
816 }
817
818 if (is_dir)
819 mrls[n]->type = mrl_net | mrl_file | mrl_file_directory;
820 else
821 mrls[n]->type = mrl_net | mrl_file | mrl_file_normal;
822 mrls[n]->origin = _x_asprintf("%s/", this->mrl);
823 mrls[n]->mrl = _x_asprintf("%s/%s", this->mrl, file);
824 n++;
825 }
826
827 if (n > 2)
828 _x_input_sort_mrls(mrls + 1, n - 1);
829
830 *nFiles = n;
831 return mrls;
832 }
833
_get_dir_common(input_class_t * this_gen,const char * filename,int * nFiles)834 static xine_mrl_t **_get_dir_common (input_class_t *this_gen, const char *filename, int *nFiles)
835 {
836 ftp_input_class_t *this = (ftp_input_class_t *) this_gen;
837 ftp_input_plugin_t *input;
838 xine_url_t url;
839
840 _x_assert(filename != NULL);
841
842 if (!_x_url_parse2(filename, &url)) {
843 xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
844 "malformed url '%s'", filename);
845 return NULL;
846 }
847
848 input = (ftp_input_plugin_t *)_get_instance(this_gen, NULL, filename);
849 if (!input)
850 goto out;
851
852 if (_ftp_connect(input, &url) < 0)
853 goto out;
854
855 this->mrls = _get_files(input, url.uri, nFiles);
856
857 out:
858 _x_url_cleanup(&url);
859 if (input)
860 input->input_plugin.dispose(&input->input_plugin);
861 return this->mrls;
862 }
863
_get_dir(input_class_t * this_gen,const char * filename,int * nFiles)864 static xine_mrl_t **_get_dir (input_class_t *this_gen, const char *filename, int *nFiles)
865 {
866 ftp_input_class_t *this = (ftp_input_class_t *) this_gen;
867
868 *nFiles = 0;
869 _x_input_free_mrls(&this->mrls);
870
871 if (!filename || !strcmp(filename, "ftp:/") || !strcmp(filename, "ftp://")) {
872 this->mrls = _x_input_get_default_server_mrls(this->xine->config, "ftp:/", nFiles);
873 return this->mrls;
874 }
875
876 return _get_dir_common(this_gen, filename, nFiles);
877 }
878
_get_dir_es(input_class_t * this_gen,const char * filename,int * nFiles)879 static xine_mrl_t **_get_dir_es (input_class_t *this_gen, const char *filename, int *nFiles)
880 {
881 ftp_input_class_t *this = (ftp_input_class_t *) this_gen;
882
883 *nFiles = 0;
884 _x_input_free_mrls(&this->mrls);
885
886 if (!filename || !strcmp(filename, "ftpes:/") || !strcmp(filename, "ftpes://")) {
887 this->mrls = _x_input_get_default_server_mrls(this->xine->config, "ftpes:/", nFiles);
888 return this->mrls;
889 }
890
891 return _get_dir_common(this_gen, filename, nFiles);
892 }
893
_dispose_class(input_class_t * this_gen)894 static void _dispose_class (input_class_t *this_gen)
895 {
896 ftp_input_class_t *this = (ftp_input_class_t *)this_gen;
897
898 _x_input_free_mrls(&this->mrls);
899
900 free(this_gen);
901 }
902
input_ftp_init_class(xine_t * xine,const void * data)903 void *input_ftp_init_class(xine_t *xine, const void *data)
904 {
905 ftp_input_class_t *this;
906
907 (void)data;
908 this = calloc(1, sizeof(*this));
909 if (!this)
910 return NULL;
911
912 this->xine = xine;
913
914 this->input_class.get_instance = _get_instance;
915 this->input_class.description = N_("FTP input plugin");
916 this->input_class.identifier = "FTP";
917 this->input_class.get_dir = _get_dir;
918 this->input_class.get_autoplay_list = NULL;
919 this->input_class.dispose = _dispose_class;
920 this->input_class.eject_media = NULL;
921
922 _x_input_register_show_hidden_files(xine->config);
923 _x_input_register_default_servers(xine->config);
924
925 return this;
926 }
927
input_ftpes_init_class(xine_t * xine,const void * data)928 void *input_ftpes_init_class(xine_t *xine, const void *data)
929 {
930 ftp_input_class_t *this;
931
932 this = input_ftp_init_class(xine, data);
933 if (this) {
934 this->input_class.description = N_("FTPES input plugin");
935 this->input_class.identifier = "FTPES";
936 this->input_class.get_dir = _get_dir_es;
937 }
938
939 return this;
940 }
941