Mercurial > mplayer.hg
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stream/stream_ftp.c Mon Jul 31 17:39:17 2006 +0000 @@ -0,0 +1,469 @@ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#ifndef HAVE_WINSOCK2 +#include <sys/socket.h> +#define closesocket close +#else +#include <winsock2.h> +#endif + +#include "mp_msg.h" +#include "stream.h" +#include "help_mp.h" +#include "m_option.h" +#include "m_struct.h" + +static struct stream_priv_s { + char* user; + char* pass; + char* host; + int port; + char* filename; + + char *cput,*cget; + int handle; + int cavail,cleft; + char *buf; +} stream_priv_dflts = { + "anonymous","no@spam", + NULL, + 21, + NULL, + NULL, + NULL, + + 0, + 0,0, + NULL +}; + +#define BUFSIZE 2048 + +#define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f) +/// URL definition +static m_option_t stream_opts_fields[] = { + {"username", ST_OFF(user), CONF_TYPE_STRING, 0, 0 ,0, NULL}, + {"password", ST_OFF(pass), CONF_TYPE_STRING, 0, 0 ,0, NULL}, + {"hostname", ST_OFF(host), CONF_TYPE_STRING, 0, 0 ,0, NULL}, + {"port", ST_OFF(port), CONF_TYPE_INT, 0, 0 ,65635, NULL}, + {"filename", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; +static struct m_struct_st stream_opts = { + "ftp", + sizeof(struct stream_priv_s), + &stream_priv_dflts, + stream_opts_fields +}; + +#define TELNET_IAC 255 /* interpret as command: */ +#define TELNET_IP 244 /* interrupt process--permanently */ +#define TELNET_SYNCH 242 /* for telfunc calls */ + +// Check if there is something to read on a fd. This avoid hanging +// forever if the network stop responding. +static int fd_can_read(int fd,int timeout) { + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + FD_SET(fd,&fds); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return (select(fd+1, &fds, NULL, NULL, &tv) > 0); +} + +/* + * read a line of text + * + * return -1 on error or bytecount + */ +static int readline(char *buf,int max,struct stream_priv_s *ctl) +{ + int x,retval = 0; + char *end,*bp=buf; + int eof = 0; + + do { + if (ctl->cavail > 0) { + x = (max >= ctl->cavail) ? ctl->cavail : max-1; + end = memccpy(bp,ctl->cget,'\n',x); + if (end != NULL) + x = end - bp; + retval += x; + bp += x; + *bp = '\0'; + max -= x; + ctl->cget += x; + ctl->cavail -= x; + if (end != NULL) { + bp -= 2; + if (strcmp(bp,"\r\n") == 0) { + *bp++ = '\n'; + *bp++ = '\0'; + --retval; + } + break; + } + } + if (max == 1) { + *buf = '\0'; + break; + } + if (ctl->cput == ctl->cget) { + ctl->cput = ctl->cget = ctl->buf; + ctl->cavail = 0; + ctl->cleft = BUFSIZE; + } + if(eof) { + if (retval == 0) + retval = -1; + break; + } + + if(!fd_can_read(ctl->handle, 15)) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n"); + retval = -1; + break; + } + + if ((x = recv(ctl->handle,ctl->cput,ctl->cleft,0)) == -1) { + mp_msg(MSGT_STREAM,MSGL_ERR, "[ftp] read error: %s\n",strerror(errno)); + retval = -1; + break; + } + if (x == 0) + eof = 1; + ctl->cleft -= x; + ctl->cavail += x; + ctl->cput += x; + } while (1); + + return retval; +} + +/* + * read a response from the server + * + * return 0 if first char doesn't match + * return 1 if first char matches + */ +static int readresp(struct stream_priv_s* ctl,char* rsp) +{ + static char response[256]; + char match[5]; + int r; + + if (readline(response,256,ctl) == -1) + return 0; + + r = atoi(response)/100; + if(rsp) strcpy(rsp,response); + + mp_msg(MSGT_STREAM,MSGL_V, "[ftp] < %s",response); + + if (response[3] == '-') { + strncpy(match,response,3); + match[3] = ' '; + match[4] = '\0'; + do { + if (readline(response,256,ctl) == -1) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Control socket read failed\n"); + return 0; + } + mp_msg(MSGT_OPEN,MSGL_V, "[ftp] < %s",response); + } while (strncmp(response,match,4)); + } + return r; +} + + +static int FtpSendCmd(const char *cmd, struct stream_priv_s *nControl,char* rsp) +{ + int l = strlen(cmd); + int hascrlf = cmd[l - 2] == '\r' && cmd[l - 1] == '\n'; + + if(hascrlf && l == 2) mp_msg(MSGT_STREAM,MSGL_V, "\n"); + else mp_msg(MSGT_STREAM,MSGL_V, "[ftp] > %s",cmd); + while(l > 0) { + int s = send(nControl->handle,cmd,l,0); + + if(s <= 0) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] write error: %s\n",strerror(errno)); + return 0; + } + + cmd += s; + l -= s; + } + + if (hascrlf) + return readresp(nControl,rsp); + else + return FtpSendCmd("\r\n", nControl, rsp); +} + +static int FtpOpenPort(struct stream_priv_s* p) { + int resp,fd; + char rsp_txt[256]; + char* par,str[128]; + int num[6]; + + resp = FtpSendCmd("PASV",p,rsp_txt); + if(resp != 2) { + mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'PASV' failed: %s\n",rsp_txt); + return 0; + } + + par = strchr(rsp_txt,'('); + + if(!par || !par[0] || !par[1]) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] invalid server response: %s ??\n",rsp_txt); + return 0; + } + + sscanf(par+1,"%u,%u,%u,%u,%u,%u",&num[0],&num[1],&num[2], + &num[3],&num[4],&num[5]); + snprintf(str,127,"%d.%d.%d.%d",num[0],num[1],num[2],num[3]); + fd = connect2Server(str,(num[4]<<8)+num[5],0); + + if(fd < 0) + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] failed to create data connection\n"); + + return fd; +} + +static int FtpOpenData(stream_t* s,size_t newpos) { + struct stream_priv_s* p = s->priv; + int resp; + char str[256],rsp_txt[256]; + + // Open a new connection + s->fd = FtpOpenPort(p); + + if(s->fd < 0) return 0; + + if(newpos > 0) { + snprintf(str,255,"REST %"PRId64, (int64_t)newpos); + + resp = FtpSendCmd(str,p,rsp_txt); + if(resp != 3) { + mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt); + newpos = 0; + } + } + + // Get the file + snprintf(str,255,"RETR %s",p->filename); + resp = FtpSendCmd(str,p,rsp_txt); + + if(resp != 1) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt); + return 0; + } + + s->pos = newpos; + return 1; +} + +static int fill_buffer(stream_t *s, char* buffer, int max_len){ + int r; + + if(s->fd < 0 && !FtpOpenData(s,s->pos)) + return -1; + + if(!fd_can_read(s->fd, 15)) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] read timed out\n"); + return -1; + } + + r = recv(s->fd,buffer,max_len,0); + return (r <= 0) ? -1 : r; +} + +static int seek(stream_t *s,off_t newpos) { + struct stream_priv_s* p = s->priv; + int resp; + char rsp_txt[256]; + + if(s->pos > s->end_pos) { + s->eof=1; + return 0; + } + + // Check to see if the server doesn't alredy terminated the transfert + if(fd_can_read(p->handle, 0)) { + if(readresp(p,rsp_txt) != 2) + mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] Warning the server didn't finished the transfert correctly: %s\n",rsp_txt); + closesocket(s->fd); + s->fd = -1; + } + + // Close current download + if(s->fd >= 0) { + static const char pre_cmd[]={TELNET_IAC,TELNET_IP,TELNET_IAC,TELNET_SYNCH}; + //int fl; + + // First close the fd + closesocket(s->fd); + s->fd = 0; + + // Send send the telnet sequence needed to make the server react + + // Dunno if this is really needed, lftp have it. I let + // it here in case it turn out to be needed on some other OS + //fl=fcntl(p->handle,F_GETFL); + //fcntl(p->handle,F_SETFL,fl&~O_NONBLOCK); + + // send only first byte as OOB due to OOB braindamage in many unices + send(p->handle,pre_cmd,1,MSG_OOB); + send(p->handle,pre_cmd+1,sizeof(pre_cmd)-1,0); + + //fcntl(p->handle,F_SETFL,fl); + + // Get the 426 Transfer aborted + // Or the 226 Transfer complete + resp = readresp(p,rsp_txt); + if(resp != 4 && resp != 2) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Server didn't abort correctly: %s\n",rsp_txt); + s->eof = 1; + return 0; + } + // Send the ABOR command + // Ignore the return code as sometimes it fail with "nothing to abort" + FtpSendCmd("ABOR",p,rsp_txt); + } + return FtpOpenData(s,newpos); +} + + +static void close_f(stream_t *s) { + struct stream_priv_s* p = s->priv; + + if(!p) return; + + if(s->fd > 0) { + closesocket(s->fd); + s->fd = 0; + } + + FtpSendCmd("QUIT",p,NULL); + + if(p->handle) closesocket(p->handle); + if(p->buf) free(p->buf); + + m_struct_free(&stream_opts,p); +} + + + +static int open_f(stream_t *stream,int mode, void* opts, int* file_format) { + int len = 0,resp; + struct stream_priv_s* p = (struct stream_priv_s*)opts; + char str[256],rsp_txt[256]; + + if(mode != STREAM_READ) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Unknown open mode %d\n",mode); + m_struct_free(&stream_opts,opts); + return STREAM_UNSUPORTED; + } + + if(!p->filename || !p->host) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] Bad url\n"); + m_struct_free(&stream_opts,opts); + return STREAM_ERROR; + } + + // Open the control connection + p->handle = connect2Server(p->host,p->port,1); + + if(p->handle < 0) { + m_struct_free(&stream_opts,opts); + return STREAM_ERROR; + } + + // We got a connection, let's start serious things + stream->fd = -1; + stream->priv = p; + p->buf = malloc(BUFSIZE); + + if (readresp(p, NULL) == 0) { + close_f(stream); + m_struct_free(&stream_opts,opts); + return STREAM_ERROR; + } + + // Login + snprintf(str,255,"USER %s",p->user); + resp = FtpSendCmd(str,p,rsp_txt); + + // password needed + if(resp == 3) { + snprintf(str,255,"PASS %s",p->pass); + resp = FtpSendCmd(str,p,rsp_txt); + if(resp != 2) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt); + close_f(stream); + return STREAM_ERROR; + } + } else if(resp != 2) { + mp_msg(MSGT_OPEN,MSGL_ERR, "[ftp] command '%s' failed: %s\n",str,rsp_txt); + close_f(stream); + return STREAM_ERROR; + } + + // Set the transfert type + resp = FtpSendCmd("TYPE I",p,rsp_txt); + if(resp != 2) { + mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command 'TYPE I' failed: %s\n",rsp_txt); + close_f(stream); + return STREAM_ERROR; + } + + // Get the filesize + snprintf(str,255,"SIZE %s",p->filename); + resp = FtpSendCmd(str,p,rsp_txt); + if(resp != 2) { + mp_msg(MSGT_OPEN,MSGL_WARN, "[ftp] command '%s' failed: %s\n",str,rsp_txt); + } else { + int dummy; + sscanf(rsp_txt,"%d %d",&dummy,&len); + } + + if(len > 0) { + stream->seek = seek; + stream->end_pos = len; + } + + // The data connection is really opened only at the first + // read/seek. This must be done when the cache is used + // because the connection would stay open in the main process, + // preventing correct abort with many servers. + stream->fd = -1; + stream->priv = p; + stream->fill_buffer = fill_buffer; + stream->close = close_f; + + return STREAM_OK; +} + +stream_info_t stream_info_ftp = { + "File Transfer Protocol", + "ftp", + "Albeu", + "reuse a bit of code from ftplib written by Thomas Pfau", + open_f, + { "ftp", NULL }, + &stream_opts, + 1 // Urls are an option string +};