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