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