1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2543 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <ctype.h> 1.11 + 1.12 +#include "prprf.h" 1.13 +#include "prlog.h" 1.14 +#include "prtime.h" 1.15 + 1.16 +#include "nsIOService.h" 1.17 +#include "nsFTPChannel.h" 1.18 +#include "nsFtpConnectionThread.h" 1.19 +#include "nsFtpControlConnection.h" 1.20 +#include "nsFtpProtocolHandler.h" 1.21 +#include "netCore.h" 1.22 +#include "nsCRT.h" 1.23 +#include "nsEscape.h" 1.24 +#include "nsMimeTypes.h" 1.25 +#include "nsNetUtil.h" 1.26 +#include "nsThreadUtils.h" 1.27 +#include "nsStreamUtils.h" 1.28 +#include "nsICacheService.h" 1.29 +#include "nsIURL.h" 1.30 +#include "nsISocketTransport.h" 1.31 +#include "nsIStreamListenerTee.h" 1.32 +#include "nsIPrefService.h" 1.33 +#include "nsIPrefBranch.h" 1.34 +#include "nsIStringBundle.h" 1.35 +#include "nsAuthInformationHolder.h" 1.36 +#include "nsIProtocolProxyService.h" 1.37 +#include "nsICancelable.h" 1.38 +#include "nsICacheEntryDescriptor.h" 1.39 +#include "nsIOutputStream.h" 1.40 +#include "nsIPrompt.h" 1.41 +#include "nsIProtocolHandler.h" 1.42 +#include "nsIProxyInfo.h" 1.43 +#include "nsIRunnable.h" 1.44 +#include "nsISocketTransportService.h" 1.45 +#include "nsIURI.h" 1.46 +#include "nsICacheSession.h" 1.47 + 1.48 +#ifdef MOZ_WIDGET_GONK 1.49 +#include "NetStatistics.h" 1.50 +#endif 1.51 + 1.52 +#if defined(PR_LOGGING) 1.53 +extern PRLogModuleInfo* gFTPLog; 1.54 +#endif 1.55 +#define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) 1.56 +#define LOG_ALWAYS(args) PR_LOG(gFTPLog, PR_LOG_ALWAYS, args) 1.57 + 1.58 +using namespace mozilla::net; 1.59 + 1.60 +// remove FTP parameters (starting with ";") from the path 1.61 +static void 1.62 +removeParamsFromPath(nsCString& path) 1.63 +{ 1.64 + int32_t index = path.FindChar(';'); 1.65 + if (index >= 0) { 1.66 + path.SetLength(index); 1.67 + } 1.68 +} 1.69 + 1.70 +NS_IMPL_ISUPPORTS_INHERITED(nsFtpState, 1.71 + nsBaseContentStream, 1.72 + nsIInputStreamCallback, 1.73 + nsITransportEventSink, 1.74 + nsICacheListener, 1.75 + nsIRequestObserver, 1.76 + nsIProtocolProxyCallback) 1.77 + 1.78 +nsFtpState::nsFtpState() 1.79 + : nsBaseContentStream(true) 1.80 + , mState(FTP_INIT) 1.81 + , mNextState(FTP_S_USER) 1.82 + , mKeepRunning(true) 1.83 + , mReceivedControlData(false) 1.84 + , mTryingCachedControl(false) 1.85 + , mRETRFailed(false) 1.86 + , mFileSize(UINT64_MAX) 1.87 + , mServerType(FTP_GENERIC_TYPE) 1.88 + , mAction(GET) 1.89 + , mAnonymous(true) 1.90 + , mRetryPass(false) 1.91 + , mStorReplyReceived(false) 1.92 + , mInternalError(NS_OK) 1.93 + , mReconnectAndLoginAgain(false) 1.94 + , mCacheConnection(true) 1.95 + , mPort(21) 1.96 + , mAddressChecked(false) 1.97 + , mServerIsIPv6(false) 1.98 + , mUseUTF8(false) 1.99 + , mControlStatus(NS_OK) 1.100 + , mDeferredCallbackPending(false) 1.101 +{ 1.102 + LOG_ALWAYS(("FTP:(%x) nsFtpState created", this)); 1.103 + 1.104 + // make sure handler stays around 1.105 + NS_ADDREF(gFtpHandler); 1.106 +} 1.107 + 1.108 +nsFtpState::~nsFtpState() 1.109 +{ 1.110 + LOG_ALWAYS(("FTP:(%x) nsFtpState destroyed", this)); 1.111 + 1.112 + if (mProxyRequest) 1.113 + mProxyRequest->Cancel(NS_ERROR_FAILURE); 1.114 + 1.115 + // release reference to handler 1.116 + nsFtpProtocolHandler *handler = gFtpHandler; 1.117 + NS_RELEASE(handler); 1.118 +} 1.119 + 1.120 +// nsIInputStreamCallback implementation 1.121 +NS_IMETHODIMP 1.122 +nsFtpState::OnInputStreamReady(nsIAsyncInputStream *aInStream) 1.123 +{ 1.124 + LOG(("FTP:(%p) data stream ready\n", this)); 1.125 + 1.126 + // We are receiving a notification from our data stream, so just forward it 1.127 + // on to our stream callback. 1.128 + if (HasPendingCallback()) 1.129 + DispatchCallbackSync(); 1.130 + 1.131 + return NS_OK; 1.132 +} 1.133 + 1.134 +void 1.135 +nsFtpState::OnControlDataAvailable(const char *aData, uint32_t aDataLen) 1.136 +{ 1.137 + LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen)); 1.138 + mControlConnection->WaitData(this); // queue up another call 1.139 + 1.140 + if (!mReceivedControlData) { 1.141 + // parameter can be null cause the channel fills them in. 1.142 + OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0); 1.143 + mReceivedControlData = true; 1.144 + } 1.145 + 1.146 + // Sometimes we can get two responses in the same packet, eg from LIST. 1.147 + // So we need to parse the response line by line 1.148 + 1.149 + nsCString buffer = mControlReadCarryOverBuf; 1.150 + 1.151 + // Clear the carryover buf - if we still don't have a line, then it will 1.152 + // be reappended below 1.153 + mControlReadCarryOverBuf.Truncate(); 1.154 + 1.155 + buffer.Append(aData, aDataLen); 1.156 + 1.157 + const char* currLine = buffer.get(); 1.158 + while (*currLine && mKeepRunning) { 1.159 + int32_t eolLength = strcspn(currLine, CRLF); 1.160 + int32_t currLineLength = strlen(currLine); 1.161 + 1.162 + // if currLine is empty or only contains CR or LF, then bail. we can 1.163 + // sometimes get an ODA event with the full response line + CR without 1.164 + // the trailing LF. the trailing LF might come in the next ODA event. 1.165 + // because we are happy enough to process a response line ending only 1.166 + // in CR, we need to take care to discard the extra LF (bug 191220). 1.167 + if (eolLength == 0 && currLineLength <= 1) 1.168 + break; 1.169 + 1.170 + if (eolLength == currLineLength) { 1.171 + mControlReadCarryOverBuf.Assign(currLine); 1.172 + break; 1.173 + } 1.174 + 1.175 + // Append the current segment, including the LF 1.176 + nsAutoCString line; 1.177 + int32_t crlfLength = 0; 1.178 + 1.179 + if ((currLineLength > eolLength) && 1.180 + (currLine[eolLength] == nsCRT::CR) && 1.181 + (currLine[eolLength+1] == nsCRT::LF)) { 1.182 + crlfLength = 2; // CR +LF 1.183 + } else { 1.184 + crlfLength = 1; // + LF or CR 1.185 + } 1.186 + 1.187 + line.Assign(currLine, eolLength + crlfLength); 1.188 + 1.189 + // Does this start with a response code? 1.190 + bool startNum = (line.Length() >= 3 && 1.191 + isdigit(line[0]) && 1.192 + isdigit(line[1]) && 1.193 + isdigit(line[2])); 1.194 + 1.195 + if (mResponseMsg.IsEmpty()) { 1.196 + // If we get here, then we know that we have a complete line, and 1.197 + // that it is the first one 1.198 + 1.199 + NS_ASSERTION(line.Length() > 4 && startNum, 1.200 + "Read buffer doesn't include response code"); 1.201 + 1.202 + mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get()); 1.203 + } 1.204 + 1.205 + mResponseMsg.Append(line); 1.206 + 1.207 + // This is the last line if its 3 numbers followed by a space 1.208 + if (startNum && line[3] == ' ') { 1.209 + // yup. last line, let's move on. 1.210 + if (mState == mNextState) { 1.211 + NS_ERROR("ftp read state mixup"); 1.212 + mInternalError = NS_ERROR_FAILURE; 1.213 + mState = FTP_ERROR; 1.214 + } else { 1.215 + mState = mNextState; 1.216 + } 1.217 + 1.218 + nsCOMPtr<nsIFTPEventSink> ftpSink; 1.219 + mChannel->GetFTPEventSink(ftpSink); 1.220 + if (ftpSink) 1.221 + ftpSink->OnFTPControlLog(true, mResponseMsg.get()); 1.222 + 1.223 + nsresult rv = Process(); 1.224 + mResponseMsg.Truncate(); 1.225 + if (NS_FAILED(rv)) { 1.226 + CloseWithStatus(rv); 1.227 + return; 1.228 + } 1.229 + } 1.230 + 1.231 + currLine = currLine + eolLength + crlfLength; 1.232 + } 1.233 +} 1.234 + 1.235 +void 1.236 +nsFtpState::OnControlError(nsresult status) 1.237 +{ 1.238 + NS_ASSERTION(NS_FAILED(status), "expecting error condition"); 1.239 + 1.240 + LOG(("FTP:(%p) CC(%p) error [%x was-cached=%u]\n", 1.241 + this, mControlConnection.get(), status, mTryingCachedControl)); 1.242 + 1.243 + mControlStatus = status; 1.244 + if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) { 1.245 + mReconnectAndLoginAgain = false; 1.246 + mAnonymous = false; 1.247 + mControlStatus = NS_OK; 1.248 + Connect(); 1.249 + } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) { 1.250 + mTryingCachedControl = false; 1.251 + Connect(); 1.252 + } else { 1.253 + CloseWithStatus(status); 1.254 + } 1.255 +} 1.256 + 1.257 +nsresult 1.258 +nsFtpState::EstablishControlConnection() 1.259 +{ 1.260 + NS_ASSERTION(!mControlConnection, "we already have a control connection"); 1.261 + 1.262 + nsresult rv; 1.263 + 1.264 + LOG(("FTP:(%x) trying cached control\n", this)); 1.265 + 1.266 + // Look to see if we can use a cached control connection: 1.267 + nsFtpControlConnection *connection = nullptr; 1.268 + // Don't use cached control if anonymous (bug #473371) 1.269 + if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) 1.270 + gFtpHandler->RemoveConnection(mChannel->URI(), &connection); 1.271 + 1.272 + if (connection) { 1.273 + mControlConnection.swap(connection); 1.274 + if (mControlConnection->IsAlive()) 1.275 + { 1.276 + // set stream listener of the control connection to be us. 1.277 + mControlConnection->WaitData(this); 1.278 + 1.279 + // read cached variables into us. 1.280 + mServerType = mControlConnection->mServerType; 1.281 + mPassword = mControlConnection->mPassword; 1.282 + mPwd = mControlConnection->mPwd; 1.283 + mUseUTF8 = mControlConnection->mUseUTF8; 1.284 + mTryingCachedControl = true; 1.285 + 1.286 + // we have to set charset to connection if server supports utf-8 1.287 + if (mUseUTF8) 1.288 + mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); 1.289 + 1.290 + // we're already connected to this server, skip login. 1.291 + mState = FTP_S_PASV; 1.292 + mResponseCode = 530; // assume the control connection was dropped. 1.293 + mControlStatus = NS_OK; 1.294 + mReceivedControlData = false; // For this request, we have not. 1.295 + 1.296 + // if we succeed, return. Otherwise, we need to create a transport 1.297 + rv = mControlConnection->Connect(mChannel->ProxyInfo(), this); 1.298 + if (NS_SUCCEEDED(rv)) 1.299 + return rv; 1.300 + } 1.301 + LOG(("FTP:(%p) cached CC(%p) is unusable\n", this, 1.302 + mControlConnection.get())); 1.303 + 1.304 + mControlConnection->WaitData(nullptr); 1.305 + mControlConnection = nullptr; 1.306 + } 1.307 + 1.308 + LOG(("FTP:(%p) creating CC\n", this)); 1.309 + 1.310 + mState = FTP_READ_BUF; 1.311 + mNextState = FTP_S_USER; 1.312 + 1.313 + nsAutoCString host; 1.314 + rv = mChannel->URI()->GetAsciiHost(host); 1.315 + if (NS_FAILED(rv)) 1.316 + return rv; 1.317 + 1.318 + mControlConnection = new nsFtpControlConnection(host, mPort); 1.319 + if (!mControlConnection) 1.320 + return NS_ERROR_OUT_OF_MEMORY; 1.321 + 1.322 + rv = mControlConnection->Connect(mChannel->ProxyInfo(), this); 1.323 + if (NS_FAILED(rv)) { 1.324 + LOG(("FTP:(%p) CC(%p) failed to connect [rv=%x]\n", this, 1.325 + mControlConnection.get(), rv)); 1.326 + mControlConnection = nullptr; 1.327 + return rv; 1.328 + } 1.329 + 1.330 + return mControlConnection->WaitData(this); 1.331 +} 1.332 + 1.333 +void 1.334 +nsFtpState::MoveToNextState(FTP_STATE nextState) 1.335 +{ 1.336 + if (NS_FAILED(mInternalError)) { 1.337 + mState = FTP_ERROR; 1.338 + LOG(("FTP:(%x) FAILED (%x)\n", this, mInternalError)); 1.339 + } else { 1.340 + mState = FTP_READ_BUF; 1.341 + mNextState = nextState; 1.342 + } 1.343 +} 1.344 + 1.345 +nsresult 1.346 +nsFtpState::Process() 1.347 +{ 1.348 + nsresult rv = NS_OK; 1.349 + bool processingRead = true; 1.350 + 1.351 + while (mKeepRunning && processingRead) { 1.352 + switch (mState) { 1.353 + case FTP_COMMAND_CONNECT: 1.354 + KillControlConnection(); 1.355 + LOG(("FTP:(%p) establishing CC", this)); 1.356 + mInternalError = EstablishControlConnection(); // sets mState 1.357 + if (NS_FAILED(mInternalError)) { 1.358 + mState = FTP_ERROR; 1.359 + LOG(("FTP:(%p) FAILED\n", this)); 1.360 + } else { 1.361 + LOG(("FTP:(%p) SUCCEEDED\n", this)); 1.362 + } 1.363 + break; 1.364 + 1.365 + case FTP_READ_BUF: 1.366 + LOG(("FTP:(%p) Waiting for CC(%p)\n", this, 1.367 + mControlConnection.get())); 1.368 + processingRead = false; 1.369 + break; 1.370 + 1.371 + case FTP_ERROR: // xx needs more work to handle dropped control connection cases 1.372 + if ((mTryingCachedControl && mResponseCode == 530 && 1.373 + mInternalError == NS_ERROR_FTP_PASV) || 1.374 + (mResponseCode == 425 && 1.375 + mInternalError == NS_ERROR_FTP_PASV)) { 1.376 + // The user was logged out during an pasv operation 1.377 + // we want to restart this request with a new control 1.378 + // channel. 1.379 + mState = FTP_COMMAND_CONNECT; 1.380 + } else if (mResponseCode == 421 && 1.381 + mInternalError != NS_ERROR_FTP_LOGIN) { 1.382 + // The command channel dropped for some reason. 1.383 + // Fire it back up, unless we were trying to login 1.384 + // in which case the server might just be telling us 1.385 + // that the max number of users has been reached... 1.386 + mState = FTP_COMMAND_CONNECT; 1.387 + } else if (mAnonymous && 1.388 + mInternalError == NS_ERROR_FTP_LOGIN) { 1.389 + // If the login was anonymous, and it failed, try again with a username 1.390 + // Don't reuse old control connection, see #386167 1.391 + mAnonymous = false; 1.392 + mState = FTP_COMMAND_CONNECT; 1.393 + } else { 1.394 + LOG(("FTP:(%x) FTP_ERROR - calling StopProcessing\n", this)); 1.395 + rv = StopProcessing(); 1.396 + NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); 1.397 + processingRead = false; 1.398 + } 1.399 + break; 1.400 + 1.401 + case FTP_COMPLETE: 1.402 + LOG(("FTP:(%x) COMPLETE\n", this)); 1.403 + rv = StopProcessing(); 1.404 + NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); 1.405 + processingRead = false; 1.406 + break; 1.407 + 1.408 +// USER 1.409 + case FTP_S_USER: 1.410 + rv = S_user(); 1.411 + 1.412 + if (NS_FAILED(rv)) 1.413 + mInternalError = NS_ERROR_FTP_LOGIN; 1.414 + 1.415 + MoveToNextState(FTP_R_USER); 1.416 + break; 1.417 + 1.418 + case FTP_R_USER: 1.419 + mState = R_user(); 1.420 + 1.421 + if (FTP_ERROR == mState) 1.422 + mInternalError = NS_ERROR_FTP_LOGIN; 1.423 + 1.424 + break; 1.425 +// PASS 1.426 + case FTP_S_PASS: 1.427 + rv = S_pass(); 1.428 + 1.429 + if (NS_FAILED(rv)) 1.430 + mInternalError = NS_ERROR_FTP_LOGIN; 1.431 + 1.432 + MoveToNextState(FTP_R_PASS); 1.433 + break; 1.434 + 1.435 + case FTP_R_PASS: 1.436 + mState = R_pass(); 1.437 + 1.438 + if (FTP_ERROR == mState) 1.439 + mInternalError = NS_ERROR_FTP_LOGIN; 1.440 + 1.441 + break; 1.442 +// ACCT 1.443 + case FTP_S_ACCT: 1.444 + rv = S_acct(); 1.445 + 1.446 + if (NS_FAILED(rv)) 1.447 + mInternalError = NS_ERROR_FTP_LOGIN; 1.448 + 1.449 + MoveToNextState(FTP_R_ACCT); 1.450 + break; 1.451 + 1.452 + case FTP_R_ACCT: 1.453 + mState = R_acct(); 1.454 + 1.455 + if (FTP_ERROR == mState) 1.456 + mInternalError = NS_ERROR_FTP_LOGIN; 1.457 + 1.458 + break; 1.459 + 1.460 +// SYST 1.461 + case FTP_S_SYST: 1.462 + rv = S_syst(); 1.463 + 1.464 + if (NS_FAILED(rv)) 1.465 + mInternalError = NS_ERROR_FTP_LOGIN; 1.466 + 1.467 + MoveToNextState(FTP_R_SYST); 1.468 + break; 1.469 + 1.470 + case FTP_R_SYST: 1.471 + mState = R_syst(); 1.472 + 1.473 + if (FTP_ERROR == mState) 1.474 + mInternalError = NS_ERROR_FTP_LOGIN; 1.475 + 1.476 + break; 1.477 + 1.478 +// TYPE 1.479 + case FTP_S_TYPE: 1.480 + rv = S_type(); 1.481 + 1.482 + if (NS_FAILED(rv)) 1.483 + mInternalError = rv; 1.484 + 1.485 + MoveToNextState(FTP_R_TYPE); 1.486 + break; 1.487 + 1.488 + case FTP_R_TYPE: 1.489 + mState = R_type(); 1.490 + 1.491 + if (FTP_ERROR == mState) 1.492 + mInternalError = NS_ERROR_FAILURE; 1.493 + 1.494 + break; 1.495 +// CWD 1.496 + case FTP_S_CWD: 1.497 + rv = S_cwd(); 1.498 + 1.499 + if (NS_FAILED(rv)) 1.500 + mInternalError = NS_ERROR_FTP_CWD; 1.501 + 1.502 + MoveToNextState(FTP_R_CWD); 1.503 + break; 1.504 + 1.505 + case FTP_R_CWD: 1.506 + mState = R_cwd(); 1.507 + 1.508 + if (FTP_ERROR == mState) 1.509 + mInternalError = NS_ERROR_FTP_CWD; 1.510 + break; 1.511 + 1.512 +// LIST 1.513 + case FTP_S_LIST: 1.514 + rv = S_list(); 1.515 + 1.516 + if (rv == NS_ERROR_NOT_RESUMABLE) { 1.517 + mInternalError = rv; 1.518 + } else if (NS_FAILED(rv)) { 1.519 + mInternalError = NS_ERROR_FTP_CWD; 1.520 + } 1.521 + 1.522 + MoveToNextState(FTP_R_LIST); 1.523 + break; 1.524 + 1.525 + case FTP_R_LIST: 1.526 + mState = R_list(); 1.527 + 1.528 + if (FTP_ERROR == mState) 1.529 + mInternalError = NS_ERROR_FAILURE; 1.530 + 1.531 + break; 1.532 + 1.533 +// SIZE 1.534 + case FTP_S_SIZE: 1.535 + rv = S_size(); 1.536 + 1.537 + if (NS_FAILED(rv)) 1.538 + mInternalError = rv; 1.539 + 1.540 + MoveToNextState(FTP_R_SIZE); 1.541 + break; 1.542 + 1.543 + case FTP_R_SIZE: 1.544 + mState = R_size(); 1.545 + 1.546 + if (FTP_ERROR == mState) 1.547 + mInternalError = NS_ERROR_FAILURE; 1.548 + 1.549 + break; 1.550 + 1.551 +// REST 1.552 + case FTP_S_REST: 1.553 + rv = S_rest(); 1.554 + 1.555 + if (NS_FAILED(rv)) 1.556 + mInternalError = rv; 1.557 + 1.558 + MoveToNextState(FTP_R_REST); 1.559 + break; 1.560 + 1.561 + case FTP_R_REST: 1.562 + mState = R_rest(); 1.563 + 1.564 + if (FTP_ERROR == mState) 1.565 + mInternalError = NS_ERROR_FAILURE; 1.566 + 1.567 + break; 1.568 + 1.569 +// MDTM 1.570 + case FTP_S_MDTM: 1.571 + rv = S_mdtm(); 1.572 + if (NS_FAILED(rv)) 1.573 + mInternalError = rv; 1.574 + MoveToNextState(FTP_R_MDTM); 1.575 + break; 1.576 + 1.577 + case FTP_R_MDTM: 1.578 + mState = R_mdtm(); 1.579 + 1.580 + // Don't want to overwrite a more explicit status code 1.581 + if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) 1.582 + mInternalError = NS_ERROR_FAILURE; 1.583 + 1.584 + break; 1.585 + 1.586 +// RETR 1.587 + case FTP_S_RETR: 1.588 + rv = S_retr(); 1.589 + 1.590 + if (NS_FAILED(rv)) 1.591 + mInternalError = rv; 1.592 + 1.593 + MoveToNextState(FTP_R_RETR); 1.594 + break; 1.595 + 1.596 + case FTP_R_RETR: 1.597 + 1.598 + mState = R_retr(); 1.599 + 1.600 + if (FTP_ERROR == mState) 1.601 + mInternalError = NS_ERROR_FAILURE; 1.602 + 1.603 + break; 1.604 + 1.605 +// STOR 1.606 + case FTP_S_STOR: 1.607 + rv = S_stor(); 1.608 + 1.609 + if (NS_FAILED(rv)) 1.610 + mInternalError = rv; 1.611 + 1.612 + MoveToNextState(FTP_R_STOR); 1.613 + break; 1.614 + 1.615 + case FTP_R_STOR: 1.616 + mState = R_stor(); 1.617 + 1.618 + if (FTP_ERROR == mState) 1.619 + mInternalError = NS_ERROR_FAILURE; 1.620 + 1.621 + break; 1.622 + 1.623 +// PASV 1.624 + case FTP_S_PASV: 1.625 + rv = S_pasv(); 1.626 + 1.627 + if (NS_FAILED(rv)) 1.628 + mInternalError = NS_ERROR_FTP_PASV; 1.629 + 1.630 + MoveToNextState(FTP_R_PASV); 1.631 + break; 1.632 + 1.633 + case FTP_R_PASV: 1.634 + mState = R_pasv(); 1.635 + 1.636 + if (FTP_ERROR == mState) 1.637 + mInternalError = NS_ERROR_FTP_PASV; 1.638 + 1.639 + break; 1.640 + 1.641 +// PWD 1.642 + case FTP_S_PWD: 1.643 + rv = S_pwd(); 1.644 + 1.645 + if (NS_FAILED(rv)) 1.646 + mInternalError = NS_ERROR_FTP_PWD; 1.647 + 1.648 + MoveToNextState(FTP_R_PWD); 1.649 + break; 1.650 + 1.651 + case FTP_R_PWD: 1.652 + mState = R_pwd(); 1.653 + 1.654 + if (FTP_ERROR == mState) 1.655 + mInternalError = NS_ERROR_FTP_PWD; 1.656 + 1.657 + break; 1.658 + 1.659 +// FEAT for RFC2640 support 1.660 + case FTP_S_FEAT: 1.661 + rv = S_feat(); 1.662 + 1.663 + if (NS_FAILED(rv)) 1.664 + mInternalError = rv; 1.665 + 1.666 + MoveToNextState(FTP_R_FEAT); 1.667 + break; 1.668 + 1.669 + case FTP_R_FEAT: 1.670 + mState = R_feat(); 1.671 + 1.672 + // Don't want to overwrite a more explicit status code 1.673 + if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) 1.674 + mInternalError = NS_ERROR_FAILURE; 1.675 + break; 1.676 + 1.677 +// OPTS for some non-RFC2640-compliant servers support 1.678 + case FTP_S_OPTS: 1.679 + rv = S_opts(); 1.680 + 1.681 + if (NS_FAILED(rv)) 1.682 + mInternalError = rv; 1.683 + 1.684 + MoveToNextState(FTP_R_OPTS); 1.685 + break; 1.686 + 1.687 + case FTP_R_OPTS: 1.688 + mState = R_opts(); 1.689 + 1.690 + // Don't want to overwrite a more explicit status code 1.691 + if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) 1.692 + mInternalError = NS_ERROR_FAILURE; 1.693 + break; 1.694 + 1.695 + default: 1.696 + ; 1.697 + 1.698 + } 1.699 + } 1.700 + 1.701 + return rv; 1.702 +} 1.703 + 1.704 +/////////////////////////////////// 1.705 +// STATE METHODS 1.706 +/////////////////////////////////// 1.707 +nsresult 1.708 +nsFtpState::S_user() { 1.709 + // some servers on connect send us a 421 or 521. (84525) (141784) 1.710 + if ((mResponseCode == 421) || (mResponseCode == 521)) 1.711 + return NS_ERROR_FAILURE; 1.712 + 1.713 + nsresult rv; 1.714 + nsAutoCString usernameStr("USER "); 1.715 + 1.716 + mResponseMsg = ""; 1.717 + 1.718 + if (mAnonymous) { 1.719 + mReconnectAndLoginAgain = true; 1.720 + usernameStr.AppendLiteral("anonymous"); 1.721 + } else { 1.722 + mReconnectAndLoginAgain = false; 1.723 + if (mUsername.IsEmpty()) { 1.724 + 1.725 + // No prompt for anonymous requests (bug #473371) 1.726 + if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) 1.727 + return NS_ERROR_FAILURE; 1.728 + 1.729 + nsCOMPtr<nsIAuthPrompt2> prompter; 1.730 + NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel), 1.731 + getter_AddRefs(prompter)); 1.732 + if (!prompter) 1.733 + return NS_ERROR_NOT_INITIALIZED; 1.734 + 1.735 + nsRefPtr<nsAuthInformationHolder> info = 1.736 + new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST, 1.737 + EmptyString(), 1.738 + EmptyCString()); 1.739 + 1.740 + bool retval; 1.741 + rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, 1.742 + info, &retval); 1.743 + 1.744 + // if the user canceled or didn't supply a username we want to fail 1.745 + if (NS_FAILED(rv) || !retval || info->User().IsEmpty()) 1.746 + return NS_ERROR_FAILURE; 1.747 + 1.748 + mUsername = info->User(); 1.749 + mPassword = info->Password(); 1.750 + } 1.751 + // XXX Is UTF-8 the best choice? 1.752 + AppendUTF16toUTF8(mUsername, usernameStr); 1.753 + } 1.754 + usernameStr.Append(CRLF); 1.755 + 1.756 + return SendFTPCommand(usernameStr); 1.757 +} 1.758 + 1.759 +FTP_STATE 1.760 +nsFtpState::R_user() { 1.761 + mReconnectAndLoginAgain = false; 1.762 + if (mResponseCode/100 == 3) { 1.763 + // send off the password 1.764 + return FTP_S_PASS; 1.765 + } 1.766 + if (mResponseCode/100 == 2) { 1.767 + // no password required, we're already logged in 1.768 + return FTP_S_SYST; 1.769 + } 1.770 + if (mResponseCode/100 == 5) { 1.771 + // problem logging in. typically this means the server 1.772 + // has reached it's user limit. 1.773 + return FTP_ERROR; 1.774 + } 1.775 + // LOGIN FAILED 1.776 + return FTP_ERROR; 1.777 +} 1.778 + 1.779 + 1.780 +nsresult 1.781 +nsFtpState::S_pass() { 1.782 + nsresult rv; 1.783 + nsAutoCString passwordStr("PASS "); 1.784 + 1.785 + mResponseMsg = ""; 1.786 + 1.787 + if (mAnonymous) { 1.788 + if (!mPassword.IsEmpty()) { 1.789 + // XXX Is UTF-8 the best choice? 1.790 + AppendUTF16toUTF8(mPassword, passwordStr); 1.791 + } else { 1.792 + nsXPIDLCString anonPassword; 1.793 + bool useRealEmail = false; 1.794 + nsCOMPtr<nsIPrefBranch> prefs = 1.795 + do_GetService(NS_PREFSERVICE_CONTRACTID); 1.796 + if (prefs) { 1.797 + rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail); 1.798 + if (NS_SUCCEEDED(rv) && useRealEmail) { 1.799 + prefs->GetCharPref("network.ftp.anonymous_password", 1.800 + getter_Copies(anonPassword)); 1.801 + } 1.802 + } 1.803 + if (!anonPassword.IsEmpty()) { 1.804 + passwordStr.AppendASCII(anonPassword); 1.805 + } else { 1.806 + // We need to default to a valid email address - bug 101027 1.807 + // example.com is reserved (rfc2606), so use that 1.808 + passwordStr.AppendLiteral("mozilla@example.com"); 1.809 + } 1.810 + } 1.811 + } else { 1.812 + if (mPassword.IsEmpty() || mRetryPass) { 1.813 + 1.814 + // No prompt for anonymous requests (bug #473371) 1.815 + if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) 1.816 + return NS_ERROR_FAILURE; 1.817 + 1.818 + nsCOMPtr<nsIAuthPrompt2> prompter; 1.819 + NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel), 1.820 + getter_AddRefs(prompter)); 1.821 + if (!prompter) 1.822 + return NS_ERROR_NOT_INITIALIZED; 1.823 + 1.824 + nsRefPtr<nsAuthInformationHolder> info = 1.825 + new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST | 1.826 + nsIAuthInformation::ONLY_PASSWORD, 1.827 + EmptyString(), 1.828 + EmptyCString()); 1.829 + 1.830 + info->SetUserInternal(mUsername); 1.831 + 1.832 + bool retval; 1.833 + rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, 1.834 + info, &retval); 1.835 + 1.836 + // we want to fail if the user canceled. Note here that if they want 1.837 + // a blank password, we will pass it along. 1.838 + if (NS_FAILED(rv) || !retval) 1.839 + return NS_ERROR_FAILURE; 1.840 + 1.841 + mPassword = info->Password(); 1.842 + } 1.843 + // XXX Is UTF-8 the best choice? 1.844 + AppendUTF16toUTF8(mPassword, passwordStr); 1.845 + } 1.846 + passwordStr.Append(CRLF); 1.847 + 1.848 + return SendFTPCommand(passwordStr); 1.849 +} 1.850 + 1.851 +FTP_STATE 1.852 +nsFtpState::R_pass() { 1.853 + if (mResponseCode/100 == 3) { 1.854 + // send account info 1.855 + return FTP_S_ACCT; 1.856 + } 1.857 + if (mResponseCode/100 == 2) { 1.858 + // logged in 1.859 + return FTP_S_SYST; 1.860 + } 1.861 + if (mResponseCode == 503) { 1.862 + // start over w/ the user command. 1.863 + // note: the password was successful, and it's stored in mPassword 1.864 + mRetryPass = false; 1.865 + return FTP_S_USER; 1.866 + } 1.867 + if (mResponseCode/100 == 5 || mResponseCode==421) { 1.868 + // There is no difference between a too-many-users error, 1.869 + // a wrong-password error, or any other sort of error 1.870 + 1.871 + if (!mAnonymous) 1.872 + mRetryPass = true; 1.873 + 1.874 + return FTP_ERROR; 1.875 + } 1.876 + // unexpected response code 1.877 + return FTP_ERROR; 1.878 +} 1.879 + 1.880 +nsresult 1.881 +nsFtpState::S_pwd() { 1.882 + return SendFTPCommand(NS_LITERAL_CSTRING("PWD" CRLF)); 1.883 +} 1.884 + 1.885 +FTP_STATE 1.886 +nsFtpState::R_pwd() { 1.887 + // Error response to PWD command isn't fatal, but don't cache the connection 1.888 + // if CWD command is sent since correct mPwd is needed for further requests. 1.889 + if (mResponseCode/100 != 2) 1.890 + return FTP_S_TYPE; 1.891 + 1.892 + nsAutoCString respStr(mResponseMsg); 1.893 + int32_t pos = respStr.FindChar('"'); 1.894 + if (pos > -1) { 1.895 + respStr.Cut(0, pos+1); 1.896 + pos = respStr.FindChar('"'); 1.897 + if (pos > -1) { 1.898 + respStr.Truncate(pos); 1.899 + if (mServerType == FTP_VMS_TYPE) 1.900 + ConvertDirspecFromVMS(respStr); 1.901 + if (respStr.IsEmpty() || respStr.Last() != '/') 1.902 + respStr.Append('/'); 1.903 + mPwd = respStr; 1.904 + } 1.905 + } 1.906 + return FTP_S_TYPE; 1.907 +} 1.908 + 1.909 +nsresult 1.910 +nsFtpState::S_syst() { 1.911 + return SendFTPCommand(NS_LITERAL_CSTRING("SYST" CRLF)); 1.912 +} 1.913 + 1.914 +FTP_STATE 1.915 +nsFtpState::R_syst() { 1.916 + if (mResponseCode/100 == 2) { 1.917 + if (( mResponseMsg.Find("L8") > -1) || 1.918 + ( mResponseMsg.Find("UNIX") > -1) || 1.919 + ( mResponseMsg.Find("BSD") > -1) || 1.920 + ( mResponseMsg.Find("MACOS Peter's Server") > -1) || 1.921 + ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) || 1.922 + ( mResponseMsg.Find("MVS") > -1) || 1.923 + ( mResponseMsg.Find("OS/390") > -1) || 1.924 + ( mResponseMsg.Find("OS/400") > -1)) { 1.925 + mServerType = FTP_UNIX_TYPE; 1.926 + } else if (( mResponseMsg.Find("WIN32", true) > -1) || 1.927 + ( mResponseMsg.Find("windows", true) > -1)) { 1.928 + mServerType = FTP_NT_TYPE; 1.929 + } else if (mResponseMsg.Find("OS/2", true) > -1) { 1.930 + mServerType = FTP_OS2_TYPE; 1.931 + } else if (mResponseMsg.Find("VMS", true) > -1) { 1.932 + mServerType = FTP_VMS_TYPE; 1.933 + } else { 1.934 + NS_ERROR("Server type list format unrecognized."); 1.935 + // Guessing causes crashes. 1.936 + // (Of course, the parsing code should be more robust...) 1.937 + nsCOMPtr<nsIStringBundleService> bundleService = 1.938 + do_GetService(NS_STRINGBUNDLE_CONTRACTID); 1.939 + if (!bundleService) 1.940 + return FTP_ERROR; 1.941 + 1.942 + nsCOMPtr<nsIStringBundle> bundle; 1.943 + nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL, 1.944 + getter_AddRefs(bundle)); 1.945 + if (NS_FAILED(rv)) 1.946 + return FTP_ERROR; 1.947 + 1.948 + char16_t* ucs2Response = ToNewUnicode(mResponseMsg); 1.949 + const char16_t *formatStrings[1] = { ucs2Response }; 1.950 + NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer"); 1.951 + 1.952 + nsXPIDLString formattedString; 1.953 + rv = bundle->FormatStringFromName(name.get(), formatStrings, 1, 1.954 + getter_Copies(formattedString)); 1.955 + nsMemory::Free(ucs2Response); 1.956 + if (NS_FAILED(rv)) 1.957 + return FTP_ERROR; 1.958 + 1.959 + // TODO(darin): this code should not be dictating UI like this! 1.960 + nsCOMPtr<nsIPrompt> prompter; 1.961 + mChannel->GetCallback(prompter); 1.962 + if (prompter) 1.963 + prompter->Alert(nullptr, formattedString.get()); 1.964 + 1.965 + // since we just alerted the user, clear mResponseMsg, 1.966 + // which is displayed to the user. 1.967 + mResponseMsg = ""; 1.968 + return FTP_ERROR; 1.969 + } 1.970 + 1.971 + return FTP_S_FEAT; 1.972 + } 1.973 + 1.974 + if (mResponseCode/100 == 5) { 1.975 + // server didn't like the SYST command. Probably (500, 501, 502) 1.976 + // No clue. We will just hope it is UNIX type server. 1.977 + mServerType = FTP_UNIX_TYPE; 1.978 + 1.979 + return FTP_S_FEAT; 1.980 + } 1.981 + return FTP_ERROR; 1.982 +} 1.983 + 1.984 +nsresult 1.985 +nsFtpState::S_acct() { 1.986 + return SendFTPCommand(NS_LITERAL_CSTRING("ACCT noaccount" CRLF)); 1.987 +} 1.988 + 1.989 +FTP_STATE 1.990 +nsFtpState::R_acct() { 1.991 + if (mResponseCode/100 == 2) 1.992 + return FTP_S_SYST; 1.993 + 1.994 + return FTP_ERROR; 1.995 +} 1.996 + 1.997 +nsresult 1.998 +nsFtpState::S_type() { 1.999 + return SendFTPCommand(NS_LITERAL_CSTRING("TYPE I" CRLF)); 1.1000 +} 1.1001 + 1.1002 +FTP_STATE 1.1003 +nsFtpState::R_type() { 1.1004 + if (mResponseCode/100 != 2) 1.1005 + return FTP_ERROR; 1.1006 + 1.1007 + return FTP_S_PASV; 1.1008 +} 1.1009 + 1.1010 +nsresult 1.1011 +nsFtpState::S_cwd() { 1.1012 + // Don't cache the connection if PWD command failed 1.1013 + if (mPwd.IsEmpty()) 1.1014 + mCacheConnection = false; 1.1015 + 1.1016 + nsAutoCString cwdStr; 1.1017 + if (mAction != PUT) 1.1018 + cwdStr = mPath; 1.1019 + if (cwdStr.IsEmpty() || cwdStr.First() != '/') 1.1020 + cwdStr.Insert(mPwd,0); 1.1021 + if (mServerType == FTP_VMS_TYPE) 1.1022 + ConvertDirspecToVMS(cwdStr); 1.1023 + cwdStr.Insert("CWD ",0); 1.1024 + cwdStr.Append(CRLF); 1.1025 + 1.1026 + return SendFTPCommand(cwdStr); 1.1027 +} 1.1028 + 1.1029 +FTP_STATE 1.1030 +nsFtpState::R_cwd() { 1.1031 + if (mResponseCode/100 == 2) { 1.1032 + if (mAction == PUT) 1.1033 + return FTP_S_STOR; 1.1034 + 1.1035 + return FTP_S_LIST; 1.1036 + } 1.1037 + 1.1038 + return FTP_ERROR; 1.1039 +} 1.1040 + 1.1041 +nsresult 1.1042 +nsFtpState::S_size() { 1.1043 + nsAutoCString sizeBuf(mPath); 1.1044 + if (sizeBuf.IsEmpty() || sizeBuf.First() != '/') 1.1045 + sizeBuf.Insert(mPwd,0); 1.1046 + if (mServerType == FTP_VMS_TYPE) 1.1047 + ConvertFilespecToVMS(sizeBuf); 1.1048 + sizeBuf.Insert("SIZE ",0); 1.1049 + sizeBuf.Append(CRLF); 1.1050 + 1.1051 + return SendFTPCommand(sizeBuf); 1.1052 +} 1.1053 + 1.1054 +FTP_STATE 1.1055 +nsFtpState::R_size() { 1.1056 + if (mResponseCode/100 == 2) { 1.1057 + PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize); 1.1058 + mChannel->SetContentLength(mFileSize); 1.1059 + } 1.1060 + 1.1061 + // We may want to be able to resume this 1.1062 + return FTP_S_MDTM; 1.1063 +} 1.1064 + 1.1065 +nsresult 1.1066 +nsFtpState::S_mdtm() { 1.1067 + nsAutoCString mdtmBuf(mPath); 1.1068 + if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/') 1.1069 + mdtmBuf.Insert(mPwd,0); 1.1070 + if (mServerType == FTP_VMS_TYPE) 1.1071 + ConvertFilespecToVMS(mdtmBuf); 1.1072 + mdtmBuf.Insert("MDTM ",0); 1.1073 + mdtmBuf.Append(CRLF); 1.1074 + 1.1075 + return SendFTPCommand(mdtmBuf); 1.1076 +} 1.1077 + 1.1078 +FTP_STATE 1.1079 +nsFtpState::R_mdtm() { 1.1080 + if (mResponseCode == 213) { 1.1081 + mResponseMsg.Cut(0,4); 1.1082 + mResponseMsg.Trim(" \t\r\n"); 1.1083 + // yyyymmddhhmmss 1.1084 + if (mResponseMsg.Length() != 14) { 1.1085 + NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response"); 1.1086 + } else { 1.1087 + mModTime = mResponseMsg; 1.1088 + 1.1089 + // Save lastModified time for downloaded files. 1.1090 + nsAutoCString timeString; 1.1091 + nsresult error; 1.1092 + PRExplodedTime exTime; 1.1093 + 1.1094 + mResponseMsg.Mid(timeString, 0, 4); 1.1095 + exTime.tm_year = timeString.ToInteger(&error); 1.1096 + mResponseMsg.Mid(timeString, 4, 2); 1.1097 + exTime.tm_month = timeString.ToInteger(&error) - 1; //january = 0 1.1098 + mResponseMsg.Mid(timeString, 6, 2); 1.1099 + exTime.tm_mday = timeString.ToInteger(&error); 1.1100 + mResponseMsg.Mid(timeString, 8, 2); 1.1101 + exTime.tm_hour = timeString.ToInteger(&error); 1.1102 + mResponseMsg.Mid(timeString, 10, 2); 1.1103 + exTime.tm_min = timeString.ToInteger(&error); 1.1104 + mResponseMsg.Mid(timeString, 12, 2); 1.1105 + exTime.tm_sec = timeString.ToInteger(&error); 1.1106 + exTime.tm_usec = 0; 1.1107 + 1.1108 + exTime.tm_params.tp_gmt_offset = 0; 1.1109 + exTime.tm_params.tp_dst_offset = 0; 1.1110 + 1.1111 + PR_NormalizeTime(&exTime, PR_GMTParameters); 1.1112 + exTime.tm_params = PR_LocalTimeParameters(&exTime); 1.1113 + 1.1114 + PRTime time = PR_ImplodeTime(&exTime); 1.1115 + (void)mChannel->SetLastModifiedTime(time); 1.1116 + } 1.1117 + } 1.1118 + 1.1119 + nsCString entityID; 1.1120 + entityID.Truncate(); 1.1121 + entityID.AppendInt(int64_t(mFileSize)); 1.1122 + entityID.Append('/'); 1.1123 + entityID.Append(mModTime); 1.1124 + mChannel->SetEntityID(entityID); 1.1125 + 1.1126 + // We weren't asked to resume 1.1127 + if (!mChannel->ResumeRequested()) 1.1128 + return FTP_S_RETR; 1.1129 + 1.1130 + //if (our entityID == supplied one (if any)) 1.1131 + if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID)) 1.1132 + return FTP_S_REST; 1.1133 + 1.1134 + mInternalError = NS_ERROR_ENTITY_CHANGED; 1.1135 + mResponseMsg.Truncate(); 1.1136 + return FTP_ERROR; 1.1137 +} 1.1138 + 1.1139 +nsresult 1.1140 +nsFtpState::SetContentType() 1.1141 +{ 1.1142 + // FTP directory URLs don't always end in a slash. Make sure they do. 1.1143 + // This check needs to be here rather than a more obvious place 1.1144 + // (e.g. LIST command processing) so that it ensures the terminating 1.1145 + // slash is appended for the new request case, as well as the case 1.1146 + // where the URL is being loaded from the cache. 1.1147 + 1.1148 + if (!mPath.IsEmpty() && mPath.Last() != '/') { 1.1149 + nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI())); 1.1150 + nsAutoCString filePath; 1.1151 + if(NS_SUCCEEDED(url->GetFilePath(filePath))) { 1.1152 + filePath.Append('/'); 1.1153 + url->SetFilePath(filePath); 1.1154 + } 1.1155 + } 1.1156 + return mChannel->SetContentType( 1.1157 + NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT)); 1.1158 +} 1.1159 + 1.1160 +nsresult 1.1161 +nsFtpState::S_list() { 1.1162 + nsresult rv = SetContentType(); 1.1163 + if (NS_FAILED(rv)) 1.1164 + // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has 1.1165 + // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109) 1.1166 + return (nsresult)FTP_ERROR; 1.1167 + 1.1168 + rv = mChannel->PushStreamConverter("text/ftp-dir", 1.1169 + APPLICATION_HTTP_INDEX_FORMAT); 1.1170 + if (NS_FAILED(rv)) { 1.1171 + // clear mResponseMsg which is displayed to the user. 1.1172 + // TODO: we should probably set this to something meaningful. 1.1173 + mResponseMsg = ""; 1.1174 + return rv; 1.1175 + } 1.1176 + 1.1177 + if (mCacheEntry) { 1.1178 + // save off the server type if we are caching. 1.1179 + nsAutoCString serverType; 1.1180 + serverType.AppendInt(mServerType); 1.1181 + mCacheEntry->SetMetaDataElement("servertype", serverType.get()); 1.1182 + 1.1183 + nsAutoCString useUTF8; 1.1184 + useUTF8.AppendInt(mUseUTF8); 1.1185 + mCacheEntry->SetMetaDataElement("useUTF8", useUTF8.get()); 1.1186 + 1.1187 + // open cache entry for writing, and configure it to receive data. 1.1188 + if (NS_FAILED(InstallCacheListener())) { 1.1189 + mCacheEntry->AsyncDoom(nullptr); 1.1190 + mCacheEntry = nullptr; 1.1191 + } 1.1192 + } 1.1193 + 1.1194 + // dir listings aren't resumable 1.1195 + NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE); 1.1196 + 1.1197 + mChannel->SetEntityID(EmptyCString()); 1.1198 + 1.1199 + const char *listString; 1.1200 + if (mServerType == FTP_VMS_TYPE) { 1.1201 + listString = "LIST *.*;0" CRLF; 1.1202 + } else { 1.1203 + listString = "LIST" CRLF; 1.1204 + } 1.1205 + 1.1206 + return SendFTPCommand(nsDependentCString(listString)); 1.1207 +} 1.1208 + 1.1209 +FTP_STATE 1.1210 +nsFtpState::R_list() { 1.1211 + if (mResponseCode/100 == 1) { 1.1212 + // OK, time to start reading from the data connection. 1.1213 + if (mDataStream && HasPendingCallback()) 1.1214 + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); 1.1215 + return FTP_READ_BUF; 1.1216 + } 1.1217 + 1.1218 + if (mResponseCode/100 == 2) { 1.1219 + //(DONE) 1.1220 + mNextState = FTP_COMPLETE; 1.1221 + mDoomCache = false; 1.1222 + return FTP_COMPLETE; 1.1223 + } 1.1224 + return FTP_ERROR; 1.1225 +} 1.1226 + 1.1227 +nsresult 1.1228 +nsFtpState::S_retr() { 1.1229 + nsAutoCString retrStr(mPath); 1.1230 + if (retrStr.IsEmpty() || retrStr.First() != '/') 1.1231 + retrStr.Insert(mPwd,0); 1.1232 + if (mServerType == FTP_VMS_TYPE) 1.1233 + ConvertFilespecToVMS(retrStr); 1.1234 + retrStr.Insert("RETR ",0); 1.1235 + retrStr.Append(CRLF); 1.1236 + return SendFTPCommand(retrStr); 1.1237 +} 1.1238 + 1.1239 +FTP_STATE 1.1240 +nsFtpState::R_retr() { 1.1241 + if (mResponseCode/100 == 2) { 1.1242 + //(DONE) 1.1243 + mNextState = FTP_COMPLETE; 1.1244 + return FTP_COMPLETE; 1.1245 + } 1.1246 + 1.1247 + if (mResponseCode/100 == 1) { 1.1248 + // We're going to grab a file, not a directory. So we need to clear 1.1249 + // any cache entry, otherwise we'll have problems reading it later. 1.1250 + // See bug 122548 1.1251 + if (mCacheEntry) { 1.1252 + (void)mCacheEntry->AsyncDoom(nullptr); 1.1253 + mCacheEntry = nullptr; 1.1254 + } 1.1255 + if (mDataStream && HasPendingCallback()) 1.1256 + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); 1.1257 + return FTP_READ_BUF; 1.1258 + } 1.1259 + 1.1260 + // These error codes are related to problems with the connection. 1.1261 + // If we encounter any at this point, do not try CWD and abort. 1.1262 + if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426) 1.1263 + return FTP_ERROR; 1.1264 + 1.1265 + if (mResponseCode/100 == 5) { 1.1266 + mRETRFailed = true; 1.1267 + return FTP_S_PASV; 1.1268 + } 1.1269 + 1.1270 + return FTP_S_CWD; 1.1271 +} 1.1272 + 1.1273 + 1.1274 +nsresult 1.1275 +nsFtpState::S_rest() { 1.1276 + 1.1277 + nsAutoCString restString("REST "); 1.1278 + // The int64_t cast is needed to avoid ambiguity 1.1279 + restString.AppendInt(int64_t(mChannel->StartPos()), 10); 1.1280 + restString.Append(CRLF); 1.1281 + 1.1282 + return SendFTPCommand(restString); 1.1283 +} 1.1284 + 1.1285 +FTP_STATE 1.1286 +nsFtpState::R_rest() { 1.1287 + if (mResponseCode/100 == 4) { 1.1288 + // If REST fails, then we can't resume 1.1289 + mChannel->SetEntityID(EmptyCString()); 1.1290 + 1.1291 + mInternalError = NS_ERROR_NOT_RESUMABLE; 1.1292 + mResponseMsg.Truncate(); 1.1293 + 1.1294 + return FTP_ERROR; 1.1295 + } 1.1296 + 1.1297 + return FTP_S_RETR; 1.1298 +} 1.1299 + 1.1300 +nsresult 1.1301 +nsFtpState::S_stor() { 1.1302 + NS_ENSURE_STATE(mChannel->UploadStream()); 1.1303 + 1.1304 + NS_ASSERTION(mAction == PUT, "Wrong state to be here"); 1.1305 + 1.1306 + nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI()); 1.1307 + NS_ASSERTION(url, "I thought you were a nsStandardURL"); 1.1308 + 1.1309 + nsAutoCString storStr; 1.1310 + url->GetFilePath(storStr); 1.1311 + NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path"); 1.1312 + 1.1313 + // kill the first slash since we want to be relative to CWD. 1.1314 + if (storStr.First() == '/') 1.1315 + storStr.Cut(0,1); 1.1316 + 1.1317 + if (mServerType == FTP_VMS_TYPE) 1.1318 + ConvertFilespecToVMS(storStr); 1.1319 + 1.1320 + NS_UnescapeURL(storStr); 1.1321 + storStr.Insert("STOR ",0); 1.1322 + storStr.Append(CRLF); 1.1323 + 1.1324 + return SendFTPCommand(storStr); 1.1325 +} 1.1326 + 1.1327 +FTP_STATE 1.1328 +nsFtpState::R_stor() { 1.1329 + if (mResponseCode/100 == 2) { 1.1330 + //(DONE) 1.1331 + mNextState = FTP_COMPLETE; 1.1332 + mStorReplyReceived = true; 1.1333 + 1.1334 + // Call Close() if it was not called in nsFtpState::OnStoprequest() 1.1335 + if (!mUploadRequest && !IsClosed()) 1.1336 + Close(); 1.1337 + 1.1338 + return FTP_COMPLETE; 1.1339 + } 1.1340 + 1.1341 + if (mResponseCode/100 == 1) { 1.1342 + LOG(("FTP:(%x) writing on DT\n", this)); 1.1343 + return FTP_READ_BUF; 1.1344 + } 1.1345 + 1.1346 + mStorReplyReceived = true; 1.1347 + return FTP_ERROR; 1.1348 +} 1.1349 + 1.1350 + 1.1351 +nsresult 1.1352 +nsFtpState::S_pasv() { 1.1353 + if (!mAddressChecked) { 1.1354 + // Find socket address 1.1355 + mAddressChecked = true; 1.1356 + mServerAddress.raw.family = AF_INET; 1.1357 + mServerAddress.inet.ip = htonl(INADDR_ANY); 1.1358 + mServerAddress.inet.port = htons(0); 1.1359 + 1.1360 + nsITransport *controlSocket = mControlConnection->Transport(); 1.1361 + if (!controlSocket) 1.1362 + // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has 1.1363 + // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109) 1.1364 + return (nsresult)FTP_ERROR; 1.1365 + 1.1366 + nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket); 1.1367 + if (sTrans) { 1.1368 + nsresult rv = sTrans->GetPeerAddr(&mServerAddress); 1.1369 + if (NS_SUCCEEDED(rv)) { 1.1370 + if (!IsIPAddrAny(&mServerAddress)) 1.1371 + mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) && 1.1372 + !IsIPAddrV4Mapped(&mServerAddress); 1.1373 + else { 1.1374 + /* 1.1375 + * In case of SOCKS5 remote DNS resolution, we do 1.1376 + * not know the remote IP address. Still, if it is 1.1377 + * an IPV6 host, then the external address of the 1.1378 + * socks server should also be IPv6, and this is the 1.1379 + * self address of the transport. 1.1380 + */ 1.1381 + NetAddr selfAddress; 1.1382 + rv = sTrans->GetSelfAddr(&selfAddress); 1.1383 + if (NS_SUCCEEDED(rv)) 1.1384 + mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) && 1.1385 + !IsIPAddrV4Mapped(&selfAddress); 1.1386 + } 1.1387 + } 1.1388 + } 1.1389 + } 1.1390 + 1.1391 + const char *string; 1.1392 + if (mServerIsIPv6) { 1.1393 + string = "EPSV" CRLF; 1.1394 + } else { 1.1395 + string = "PASV" CRLF; 1.1396 + } 1.1397 + 1.1398 + return SendFTPCommand(nsDependentCString(string)); 1.1399 + 1.1400 +} 1.1401 + 1.1402 +FTP_STATE 1.1403 +nsFtpState::R_pasv() { 1.1404 + if (mResponseCode/100 != 2) 1.1405 + return FTP_ERROR; 1.1406 + 1.1407 + nsresult rv; 1.1408 + int32_t port; 1.1409 + 1.1410 + nsAutoCString responseCopy(mResponseMsg); 1.1411 + char *response = responseCopy.BeginWriting(); 1.1412 + 1.1413 + char *ptr = response; 1.1414 + 1.1415 + // Make sure to ignore the address in the PASV response (bug 370559) 1.1416 + 1.1417 + if (mServerIsIPv6) { 1.1418 + // The returned string is of the form 1.1419 + // text (|||ppp|) 1.1420 + // Where '|' can be any single character 1.1421 + char delim; 1.1422 + while (*ptr && *ptr != '(') 1.1423 + ptr++; 1.1424 + if (*ptr++ != '(') 1.1425 + return FTP_ERROR; 1.1426 + delim = *ptr++; 1.1427 + if (!delim || *ptr++ != delim || 1.1428 + *ptr++ != delim || 1.1429 + *ptr < '0' || *ptr > '9') 1.1430 + return FTP_ERROR; 1.1431 + port = 0; 1.1432 + do { 1.1433 + port = port * 10 + *ptr++ - '0'; 1.1434 + } while (*ptr >= '0' && *ptr <= '9'); 1.1435 + if (*ptr++ != delim || *ptr != ')') 1.1436 + return FTP_ERROR; 1.1437 + } else { 1.1438 + // The returned address string can be of the form 1.1439 + // (xxx,xxx,xxx,xxx,ppp,ppp) or 1.1440 + // xxx,xxx,xxx,xxx,ppp,ppp (without parens) 1.1441 + int32_t h0, h1, h2, h3, p0, p1; 1.1442 + 1.1443 + uint32_t fields = 0; 1.1444 + // First try with parens 1.1445 + while (*ptr && *ptr != '(') 1.1446 + ++ptr; 1.1447 + if (*ptr) { 1.1448 + ++ptr; 1.1449 + fields = PR_sscanf(ptr, 1.1450 + "%ld,%ld,%ld,%ld,%ld,%ld", 1.1451 + &h0, &h1, &h2, &h3, &p0, &p1); 1.1452 + } 1.1453 + if (!*ptr || fields < 6) { 1.1454 + // OK, lets try w/o parens 1.1455 + ptr = response; 1.1456 + while (*ptr && *ptr != ',') 1.1457 + ++ptr; 1.1458 + if (*ptr) { 1.1459 + // backup to the start of the digits 1.1460 + do { 1.1461 + ptr--; 1.1462 + } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9')); 1.1463 + ptr++; // get back onto the numbers 1.1464 + fields = PR_sscanf(ptr, 1.1465 + "%ld,%ld,%ld,%ld,%ld,%ld", 1.1466 + &h0, &h1, &h2, &h3, &p0, &p1); 1.1467 + } 1.1468 + } 1.1469 + 1.1470 + NS_ASSERTION(fields == 6, "Can't parse PASV response"); 1.1471 + if (fields < 6) 1.1472 + return FTP_ERROR; 1.1473 + 1.1474 + port = ((int32_t) (p0<<8)) + p1; 1.1475 + } 1.1476 + 1.1477 + bool newDataConn = true; 1.1478 + if (mDataTransport) { 1.1479 + // Reuse this connection only if its still alive, and the port 1.1480 + // is the same 1.1481 + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport); 1.1482 + if (strans) { 1.1483 + int32_t oldPort; 1.1484 + nsresult rv = strans->GetPort(&oldPort); 1.1485 + if (NS_SUCCEEDED(rv)) { 1.1486 + if (oldPort == port) { 1.1487 + bool isAlive; 1.1488 + if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive) 1.1489 + newDataConn = false; 1.1490 + } 1.1491 + } 1.1492 + } 1.1493 + 1.1494 + if (newDataConn) { 1.1495 + mDataTransport->Close(NS_ERROR_ABORT); 1.1496 + mDataTransport = nullptr; 1.1497 + mDataStream = nullptr; 1.1498 + } 1.1499 + } 1.1500 + 1.1501 + if (newDataConn) { 1.1502 + // now we know where to connect our data channel 1.1503 + nsCOMPtr<nsISocketTransportService> sts = 1.1504 + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); 1.1505 + if (!sts) 1.1506 + return FTP_ERROR; 1.1507 + 1.1508 + nsCOMPtr<nsISocketTransport> strans; 1.1509 + 1.1510 + nsAutoCString host; 1.1511 + if (!IsIPAddrAny(&mServerAddress)) { 1.1512 + char buf[kIPv6CStrBufSize]; 1.1513 + NetAddrToString(&mServerAddress, buf, sizeof(buf)); 1.1514 + host.Assign(buf); 1.1515 + } else { 1.1516 + /* 1.1517 + * In case of SOCKS5 remote DNS resolving, the peer address 1.1518 + * fetched previously will be invalid (0.0.0.0): it is unknown 1.1519 + * to us. But we can pass on the original hostname to the 1.1520 + * connect for the data connection. 1.1521 + */ 1.1522 + rv = mChannel->URI()->GetAsciiHost(host); 1.1523 + if (NS_FAILED(rv)) 1.1524 + return FTP_ERROR; 1.1525 + } 1.1526 + 1.1527 + rv = sts->CreateTransport(nullptr, 0, host, 1.1528 + port, mChannel->ProxyInfo(), 1.1529 + getter_AddRefs(strans)); // the data socket 1.1530 + if (NS_FAILED(rv)) 1.1531 + return FTP_ERROR; 1.1532 + mDataTransport = strans; 1.1533 + 1.1534 + strans->SetQoSBits(gFtpHandler->GetDataQoSBits()); 1.1535 + 1.1536 + LOG(("FTP:(%x) created DT (%s:%x)\n", this, host.get(), port)); 1.1537 + 1.1538 + // hook ourself up as a proxy for status notifications 1.1539 + rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread()); 1.1540 + NS_ENSURE_SUCCESS(rv, FTP_ERROR); 1.1541 + 1.1542 + if (mAction == PUT) { 1.1543 + NS_ASSERTION(!mRETRFailed, "Failed before uploading"); 1.1544 + 1.1545 + // nsIUploadChannel requires the upload stream to support ReadSegments. 1.1546 + // therefore, we can open an unbuffered socket output stream. 1.1547 + nsCOMPtr<nsIOutputStream> output; 1.1548 + rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 1.1549 + 0, 0, getter_AddRefs(output)); 1.1550 + if (NS_FAILED(rv)) 1.1551 + return FTP_ERROR; 1.1552 + 1.1553 + // perform the data copy on the socket transport thread. we do this 1.1554 + // because "output" is a socket output stream, so the result is that 1.1555 + // all work will be done on the socket transport thread. 1.1556 + nsCOMPtr<nsIEventTarget> stEventTarget = 1.1557 + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); 1.1558 + if (!stEventTarget) 1.1559 + return FTP_ERROR; 1.1560 + 1.1561 + nsCOMPtr<nsIAsyncStreamCopier> copier; 1.1562 + rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier), 1.1563 + mChannel->UploadStream(), 1.1564 + output, 1.1565 + stEventTarget, 1.1566 + true, // upload stream is buffered 1.1567 + false); // output is NOT buffered 1.1568 + if (NS_FAILED(rv)) 1.1569 + return FTP_ERROR; 1.1570 + 1.1571 + rv = copier->AsyncCopy(this, nullptr); 1.1572 + if (NS_FAILED(rv)) 1.1573 + return FTP_ERROR; 1.1574 + 1.1575 + // hold a reference to the copier so we can cancel it if necessary. 1.1576 + mUploadRequest = copier; 1.1577 + 1.1578 + // update the current working directory before sending the STOR 1.1579 + // command. this is needed since we might be reusing a control 1.1580 + // connection. 1.1581 + return FTP_S_CWD; 1.1582 + } 1.1583 + 1.1584 + // 1.1585 + // else, we are reading from the data connection... 1.1586 + // 1.1587 + 1.1588 + // open a buffered, asynchronous socket input stream 1.1589 + nsCOMPtr<nsIInputStream> input; 1.1590 + rv = mDataTransport->OpenInputStream(0, 1.1591 + nsIOService::gDefaultSegmentSize, 1.1592 + nsIOService::gDefaultSegmentCount, 1.1593 + getter_AddRefs(input)); 1.1594 + NS_ENSURE_SUCCESS(rv, FTP_ERROR); 1.1595 + mDataStream = do_QueryInterface(input); 1.1596 + } 1.1597 + 1.1598 + if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/') 1.1599 + return FTP_S_CWD; 1.1600 + return FTP_S_SIZE; 1.1601 +} 1.1602 + 1.1603 +nsresult 1.1604 +nsFtpState::S_feat() { 1.1605 + return SendFTPCommand(NS_LITERAL_CSTRING("FEAT" CRLF)); 1.1606 +} 1.1607 + 1.1608 +FTP_STATE 1.1609 +nsFtpState::R_feat() { 1.1610 + if (mResponseCode/100 == 2) { 1.1611 + if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) { 1.1612 + // This FTP server supports UTF-8 encoding 1.1613 + mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); 1.1614 + mUseUTF8 = true; 1.1615 + return FTP_S_OPTS; 1.1616 + } 1.1617 + } 1.1618 + 1.1619 + mUseUTF8 = false; 1.1620 + return FTP_S_PWD; 1.1621 +} 1.1622 + 1.1623 +nsresult 1.1624 +nsFtpState::S_opts() { 1.1625 + // This command is for compatibility of old FTP spec (IETF Draft) 1.1626 + return SendFTPCommand(NS_LITERAL_CSTRING("OPTS UTF8 ON" CRLF)); 1.1627 +} 1.1628 + 1.1629 +FTP_STATE 1.1630 +nsFtpState::R_opts() { 1.1631 + // Ignore error code because "OPTS UTF8 ON" is for compatibility of 1.1632 + // FTP server using IETF draft 1.1633 + return FTP_S_PWD; 1.1634 +} 1.1635 + 1.1636 +//////////////////////////////////////////////////////////////////////////////// 1.1637 +// nsIRequest methods: 1.1638 + 1.1639 +static inline 1.1640 +uint32_t GetFtpTime() 1.1641 +{ 1.1642 + return uint32_t(PR_Now() / PR_USEC_PER_SEC); 1.1643 +} 1.1644 + 1.1645 +uint32_t nsFtpState::mSessionStartTime = GetFtpTime(); 1.1646 + 1.1647 +/* Is this cache entry valid to use for reading? 1.1648 + * Since we make up an expiration time for ftp, use the following rules: 1.1649 + * (see bug 103726) 1.1650 + * 1.1651 + * LOAD_FROM_CACHE : always use cache entry, even if expired 1.1652 + * LOAD_BYPASS_CACHE : overwrite cache entry 1.1653 + * LOAD_NORMAL|VALIDATE_ALWAYS : overwrite cache entry 1.1654 + * LOAD_NORMAL : honor expiration time 1.1655 + * LOAD_NORMAL|VALIDATE_ONCE_PER_SESSION : overwrite cache entry if first access 1.1656 + * this session, otherwise use cache entry 1.1657 + * even if expired. 1.1658 + * LOAD_NORMAL|VALIDATE_NEVER : always use cache entry, even if expired 1.1659 + * 1.1660 + * Note that in theory we could use the mdtm time on the directory 1.1661 + * In practice, the lack of a timezone plus the general lack of support for that 1.1662 + * on directories means that its not worth it, I suspect. Revisit if we start 1.1663 + * caching files - bbaetz 1.1664 + */ 1.1665 +bool 1.1666 +nsFtpState::CanReadCacheEntry() 1.1667 +{ 1.1668 + NS_ASSERTION(mCacheEntry, "must have a cache entry"); 1.1669 + 1.1670 + nsCacheAccessMode access; 1.1671 + nsresult rv = mCacheEntry->GetAccessGranted(&access); 1.1672 + if (NS_FAILED(rv)) 1.1673 + return false; 1.1674 + 1.1675 + // If I'm not granted read access, then I can't reuse it... 1.1676 + if (!(access & nsICache::ACCESS_READ)) 1.1677 + return false; 1.1678 + 1.1679 + if (mChannel->HasLoadFlag(nsIRequest::LOAD_FROM_CACHE)) 1.1680 + return true; 1.1681 + 1.1682 + if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) 1.1683 + return false; 1.1684 + 1.1685 + if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ALWAYS)) 1.1686 + return false; 1.1687 + 1.1688 + uint32_t time; 1.1689 + if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ONCE_PER_SESSION)) { 1.1690 + rv = mCacheEntry->GetLastModified(&time); 1.1691 + if (NS_FAILED(rv)) 1.1692 + return false; 1.1693 + return (mSessionStartTime > time); 1.1694 + } 1.1695 + 1.1696 + if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_NEVER)) 1.1697 + return true; 1.1698 + 1.1699 + // OK, now we just check the expiration time as usual 1.1700 + rv = mCacheEntry->GetExpirationTime(&time); 1.1701 + if (NS_FAILED(rv)) 1.1702 + return false; 1.1703 + 1.1704 + return (GetFtpTime() <= time); 1.1705 +} 1.1706 + 1.1707 +nsresult 1.1708 +nsFtpState::InstallCacheListener() 1.1709 +{ 1.1710 + NS_ASSERTION(mCacheEntry, "must have a cache entry"); 1.1711 + 1.1712 + nsCOMPtr<nsIOutputStream> out; 1.1713 + mCacheEntry->OpenOutputStream(0, getter_AddRefs(out)); 1.1714 + NS_ENSURE_STATE(out); 1.1715 + 1.1716 + nsCOMPtr<nsIStreamListenerTee> tee = 1.1717 + do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID); 1.1718 + NS_ENSURE_STATE(tee); 1.1719 + 1.1720 + nsresult rv = tee->Init(mChannel->StreamListener(), out, nullptr); 1.1721 + NS_ENSURE_SUCCESS(rv, rv); 1.1722 + 1.1723 + mChannel->SetStreamListener(tee); 1.1724 + return NS_OK; 1.1725 +} 1.1726 + 1.1727 +nsresult 1.1728 +nsFtpState::OpenCacheDataStream() 1.1729 +{ 1.1730 + NS_ASSERTION(mCacheEntry, "must have a cache entry"); 1.1731 + 1.1732 + // Get a transport to the cached data... 1.1733 + nsCOMPtr<nsIInputStream> input; 1.1734 + mCacheEntry->OpenInputStream(0, getter_AddRefs(input)); 1.1735 + NS_ENSURE_STATE(input); 1.1736 + 1.1737 + nsCOMPtr<nsIStreamTransportService> sts = 1.1738 + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 1.1739 + NS_ENSURE_STATE(sts); 1.1740 + 1.1741 + nsCOMPtr<nsITransport> transport; 1.1742 + sts->CreateInputTransport(input, -1, -1, true, 1.1743 + getter_AddRefs(transport)); 1.1744 + NS_ENSURE_STATE(transport); 1.1745 + 1.1746 + nsresult rv = transport->SetEventSink(this, NS_GetCurrentThread()); 1.1747 + NS_ENSURE_SUCCESS(rv, rv); 1.1748 + 1.1749 + // Open a non-blocking, buffered input stream... 1.1750 + nsCOMPtr<nsIInputStream> transportInput; 1.1751 + transport->OpenInputStream(0, 1.1752 + nsIOService::gDefaultSegmentSize, 1.1753 + nsIOService::gDefaultSegmentCount, 1.1754 + getter_AddRefs(transportInput)); 1.1755 + NS_ENSURE_STATE(transportInput); 1.1756 + 1.1757 + mDataStream = do_QueryInterface(transportInput); 1.1758 + NS_ENSURE_STATE(mDataStream); 1.1759 + 1.1760 + mDataTransport = transport; 1.1761 + return NS_OK; 1.1762 +} 1.1763 + 1.1764 +nsresult 1.1765 +nsFtpState::Init(nsFtpChannel *channel) 1.1766 +{ 1.1767 + // parameter validation 1.1768 + NS_ASSERTION(channel, "FTP: needs a channel"); 1.1769 + 1.1770 + mChannel = channel; // a straight ref ptr to the channel 1.1771 + 1.1772 + // initialize counter for network metering 1.1773 + mCountRecv = 0; 1.1774 + 1.1775 +#ifdef MOZ_WIDGET_GONK 1.1776 + nsCOMPtr<nsINetworkInterface> activeNetwork; 1.1777 + GetActiveNetworkInterface(activeNetwork); 1.1778 + mActiveNetwork = 1.1779 + new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork); 1.1780 +#endif 1.1781 + 1.1782 + mKeepRunning = true; 1.1783 + mSuppliedEntityID = channel->EntityID(); 1.1784 + 1.1785 + if (channel->UploadStream()) 1.1786 + mAction = PUT; 1.1787 + 1.1788 + nsresult rv; 1.1789 + nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI()); 1.1790 + 1.1791 + nsAutoCString host; 1.1792 + if (url) { 1.1793 + rv = url->GetAsciiHost(host); 1.1794 + } else { 1.1795 + rv = mChannel->URI()->GetAsciiHost(host); 1.1796 + } 1.1797 + if (NS_FAILED(rv) || host.IsEmpty()) { 1.1798 + return NS_ERROR_MALFORMED_URI; 1.1799 + } 1.1800 + 1.1801 + nsAutoCString path; 1.1802 + if (url) { 1.1803 + rv = url->GetFilePath(path); 1.1804 + } else { 1.1805 + rv = mChannel->URI()->GetPath(path); 1.1806 + } 1.1807 + if (NS_FAILED(rv)) 1.1808 + return rv; 1.1809 + 1.1810 + removeParamsFromPath(path); 1.1811 + 1.1812 + // FTP parameters such as type=i are ignored 1.1813 + if (url) { 1.1814 + url->SetFilePath(path); 1.1815 + } else { 1.1816 + mChannel->URI()->SetPath(path); 1.1817 + } 1.1818 + 1.1819 + // Skip leading slash 1.1820 + char *fwdPtr = path.BeginWriting(); 1.1821 + if (!fwdPtr) 1.1822 + return NS_ERROR_OUT_OF_MEMORY; 1.1823 + if (*fwdPtr == '/') 1.1824 + fwdPtr++; 1.1825 + if (*fwdPtr != '\0') { 1.1826 + // now unescape it... %xx reduced inline to resulting character 1.1827 + int32_t len = NS_UnescapeURL(fwdPtr); 1.1828 + mPath.Assign(fwdPtr, len); 1.1829 + 1.1830 +#ifdef DEBUG 1.1831 + if (mPath.FindCharInSet(CRLF) >= 0) 1.1832 + NS_ERROR("NewURI() should've prevented this!!!"); 1.1833 +#endif 1.1834 + } 1.1835 + 1.1836 + // pull any username and/or password out of the uri 1.1837 + nsAutoCString uname; 1.1838 + rv = mChannel->URI()->GetUsername(uname); 1.1839 + if (NS_FAILED(rv)) 1.1840 + return rv; 1.1841 + 1.1842 + if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) { 1.1843 + mAnonymous = false; 1.1844 + CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername); 1.1845 + 1.1846 + // return an error if we find a CR or LF in the username 1.1847 + if (uname.FindCharInSet(CRLF) >= 0) 1.1848 + return NS_ERROR_MALFORMED_URI; 1.1849 + } 1.1850 + 1.1851 + nsAutoCString password; 1.1852 + rv = mChannel->URI()->GetPassword(password); 1.1853 + if (NS_FAILED(rv)) 1.1854 + return rv; 1.1855 + 1.1856 + CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword); 1.1857 + 1.1858 + // return an error if we find a CR or LF in the password 1.1859 + if (mPassword.FindCharInSet(CRLF) >= 0) 1.1860 + return NS_ERROR_MALFORMED_URI; 1.1861 + 1.1862 + // setup the connection cache key 1.1863 + 1.1864 + int32_t port; 1.1865 + rv = mChannel->URI()->GetPort(&port); 1.1866 + if (NS_FAILED(rv)) 1.1867 + return rv; 1.1868 + 1.1869 + if (port > 0) 1.1870 + mPort = port; 1.1871 + 1.1872 + // Lookup Proxy information asynchronously if it isn't already set 1.1873 + // on the channel and if we aren't configured explicitly to go directly 1.1874 + nsCOMPtr<nsIProtocolProxyService> pps = 1.1875 + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); 1.1876 + 1.1877 + if (pps && !mChannel->ProxyInfo()) { 1.1878 + pps->AsyncResolve(mChannel, 0, this, 1.1879 + getter_AddRefs(mProxyRequest)); 1.1880 + } 1.1881 + 1.1882 + return NS_OK; 1.1883 +} 1.1884 + 1.1885 +void 1.1886 +nsFtpState::Connect() 1.1887 +{ 1.1888 + mState = FTP_COMMAND_CONNECT; 1.1889 + mNextState = FTP_S_USER; 1.1890 + 1.1891 + nsresult rv = Process(); 1.1892 + 1.1893 + // check for errors. 1.1894 + if (NS_FAILED(rv)) { 1.1895 + LOG(("FTP:Process() failed: %x\n", rv)); 1.1896 + mInternalError = NS_ERROR_FAILURE; 1.1897 + mState = FTP_ERROR; 1.1898 + CloseWithStatus(mInternalError); 1.1899 + } 1.1900 +} 1.1901 + 1.1902 +void 1.1903 +nsFtpState::KillControlConnection() 1.1904 +{ 1.1905 + mControlReadCarryOverBuf.Truncate(0); 1.1906 + 1.1907 + mAddressChecked = false; 1.1908 + mServerIsIPv6 = false; 1.1909 + 1.1910 + // if everything went okay, save the connection. 1.1911 + // FIX: need a better way to determine if we can cache the connections. 1.1912 + // there are some errors which do not mean that we need to kill the connection 1.1913 + // e.g. fnf. 1.1914 + 1.1915 + if (!mControlConnection) 1.1916 + return; 1.1917 + 1.1918 + // kill the reference to ourselves in the control connection. 1.1919 + mControlConnection->WaitData(nullptr); 1.1920 + 1.1921 + if (NS_SUCCEEDED(mInternalError) && 1.1922 + NS_SUCCEEDED(mControlStatus) && 1.1923 + mControlConnection->IsAlive() && 1.1924 + mCacheConnection) { 1.1925 + 1.1926 + LOG_ALWAYS(("FTP:(%p) caching CC(%p)", this, mControlConnection.get())); 1.1927 + 1.1928 + // Store connection persistent data 1.1929 + mControlConnection->mServerType = mServerType; 1.1930 + mControlConnection->mPassword = mPassword; 1.1931 + mControlConnection->mPwd = mPwd; 1.1932 + mControlConnection->mUseUTF8 = mUseUTF8; 1.1933 + 1.1934 + nsresult rv = NS_OK; 1.1935 + // Don't cache controlconnection if anonymous (bug #473371) 1.1936 + if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) 1.1937 + rv = gFtpHandler->InsertConnection(mChannel->URI(), 1.1938 + mControlConnection); 1.1939 + // Can't cache it? Kill it then. 1.1940 + mControlConnection->Disconnect(rv); 1.1941 + } else { 1.1942 + mControlConnection->Disconnect(NS_BINDING_ABORTED); 1.1943 + } 1.1944 + 1.1945 + mControlConnection = nullptr; 1.1946 +} 1.1947 + 1.1948 +class nsFtpAsyncAlert : public nsRunnable 1.1949 +{ 1.1950 +public: 1.1951 + nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg) 1.1952 + : mPrompter(aPrompter) 1.1953 + , mResponseMsg(aResponseMsg) 1.1954 + { 1.1955 + MOZ_COUNT_CTOR(nsFtpAsyncAlert); 1.1956 + } 1.1957 + virtual ~nsFtpAsyncAlert() 1.1958 + { 1.1959 + MOZ_COUNT_DTOR(nsFtpAsyncAlert); 1.1960 + } 1.1961 + NS_IMETHOD Run() 1.1962 + { 1.1963 + if (mPrompter) { 1.1964 + mPrompter->Alert(nullptr, mResponseMsg.get()); 1.1965 + } 1.1966 + return NS_OK; 1.1967 + } 1.1968 +private: 1.1969 + nsCOMPtr<nsIPrompt> mPrompter; 1.1970 + nsString mResponseMsg; 1.1971 +}; 1.1972 + 1.1973 + 1.1974 +nsresult 1.1975 +nsFtpState::StopProcessing() 1.1976 +{ 1.1977 + // Only do this function once. 1.1978 + if (!mKeepRunning) 1.1979 + return NS_OK; 1.1980 + mKeepRunning = false; 1.1981 + 1.1982 + LOG_ALWAYS(("FTP:(%x) nsFtpState stopping", this)); 1.1983 + 1.1984 +#ifdef DEBUG_dougt 1.1985 + printf("FTP Stopped: [response code %d] [response msg follows:]\n%s\n", mResponseCode, mResponseMsg.get()); 1.1986 +#endif 1.1987 + 1.1988 + if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) { 1.1989 + // check to see if the control status is bad. 1.1990 + // web shell wont throw an alert. we better: 1.1991 + 1.1992 + // XXX(darin): this code should not be dictating UI like this! 1.1993 + nsCOMPtr<nsIPrompt> prompter; 1.1994 + mChannel->GetCallback(prompter); 1.1995 + if (prompter) { 1.1996 + nsCOMPtr<nsIRunnable> alertEvent; 1.1997 + if (mUseUTF8) { 1.1998 + alertEvent = new nsFtpAsyncAlert(prompter, 1.1999 + NS_ConvertUTF8toUTF16(mResponseMsg)); 1.2000 + } else { 1.2001 + alertEvent = new nsFtpAsyncAlert(prompter, 1.2002 + NS_ConvertASCIItoUTF16(mResponseMsg)); 1.2003 + } 1.2004 + NS_DispatchToMainThread(alertEvent, NS_DISPATCH_NORMAL); 1.2005 + } 1.2006 + } 1.2007 + 1.2008 + nsresult broadcastErrorCode = mControlStatus; 1.2009 + if (NS_SUCCEEDED(broadcastErrorCode)) 1.2010 + broadcastErrorCode = mInternalError; 1.2011 + 1.2012 + mInternalError = broadcastErrorCode; 1.2013 + 1.2014 + KillControlConnection(); 1.2015 + 1.2016 + // XXX This can fire before we are done loading data. Is that a problem? 1.2017 + OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0); 1.2018 + 1.2019 + if (NS_FAILED(broadcastErrorCode)) 1.2020 + CloseWithStatus(broadcastErrorCode); 1.2021 + 1.2022 + return NS_OK; 1.2023 +} 1.2024 + 1.2025 +nsresult 1.2026 +nsFtpState::SendFTPCommand(const nsCSubstring& command) 1.2027 +{ 1.2028 + NS_ASSERTION(mControlConnection, "null control connection"); 1.2029 + 1.2030 + // we don't want to log the password: 1.2031 + nsAutoCString logcmd(command); 1.2032 + if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS "))) 1.2033 + logcmd = "PASS xxxxx"; 1.2034 + 1.2035 + LOG(("FTP:(%x) writing \"%s\"\n", this, logcmd.get())); 1.2036 + 1.2037 + nsCOMPtr<nsIFTPEventSink> ftpSink; 1.2038 + mChannel->GetFTPEventSink(ftpSink); 1.2039 + if (ftpSink) 1.2040 + ftpSink->OnFTPControlLog(false, logcmd.get()); 1.2041 + 1.2042 + if (mControlConnection) 1.2043 + return mControlConnection->Write(command); 1.2044 + 1.2045 + return NS_ERROR_FAILURE; 1.2046 +} 1.2047 + 1.2048 +// Convert a unix-style filespec to VMS format 1.2049 +// /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt 1.2050 +// /foo/file.txt -> foo:[000000]file.txt 1.2051 +void 1.2052 +nsFtpState::ConvertFilespecToVMS(nsCString& fileString) 1.2053 +{ 1.2054 + int ntok=1; 1.2055 + char *t, *nextToken; 1.2056 + nsAutoCString fileStringCopy; 1.2057 + 1.2058 + // Get a writeable copy we can strtok with. 1.2059 + fileStringCopy = fileString; 1.2060 + t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken); 1.2061 + if (t) 1.2062 + while (nsCRT::strtok(nextToken, "/", &nextToken)) 1.2063 + ntok++; // count number of terms (tokens) 1.2064 + LOG(("FTP:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok)); 1.2065 + LOG(("FTP:(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get())); 1.2066 + 1.2067 + if (fileString.First() == '/') { 1.2068 + // absolute filespec 1.2069 + // / -> [] 1.2070 + // /a -> a (doesn't really make much sense) 1.2071 + // /a/b -> a:[000000]b 1.2072 + // /a/b/c -> a:[b]c 1.2073 + // /a/b/c/d -> a:[b.c]d 1.2074 + if (ntok == 1) { 1.2075 + if (fileString.Length() == 1) { 1.2076 + // Just a slash 1.2077 + fileString.Truncate(); 1.2078 + fileString.AppendLiteral("[]"); 1.2079 + } else { 1.2080 + // just copy the name part (drop the leading slash) 1.2081 + fileStringCopy = fileString; 1.2082 + fileString = Substring(fileStringCopy, 1, 1.2083 + fileStringCopy.Length()-1); 1.2084 + } 1.2085 + } else { 1.2086 + // Get another copy since the last one was written to. 1.2087 + fileStringCopy = fileString; 1.2088 + fileString.Truncate(); 1.2089 + fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), 1.2090 + "/", &nextToken)); 1.2091 + fileString.AppendLiteral(":["); 1.2092 + if (ntok > 2) { 1.2093 + for (int i=2; i<ntok; i++) { 1.2094 + if (i > 2) fileString.Append('.'); 1.2095 + fileString.Append(nsCRT::strtok(nextToken, 1.2096 + "/", &nextToken)); 1.2097 + } 1.2098 + } else { 1.2099 + fileString.AppendLiteral("000000"); 1.2100 + } 1.2101 + fileString.Append(']'); 1.2102 + fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); 1.2103 + } 1.2104 + } else { 1.2105 + // relative filespec 1.2106 + // a -> a 1.2107 + // a/b -> [.a]b 1.2108 + // a/b/c -> [.a.b]c 1.2109 + if (ntok == 1) { 1.2110 + // no slashes, just use the name as is 1.2111 + } else { 1.2112 + // Get another copy since the last one was written to. 1.2113 + fileStringCopy = fileString; 1.2114 + fileString.Truncate(); 1.2115 + fileString.AppendLiteral("[."); 1.2116 + fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), 1.2117 + "/", &nextToken)); 1.2118 + if (ntok > 2) { 1.2119 + for (int i=2; i<ntok; i++) { 1.2120 + fileString.Append('.'); 1.2121 + fileString.Append(nsCRT::strtok(nextToken, 1.2122 + "/", &nextToken)); 1.2123 + } 1.2124 + } 1.2125 + fileString.Append(']'); 1.2126 + fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); 1.2127 + } 1.2128 + } 1.2129 + LOG(("FTP:(%x) ConvertFilespecToVMS to: \"%s\"\n", this, fileString.get())); 1.2130 +} 1.2131 + 1.2132 +// Convert a unix-style dirspec to VMS format 1.2133 +// /foo/fred/barney/rubble -> foo:[fred.barney.rubble] 1.2134 +// /foo/fred -> foo:[fred] 1.2135 +// /foo -> foo:[000000] 1.2136 +// (null) -> (null) 1.2137 +void 1.2138 +nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec) 1.2139 +{ 1.2140 + LOG(("FTP:(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get())); 1.2141 + if (!dirSpec.IsEmpty()) { 1.2142 + if (dirSpec.Last() != '/') 1.2143 + dirSpec.Append('/'); 1.2144 + // we can use the filespec routine if we make it look like a file name 1.2145 + dirSpec.Append('x'); 1.2146 + ConvertFilespecToVMS(dirSpec); 1.2147 + dirSpec.Truncate(dirSpec.Length()-1); 1.2148 + } 1.2149 + LOG(("FTP:(%x) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get())); 1.2150 +} 1.2151 + 1.2152 +// Convert an absolute VMS style dirspec to UNIX format 1.2153 +void 1.2154 +nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec) 1.2155 +{ 1.2156 + LOG(("FTP:(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get())); 1.2157 + if (dirSpec.IsEmpty()) { 1.2158 + dirSpec.Insert('.', 0); 1.2159 + } else { 1.2160 + dirSpec.Insert('/', 0); 1.2161 + dirSpec.ReplaceSubstring(":[", "/"); 1.2162 + dirSpec.ReplaceChar('.', '/'); 1.2163 + dirSpec.ReplaceChar(']', '/'); 1.2164 + } 1.2165 + LOG(("FTP:(%x) ConvertDirspecFromVMS to: \"%s\"\n", this, dirSpec.get())); 1.2166 +} 1.2167 + 1.2168 +//----------------------------------------------------------------------------- 1.2169 + 1.2170 +NS_IMETHODIMP 1.2171 +nsFtpState::OnTransportStatus(nsITransport *transport, nsresult status, 1.2172 + uint64_t progress, uint64_t progressMax) 1.2173 +{ 1.2174 + // Mix signals from both the control and data connections. 1.2175 + 1.2176 + // Ignore data transfer events on the control connection. 1.2177 + if (mControlConnection && transport == mControlConnection->Transport()) { 1.2178 + switch (status) { 1.2179 + case NS_NET_STATUS_RESOLVING_HOST: 1.2180 + case NS_NET_STATUS_RESOLVED_HOST: 1.2181 + case NS_NET_STATUS_CONNECTING_TO: 1.2182 + case NS_NET_STATUS_CONNECTED_TO: 1.2183 + break; 1.2184 + default: 1.2185 + return NS_OK; 1.2186 + } 1.2187 + } 1.2188 + 1.2189 + // Ignore the progressMax value from the socket. We know the true size of 1.2190 + // the file based on the response from our SIZE request. Additionally, only 1.2191 + // report the max progress based on where we started/resumed. 1.2192 + mChannel->OnTransportStatus(nullptr, status, progress, 1.2193 + mFileSize - mChannel->StartPos()); 1.2194 + return NS_OK; 1.2195 +} 1.2196 + 1.2197 +//----------------------------------------------------------------------------- 1.2198 + 1.2199 + 1.2200 +NS_IMETHODIMP 1.2201 +nsFtpState::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, 1.2202 + nsCacheAccessMode access, 1.2203 + nsresult status) 1.2204 +{ 1.2205 + // We may have been closed while we were waiting for this cache entry. 1.2206 + if (IsClosed()) 1.2207 + return NS_OK; 1.2208 + 1.2209 + if (NS_SUCCEEDED(status) && entry) { 1.2210 + mDoomCache = true; 1.2211 + mCacheEntry = entry; 1.2212 + if (CanReadCacheEntry() && ReadCacheEntry()) { 1.2213 + mState = FTP_READ_CACHE; 1.2214 + return NS_OK; 1.2215 + } 1.2216 + } 1.2217 + 1.2218 + Connect(); 1.2219 + return NS_OK; 1.2220 +} 1.2221 + 1.2222 +//----------------------------------------------------------------------------- 1.2223 + 1.2224 +NS_IMETHODIMP 1.2225 +nsFtpState::OnCacheEntryDoomed(nsresult status) 1.2226 +{ 1.2227 + return NS_ERROR_NOT_IMPLEMENTED; 1.2228 +} 1.2229 + 1.2230 +//----------------------------------------------------------------------------- 1.2231 + 1.2232 +NS_IMETHODIMP 1.2233 +nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context) 1.2234 +{ 1.2235 + mStorReplyReceived = false; 1.2236 + return NS_OK; 1.2237 +} 1.2238 + 1.2239 +NS_IMETHODIMP 1.2240 +nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context, 1.2241 + nsresult status) 1.2242 +{ 1.2243 + mUploadRequest = nullptr; 1.2244 + 1.2245 + // Close() will be called when reply to STOR command is received 1.2246 + // see bug #389394 1.2247 + if (!mStorReplyReceived) 1.2248 + return NS_OK; 1.2249 + 1.2250 + // We're done uploading. Let our consumer know that we're done. 1.2251 + Close(); 1.2252 + return NS_OK; 1.2253 +} 1.2254 + 1.2255 +//----------------------------------------------------------------------------- 1.2256 + 1.2257 +NS_IMETHODIMP 1.2258 +nsFtpState::Available(uint64_t *result) 1.2259 +{ 1.2260 + if (mDataStream) 1.2261 + return mDataStream->Available(result); 1.2262 + 1.2263 + return nsBaseContentStream::Available(result); 1.2264 +} 1.2265 + 1.2266 +NS_IMETHODIMP 1.2267 +nsFtpState::ReadSegments(nsWriteSegmentFun writer, void *closure, 1.2268 + uint32_t count, uint32_t *result) 1.2269 +{ 1.2270 + // Insert a thunk here so that the input stream passed to the writer is this 1.2271 + // input stream instead of mDataStream. 1.2272 + 1.2273 + if (mDataStream) { 1.2274 + nsWriteSegmentThunk thunk = { this, writer, closure }; 1.2275 + nsresult rv; 1.2276 + rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count, 1.2277 + result); 1.2278 + if (NS_SUCCEEDED(rv)) { 1.2279 + CountRecvBytes(*result); 1.2280 + } 1.2281 + return rv; 1.2282 + } 1.2283 + 1.2284 + return nsBaseContentStream::ReadSegments(writer, closure, count, result); 1.2285 +} 1.2286 + 1.2287 +nsresult 1.2288 +nsFtpState::SaveNetworkStats(bool enforce) 1.2289 +{ 1.2290 +#ifdef MOZ_WIDGET_GONK 1.2291 + // Obtain app id 1.2292 + uint32_t appId; 1.2293 + bool isInBrowser; 1.2294 + NS_GetAppInfo(mChannel, &appId, &isInBrowser); 1.2295 + 1.2296 + // Check if active network and appid are valid. 1.2297 + if (!mActiveNetwork || appId == NECKO_NO_APP_ID) { 1.2298 + return NS_OK; 1.2299 + } 1.2300 + 1.2301 + if (mCountRecv <= 0) { 1.2302 + // There is no traffic, no need to save. 1.2303 + return NS_OK; 1.2304 + } 1.2305 + 1.2306 + // If |enforce| is false, the traffic amount is saved 1.2307 + // only when the total amount exceeds the predefined 1.2308 + // threshold. 1.2309 + if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) { 1.2310 + return NS_OK; 1.2311 + } 1.2312 + 1.2313 + // Create the event to save the network statistics. 1.2314 + // the event is then dispathed to the main thread. 1.2315 + nsRefPtr<nsRunnable> event = 1.2316 + new SaveNetworkStatsEvent(appId, mActiveNetwork, mCountRecv, 0, false); 1.2317 + NS_DispatchToMainThread(event); 1.2318 + 1.2319 + // Reset the counters after saving. 1.2320 + mCountRecv = 0; 1.2321 + 1.2322 + return NS_OK; 1.2323 +#else 1.2324 + return NS_ERROR_NOT_IMPLEMENTED; 1.2325 +#endif 1.2326 +} 1.2327 + 1.2328 +NS_IMETHODIMP 1.2329 +nsFtpState::CloseWithStatus(nsresult status) 1.2330 +{ 1.2331 + LOG(("FTP:(%p) close [%x]\n", this, status)); 1.2332 + 1.2333 + // Shutdown the control connection processing if we are being closed with an 1.2334 + // error. Note: This method may be called several times. 1.2335 + if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) { 1.2336 + if (NS_SUCCEEDED(mInternalError)) 1.2337 + mInternalError = status; 1.2338 + StopProcessing(); 1.2339 + } 1.2340 + 1.2341 + if (mUploadRequest) { 1.2342 + mUploadRequest->Cancel(NS_ERROR_ABORT); 1.2343 + mUploadRequest = nullptr; 1.2344 + } 1.2345 + 1.2346 + if (mDataTransport) { 1.2347 + // Save the network stats before data transport is closing. 1.2348 + SaveNetworkStats(true); 1.2349 + 1.2350 + // Shutdown the data transport. 1.2351 + mDataTransport->Close(NS_ERROR_ABORT); 1.2352 + mDataTransport = nullptr; 1.2353 + } 1.2354 + 1.2355 + mDataStream = nullptr; 1.2356 + if (mDoomCache && mCacheEntry) 1.2357 + mCacheEntry->AsyncDoom(nullptr); 1.2358 + mCacheEntry = nullptr; 1.2359 + 1.2360 + return nsBaseContentStream::CloseWithStatus(status); 1.2361 +} 1.2362 + 1.2363 +static nsresult 1.2364 +CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsIChannel **newChannel) 1.2365 +{ 1.2366 + nsresult rv; 1.2367 + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); 1.2368 + if (NS_FAILED(rv)) 1.2369 + return rv; 1.2370 + 1.2371 + nsCOMPtr<nsIProtocolHandler> handler; 1.2372 + rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler)); 1.2373 + if (NS_FAILED(rv)) 1.2374 + return rv; 1.2375 + 1.2376 + nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv); 1.2377 + if (NS_FAILED(rv)) 1.2378 + return rv; 1.2379 + 1.2380 + nsCOMPtr<nsIURI> uri; 1.2381 + channel->GetURI(getter_AddRefs(uri)); 1.2382 + 1.2383 + return pph->NewProxiedChannel(uri, pi, 0, nullptr, newChannel); 1.2384 +} 1.2385 + 1.2386 +NS_IMETHODIMP 1.2387 +nsFtpState::OnProxyAvailable(nsICancelable *request, nsIChannel *channel, 1.2388 + nsIProxyInfo *pi, nsresult status) 1.2389 +{ 1.2390 + mProxyRequest = nullptr; 1.2391 + 1.2392 + // failed status code just implies DIRECT processing 1.2393 + 1.2394 + if (NS_SUCCEEDED(status)) { 1.2395 + nsAutoCString type; 1.2396 + if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) { 1.2397 + // Proxy the FTP url via HTTP 1.2398 + // This would have been easier to just return a HTTP channel directly 1.2399 + // from nsIIOService::NewChannelFromURI(), but the proxy type cannot 1.2400 + // be reliabliy determined synchronously without jank due to pac, etc.. 1.2401 + LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this)); 1.2402 + 1.2403 + nsCOMPtr<nsIChannel> newChannel; 1.2404 + if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi, 1.2405 + getter_AddRefs(newChannel))) && 1.2406 + NS_SUCCEEDED(mChannel->Redirect(newChannel, 1.2407 + nsIChannelEventSink::REDIRECT_INTERNAL, 1.2408 + true))) { 1.2409 + LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this)); 1.2410 + return NS_OK; 1.2411 + } 1.2412 + } 1.2413 + else if (pi) { 1.2414 + // Proxy using the FTP protocol routed through a socks proxy 1.2415 + LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this)); 1.2416 + mChannel->SetProxyInfo(pi); 1.2417 + } 1.2418 + } 1.2419 + 1.2420 + if (mDeferredCallbackPending) { 1.2421 + mDeferredCallbackPending = false; 1.2422 + OnCallbackPending(); 1.2423 + } 1.2424 + return NS_OK; 1.2425 +} 1.2426 + 1.2427 +void 1.2428 +nsFtpState::OnCallbackPending() 1.2429 +{ 1.2430 + // If this is the first call, then see if we could use the cache. If we 1.2431 + // aren't going to read from (or write to) the cache, then just proceed to 1.2432 + // connect to the server. 1.2433 + 1.2434 + if (mState == FTP_INIT) { 1.2435 + if (mProxyRequest) { 1.2436 + mDeferredCallbackPending = true; 1.2437 + return; 1.2438 + } 1.2439 + 1.2440 + if (CheckCache()) { 1.2441 + mState = FTP_WAIT_CACHE; 1.2442 + return; 1.2443 + } 1.2444 + if (mCacheEntry && CanReadCacheEntry() && ReadCacheEntry()) { 1.2445 + mState = FTP_READ_CACHE; 1.2446 + return; 1.2447 + } 1.2448 + Connect(); 1.2449 + } else if (mDataStream) { 1.2450 + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); 1.2451 + } 1.2452 +} 1.2453 + 1.2454 +bool 1.2455 +nsFtpState::ReadCacheEntry() 1.2456 +{ 1.2457 + NS_ASSERTION(mCacheEntry, "should have a cache entry"); 1.2458 + 1.2459 + // make sure the channel knows wassup 1.2460 + SetContentType(); 1.2461 + 1.2462 + nsXPIDLCString serverType; 1.2463 + mCacheEntry->GetMetaDataElement("servertype", getter_Copies(serverType)); 1.2464 + nsAutoCString serverNum(serverType.get()); 1.2465 + nsresult err; 1.2466 + mServerType = serverNum.ToInteger(&err); 1.2467 + 1.2468 + nsXPIDLCString charset; 1.2469 + mCacheEntry->GetMetaDataElement("useUTF8", getter_Copies(charset)); 1.2470 + const char *useUTF8 = charset.get(); 1.2471 + if (useUTF8 && atoi(useUTF8) == 1) 1.2472 + mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); 1.2473 + 1.2474 + mChannel->PushStreamConverter("text/ftp-dir", 1.2475 + APPLICATION_HTTP_INDEX_FORMAT); 1.2476 + 1.2477 + mChannel->SetEntityID(EmptyCString()); 1.2478 + 1.2479 + if (NS_FAILED(OpenCacheDataStream())) 1.2480 + return false; 1.2481 + 1.2482 + if (mDataStream && HasPendingCallback()) 1.2483 + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); 1.2484 + 1.2485 + mDoomCache = false; 1.2486 + return true; 1.2487 +} 1.2488 + 1.2489 +bool 1.2490 +nsFtpState::CheckCache() 1.2491 +{ 1.2492 + // This function is responsible for setting mCacheEntry if there is a cache 1.2493 + // entry that we can use. It returns true if we end up waiting for access 1.2494 + // to the cache. 1.2495 + 1.2496 + // In some cases, we don't want to use the cache: 1.2497 + if (mChannel->UploadStream() || mChannel->ResumeRequested()) 1.2498 + return false; 1.2499 + 1.2500 + nsCOMPtr<nsICacheService> cache = do_GetService(NS_CACHESERVICE_CONTRACTID); 1.2501 + if (!cache) 1.2502 + return false; 1.2503 + 1.2504 + bool isPrivate = NS_UsePrivateBrowsing(mChannel); 1.2505 + const char* sessionName = isPrivate ? "FTP-private" : "FTP"; 1.2506 + nsCacheStoragePolicy policy = 1.2507 + isPrivate ? nsICache::STORE_IN_MEMORY : nsICache::STORE_ANYWHERE; 1.2508 + nsCOMPtr<nsICacheSession> session; 1.2509 + cache->CreateSession(sessionName, 1.2510 + policy, 1.2511 + nsICache::STREAM_BASED, 1.2512 + getter_AddRefs(session)); 1.2513 + if (!session) 1.2514 + return false; 1.2515 + session->SetDoomEntriesIfExpired(false); 1.2516 + session->SetIsPrivate(isPrivate); 1.2517 + 1.2518 + // Set cache access requested: 1.2519 + nsCacheAccessMode accessReq; 1.2520 + if (NS_IsOffline()) { 1.2521 + accessReq = nsICache::ACCESS_READ; // can only read 1.2522 + } else if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) { 1.2523 + accessReq = nsICache::ACCESS_WRITE; // replace cache entry 1.2524 + } else { 1.2525 + accessReq = nsICache::ACCESS_READ_WRITE; // normal browsing 1.2526 + } 1.2527 + 1.2528 + // Check to see if we are not allowed to write to the cache: 1.2529 + if (mChannel->HasLoadFlag(nsIRequest::INHIBIT_CACHING)) { 1.2530 + accessReq &= ~nsICache::ACCESS_WRITE; 1.2531 + if (accessReq == nsICache::ACCESS_NONE) 1.2532 + return false; 1.2533 + } 1.2534 + 1.2535 + // Generate cache key (remove trailing #ref if any): 1.2536 + nsAutoCString key; 1.2537 + mChannel->URI()->GetAsciiSpec(key); 1.2538 + int32_t pos = key.RFindChar('#'); 1.2539 + if (pos != kNotFound) 1.2540 + key.Truncate(pos); 1.2541 + NS_ENSURE_FALSE(key.IsEmpty(), false); 1.2542 + 1.2543 + nsresult rv = session->AsyncOpenCacheEntry(key, accessReq, this, false); 1.2544 + return NS_SUCCEEDED(rv); 1.2545 + 1.2546 +}