]> icculus.org git repositories - taylor/freespace2.git/blob - src/inetfile/cftp.cpp
Initial revision
[taylor/freespace2.git] / src / inetfile / cftp.cpp
1  /*
2  * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
3  * $Revision$
4  * $Date$
5  *  $Author$
6  *
7  * FTP Client class (get only)
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:09  root
11  * Initial revision
12  *
13  * 
14  * 3     5/04/99 7:34p Dave
15  * Fixed slow HTTP get problem.
16  * 
17  * 2     4/20/99 6:39p Dave
18  * Almost done with artillery targeting. Added support for downloading
19  * images on the PXO screen.
20  * 
21  * 1     4/20/99 4:37p Dave
22  * 
23  * Initial version
24  *
25  * $NoKeywords: $
26  */
27 #include <windows.h>
28 #include <process.h>
29 #include <stdio.h>
30
31 #include "cftp.h"
32
33 void FTPObjThread( void * obj )
34 {
35         ((CFtpGet *)obj)->WorkerThread();
36 }
37
38 void CFtpGet::AbortGet()
39 {
40         m_Aborting = true;
41         while(!m_Aborted) ; //Wait for the thread to end
42         fclose(LOCALFILE);
43 }
44
45 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
46 {
47         SOCKADDR_IN listensockaddr;
48         m_State = FTP_STATE_STARTUP;
49
50         m_ListenSock = INVALID_SOCKET;
51         m_DataSock = INVALID_SOCKET;
52         m_ControlSock = INVALID_SOCKET;
53         m_iBytesIn = 0;
54         m_iBytesTotal = 0;
55         m_Aborting = false;
56         m_Aborted = false;
57
58         LOCALFILE = fopen(localfile,"wb");
59         if(NULL == LOCALFILE)
60         {
61                 m_State = FTP_STATE_CANT_WRITE_FILE;
62                 return;
63         }
64
65         if(Username)
66         {
67                 strcpy(m_szUserName,Username);
68         }
69         else
70         {
71                 strcpy(m_szUserName,"anonymous");
72         }
73         if(Password)
74         {
75                 strcpy(m_szPassword,Password);
76         }
77         else
78         {
79                 strcpy(m_szPassword,"pxouser@pxo.net");
80         }
81         m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
82         if(INVALID_SOCKET == m_ListenSock)
83         {
84                 // vint iWinsockErr = WSAGetLastError();
85                 m_State = FTP_STATE_SOCKET_ERROR;
86                 return;
87         }
88         else
89         {
90                 listensockaddr.sin_family = AF_INET;            
91                 listensockaddr.sin_port = 0;
92                 listensockaddr.sin_addr.s_addr = INADDR_ANY;
93                                                         
94                 // Bind the listen socket
95                 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
96                 {
97                         //Couldn't bind the socket
98                         // int iWinsockErr = WSAGetLastError();
99                         m_State = FTP_STATE_SOCKET_ERROR;
100                         return;
101                 }
102
103                 // Listen for the server connection
104                 if (listen(m_ListenSock, 1))    
105                 {
106                         //Couldn't listen on the socket
107                         // int iWinsockErr = WSAGetLastError();
108                         m_State = FTP_STATE_SOCKET_ERROR;
109                         return;
110                 }
111         }
112         m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
113         if(INVALID_SOCKET == m_ControlSock)
114         {
115                 m_State = FTP_STATE_SOCKET_ERROR;
116                 return;
117         }
118         //Parse the URL
119         //Get rid of any extra ftp:// stuff
120         char *pURL = URL;
121         if(_strnicmp(URL,"ftp:",4)==0)
122         {
123                 pURL +=4;
124                 while(*pURL == '/')
125                 {
126                         pURL++;
127                 }
128         }
129         //There shouldn't be any : in this string
130         if(strchr(pURL,':'))
131         {
132                 m_State = FTP_STATE_URL_PARSING_ERROR;
133                 return;
134         }
135         //read the filename by searching backwards for a /
136         //then keep reading until you find the first /
137         //when you found it, you have the host and dir
138         char *filestart = NULL;
139         char *dirstart = NULL;
140         for(int i = strlen(pURL);i>=0;i--)
141         {
142                 if(pURL[i]== '/')
143                 {
144                         if(!filestart)
145                         {
146                                 filestart = pURL+i+1;
147                                 dirstart = pURL+i+1;
148                                 strcpy(m_szFilename,filestart);
149                         }
150                         else
151                         {
152                                 dirstart = pURL+i+1;
153                         }
154                 }
155         }
156         if((dirstart==NULL) || (filestart==NULL))
157         {
158                 m_State = FTP_STATE_URL_PARSING_ERROR;
159                 return;
160         }
161         else
162         {
163                 strncpy(m_szDir,dirstart,(filestart-dirstart));
164                 m_szDir[(filestart-dirstart)] = NULL;
165                 strncpy(m_szHost,pURL,(dirstart-pURL));
166                 m_szHost[(dirstart-pURL)-1] = NULL;
167         }
168         //At this point we should have a nice host,dir and filename
169         
170         //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
171         if(NULL==_beginthread(FTPObjThread,0,this))
172         {
173                 m_State = FTP_STATE_INTERNAL_ERROR;
174                 return;
175         }
176         m_State = FTP_STATE_CONNECTING;
177 }
178
179
180
181 CFtpGet::~CFtpGet()
182 {
183         if(m_ListenSock != INVALID_SOCKET)
184         {
185                 shutdown(m_ListenSock,2);
186                 closesocket(m_ListenSock);
187         }
188         if(m_DataSock != INVALID_SOCKET)
189         {
190                 shutdown(m_DataSock,2);
191                 closesocket(m_DataSock);
192         }
193         if(m_ControlSock != INVALID_SOCKET)
194         {
195                 shutdown(m_ControlSock,2);
196                 closesocket(m_ControlSock);
197         }
198
199
200 }
201
202 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
203 int CFtpGet::GetStatus()
204 {
205         return m_State;
206 }
207
208 unsigned int CFtpGet::GetBytesIn()
209 {
210         return m_iBytesIn;
211 }
212
213 unsigned int CFtpGet::GetTotalBytes()
214 {
215
216         return m_iBytesTotal;
217 }
218
219 //This function does all the work -- connects on a blocking socket
220 //then sends the appropriate user and password commands
221 //and then the cwd command, the port command then get and finally the quit
222 void CFtpGet::WorkerThread()
223 {
224         ConnectControlSocket();
225         if(m_State != FTP_STATE_LOGGING_IN)
226         {
227                 return;
228         }
229         LoginHost();
230         if(m_State != FTP_STATE_LOGGED_IN)
231         {
232                 return;
233         }
234         GetFile();
235
236         //We are all done now, and state has the current state.
237         m_Aborted = true;
238         
239
240 }
241
242 unsigned int CFtpGet::GetFile()
243 {
244         //Start off by changing into the proper dir.
245         char szCommandString[200];
246         int rcode;
247         
248         sprintf(szCommandString,"TYPE I\r\n");
249         rcode = SendFTPCommand(szCommandString);
250         if(rcode >=400)
251         {
252                 m_State = FTP_STATE_UNKNOWN_ERROR;      
253                 return 0;
254         }
255         if(m_Aborting)
256                 return 0;
257         if(m_szDir[0])
258         {
259                 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
260                 rcode = SendFTPCommand(szCommandString);
261                 if(rcode >=400)
262                 {
263                         m_State = FTP_STATE_DIRECTORY_INVALID;  
264                         return 0;
265                 }
266         }
267         if(m_Aborting)
268                 return 0;
269         if(!IssuePort())
270         {
271                 m_State = FTP_STATE_UNKNOWN_ERROR;
272                 return 0;
273         }
274         if(m_Aborting)
275                 return 0;
276         sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
277         rcode = SendFTPCommand(szCommandString);
278         if(rcode >=400)
279         {
280                 m_State = FTP_STATE_FILE_NOT_FOUND;     
281                 return 0;
282         }
283         if(m_Aborting)
284                 return 0;
285         //Now we will try to determine the file size...
286         char *p,*s;
287         p = strchr(recv_buffer,'(');
288         p++;
289         if(p)
290         {
291                 s = strchr(p,' ');
292                 *s = NULL;
293                 m_iBytesTotal = atoi(p);
294         }
295         if(m_Aborting)
296                 return 0;
297
298         m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength); 
299         // Close the listen socket
300         closesocket(m_ListenSock);
301         if (m_DataSock == INVALID_SOCKET)
302         {
303                 // int iWinsockErr = WSAGetLastError();
304                 m_State = FTP_STATE_SOCKET_ERROR;
305                 return 0;
306         }
307         if(m_Aborting)
308                 return 0;
309         ReadDataChannel();
310         
311         m_State = FTP_STATE_FILE_RECEIVED;
312         return 1;
313 }
314
315 unsigned int CFtpGet::IssuePort()
316 {
317
318         char szCommandString[200];
319         SOCKADDR_IN listenaddr;                                 // Socket address structure
320    int iLength;                                                                 // Length of the address structure
321    UINT nLocalPort;                                                     // Local port for listening
322         UINT nReplyCode;                                                        // FTP server reply code
323
324
325    // Get the address for the hListenSocket
326         iLength = sizeof(listenaddr);
327         if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
328         {
329                 // int iWinsockErr = WSAGetLastError();
330                 m_State = FTP_STATE_SOCKET_ERROR;
331                 return 0;
332         }
333
334         // Extract the local port from the hListenSocket
335         nLocalPort = listenaddr.sin_port;
336                                                         
337         // Now, reuse the socket address structure to 
338         // get the IP address from the control socket.
339         if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
340         {
341                 // int iWinsockErr = WSAGetLastError();
342                 m_State = FTP_STATE_SOCKET_ERROR;
343                 return 0;
344         }
345                                 
346         // Format the PORT command with the correct numbers.
347         sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n", 
348                                 listenaddr.sin_addr.S_un.S_un_b.s_b1, 
349                                 listenaddr.sin_addr.S_un.S_un_b.s_b2,
350                                 listenaddr.sin_addr.S_un.S_un_b.s_b3,
351                                 listenaddr.sin_addr.S_un.S_un_b.s_b4,
352                                 nLocalPort & 0xFF,      
353                                 nLocalPort >> 8);
354                                                                                                                         
355         // Tell the server which port to use for data.
356         nReplyCode = SendFTPCommand(szCommandString);
357         if (nReplyCode != 200)
358         {
359                 // int iWinsockErr = WSAGetLastError();
360                 m_State = FTP_STATE_SOCKET_ERROR;
361                 return 0;
362         }
363         return 1;
364 }
365
366 int CFtpGet::ConnectControlSocket()
367 {
368         HOSTENT *he;
369         SERVENT *se;
370         SOCKADDR_IN hostaddr;
371         he = gethostbyname(m_szHost);
372         if(he == NULL)
373         {
374                 m_State = FTP_STATE_HOST_NOT_FOUND;
375                 return 0;
376         }
377         //m_ControlSock
378         if(m_Aborting)
379                 return 0;
380         se = getservbyname("ftp", NULL);
381
382         if(se == NULL)
383         {
384                 hostaddr.sin_port = htons(21);
385         }
386         else
387         {
388                 hostaddr.sin_port = se->s_port;
389         }
390         hostaddr.sin_family = AF_INET;          
391         memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
392         if(m_Aborting)
393                 return 0;
394         //Now we will connect to the host                                       
395         if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
396         {
397                 // int iWinsockErr = WSAGetLastError();
398                 m_State = FTP_STATE_CANT_CONNECT;
399                 return 0;
400         }
401         m_State = FTP_STATE_LOGGING_IN;
402         return 1;
403 }
404
405
406 int CFtpGet::LoginHost()
407 {
408         char szLoginString[200];
409         int rcode;
410         
411         sprintf(szLoginString,"USER %s\r\n",m_szUserName);
412         rcode = SendFTPCommand(szLoginString);
413         if(rcode >=400)
414         {
415                 m_State = FTP_STATE_LOGIN_ERROR;        
416                 return 0;
417         }
418         sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
419         rcode = SendFTPCommand(szLoginString);
420         if(rcode >=400)
421         {
422                 m_State = FTP_STATE_LOGIN_ERROR;        
423                 return 0;
424         }
425
426         m_State = FTP_STATE_LOGGED_IN;
427         return 1;
428 }
429
430
431 unsigned int CFtpGet::SendFTPCommand(char *command)
432 {
433
434         FlushControlChannel();
435         // Send the FTP command
436         if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
437                 {
438                         // int iWinsockErr = WSAGetLastError();
439                   // Return 999 to indicate an error has occurred
440                         return(999);
441                 } 
442                 
443         // Read the server's reply and return the reply code as an integer
444         return(ReadFTPServerReply());               
445 }       
446
447
448
449 unsigned int CFtpGet::ReadFTPServerReply()
450 {
451         unsigned int rcode;
452         unsigned int iBytesRead;
453         char chunk[2];
454         char szcode[5];
455         unsigned int igotcrlf = 0;
456         memset(recv_buffer,0,1000);
457         do
458         {
459                 chunk[0]=NULL;
460                 iBytesRead = recv(m_ControlSock,chunk,1,0);
461
462                 if (iBytesRead == SOCKET_ERROR)
463                 {
464                         // int iWinsockErr = WSAGetLastError();
465                   // Return 999 to indicate an error has occurred
466                         return(999);
467                 }
468                 
469                 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
470                 {
471                         if(recv_buffer[0]!=NULL) 
472                         {
473                                 igotcrlf = 1;   
474                         }
475                 }
476                 else
477                 {       chunk[1] = NULL;
478                         strcat(recv_buffer,chunk);
479                 }
480                 
481                 Sleep(1);       
482         }while(igotcrlf==0);
483                                         
484         if(recv_buffer[3] == '-')
485         {
486                 //Hack -- must be a MOTD
487                 return ReadFTPServerReply();
488         }
489         if(recv_buffer[3] != ' ')
490         {
491                 //We should have 3 numbers then a space
492                 return 999;
493         }
494         memcpy(szcode,recv_buffer,3);
495         szcode[3] = NULL;
496         rcode = atoi(szcode);
497     // Extract the reply code from the server reply and return as an integer
498         return(rcode);              
499 }       
500
501
502 unsigned int CFtpGet::ReadDataChannel()
503 {
504         char sDataBuffer[4096];         // Data-storage buffer for the data channel
505         int nBytesRecv;                                         // Bytes received from the data channel
506         m_State = FTP_STATE_RECEIVING;                  
507    if(m_Aborting)
508                 return 0;
509         do      
510    {
511                 if(m_Aborting)
512                         return 0;
513                 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
514                                         
515                 m_iBytesIn += nBytesRecv;
516                 if (nBytesRecv > 0 )
517                 {
518                         fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
519                         //Write sDataBuffer, nBytesRecv
520         }
521
522                 Sleep(1);
523         }while (nBytesRecv > 0);
524         fclose(LOCALFILE);                                                      
525         // Close the file and check for error returns.
526         if (nBytesRecv == SOCKET_ERROR)
527         { 
528                 //Ok, we got a socket error -- xfer aborted?
529                 m_State = FTP_STATE_RECV_FAILED;
530                 return 0;
531         }
532         else
533         {
534                 //done!
535                 m_State = FTP_STATE_FILE_RECEIVED;
536                 return 1;
537         }
538 }       
539
540
541 void CFtpGet::FlushControlChannel()
542 {
543         fd_set read_fds;                   
544         TIMEVAL timeout;        
545         char flushbuff[3];
546
547         timeout.tv_sec=0;            
548         timeout.tv_usec=0;
549         
550         FD_ZERO(&read_fds);
551         FD_SET(m_ControlSock,&read_fds);    
552         
553         while(select(0,&read_fds,NULL,NULL,&timeout))
554         {
555                 recv(m_ControlSock,flushbuff,1,0);
556
557                 Sleep(1);
558         }
559 }