2 * $Logfile: /Freespace2/code/Inetfile/CFtp.cpp $
7 * FTP Client class (get only)
10 * Revision 1.4 2002/05/26 20:20:53 relnev
13 * inetfile/*: complete
15 * Revision 1.3 2002/05/26 19:55:20 relnev
16 * unix.h: winsock defines
18 * cftp.cpp: now compiles!
20 * Revision 1.2 2002/05/07 03:16:45 theoddone33
21 * The Great Newline Fix
23 * Revision 1.1.1.1 2002/05/03 03:28:09 root
27 * 3 5/04/99 7:34p Dave
28 * Fixed slow HTTP get problem.
30 * 2 4/20/99 6:39p Dave
31 * Almost done with artillery targeting. Added support for downloading
32 * images on the PXO screen.
34 * 1 4/20/99 4:37p Dave
45 #include <sys/types.h>
46 #include <sys/socket.h>
47 #include <netinet/in.h>
48 #include <arpa/inet.h>
51 #include "unix.h" // unix.h
60 void FTPObjThread( void * obj )
62 ((CFtpGet *)obj)->WorkerThread();
65 void CFtpGet::AbortGet()
68 while(!m_Aborted) ; //Wait for the thread to end
72 CFtpGet::CFtpGet(char *URL,char *localfile,char *Username,char *Password)
74 SOCKADDR_IN listensockaddr;
75 m_State = FTP_STATE_STARTUP;
77 m_ListenSock = INVALID_SOCKET;
78 m_DataSock = INVALID_SOCKET;
79 m_ControlSock = INVALID_SOCKET;
85 LOCALFILE = fopen(localfile,"wb");
88 m_State = FTP_STATE_CANT_WRITE_FILE;
94 strcpy(m_szUserName,Username);
98 strcpy(m_szUserName,"anonymous");
102 strcpy(m_szPassword,Password);
106 strcpy(m_szPassword,"pxouser@pxo.net");
108 m_ListenSock = socket(AF_INET, SOCK_STREAM, 0);
109 if(INVALID_SOCKET == m_ListenSock)
111 // vint iWinsockErr = WSAGetLastError();
112 m_State = FTP_STATE_SOCKET_ERROR;
117 listensockaddr.sin_family = AF_INET;
118 listensockaddr.sin_port = 0;
119 listensockaddr.sin_addr.s_addr = INADDR_ANY;
121 // Bind the listen socket
122 if (bind(m_ListenSock, (SOCKADDR *)&listensockaddr, sizeof(SOCKADDR)))
124 //Couldn't bind the socket
125 // int iWinsockErr = WSAGetLastError();
126 m_State = FTP_STATE_SOCKET_ERROR;
130 // Listen for the server connection
131 if (listen(m_ListenSock, 1))
133 //Couldn't listen on the socket
134 // int iWinsockErr = WSAGetLastError();
135 m_State = FTP_STATE_SOCKET_ERROR;
139 m_ControlSock = socket(AF_INET, SOCK_STREAM, 0);
140 if(INVALID_SOCKET == m_ControlSock)
142 m_State = FTP_STATE_SOCKET_ERROR;
146 //Get rid of any extra ftp:// stuff
148 if(_strnicmp(URL,"ftp:",4)==0)
156 //There shouldn't be any : in this string
159 m_State = FTP_STATE_URL_PARSING_ERROR;
162 //read the filename by searching backwards for a /
163 //then keep reading until you find the first /
164 //when you found it, you have the host and dir
165 char *filestart = NULL;
166 char *dirstart = NULL;
167 for(int i = strlen(pURL);i>=0;i--)
173 filestart = pURL+i+1;
175 strcpy(m_szFilename,filestart);
183 if((dirstart==NULL) || (filestart==NULL))
185 m_State = FTP_STATE_URL_PARSING_ERROR;
190 strncpy(m_szDir,dirstart,(filestart-dirstart));
191 m_szDir[(filestart-dirstart)] = NULL;
192 strncpy(m_szHost,pURL,(dirstart-pURL));
193 m_szHost[(dirstart-pURL)-1] = NULL;
195 //At this point we should have a nice host,dir and filename
197 //if(NULL==CreateThread(NULL,0,ObjThread,this,0,&m_dwThreadId))
198 if(NULL==_beginthread(FTPObjThread,0,this))
200 m_State = FTP_STATE_INTERNAL_ERROR;
203 m_State = FTP_STATE_CONNECTING;
210 if(m_ListenSock != INVALID_SOCKET)
212 shutdown(m_ListenSock,2);
213 closesocket(m_ListenSock);
215 if(m_DataSock != INVALID_SOCKET)
217 shutdown(m_DataSock,2);
218 closesocket(m_DataSock);
220 if(m_ControlSock != INVALID_SOCKET)
222 shutdown(m_ControlSock,2);
223 closesocket(m_ControlSock);
229 //Returns a value to specify the status (ie. connecting/connected/transferring/done)
230 int CFtpGet::GetStatus()
235 unsigned int CFtpGet::GetBytesIn()
240 unsigned int CFtpGet::GetTotalBytes()
243 return m_iBytesTotal;
246 //This function does all the work -- connects on a blocking socket
247 //then sends the appropriate user and password commands
248 //and then the cwd command, the port command then get and finally the quit
249 void CFtpGet::WorkerThread()
251 ConnectControlSocket();
252 if(m_State != FTP_STATE_LOGGING_IN)
257 if(m_State != FTP_STATE_LOGGED_IN)
263 //We are all done now, and state has the current state.
269 unsigned int CFtpGet::GetFile()
271 //Start off by changing into the proper dir.
272 char szCommandString[200];
275 sprintf(szCommandString,"TYPE I\r\n");
276 rcode = SendFTPCommand(szCommandString);
279 m_State = FTP_STATE_UNKNOWN_ERROR;
286 sprintf(szCommandString,"CWD %s\r\n",m_szDir);
287 rcode = SendFTPCommand(szCommandString);
290 m_State = FTP_STATE_DIRECTORY_INVALID;
298 m_State = FTP_STATE_UNKNOWN_ERROR;
303 sprintf(szCommandString,"RETR %s\r\n",m_szFilename);
304 rcode = SendFTPCommand(szCommandString);
307 m_State = FTP_STATE_FILE_NOT_FOUND;
312 //Now we will try to determine the file size...
314 p = strchr(recv_buffer,'(');
320 m_iBytesTotal = atoi(p);
325 m_DataSock = accept(m_ListenSock, NULL,NULL);//(SOCKADDR *)&sockaddr,&iAddrLength);
326 // Close the listen socket
327 closesocket(m_ListenSock);
328 if (m_DataSock == INVALID_SOCKET)
330 // int iWinsockErr = WSAGetLastError();
331 m_State = FTP_STATE_SOCKET_ERROR;
338 m_State = FTP_STATE_FILE_RECEIVED;
342 unsigned int CFtpGet::IssuePort()
345 char szCommandString[200];
346 SOCKADDR_IN listenaddr; // Socket address structure
348 int iLength; // Length of the address structure
352 UINT nLocalPort; // Local port for listening
353 UINT nReplyCode; // FTP server reply code
356 // Get the address for the hListenSocket
357 iLength = sizeof(listenaddr);
358 if (getsockname(m_ListenSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
360 // int iWinsockErr = WSAGetLastError();
361 m_State = FTP_STATE_SOCKET_ERROR;
365 // Extract the local port from the hListenSocket
366 nLocalPort = listenaddr.sin_port;
368 // Now, reuse the socket address structure to
369 // get the IP address from the control socket.
370 if (getsockname(m_ControlSock, (LPSOCKADDR)&listenaddr,&iLength) == SOCKET_ERROR)
372 // int iWinsockErr = WSAGetLastError();
373 m_State = FTP_STATE_SOCKET_ERROR;
377 // Format the PORT command with the correct numbers.
379 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
380 listenaddr.sin_addr.S_un.S_un_b.s_b1,
381 listenaddr.sin_addr.S_un.S_un_b.s_b2,
382 listenaddr.sin_addr.S_un.S_un_b.s_b3,
383 listenaddr.sin_addr.S_un.S_un_b.s_b4,
387 sprintf(szCommandString, "PORT %d,%d,%d,%d,%d,%d\r\n",
388 (listenaddr.sin_addr.s_addr >> 0) & 0x000000FF,
389 (listenaddr.sin_addr.s_addr >> 8) & 0x0000FF00,
390 (listenaddr.sin_addr.s_addr >> 16) & 0x00FF0000,
391 (listenaddr.sin_addr.s_addr >> 24) & 0xFF000000,
396 // Tell the server which port to use for data.
397 nReplyCode = SendFTPCommand(szCommandString);
398 if (nReplyCode != 200)
400 // int iWinsockErr = WSAGetLastError();
401 m_State = FTP_STATE_SOCKET_ERROR;
407 int CFtpGet::ConnectControlSocket()
411 SOCKADDR_IN hostaddr;
412 he = gethostbyname(m_szHost);
415 m_State = FTP_STATE_HOST_NOT_FOUND;
421 se = getservbyname("ftp", NULL);
425 hostaddr.sin_port = htons(21);
429 hostaddr.sin_port = se->s_port;
431 hostaddr.sin_family = AF_INET;
432 memcpy(&hostaddr.sin_addr,he->h_addr_list[0],4);
435 //Now we will connect to the host
436 if(connect(m_ControlSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR)))
438 // int iWinsockErr = WSAGetLastError();
439 m_State = FTP_STATE_CANT_CONNECT;
442 m_State = FTP_STATE_LOGGING_IN;
447 int CFtpGet::LoginHost()
449 char szLoginString[200];
452 sprintf(szLoginString,"USER %s\r\n",m_szUserName);
453 rcode = SendFTPCommand(szLoginString);
456 m_State = FTP_STATE_LOGIN_ERROR;
459 sprintf(szLoginString,"PASS %s\r\n",m_szPassword);
460 rcode = SendFTPCommand(szLoginString);
463 m_State = FTP_STATE_LOGIN_ERROR;
467 m_State = FTP_STATE_LOGGED_IN;
472 unsigned int CFtpGet::SendFTPCommand(char *command)
475 FlushControlChannel();
476 // Send the FTP command
477 if (SOCKET_ERROR ==(send(m_ControlSock,command,strlen(command), 0)))
479 // int iWinsockErr = WSAGetLastError();
480 // Return 999 to indicate an error has occurred
484 // Read the server's reply and return the reply code as an integer
485 return(ReadFTPServerReply());
490 unsigned int CFtpGet::ReadFTPServerReply()
493 unsigned int iBytesRead;
496 unsigned int igotcrlf = 0;
497 memset(recv_buffer,0,1000);
501 iBytesRead = recv(m_ControlSock,chunk,1,0);
503 if (iBytesRead == SOCKET_ERROR)
505 // int iWinsockErr = WSAGetLastError();
506 // Return 999 to indicate an error has occurred
510 if((chunk[0]==0x0a) || (chunk[0]==0x0d))
512 if(recv_buffer[0]!=0)
519 strcat(recv_buffer,chunk);
525 if(recv_buffer[3] == '-')
527 //Hack -- must be a MOTD
528 return ReadFTPServerReply();
530 if(recv_buffer[3] != ' ')
532 //We should have 3 numbers then a space
535 memcpy(szcode,recv_buffer,3);
537 rcode = atoi(szcode);
538 // Extract the reply code from the server reply and return as an integer
543 unsigned int CFtpGet::ReadDataChannel()
545 char sDataBuffer[4096]; // Data-storage buffer for the data channel
546 int nBytesRecv; // Bytes received from the data channel
547 m_State = FTP_STATE_RECEIVING;
554 nBytesRecv = recv(m_DataSock, (LPSTR)&sDataBuffer,sizeof(sDataBuffer), 0);
556 m_iBytesIn += nBytesRecv;
559 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
560 //Write sDataBuffer, nBytesRecv
564 }while (nBytesRecv > 0);
566 // Close the file and check for error returns.
567 if (nBytesRecv == SOCKET_ERROR)
569 //Ok, we got a socket error -- xfer aborted?
570 m_State = FTP_STATE_RECV_FAILED;
576 m_State = FTP_STATE_FILE_RECEIVED;
582 void CFtpGet::FlushControlChannel()
592 FD_SET(m_ControlSock,&read_fds);
594 while(select(0,&read_fds,NULL,NULL,&timeout))
596 recv(m_ControlSock,flushbuff,1,0);