695 lines
17 KiB
C++
695 lines
17 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code, released
|
|
* March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Samir Gehani <sgehani@netscape.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
|
|
#if defined(__unix) || defined(__unix__) || defined(_AIX)
|
|
#include <sys/param.h>
|
|
#elif defined(_WINDOWS)
|
|
#include <windows.h>
|
|
#define MAXPATHLEN MAX_PATH
|
|
#elif defined(macintosh)
|
|
#define MAXPATHLEN 512
|
|
#elif defined(__OS2__)
|
|
#include <os2.h>
|
|
#define MAXPATHLEN CCHMAXPATH
|
|
#endif
|
|
|
|
#include "nsSocket.h"
|
|
#include "nsFTPConn.h"
|
|
|
|
const int kCntlPort = 21;
|
|
const int kDataPort = 20;
|
|
const int kCmdBufSize = 64 + MAXPATHLEN;
|
|
const int kRespBufSize = 1024;
|
|
const int kKilobyte = 1024;
|
|
const int kUsecsPerSec = 1000000;
|
|
const int kDlBufSize = 1024;
|
|
const int kMaxBailOnTimeOut = 50;
|
|
|
|
nsFTPConn::nsFTPConn(char *aHost, int (*aEventPumpCB)(void)) :
|
|
mEventPumpCB(aEventPumpCB),
|
|
mHost(aHost),
|
|
mState(CLOSED),
|
|
mPassive(FALSE),
|
|
mCntlSock(NULL),
|
|
mDataSock(NULL)
|
|
{
|
|
}
|
|
|
|
nsFTPConn::nsFTPConn(char *aHost) :
|
|
mEventPumpCB(NULL),
|
|
mHost(aHost),
|
|
mState(CLOSED),
|
|
mPassive(FALSE),
|
|
mCntlSock(NULL),
|
|
mDataSock(NULL)
|
|
{
|
|
}
|
|
|
|
nsFTPConn::~nsFTPConn()
|
|
{
|
|
// don't release mHost cause we don't own it
|
|
}
|
|
|
|
int
|
|
nsFTPConn::Open()
|
|
{
|
|
int err = OK;
|
|
char cmd[kCmdBufSize], resp[kRespBufSize];
|
|
|
|
if (!mHost)
|
|
return E_PARAM;
|
|
if (mState != CLOSED)
|
|
return E_ALREADY_OPEN;
|
|
|
|
/* open control connection on port 21 */
|
|
mCntlSock = new nsSocket(mHost, kCntlPort, mEventPumpCB);
|
|
if (!mCntlSock)
|
|
return E_MEM;
|
|
ERR_CHECK(mCntlSock->Open());
|
|
FlushCntlSock(mCntlSock);
|
|
|
|
/* issue USER command on control connection */
|
|
err = IssueCmd("USER anonymous\r\n", resp, kRespBufSize, mCntlSock);
|
|
|
|
/* issue PASS command on control connection */
|
|
ERR_CHECK(IssueCmd("PASS -linux@installer.sbg\r\n", resp, kRespBufSize, mCntlSock));
|
|
|
|
mState = OPEN;
|
|
|
|
return err;
|
|
|
|
BAIL:
|
|
if (mCntlSock)
|
|
{
|
|
/* issue QUIT command on control connection */
|
|
sprintf(cmd, "QUIT\r\n");
|
|
IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
|
|
}
|
|
if (mDataSock)
|
|
{
|
|
mDataSock->Close();
|
|
delete mDataSock;
|
|
mDataSock = NULL;
|
|
FlushCntlSock(mCntlSock);
|
|
}
|
|
if (mCntlSock)
|
|
{
|
|
mCntlSock->Close();
|
|
delete mCntlSock;
|
|
mCntlSock = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int
|
|
nsFTPConn::Open(char *aHost)
|
|
{
|
|
if (!aHost)
|
|
return E_PARAM;
|
|
|
|
mHost = aHost;
|
|
return Open();
|
|
}
|
|
|
|
int
|
|
nsFTPConn::ResumeOrGet(char *aSrvPath, char *aLoclPath, int aType,
|
|
int aOvWrite, FTPGetCB aCBFunc)
|
|
{
|
|
struct stat stbuf;
|
|
int err = OK;
|
|
int resPos = 0;
|
|
|
|
if (!aLoclPath)
|
|
return E_PARAM;
|
|
|
|
/* stat local file */
|
|
err = stat(aLoclPath, &stbuf);
|
|
if (err == 0)
|
|
resPos = stbuf.st_size;
|
|
|
|
return Get(aSrvPath, aLoclPath, aType, resPos, aOvWrite, aCBFunc);
|
|
}
|
|
|
|
int
|
|
nsFTPConn::Get(char *aSrvPath, char *aLoclPath, int aType,
|
|
int aOvWrite, FTPGetCB aCBFunc)
|
|
{
|
|
// deprecated API; wrapper for backwards compatibility
|
|
|
|
return ResumeOrGet(aSrvPath, aLoclPath, aType, aOvWrite, aCBFunc);
|
|
}
|
|
|
|
int
|
|
nsFTPConn::Get(char *aSrvPath, char *aLoclPath, int aType, int aResumePos,
|
|
int aOvWrite, FTPGetCB aCBFunc)
|
|
{
|
|
struct stat dummy;
|
|
int err = OK, totBytesRd = 0;
|
|
char cmd[kCmdBufSize], resp[kRespBufSize];
|
|
int fileSize = 0;
|
|
FILE *loclfd = NULL;
|
|
|
|
if (!aSrvPath || !aLoclPath)
|
|
return E_PARAM;
|
|
if (mState != OPEN || !mCntlSock)
|
|
return E_NOT_OPEN;
|
|
|
|
/* stat local path and verify aOvWrite is set if file already exists */
|
|
err = stat(aLoclPath, &dummy);
|
|
if (err != -1 && aOvWrite == FALSE)
|
|
return E_CANT_OVWRITE;
|
|
|
|
mState = GETTING;
|
|
|
|
/* initialize data connection */
|
|
ERR_CHECK(DataInit(mHost, kDataPort, &mDataSock));
|
|
|
|
/* issue TYPE command on control connection */
|
|
sprintf(cmd, "TYPE %s\r\n", aType==BINARY ? "I" : "A");
|
|
ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
|
|
|
|
/* issue SIZE command on control connection */
|
|
sprintf(cmd, "SIZE %s\r\n", aSrvPath);
|
|
err = IssueCmd(cmd, resp, kRespBufSize, mCntlSock); /* non-fatal */
|
|
if (err == OK && (resp[0] == '2'))
|
|
fileSize = atoi(&resp[4]); // else ???
|
|
|
|
if (aResumePos > 0)
|
|
{
|
|
/* issue restart command */
|
|
sprintf(cmd, "REST %d\r\n", aResumePos);
|
|
ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
|
|
}
|
|
|
|
/* issue RETR command on control connection */
|
|
sprintf(cmd, "RETR %s\r\n", aSrvPath);
|
|
ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
|
|
|
|
/* get file contents on data connection */
|
|
if (!mPassive)
|
|
ERR_CHECK(mDataSock->SrvAccept());
|
|
|
|
/* initialize locl file */
|
|
if (aResumePos <= 0)
|
|
{
|
|
if (!(loclfd = fopen(aLoclPath, aType==BINARY ? "w+b" : "w+")))
|
|
{
|
|
err = E_LOCL_INIT;
|
|
goto BAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(loclfd = fopen(aLoclPath, aType==BINARY ? "r+b" : "r+")) ||
|
|
(fseek(loclfd, aResumePos, SEEK_SET) != 0))
|
|
{
|
|
err = E_LOCL_INIT;
|
|
goto BAIL;
|
|
}
|
|
}
|
|
|
|
do
|
|
{
|
|
int respBufSize = kDlBufSize;
|
|
err = mDataSock->Recv((unsigned char *)resp, &respBufSize);
|
|
if (err != nsSocket::E_READ_MORE &&
|
|
err != nsSocket::E_EOF_FOUND &&
|
|
err != nsSocket::OK)
|
|
goto BAIL;
|
|
totBytesRd += respBufSize;
|
|
if (err == nsSocket::E_READ_MORE && aCBFunc)
|
|
if ((err = aCBFunc(totBytesRd, fileSize)) == E_USER_CANCEL)
|
|
goto BAIL;
|
|
|
|
/* append to local file */
|
|
const int wrote = fwrite((void *)resp, 1, respBufSize, loclfd);
|
|
if (wrote != respBufSize)
|
|
{
|
|
err = E_WRITE;
|
|
goto BAIL;
|
|
}
|
|
if ( mEventPumpCB )
|
|
mEventPumpCB();
|
|
}
|
|
while (err == nsSocket::E_READ_MORE || err == nsSocket::OK);
|
|
if (err == nsSocket::E_EOF_FOUND)
|
|
err = OK;
|
|
|
|
BAIL:
|
|
if ( err != OK && err != E_USER_CANCEL ) {
|
|
sprintf(cmd, "QUIT\r\n");
|
|
IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
|
|
FlushCntlSock(mCntlSock);
|
|
}
|
|
|
|
/* close locl file if open */
|
|
if (loclfd)
|
|
fclose(loclfd);
|
|
if ( err == E_USER_CANCEL ) {
|
|
return err;
|
|
}
|
|
/* kill data connection if it exists */
|
|
if (mDataSock)
|
|
{
|
|
/* err != OK means that the file attempted to be downloaded
|
|
* was not successfully initiated. This means that the server
|
|
* will not post a response when the data socket is closed.
|
|
* Since it's not going to post a response, we don't have to
|
|
* wait kMaxBailOnTimeOut. */
|
|
int bailOnTimeOut = err != OK ? 1 : kMaxBailOnTimeOut;
|
|
|
|
mDataSock->Close();
|
|
delete mDataSock;
|
|
mDataSock = NULL;
|
|
|
|
/* We are expecting a response from this call to FlushCntlSock()
|
|
* _only_ if err == OK (meaning that a file was downloaded).
|
|
* We return only when a responce has been received, or else the next
|
|
* file that is to be downloaded will catch this response
|
|
* and fail. */
|
|
FlushCntlSock(mCntlSock, bailOnTimeOut);
|
|
}
|
|
|
|
mState = OPEN;
|
|
mPassive = FALSE;
|
|
|
|
return err;
|
|
}
|
|
|
|
int
|
|
nsFTPConn::Close()
|
|
{
|
|
int err = OK;
|
|
char cmd[kCmdBufSize], resp[kRespBufSize];
|
|
|
|
/* close sockets */
|
|
if (mCntlSock)
|
|
{
|
|
/* issue QUIT command on control connection */
|
|
sprintf(cmd, "ABORT\r\n");
|
|
IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
|
|
}
|
|
if (mDataSock)
|
|
{
|
|
mDataSock->Close();
|
|
delete mDataSock;
|
|
mDataSock = NULL;
|
|
FlushCntlSock(mCntlSock);
|
|
}
|
|
if (mCntlSock)
|
|
{
|
|
mCntlSock->Close();
|
|
delete mCntlSock;
|
|
mCntlSock = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int
|
|
nsFTPConn::FlushCntlSock(nsSocket *aSock, int bailOnTimeOut)
|
|
{
|
|
int err = OK;
|
|
char resp[kRespBufSize];
|
|
int bailOnTimeOutCount = 0;
|
|
|
|
/* param check */
|
|
if (!aSock)
|
|
return E_PARAM;
|
|
|
|
do
|
|
{
|
|
// Time out value is in Usecs. This should give us 3 tries.
|
|
const int timeout = 300000;
|
|
++bailOnTimeOutCount;
|
|
int respSize = kRespBufSize;
|
|
err = aSock->Recv((unsigned char *)resp, &respSize, timeout);
|
|
if (err != nsSocket::OK &&
|
|
err != nsSocket::E_READ_MORE &&
|
|
err != nsSocket::E_EOF_FOUND)
|
|
{
|
|
if((err == nsSocket::E_TIMEOUT) &&
|
|
(bailOnTimeOutCount < bailOnTimeOut))
|
|
// wait a little longer for response
|
|
err = nsSocket::E_READ_MORE;
|
|
else
|
|
goto BAIL;
|
|
}
|
|
|
|
DUMP(resp);
|
|
if ( mEventPumpCB )
|
|
if (mEventPumpCB() == E_USER_CANCEL)
|
|
return E_USER_CANCEL;
|
|
}
|
|
while (err == nsSocket::E_READ_MORE);
|
|
|
|
switch (*resp)
|
|
{
|
|
case '2':
|
|
break;
|
|
case '1':
|
|
case '3':
|
|
err = E_CMD_ERR;
|
|
break;
|
|
case '4':
|
|
case '5':
|
|
err = E_CMD_FAIL;
|
|
break;
|
|
default:
|
|
err = E_CMD_UNEXPECTED;
|
|
break;
|
|
}
|
|
|
|
BAIL:
|
|
return err;
|
|
}
|
|
|
|
int
|
|
nsFTPConn::IssueCmd(const char *aCmd, char *aResp, int aRespSize, nsSocket *aSock)
|
|
{
|
|
int err = OK;
|
|
int len;
|
|
|
|
/* param check */
|
|
if (!aSock || !aCmd || !aResp || aRespSize <= 0)
|
|
return E_PARAM;
|
|
|
|
/* send command */
|
|
len = strlen(aCmd);
|
|
ERR_CHECK(aSock->Send((unsigned char *)aCmd, &len));
|
|
DUMP(aCmd);
|
|
|
|
/* receive response */
|
|
do
|
|
{
|
|
int respSize = aRespSize;
|
|
err = aSock->Recv((unsigned char *)aResp, &respSize);
|
|
if (err != nsSocket::OK &&
|
|
err != nsSocket::E_READ_MORE &&
|
|
err != nsSocket::E_EOF_FOUND)
|
|
goto BAIL;
|
|
DUMP(aResp);
|
|
if ( mEventPumpCB )
|
|
if (mEventPumpCB() == E_USER_CANCEL)
|
|
return E_USER_CANCEL;
|
|
}
|
|
while (err == nsSocket::E_READ_MORE);
|
|
|
|
/* alternate interpretation of err codes */
|
|
if ( (strncmp(aCmd, "APPE", 4) == 0) ||
|
|
(strncmp(aCmd, "LIST", 4) == 0) ||
|
|
(strncmp(aCmd, "NLST", 4) == 0) ||
|
|
(strncmp(aCmd, "REIN", 4) == 0) ||
|
|
(strncmp(aCmd, "RETR", 4) == 0) ||
|
|
(strncmp(aCmd, "STOR", 4) == 0) ||
|
|
(strncmp(aCmd, "STOU", 4) == 0) )
|
|
{
|
|
switch (*aResp)
|
|
{
|
|
case '1': /* exception: 100 series is OK */
|
|
case '2':
|
|
break;
|
|
case '3':
|
|
err = E_CMD_ERR;
|
|
break;
|
|
case '4':
|
|
case '5':
|
|
err = E_CMD_FAIL;
|
|
break;
|
|
default:
|
|
err = E_CMD_UNEXPECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* restart command case */
|
|
else if (strncmp(aCmd, "REST", 4) == 0)
|
|
{
|
|
switch (*aResp)
|
|
{
|
|
case '1':
|
|
case '2':
|
|
err = E_CMD_ERR;
|
|
break;
|
|
case '3':
|
|
break;
|
|
case '4':
|
|
case '5':
|
|
err = E_CMD_FAIL;
|
|
break;
|
|
default:
|
|
err = E_CMD_UNEXPECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* quit command case */
|
|
else if (strncmp(aCmd, "QUIT", 4) == 0)
|
|
{
|
|
err = OK;
|
|
goto BAIL;
|
|
}
|
|
|
|
/* regular interpretation of err codes */
|
|
else
|
|
{
|
|
switch (*aResp)
|
|
{
|
|
case '2':
|
|
break;
|
|
case '1':
|
|
case '3':
|
|
err = E_CMD_ERR;
|
|
break;
|
|
case '4':
|
|
case '5':
|
|
err = E_CMD_FAIL;
|
|
break;
|
|
default:
|
|
err = E_CMD_UNEXPECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
BAIL:
|
|
return err;
|
|
}
|
|
|
|
int
|
|
nsFTPConn::ParseAddr(char *aBuf, char **aHost, int *aPort)
|
|
{
|
|
int err = OK;
|
|
char *c;
|
|
int addr[6];
|
|
|
|
/* param check */
|
|
if (!aBuf || !aHost || !aPort)
|
|
return E_PARAM;
|
|
|
|
c = aBuf + strlen("227 "); /* pass by return code */
|
|
while (!isdigit((int)(*c)))
|
|
{
|
|
if (*c == '\0')
|
|
return E_INVALID_ADDR;
|
|
c++;
|
|
}
|
|
|
|
if (sscanf(c, "%d,%d,%d,%d,%d,%d",
|
|
&addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]) != 6)
|
|
return E_INVALID_ADDR;
|
|
|
|
*aHost = (char *)malloc(strlen("XXX.XXX.XXX.XXX") + 1);
|
|
sprintf(*aHost, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
|
|
|
|
*aPort = ((addr[4] & 0xFF) << 8) | (addr[5] & 0xFF);
|
|
|
|
#ifdef DEBUG
|
|
printf("%s %d: PASV response: %d,%d,%d,%d,%d,%d\n", __FILE__, __LINE__,
|
|
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
|
|
printf("%s %d: Host = %s\tPort = %d\n", __FILE__, __LINE__, *aHost, *aPort);
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
int
|
|
nsFTPConn::DataInit(char *aHost, int aPort, nsSocket **aSock)
|
|
{
|
|
int err = OK;
|
|
char cmd[kCmdBufSize], resp[kRespBufSize];
|
|
char *srvHost = NULL;
|
|
int srvPort = 0;
|
|
char *hostPort = NULL;
|
|
|
|
/* param check */
|
|
if (!aHost || !aSock)
|
|
return E_PARAM;
|
|
|
|
/* issue PASV command */
|
|
sprintf(cmd, "PASV\r\n");
|
|
err = IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
|
|
if (err != OK)
|
|
{
|
|
err = OK;
|
|
goto ACTIVE; /* failover to active mode */
|
|
}
|
|
|
|
mPassive = TRUE;
|
|
|
|
ERR_CHECK(ParseAddr(resp, &srvHost, &srvPort));
|
|
*aSock = new nsSocket(srvHost, srvPort, mEventPumpCB);
|
|
if (!*aSock)
|
|
{
|
|
err = E_MEM;
|
|
goto BAIL;
|
|
}
|
|
|
|
ERR_CHECK((*aSock)->Open());
|
|
|
|
if (srvHost)
|
|
{
|
|
free(srvHost);
|
|
srvHost = NULL;
|
|
}
|
|
|
|
return err;
|
|
|
|
ACTIVE:
|
|
|
|
*aSock = new nsSocket(aHost, aPort, mEventPumpCB);
|
|
if (!*aSock)
|
|
{
|
|
err = E_MEM;
|
|
goto BAIL;
|
|
}
|
|
|
|
/* init data socket making it listen */
|
|
ERR_CHECK((*aSock)->SrvOpen());
|
|
|
|
ERR_CHECK((*aSock)->GetHostPortString(&hostPort)); // allocates
|
|
if (!hostPort)
|
|
{
|
|
err = E_MEM;
|
|
goto BAIL;
|
|
}
|
|
|
|
sprintf(cmd, "PORT %s\r\n", hostPort);
|
|
ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
|
|
|
|
BAIL:
|
|
if (mPassive && err != OK)
|
|
mPassive = FALSE;
|
|
|
|
if (err != OK && (*aSock))
|
|
{
|
|
delete *aSock;
|
|
*aSock = NULL;
|
|
}
|
|
|
|
if (srvHost)
|
|
{
|
|
free(srvHost);
|
|
srvHost = NULL;
|
|
}
|
|
|
|
if (hostPort)
|
|
{
|
|
free(hostPort);
|
|
hostPort = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef TEST_NSFTPCONN
|
|
static struct timeval init;
|
|
int
|
|
TestFTPGetCB(int aBytesRd, int aTotal)
|
|
{
|
|
struct timeval now;
|
|
float rate;
|
|
|
|
gettimeofday(&now, NULL);
|
|
rate = nsSocket::CalcRate(&init, &now, aBytesRd);
|
|
printf("br=%d\ttot=%d\trt=%f\tirt=%d\n",aBytesRd, aTotal, rate, (int)rate);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int err = nsFTPConn::OK;
|
|
nsFTPConn *conn = 0;
|
|
char *leaf = NULL;
|
|
|
|
if (argc < 2)
|
|
{
|
|
printf("usage: %s <host> <path/on/server>\n", argv[0]);
|
|
exit(0);
|
|
}
|
|
|
|
if ((leaf = strrchr(argv[2], '/'))) leaf++;
|
|
else leaf = argv[2];
|
|
|
|
conn = new nsFTPConn(argv[1]);
|
|
|
|
printf("Opening connection to %s...\n", argv[1]);
|
|
err = conn->Open();
|
|
if (err != nsFTPConn::OK) { printf("error: %d\n", err); exit(err); }
|
|
|
|
printf("Getting binary file %s...\n", argv[2]);
|
|
gettimeofday(&init, NULL);
|
|
err = conn->Get(argv[2], leaf, nsFTPConn::BINARY, TRUE, TestFTPGetCB);
|
|
if (err != nsFTPConn::OK) { printf("error: %d\n", err); exit(err); }
|
|
|
|
printf("Closing connection to %s...\n", argv[1]);
|
|
err = conn->Close();
|
|
if (err != nsFTPConn::OK) { printf("error: %d\n", err); exit(err); }
|
|
|
|
printf("Test successful!\n");
|
|
exit(err);
|
|
}
|
|
#endif /* TEST_NSFTPCONN */
|