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