2 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
7 * FTP Client class (get only)
10 * Revision 1.7 2002/06/02 04:26:34 relnev
13 * Revision 1.6 2002/05/26 21:06:44 relnev
16 * Revision 1.5 2002/05/26 20:32:24 theoddone33
17 * Fix some minor stuff
19 * Revision 1.4 2002/05/26 20:20:53 relnev
24 * Revision 1.3 2002/05/26 19:55:20 relnev
25 * unix.h: winsock defines
27 * cftp.cpp: now compiles!
29 * Revision 1.2 2002/05/07 03:16:45 theoddone33
30 * The Great Newline Fix
32 * Revision 1.1.1.1 2002/05/03 03:28:09 root
36 * 3 5/04/99 7:34p Dave
37 * Fixed slow HTTP get problem.
39 * 2 4/20/99 6:39p Dave
40 * Almost done with artillery targeting. Added support for downloading
41 * images on the PXO screen.
43 * 1 4/20/99 4:37p Dave
54 #include <sys/types.h>
55 #include <sys/socket.h>
56 #include <netinet/in.h>
57 #include <arpa/inet.h>
60 #include "unix.h" // unix.h
69 void FTPObjThread( void * obj )
71 ((CFtpGet *)obj)->WorkerThread();
74 void CFtpGet::AbortGet()
77 while(!m_Aborted) ; //Wait for the thread to end
81 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
83 SOCKADDR_IN listensockaddr;
84 m_State = FTP_STATE_STARTUP;
86 m_ListenSock = INVALID_SOCKET;
87 m_DataSock = INVALID_SOCKET;
88 m_ControlSock = INVALID_SOCKET;
94 LOCALFILE = fopen(localfile,"wb");
97 m_State = FTP_STATE_CANT_WRITE_FILE;
103 strcpy(m_szUserName,Username);
107 strcpy(m_szUserName,"anonymous");
111 strcpy(m_szPassword,Password);
115 strcpy(m_szPassword,"pxouser@pxo.net");
117 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
118 if(INVALID_SOCKET == m_ListenSock)
120 // vint iWinsockErr = WSAGetLastError();
121 m_State = FTP_STATE_SOCKET_ERROR;
126 listensockaddr.sin_family = AF_INET;
127 listensockaddr.sin_port = 0;
128 listensockaddr.sin_addr.s_addr = INADDR_ANY;
130 // Bind the listen socket
131 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
133 //Couldn't bind the socket
134 // int iWinsockErr = WSAGetLastError();
135 m_State = FTP_STATE_SOCKET_ERROR;
139 // Listen for the server connection
140 if (listen(m_ListenSock, 1))
142 //Couldn't listen on the socket
143 // int iWinsockErr = WSAGetLastError();
144 m_State = FTP_STATE_SOCKET_ERROR;
148 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
149 if(INVALID_SOCKET == m_ControlSock)
151 m_State = FTP_STATE_SOCKET_ERROR;
155 //Get rid of any extra ftp:// stuff
157 if(_strnicmp(URL,"ftp:",4)==0)
165 //There shouldn't be any : in this string
168 m_State = FTP_STATE_URL_PARSING_ERROR;
171 //read the filename by searching backwards for a /
172 //then keep reading until you find the first /
173 //when you found it, you have the host and dir
174 char *filestart = NULL;
175 char *dirstart = NULL;
176 for(int i = strlen(pURL);i>=0;i--)
182 filestart = pURL+i+1;
184 strcpy(m_szFilename,filestart);
192 if((dirstart==NULL) || (filestart==NULL))
194 m_State = FTP_STATE_URL_PARSING_ERROR;
199 strncpy(m_szDir,dirstart,(filestart-dirstart));
200 m_szDir[(filestart-dirstart)] = 0;
201 strncpy(m_szHost,pURL,(dirstart-pURL));
202 m_szHost[(dirstart-pURL)-1] = 0;
204 //At this point we should have a nice host,dir and filename
206 //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
207 if(NULL==_beginthread(FTPObjThread,0,this))
209 m_State = FTP_STATE_INTERNAL_ERROR;
212 m_State = FTP_STATE_CONNECTING;
219 if(m_ListenSock != INVALID_SOCKET)
221 shutdown(m_ListenSock,2);
222 closesocket(m_ListenSock);
224 if(m_DataSock != INVALID_SOCKET)
226 shutdown(m_DataSock,2);
227 closesocket(m_DataSock);
229 if(m_ControlSock != INVALID_SOCKET)
231 shutdown(m_ControlSock,2);
232 closesocket(m_ControlSock);
238 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
239 int CFtpGet::GetStatus()
244 unsigned int CFtpGet::GetBytesIn()
249 unsigned int CFtpGet::GetTotalBytes()
252 return m_iBytesTotal;
255 //This function does all the work -- connects on a blocking socket
256 //then sends the appropriate user and password commands
257 //and then the cwd command, the port command then get and finally the quit
258 void CFtpGet::WorkerThread()
260 ConnectControlSocket();
261 if(m_State != FTP_STATE_LOGGING_IN)
266 if(m_State != FTP_STATE_LOGGED_IN)
272 //We are all done now, and state has the current state.
278 unsigned int CFtpGet::GetFile()
280 //Start off by changing into the proper dir.
281 char szCommandString[200];
284 sprintf(szCommandString,"TYPE I\r\n");
285 rcode = SendFTPCommand(szCommandString);
288 m_State = FTP_STATE_UNKNOWN_ERROR;
295 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
296 rcode = SendFTPCommand(szCommandString);
299 m_State = FTP_STATE_DIRECTORY_INVALID;
307 m_State = FTP_STATE_UNKNOWN_ERROR;
312 sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
313 rcode = SendFTPCommand(szCommandString);
316 m_State = FTP_STATE_FILE_NOT_FOUND;
321 //Now we will try to determine the file size...
323 p = strchr(recv_buffer,'(');
329 m_iBytesTotal = atoi(p);
334 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
335 // Close the listen socket
336 closesocket(m_ListenSock);
337 if (m_DataSock == INVALID_SOCKET)
339 // int iWinsockErr = WSAGetLastError();
340 m_State = FTP_STATE_SOCKET_ERROR;
347 m_State = FTP_STATE_FILE_RECEIVED;
351 unsigned int CFtpGet::IssuePort()
354 char szCommandString[200];
355 SOCKADDR_IN listenaddr; // Socket address structure
357 int iLength; // Length of the address structure
361 UINT nLocalPort; // Local port for listening
362 UINT nReplyCode; // FTP server reply code
365 // Get the address for the hListenSocket
366 iLength = sizeof(listenaddr);
367 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
369 // int iWinsockErr = WSAGetLastError();
370 m_State = FTP_STATE_SOCKET_ERROR;
374 // Extract the local port from the hListenSocket
375 nLocalPort = listenaddr.sin_port;
377 // Now, reuse the socket address structure to
378 // get the IP address from the control socket.
379 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
381 // int iWinsockErr = WSAGetLastError();
382 m_State = FTP_STATE_SOCKET_ERROR;
386 // Format the PORT command with the correct numbers.
388 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
389 listenaddr.sin_addr.S_un.S_un_b.s_b1,
390 listenaddr.sin_addr.S_un.S_un_b.s_b2,
391 listenaddr.sin_addr.S_un.S_un_b.s_b3,
392 listenaddr.sin_addr.S_un.S_un_b.s_b4,
396 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
397 (listenaddr.sin_addr.s_addr >> 0) & 0xFF,
398 (listenaddr.sin_addr.s_addr >> 8) & 0xFF,
399 (listenaddr.sin_addr.s_addr >> 16) & 0xFF,
400 (listenaddr.sin_addr.s_addr >> 24) & 0xFF,
405 // Tell the server which port to use for data.
406 nReplyCode = SendFTPCommand(szCommandString);
407 if (nReplyCode != 200)
409 // int iWinsockErr = WSAGetLastError();
410 m_State = FTP_STATE_SOCKET_ERROR;
416 int CFtpGet::ConnectControlSocket()
420 SOCKADDR_IN hostaddr;
421 he = gethostbyname(m_szHost);
424 m_State = FTP_STATE_HOST_NOT_FOUND;
430 se = getservbyname("ftp", NULL);
434 hostaddr.sin_port = htons(21);
438 hostaddr.sin_port = se->s_port;
440 hostaddr.sin_family = AF_INET;
441 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
444 //Now we will connect to the host
445 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
447 // int iWinsockErr = WSAGetLastError();
448 m_State = FTP_STATE_CANT_CONNECT;
451 m_State = FTP_STATE_LOGGING_IN;
456 int CFtpGet::LoginHost()
458 char szLoginString[200];
461 sprintf(szLoginString,"USER %s\r\n",m_szUserName);
462 rcode = SendFTPCommand(szLoginString);
465 m_State = FTP_STATE_LOGIN_ERROR;
468 sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
469 rcode = SendFTPCommand(szLoginString);
472 m_State = FTP_STATE_LOGIN_ERROR;
476 m_State = FTP_STATE_LOGGED_IN;
481 unsigned int CFtpGet::SendFTPCommand(char *command)
484 FlushControlChannel();
485 // Send the FTP command
486 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
488 // int iWinsockErr = WSAGetLastError();
489 // Return 999 to indicate an error has occurred
493 // Read the server's reply and return the reply code as an integer
494 return(ReadFTPServerReply());
499 unsigned int CFtpGet::ReadFTPServerReply()
505 unsigned int igotcrlf = 0;
506 memset(recv_buffer,0,1000);
510 iBytesRead = recv(m_ControlSock,chunk,1,0);
512 if (iBytesRead == SOCKET_ERROR)
514 // int iWinsockErr = WSAGetLastError();
515 // Return 999 to indicate an error has occurred
519 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
521 if(recv_buffer[0]!=0)
528 strcat(recv_buffer,chunk);
534 if(recv_buffer[3] == '-')
536 //Hack -- must be a MOTD
537 return ReadFTPServerReply();
539 if(recv_buffer[3] != ' ')
541 //We should have 3 numbers then a space
544 memcpy(szcode,recv_buffer,3);
546 rcode = atoi(szcode);
547 // Extract the reply code from the server reply and return as an integer
552 unsigned int CFtpGet::ReadDataChannel()
554 char sDataBuffer[4096]; // Data-storage buffer for the data channel
555 int nBytesRecv; // Bytes received from the data channel
556 m_State = FTP_STATE_RECEIVING;
563 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
565 m_iBytesIn += nBytesRecv;
568 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
569 //Write sDataBuffer, nBytesRecv
573 }while (nBytesRecv > 0);
575 // Close the file and check for error returns.
576 if (nBytesRecv == SOCKET_ERROR)
578 //Ok, we got a socket error -- xfer aborted?
579 m_State = FTP_STATE_RECV_FAILED;
585 m_State = FTP_STATE_FILE_RECEIVED;
591 void CFtpGet::FlushControlChannel()
601 FD_SET(m_ControlSock,&read_fds);
603 while(select(0,&read_fds,NULL,NULL,&timeout))
605 recv(m_ControlSock,flushbuff,1,0);