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