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