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