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