12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147 |
- /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
- /* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include <ctype.h>
- #include "prprf.h"
- #include "mozilla/Logging.h"
- #include "prtime.h"
- #include "nsIOService.h"
- #include "nsFTPChannel.h"
- #include "nsFtpConnectionThread.h"
- #include "nsFtpControlConnection.h"
- #include "nsFtpProtocolHandler.h"
- #include "netCore.h"
- #include "nsCRT.h"
- #include "nsEscape.h"
- #include "nsMimeTypes.h"
- #include "nsNetCID.h"
- #include "nsNetUtil.h"
- #include "nsIAsyncStreamCopier.h"
- #include "nsThreadUtils.h"
- #include "nsStreamUtils.h"
- #include "nsIURL.h"
- #include "nsISocketTransport.h"
- #include "nsIStreamListenerTee.h"
- #include "nsIPrefService.h"
- #include "nsIPrefBranch.h"
- #include "nsAuthInformationHolder.h"
- #include "nsIProtocolProxyService.h"
- #include "nsICancelable.h"
- #include "nsIOutputStream.h"
- #include "nsIPrompt.h"
- #include "nsIProtocolHandler.h"
- #include "nsIProxyInfo.h"
- #include "nsIRunnable.h"
- #include "nsISocketTransportService.h"
- #include "nsIURI.h"
- #include "nsILoadInfo.h"
- #include "nsNullPrincipal.h"
- #include "nsIAuthPrompt2.h"
- #include "nsIFTPChannelParentInternal.h"
- using namespace mozilla;
- using namespace mozilla::net;
- extern LazyLogModule gFTPLog;
- #define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
- #define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args)
- // remove FTP parameters (starting with ";") from the path
- static void
- removeParamsFromPath(nsCString& path)
- {
- int32_t index = path.FindChar(';');
- if (index >= 0) {
- path.SetLength(index);
- }
- }
- NS_IMPL_ISUPPORTS_INHERITED(nsFtpState,
- nsBaseContentStream,
- nsIInputStreamCallback,
- nsITransportEventSink,
- nsIRequestObserver,
- nsIProtocolProxyCallback)
- nsFtpState::nsFtpState()
- : nsBaseContentStream(true)
- , mState(FTP_INIT)
- , mNextState(FTP_S_USER)
- , mKeepRunning(true)
- , mReceivedControlData(false)
- , mTryingCachedControl(false)
- , mRETRFailed(false)
- , mFileSize(kJS_MAX_SAFE_UINTEGER)
- , mServerType(FTP_GENERIC_TYPE)
- , mAction(GET)
- , mAnonymous(true)
- , mRetryPass(false)
- , mStorReplyReceived(false)
- , mRlist1xxReceived(false)
- , mRstor1xxReceived(false)
- , mRretr1xxReceived(false)
- , mInternalError(NS_OK)
- , mReconnectAndLoginAgain(false)
- , mCacheConnection(true)
- , mPort(21)
- , mAddressChecked(false)
- , mServerIsIPv6(false)
- , mUseUTF8(false)
- , mControlStatus(NS_OK)
- , mDeferredCallbackPending(false)
- {
- LOG_INFO(("FTP:(%x) nsFtpState created", this));
- // make sure handler stays around
- NS_ADDREF(gFtpHandler);
- }
- nsFtpState::~nsFtpState()
- {
- LOG_INFO(("FTP:(%x) nsFtpState destroyed", this));
- if (mProxyRequest)
- mProxyRequest->Cancel(NS_ERROR_FAILURE);
- // release reference to handler
- nsFtpProtocolHandler *handler = gFtpHandler;
- NS_RELEASE(handler);
- }
- // nsIInputStreamCallback implementation
- NS_IMETHODIMP
- nsFtpState::OnInputStreamReady(nsIAsyncInputStream *aInStream)
- {
- LOG(("FTP:(%p) data stream ready\n", this));
- // We are receiving a notification from our data stream, so just forward it
- // on to our stream callback.
- if (HasPendingCallback())
- DispatchCallbackSync();
- return NS_OK;
- }
- void
- nsFtpState::OnControlDataAvailable(const char *aData, uint32_t aDataLen)
- {
- LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen));
- mControlConnection->WaitData(this); // queue up another call
- if (!mReceivedControlData) {
- // parameter can be null cause the channel fills them in.
- OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0);
- mReceivedControlData = true;
- }
- // Sometimes we can get two responses in the same packet, eg from LIST.
- // So we need to parse the response line by line
- nsCString buffer = mControlReadCarryOverBuf;
- // Clear the carryover buf - if we still don't have a line, then it will
- // be reappended below
- mControlReadCarryOverBuf.Truncate();
- buffer.Append(aData, aDataLen);
- const char* currLine = buffer.get();
- while (*currLine && mKeepRunning) {
- int32_t eolLength = strcspn(currLine, CRLF);
- int32_t currLineLength = strlen(currLine);
- // if currLine is empty or only contains CR or LF, then bail. we can
- // sometimes get an ODA event with the full response line + CR without
- // the trailing LF. the trailing LF might come in the next ODA event.
- // because we are happy enough to process a response line ending only
- // in CR, we need to take care to discard the extra LF (bug 191220).
- if (eolLength == 0 && currLineLength <= 1)
- break;
- if (eolLength == currLineLength) {
- mControlReadCarryOverBuf.Assign(currLine);
- break;
- }
- // Append the current segment, including the LF
- nsAutoCString line;
- int32_t crlfLength = 0;
- if ((currLineLength > eolLength) &&
- (currLine[eolLength] == nsCRT::CR) &&
- (currLine[eolLength+1] == nsCRT::LF)) {
- crlfLength = 2; // CR +LF
- } else {
- crlfLength = 1; // + LF or CR
- }
- line.Assign(currLine, eolLength + crlfLength);
-
- // Does this start with a response code?
- bool startNum = (line.Length() >= 3 &&
- isdigit(line[0]) &&
- isdigit(line[1]) &&
- isdigit(line[2]));
- if (mResponseMsg.IsEmpty()) {
- // If we get here, then we know that we have a complete line, and
- // that it is the first one
- NS_ASSERTION(line.Length() > 4 && startNum,
- "Read buffer doesn't include response code");
-
- mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get());
- }
- mResponseMsg.Append(line);
- // This is the last line if its 3 numbers followed by a space
- if (startNum && line[3] == ' ') {
- // yup. last line, let's move on.
- if (mState == mNextState) {
- NS_ERROR("ftp read state mixup");
- mInternalError = NS_ERROR_FAILURE;
- mState = FTP_ERROR;
- } else {
- mState = mNextState;
- }
- nsCOMPtr<nsIFTPEventSink> ftpSink;
- mChannel->GetFTPEventSink(ftpSink);
- if (ftpSink)
- ftpSink->OnFTPControlLog(true, mResponseMsg.get());
-
- nsresult rv = Process();
- mResponseMsg.Truncate();
- if (NS_FAILED(rv)) {
- CloseWithStatus(rv);
- return;
- }
- }
- currLine = currLine + eolLength + crlfLength;
- }
- }
- void
- nsFtpState::OnControlError(nsresult status)
- {
- NS_ASSERTION(NS_FAILED(status), "expecting error condition");
- LOG(("FTP:(%p) CC(%p) error [%x was-cached=%u]\n",
- this, mControlConnection.get(), status, mTryingCachedControl));
- mControlStatus = status;
- if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) {
- mReconnectAndLoginAgain = false;
- mAnonymous = false;
- mControlStatus = NS_OK;
- Connect();
- } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) {
- mTryingCachedControl = false;
- Connect();
- } else {
- CloseWithStatus(status);
- }
- }
- nsresult
- nsFtpState::EstablishControlConnection()
- {
- NS_ASSERTION(!mControlConnection, "we already have a control connection");
-
- nsresult rv;
- LOG(("FTP:(%x) trying cached control\n", this));
-
- // Look to see if we can use a cached control connection:
- RefPtr<nsFtpControlConnection> connection;
- // Don't use cached control if anonymous (bug #473371)
- if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
- gFtpHandler->RemoveConnection(mChannel->URI(), getter_AddRefs(connection));
- if (connection) {
- mControlConnection.swap(connection);
- if (mControlConnection->IsAlive())
- {
- // set stream listener of the control connection to be us.
- mControlConnection->WaitData(this);
-
- // read cached variables into us.
- mServerType = mControlConnection->mServerType;
- mPassword = mControlConnection->mPassword;
- mPwd = mControlConnection->mPwd;
- mUseUTF8 = mControlConnection->mUseUTF8;
- mTryingCachedControl = true;
- // we have to set charset to connection if server supports utf-8
- if (mUseUTF8)
- mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
-
- // we're already connected to this server, skip login.
- mState = FTP_S_PASV;
- mResponseCode = 530; // assume the control connection was dropped.
- mControlStatus = NS_OK;
- mReceivedControlData = false; // For this request, we have not.
- // if we succeed, return. Otherwise, we need to create a transport
- rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
- if (NS_SUCCEEDED(rv))
- return rv;
- }
- LOG(("FTP:(%p) cached CC(%p) is unusable\n", this,
- mControlConnection.get()));
- mControlConnection->WaitData(nullptr);
- mControlConnection = nullptr;
- }
- LOG(("FTP:(%p) creating CC\n", this));
-
- mState = FTP_READ_BUF;
- mNextState = FTP_S_USER;
-
- nsAutoCString host;
- rv = mChannel->URI()->GetAsciiHost(host);
- if (NS_FAILED(rv))
- return rv;
- mControlConnection = new nsFtpControlConnection(host, mPort);
- if (!mControlConnection)
- return NS_ERROR_OUT_OF_MEMORY;
- rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
- if (NS_FAILED(rv)) {
- LOG(("FTP:(%p) CC(%p) failed to connect [rv=%x]\n", this,
- mControlConnection.get(), rv));
- mControlConnection = nullptr;
- return rv;
- }
- return mControlConnection->WaitData(this);
- }
- void
- nsFtpState::MoveToNextState(FTP_STATE nextState)
- {
- if (NS_FAILED(mInternalError)) {
- mState = FTP_ERROR;
- LOG(("FTP:(%x) FAILED (%x)\n", this, mInternalError));
- } else {
- mState = FTP_READ_BUF;
- mNextState = nextState;
- }
- }
- nsresult
- nsFtpState::Process()
- {
- nsresult rv = NS_OK;
- bool processingRead = true;
-
- while (mKeepRunning && processingRead) {
- switch (mState) {
- case FTP_COMMAND_CONNECT:
- KillControlConnection();
- LOG(("FTP:(%p) establishing CC", this));
- mInternalError = EstablishControlConnection(); // sets mState
- if (NS_FAILED(mInternalError)) {
- mState = FTP_ERROR;
- LOG(("FTP:(%p) FAILED\n", this));
- } else {
- LOG(("FTP:(%p) SUCCEEDED\n", this));
- }
- break;
-
- case FTP_READ_BUF:
- LOG(("FTP:(%p) Waiting for CC(%p)\n", this,
- mControlConnection.get()));
- processingRead = false;
- break;
-
- case FTP_ERROR: // xx needs more work to handle dropped control connection cases
- if ((mTryingCachedControl && mResponseCode == 530 &&
- mInternalError == NS_ERROR_FTP_PASV) ||
- (mResponseCode == 425 &&
- mInternalError == NS_ERROR_FTP_PASV)) {
- // The user was logged out during an pasv operation
- // we want to restart this request with a new control
- // channel.
- mState = FTP_COMMAND_CONNECT;
- } else if (mResponseCode == 421 &&
- mInternalError != NS_ERROR_FTP_LOGIN) {
- // The command channel dropped for some reason.
- // Fire it back up, unless we were trying to login
- // in which case the server might just be telling us
- // that the max number of users has been reached...
- mState = FTP_COMMAND_CONNECT;
- } else if (mAnonymous &&
- mInternalError == NS_ERROR_FTP_LOGIN) {
- // If the login was anonymous, and it failed, try again with a username
- // Don't reuse old control connection, see #386167
- mAnonymous = false;
- mState = FTP_COMMAND_CONNECT;
- } else {
- LOG(("FTP:(%x) FTP_ERROR - calling StopProcessing\n", this));
- rv = StopProcessing();
- NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
- processingRead = false;
- }
- break;
-
- case FTP_COMPLETE:
- LOG(("FTP:(%x) COMPLETE\n", this));
- rv = StopProcessing();
- NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
- processingRead = false;
- break;
- // USER
- case FTP_S_USER:
- rv = S_user();
-
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- MoveToNextState(FTP_R_USER);
- break;
-
- case FTP_R_USER:
- mState = R_user();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- break;
- // PASS
- case FTP_S_PASS:
- rv = S_pass();
-
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- MoveToNextState(FTP_R_PASS);
- break;
-
- case FTP_R_PASS:
- mState = R_pass();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- break;
- // ACCT
- case FTP_S_ACCT:
- rv = S_acct();
-
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- MoveToNextState(FTP_R_ACCT);
- break;
-
- case FTP_R_ACCT:
- mState = R_acct();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- break;
- // SYST
- case FTP_S_SYST:
- rv = S_syst();
-
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_LOGIN;
-
- MoveToNextState(FTP_R_SYST);
- break;
-
- case FTP_R_SYST:
- mState = R_syst();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_LOGIN;
- break;
- // TYPE
- case FTP_S_TYPE:
- rv = S_type();
-
- if (NS_FAILED(rv))
- mInternalError = rv;
-
- MoveToNextState(FTP_R_TYPE);
- break;
-
- case FTP_R_TYPE:
- mState = R_type();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FAILURE;
-
- break;
- // CWD
- case FTP_S_CWD:
- rv = S_cwd();
-
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_CWD;
-
- MoveToNextState(FTP_R_CWD);
- break;
-
- case FTP_R_CWD:
- mState = R_cwd();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_CWD;
- break;
-
- // LIST
- case FTP_S_LIST:
- rv = S_list();
- if (rv == NS_ERROR_NOT_RESUMABLE) {
- mInternalError = rv;
- } else if (NS_FAILED(rv)) {
- mInternalError = NS_ERROR_FTP_CWD;
- }
-
- MoveToNextState(FTP_R_LIST);
- break;
- case FTP_R_LIST:
- mState = R_list();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FAILURE;
- break;
- // SIZE
- case FTP_S_SIZE:
- rv = S_size();
-
- if (NS_FAILED(rv))
- mInternalError = rv;
-
- MoveToNextState(FTP_R_SIZE);
- break;
-
- case FTP_R_SIZE:
- mState = R_size();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FAILURE;
-
- break;
- // REST
- case FTP_S_REST:
- rv = S_rest();
-
- if (NS_FAILED(rv))
- mInternalError = rv;
-
- MoveToNextState(FTP_R_REST);
- break;
-
- case FTP_R_REST:
- mState = R_rest();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FAILURE;
-
- break;
- // MDTM
- case FTP_S_MDTM:
- rv = S_mdtm();
- if (NS_FAILED(rv))
- mInternalError = rv;
- MoveToNextState(FTP_R_MDTM);
- break;
- case FTP_R_MDTM:
- mState = R_mdtm();
- // Don't want to overwrite a more explicit status code
- if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
- mInternalError = NS_ERROR_FAILURE;
-
- break;
-
- // RETR
- case FTP_S_RETR:
- rv = S_retr();
-
- if (NS_FAILED(rv))
- mInternalError = rv;
-
- MoveToNextState(FTP_R_RETR);
- break;
-
- case FTP_R_RETR:
- mState = R_retr();
-
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FAILURE;
-
- break;
-
- // STOR
- case FTP_S_STOR:
- rv = S_stor();
- if (NS_FAILED(rv))
- mInternalError = rv;
-
- MoveToNextState(FTP_R_STOR);
- break;
-
- case FTP_R_STOR:
- mState = R_stor();
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FAILURE;
- break;
-
- // PASV
- case FTP_S_PASV:
- rv = S_pasv();
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_PASV;
-
- MoveToNextState(FTP_R_PASV);
- break;
-
- case FTP_R_PASV:
- mState = R_pasv();
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_PASV;
- break;
-
- // PWD
- case FTP_S_PWD:
- rv = S_pwd();
- if (NS_FAILED(rv))
- mInternalError = NS_ERROR_FTP_PWD;
-
- MoveToNextState(FTP_R_PWD);
- break;
-
- case FTP_R_PWD:
- mState = R_pwd();
- if (FTP_ERROR == mState)
- mInternalError = NS_ERROR_FTP_PWD;
- break;
- // FEAT for RFC2640 support
- case FTP_S_FEAT:
- rv = S_feat();
- if (NS_FAILED(rv))
- mInternalError = rv;
- MoveToNextState(FTP_R_FEAT);
- break;
- case FTP_R_FEAT:
- mState = R_feat();
- // Don't want to overwrite a more explicit status code
- if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
- mInternalError = NS_ERROR_FAILURE;
- break;
- // OPTS for some non-RFC2640-compliant servers support
- case FTP_S_OPTS:
- rv = S_opts();
- if (NS_FAILED(rv))
- mInternalError = rv;
- MoveToNextState(FTP_R_OPTS);
- break;
- case FTP_R_OPTS:
- mState = R_opts();
- // Don't want to overwrite a more explicit status code
- if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
- mInternalError = NS_ERROR_FAILURE;
- break;
- default:
- ;
-
- }
- }
- return rv;
- }
- ///////////////////////////////////
- // STATE METHODS
- ///////////////////////////////////
- nsresult
- nsFtpState::S_user() {
- // some servers on connect send us a 421 or 521. (84525) (141784)
- if ((mResponseCode == 421) || (mResponseCode == 521))
- return NS_ERROR_FAILURE;
- nsresult rv;
- nsAutoCString usernameStr("USER ");
- mResponseMsg = "";
- if (mAnonymous) {
- mReconnectAndLoginAgain = true;
- usernameStr.AppendLiteral("anonymous");
- } else {
- mReconnectAndLoginAgain = false;
- if (mUsername.IsEmpty()) {
- // No prompt for anonymous requests (bug #473371)
- if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
- return NS_ERROR_FAILURE;
- nsCOMPtr<nsIAuthPrompt2> prompter;
- NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
- getter_AddRefs(prompter));
- if (!prompter)
- return NS_ERROR_NOT_INITIALIZED;
- RefPtr<nsAuthInformationHolder> info =
- new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST,
- EmptyString(),
- EmptyCString());
- bool retval;
- rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
- info, &retval);
- // if the user canceled or didn't supply a username we want to fail
- if (NS_FAILED(rv) || !retval || info->User().IsEmpty())
- return NS_ERROR_FAILURE;
- mUsername = info->User();
- mPassword = info->Password();
- }
- // XXX Is UTF-8 the best choice?
- AppendUTF16toUTF8(mUsername, usernameStr);
- }
- usernameStr.Append(CRLF);
- return SendFTPCommand(usernameStr);
- }
- FTP_STATE
- nsFtpState::R_user() {
- mReconnectAndLoginAgain = false;
- if (mResponseCode/100 == 3) {
- // send off the password
- return FTP_S_PASS;
- }
- if (mResponseCode/100 == 2) {
- // no password required, we're already logged in
- return FTP_S_SYST;
- }
- if (mResponseCode/100 == 5) {
- // problem logging in. typically this means the server
- // has reached it's user limit.
- return FTP_ERROR;
- }
- // LOGIN FAILED
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_pass() {
- nsresult rv;
- nsAutoCString passwordStr("PASS ");
- mResponseMsg = "";
- if (mAnonymous) {
- if (!mPassword.IsEmpty()) {
- // XXX Is UTF-8 the best choice?
- AppendUTF16toUTF8(mPassword, passwordStr);
- } else {
- nsXPIDLCString anonPassword;
- bool useRealEmail = false;
- nsCOMPtr<nsIPrefBranch> prefs =
- do_GetService(NS_PREFSERVICE_CONTRACTID);
- if (prefs) {
- rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail);
- if (NS_SUCCEEDED(rv) && useRealEmail) {
- prefs->GetCharPref("network.ftp.anonymous_password",
- getter_Copies(anonPassword));
- }
- }
- if (!anonPassword.IsEmpty()) {
- passwordStr.AppendASCII(anonPassword);
- } else {
- // We need to default to a valid email address - bug 101027
- // example.com is reserved (rfc2606), so use that
- passwordStr.AppendLiteral("mozilla@example.com");
- }
- }
- } else {
- if (mPassword.IsEmpty() || mRetryPass) {
-
- // No prompt for anonymous requests (bug #473371)
- if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
- return NS_ERROR_FAILURE;
- nsCOMPtr<nsIAuthPrompt2> prompter;
- NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
- getter_AddRefs(prompter));
- if (!prompter)
- return NS_ERROR_NOT_INITIALIZED;
- RefPtr<nsAuthInformationHolder> info =
- new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST |
- nsIAuthInformation::ONLY_PASSWORD,
- EmptyString(),
- EmptyCString());
- info->SetUserInternal(mUsername);
- bool retval;
- rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
- info, &retval);
- // we want to fail if the user canceled. Note here that if they want
- // a blank password, we will pass it along.
- if (NS_FAILED(rv) || !retval)
- return NS_ERROR_FAILURE;
- mPassword = info->Password();
- }
- // XXX Is UTF-8 the best choice?
- AppendUTF16toUTF8(mPassword, passwordStr);
- }
- passwordStr.Append(CRLF);
- return SendFTPCommand(passwordStr);
- }
- FTP_STATE
- nsFtpState::R_pass() {
- if (mResponseCode/100 == 3) {
- // send account info
- return FTP_S_ACCT;
- }
- if (mResponseCode/100 == 2) {
- // logged in
- return FTP_S_SYST;
- }
- if (mResponseCode == 503) {
- // start over w/ the user command.
- // note: the password was successful, and it's stored in mPassword
- mRetryPass = false;
- return FTP_S_USER;
- }
- if (mResponseCode/100 == 5 || mResponseCode==421) {
- // There is no difference between a too-many-users error,
- // a wrong-password error, or any other sort of error
- if (!mAnonymous)
- mRetryPass = true;
- return FTP_ERROR;
- }
- // unexpected response code
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_pwd() {
- return SendFTPCommand(NS_LITERAL_CSTRING("PWD" CRLF));
- }
- FTP_STATE
- nsFtpState::R_pwd() {
- // Error response to PWD command isn't fatal, but don't cache the connection
- // if CWD command is sent since correct mPwd is needed for further requests.
- if (mResponseCode/100 != 2)
- return FTP_S_TYPE;
- nsAutoCString respStr(mResponseMsg);
- int32_t pos = respStr.FindChar('"');
- if (pos > -1) {
- respStr.Cut(0, pos+1);
- pos = respStr.FindChar('"');
- if (pos > -1) {
- respStr.Truncate(pos);
- if (mServerType == FTP_VMS_TYPE)
- ConvertDirspecFromVMS(respStr);
- if (respStr.IsEmpty() || respStr.Last() != '/')
- respStr.Append('/');
- mPwd = respStr;
- }
- }
- return FTP_S_TYPE;
- }
- nsresult
- nsFtpState::S_syst() {
- return SendFTPCommand(NS_LITERAL_CSTRING("SYST" CRLF));
- }
- FTP_STATE
- nsFtpState::R_syst() {
- if (mResponseCode/100 == 2) {
- if (( mResponseMsg.Find("L8") > -1) ||
- ( mResponseMsg.Find("UNIX") > -1) ||
- ( mResponseMsg.Find("BSD") > -1) ||
- ( mResponseMsg.Find("MACOS Peter's Server") > -1) ||
- ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) ||
- ( mResponseMsg.Find("MVS") > -1) ||
- ( mResponseMsg.Find("OS/390") > -1) ||
- ( mResponseMsg.Find("OS/400") > -1)) {
- mServerType = FTP_UNIX_TYPE;
- } else if (( mResponseMsg.Find("WIN32", true) > -1) ||
- ( mResponseMsg.Find("windows", true) > -1)) {
- mServerType = FTP_NT_TYPE;
- } else if (mResponseMsg.Find("OS/2", true) > -1) {
- mServerType = FTP_OS2_TYPE;
- } else if (mResponseMsg.Find("VMS", true) > -1) {
- mServerType = FTP_VMS_TYPE;
- } else {
- NS_ERROR("Server type list format unrecognized.");
- // clear mResponseMsg, which is displayed to the user.
- mResponseMsg = "";
- return FTP_ERROR;
- }
-
- return FTP_S_FEAT;
- }
- if (mResponseCode/100 == 5) {
- // server didn't like the SYST command. Probably (500, 501, 502)
- // No clue. We will just hope it is UNIX type server.
- mServerType = FTP_UNIX_TYPE;
- return FTP_S_FEAT;
- }
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_acct() {
- return SendFTPCommand(NS_LITERAL_CSTRING("ACCT noaccount" CRLF));
- }
- FTP_STATE
- nsFtpState::R_acct() {
- if (mResponseCode/100 == 2)
- return FTP_S_SYST;
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_type() {
- return SendFTPCommand(NS_LITERAL_CSTRING("TYPE I" CRLF));
- }
- FTP_STATE
- nsFtpState::R_type() {
- if (mResponseCode/100 != 2)
- return FTP_ERROR;
-
- return FTP_S_PASV;
- }
- nsresult
- nsFtpState::S_cwd() {
- // Don't cache the connection if PWD command failed
- if (mPwd.IsEmpty())
- mCacheConnection = false;
- nsAutoCString cwdStr;
- if (mAction != PUT)
- cwdStr = mPath;
- if (cwdStr.IsEmpty() || cwdStr.First() != '/')
- cwdStr.Insert(mPwd,0);
- if (mServerType == FTP_VMS_TYPE)
- ConvertDirspecToVMS(cwdStr);
- cwdStr.Insert("CWD ",0);
- cwdStr.Append(CRLF);
- return SendFTPCommand(cwdStr);
- }
- FTP_STATE
- nsFtpState::R_cwd() {
- if (mResponseCode/100 == 2) {
- if (mAction == PUT)
- return FTP_S_STOR;
-
- return FTP_S_LIST;
- }
-
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_size() {
- nsAutoCString sizeBuf(mPath);
- if (sizeBuf.IsEmpty() || sizeBuf.First() != '/')
- sizeBuf.Insert(mPwd,0);
- if (mServerType == FTP_VMS_TYPE)
- ConvertFilespecToVMS(sizeBuf);
- sizeBuf.Insert("SIZE ",0);
- sizeBuf.Append(CRLF);
- return SendFTPCommand(sizeBuf);
- }
- FTP_STATE
- nsFtpState::R_size() {
- if (mResponseCode/100 == 2) {
- PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize);
- mChannel->SetContentLength(mFileSize);
- }
- // We may want to be able to resume this
- return FTP_S_MDTM;
- }
- nsresult
- nsFtpState::S_mdtm() {
- nsAutoCString mdtmBuf(mPath);
- if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/')
- mdtmBuf.Insert(mPwd,0);
- if (mServerType == FTP_VMS_TYPE)
- ConvertFilespecToVMS(mdtmBuf);
- mdtmBuf.Insert("MDTM ",0);
- mdtmBuf.Append(CRLF);
- return SendFTPCommand(mdtmBuf);
- }
- FTP_STATE
- nsFtpState::R_mdtm() {
- if (mResponseCode == 213) {
- mResponseMsg.Cut(0,4);
- mResponseMsg.Trim(" \t\r\n");
- // yyyymmddhhmmss
- if (mResponseMsg.Length() != 14) {
- NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
- } else {
- mModTime = mResponseMsg;
- // Save lastModified time for downloaded files.
- nsAutoCString timeString;
- nsresult error;
- PRExplodedTime exTime;
- mResponseMsg.Mid(timeString, 0, 4);
- exTime.tm_year = timeString.ToInteger(&error);
- mResponseMsg.Mid(timeString, 4, 2);
- exTime.tm_month = timeString.ToInteger(&error) - 1; //january = 0
- mResponseMsg.Mid(timeString, 6, 2);
- exTime.tm_mday = timeString.ToInteger(&error);
- mResponseMsg.Mid(timeString, 8, 2);
- exTime.tm_hour = timeString.ToInteger(&error);
- mResponseMsg.Mid(timeString, 10, 2);
- exTime.tm_min = timeString.ToInteger(&error);
- mResponseMsg.Mid(timeString, 12, 2);
- exTime.tm_sec = timeString.ToInteger(&error);
- exTime.tm_usec = 0;
- exTime.tm_params.tp_gmt_offset = 0;
- exTime.tm_params.tp_dst_offset = 0;
- PR_NormalizeTime(&exTime, PR_GMTParameters);
- exTime.tm_params = PR_LocalTimeParameters(&exTime);
- PRTime time = PR_ImplodeTime(&exTime);
- (void)mChannel->SetLastModifiedTime(time);
- }
- }
- nsCString entityID;
- entityID.Truncate();
- entityID.AppendInt(int64_t(mFileSize));
- entityID.Append('/');
- entityID.Append(mModTime);
- mChannel->SetEntityID(entityID);
- // We weren't asked to resume
- if (!mChannel->ResumeRequested())
- return FTP_S_RETR;
- //if (our entityID == supplied one (if any))
- if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID))
- return FTP_S_REST;
- mInternalError = NS_ERROR_ENTITY_CHANGED;
- mResponseMsg.Truncate();
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::SetContentType()
- {
- // FTP directory URLs don't always end in a slash. Make sure they do.
- // This check needs to be here rather than a more obvious place
- // (e.g. LIST command processing) so that it ensures the terminating
- // slash is appended for the new request case.
- if (!mPath.IsEmpty() && mPath.Last() != '/') {
- nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI()));
- nsAutoCString filePath;
- if(NS_SUCCEEDED(url->GetFilePath(filePath))) {
- filePath.Append('/');
- url->SetFilePath(filePath);
- }
- }
- return mChannel->SetContentType(
- NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
- }
- nsresult
- nsFtpState::S_list() {
- nsresult rv = SetContentType();
- if (NS_FAILED(rv))
- // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
- // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
- return (nsresult)FTP_ERROR;
- rv = mChannel->PushStreamConverter("text/ftp-dir",
- APPLICATION_HTTP_INDEX_FORMAT);
- if (NS_FAILED(rv)) {
- // clear mResponseMsg which is displayed to the user.
- // TODO: we should probably set this to something meaningful.
- mResponseMsg = "";
- return rv;
- }
- // dir listings aren't resumable
- NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE);
- mChannel->SetEntityID(EmptyCString());
- const char *listString;
- if (mServerType == FTP_VMS_TYPE) {
- listString = "LIST *.*;0" CRLF;
- } else {
- listString = "LIST" CRLF;
- }
- return SendFTPCommand(nsDependentCString(listString));
- }
- FTP_STATE
- nsFtpState::R_list() {
- if (mResponseCode/100 == 1) {
- mRlist1xxReceived = true;
- // OK, time to start reading from the data connection.
- if (mDataStream && HasPendingCallback())
- mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
- return FTP_READ_BUF;
- }
- if (mResponseCode/100 == 2 && mRlist1xxReceived) {
- //(DONE)
- mNextState = FTP_COMPLETE;
- mRlist1xxReceived = false;
- return FTP_COMPLETE;
- }
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_retr() {
- nsAutoCString retrStr(mPath);
- if (retrStr.IsEmpty() || retrStr.First() != '/')
- retrStr.Insert(mPwd,0);
- if (mServerType == FTP_VMS_TYPE)
- ConvertFilespecToVMS(retrStr);
- retrStr.Insert("RETR ",0);
- retrStr.Append(CRLF);
- return SendFTPCommand(retrStr);
- }
- FTP_STATE
- nsFtpState::R_retr() {
- if (mResponseCode/100 == 2 && mRretr1xxReceived) {
- //(DONE)
- mNextState = FTP_COMPLETE;
- mRretr1xxReceived = false;
- return FTP_COMPLETE;
- }
- if (mResponseCode/100 == 1) {
- mRretr1xxReceived = true;
- if (mDataStream && HasPendingCallback())
- mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
- return FTP_READ_BUF;
- }
-
- // These error codes are related to problems with the connection.
- // If we encounter any at this point, do not try CWD and abort.
- if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
- return FTP_ERROR;
- if (mResponseCode/100 == 5) {
- mRETRFailed = true;
- return FTP_S_PASV;
- }
- return FTP_S_CWD;
- }
- nsresult
- nsFtpState::S_rest() {
-
- nsAutoCString restString("REST ");
- // The int64_t cast is needed to avoid ambiguity
- restString.AppendInt(int64_t(mChannel->StartPos()), 10);
- restString.Append(CRLF);
- return SendFTPCommand(restString);
- }
- FTP_STATE
- nsFtpState::R_rest() {
- if (mResponseCode/100 == 4) {
- // If REST fails, then we can't resume
- mChannel->SetEntityID(EmptyCString());
- mInternalError = NS_ERROR_NOT_RESUMABLE;
- mResponseMsg.Truncate();
- return FTP_ERROR;
- }
-
- return FTP_S_RETR;
- }
- nsresult
- nsFtpState::S_stor() {
- NS_ENSURE_STATE(mChannel->UploadStream());
- NS_ASSERTION(mAction == PUT, "Wrong state to be here");
-
- nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
- NS_ASSERTION(url, "I thought you were a nsStandardURL");
- nsAutoCString storStr;
- url->GetFilePath(storStr);
- NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path");
-
- // kill the first slash since we want to be relative to CWD.
- if (storStr.First() == '/')
- storStr.Cut(0,1);
- if (mServerType == FTP_VMS_TYPE)
- ConvertFilespecToVMS(storStr);
- NS_UnescapeURL(storStr);
- storStr.Insert("STOR ",0);
- storStr.Append(CRLF);
- return SendFTPCommand(storStr);
- }
- FTP_STATE
- nsFtpState::R_stor() {
- if (mResponseCode/100 == 2 && mRstor1xxReceived) {
- //(DONE)
- mNextState = FTP_COMPLETE;
- mStorReplyReceived = true;
- // Call Close() if it was not called in nsFtpState::OnStoprequest()
- if (!mUploadRequest && !IsClosed())
- Close();
- mRstor1xxReceived = false;
- return FTP_COMPLETE;
- }
- if (mResponseCode/100 == 1) {
- mRstor1xxReceived = true;
- LOG(("FTP:(%x) writing on DT\n", this));
- return FTP_READ_BUF;
- }
- mStorReplyReceived = true;
- return FTP_ERROR;
- }
- nsresult
- nsFtpState::S_pasv() {
- if (!mAddressChecked) {
- // Find socket address
- mAddressChecked = true;
- mServerAddress.raw.family = AF_INET;
- mServerAddress.inet.ip = htonl(INADDR_ANY);
- mServerAddress.inet.port = htons(0);
- nsITransport *controlSocket = mControlConnection->Transport();
- if (!controlSocket)
- // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
- // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
- return (nsresult)FTP_ERROR;
- nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket);
- if (sTrans) {
- nsresult rv = sTrans->GetPeerAddr(&mServerAddress);
- if (NS_SUCCEEDED(rv)) {
- if (!IsIPAddrAny(&mServerAddress))
- mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) &&
- !IsIPAddrV4Mapped(&mServerAddress);
- else {
- /*
- * In case of SOCKS5 remote DNS resolution, we do
- * not know the remote IP address. Still, if it is
- * an IPV6 host, then the external address of the
- * socks server should also be IPv6, and this is the
- * self address of the transport.
- */
- NetAddr selfAddress;
- rv = sTrans->GetSelfAddr(&selfAddress);
- if (NS_SUCCEEDED(rv))
- mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) &&
- !IsIPAddrV4Mapped(&selfAddress);
- }
- }
- }
- }
- const char *string;
- if (mServerIsIPv6) {
- string = "EPSV" CRLF;
- } else {
- string = "PASV" CRLF;
- }
- return SendFTPCommand(nsDependentCString(string));
-
- }
- FTP_STATE
- nsFtpState::R_pasv() {
- if (mResponseCode/100 != 2)
- return FTP_ERROR;
- nsresult rv;
- int32_t port;
- nsAutoCString responseCopy(mResponseMsg);
- char *response = responseCopy.BeginWriting();
- char *ptr = response;
- // Make sure to ignore the address in the PASV response (bug 370559)
- if (mServerIsIPv6) {
- // The returned string is of the form
- // text (|||ppp|)
- // Where '|' can be any single character
- char delim;
- while (*ptr && *ptr != '(')
- ptr++;
- if (*ptr++ != '(')
- return FTP_ERROR;
- delim = *ptr++;
- if (!delim || *ptr++ != delim ||
- *ptr++ != delim ||
- *ptr < '0' || *ptr > '9')
- return FTP_ERROR;
- port = 0;
- do {
- port = port * 10 + *ptr++ - '0';
- } while (*ptr >= '0' && *ptr <= '9');
- if (*ptr++ != delim || *ptr != ')')
- return FTP_ERROR;
- } else {
- // The returned address string can be of the form
- // (xxx,xxx,xxx,xxx,ppp,ppp) or
- // xxx,xxx,xxx,xxx,ppp,ppp (without parens)
- int32_t h0, h1, h2, h3, p0, p1;
- int32_t fields = 0;
- // First try with parens
- while (*ptr && *ptr != '(')
- ++ptr;
- if (*ptr) {
- ++ptr;
- fields = PR_sscanf(ptr,
- "%ld,%ld,%ld,%ld,%ld,%ld",
- &h0, &h1, &h2, &h3, &p0, &p1);
- }
- if (!*ptr || fields < 6) {
- // OK, lets try w/o parens
- ptr = response;
- while (*ptr && *ptr != ',')
- ++ptr;
- if (*ptr) {
- // backup to the start of the digits
- do {
- ptr--;
- } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9'));
- ptr++; // get back onto the numbers
- fields = PR_sscanf(ptr,
- "%ld,%ld,%ld,%ld,%ld,%ld",
- &h0, &h1, &h2, &h3, &p0, &p1);
- }
- }
- NS_ASSERTION(fields == 6, "Can't parse PASV response");
- if (fields < 6)
- return FTP_ERROR;
- port = ((int32_t) (p0<<8)) + p1;
- }
- bool newDataConn = true;
- if (mDataTransport) {
- // Reuse this connection only if its still alive, and the port
- // is the same
- nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport);
- if (strans) {
- int32_t oldPort;
- nsresult rv = strans->GetPort(&oldPort);
- if (NS_SUCCEEDED(rv)) {
- if (oldPort == port) {
- bool isAlive;
- if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive)
- newDataConn = false;
- }
- }
- }
- if (newDataConn) {
- mDataTransport->Close(NS_ERROR_ABORT);
- mDataTransport = nullptr;
- mDataStream = nullptr;
- }
- }
- if (newDataConn) {
- // now we know where to connect our data channel
- nsCOMPtr<nsISocketTransportService> sts =
- do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
- if (!sts)
- return FTP_ERROR;
-
- nsCOMPtr<nsISocketTransport> strans;
- nsAutoCString host;
- if (!IsIPAddrAny(&mServerAddress)) {
- char buf[kIPv6CStrBufSize];
- NetAddrToString(&mServerAddress, buf, sizeof(buf));
- host.Assign(buf);
- } else {
- /*
- * In case of SOCKS5 remote DNS resolving, the peer address
- * fetched previously will be invalid (0.0.0.0): it is unknown
- * to us. But we can pass on the original hostname to the
- * connect for the data connection.
- */
- rv = mChannel->URI()->GetAsciiHost(host);
- if (NS_FAILED(rv))
- return FTP_ERROR;
- }
- rv = sts->CreateTransport(nullptr, 0, host,
- port, mChannel->ProxyInfo(),
- getter_AddRefs(strans)); // the data socket
- if (NS_FAILED(rv))
- return FTP_ERROR;
- mDataTransport = strans;
- strans->SetQoSBits(gFtpHandler->GetDataQoSBits());
-
- LOG(("FTP:(%x) created DT (%s:%x)\n", this, host.get(), port));
-
- // hook ourself up as a proxy for status notifications
- rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread());
- NS_ENSURE_SUCCESS(rv, FTP_ERROR);
- if (mAction == PUT) {
- NS_ASSERTION(!mRETRFailed, "Failed before uploading");
- // nsIUploadChannel requires the upload stream to support ReadSegments.
- // therefore, we can open an unbuffered socket output stream.
- nsCOMPtr<nsIOutputStream> output;
- rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
- 0, 0, getter_AddRefs(output));
- if (NS_FAILED(rv))
- return FTP_ERROR;
- // perform the data copy on the socket transport thread. we do this
- // because "output" is a socket output stream, so the result is that
- // all work will be done on the socket transport thread.
- nsCOMPtr<nsIEventTarget> stEventTarget =
- do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
- if (!stEventTarget)
- return FTP_ERROR;
-
- nsCOMPtr<nsIAsyncStreamCopier> copier;
- rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier),
- mChannel->UploadStream(),
- output,
- stEventTarget,
- true, // upload stream is buffered
- false); // output is NOT buffered
- if (NS_FAILED(rv))
- return FTP_ERROR;
-
- rv = copier->AsyncCopy(this, nullptr);
- if (NS_FAILED(rv))
- return FTP_ERROR;
- // hold a reference to the copier so we can cancel it if necessary.
- mUploadRequest = copier;
- // update the current working directory before sending the STOR
- // command. this is needed since we might be reusing a control
- // connection.
- return FTP_S_CWD;
- }
- //
- // else, we are reading from the data connection...
- //
- // open a buffered, asynchronous socket input stream
- nsCOMPtr<nsIInputStream> input;
- rv = mDataTransport->OpenInputStream(0,
- nsIOService::gDefaultSegmentSize,
- nsIOService::gDefaultSegmentCount,
- getter_AddRefs(input));
- NS_ENSURE_SUCCESS(rv, FTP_ERROR);
- mDataStream = do_QueryInterface(input);
- }
- if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/')
- return FTP_S_CWD;
- return FTP_S_SIZE;
- }
- nsresult
- nsFtpState::S_feat() {
- return SendFTPCommand(NS_LITERAL_CSTRING("FEAT" CRLF));
- }
- FTP_STATE
- nsFtpState::R_feat() {
- if (mResponseCode/100 == 2) {
- if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) {
- // This FTP server supports UTF-8 encoding
- mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
- mUseUTF8 = true;
- return FTP_S_OPTS;
- }
- }
- mUseUTF8 = false;
- return FTP_S_PWD;
- }
- nsresult
- nsFtpState::S_opts() {
- // This command is for compatibility of old FTP spec (IETF Draft)
- return SendFTPCommand(NS_LITERAL_CSTRING("OPTS UTF8 ON" CRLF));
- }
- FTP_STATE
- nsFtpState::R_opts() {
- // Ignore error code because "OPTS UTF8 ON" is for compatibility of
- // FTP server using IETF draft
- return FTP_S_PWD;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // nsIRequest methods:
- nsresult
- nsFtpState::Init(nsFtpChannel *channel)
- {
- // parameter validation
- NS_ASSERTION(channel, "FTP: needs a channel");
- mChannel = channel; // a straight ref ptr to the channel
- // initialize counter for network metering
- mCountRecv = 0;
- mKeepRunning = true;
- mSuppliedEntityID = channel->EntityID();
- if (channel->UploadStream())
- mAction = PUT;
- nsresult rv;
- nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
- nsAutoCString host;
- if (url) {
- rv = url->GetAsciiHost(host);
- } else {
- rv = mChannel->URI()->GetAsciiHost(host);
- }
- if (NS_FAILED(rv) || host.IsEmpty()) {
- return NS_ERROR_MALFORMED_URI;
- }
- nsAutoCString path;
- if (url) {
- rv = url->GetFilePath(path);
- } else {
- rv = mChannel->URI()->GetPath(path);
- }
- if (NS_FAILED(rv))
- return rv;
- removeParamsFromPath(path);
-
- // FTP parameters such as type=i are ignored
- if (url) {
- url->SetFilePath(path);
- } else {
- mChannel->URI()->SetPath(path);
- }
-
- // Skip leading slash
- char *fwdPtr = path.BeginWriting();
- if (!fwdPtr)
- return NS_ERROR_OUT_OF_MEMORY;
- if (*fwdPtr == '/')
- fwdPtr++;
- if (*fwdPtr != '\0') {
- // now unescape it... %xx reduced inline to resulting character
- int32_t len = NS_UnescapeURL(fwdPtr);
- mPath.Assign(fwdPtr, len);
- #ifdef DEBUG
- if (mPath.FindCharInSet(CRLF) >= 0)
- NS_ERROR("NewURI() should've prevented this!!!");
- #endif
- }
- // pull any username and/or password out of the uri
- nsAutoCString uname;
- rv = mChannel->URI()->GetUsername(uname);
- if (NS_FAILED(rv))
- return rv;
- if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) {
- mAnonymous = false;
- CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername);
-
- // return an error if we find a CR or LF in the username
- if (uname.FindCharInSet(CRLF) >= 0)
- return NS_ERROR_MALFORMED_URI;
- }
- nsAutoCString password;
- rv = mChannel->URI()->GetPassword(password);
- if (NS_FAILED(rv))
- return rv;
- CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword);
- // return an error if we find a CR or LF in the password
- if (mPassword.FindCharInSet(CRLF) >= 0)
- return NS_ERROR_MALFORMED_URI;
- int32_t port;
- rv = mChannel->URI()->GetPort(&port);
- if (NS_FAILED(rv))
- return rv;
- if (port > 0)
- mPort = port;
- // Lookup Proxy information asynchronously if it isn't already set
- // on the channel and if we aren't configured explicitly to go directly
- nsCOMPtr<nsIProtocolProxyService> pps =
- do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
- if (pps && !mChannel->ProxyInfo()) {
- pps->AsyncResolve(static_cast<nsIChannel*>(mChannel), 0, this,
- getter_AddRefs(mProxyRequest));
- }
- return NS_OK;
- }
- void
- nsFtpState::Connect()
- {
- mState = FTP_COMMAND_CONNECT;
- mNextState = FTP_S_USER;
- nsresult rv = Process();
- // check for errors.
- if (NS_FAILED(rv)) {
- LOG(("FTP:Process() failed: %x\n", rv));
- mInternalError = NS_ERROR_FAILURE;
- mState = FTP_ERROR;
- CloseWithStatus(mInternalError);
- }
- }
- void
- nsFtpState::KillControlConnection()
- {
- mControlReadCarryOverBuf.Truncate(0);
- mAddressChecked = false;
- mServerIsIPv6 = false;
- // if everything went okay, save the connection.
- // FIX: need a better way to determine if we can cache the connections.
- // there are some errors which do not mean that we need to kill the connection
- // e.g. fnf.
- if (!mControlConnection)
- return;
- // kill the reference to ourselves in the control connection.
- mControlConnection->WaitData(nullptr);
- if (NS_SUCCEEDED(mInternalError) &&
- NS_SUCCEEDED(mControlStatus) &&
- mControlConnection->IsAlive() &&
- mCacheConnection) {
- LOG_INFO(("FTP:(%p) caching CC(%p)", this, mControlConnection.get()));
- // Store connection persistent data
- mControlConnection->mServerType = mServerType;
- mControlConnection->mPassword = mPassword;
- mControlConnection->mPwd = mPwd;
- mControlConnection->mUseUTF8 = mUseUTF8;
-
- nsresult rv = NS_OK;
- // Don't cache controlconnection if anonymous (bug #473371)
- if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
- rv = gFtpHandler->InsertConnection(mChannel->URI(),
- mControlConnection);
- // Can't cache it? Kill it then.
- mControlConnection->Disconnect(rv);
- } else {
- mControlConnection->Disconnect(NS_BINDING_ABORTED);
- }
- mControlConnection = nullptr;
- }
- nsresult
- nsFtpState::StopProcessing()
- {
- // Only do this function once.
- if (!mKeepRunning)
- return NS_OK;
- mKeepRunning = false;
- LOG_INFO(("FTP:(%x) nsFtpState stopping", this));
- if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) {
- NS_ERROR("FTP: bad control status.");
- // check to see if the control status is bad; forward the error message.
- nsCOMPtr<nsIFTPChannelParentInternal> ftpChanP;
- mChannel->GetCallback(ftpChanP);
- if (ftpChanP) {
- ftpChanP->SetErrorMsg(mResponseMsg.get(), mUseUTF8);
- }
- }
- nsresult broadcastErrorCode = mControlStatus;
- if (NS_SUCCEEDED(broadcastErrorCode))
- broadcastErrorCode = mInternalError;
- mInternalError = broadcastErrorCode;
- KillControlConnection();
- // XXX This can fire before we are done loading data. Is that a problem?
- OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0);
- if (NS_FAILED(broadcastErrorCode))
- CloseWithStatus(broadcastErrorCode);
- return NS_OK;
- }
- nsresult
- nsFtpState::SendFTPCommand(const nsCSubstring& command)
- {
- NS_ASSERTION(mControlConnection, "null control connection");
-
- // we don't want to log the password:
- nsAutoCString logcmd(command);
- if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS ")))
- logcmd = "PASS xxxxx";
-
- LOG(("FTP:(%x) writing \"%s\"\n", this, logcmd.get()));
- nsCOMPtr<nsIFTPEventSink> ftpSink;
- mChannel->GetFTPEventSink(ftpSink);
- if (ftpSink)
- ftpSink->OnFTPControlLog(false, logcmd.get());
-
- if (mControlConnection)
- return mControlConnection->Write(command);
- return NS_ERROR_FAILURE;
- }
- // Convert a unix-style filespec to VMS format
- // /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt
- // /foo/file.txt -> foo:[000000]file.txt
- void
- nsFtpState::ConvertFilespecToVMS(nsCString& fileString)
- {
- int ntok=1;
- char *t, *nextToken;
- nsAutoCString fileStringCopy;
- // Get a writeable copy we can strtok with.
- fileStringCopy = fileString;
- t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken);
- if (t)
- while (nsCRT::strtok(nextToken, "/", &nextToken))
- ntok++; // count number of terms (tokens)
- LOG(("FTP:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok));
- LOG(("FTP:(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get()));
- if (fileString.First() == '/') {
- // absolute filespec
- // / -> []
- // /a -> a (doesn't really make much sense)
- // /a/b -> a:[000000]b
- // /a/b/c -> a:[b]c
- // /a/b/c/d -> a:[b.c]d
- if (ntok == 1) {
- if (fileString.Length() == 1) {
- // Just a slash
- fileString.Truncate();
- fileString.AppendLiteral("[]");
- } else {
- // just copy the name part (drop the leading slash)
- fileStringCopy = fileString;
- fileString = Substring(fileStringCopy, 1,
- fileStringCopy.Length()-1);
- }
- } else {
- // Get another copy since the last one was written to.
- fileStringCopy = fileString;
- fileString.Truncate();
- fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
- "/", &nextToken));
- fileString.AppendLiteral(":[");
- if (ntok > 2) {
- for (int i=2; i<ntok; i++) {
- if (i > 2) fileString.Append('.');
- fileString.Append(nsCRT::strtok(nextToken,
- "/", &nextToken));
- }
- } else {
- fileString.AppendLiteral("000000");
- }
- fileString.Append(']');
- fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
- }
- } else {
- // relative filespec
- // a -> a
- // a/b -> [.a]b
- // a/b/c -> [.a.b]c
- if (ntok == 1) {
- // no slashes, just use the name as is
- } else {
- // Get another copy since the last one was written to.
- fileStringCopy = fileString;
- fileString.Truncate();
- fileString.AppendLiteral("[.");
- fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
- "/", &nextToken));
- if (ntok > 2) {
- for (int i=2; i<ntok; i++) {
- fileString.Append('.');
- fileString.Append(nsCRT::strtok(nextToken,
- "/", &nextToken));
- }
- }
- fileString.Append(']');
- fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
- }
- }
- LOG(("FTP:(%x) ConvertFilespecToVMS to: \"%s\"\n", this, fileString.get()));
- }
- // Convert a unix-style dirspec to VMS format
- // /foo/fred/barney/rubble -> foo:[fred.barney.rubble]
- // /foo/fred -> foo:[fred]
- // /foo -> foo:[000000]
- // (null) -> (null)
- void
- nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec)
- {
- LOG(("FTP:(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get()));
- if (!dirSpec.IsEmpty()) {
- if (dirSpec.Last() != '/')
- dirSpec.Append('/');
- // we can use the filespec routine if we make it look like a file name
- dirSpec.Append('x');
- ConvertFilespecToVMS(dirSpec);
- dirSpec.Truncate(dirSpec.Length()-1);
- }
- LOG(("FTP:(%x) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get()));
- }
- // Convert an absolute VMS style dirspec to UNIX format
- void
- nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec)
- {
- LOG(("FTP:(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get()));
- if (dirSpec.IsEmpty()) {
- dirSpec.Insert('.', 0);
- } else {
- dirSpec.Insert('/', 0);
- dirSpec.ReplaceSubstring(":[", "/");
- dirSpec.ReplaceChar('.', '/');
- dirSpec.ReplaceChar(']', '/');
- }
- LOG(("FTP:(%x) ConvertDirspecFromVMS to: \"%s\"\n", this, dirSpec.get()));
- }
- //-----------------------------------------------------------------------------
- NS_IMETHODIMP
- nsFtpState::OnTransportStatus(nsITransport *transport, nsresult status,
- int64_t progress, int64_t progressMax)
- {
- // Mix signals from both the control and data connections.
- // Ignore data transfer events on the control connection.
- if (mControlConnection && transport == mControlConnection->Transport()) {
- switch (status) {
- case NS_NET_STATUS_RESOLVING_HOST:
- case NS_NET_STATUS_RESOLVED_HOST:
- case NS_NET_STATUS_CONNECTING_TO:
- case NS_NET_STATUS_CONNECTED_TO:
- case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
- case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
- break;
- default:
- return NS_OK;
- }
- }
- // Ignore the progressMax value from the socket. We know the true size of
- // the file based on the response from our SIZE request. Additionally, only
- // report the max progress based on where we started/resumed.
- mChannel->OnTransportStatus(nullptr, status, progress,
- mFileSize - mChannel->StartPos());
- return NS_OK;
- }
- //-----------------------------------------------------------------------------
- NS_IMETHODIMP
- nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context)
- {
- mStorReplyReceived = false;
- return NS_OK;
- }
- NS_IMETHODIMP
- nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context,
- nsresult status)
- {
- mUploadRequest = nullptr;
- // Close() will be called when reply to STOR command is received
- // see bug #389394
- if (!mStorReplyReceived)
- return NS_OK;
- // We're done uploading. Let our consumer know that we're done.
- Close();
- return NS_OK;
- }
- //-----------------------------------------------------------------------------
- NS_IMETHODIMP
- nsFtpState::Available(uint64_t *result)
- {
- if (mDataStream)
- return mDataStream->Available(result);
- return nsBaseContentStream::Available(result);
- }
- NS_IMETHODIMP
- nsFtpState::ReadSegments(nsWriteSegmentFun writer, void *closure,
- uint32_t count, uint32_t *result)
- {
- // Insert a thunk here so that the input stream passed to the writer is this
- // input stream instead of mDataStream.
- if (mDataStream) {
- nsWriteSegmentThunk thunk = { this, writer, closure };
- nsresult rv;
- rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count,
- result);
- if (NS_SUCCEEDED(rv)) {
- CountRecvBytes(*result);
- }
- return rv;
- }
- return nsBaseContentStream::ReadSegments(writer, closure, count, result);
- }
- nsresult
- nsFtpState::SaveNetworkStats(bool enforce)
- {
- return NS_ERROR_NOT_IMPLEMENTED;
- }
- NS_IMETHODIMP
- nsFtpState::CloseWithStatus(nsresult status)
- {
- LOG(("FTP:(%p) close [%x]\n", this, status));
- // Shutdown the control connection processing if we are being closed with an
- // error. Note: This method may be called several times.
- if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) {
- if (NS_SUCCEEDED(mInternalError))
- mInternalError = status;
- StopProcessing();
- }
- if (mUploadRequest) {
- mUploadRequest->Cancel(NS_ERROR_ABORT);
- mUploadRequest = nullptr;
- }
- if (mDataTransport) {
- // Save the network stats before data transport is closing.
- SaveNetworkStats(true);
- // Shutdown the data transport.
- mDataTransport->Close(NS_ERROR_ABORT);
- mDataTransport = nullptr;
- }
- mDataStream = nullptr;
- return nsBaseContentStream::CloseWithStatus(status);
- }
- static nsresult
- CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsIChannel **newChannel)
- {
- nsresult rv;
- nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
- if (NS_FAILED(rv))
- return rv;
- nsCOMPtr<nsIProtocolHandler> handler;
- rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
- if (NS_FAILED(rv))
- return rv;
- nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
- if (NS_FAILED(rv))
- return rv;
- nsCOMPtr<nsIURI> uri;
- channel->GetURI(getter_AddRefs(uri));
- nsCOMPtr<nsILoadInfo> loadInfo;
- channel->GetLoadInfo(getter_AddRefs(loadInfo));
- return pph->NewProxiedChannel2(uri, pi, 0, nullptr, loadInfo, newChannel);
- }
- NS_IMETHODIMP
- nsFtpState::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
- nsIProxyInfo *pi, nsresult status)
- {
- mProxyRequest = nullptr;
- // failed status code just implies DIRECT processing
- if (NS_SUCCEEDED(status)) {
- nsAutoCString type;
- if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) {
- // Proxy the FTP url via HTTP
- // This would have been easier to just return a HTTP channel directly
- // from nsIIOService::NewChannelFromURI(), but the proxy type cannot
- // be reliabliy determined synchronously without jank due to pac, etc..
- LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this));
- nsCOMPtr<nsIChannel> newChannel;
- if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi,
- getter_AddRefs(newChannel))) &&
- NS_SUCCEEDED(mChannel->Redirect(newChannel,
- nsIChannelEventSink::REDIRECT_INTERNAL,
- true))) {
- LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this));
- return NS_OK;
- }
- }
- else if (pi) {
- // Proxy using the FTP protocol routed through a socks proxy
- LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this));
- mChannel->SetProxyInfo(pi);
- }
- }
- if (mDeferredCallbackPending) {
- mDeferredCallbackPending = false;
- OnCallbackPending();
- }
- return NS_OK;
- }
- void
- nsFtpState::OnCallbackPending()
- {
- if (mState == FTP_INIT) {
- if (mProxyRequest) {
- mDeferredCallbackPending = true;
- return;
- }
- Connect();
- } else if (mDataStream) {
- mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
- }
- }
|