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