2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
15 * FTP Client class (get only)
18 * Revision 1.11 2002/06/21 03:04:12 relnev
21 * Revision 1.10 2002/06/19 04:52:45 relnev
22 * MacOS X updates (Ryan)
24 * Revision 1.9 2002/06/17 06:33:09 relnev
25 * ryan's struct patch for gcc 2.95
27 * Revision 1.8 2002/06/09 04:41:21 relnev
28 * added copyright header
30 * Revision 1.7 2002/06/02 04:26:34 relnev
33 * Revision 1.6 2002/05/26 21:06:44 relnev
36 * Revision 1.5 2002/05/26 20:32:24 theoddone33
37 * Fix some minor stuff
39 * Revision 1.4 2002/05/26 20:20:53 relnev
44 * Revision 1.3 2002/05/26 19:55:20 relnev
45 * unix.h: winsock defines
47 * cftp.cpp: now compiles!
49 * Revision 1.2 2002/05/07 03:16:45 theoddone33
50 * The Great Newline Fix
52 * Revision 1.1.1.1 2002/05/03 03:28:09 root
56 * 3 5/04/99 7:34p Dave
57 * Fixed slow HTTP get problem.
59 * 2 4/20/99 6:39p Dave
60 * Almost done with artillery targeting. Added support for downloading
61 * images on the PXO screen.
63 * 1 4/20/99 4:37p Dave
75 #include <sys/types.h>
76 #include <sys/socket.h>
77 #include <netinet/in.h>
78 #include <arpa/inet.h>
92 int FTPObjThread( void * obj )
94 ((CFtpGet *)obj)->WorkerThread();
96 return ((CFtpGet *)obj)->GetStatus();
99 void CFtpGet::AbortGet()
102 while(!m_Aborted) SDL_Delay(10); //Wait for the thread to end
104 if(LOCALFILE != NULL)
111 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
113 SOCKADDR_IN listensockaddr;
114 m_State = FTP_STATE_STARTUP;
116 m_ListenSock = INVALID_SOCKET;
117 m_DataSock = INVALID_SOCKET;
118 m_ControlSock = INVALID_SOCKET;
124 LOCALFILE = fopen(localfile,"wb");
125 if(NULL == LOCALFILE)
127 m_State = FTP_STATE_CANT_WRITE_FILE;
134 SDL_strlcpy(m_szUserName, Username, sizeof(m_szUserName));
138 SDL_strlcpy(m_szUserName, "anonymous", sizeof(m_szUserName));
142 SDL_strlcpy(m_szPassword, Password, sizeof(m_szPassword));
146 SDL_strlcpy(m_szPassword, "pxouser@pxo.net", sizeof(m_szPassword));
148 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
149 if(INVALID_SOCKET == m_ListenSock)
151 // vint iWinsockErr = WSAGetLastError();
152 m_State = FTP_STATE_SOCKET_ERROR;
158 listensockaddr.sin_family = AF_INET;
159 listensockaddr.sin_port = 0;
160 listensockaddr.sin_addr.s_addr = INADDR_ANY;
162 // Bind the listen socket
163 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
165 //Couldn't bind the socket
166 // int iWinsockErr = WSAGetLastError();
167 m_State = FTP_STATE_SOCKET_ERROR;
172 // Listen for the server connection
173 if (listen(m_ListenSock, 1))
175 //Couldn't listen on the socket
176 // int iWinsockErr = WSAGetLastError();
177 m_State = FTP_STATE_SOCKET_ERROR;
182 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
183 if(INVALID_SOCKET == m_ControlSock)
185 m_State = FTP_STATE_SOCKET_ERROR;
190 //Get rid of any extra ftp:// stuff
192 if(SDL_strncasecmp(URL,"ftp:",4)==0)
200 //There shouldn't be any : in this string
201 if(SDL_strchr(pURL,':'))
203 m_State = FTP_STATE_URL_PARSING_ERROR;
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--)
218 filestart = pURL+i+1;
220 SDL_strlcpy(m_szFilename, filestart, sizeof(m_szFilename));
228 if((dirstart==NULL) || (filestart==NULL))
230 m_State = FTP_STATE_URL_PARSING_ERROR;
236 int len = min((filestart-dirstart)+1, sizeof(m_szDir));
237 SDL_strlcpy(m_szDir, dirstart, len);
238 len = min((dirstart-pURL), sizeof(m_szHost));
239 SDL_strlcpy(m_szHost, pURL, len);
241 //At this point we should have a nice host,dir and filename
243 SDL_Thread *thread = SDL_CreateThread(FTPObjThread, "FTPObjThread", this);
247 m_State = FTP_STATE_INTERNAL_ERROR;
254 SDL_WaitThread(thread, &ret_val);
256 m_State = FTP_STATE_CONNECTING;
263 if(m_ListenSock != INVALID_SOCKET)
265 shutdown(m_ListenSock,2);
266 closesocket(m_ListenSock);
268 if(m_DataSock != INVALID_SOCKET)
270 shutdown(m_DataSock,2);
271 closesocket(m_DataSock);
273 if(m_ControlSock != INVALID_SOCKET)
275 shutdown(m_ControlSock,2);
276 closesocket(m_ControlSock);
278 if(LOCALFILE != NULL)
284 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
285 int CFtpGet::GetStatus()
290 unsigned int CFtpGet::GetBytesIn()
295 unsigned int CFtpGet::GetTotalBytes()
298 return m_iBytesTotal;
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()
306 ConnectControlSocket();
307 if(m_State != FTP_STATE_LOGGING_IN)
312 if(m_State != FTP_STATE_LOGGED_IN)
318 //We are all done now, and state has the current state.
324 unsigned int CFtpGet::GetFile()
326 //Start off by changing into the proper dir.
327 char szCommandString[200];
330 SDL_strlcpy(szCommandString, "TYPE I\r\n", sizeof(szCommandString));
331 rcode = SendFTPCommand(szCommandString);
334 m_State = FTP_STATE_UNKNOWN_ERROR;
341 SDL_snprintf(szCommandString, sizeof(szCommandString), "CWD %s\r\n", m_szDir);
342 rcode = SendFTPCommand(szCommandString);
345 m_State = FTP_STATE_DIRECTORY_INVALID;
353 m_State = FTP_STATE_UNKNOWN_ERROR;
358 SDL_snprintf(szCommandString, sizeof(szCommandString), "RETR %s\r\n", m_szFilename);
359 rcode = SendFTPCommand(szCommandString);
362 m_State = FTP_STATE_FILE_NOT_FOUND;
367 //Now we will try to determine the file size...
369 p = SDL_strchr(recv_buffer,'(');
373 s = SDL_strchr(p,' ');
375 m_iBytesTotal = atoi(p);
380 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
381 // Close the listen socket
382 closesocket(m_ListenSock);
383 if (m_DataSock == INVALID_SOCKET)
385 // int iWinsockErr = WSAGetLastError();
386 m_State = FTP_STATE_SOCKET_ERROR;
393 m_State = FTP_STATE_FILE_RECEIVED;
397 unsigned int CFtpGet::IssuePort()
400 char szCommandString[200];
401 SOCKADDR_IN listenaddr; // Socket address structure
403 int iLength; // Length of the address structure
407 UINT nLocalPort; // Local port for listening
408 UINT nReplyCode; // FTP server reply code
411 // Get the address for the hListenSocket
412 iLength = sizeof(listenaddr);
413 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
415 // int iWinsockErr = WSAGetLastError();
416 m_State = FTP_STATE_SOCKET_ERROR;
420 // Extract the local port from the hListenSocket
421 nLocalPort = listenaddr.sin_port;
423 // Now, reuse the socket address structure to
424 // get the IP address from the control socket.
425 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
427 // int iWinsockErr = WSAGetLastError();
428 m_State = FTP_STATE_SOCKET_ERROR;
432 // Format the PORT command with the correct numbers.
434 SDL_snprintf(szCommandString, sizeof(szCommandString), "PORT %d,%d,%d,%d,%d,%d\r\n",
435 listenaddr.sin_addr.S_un.S_un_b.s_b1,
436 listenaddr.sin_addr.S_un.S_un_b.s_b2,
437 listenaddr.sin_addr.S_un.S_un_b.s_b3,
438 listenaddr.sin_addr.S_un.S_un_b.s_b4,
442 SDL_snprintf(szCommandString, sizeof(szCommandString), "PORT %d,%d,%d,%d,%d,%d\r\n",
443 (listenaddr.sin_addr.s_addr >> 0) & 0xFF,
444 (listenaddr.sin_addr.s_addr >> 8) & 0xFF,
445 (listenaddr.sin_addr.s_addr >> 16) & 0xFF,
446 (listenaddr.sin_addr.s_addr >> 24) & 0xFF,
451 // Tell the server which port to use for data.
452 nReplyCode = SendFTPCommand(szCommandString);
453 if (nReplyCode != 200)
455 // int iWinsockErr = WSAGetLastError();
456 m_State = FTP_STATE_SOCKET_ERROR;
462 int CFtpGet::ConnectControlSocket()
466 SOCKADDR_IN hostaddr;
467 he = gethostbyname(m_szHost);
470 m_State = FTP_STATE_HOST_NOT_FOUND;
476 se = getservbyname("ftp", NULL);
480 hostaddr.sin_port = htons(21);
484 hostaddr.sin_port = se->s_port;
486 hostaddr.sin_family = AF_INET;
487 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
490 //Now we will connect to the host
491 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
493 // int iWinsockErr = WSAGetLastError();
494 m_State = FTP_STATE_CANT_CONNECT;
497 m_State = FTP_STATE_LOGGING_IN;
502 int CFtpGet::LoginHost()
504 char szLoginString[200];
507 SDL_snprintf(szLoginString, sizeof(szLoginString), "USER %s\r\n" ,m_szUserName);
508 rcode = SendFTPCommand(szLoginString);
511 m_State = FTP_STATE_LOGIN_ERROR;
514 SDL_snprintf(szLoginString, sizeof(szLoginString), "PASS %s\r\n" ,m_szPassword);
515 rcode = SendFTPCommand(szLoginString);
518 m_State = FTP_STATE_LOGIN_ERROR;
522 m_State = FTP_STATE_LOGGED_IN;
527 unsigned int CFtpGet::SendFTPCommand(char *command)
530 FlushControlChannel();
531 // Send the FTP command
532 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
534 // int iWinsockErr = WSAGetLastError();
535 // Return 999 to indicate an error has occurred
539 // Read the server's reply and return the reply code as an integer
540 return(ReadFTPServerReply());
545 unsigned int CFtpGet::ReadFTPServerReply()
551 unsigned int igotcrlf = 0;
552 memset(recv_buffer,0,1000);
556 iBytesRead = recv(m_ControlSock,chunk,1,0);
558 if (iBytesRead == SOCKET_ERROR)
560 // int iWinsockErr = WSAGetLastError();
561 // Return 999 to indicate an error has occurred
565 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
567 if(recv_buffer[0]!=0)
574 SDL_strlcat(recv_buffer, chunk, sizeof(recv_buffer));
580 if(recv_buffer[3] == '-')
582 //Hack -- must be a MOTD
583 return ReadFTPServerReply();
585 if(recv_buffer[3] != ' ')
587 //We should have 3 numbers then a space
590 memcpy(szcode,recv_buffer,3);
592 rcode = atoi(szcode);
593 // Extract the reply code from the server reply and return as an integer
598 unsigned int CFtpGet::ReadDataChannel()
600 char sDataBuffer[4096]; // Data-storage buffer for the data channel
601 int nBytesRecv; // Bytes received from the data channel
602 m_State = FTP_STATE_RECEIVING;
609 nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer,sizeof(sDataBuffer), 0);
611 m_iBytesIn += nBytesRecv;
614 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
615 //Write sDataBuffer, nBytesRecv
619 }while (nBytesRecv > 0);
621 // Close the file and check for error returns.
622 if (nBytesRecv == SOCKET_ERROR)
624 //Ok, we got a socket error -- xfer aborted?
625 m_State = FTP_STATE_RECV_FAILED;
631 m_State = FTP_STATE_FILE_RECEIVED;
637 void CFtpGet::FlushControlChannel()
647 FD_SET(m_ControlSock,&read_fds);
649 while(select(0,&read_fds,NULL,NULL,&timeout))
651 recv(m_ControlSock,flushbuff,1,0);