2 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
7 * FTP Client class (get only)
10 * Revision 1.3 2002/05/26 19:55:20 relnev
11 * unix.h: winsock defines
13 * cftp.cpp: now compiles!
15 * Revision 1.2 2002/05/07 03:16:45 theoddone33
16 * The Great Newline Fix
18 * Revision 1.1.1.1 2002/05/03 03:28:09 root
22 * 3 5/04/99 7:34p Dave
23 * Fixed slow HTTP get problem.
25 * 2 4/20/99 6:39p Dave
26 * Almost done with artillery targeting. Added support for downloading
27 * images on the PXO screen.
29 * 1 4/20/99 4:37p Dave
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
46 #include "pstypes.h" // unix.h
55 void FTPObjThread( void * obj )
57 ((CFtpGet *)obj)->WorkerThread();
60 void CFtpGet::AbortGet()
63 while(!m_Aborted) ; //Wait for the thread to end
67 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
69 SOCKADDR_IN listensockaddr;
70 m_State = FTP_STATE_STARTUP;
72 m_ListenSock = INVALID_SOCKET;
73 m_DataSock = INVALID_SOCKET;
74 m_ControlSock = INVALID_SOCKET;
80 LOCALFILE = fopen(localfile,"wb");
83 m_State = FTP_STATE_CANT_WRITE_FILE;
89 strcpy(m_szUserName,Username);
93 strcpy(m_szUserName,"anonymous");
97 strcpy(m_szPassword,Password);
101 strcpy(m_szPassword,"pxouser@pxo.net");
103 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
104 if(INVALID_SOCKET == m_ListenSock)
106 // vint iWinsockErr = WSAGetLastError();
107 m_State = FTP_STATE_SOCKET_ERROR;
112 listensockaddr.sin_family = AF_INET;
113 listensockaddr.sin_port = 0;
114 listensockaddr.sin_addr.s_addr = INADDR_ANY;
116 // Bind the listen socket
117 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
119 //Couldn't bind the socket
120 // int iWinsockErr = WSAGetLastError();
121 m_State = FTP_STATE_SOCKET_ERROR;
125 // Listen for the server connection
126 if (listen(m_ListenSock, 1))
128 //Couldn't listen on the socket
129 // int iWinsockErr = WSAGetLastError();
130 m_State = FTP_STATE_SOCKET_ERROR;
134 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
135 if(INVALID_SOCKET == m_ControlSock)
137 m_State = FTP_STATE_SOCKET_ERROR;
141 //Get rid of any extra ftp:// stuff
143 if(_strnicmp(URL,"ftp:",4)==0)
151 //There shouldn't be any : in this string
154 m_State = FTP_STATE_URL_PARSING_ERROR;
157 //read the filename by searching backwards for a /
158 //then keep reading until you find the first /
159 //when you found it, you have the host and dir
160 char *filestart = NULL;
161 char *dirstart = NULL;
162 for(int i = strlen(pURL);i>=0;i--)
168 filestart = pURL+i+1;
170 strcpy(m_szFilename,filestart);
178 if((dirstart==NULL) || (filestart==NULL))
180 m_State = FTP_STATE_URL_PARSING_ERROR;
185 strncpy(m_szDir,dirstart,(filestart-dirstart));
186 m_szDir[(filestart-dirstart)] = NULL;
187 strncpy(m_szHost,pURL,(dirstart-pURL));
188 m_szHost[(dirstart-pURL)-1] = NULL;
190 //At this point we should have a nice host,dir and filename
192 //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
193 if(NULL==_beginthread(FTPObjThread,0,this))
195 m_State = FTP_STATE_INTERNAL_ERROR;
198 m_State = FTP_STATE_CONNECTING;
205 if(m_ListenSock != INVALID_SOCKET)
207 shutdown(m_ListenSock,2);
208 closesocket(m_ListenSock);
210 if(m_DataSock != INVALID_SOCKET)
212 shutdown(m_DataSock,2);
213 closesocket(m_DataSock);
215 if(m_ControlSock != INVALID_SOCKET)
217 shutdown(m_ControlSock,2);
218 closesocket(m_ControlSock);
224 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
225 int CFtpGet::GetStatus()
230 unsigned int CFtpGet::GetBytesIn()
235 unsigned int CFtpGet::GetTotalBytes()
238 return m_iBytesTotal;
241 //This function does all the work -- connects on a blocking socket
242 //then sends the appropriate user and password commands
243 //and then the cwd command, the port command then get and finally the quit
244 void CFtpGet::WorkerThread()
246 ConnectControlSocket();
247 if(m_State != FTP_STATE_LOGGING_IN)
252 if(m_State != FTP_STATE_LOGGED_IN)
258 //We are all done now, and state has the current state.
264 unsigned int CFtpGet::GetFile()
266 //Start off by changing into the proper dir.
267 char szCommandString[200];
270 sprintf(szCommandString,"TYPE I\r\n");
271 rcode = SendFTPCommand(szCommandString);
274 m_State = FTP_STATE_UNKNOWN_ERROR;
281 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
282 rcode = SendFTPCommand(szCommandString);
285 m_State = FTP_STATE_DIRECTORY_INVALID;
293 m_State = FTP_STATE_UNKNOWN_ERROR;
298 sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
299 rcode = SendFTPCommand(szCommandString);
302 m_State = FTP_STATE_FILE_NOT_FOUND;
307 //Now we will try to determine the file size...
309 p = strchr(recv_buffer,'(');
315 m_iBytesTotal = atoi(p);
320 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
321 // Close the listen socket
322 closesocket(m_ListenSock);
323 if (m_DataSock == INVALID_SOCKET)
325 // int iWinsockErr = WSAGetLastError();
326 m_State = FTP_STATE_SOCKET_ERROR;
333 m_State = FTP_STATE_FILE_RECEIVED;
337 unsigned int CFtpGet::IssuePort()
340 char szCommandString[200];
341 SOCKADDR_IN listenaddr; // Socket address structure
343 int iLength; // Length of the address structure
347 UINT nLocalPort; // Local port for listening
348 UINT nReplyCode; // FTP server reply code
351 // Get the address for the hListenSocket
352 iLength = sizeof(listenaddr);
353 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
355 // int iWinsockErr = WSAGetLastError();
356 m_State = FTP_STATE_SOCKET_ERROR;
360 // Extract the local port from the hListenSocket
361 nLocalPort = listenaddr.sin_port;
363 // Now, reuse the socket address structure to
364 // get the IP address from the control socket.
365 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
367 // int iWinsockErr = WSAGetLastError();
368 m_State = FTP_STATE_SOCKET_ERROR;
372 // Format the PORT command with the correct numbers.
374 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
375 listenaddr.sin_addr.S_un.S_un_b.s_b1,
376 listenaddr.sin_addr.S_un.S_un_b.s_b2,
377 listenaddr.sin_addr.S_un.S_un_b.s_b3,
378 listenaddr.sin_addr.S_un.S_un_b.s_b4,
382 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
383 (listenaddr.sin_addr.s_addr >> 0) & 0x000000FF,
384 (listenaddr.sin_addr.s_addr >> 8) & 0x0000FF00,
385 (listenaddr.sin_addr.s_addr >> 16) & 0x00FF0000,
386 (listenaddr.sin_addr.s_addr >> 24) & 0xFF000000,
391 // Tell the server which port to use for data.
392 nReplyCode = SendFTPCommand(szCommandString);
393 if (nReplyCode != 200)
395 // int iWinsockErr = WSAGetLastError();
396 m_State = FTP_STATE_SOCKET_ERROR;
402 int CFtpGet::ConnectControlSocket()
406 SOCKADDR_IN hostaddr;
407 he = gethostbyname(m_szHost);
410 m_State = FTP_STATE_HOST_NOT_FOUND;
416 se = getservbyname("ftp", NULL);
420 hostaddr.sin_port = htons(21);
424 hostaddr.sin_port = se->s_port;
426 hostaddr.sin_family = AF_INET;
427 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
430 //Now we will connect to the host
431 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
433 // int iWinsockErr = WSAGetLastError();
434 m_State = FTP_STATE_CANT_CONNECT;
437 m_State = FTP_STATE_LOGGING_IN;
442 int CFtpGet::LoginHost()
444 char szLoginString[200];
447 sprintf(szLoginString,"USER %s\r\n",m_szUserName);
448 rcode = SendFTPCommand(szLoginString);
451 m_State = FTP_STATE_LOGIN_ERROR;
454 sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
455 rcode = SendFTPCommand(szLoginString);
458 m_State = FTP_STATE_LOGIN_ERROR;
462 m_State = FTP_STATE_LOGGED_IN;
467 unsigned int CFtpGet::SendFTPCommand(char *command)
470 FlushControlChannel();
471 // Send the FTP command
472 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
474 // int iWinsockErr = WSAGetLastError();
475 // Return 999 to indicate an error has occurred
479 // Read the server's reply and return the reply code as an integer
480 return(ReadFTPServerReply());
485 unsigned int CFtpGet::ReadFTPServerReply()
488 unsigned int iBytesRead;
491 unsigned int igotcrlf = 0;
492 memset(recv_buffer,0,1000);
496 iBytesRead = recv(m_ControlSock,chunk,1,0);
498 if (iBytesRead == SOCKET_ERROR)
500 // int iWinsockErr = WSAGetLastError();
501 // Return 999 to indicate an error has occurred
505 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
507 if(recv_buffer[0]!=0)
514 strcat(recv_buffer,chunk);
520 if(recv_buffer[3] == '-')
522 //Hack -- must be a MOTD
523 return ReadFTPServerReply();
525 if(recv_buffer[3] != ' ')
527 //We should have 3 numbers then a space
530 memcpy(szcode,recv_buffer,3);
532 rcode = atoi(szcode);
533 // Extract the reply code from the server reply and return as an integer
538 unsigned int CFtpGet::ReadDataChannel()
540 char sDataBuffer[4096]; // Data-storage buffer for the data channel
541 int nBytesRecv; // Bytes received from the data channel
542 m_State = FTP_STATE_RECEIVING;
549 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
551 m_iBytesIn += nBytesRecv;
554 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
555 //Write sDataBuffer, nBytesRecv
559 }while (nBytesRecv > 0);
561 // Close the file and check for error returns.
562 if (nBytesRecv == SOCKET_ERROR)
564 //Ok, we got a socket error -- xfer aborted?
565 m_State = FTP_STATE_RECV_FAILED;
571 m_State = FTP_STATE_FILE_RECEIVED;
577 void CFtpGet::FlushControlChannel()
587 FD_SET(m_ControlSock,&read_fds);
589 while(select(0,&read_fds,NULL,NULL,&timeout))
591 recv(m_ControlSock,flushbuff,1,0);