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