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