Fix:
[mplayer/glamo.git] / libmpdemux / stream_ftp.c
blob2a23fd2d3db3c21ef969c17005257615972a3ae0
2 #include "config.h"
4 #ifdef HAVE_FTP
6 #include <stdlib.h>
7 #include <stdio.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <errno.h>
14 #ifndef HAVE_WINSOCK2
15 #include <sys/socket.h>
16 #define closesocket close
17 #else
18 #include <winsock2.h>
19 #endif
21 #include "mp_msg.h"
22 #include "stream.h"
23 #include "help_mp.h"
24 #include "m_option.h"
25 #include "m_struct.h"
27 static struct stream_priv_s {
28 char* user;
29 char* pass;
30 char* host;
31 int port;
32 char* filename;
34 char *cput,*cget;
35 int handle;
36 int cavail,cleft;
37 char *buf;
38 } stream_priv_dflts = {
39 "anonymous","no@spam",
40 NULL,
41 21,
42 NULL,
43 NULL,
44 NULL,
47 0,0,
48 NULL
51 #define BUFSIZE 2048
53 #define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
54 /// URL definition
55 static m_option_t stream_opts_fields[] = {
56 {"username", ST_OFF(user), CONF_TYPE_STRING, 0, 0 ,0, NULL},
57 {"password", ST_OFF(pass), CONF_TYPE_STRING, 0, 0 ,0, NULL},
58 {"hostname", ST_OFF(host), CONF_TYPE_STRING, 0, 0 ,0, NULL},
59 {"port", ST_OFF(port), CONF_TYPE_INT, 0, 0 ,65635, NULL},
60 {"filename", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 ,0, NULL},
61 { NULL, NULL, 0, 0, 0, 0, NULL }
63 static struct m_struct_st stream_opts = {
64 "ftp",
65 sizeof(struct stream_priv_s),
66 &stream_priv_dflts,
67 stream_opts_fields
70 #define TELNET_IAC 255 /* interpret as command: */
71 #define TELNET_IP 244 /* interrupt process--permanently */
72 #define TELNET_SYNCH 242 /* for telfunc calls */
74 // Check if there is something to read on a fd. This avoid hanging
75 // forever if the network stop responding.
76 static int fd_can_read(int fd,int timeout) {
77 fd_set fds;
78 struct timeval tv;
80 FD_ZERO(&fds);
81 FD_SET(fd,&fds);
82 tv.tv_sec = timeout;
83 tv.tv_usec = 0;
85 return (select(fd+1, &fds, NULL, NULL, &tv) > 0);
89 * read a line of text
91 * return -1 on error or bytecount
93 static int readline(char *buf,int max,struct stream_priv_s *ctl)
95 int x,retval = 0;
96 char *end,*bp=buf;
97 int eof = 0;
99 do {
100 if (ctl->cavail > 0) {
101 x = (max >= ctl->cavail) ? ctl->cavail : max-1;
102 end = memccpy(bp,ctl->cget,'\n',x);
103 if (end != NULL)
104 x = end - bp;
105 retval += x;
106 bp += x;
107 *bp = '\0';
108 max -= x;
109 ctl->cget += x;
110 ctl->cavail -= x;
111 if (end != NULL) {
112 bp -= 2;
113 if (strcmp(bp,"\r\n") == 0) {
114 *bp++ = '\n';
115 *bp++ = '\0';
116 --retval;
118 break;
121 if (max == 1) {
122 *buf = '\0';
123 break;
125 if (ctl->cput == ctl->cget) {
126 ctl->cput = ctl->cget = ctl->buf;
127 ctl->cavail = 0;
128 ctl->cleft = BUFSIZE;
130 if(eof) {
131 if (retval == 0)
132 retval = -1;
133 break;
136 if(!fd_can_read(ctl->handle, 15)) {
137 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n");
138 retval = -1;
139 break;
142 if ((x = recv(ctl->handle,ctl->cput,ctl->cleft,0)) == -1) {
143 mp_msg(MSGT_STREAM,MSGL_ERR, "[ftp] read error: %s\n",strerror(errno));
144 retval = -1;
145 break;
147 if (x == 0)
148 eof = 1;
149 ctl->cleft -= x;
150 ctl->cavail += x;
151 ctl->cput += x;
152 } while (1);
154 return retval;
158 * read a response from the server
160 * return 0 if first char doesn't match
161 * return 1 if first char matches
163 static int readresp(struct stream_priv_s* ctl,char* rsp)
165 static char response[256];
166 char match[5];
167 int r;
169 if (readline(response,256,ctl) == -1)
170 return 0;
172 r = atoi(response)/100;
173 if(rsp) strcpy(rsp,response);
175 mp_msg(MSGT_STREAM,MSGL_V, "[ftp] < %s",response);
177 if (response[3] == '-') {
178 strncpy(match,response,3);
179 match[3] = ' ';
180 match[4] = '\0';
181 do {
182 if (readline(response,256,ctl) == -1) {
183 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Control socket read failed\n");
184 return 0;
186 mp_msg(MSGT_OPEN,MSGL_V, "[ftp] < %s",response);
187 } while (strncmp(response,match,4));
189 return r;
193 static int FtpSendCmd(const char *cmd, struct stream_priv_s *nControl,char* rsp)
195 int l = strlen(cmd);
196 int hascrlf = cmd[l - 2] == '\r' && cmd[l - 1] == '\n';
198 if(hascrlf && l == 2) mp_msg(MSGT_STREAM,MSGL_V, "\n");
199 else mp_msg(MSGT_STREAM,MSGL_V, "[ftp] > %s",cmd);
200 while(l > 0) {
201 int s = send(nControl->handle,cmd,l,0);
203 if(s <= 0) {
204 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] write error: %s\n",strerror(errno));
205 return 0;
208 cmd += s;
209 l -= s;
212 if (hascrlf)
213 return readresp(nControl,rsp);
214 else
215 return FtpSendCmd("\r\n", nControl, rsp);
218 static int FtpOpenPort(struct stream_priv_s* p) {
219 int resp,fd;
220 char rsp_txt[256];
221 char* par,str[128];
222 int num[6];
224 resp = FtpSendCmd("PASV",p,rsp_txt);
225 if(resp != 2) {
226 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'PASV' failed: %s\n",rsp_txt);
227 return 0;
230 par = strchr(rsp_txt,'(');
232 if(!par || !par[0] || !par[1]) {
233 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] invalid server response: %s ??\n",rsp_txt);
234 return 0;
237 sscanf(par+1,"%u,%u,%u,%u,%u,%u",&num[0],&num[1],&num[2],
238 &num[3],&num[4],&num[5]);
239 snprintf(str,127,"%d.%d.%d.%d",num[0],num[1],num[2],num[3]);
240 fd = connect2Server(str,(num[4]<<8)+num[5],0);
242 if(fd < 0)
243 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] failed to create data connection\n");
245 return fd;
248 static int FtpOpenData(stream_t* s,size_t newpos) {
249 struct stream_priv_s* p = s->priv;
250 int resp;
251 char str[256],rsp_txt[256];
253 // Open a new connection
254 s->fd = FtpOpenPort(p);
256 if(s->fd < 0) return 0;
258 if(newpos > 0) {
259 snprintf(str,255,"REST %"PRId64, (int64_t)newpos);
261 resp = FtpSendCmd(str,p,rsp_txt);
262 if(resp != 3) {
263 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
264 newpos = 0;
268 // Get the file
269 snprintf(str,255,"RETR %s",p->filename);
270 resp = FtpSendCmd(str,p,rsp_txt);
272 if(resp != 1) {
273 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
274 return 0;
277 s->pos = newpos;
278 return 1;
281 static int fill_buffer(stream_t *s, char* buffer, int max_len){
282 int r;
284 if(s->fd < 0 && !FtpOpenData(s,s->pos))
285 return -1;
287 if(!fd_can_read(s->fd, 15)) {
288 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n");
289 return -1;
292 r = recv(s->fd,buffer,max_len,0);
293 return (r <= 0) ? -1 : r;
296 static int seek(stream_t *s,off_t newpos) {
297 struct stream_priv_s* p = s->priv;
298 int resp;
299 char rsp_txt[256];
301 if(s->pos > s->end_pos) {
302 s->eof=1;
303 return 0;
306 // Check to see if the server doesn't alredy terminated the transfert
307 if(fd_can_read(p->handle, 0)) {
308 if(readresp(p,rsp_txt) != 2)
309 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] Warning the server didn't finished the transfert correctly: %s\n",rsp_txt);
310 closesocket(s->fd);
311 s->fd = -1;
314 // Close current download
315 if(s->fd >= 0) {
316 static const char pre_cmd[]={TELNET_IAC,TELNET_IP,TELNET_IAC,TELNET_SYNCH};
317 //int fl;
319 // First close the fd
320 closesocket(s->fd);
321 s->fd = 0;
323 // Send send the telnet sequence needed to make the server react
325 // Dunno if this is really needed, lftp have it. I let
326 // it here in case it turn out to be needed on some other OS
327 //fl=fcntl(p->handle,F_GETFL);
328 //fcntl(p->handle,F_SETFL,fl&~O_NONBLOCK);
330 // send only first byte as OOB due to OOB braindamage in many unices
331 send(p->handle,pre_cmd,1,MSG_OOB);
332 send(p->handle,pre_cmd+1,sizeof(pre_cmd)-1,0);
334 //fcntl(p->handle,F_SETFL,fl);
336 // Get the 426 Transfer aborted
337 // Or the 226 Transfer complete
338 resp = readresp(p,rsp_txt);
339 if(resp != 4 && resp != 2) {
340 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Server didn't abort correctly: %s\n",rsp_txt);
341 s->eof = 1;
342 return 0;
344 // Send the ABOR command
345 // Ignore the return code as sometimes it fail with "nothing to abort"
346 FtpSendCmd("ABOR",p,rsp_txt);
348 return FtpOpenData(s,newpos);
352 static void close_f(stream_t *s) {
353 struct stream_priv_s* p = s->priv;
355 if(!p) return;
357 if(s->fd > 0) {
358 closesocket(s->fd);
359 s->fd = 0;
362 FtpSendCmd("QUIT",p,NULL);
364 if(p->handle) closesocket(p->handle);
365 if(p->buf) free(p->buf);
367 m_struct_free(&stream_opts,p);
372 static int open_f(stream_t *stream,int mode, void* opts, int* file_format) {
373 int len = 0,resp;
374 struct stream_priv_s* p = (struct stream_priv_s*)opts;
375 char str[256],rsp_txt[256];
377 if(mode != STREAM_READ) {
378 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Unknown open mode %d\n",mode);
379 m_struct_free(&stream_opts,opts);
380 return STREAM_UNSUPORTED;
383 if(!p->filename || !p->host) {
384 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Bad url\n");
385 m_struct_free(&stream_opts,opts);
386 return STREAM_ERROR;
389 // Open the control connection
390 p->handle = connect2Server(p->host,p->port,1);
392 if(p->handle < 0) {
393 m_struct_free(&stream_opts,opts);
394 return STREAM_ERROR;
397 // We got a connection, let's start serious things
398 stream->fd = -1;
399 stream->priv = p;
400 p->buf = malloc(BUFSIZE);
402 if (readresp(p, NULL) == 0) {
403 close_f(stream);
404 m_struct_free(&stream_opts,opts);
405 return STREAM_ERROR;
408 // Login
409 snprintf(str,255,"USER %s",p->user);
410 resp = FtpSendCmd(str,p,rsp_txt);
412 // password needed
413 if(resp == 3) {
414 snprintf(str,255,"PASS %s",p->pass);
415 resp = FtpSendCmd(str,p,rsp_txt);
416 if(resp != 2) {
417 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
418 close_f(stream);
419 return STREAM_ERROR;
421 } else if(resp != 2) {
422 mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
423 close_f(stream);
424 return STREAM_ERROR;
427 // Set the transfert type
428 resp = FtpSendCmd("TYPE I",p,rsp_txt);
429 if(resp != 2) {
430 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'TYPE I' failed: %s\n",rsp_txt);
431 close_f(stream);
432 return STREAM_ERROR;
435 // Get the filesize
436 snprintf(str,255,"SIZE %s",p->filename);
437 resp = FtpSendCmd(str,p,rsp_txt);
438 if(resp != 2) {
439 mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt);
440 } else {
441 int dummy;
442 sscanf(rsp_txt,"%d %d",&dummy,&len);
445 if(len > 0) {
446 stream->seek = seek;
447 stream->end_pos = len;
450 // The data connection is really opened only at the first
451 // read/seek. This must be done when the cache is used
452 // because the connection would stay open in the main process,
453 // preventing correct abort with many servers.
454 stream->fd = -1;
455 stream->priv = p;
456 stream->fill_buffer = fill_buffer;
457 stream->close = close_f;
459 return STREAM_OK;
462 stream_info_t stream_info_ftp = {
463 "File Transfer Protocol",
464 "ftp",
465 "Albeu",
466 "reuse a bit of code from ftplib written by Thomas Pfau",
467 open_f,
468 { "ftp", NULL },
469 &stream_opts,
470 1 // Urls are an option string
473 #endif