Back to index

lightning-sunbird  0.9+nobinonly
nsFTPConn.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code, released
00016  * March 31, 1998.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Samir Gehani <sgehani@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include <stdio.h>
00041 #include <stdlib.h>
00042 #include <string.h>
00043 #include <ctype.h>
00044 #include <sys/stat.h>
00045 
00046 #if defined(__unix) || defined(__unix__) || defined(_AIX)
00047 #include <sys/param.h>
00048 #elif defined(_WINDOWS)
00049 #include <windows.h>
00050 #define MAXPATHLEN MAX_PATH
00051 #elif defined(macintosh)
00052 #define MAXPATHLEN 512
00053 #elif defined(__OS2__)
00054 #include <os2.h>
00055 #define MAXPATHLEN CCHMAXPATH
00056 #endif
00057 
00058 #include "nsSocket.h"
00059 #include "nsFTPConn.h"
00060 
00061 const int kCntlPort = 21;
00062 const int kDataPort = 20;
00063 const int kCmdBufSize = 64 + MAXPATHLEN;
00064 const int kRespBufSize = 1024;
00065 const int kKilobyte = 1024;
00066 const int kUsecsPerSec = 1000000;
00067 const int kDlBufSize = 1024;
00068 const int kMaxBailOnTimeOut = 50;
00069 
00070 nsFTPConn::nsFTPConn(char *aHost, int (*aEventPumpCB)(void)) :
00071     mEventPumpCB(aEventPumpCB),
00072     mHost(aHost),
00073     mState(CLOSED),
00074     mPassive(FALSE),
00075     mCntlSock(NULL),
00076     mDataSock(NULL)
00077 {
00078 }
00079 
00080 nsFTPConn::nsFTPConn(char *aHost) :
00081     mEventPumpCB(NULL),
00082     mHost(aHost),
00083     mState(CLOSED),
00084     mPassive(FALSE),
00085     mCntlSock(NULL),
00086     mDataSock(NULL)
00087 {
00088 }
00089 
00090 nsFTPConn::~nsFTPConn()
00091 {
00092     // don't release mHost cause we don't own it
00093 }
00094 
00095 int
00096 nsFTPConn::Open()
00097 {
00098     int err = OK;
00099     char cmd[kCmdBufSize], resp[kRespBufSize];
00100 
00101     if (!mHost)
00102         return E_PARAM;
00103     if (mState != CLOSED)
00104         return E_ALREADY_OPEN;
00105 
00106     /* open control connection on port 21 */
00107     mCntlSock = new nsSocket(mHost, kCntlPort, mEventPumpCB);
00108     if (!mCntlSock)
00109         return E_MEM;
00110     ERR_CHECK(mCntlSock->Open());
00111     FlushCntlSock(mCntlSock);
00112 
00113     /* issue USER command on control connection */
00114     err = IssueCmd("USER anonymous\r\n", resp, kRespBufSize, mCntlSock);
00115 
00116     /* issue PASS command on control connection */
00117     ERR_CHECK(IssueCmd("PASS -linux@installer.sbg\r\n", resp, kRespBufSize, mCntlSock));
00118 
00119     mState = OPEN;
00120 
00121     return err;
00122 
00123 BAIL:
00124     if (mCntlSock)
00125     {
00126         /* issue QUIT command on control connection */
00127         sprintf(cmd, "QUIT\r\n");
00128         IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
00129     }
00130     if (mDataSock)
00131     {
00132         mDataSock->Close();
00133         delete mDataSock;
00134         mDataSock = NULL;
00135         FlushCntlSock(mCntlSock);
00136     }
00137     if (mCntlSock)
00138     {
00139         mCntlSock->Close();
00140         delete mCntlSock;
00141         mCntlSock = NULL;
00142     }
00143     return err;
00144 }
00145 
00146 int
00147 nsFTPConn::Open(char *aHost)
00148 {
00149     if (!aHost)
00150         return E_PARAM;
00151 
00152     mHost = aHost;
00153     return Open();
00154 }
00155 
00156 int
00157 nsFTPConn::ResumeOrGet(char *aSrvPath, char *aLoclPath, int aType, 
00158                        int aOvWrite, FTPGetCB aCBFunc)
00159 {
00160     struct stat stbuf;
00161     int err = OK;
00162     int resPos = 0;
00163 
00164     if (!aLoclPath)
00165         return E_PARAM;
00166 
00167     /* stat local file */
00168     err = stat(aLoclPath, &stbuf);
00169     if (err == 0)
00170         resPos = stbuf.st_size;
00171 
00172     return Get(aSrvPath, aLoclPath, aType, resPos, aOvWrite, aCBFunc);
00173 }
00174 
00175 int
00176 nsFTPConn::Get(char *aSrvPath, char *aLoclPath, int aType,
00177                int aOvWrite, FTPGetCB aCBFunc)
00178 {
00179     // deprecated API; wrapper for backwards compatibility
00180 
00181     return ResumeOrGet(aSrvPath, aLoclPath, aType, aOvWrite, aCBFunc);
00182 }
00183 
00184 int
00185 nsFTPConn::Get(char *aSrvPath, char *aLoclPath, int aType, int aResumePos,
00186                int aOvWrite, FTPGetCB aCBFunc)
00187 {
00188     struct stat dummy;
00189     int err = OK, totBytesRd = 0;
00190     char cmd[kCmdBufSize], resp[kRespBufSize];
00191     int fileSize = 0;
00192     FILE *loclfd = NULL;
00193 
00194     if (!aSrvPath || !aLoclPath)
00195         return E_PARAM;
00196     if (mState != OPEN || !mCntlSock)
00197         return E_NOT_OPEN;
00198 
00199     /* stat local path and verify aOvWrite is set if file already exists */
00200     err = stat(aLoclPath, &dummy);
00201     if (err != -1 && aOvWrite == FALSE)
00202         return E_CANT_OVWRITE;
00203 
00204     mState = GETTING;
00205 
00206     /* initialize data connection */
00207     ERR_CHECK(DataInit(mHost, kDataPort, &mDataSock));
00208 
00209     /* issue TYPE command on control connection */
00210     sprintf(cmd, "TYPE %s\r\n", aType==BINARY ? "I" : "A");
00211     ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
00212     
00213     /* issue SIZE command on control connection */
00214     sprintf(cmd, "SIZE %s\r\n", aSrvPath);
00215     err = IssueCmd(cmd, resp, kRespBufSize, mCntlSock); /* non-fatal */
00216     if (err == OK && (resp[0] == '2'))
00217         fileSize = atoi(&resp[4]);   // else ???
00218 
00219     if (aResumePos > 0)
00220     {
00221         /* issue restart command */
00222         sprintf(cmd, "REST %d\r\n", aResumePos);
00223         ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
00224     }
00225 
00226     /* issue RETR command on control connection */
00227     sprintf(cmd, "RETR %s\r\n", aSrvPath);
00228     ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
00229 
00230     /* get file contents on data connection */
00231     if (!mPassive)
00232         ERR_CHECK(mDataSock->SrvAccept());
00233 
00234     /* initialize locl file */
00235     if (aResumePos <= 0)
00236     {
00237         if (!(loclfd = fopen(aLoclPath, aType==BINARY ? "w+b" : "w+")))
00238         {
00239             err = E_LOCL_INIT;
00240             goto BAIL;
00241         }
00242     }
00243     else
00244     {
00245         if (!(loclfd = fopen(aLoclPath, aType==BINARY ? "r+b" : "r+")) ||
00246             (fseek(loclfd, aResumePos, SEEK_SET) != 0)) 
00247         {
00248             err = E_LOCL_INIT;
00249             goto BAIL;
00250         }
00251     }
00252 
00253     do 
00254     {
00255         int respBufSize = kDlBufSize;
00256         err = mDataSock->Recv((unsigned char *)resp, &respBufSize);
00257         if (err != nsSocket::E_READ_MORE && 
00258             err != nsSocket::E_EOF_FOUND &&
00259             err != nsSocket::OK)
00260             goto BAIL;
00261         totBytesRd += respBufSize;
00262         if (err == nsSocket::E_READ_MORE && aCBFunc)
00263             if ((err = aCBFunc(totBytesRd, fileSize)) == E_USER_CANCEL)
00264                 goto BAIL;
00265             
00266         /* append to local file */
00267         const int wrote = fwrite((void *)resp, 1, respBufSize, loclfd);
00268         if (wrote != respBufSize)
00269         {   
00270             err = E_WRITE;
00271             goto BAIL;
00272         }
00273         if ( mEventPumpCB )
00274             mEventPumpCB();
00275     }
00276     while (err == nsSocket::E_READ_MORE || err == nsSocket::OK);
00277     if (err == nsSocket::E_EOF_FOUND)
00278         err = OK;
00279 
00280 BAIL:
00281        if ( err != OK && err != E_USER_CANCEL ) {
00282               sprintf(cmd, "QUIT\r\n");
00283               IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
00284        FlushCntlSock(mCntlSock);
00285     }
00286     
00287     /* close locl file if open */
00288     if (loclfd)
00289         fclose(loclfd);
00290     if ( err == E_USER_CANCEL ) {
00291         return err;
00292     }
00293     /* kill data connection if it exists */
00294     if (mDataSock)
00295     {
00296         /* err != OK means that the file attempted to be downloaded
00297          * was not successfully initiated.  This means that the server
00298          * will not post a response when the data socket is closed.
00299          * Since it's not going to post a response, we don't have to
00300          * wait kMaxBailOnTimeOut. */
00301         int bailOnTimeOut = err != OK ? 1 : kMaxBailOnTimeOut;
00302 
00303         mDataSock->Close();
00304         delete mDataSock;
00305         mDataSock = NULL;
00306 
00307         /* We are expecting a response from this call to FlushCntlSock()
00308          * _only_ if err == OK (meaning that a file was downloaded).
00309          * We return only when a responce has been received, or else the next
00310          * file that is to be downloaded will catch this response
00311          * and fail. */
00312         FlushCntlSock(mCntlSock, bailOnTimeOut);
00313     }
00314 
00315     mState = OPEN;
00316     mPassive = FALSE;
00317 
00318     return err;
00319 }
00320 
00321 int 
00322 nsFTPConn::Close()
00323 {
00324     int err = OK;
00325     char cmd[kCmdBufSize], resp[kRespBufSize];
00326 
00327     /* close sockets */
00328     if (mCntlSock)
00329     {
00330         /* issue QUIT command on control connection */
00331         sprintf(cmd, "ABORT\r\n");
00332         IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
00333     }
00334     if (mDataSock)
00335     {
00336         mDataSock->Close();
00337         delete mDataSock;
00338         mDataSock = NULL;
00339         FlushCntlSock(mCntlSock);
00340     }
00341     if (mCntlSock)
00342     {
00343         mCntlSock->Close();
00344         delete mCntlSock;
00345         mCntlSock = NULL;
00346     }
00347 
00348     return err;
00349 }
00350 
00351 int
00352 nsFTPConn::FlushCntlSock(nsSocket *aSock, int bailOnTimeOut)
00353 {
00354     int err = OK;
00355     char resp[kRespBufSize];
00356     int bailOnTimeOutCount = 0;
00357 
00358     /* param check */
00359     if (!aSock)
00360         return E_PARAM;
00361 
00362     do
00363     {
00364         // Time out value is in Usecs. This should give us 3 tries.
00365         const int timeout = 300000;
00366         ++bailOnTimeOutCount;
00367         int respSize = kRespBufSize;
00368         err = aSock->Recv((unsigned char *)resp, &respSize, timeout);
00369         if (err != nsSocket::OK && 
00370             err != nsSocket::E_READ_MORE && 
00371             err != nsSocket::E_EOF_FOUND)
00372         {
00373             if((err == nsSocket::E_TIMEOUT) &&
00374                (bailOnTimeOutCount < bailOnTimeOut))
00375                 // wait a little longer for response
00376                 err = nsSocket::E_READ_MORE;
00377             else
00378                 goto BAIL;
00379         }
00380 
00381         DUMP(resp);
00382         if ( mEventPumpCB )
00383             if (mEventPumpCB() == E_USER_CANCEL)
00384                 return E_USER_CANCEL;
00385     }
00386     while (err == nsSocket::E_READ_MORE);
00387 
00388     switch (*resp)
00389     {
00390         case '2':
00391             break;
00392         case '1':
00393         case '3':
00394             err = E_CMD_ERR;
00395             break;
00396         case '4':
00397         case '5':
00398             err = E_CMD_FAIL;
00399             break;
00400         default:
00401             err = E_CMD_UNEXPECTED;
00402             break;
00403     }
00404 
00405 BAIL:
00406     return err;
00407 }
00408 
00409 int
00410 nsFTPConn::IssueCmd(const char *aCmd, char *aResp, int aRespSize, nsSocket *aSock)
00411 {
00412     int err = OK;
00413     int len;
00414 
00415     /* param check */
00416     if (!aSock || !aCmd || !aResp || aRespSize <= 0)
00417         return E_PARAM;
00418 
00419     /* send command */
00420     len = strlen(aCmd);
00421     ERR_CHECK(aSock->Send((unsigned char *)aCmd, &len));
00422     DUMP(aCmd);
00423 
00424     /* receive response */
00425     do
00426     {
00427         int respSize = aRespSize;
00428         err = aSock->Recv((unsigned char *)aResp, &respSize);
00429         if (err != nsSocket::OK && 
00430             err != nsSocket::E_READ_MORE && 
00431             err != nsSocket::E_EOF_FOUND)
00432             goto BAIL;
00433         DUMP(aResp);
00434         if ( mEventPumpCB )
00435             if (mEventPumpCB() == E_USER_CANCEL)
00436                 return E_USER_CANCEL;
00437     }
00438     while (err == nsSocket::E_READ_MORE);
00439 
00440     /* alternate interpretation of err codes */
00441     if ( (strncmp(aCmd, "APPE", 4) == 0) ||
00442          (strncmp(aCmd, "LIST", 4) == 0) ||
00443          (strncmp(aCmd, "NLST", 4) == 0) ||
00444          (strncmp(aCmd, "REIN", 4) == 0) ||
00445          (strncmp(aCmd, "RETR", 4) == 0) ||
00446          (strncmp(aCmd, "STOR", 4) == 0) ||
00447          (strncmp(aCmd, "STOU", 4) == 0) )
00448     {
00449         switch (*aResp)
00450         {
00451             case '1':   /* exception: 100 series is OK */
00452             case '2':
00453                 break;
00454             case '3':
00455                 err = E_CMD_ERR;
00456                 break;
00457             case '4':
00458             case '5':
00459                 err = E_CMD_FAIL;
00460                 break;
00461             default:
00462                 err = E_CMD_UNEXPECTED;
00463                 break;
00464         }
00465     }
00466 
00467     /* restart command case */
00468     else if (strncmp(aCmd, "REST", 4) == 0)
00469     {
00470         switch (*aResp)
00471         {
00472             case '1':
00473             case '2':
00474                 err = E_CMD_ERR;
00475                 break;
00476             case '3':
00477                 break;
00478             case '4':
00479             case '5':
00480                 err = E_CMD_FAIL;
00481                 break;
00482             default:
00483                 err = E_CMD_UNEXPECTED;
00484                 break;
00485         }
00486     }
00487 
00488     /* quit command case */
00489     else if (strncmp(aCmd, "QUIT", 4) == 0)
00490     {
00491         err = OK;
00492         goto BAIL;
00493     }
00494 
00495     /* regular interpretation of err codes */
00496     else
00497     {
00498         switch (*aResp)
00499         {
00500             case '2':
00501                 break;
00502             case '1':
00503             case '3':
00504                 err = E_CMD_ERR;
00505                 break;
00506             case '4':
00507             case '5':
00508                 err = E_CMD_FAIL;
00509                 break;
00510             default:
00511                 err = E_CMD_UNEXPECTED;
00512                 break;
00513         }
00514     }
00515 
00516 BAIL:
00517     return err;
00518 }
00519 
00520 int
00521 nsFTPConn::ParseAddr(char *aBuf, char **aHost, int *aPort)
00522 {
00523     int err = OK;
00524     char *c;
00525     int addr[6];
00526 
00527     /* param check */
00528     if (!aBuf || !aHost || !aPort)
00529         return E_PARAM;
00530 
00531     c = aBuf + strlen("227 "); /* pass by return code */
00532     while (!isdigit((int)(*c)))
00533     {
00534         if (*c == '\0')
00535             return E_INVALID_ADDR;
00536         c++;
00537     }
00538 
00539     if (sscanf(c, "%d,%d,%d,%d,%d,%d", 
00540         &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]) != 6)
00541         return E_INVALID_ADDR;
00542 
00543     *aHost = (char *)malloc(strlen("XXX.XXX.XXX.XXX") + 1);
00544     sprintf(*aHost, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
00545 
00546     *aPort = ((addr[4] & 0xFF) << 8) | (addr[5] & 0xFF);
00547 
00548 #ifdef DEBUG
00549     printf("%s %d: PASV response: %d,%d,%d,%d,%d,%d\n", __FILE__, __LINE__,
00550             addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
00551     printf("%s %d: Host = %s\tPort = %d\n", __FILE__, __LINE__, *aHost, *aPort);
00552 #endif
00553     
00554     return err;
00555 }
00556 
00557 int
00558 nsFTPConn::DataInit(char *aHost, int aPort, nsSocket **aSock)
00559 {
00560     int err = OK;
00561     char cmd[kCmdBufSize], resp[kRespBufSize];
00562     char *srvHost = NULL; 
00563     int srvPort = 0;
00564     char *hostPort = NULL;
00565 
00566     /* param check */
00567     if (!aHost || !aSock)
00568         return E_PARAM;
00569 
00570     /* issue PASV command */
00571     sprintf(cmd, "PASV\r\n");
00572     err = IssueCmd(cmd, resp, kRespBufSize, mCntlSock);
00573     if (err != OK)
00574     {
00575         err = OK;
00576         goto ACTIVE; /* failover to active mode */
00577     }
00578        
00579     mPassive = TRUE;
00580 
00581     ERR_CHECK(ParseAddr(resp, &srvHost, &srvPort));
00582     *aSock = new nsSocket(srvHost, srvPort, mEventPumpCB);
00583     if (!*aSock)
00584     {
00585         err = E_MEM;
00586         goto BAIL;
00587     }
00588     
00589     ERR_CHECK((*aSock)->Open());
00590 
00591     if (srvHost) 
00592     {
00593         free(srvHost);
00594         srvHost = NULL;
00595     }
00596 
00597     return err;
00598 
00599 ACTIVE:
00600 
00601     *aSock = new nsSocket(aHost, aPort, mEventPumpCB);
00602     if (!*aSock)
00603     {
00604         err = E_MEM;
00605         goto BAIL;
00606     }
00607 
00608     /* init data socket making it listen */
00609     ERR_CHECK((*aSock)->SrvOpen());
00610 
00611     ERR_CHECK((*aSock)->GetHostPortString(&hostPort)); // allocates
00612     if (!hostPort)
00613     {
00614         err = E_MEM;
00615         goto BAIL;
00616     }
00617 
00618     sprintf(cmd, "PORT %s\r\n", hostPort);
00619     ERR_CHECK(IssueCmd(cmd, resp, kRespBufSize, mCntlSock));
00620 
00621 BAIL:
00622     if (mPassive && err != OK)
00623         mPassive = FALSE;
00624 
00625     if (err != OK && (*aSock))
00626     {
00627         delete *aSock;
00628         *aSock = NULL;
00629     }
00630 
00631     if (srvHost)
00632     {
00633         free(srvHost);
00634         srvHost = NULL;
00635     }
00636 
00637     if (hostPort)
00638     {
00639         free(hostPort);
00640         hostPort = NULL;
00641     }
00642 
00643     return err;
00644 }
00645 
00646 #ifdef TEST_NSFTPCONN
00647 static struct timeval init;
00648 int
00649 TestFTPGetCB(int aBytesRd, int aTotal)
00650 {
00651     struct timeval now;
00652     float rate;
00653 
00654     gettimeofday(&now, NULL);
00655     rate = nsSocket::CalcRate(&init, &now, aBytesRd);
00656     printf("br=%d\ttot=%d\trt=%f\tirt=%d\n",aBytesRd, aTotal, rate, (int)rate);
00657     return 0;
00658 }
00659 
00660 int
00661 main(int argc, char **argv)
00662 {
00663     int err = nsFTPConn::OK;
00664     nsFTPConn *conn = 0;
00665     char *leaf = NULL;
00666     
00667     if (argc < 2)
00668     {
00669         printf("usage: %s <host> <path/on/server>\n", argv[0]);
00670         exit(0);
00671     }
00672 
00673     if ((leaf = strrchr(argv[2], '/'))) leaf++;
00674     else leaf = argv[2];
00675 
00676     conn = new nsFTPConn(argv[1]);
00677 
00678     printf("Opening connection to %s...\n", argv[1]);
00679     err = conn->Open();
00680     if (err != nsFTPConn::OK) { printf("error: %d\n", err); exit(err); }
00681 
00682     printf("Getting binary file %s...\n", argv[2]);
00683     gettimeofday(&init, NULL);
00684     err = conn->Get(argv[2], leaf, nsFTPConn::BINARY, TRUE, TestFTPGetCB);
00685     if (err != nsFTPConn::OK) { printf("error: %d\n", err); exit(err); }
00686 
00687     printf("Closing connection to %s...\n", argv[1]);
00688     err = conn->Close();
00689     if (err != nsFTPConn::OK) { printf("error: %d\n", err); exit(err); }
00690 
00691     printf("Test successful!\n");
00692     exit(err);
00693 }
00694 #endif /* TEST_NSFTPCONN */