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