Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
michael@0 | 1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include <ctype.h> |
michael@0 | 8 | |
michael@0 | 9 | #include "prprf.h" |
michael@0 | 10 | #include "prlog.h" |
michael@0 | 11 | #include "prtime.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "nsIOService.h" |
michael@0 | 14 | #include "nsFTPChannel.h" |
michael@0 | 15 | #include "nsFtpConnectionThread.h" |
michael@0 | 16 | #include "nsFtpControlConnection.h" |
michael@0 | 17 | #include "nsFtpProtocolHandler.h" |
michael@0 | 18 | #include "netCore.h" |
michael@0 | 19 | #include "nsCRT.h" |
michael@0 | 20 | #include "nsEscape.h" |
michael@0 | 21 | #include "nsMimeTypes.h" |
michael@0 | 22 | #include "nsNetUtil.h" |
michael@0 | 23 | #include "nsThreadUtils.h" |
michael@0 | 24 | #include "nsStreamUtils.h" |
michael@0 | 25 | #include "nsICacheService.h" |
michael@0 | 26 | #include "nsIURL.h" |
michael@0 | 27 | #include "nsISocketTransport.h" |
michael@0 | 28 | #include "nsIStreamListenerTee.h" |
michael@0 | 29 | #include "nsIPrefService.h" |
michael@0 | 30 | #include "nsIPrefBranch.h" |
michael@0 | 31 | #include "nsIStringBundle.h" |
michael@0 | 32 | #include "nsAuthInformationHolder.h" |
michael@0 | 33 | #include "nsIProtocolProxyService.h" |
michael@0 | 34 | #include "nsICancelable.h" |
michael@0 | 35 | #include "nsICacheEntryDescriptor.h" |
michael@0 | 36 | #include "nsIOutputStream.h" |
michael@0 | 37 | #include "nsIPrompt.h" |
michael@0 | 38 | #include "nsIProtocolHandler.h" |
michael@0 | 39 | #include "nsIProxyInfo.h" |
michael@0 | 40 | #include "nsIRunnable.h" |
michael@0 | 41 | #include "nsISocketTransportService.h" |
michael@0 | 42 | #include "nsIURI.h" |
michael@0 | 43 | #include "nsICacheSession.h" |
michael@0 | 44 | |
michael@0 | 45 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 46 | #include "NetStatistics.h" |
michael@0 | 47 | #endif |
michael@0 | 48 | |
michael@0 | 49 | #if defined(PR_LOGGING) |
michael@0 | 50 | extern PRLogModuleInfo* gFTPLog; |
michael@0 | 51 | #endif |
michael@0 | 52 | #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) |
michael@0 | 53 | #define LOG_ALWAYS(args) PR_LOG(gFTPLog, PR_LOG_ALWAYS, args) |
michael@0 | 54 | |
michael@0 | 55 | using namespace mozilla::net; |
michael@0 | 56 | |
michael@0 | 57 | // remove FTP parameters (starting with ";") from the path |
michael@0 | 58 | static void |
michael@0 | 59 | removeParamsFromPath(nsCString& path) |
michael@0 | 60 | { |
michael@0 | 61 | int32_t index = path.FindChar(';'); |
michael@0 | 62 | if (index >= 0) { |
michael@0 | 63 | path.SetLength(index); |
michael@0 | 64 | } |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | NS_IMPL_ISUPPORTS_INHERITED(nsFtpState, |
michael@0 | 68 | nsBaseContentStream, |
michael@0 | 69 | nsIInputStreamCallback, |
michael@0 | 70 | nsITransportEventSink, |
michael@0 | 71 | nsICacheListener, |
michael@0 | 72 | nsIRequestObserver, |
michael@0 | 73 | nsIProtocolProxyCallback) |
michael@0 | 74 | |
michael@0 | 75 | nsFtpState::nsFtpState() |
michael@0 | 76 | : nsBaseContentStream(true) |
michael@0 | 77 | , mState(FTP_INIT) |
michael@0 | 78 | , mNextState(FTP_S_USER) |
michael@0 | 79 | , mKeepRunning(true) |
michael@0 | 80 | , mReceivedControlData(false) |
michael@0 | 81 | , mTryingCachedControl(false) |
michael@0 | 82 | , mRETRFailed(false) |
michael@0 | 83 | , mFileSize(UINT64_MAX) |
michael@0 | 84 | , mServerType(FTP_GENERIC_TYPE) |
michael@0 | 85 | , mAction(GET) |
michael@0 | 86 | , mAnonymous(true) |
michael@0 | 87 | , mRetryPass(false) |
michael@0 | 88 | , mStorReplyReceived(false) |
michael@0 | 89 | , mInternalError(NS_OK) |
michael@0 | 90 | , mReconnectAndLoginAgain(false) |
michael@0 | 91 | , mCacheConnection(true) |
michael@0 | 92 | , mPort(21) |
michael@0 | 93 | , mAddressChecked(false) |
michael@0 | 94 | , mServerIsIPv6(false) |
michael@0 | 95 | , mUseUTF8(false) |
michael@0 | 96 | , mControlStatus(NS_OK) |
michael@0 | 97 | , mDeferredCallbackPending(false) |
michael@0 | 98 | { |
michael@0 | 99 | LOG_ALWAYS(("FTP:(%x) nsFtpState created", this)); |
michael@0 | 100 | |
michael@0 | 101 | // make sure handler stays around |
michael@0 | 102 | NS_ADDREF(gFtpHandler); |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | nsFtpState::~nsFtpState() |
michael@0 | 106 | { |
michael@0 | 107 | LOG_ALWAYS(("FTP:(%x) nsFtpState destroyed", this)); |
michael@0 | 108 | |
michael@0 | 109 | if (mProxyRequest) |
michael@0 | 110 | mProxyRequest->Cancel(NS_ERROR_FAILURE); |
michael@0 | 111 | |
michael@0 | 112 | // release reference to handler |
michael@0 | 113 | nsFtpProtocolHandler *handler = gFtpHandler; |
michael@0 | 114 | NS_RELEASE(handler); |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | // nsIInputStreamCallback implementation |
michael@0 | 118 | NS_IMETHODIMP |
michael@0 | 119 | nsFtpState::OnInputStreamReady(nsIAsyncInputStream *aInStream) |
michael@0 | 120 | { |
michael@0 | 121 | LOG(("FTP:(%p) data stream ready\n", this)); |
michael@0 | 122 | |
michael@0 | 123 | // We are receiving a notification from our data stream, so just forward it |
michael@0 | 124 | // on to our stream callback. |
michael@0 | 125 | if (HasPendingCallback()) |
michael@0 | 126 | DispatchCallbackSync(); |
michael@0 | 127 | |
michael@0 | 128 | return NS_OK; |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | void |
michael@0 | 132 | nsFtpState::OnControlDataAvailable(const char *aData, uint32_t aDataLen) |
michael@0 | 133 | { |
michael@0 | 134 | LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen)); |
michael@0 | 135 | mControlConnection->WaitData(this); // queue up another call |
michael@0 | 136 | |
michael@0 | 137 | if (!mReceivedControlData) { |
michael@0 | 138 | // parameter can be null cause the channel fills them in. |
michael@0 | 139 | OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0); |
michael@0 | 140 | mReceivedControlData = true; |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | // Sometimes we can get two responses in the same packet, eg from LIST. |
michael@0 | 144 | // So we need to parse the response line by line |
michael@0 | 145 | |
michael@0 | 146 | nsCString buffer = mControlReadCarryOverBuf; |
michael@0 | 147 | |
michael@0 | 148 | // Clear the carryover buf - if we still don't have a line, then it will |
michael@0 | 149 | // be reappended below |
michael@0 | 150 | mControlReadCarryOverBuf.Truncate(); |
michael@0 | 151 | |
michael@0 | 152 | buffer.Append(aData, aDataLen); |
michael@0 | 153 | |
michael@0 | 154 | const char* currLine = buffer.get(); |
michael@0 | 155 | while (*currLine && mKeepRunning) { |
michael@0 | 156 | int32_t eolLength = strcspn(currLine, CRLF); |
michael@0 | 157 | int32_t currLineLength = strlen(currLine); |
michael@0 | 158 | |
michael@0 | 159 | // if currLine is empty or only contains CR or LF, then bail. we can |
michael@0 | 160 | // sometimes get an ODA event with the full response line + CR without |
michael@0 | 161 | // the trailing LF. the trailing LF might come in the next ODA event. |
michael@0 | 162 | // because we are happy enough to process a response line ending only |
michael@0 | 163 | // in CR, we need to take care to discard the extra LF (bug 191220). |
michael@0 | 164 | if (eolLength == 0 && currLineLength <= 1) |
michael@0 | 165 | break; |
michael@0 | 166 | |
michael@0 | 167 | if (eolLength == currLineLength) { |
michael@0 | 168 | mControlReadCarryOverBuf.Assign(currLine); |
michael@0 | 169 | break; |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | // Append the current segment, including the LF |
michael@0 | 173 | nsAutoCString line; |
michael@0 | 174 | int32_t crlfLength = 0; |
michael@0 | 175 | |
michael@0 | 176 | if ((currLineLength > eolLength) && |
michael@0 | 177 | (currLine[eolLength] == nsCRT::CR) && |
michael@0 | 178 | (currLine[eolLength+1] == nsCRT::LF)) { |
michael@0 | 179 | crlfLength = 2; // CR +LF |
michael@0 | 180 | } else { |
michael@0 | 181 | crlfLength = 1; // + LF or CR |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | line.Assign(currLine, eolLength + crlfLength); |
michael@0 | 185 | |
michael@0 | 186 | // Does this start with a response code? |
michael@0 | 187 | bool startNum = (line.Length() >= 3 && |
michael@0 | 188 | isdigit(line[0]) && |
michael@0 | 189 | isdigit(line[1]) && |
michael@0 | 190 | isdigit(line[2])); |
michael@0 | 191 | |
michael@0 | 192 | if (mResponseMsg.IsEmpty()) { |
michael@0 | 193 | // If we get here, then we know that we have a complete line, and |
michael@0 | 194 | // that it is the first one |
michael@0 | 195 | |
michael@0 | 196 | NS_ASSERTION(line.Length() > 4 && startNum, |
michael@0 | 197 | "Read buffer doesn't include response code"); |
michael@0 | 198 | |
michael@0 | 199 | mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get()); |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | mResponseMsg.Append(line); |
michael@0 | 203 | |
michael@0 | 204 | // This is the last line if its 3 numbers followed by a space |
michael@0 | 205 | if (startNum && line[3] == ' ') { |
michael@0 | 206 | // yup. last line, let's move on. |
michael@0 | 207 | if (mState == mNextState) { |
michael@0 | 208 | NS_ERROR("ftp read state mixup"); |
michael@0 | 209 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 210 | mState = FTP_ERROR; |
michael@0 | 211 | } else { |
michael@0 | 212 | mState = mNextState; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | nsCOMPtr<nsIFTPEventSink> ftpSink; |
michael@0 | 216 | mChannel->GetFTPEventSink(ftpSink); |
michael@0 | 217 | if (ftpSink) |
michael@0 | 218 | ftpSink->OnFTPControlLog(true, mResponseMsg.get()); |
michael@0 | 219 | |
michael@0 | 220 | nsresult rv = Process(); |
michael@0 | 221 | mResponseMsg.Truncate(); |
michael@0 | 222 | if (NS_FAILED(rv)) { |
michael@0 | 223 | CloseWithStatus(rv); |
michael@0 | 224 | return; |
michael@0 | 225 | } |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | currLine = currLine + eolLength + crlfLength; |
michael@0 | 229 | } |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | void |
michael@0 | 233 | nsFtpState::OnControlError(nsresult status) |
michael@0 | 234 | { |
michael@0 | 235 | NS_ASSERTION(NS_FAILED(status), "expecting error condition"); |
michael@0 | 236 | |
michael@0 | 237 | LOG(("FTP:(%p) CC(%p) error [%x was-cached=%u]\n", |
michael@0 | 238 | this, mControlConnection.get(), status, mTryingCachedControl)); |
michael@0 | 239 | |
michael@0 | 240 | mControlStatus = status; |
michael@0 | 241 | if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) { |
michael@0 | 242 | mReconnectAndLoginAgain = false; |
michael@0 | 243 | mAnonymous = false; |
michael@0 | 244 | mControlStatus = NS_OK; |
michael@0 | 245 | Connect(); |
michael@0 | 246 | } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) { |
michael@0 | 247 | mTryingCachedControl = false; |
michael@0 | 248 | Connect(); |
michael@0 | 249 | } else { |
michael@0 | 250 | CloseWithStatus(status); |
michael@0 | 251 | } |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | nsresult |
michael@0 | 255 | nsFtpState::EstablishControlConnection() |
michael@0 | 256 | { |
michael@0 | 257 | NS_ASSERTION(!mControlConnection, "we already have a control connection"); |
michael@0 | 258 | |
michael@0 | 259 | nsresult rv; |
michael@0 | 260 | |
michael@0 | 261 | LOG(("FTP:(%x) trying cached control\n", this)); |
michael@0 | 262 | |
michael@0 | 263 | // Look to see if we can use a cached control connection: |
michael@0 | 264 | nsFtpControlConnection *connection = nullptr; |
michael@0 | 265 | // Don't use cached control if anonymous (bug #473371) |
michael@0 | 266 | if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) |
michael@0 | 267 | gFtpHandler->RemoveConnection(mChannel->URI(), &connection); |
michael@0 | 268 | |
michael@0 | 269 | if (connection) { |
michael@0 | 270 | mControlConnection.swap(connection); |
michael@0 | 271 | if (mControlConnection->IsAlive()) |
michael@0 | 272 | { |
michael@0 | 273 | // set stream listener of the control connection to be us. |
michael@0 | 274 | mControlConnection->WaitData(this); |
michael@0 | 275 | |
michael@0 | 276 | // read cached variables into us. |
michael@0 | 277 | mServerType = mControlConnection->mServerType; |
michael@0 | 278 | mPassword = mControlConnection->mPassword; |
michael@0 | 279 | mPwd = mControlConnection->mPwd; |
michael@0 | 280 | mUseUTF8 = mControlConnection->mUseUTF8; |
michael@0 | 281 | mTryingCachedControl = true; |
michael@0 | 282 | |
michael@0 | 283 | // we have to set charset to connection if server supports utf-8 |
michael@0 | 284 | if (mUseUTF8) |
michael@0 | 285 | mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); |
michael@0 | 286 | |
michael@0 | 287 | // we're already connected to this server, skip login. |
michael@0 | 288 | mState = FTP_S_PASV; |
michael@0 | 289 | mResponseCode = 530; // assume the control connection was dropped. |
michael@0 | 290 | mControlStatus = NS_OK; |
michael@0 | 291 | mReceivedControlData = false; // For this request, we have not. |
michael@0 | 292 | |
michael@0 | 293 | // if we succeed, return. Otherwise, we need to create a transport |
michael@0 | 294 | rv = mControlConnection->Connect(mChannel->ProxyInfo(), this); |
michael@0 | 295 | if (NS_SUCCEEDED(rv)) |
michael@0 | 296 | return rv; |
michael@0 | 297 | } |
michael@0 | 298 | LOG(("FTP:(%p) cached CC(%p) is unusable\n", this, |
michael@0 | 299 | mControlConnection.get())); |
michael@0 | 300 | |
michael@0 | 301 | mControlConnection->WaitData(nullptr); |
michael@0 | 302 | mControlConnection = nullptr; |
michael@0 | 303 | } |
michael@0 | 304 | |
michael@0 | 305 | LOG(("FTP:(%p) creating CC\n", this)); |
michael@0 | 306 | |
michael@0 | 307 | mState = FTP_READ_BUF; |
michael@0 | 308 | mNextState = FTP_S_USER; |
michael@0 | 309 | |
michael@0 | 310 | nsAutoCString host; |
michael@0 | 311 | rv = mChannel->URI()->GetAsciiHost(host); |
michael@0 | 312 | if (NS_FAILED(rv)) |
michael@0 | 313 | return rv; |
michael@0 | 314 | |
michael@0 | 315 | mControlConnection = new nsFtpControlConnection(host, mPort); |
michael@0 | 316 | if (!mControlConnection) |
michael@0 | 317 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 318 | |
michael@0 | 319 | rv = mControlConnection->Connect(mChannel->ProxyInfo(), this); |
michael@0 | 320 | if (NS_FAILED(rv)) { |
michael@0 | 321 | LOG(("FTP:(%p) CC(%p) failed to connect [rv=%x]\n", this, |
michael@0 | 322 | mControlConnection.get(), rv)); |
michael@0 | 323 | mControlConnection = nullptr; |
michael@0 | 324 | return rv; |
michael@0 | 325 | } |
michael@0 | 326 | |
michael@0 | 327 | return mControlConnection->WaitData(this); |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | void |
michael@0 | 331 | nsFtpState::MoveToNextState(FTP_STATE nextState) |
michael@0 | 332 | { |
michael@0 | 333 | if (NS_FAILED(mInternalError)) { |
michael@0 | 334 | mState = FTP_ERROR; |
michael@0 | 335 | LOG(("FTP:(%x) FAILED (%x)\n", this, mInternalError)); |
michael@0 | 336 | } else { |
michael@0 | 337 | mState = FTP_READ_BUF; |
michael@0 | 338 | mNextState = nextState; |
michael@0 | 339 | } |
michael@0 | 340 | } |
michael@0 | 341 | |
michael@0 | 342 | nsresult |
michael@0 | 343 | nsFtpState::Process() |
michael@0 | 344 | { |
michael@0 | 345 | nsresult rv = NS_OK; |
michael@0 | 346 | bool processingRead = true; |
michael@0 | 347 | |
michael@0 | 348 | while (mKeepRunning && processingRead) { |
michael@0 | 349 | switch (mState) { |
michael@0 | 350 | case FTP_COMMAND_CONNECT: |
michael@0 | 351 | KillControlConnection(); |
michael@0 | 352 | LOG(("FTP:(%p) establishing CC", this)); |
michael@0 | 353 | mInternalError = EstablishControlConnection(); // sets mState |
michael@0 | 354 | if (NS_FAILED(mInternalError)) { |
michael@0 | 355 | mState = FTP_ERROR; |
michael@0 | 356 | LOG(("FTP:(%p) FAILED\n", this)); |
michael@0 | 357 | } else { |
michael@0 | 358 | LOG(("FTP:(%p) SUCCEEDED\n", this)); |
michael@0 | 359 | } |
michael@0 | 360 | break; |
michael@0 | 361 | |
michael@0 | 362 | case FTP_READ_BUF: |
michael@0 | 363 | LOG(("FTP:(%p) Waiting for CC(%p)\n", this, |
michael@0 | 364 | mControlConnection.get())); |
michael@0 | 365 | processingRead = false; |
michael@0 | 366 | break; |
michael@0 | 367 | |
michael@0 | 368 | case FTP_ERROR: // xx needs more work to handle dropped control connection cases |
michael@0 | 369 | if ((mTryingCachedControl && mResponseCode == 530 && |
michael@0 | 370 | mInternalError == NS_ERROR_FTP_PASV) || |
michael@0 | 371 | (mResponseCode == 425 && |
michael@0 | 372 | mInternalError == NS_ERROR_FTP_PASV)) { |
michael@0 | 373 | // The user was logged out during an pasv operation |
michael@0 | 374 | // we want to restart this request with a new control |
michael@0 | 375 | // channel. |
michael@0 | 376 | mState = FTP_COMMAND_CONNECT; |
michael@0 | 377 | } else if (mResponseCode == 421 && |
michael@0 | 378 | mInternalError != NS_ERROR_FTP_LOGIN) { |
michael@0 | 379 | // The command channel dropped for some reason. |
michael@0 | 380 | // Fire it back up, unless we were trying to login |
michael@0 | 381 | // in which case the server might just be telling us |
michael@0 | 382 | // that the max number of users has been reached... |
michael@0 | 383 | mState = FTP_COMMAND_CONNECT; |
michael@0 | 384 | } else if (mAnonymous && |
michael@0 | 385 | mInternalError == NS_ERROR_FTP_LOGIN) { |
michael@0 | 386 | // If the login was anonymous, and it failed, try again with a username |
michael@0 | 387 | // Don't reuse old control connection, see #386167 |
michael@0 | 388 | mAnonymous = false; |
michael@0 | 389 | mState = FTP_COMMAND_CONNECT; |
michael@0 | 390 | } else { |
michael@0 | 391 | LOG(("FTP:(%x) FTP_ERROR - calling StopProcessing\n", this)); |
michael@0 | 392 | rv = StopProcessing(); |
michael@0 | 393 | NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); |
michael@0 | 394 | processingRead = false; |
michael@0 | 395 | } |
michael@0 | 396 | break; |
michael@0 | 397 | |
michael@0 | 398 | case FTP_COMPLETE: |
michael@0 | 399 | LOG(("FTP:(%x) COMPLETE\n", this)); |
michael@0 | 400 | rv = StopProcessing(); |
michael@0 | 401 | NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); |
michael@0 | 402 | processingRead = false; |
michael@0 | 403 | break; |
michael@0 | 404 | |
michael@0 | 405 | // USER |
michael@0 | 406 | case FTP_S_USER: |
michael@0 | 407 | rv = S_user(); |
michael@0 | 408 | |
michael@0 | 409 | if (NS_FAILED(rv)) |
michael@0 | 410 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 411 | |
michael@0 | 412 | MoveToNextState(FTP_R_USER); |
michael@0 | 413 | break; |
michael@0 | 414 | |
michael@0 | 415 | case FTP_R_USER: |
michael@0 | 416 | mState = R_user(); |
michael@0 | 417 | |
michael@0 | 418 | if (FTP_ERROR == mState) |
michael@0 | 419 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 420 | |
michael@0 | 421 | break; |
michael@0 | 422 | // PASS |
michael@0 | 423 | case FTP_S_PASS: |
michael@0 | 424 | rv = S_pass(); |
michael@0 | 425 | |
michael@0 | 426 | if (NS_FAILED(rv)) |
michael@0 | 427 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 428 | |
michael@0 | 429 | MoveToNextState(FTP_R_PASS); |
michael@0 | 430 | break; |
michael@0 | 431 | |
michael@0 | 432 | case FTP_R_PASS: |
michael@0 | 433 | mState = R_pass(); |
michael@0 | 434 | |
michael@0 | 435 | if (FTP_ERROR == mState) |
michael@0 | 436 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 437 | |
michael@0 | 438 | break; |
michael@0 | 439 | // ACCT |
michael@0 | 440 | case FTP_S_ACCT: |
michael@0 | 441 | rv = S_acct(); |
michael@0 | 442 | |
michael@0 | 443 | if (NS_FAILED(rv)) |
michael@0 | 444 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 445 | |
michael@0 | 446 | MoveToNextState(FTP_R_ACCT); |
michael@0 | 447 | break; |
michael@0 | 448 | |
michael@0 | 449 | case FTP_R_ACCT: |
michael@0 | 450 | mState = R_acct(); |
michael@0 | 451 | |
michael@0 | 452 | if (FTP_ERROR == mState) |
michael@0 | 453 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 454 | |
michael@0 | 455 | break; |
michael@0 | 456 | |
michael@0 | 457 | // SYST |
michael@0 | 458 | case FTP_S_SYST: |
michael@0 | 459 | rv = S_syst(); |
michael@0 | 460 | |
michael@0 | 461 | if (NS_FAILED(rv)) |
michael@0 | 462 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 463 | |
michael@0 | 464 | MoveToNextState(FTP_R_SYST); |
michael@0 | 465 | break; |
michael@0 | 466 | |
michael@0 | 467 | case FTP_R_SYST: |
michael@0 | 468 | mState = R_syst(); |
michael@0 | 469 | |
michael@0 | 470 | if (FTP_ERROR == mState) |
michael@0 | 471 | mInternalError = NS_ERROR_FTP_LOGIN; |
michael@0 | 472 | |
michael@0 | 473 | break; |
michael@0 | 474 | |
michael@0 | 475 | // TYPE |
michael@0 | 476 | case FTP_S_TYPE: |
michael@0 | 477 | rv = S_type(); |
michael@0 | 478 | |
michael@0 | 479 | if (NS_FAILED(rv)) |
michael@0 | 480 | mInternalError = rv; |
michael@0 | 481 | |
michael@0 | 482 | MoveToNextState(FTP_R_TYPE); |
michael@0 | 483 | break; |
michael@0 | 484 | |
michael@0 | 485 | case FTP_R_TYPE: |
michael@0 | 486 | mState = R_type(); |
michael@0 | 487 | |
michael@0 | 488 | if (FTP_ERROR == mState) |
michael@0 | 489 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 490 | |
michael@0 | 491 | break; |
michael@0 | 492 | // CWD |
michael@0 | 493 | case FTP_S_CWD: |
michael@0 | 494 | rv = S_cwd(); |
michael@0 | 495 | |
michael@0 | 496 | if (NS_FAILED(rv)) |
michael@0 | 497 | mInternalError = NS_ERROR_FTP_CWD; |
michael@0 | 498 | |
michael@0 | 499 | MoveToNextState(FTP_R_CWD); |
michael@0 | 500 | break; |
michael@0 | 501 | |
michael@0 | 502 | case FTP_R_CWD: |
michael@0 | 503 | mState = R_cwd(); |
michael@0 | 504 | |
michael@0 | 505 | if (FTP_ERROR == mState) |
michael@0 | 506 | mInternalError = NS_ERROR_FTP_CWD; |
michael@0 | 507 | break; |
michael@0 | 508 | |
michael@0 | 509 | // LIST |
michael@0 | 510 | case FTP_S_LIST: |
michael@0 | 511 | rv = S_list(); |
michael@0 | 512 | |
michael@0 | 513 | if (rv == NS_ERROR_NOT_RESUMABLE) { |
michael@0 | 514 | mInternalError = rv; |
michael@0 | 515 | } else if (NS_FAILED(rv)) { |
michael@0 | 516 | mInternalError = NS_ERROR_FTP_CWD; |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | MoveToNextState(FTP_R_LIST); |
michael@0 | 520 | break; |
michael@0 | 521 | |
michael@0 | 522 | case FTP_R_LIST: |
michael@0 | 523 | mState = R_list(); |
michael@0 | 524 | |
michael@0 | 525 | if (FTP_ERROR == mState) |
michael@0 | 526 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 527 | |
michael@0 | 528 | break; |
michael@0 | 529 | |
michael@0 | 530 | // SIZE |
michael@0 | 531 | case FTP_S_SIZE: |
michael@0 | 532 | rv = S_size(); |
michael@0 | 533 | |
michael@0 | 534 | if (NS_FAILED(rv)) |
michael@0 | 535 | mInternalError = rv; |
michael@0 | 536 | |
michael@0 | 537 | MoveToNextState(FTP_R_SIZE); |
michael@0 | 538 | break; |
michael@0 | 539 | |
michael@0 | 540 | case FTP_R_SIZE: |
michael@0 | 541 | mState = R_size(); |
michael@0 | 542 | |
michael@0 | 543 | if (FTP_ERROR == mState) |
michael@0 | 544 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 545 | |
michael@0 | 546 | break; |
michael@0 | 547 | |
michael@0 | 548 | // REST |
michael@0 | 549 | case FTP_S_REST: |
michael@0 | 550 | rv = S_rest(); |
michael@0 | 551 | |
michael@0 | 552 | if (NS_FAILED(rv)) |
michael@0 | 553 | mInternalError = rv; |
michael@0 | 554 | |
michael@0 | 555 | MoveToNextState(FTP_R_REST); |
michael@0 | 556 | break; |
michael@0 | 557 | |
michael@0 | 558 | case FTP_R_REST: |
michael@0 | 559 | mState = R_rest(); |
michael@0 | 560 | |
michael@0 | 561 | if (FTP_ERROR == mState) |
michael@0 | 562 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 563 | |
michael@0 | 564 | break; |
michael@0 | 565 | |
michael@0 | 566 | // MDTM |
michael@0 | 567 | case FTP_S_MDTM: |
michael@0 | 568 | rv = S_mdtm(); |
michael@0 | 569 | if (NS_FAILED(rv)) |
michael@0 | 570 | mInternalError = rv; |
michael@0 | 571 | MoveToNextState(FTP_R_MDTM); |
michael@0 | 572 | break; |
michael@0 | 573 | |
michael@0 | 574 | case FTP_R_MDTM: |
michael@0 | 575 | mState = R_mdtm(); |
michael@0 | 576 | |
michael@0 | 577 | // Don't want to overwrite a more explicit status code |
michael@0 | 578 | if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) |
michael@0 | 579 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 580 | |
michael@0 | 581 | break; |
michael@0 | 582 | |
michael@0 | 583 | // RETR |
michael@0 | 584 | case FTP_S_RETR: |
michael@0 | 585 | rv = S_retr(); |
michael@0 | 586 | |
michael@0 | 587 | if (NS_FAILED(rv)) |
michael@0 | 588 | mInternalError = rv; |
michael@0 | 589 | |
michael@0 | 590 | MoveToNextState(FTP_R_RETR); |
michael@0 | 591 | break; |
michael@0 | 592 | |
michael@0 | 593 | case FTP_R_RETR: |
michael@0 | 594 | |
michael@0 | 595 | mState = R_retr(); |
michael@0 | 596 | |
michael@0 | 597 | if (FTP_ERROR == mState) |
michael@0 | 598 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 599 | |
michael@0 | 600 | break; |
michael@0 | 601 | |
michael@0 | 602 | // STOR |
michael@0 | 603 | case FTP_S_STOR: |
michael@0 | 604 | rv = S_stor(); |
michael@0 | 605 | |
michael@0 | 606 | if (NS_FAILED(rv)) |
michael@0 | 607 | mInternalError = rv; |
michael@0 | 608 | |
michael@0 | 609 | MoveToNextState(FTP_R_STOR); |
michael@0 | 610 | break; |
michael@0 | 611 | |
michael@0 | 612 | case FTP_R_STOR: |
michael@0 | 613 | mState = R_stor(); |
michael@0 | 614 | |
michael@0 | 615 | if (FTP_ERROR == mState) |
michael@0 | 616 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 617 | |
michael@0 | 618 | break; |
michael@0 | 619 | |
michael@0 | 620 | // PASV |
michael@0 | 621 | case FTP_S_PASV: |
michael@0 | 622 | rv = S_pasv(); |
michael@0 | 623 | |
michael@0 | 624 | if (NS_FAILED(rv)) |
michael@0 | 625 | mInternalError = NS_ERROR_FTP_PASV; |
michael@0 | 626 | |
michael@0 | 627 | MoveToNextState(FTP_R_PASV); |
michael@0 | 628 | break; |
michael@0 | 629 | |
michael@0 | 630 | case FTP_R_PASV: |
michael@0 | 631 | mState = R_pasv(); |
michael@0 | 632 | |
michael@0 | 633 | if (FTP_ERROR == mState) |
michael@0 | 634 | mInternalError = NS_ERROR_FTP_PASV; |
michael@0 | 635 | |
michael@0 | 636 | break; |
michael@0 | 637 | |
michael@0 | 638 | // PWD |
michael@0 | 639 | case FTP_S_PWD: |
michael@0 | 640 | rv = S_pwd(); |
michael@0 | 641 | |
michael@0 | 642 | if (NS_FAILED(rv)) |
michael@0 | 643 | mInternalError = NS_ERROR_FTP_PWD; |
michael@0 | 644 | |
michael@0 | 645 | MoveToNextState(FTP_R_PWD); |
michael@0 | 646 | break; |
michael@0 | 647 | |
michael@0 | 648 | case FTP_R_PWD: |
michael@0 | 649 | mState = R_pwd(); |
michael@0 | 650 | |
michael@0 | 651 | if (FTP_ERROR == mState) |
michael@0 | 652 | mInternalError = NS_ERROR_FTP_PWD; |
michael@0 | 653 | |
michael@0 | 654 | break; |
michael@0 | 655 | |
michael@0 | 656 | // FEAT for RFC2640 support |
michael@0 | 657 | case FTP_S_FEAT: |
michael@0 | 658 | rv = S_feat(); |
michael@0 | 659 | |
michael@0 | 660 | if (NS_FAILED(rv)) |
michael@0 | 661 | mInternalError = rv; |
michael@0 | 662 | |
michael@0 | 663 | MoveToNextState(FTP_R_FEAT); |
michael@0 | 664 | break; |
michael@0 | 665 | |
michael@0 | 666 | case FTP_R_FEAT: |
michael@0 | 667 | mState = R_feat(); |
michael@0 | 668 | |
michael@0 | 669 | // Don't want to overwrite a more explicit status code |
michael@0 | 670 | if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) |
michael@0 | 671 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 672 | break; |
michael@0 | 673 | |
michael@0 | 674 | // OPTS for some non-RFC2640-compliant servers support |
michael@0 | 675 | case FTP_S_OPTS: |
michael@0 | 676 | rv = S_opts(); |
michael@0 | 677 | |
michael@0 | 678 | if (NS_FAILED(rv)) |
michael@0 | 679 | mInternalError = rv; |
michael@0 | 680 | |
michael@0 | 681 | MoveToNextState(FTP_R_OPTS); |
michael@0 | 682 | break; |
michael@0 | 683 | |
michael@0 | 684 | case FTP_R_OPTS: |
michael@0 | 685 | mState = R_opts(); |
michael@0 | 686 | |
michael@0 | 687 | // Don't want to overwrite a more explicit status code |
michael@0 | 688 | if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) |
michael@0 | 689 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 690 | break; |
michael@0 | 691 | |
michael@0 | 692 | default: |
michael@0 | 693 | ; |
michael@0 | 694 | |
michael@0 | 695 | } |
michael@0 | 696 | } |
michael@0 | 697 | |
michael@0 | 698 | return rv; |
michael@0 | 699 | } |
michael@0 | 700 | |
michael@0 | 701 | /////////////////////////////////// |
michael@0 | 702 | // STATE METHODS |
michael@0 | 703 | /////////////////////////////////// |
michael@0 | 704 | nsresult |
michael@0 | 705 | nsFtpState::S_user() { |
michael@0 | 706 | // some servers on connect send us a 421 or 521. (84525) (141784) |
michael@0 | 707 | if ((mResponseCode == 421) || (mResponseCode == 521)) |
michael@0 | 708 | return NS_ERROR_FAILURE; |
michael@0 | 709 | |
michael@0 | 710 | nsresult rv; |
michael@0 | 711 | nsAutoCString usernameStr("USER "); |
michael@0 | 712 | |
michael@0 | 713 | mResponseMsg = ""; |
michael@0 | 714 | |
michael@0 | 715 | if (mAnonymous) { |
michael@0 | 716 | mReconnectAndLoginAgain = true; |
michael@0 | 717 | usernameStr.AppendLiteral("anonymous"); |
michael@0 | 718 | } else { |
michael@0 | 719 | mReconnectAndLoginAgain = false; |
michael@0 | 720 | if (mUsername.IsEmpty()) { |
michael@0 | 721 | |
michael@0 | 722 | // No prompt for anonymous requests (bug #473371) |
michael@0 | 723 | if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) |
michael@0 | 724 | return NS_ERROR_FAILURE; |
michael@0 | 725 | |
michael@0 | 726 | nsCOMPtr<nsIAuthPrompt2> prompter; |
michael@0 | 727 | NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel), |
michael@0 | 728 | getter_AddRefs(prompter)); |
michael@0 | 729 | if (!prompter) |
michael@0 | 730 | return NS_ERROR_NOT_INITIALIZED; |
michael@0 | 731 | |
michael@0 | 732 | nsRefPtr<nsAuthInformationHolder> info = |
michael@0 | 733 | new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST, |
michael@0 | 734 | EmptyString(), |
michael@0 | 735 | EmptyCString()); |
michael@0 | 736 | |
michael@0 | 737 | bool retval; |
michael@0 | 738 | rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, |
michael@0 | 739 | info, &retval); |
michael@0 | 740 | |
michael@0 | 741 | // if the user canceled or didn't supply a username we want to fail |
michael@0 | 742 | if (NS_FAILED(rv) || !retval || info->User().IsEmpty()) |
michael@0 | 743 | return NS_ERROR_FAILURE; |
michael@0 | 744 | |
michael@0 | 745 | mUsername = info->User(); |
michael@0 | 746 | mPassword = info->Password(); |
michael@0 | 747 | } |
michael@0 | 748 | // XXX Is UTF-8 the best choice? |
michael@0 | 749 | AppendUTF16toUTF8(mUsername, usernameStr); |
michael@0 | 750 | } |
michael@0 | 751 | usernameStr.Append(CRLF); |
michael@0 | 752 | |
michael@0 | 753 | return SendFTPCommand(usernameStr); |
michael@0 | 754 | } |
michael@0 | 755 | |
michael@0 | 756 | FTP_STATE |
michael@0 | 757 | nsFtpState::R_user() { |
michael@0 | 758 | mReconnectAndLoginAgain = false; |
michael@0 | 759 | if (mResponseCode/100 == 3) { |
michael@0 | 760 | // send off the password |
michael@0 | 761 | return FTP_S_PASS; |
michael@0 | 762 | } |
michael@0 | 763 | if (mResponseCode/100 == 2) { |
michael@0 | 764 | // no password required, we're already logged in |
michael@0 | 765 | return FTP_S_SYST; |
michael@0 | 766 | } |
michael@0 | 767 | if (mResponseCode/100 == 5) { |
michael@0 | 768 | // problem logging in. typically this means the server |
michael@0 | 769 | // has reached it's user limit. |
michael@0 | 770 | return FTP_ERROR; |
michael@0 | 771 | } |
michael@0 | 772 | // LOGIN FAILED |
michael@0 | 773 | return FTP_ERROR; |
michael@0 | 774 | } |
michael@0 | 775 | |
michael@0 | 776 | |
michael@0 | 777 | nsresult |
michael@0 | 778 | nsFtpState::S_pass() { |
michael@0 | 779 | nsresult rv; |
michael@0 | 780 | nsAutoCString passwordStr("PASS "); |
michael@0 | 781 | |
michael@0 | 782 | mResponseMsg = ""; |
michael@0 | 783 | |
michael@0 | 784 | if (mAnonymous) { |
michael@0 | 785 | if (!mPassword.IsEmpty()) { |
michael@0 | 786 | // XXX Is UTF-8 the best choice? |
michael@0 | 787 | AppendUTF16toUTF8(mPassword, passwordStr); |
michael@0 | 788 | } else { |
michael@0 | 789 | nsXPIDLCString anonPassword; |
michael@0 | 790 | bool useRealEmail = false; |
michael@0 | 791 | nsCOMPtr<nsIPrefBranch> prefs = |
michael@0 | 792 | do_GetService(NS_PREFSERVICE_CONTRACTID); |
michael@0 | 793 | if (prefs) { |
michael@0 | 794 | rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail); |
michael@0 | 795 | if (NS_SUCCEEDED(rv) && useRealEmail) { |
michael@0 | 796 | prefs->GetCharPref("network.ftp.anonymous_password", |
michael@0 | 797 | getter_Copies(anonPassword)); |
michael@0 | 798 | } |
michael@0 | 799 | } |
michael@0 | 800 | if (!anonPassword.IsEmpty()) { |
michael@0 | 801 | passwordStr.AppendASCII(anonPassword); |
michael@0 | 802 | } else { |
michael@0 | 803 | // We need to default to a valid email address - bug 101027 |
michael@0 | 804 | // example.com is reserved (rfc2606), so use that |
michael@0 | 805 | passwordStr.AppendLiteral("mozilla@example.com"); |
michael@0 | 806 | } |
michael@0 | 807 | } |
michael@0 | 808 | } else { |
michael@0 | 809 | if (mPassword.IsEmpty() || mRetryPass) { |
michael@0 | 810 | |
michael@0 | 811 | // No prompt for anonymous requests (bug #473371) |
michael@0 | 812 | if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) |
michael@0 | 813 | return NS_ERROR_FAILURE; |
michael@0 | 814 | |
michael@0 | 815 | nsCOMPtr<nsIAuthPrompt2> prompter; |
michael@0 | 816 | NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel), |
michael@0 | 817 | getter_AddRefs(prompter)); |
michael@0 | 818 | if (!prompter) |
michael@0 | 819 | return NS_ERROR_NOT_INITIALIZED; |
michael@0 | 820 | |
michael@0 | 821 | nsRefPtr<nsAuthInformationHolder> info = |
michael@0 | 822 | new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST | |
michael@0 | 823 | nsIAuthInformation::ONLY_PASSWORD, |
michael@0 | 824 | EmptyString(), |
michael@0 | 825 | EmptyCString()); |
michael@0 | 826 | |
michael@0 | 827 | info->SetUserInternal(mUsername); |
michael@0 | 828 | |
michael@0 | 829 | bool retval; |
michael@0 | 830 | rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, |
michael@0 | 831 | info, &retval); |
michael@0 | 832 | |
michael@0 | 833 | // we want to fail if the user canceled. Note here that if they want |
michael@0 | 834 | // a blank password, we will pass it along. |
michael@0 | 835 | if (NS_FAILED(rv) || !retval) |
michael@0 | 836 | return NS_ERROR_FAILURE; |
michael@0 | 837 | |
michael@0 | 838 | mPassword = info->Password(); |
michael@0 | 839 | } |
michael@0 | 840 | // XXX Is UTF-8 the best choice? |
michael@0 | 841 | AppendUTF16toUTF8(mPassword, passwordStr); |
michael@0 | 842 | } |
michael@0 | 843 | passwordStr.Append(CRLF); |
michael@0 | 844 | |
michael@0 | 845 | return SendFTPCommand(passwordStr); |
michael@0 | 846 | } |
michael@0 | 847 | |
michael@0 | 848 | FTP_STATE |
michael@0 | 849 | nsFtpState::R_pass() { |
michael@0 | 850 | if (mResponseCode/100 == 3) { |
michael@0 | 851 | // send account info |
michael@0 | 852 | return FTP_S_ACCT; |
michael@0 | 853 | } |
michael@0 | 854 | if (mResponseCode/100 == 2) { |
michael@0 | 855 | // logged in |
michael@0 | 856 | return FTP_S_SYST; |
michael@0 | 857 | } |
michael@0 | 858 | if (mResponseCode == 503) { |
michael@0 | 859 | // start over w/ the user command. |
michael@0 | 860 | // note: the password was successful, and it's stored in mPassword |
michael@0 | 861 | mRetryPass = false; |
michael@0 | 862 | return FTP_S_USER; |
michael@0 | 863 | } |
michael@0 | 864 | if (mResponseCode/100 == 5 || mResponseCode==421) { |
michael@0 | 865 | // There is no difference between a too-many-users error, |
michael@0 | 866 | // a wrong-password error, or any other sort of error |
michael@0 | 867 | |
michael@0 | 868 | if (!mAnonymous) |
michael@0 | 869 | mRetryPass = true; |
michael@0 | 870 | |
michael@0 | 871 | return FTP_ERROR; |
michael@0 | 872 | } |
michael@0 | 873 | // unexpected response code |
michael@0 | 874 | return FTP_ERROR; |
michael@0 | 875 | } |
michael@0 | 876 | |
michael@0 | 877 | nsresult |
michael@0 | 878 | nsFtpState::S_pwd() { |
michael@0 | 879 | return SendFTPCommand(NS_LITERAL_CSTRING("PWD" CRLF)); |
michael@0 | 880 | } |
michael@0 | 881 | |
michael@0 | 882 | FTP_STATE |
michael@0 | 883 | nsFtpState::R_pwd() { |
michael@0 | 884 | // Error response to PWD command isn't fatal, but don't cache the connection |
michael@0 | 885 | // if CWD command is sent since correct mPwd is needed for further requests. |
michael@0 | 886 | if (mResponseCode/100 != 2) |
michael@0 | 887 | return FTP_S_TYPE; |
michael@0 | 888 | |
michael@0 | 889 | nsAutoCString respStr(mResponseMsg); |
michael@0 | 890 | int32_t pos = respStr.FindChar('"'); |
michael@0 | 891 | if (pos > -1) { |
michael@0 | 892 | respStr.Cut(0, pos+1); |
michael@0 | 893 | pos = respStr.FindChar('"'); |
michael@0 | 894 | if (pos > -1) { |
michael@0 | 895 | respStr.Truncate(pos); |
michael@0 | 896 | if (mServerType == FTP_VMS_TYPE) |
michael@0 | 897 | ConvertDirspecFromVMS(respStr); |
michael@0 | 898 | if (respStr.IsEmpty() || respStr.Last() != '/') |
michael@0 | 899 | respStr.Append('/'); |
michael@0 | 900 | mPwd = respStr; |
michael@0 | 901 | } |
michael@0 | 902 | } |
michael@0 | 903 | return FTP_S_TYPE; |
michael@0 | 904 | } |
michael@0 | 905 | |
michael@0 | 906 | nsresult |
michael@0 | 907 | nsFtpState::S_syst() { |
michael@0 | 908 | return SendFTPCommand(NS_LITERAL_CSTRING("SYST" CRLF)); |
michael@0 | 909 | } |
michael@0 | 910 | |
michael@0 | 911 | FTP_STATE |
michael@0 | 912 | nsFtpState::R_syst() { |
michael@0 | 913 | if (mResponseCode/100 == 2) { |
michael@0 | 914 | if (( mResponseMsg.Find("L8") > -1) || |
michael@0 | 915 | ( mResponseMsg.Find("UNIX") > -1) || |
michael@0 | 916 | ( mResponseMsg.Find("BSD") > -1) || |
michael@0 | 917 | ( mResponseMsg.Find("MACOS Peter's Server") > -1) || |
michael@0 | 918 | ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) || |
michael@0 | 919 | ( mResponseMsg.Find("MVS") > -1) || |
michael@0 | 920 | ( mResponseMsg.Find("OS/390") > -1) || |
michael@0 | 921 | ( mResponseMsg.Find("OS/400") > -1)) { |
michael@0 | 922 | mServerType = FTP_UNIX_TYPE; |
michael@0 | 923 | } else if (( mResponseMsg.Find("WIN32", true) > -1) || |
michael@0 | 924 | ( mResponseMsg.Find("windows", true) > -1)) { |
michael@0 | 925 | mServerType = FTP_NT_TYPE; |
michael@0 | 926 | } else if (mResponseMsg.Find("OS/2", true) > -1) { |
michael@0 | 927 | mServerType = FTP_OS2_TYPE; |
michael@0 | 928 | } else if (mResponseMsg.Find("VMS", true) > -1) { |
michael@0 | 929 | mServerType = FTP_VMS_TYPE; |
michael@0 | 930 | } else { |
michael@0 | 931 | NS_ERROR("Server type list format unrecognized."); |
michael@0 | 932 | // Guessing causes crashes. |
michael@0 | 933 | // (Of course, the parsing code should be more robust...) |
michael@0 | 934 | nsCOMPtr<nsIStringBundleService> bundleService = |
michael@0 | 935 | do_GetService(NS_STRINGBUNDLE_CONTRACTID); |
michael@0 | 936 | if (!bundleService) |
michael@0 | 937 | return FTP_ERROR; |
michael@0 | 938 | |
michael@0 | 939 | nsCOMPtr<nsIStringBundle> bundle; |
michael@0 | 940 | nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL, |
michael@0 | 941 | getter_AddRefs(bundle)); |
michael@0 | 942 | if (NS_FAILED(rv)) |
michael@0 | 943 | return FTP_ERROR; |
michael@0 | 944 | |
michael@0 | 945 | char16_t* ucs2Response = ToNewUnicode(mResponseMsg); |
michael@0 | 946 | const char16_t *formatStrings[1] = { ucs2Response }; |
michael@0 | 947 | NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer"); |
michael@0 | 948 | |
michael@0 | 949 | nsXPIDLString formattedString; |
michael@0 | 950 | rv = bundle->FormatStringFromName(name.get(), formatStrings, 1, |
michael@0 | 951 | getter_Copies(formattedString)); |
michael@0 | 952 | nsMemory::Free(ucs2Response); |
michael@0 | 953 | if (NS_FAILED(rv)) |
michael@0 | 954 | return FTP_ERROR; |
michael@0 | 955 | |
michael@0 | 956 | // TODO(darin): this code should not be dictating UI like this! |
michael@0 | 957 | nsCOMPtr<nsIPrompt> prompter; |
michael@0 | 958 | mChannel->GetCallback(prompter); |
michael@0 | 959 | if (prompter) |
michael@0 | 960 | prompter->Alert(nullptr, formattedString.get()); |
michael@0 | 961 | |
michael@0 | 962 | // since we just alerted the user, clear mResponseMsg, |
michael@0 | 963 | // which is displayed to the user. |
michael@0 | 964 | mResponseMsg = ""; |
michael@0 | 965 | return FTP_ERROR; |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | return FTP_S_FEAT; |
michael@0 | 969 | } |
michael@0 | 970 | |
michael@0 | 971 | if (mResponseCode/100 == 5) { |
michael@0 | 972 | // server didn't like the SYST command. Probably (500, 501, 502) |
michael@0 | 973 | // No clue. We will just hope it is UNIX type server. |
michael@0 | 974 | mServerType = FTP_UNIX_TYPE; |
michael@0 | 975 | |
michael@0 | 976 | return FTP_S_FEAT; |
michael@0 | 977 | } |
michael@0 | 978 | return FTP_ERROR; |
michael@0 | 979 | } |
michael@0 | 980 | |
michael@0 | 981 | nsresult |
michael@0 | 982 | nsFtpState::S_acct() { |
michael@0 | 983 | return SendFTPCommand(NS_LITERAL_CSTRING("ACCT noaccount" CRLF)); |
michael@0 | 984 | } |
michael@0 | 985 | |
michael@0 | 986 | FTP_STATE |
michael@0 | 987 | nsFtpState::R_acct() { |
michael@0 | 988 | if (mResponseCode/100 == 2) |
michael@0 | 989 | return FTP_S_SYST; |
michael@0 | 990 | |
michael@0 | 991 | return FTP_ERROR; |
michael@0 | 992 | } |
michael@0 | 993 | |
michael@0 | 994 | nsresult |
michael@0 | 995 | nsFtpState::S_type() { |
michael@0 | 996 | return SendFTPCommand(NS_LITERAL_CSTRING("TYPE I" CRLF)); |
michael@0 | 997 | } |
michael@0 | 998 | |
michael@0 | 999 | FTP_STATE |
michael@0 | 1000 | nsFtpState::R_type() { |
michael@0 | 1001 | if (mResponseCode/100 != 2) |
michael@0 | 1002 | return FTP_ERROR; |
michael@0 | 1003 | |
michael@0 | 1004 | return FTP_S_PASV; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | nsresult |
michael@0 | 1008 | nsFtpState::S_cwd() { |
michael@0 | 1009 | // Don't cache the connection if PWD command failed |
michael@0 | 1010 | if (mPwd.IsEmpty()) |
michael@0 | 1011 | mCacheConnection = false; |
michael@0 | 1012 | |
michael@0 | 1013 | nsAutoCString cwdStr; |
michael@0 | 1014 | if (mAction != PUT) |
michael@0 | 1015 | cwdStr = mPath; |
michael@0 | 1016 | if (cwdStr.IsEmpty() || cwdStr.First() != '/') |
michael@0 | 1017 | cwdStr.Insert(mPwd,0); |
michael@0 | 1018 | if (mServerType == FTP_VMS_TYPE) |
michael@0 | 1019 | ConvertDirspecToVMS(cwdStr); |
michael@0 | 1020 | cwdStr.Insert("CWD ",0); |
michael@0 | 1021 | cwdStr.Append(CRLF); |
michael@0 | 1022 | |
michael@0 | 1023 | return SendFTPCommand(cwdStr); |
michael@0 | 1024 | } |
michael@0 | 1025 | |
michael@0 | 1026 | FTP_STATE |
michael@0 | 1027 | nsFtpState::R_cwd() { |
michael@0 | 1028 | if (mResponseCode/100 == 2) { |
michael@0 | 1029 | if (mAction == PUT) |
michael@0 | 1030 | return FTP_S_STOR; |
michael@0 | 1031 | |
michael@0 | 1032 | return FTP_S_LIST; |
michael@0 | 1033 | } |
michael@0 | 1034 | |
michael@0 | 1035 | return FTP_ERROR; |
michael@0 | 1036 | } |
michael@0 | 1037 | |
michael@0 | 1038 | nsresult |
michael@0 | 1039 | nsFtpState::S_size() { |
michael@0 | 1040 | nsAutoCString sizeBuf(mPath); |
michael@0 | 1041 | if (sizeBuf.IsEmpty() || sizeBuf.First() != '/') |
michael@0 | 1042 | sizeBuf.Insert(mPwd,0); |
michael@0 | 1043 | if (mServerType == FTP_VMS_TYPE) |
michael@0 | 1044 | ConvertFilespecToVMS(sizeBuf); |
michael@0 | 1045 | sizeBuf.Insert("SIZE ",0); |
michael@0 | 1046 | sizeBuf.Append(CRLF); |
michael@0 | 1047 | |
michael@0 | 1048 | return SendFTPCommand(sizeBuf); |
michael@0 | 1049 | } |
michael@0 | 1050 | |
michael@0 | 1051 | FTP_STATE |
michael@0 | 1052 | nsFtpState::R_size() { |
michael@0 | 1053 | if (mResponseCode/100 == 2) { |
michael@0 | 1054 | PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize); |
michael@0 | 1055 | mChannel->SetContentLength(mFileSize); |
michael@0 | 1056 | } |
michael@0 | 1057 | |
michael@0 | 1058 | // We may want to be able to resume this |
michael@0 | 1059 | return FTP_S_MDTM; |
michael@0 | 1060 | } |
michael@0 | 1061 | |
michael@0 | 1062 | nsresult |
michael@0 | 1063 | nsFtpState::S_mdtm() { |
michael@0 | 1064 | nsAutoCString mdtmBuf(mPath); |
michael@0 | 1065 | if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/') |
michael@0 | 1066 | mdtmBuf.Insert(mPwd,0); |
michael@0 | 1067 | if (mServerType == FTP_VMS_TYPE) |
michael@0 | 1068 | ConvertFilespecToVMS(mdtmBuf); |
michael@0 | 1069 | mdtmBuf.Insert("MDTM ",0); |
michael@0 | 1070 | mdtmBuf.Append(CRLF); |
michael@0 | 1071 | |
michael@0 | 1072 | return SendFTPCommand(mdtmBuf); |
michael@0 | 1073 | } |
michael@0 | 1074 | |
michael@0 | 1075 | FTP_STATE |
michael@0 | 1076 | nsFtpState::R_mdtm() { |
michael@0 | 1077 | if (mResponseCode == 213) { |
michael@0 | 1078 | mResponseMsg.Cut(0,4); |
michael@0 | 1079 | mResponseMsg.Trim(" \t\r\n"); |
michael@0 | 1080 | // yyyymmddhhmmss |
michael@0 | 1081 | if (mResponseMsg.Length() != 14) { |
michael@0 | 1082 | NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response"); |
michael@0 | 1083 | } else { |
michael@0 | 1084 | mModTime = mResponseMsg; |
michael@0 | 1085 | |
michael@0 | 1086 | // Save lastModified time for downloaded files. |
michael@0 | 1087 | nsAutoCString timeString; |
michael@0 | 1088 | nsresult error; |
michael@0 | 1089 | PRExplodedTime exTime; |
michael@0 | 1090 | |
michael@0 | 1091 | mResponseMsg.Mid(timeString, 0, 4); |
michael@0 | 1092 | exTime.tm_year = timeString.ToInteger(&error); |
michael@0 | 1093 | mResponseMsg.Mid(timeString, 4, 2); |
michael@0 | 1094 | exTime.tm_month = timeString.ToInteger(&error) - 1; //january = 0 |
michael@0 | 1095 | mResponseMsg.Mid(timeString, 6, 2); |
michael@0 | 1096 | exTime.tm_mday = timeString.ToInteger(&error); |
michael@0 | 1097 | mResponseMsg.Mid(timeString, 8, 2); |
michael@0 | 1098 | exTime.tm_hour = timeString.ToInteger(&error); |
michael@0 | 1099 | mResponseMsg.Mid(timeString, 10, 2); |
michael@0 | 1100 | exTime.tm_min = timeString.ToInteger(&error); |
michael@0 | 1101 | mResponseMsg.Mid(timeString, 12, 2); |
michael@0 | 1102 | exTime.tm_sec = timeString.ToInteger(&error); |
michael@0 | 1103 | exTime.tm_usec = 0; |
michael@0 | 1104 | |
michael@0 | 1105 | exTime.tm_params.tp_gmt_offset = 0; |
michael@0 | 1106 | exTime.tm_params.tp_dst_offset = 0; |
michael@0 | 1107 | |
michael@0 | 1108 | PR_NormalizeTime(&exTime, PR_GMTParameters); |
michael@0 | 1109 | exTime.tm_params = PR_LocalTimeParameters(&exTime); |
michael@0 | 1110 | |
michael@0 | 1111 | PRTime time = PR_ImplodeTime(&exTime); |
michael@0 | 1112 | (void)mChannel->SetLastModifiedTime(time); |
michael@0 | 1113 | } |
michael@0 | 1114 | } |
michael@0 | 1115 | |
michael@0 | 1116 | nsCString entityID; |
michael@0 | 1117 | entityID.Truncate(); |
michael@0 | 1118 | entityID.AppendInt(int64_t(mFileSize)); |
michael@0 | 1119 | entityID.Append('/'); |
michael@0 | 1120 | entityID.Append(mModTime); |
michael@0 | 1121 | mChannel->SetEntityID(entityID); |
michael@0 | 1122 | |
michael@0 | 1123 | // We weren't asked to resume |
michael@0 | 1124 | if (!mChannel->ResumeRequested()) |
michael@0 | 1125 | return FTP_S_RETR; |
michael@0 | 1126 | |
michael@0 | 1127 | //if (our entityID == supplied one (if any)) |
michael@0 | 1128 | if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID)) |
michael@0 | 1129 | return FTP_S_REST; |
michael@0 | 1130 | |
michael@0 | 1131 | mInternalError = NS_ERROR_ENTITY_CHANGED; |
michael@0 | 1132 | mResponseMsg.Truncate(); |
michael@0 | 1133 | return FTP_ERROR; |
michael@0 | 1134 | } |
michael@0 | 1135 | |
michael@0 | 1136 | nsresult |
michael@0 | 1137 | nsFtpState::SetContentType() |
michael@0 | 1138 | { |
michael@0 | 1139 | // FTP directory URLs don't always end in a slash. Make sure they do. |
michael@0 | 1140 | // This check needs to be here rather than a more obvious place |
michael@0 | 1141 | // (e.g. LIST command processing) so that it ensures the terminating |
michael@0 | 1142 | // slash is appended for the new request case, as well as the case |
michael@0 | 1143 | // where the URL is being loaded from the cache. |
michael@0 | 1144 | |
michael@0 | 1145 | if (!mPath.IsEmpty() && mPath.Last() != '/') { |
michael@0 | 1146 | nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI())); |
michael@0 | 1147 | nsAutoCString filePath; |
michael@0 | 1148 | if(NS_SUCCEEDED(url->GetFilePath(filePath))) { |
michael@0 | 1149 | filePath.Append('/'); |
michael@0 | 1150 | url->SetFilePath(filePath); |
michael@0 | 1151 | } |
michael@0 | 1152 | } |
michael@0 | 1153 | return mChannel->SetContentType( |
michael@0 | 1154 | NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT)); |
michael@0 | 1155 | } |
michael@0 | 1156 | |
michael@0 | 1157 | nsresult |
michael@0 | 1158 | nsFtpState::S_list() { |
michael@0 | 1159 | nsresult rv = SetContentType(); |
michael@0 | 1160 | if (NS_FAILED(rv)) |
michael@0 | 1161 | // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has |
michael@0 | 1162 | // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109) |
michael@0 | 1163 | return (nsresult)FTP_ERROR; |
michael@0 | 1164 | |
michael@0 | 1165 | rv = mChannel->PushStreamConverter("text/ftp-dir", |
michael@0 | 1166 | APPLICATION_HTTP_INDEX_FORMAT); |
michael@0 | 1167 | if (NS_FAILED(rv)) { |
michael@0 | 1168 | // clear mResponseMsg which is displayed to the user. |
michael@0 | 1169 | // TODO: we should probably set this to something meaningful. |
michael@0 | 1170 | mResponseMsg = ""; |
michael@0 | 1171 | return rv; |
michael@0 | 1172 | } |
michael@0 | 1173 | |
michael@0 | 1174 | if (mCacheEntry) { |
michael@0 | 1175 | // save off the server type if we are caching. |
michael@0 | 1176 | nsAutoCString serverType; |
michael@0 | 1177 | serverType.AppendInt(mServerType); |
michael@0 | 1178 | mCacheEntry->SetMetaDataElement("servertype", serverType.get()); |
michael@0 | 1179 | |
michael@0 | 1180 | nsAutoCString useUTF8; |
michael@0 | 1181 | useUTF8.AppendInt(mUseUTF8); |
michael@0 | 1182 | mCacheEntry->SetMetaDataElement("useUTF8", useUTF8.get()); |
michael@0 | 1183 | |
michael@0 | 1184 | // open cache entry for writing, and configure it to receive data. |
michael@0 | 1185 | if (NS_FAILED(InstallCacheListener())) { |
michael@0 | 1186 | mCacheEntry->AsyncDoom(nullptr); |
michael@0 | 1187 | mCacheEntry = nullptr; |
michael@0 | 1188 | } |
michael@0 | 1189 | } |
michael@0 | 1190 | |
michael@0 | 1191 | // dir listings aren't resumable |
michael@0 | 1192 | NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE); |
michael@0 | 1193 | |
michael@0 | 1194 | mChannel->SetEntityID(EmptyCString()); |
michael@0 | 1195 | |
michael@0 | 1196 | const char *listString; |
michael@0 | 1197 | if (mServerType == FTP_VMS_TYPE) { |
michael@0 | 1198 | listString = "LIST *.*;0" CRLF; |
michael@0 | 1199 | } else { |
michael@0 | 1200 | listString = "LIST" CRLF; |
michael@0 | 1201 | } |
michael@0 | 1202 | |
michael@0 | 1203 | return SendFTPCommand(nsDependentCString(listString)); |
michael@0 | 1204 | } |
michael@0 | 1205 | |
michael@0 | 1206 | FTP_STATE |
michael@0 | 1207 | nsFtpState::R_list() { |
michael@0 | 1208 | if (mResponseCode/100 == 1) { |
michael@0 | 1209 | // OK, time to start reading from the data connection. |
michael@0 | 1210 | if (mDataStream && HasPendingCallback()) |
michael@0 | 1211 | mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); |
michael@0 | 1212 | return FTP_READ_BUF; |
michael@0 | 1213 | } |
michael@0 | 1214 | |
michael@0 | 1215 | if (mResponseCode/100 == 2) { |
michael@0 | 1216 | //(DONE) |
michael@0 | 1217 | mNextState = FTP_COMPLETE; |
michael@0 | 1218 | mDoomCache = false; |
michael@0 | 1219 | return FTP_COMPLETE; |
michael@0 | 1220 | } |
michael@0 | 1221 | return FTP_ERROR; |
michael@0 | 1222 | } |
michael@0 | 1223 | |
michael@0 | 1224 | nsresult |
michael@0 | 1225 | nsFtpState::S_retr() { |
michael@0 | 1226 | nsAutoCString retrStr(mPath); |
michael@0 | 1227 | if (retrStr.IsEmpty() || retrStr.First() != '/') |
michael@0 | 1228 | retrStr.Insert(mPwd,0); |
michael@0 | 1229 | if (mServerType == FTP_VMS_TYPE) |
michael@0 | 1230 | ConvertFilespecToVMS(retrStr); |
michael@0 | 1231 | retrStr.Insert("RETR ",0); |
michael@0 | 1232 | retrStr.Append(CRLF); |
michael@0 | 1233 | return SendFTPCommand(retrStr); |
michael@0 | 1234 | } |
michael@0 | 1235 | |
michael@0 | 1236 | FTP_STATE |
michael@0 | 1237 | nsFtpState::R_retr() { |
michael@0 | 1238 | if (mResponseCode/100 == 2) { |
michael@0 | 1239 | //(DONE) |
michael@0 | 1240 | mNextState = FTP_COMPLETE; |
michael@0 | 1241 | return FTP_COMPLETE; |
michael@0 | 1242 | } |
michael@0 | 1243 | |
michael@0 | 1244 | if (mResponseCode/100 == 1) { |
michael@0 | 1245 | // We're going to grab a file, not a directory. So we need to clear |
michael@0 | 1246 | // any cache entry, otherwise we'll have problems reading it later. |
michael@0 | 1247 | // See bug 122548 |
michael@0 | 1248 | if (mCacheEntry) { |
michael@0 | 1249 | (void)mCacheEntry->AsyncDoom(nullptr); |
michael@0 | 1250 | mCacheEntry = nullptr; |
michael@0 | 1251 | } |
michael@0 | 1252 | if (mDataStream && HasPendingCallback()) |
michael@0 | 1253 | mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); |
michael@0 | 1254 | return FTP_READ_BUF; |
michael@0 | 1255 | } |
michael@0 | 1256 | |
michael@0 | 1257 | // These error codes are related to problems with the connection. |
michael@0 | 1258 | // If we encounter any at this point, do not try CWD and abort. |
michael@0 | 1259 | if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426) |
michael@0 | 1260 | return FTP_ERROR; |
michael@0 | 1261 | |
michael@0 | 1262 | if (mResponseCode/100 == 5) { |
michael@0 | 1263 | mRETRFailed = true; |
michael@0 | 1264 | return FTP_S_PASV; |
michael@0 | 1265 | } |
michael@0 | 1266 | |
michael@0 | 1267 | return FTP_S_CWD; |
michael@0 | 1268 | } |
michael@0 | 1269 | |
michael@0 | 1270 | |
michael@0 | 1271 | nsresult |
michael@0 | 1272 | nsFtpState::S_rest() { |
michael@0 | 1273 | |
michael@0 | 1274 | nsAutoCString restString("REST "); |
michael@0 | 1275 | // The int64_t cast is needed to avoid ambiguity |
michael@0 | 1276 | restString.AppendInt(int64_t(mChannel->StartPos()), 10); |
michael@0 | 1277 | restString.Append(CRLF); |
michael@0 | 1278 | |
michael@0 | 1279 | return SendFTPCommand(restString); |
michael@0 | 1280 | } |
michael@0 | 1281 | |
michael@0 | 1282 | FTP_STATE |
michael@0 | 1283 | nsFtpState::R_rest() { |
michael@0 | 1284 | if (mResponseCode/100 == 4) { |
michael@0 | 1285 | // If REST fails, then we can't resume |
michael@0 | 1286 | mChannel->SetEntityID(EmptyCString()); |
michael@0 | 1287 | |
michael@0 | 1288 | mInternalError = NS_ERROR_NOT_RESUMABLE; |
michael@0 | 1289 | mResponseMsg.Truncate(); |
michael@0 | 1290 | |
michael@0 | 1291 | return FTP_ERROR; |
michael@0 | 1292 | } |
michael@0 | 1293 | |
michael@0 | 1294 | return FTP_S_RETR; |
michael@0 | 1295 | } |
michael@0 | 1296 | |
michael@0 | 1297 | nsresult |
michael@0 | 1298 | nsFtpState::S_stor() { |
michael@0 | 1299 | NS_ENSURE_STATE(mChannel->UploadStream()); |
michael@0 | 1300 | |
michael@0 | 1301 | NS_ASSERTION(mAction == PUT, "Wrong state to be here"); |
michael@0 | 1302 | |
michael@0 | 1303 | nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI()); |
michael@0 | 1304 | NS_ASSERTION(url, "I thought you were a nsStandardURL"); |
michael@0 | 1305 | |
michael@0 | 1306 | nsAutoCString storStr; |
michael@0 | 1307 | url->GetFilePath(storStr); |
michael@0 | 1308 | NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path"); |
michael@0 | 1309 | |
michael@0 | 1310 | // kill the first slash since we want to be relative to CWD. |
michael@0 | 1311 | if (storStr.First() == '/') |
michael@0 | 1312 | storStr.Cut(0,1); |
michael@0 | 1313 | |
michael@0 | 1314 | if (mServerType == FTP_VMS_TYPE) |
michael@0 | 1315 | ConvertFilespecToVMS(storStr); |
michael@0 | 1316 | |
michael@0 | 1317 | NS_UnescapeURL(storStr); |
michael@0 | 1318 | storStr.Insert("STOR ",0); |
michael@0 | 1319 | storStr.Append(CRLF); |
michael@0 | 1320 | |
michael@0 | 1321 | return SendFTPCommand(storStr); |
michael@0 | 1322 | } |
michael@0 | 1323 | |
michael@0 | 1324 | FTP_STATE |
michael@0 | 1325 | nsFtpState::R_stor() { |
michael@0 | 1326 | if (mResponseCode/100 == 2) { |
michael@0 | 1327 | //(DONE) |
michael@0 | 1328 | mNextState = FTP_COMPLETE; |
michael@0 | 1329 | mStorReplyReceived = true; |
michael@0 | 1330 | |
michael@0 | 1331 | // Call Close() if it was not called in nsFtpState::OnStoprequest() |
michael@0 | 1332 | if (!mUploadRequest && !IsClosed()) |
michael@0 | 1333 | Close(); |
michael@0 | 1334 | |
michael@0 | 1335 | return FTP_COMPLETE; |
michael@0 | 1336 | } |
michael@0 | 1337 | |
michael@0 | 1338 | if (mResponseCode/100 == 1) { |
michael@0 | 1339 | LOG(("FTP:(%x) writing on DT\n", this)); |
michael@0 | 1340 | return FTP_READ_BUF; |
michael@0 | 1341 | } |
michael@0 | 1342 | |
michael@0 | 1343 | mStorReplyReceived = true; |
michael@0 | 1344 | return FTP_ERROR; |
michael@0 | 1345 | } |
michael@0 | 1346 | |
michael@0 | 1347 | |
michael@0 | 1348 | nsresult |
michael@0 | 1349 | nsFtpState::S_pasv() { |
michael@0 | 1350 | if (!mAddressChecked) { |
michael@0 | 1351 | // Find socket address |
michael@0 | 1352 | mAddressChecked = true; |
michael@0 | 1353 | mServerAddress.raw.family = AF_INET; |
michael@0 | 1354 | mServerAddress.inet.ip = htonl(INADDR_ANY); |
michael@0 | 1355 | mServerAddress.inet.port = htons(0); |
michael@0 | 1356 | |
michael@0 | 1357 | nsITransport *controlSocket = mControlConnection->Transport(); |
michael@0 | 1358 | if (!controlSocket) |
michael@0 | 1359 | // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has |
michael@0 | 1360 | // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109) |
michael@0 | 1361 | return (nsresult)FTP_ERROR; |
michael@0 | 1362 | |
michael@0 | 1363 | nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket); |
michael@0 | 1364 | if (sTrans) { |
michael@0 | 1365 | nsresult rv = sTrans->GetPeerAddr(&mServerAddress); |
michael@0 | 1366 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 1367 | if (!IsIPAddrAny(&mServerAddress)) |
michael@0 | 1368 | mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) && |
michael@0 | 1369 | !IsIPAddrV4Mapped(&mServerAddress); |
michael@0 | 1370 | else { |
michael@0 | 1371 | /* |
michael@0 | 1372 | * In case of SOCKS5 remote DNS resolution, we do |
michael@0 | 1373 | * not know the remote IP address. Still, if it is |
michael@0 | 1374 | * an IPV6 host, then the external address of the |
michael@0 | 1375 | * socks server should also be IPv6, and this is the |
michael@0 | 1376 | * self address of the transport. |
michael@0 | 1377 | */ |
michael@0 | 1378 | NetAddr selfAddress; |
michael@0 | 1379 | rv = sTrans->GetSelfAddr(&selfAddress); |
michael@0 | 1380 | if (NS_SUCCEEDED(rv)) |
michael@0 | 1381 | mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) && |
michael@0 | 1382 | !IsIPAddrV4Mapped(&selfAddress); |
michael@0 | 1383 | } |
michael@0 | 1384 | } |
michael@0 | 1385 | } |
michael@0 | 1386 | } |
michael@0 | 1387 | |
michael@0 | 1388 | const char *string; |
michael@0 | 1389 | if (mServerIsIPv6) { |
michael@0 | 1390 | string = "EPSV" CRLF; |
michael@0 | 1391 | } else { |
michael@0 | 1392 | string = "PASV" CRLF; |
michael@0 | 1393 | } |
michael@0 | 1394 | |
michael@0 | 1395 | return SendFTPCommand(nsDependentCString(string)); |
michael@0 | 1396 | |
michael@0 | 1397 | } |
michael@0 | 1398 | |
michael@0 | 1399 | FTP_STATE |
michael@0 | 1400 | nsFtpState::R_pasv() { |
michael@0 | 1401 | if (mResponseCode/100 != 2) |
michael@0 | 1402 | return FTP_ERROR; |
michael@0 | 1403 | |
michael@0 | 1404 | nsresult rv; |
michael@0 | 1405 | int32_t port; |
michael@0 | 1406 | |
michael@0 | 1407 | nsAutoCString responseCopy(mResponseMsg); |
michael@0 | 1408 | char *response = responseCopy.BeginWriting(); |
michael@0 | 1409 | |
michael@0 | 1410 | char *ptr = response; |
michael@0 | 1411 | |
michael@0 | 1412 | // Make sure to ignore the address in the PASV response (bug 370559) |
michael@0 | 1413 | |
michael@0 | 1414 | if (mServerIsIPv6) { |
michael@0 | 1415 | // The returned string is of the form |
michael@0 | 1416 | // text (|||ppp|) |
michael@0 | 1417 | // Where '|' can be any single character |
michael@0 | 1418 | char delim; |
michael@0 | 1419 | while (*ptr && *ptr != '(') |
michael@0 | 1420 | ptr++; |
michael@0 | 1421 | if (*ptr++ != '(') |
michael@0 | 1422 | return FTP_ERROR; |
michael@0 | 1423 | delim = *ptr++; |
michael@0 | 1424 | if (!delim || *ptr++ != delim || |
michael@0 | 1425 | *ptr++ != delim || |
michael@0 | 1426 | *ptr < '0' || *ptr > '9') |
michael@0 | 1427 | return FTP_ERROR; |
michael@0 | 1428 | port = 0; |
michael@0 | 1429 | do { |
michael@0 | 1430 | port = port * 10 + *ptr++ - '0'; |
michael@0 | 1431 | } while (*ptr >= '0' && *ptr <= '9'); |
michael@0 | 1432 | if (*ptr++ != delim || *ptr != ')') |
michael@0 | 1433 | return FTP_ERROR; |
michael@0 | 1434 | } else { |
michael@0 | 1435 | // The returned address string can be of the form |
michael@0 | 1436 | // (xxx,xxx,xxx,xxx,ppp,ppp) or |
michael@0 | 1437 | // xxx,xxx,xxx,xxx,ppp,ppp (without parens) |
michael@0 | 1438 | int32_t h0, h1, h2, h3, p0, p1; |
michael@0 | 1439 | |
michael@0 | 1440 | uint32_t fields = 0; |
michael@0 | 1441 | // First try with parens |
michael@0 | 1442 | while (*ptr && *ptr != '(') |
michael@0 | 1443 | ++ptr; |
michael@0 | 1444 | if (*ptr) { |
michael@0 | 1445 | ++ptr; |
michael@0 | 1446 | fields = PR_sscanf(ptr, |
michael@0 | 1447 | "%ld,%ld,%ld,%ld,%ld,%ld", |
michael@0 | 1448 | &h0, &h1, &h2, &h3, &p0, &p1); |
michael@0 | 1449 | } |
michael@0 | 1450 | if (!*ptr || fields < 6) { |
michael@0 | 1451 | // OK, lets try w/o parens |
michael@0 | 1452 | ptr = response; |
michael@0 | 1453 | while (*ptr && *ptr != ',') |
michael@0 | 1454 | ++ptr; |
michael@0 | 1455 | if (*ptr) { |
michael@0 | 1456 | // backup to the start of the digits |
michael@0 | 1457 | do { |
michael@0 | 1458 | ptr--; |
michael@0 | 1459 | } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9')); |
michael@0 | 1460 | ptr++; // get back onto the numbers |
michael@0 | 1461 | fields = PR_sscanf(ptr, |
michael@0 | 1462 | "%ld,%ld,%ld,%ld,%ld,%ld", |
michael@0 | 1463 | &h0, &h1, &h2, &h3, &p0, &p1); |
michael@0 | 1464 | } |
michael@0 | 1465 | } |
michael@0 | 1466 | |
michael@0 | 1467 | NS_ASSERTION(fields == 6, "Can't parse PASV response"); |
michael@0 | 1468 | if (fields < 6) |
michael@0 | 1469 | return FTP_ERROR; |
michael@0 | 1470 | |
michael@0 | 1471 | port = ((int32_t) (p0<<8)) + p1; |
michael@0 | 1472 | } |
michael@0 | 1473 | |
michael@0 | 1474 | bool newDataConn = true; |
michael@0 | 1475 | if (mDataTransport) { |
michael@0 | 1476 | // Reuse this connection only if its still alive, and the port |
michael@0 | 1477 | // is the same |
michael@0 | 1478 | nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport); |
michael@0 | 1479 | if (strans) { |
michael@0 | 1480 | int32_t oldPort; |
michael@0 | 1481 | nsresult rv = strans->GetPort(&oldPort); |
michael@0 | 1482 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 1483 | if (oldPort == port) { |
michael@0 | 1484 | bool isAlive; |
michael@0 | 1485 | if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive) |
michael@0 | 1486 | newDataConn = false; |
michael@0 | 1487 | } |
michael@0 | 1488 | } |
michael@0 | 1489 | } |
michael@0 | 1490 | |
michael@0 | 1491 | if (newDataConn) { |
michael@0 | 1492 | mDataTransport->Close(NS_ERROR_ABORT); |
michael@0 | 1493 | mDataTransport = nullptr; |
michael@0 | 1494 | mDataStream = nullptr; |
michael@0 | 1495 | } |
michael@0 | 1496 | } |
michael@0 | 1497 | |
michael@0 | 1498 | if (newDataConn) { |
michael@0 | 1499 | // now we know where to connect our data channel |
michael@0 | 1500 | nsCOMPtr<nsISocketTransportService> sts = |
michael@0 | 1501 | do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); |
michael@0 | 1502 | if (!sts) |
michael@0 | 1503 | return FTP_ERROR; |
michael@0 | 1504 | |
michael@0 | 1505 | nsCOMPtr<nsISocketTransport> strans; |
michael@0 | 1506 | |
michael@0 | 1507 | nsAutoCString host; |
michael@0 | 1508 | if (!IsIPAddrAny(&mServerAddress)) { |
michael@0 | 1509 | char buf[kIPv6CStrBufSize]; |
michael@0 | 1510 | NetAddrToString(&mServerAddress, buf, sizeof(buf)); |
michael@0 | 1511 | host.Assign(buf); |
michael@0 | 1512 | } else { |
michael@0 | 1513 | /* |
michael@0 | 1514 | * In case of SOCKS5 remote DNS resolving, the peer address |
michael@0 | 1515 | * fetched previously will be invalid (0.0.0.0): it is unknown |
michael@0 | 1516 | * to us. But we can pass on the original hostname to the |
michael@0 | 1517 | * connect for the data connection. |
michael@0 | 1518 | */ |
michael@0 | 1519 | rv = mChannel->URI()->GetAsciiHost(host); |
michael@0 | 1520 | if (NS_FAILED(rv)) |
michael@0 | 1521 | return FTP_ERROR; |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | rv = sts->CreateTransport(nullptr, 0, host, |
michael@0 | 1525 | port, mChannel->ProxyInfo(), |
michael@0 | 1526 | getter_AddRefs(strans)); // the data socket |
michael@0 | 1527 | if (NS_FAILED(rv)) |
michael@0 | 1528 | return FTP_ERROR; |
michael@0 | 1529 | mDataTransport = strans; |
michael@0 | 1530 | |
michael@0 | 1531 | strans->SetQoSBits(gFtpHandler->GetDataQoSBits()); |
michael@0 | 1532 | |
michael@0 | 1533 | LOG(("FTP:(%x) created DT (%s:%x)\n", this, host.get(), port)); |
michael@0 | 1534 | |
michael@0 | 1535 | // hook ourself up as a proxy for status notifications |
michael@0 | 1536 | rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread()); |
michael@0 | 1537 | NS_ENSURE_SUCCESS(rv, FTP_ERROR); |
michael@0 | 1538 | |
michael@0 | 1539 | if (mAction == PUT) { |
michael@0 | 1540 | NS_ASSERTION(!mRETRFailed, "Failed before uploading"); |
michael@0 | 1541 | |
michael@0 | 1542 | // nsIUploadChannel requires the upload stream to support ReadSegments. |
michael@0 | 1543 | // therefore, we can open an unbuffered socket output stream. |
michael@0 | 1544 | nsCOMPtr<nsIOutputStream> output; |
michael@0 | 1545 | rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, |
michael@0 | 1546 | 0, 0, getter_AddRefs(output)); |
michael@0 | 1547 | if (NS_FAILED(rv)) |
michael@0 | 1548 | return FTP_ERROR; |
michael@0 | 1549 | |
michael@0 | 1550 | // perform the data copy on the socket transport thread. we do this |
michael@0 | 1551 | // because "output" is a socket output stream, so the result is that |
michael@0 | 1552 | // all work will be done on the socket transport thread. |
michael@0 | 1553 | nsCOMPtr<nsIEventTarget> stEventTarget = |
michael@0 | 1554 | do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); |
michael@0 | 1555 | if (!stEventTarget) |
michael@0 | 1556 | return FTP_ERROR; |
michael@0 | 1557 | |
michael@0 | 1558 | nsCOMPtr<nsIAsyncStreamCopier> copier; |
michael@0 | 1559 | rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier), |
michael@0 | 1560 | mChannel->UploadStream(), |
michael@0 | 1561 | output, |
michael@0 | 1562 | stEventTarget, |
michael@0 | 1563 | true, // upload stream is buffered |
michael@0 | 1564 | false); // output is NOT buffered |
michael@0 | 1565 | if (NS_FAILED(rv)) |
michael@0 | 1566 | return FTP_ERROR; |
michael@0 | 1567 | |
michael@0 | 1568 | rv = copier->AsyncCopy(this, nullptr); |
michael@0 | 1569 | if (NS_FAILED(rv)) |
michael@0 | 1570 | return FTP_ERROR; |
michael@0 | 1571 | |
michael@0 | 1572 | // hold a reference to the copier so we can cancel it if necessary. |
michael@0 | 1573 | mUploadRequest = copier; |
michael@0 | 1574 | |
michael@0 | 1575 | // update the current working directory before sending the STOR |
michael@0 | 1576 | // command. this is needed since we might be reusing a control |
michael@0 | 1577 | // connection. |
michael@0 | 1578 | return FTP_S_CWD; |
michael@0 | 1579 | } |
michael@0 | 1580 | |
michael@0 | 1581 | // |
michael@0 | 1582 | // else, we are reading from the data connection... |
michael@0 | 1583 | // |
michael@0 | 1584 | |
michael@0 | 1585 | // open a buffered, asynchronous socket input stream |
michael@0 | 1586 | nsCOMPtr<nsIInputStream> input; |
michael@0 | 1587 | rv = mDataTransport->OpenInputStream(0, |
michael@0 | 1588 | nsIOService::gDefaultSegmentSize, |
michael@0 | 1589 | nsIOService::gDefaultSegmentCount, |
michael@0 | 1590 | getter_AddRefs(input)); |
michael@0 | 1591 | NS_ENSURE_SUCCESS(rv, FTP_ERROR); |
michael@0 | 1592 | mDataStream = do_QueryInterface(input); |
michael@0 | 1593 | } |
michael@0 | 1594 | |
michael@0 | 1595 | if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/') |
michael@0 | 1596 | return FTP_S_CWD; |
michael@0 | 1597 | return FTP_S_SIZE; |
michael@0 | 1598 | } |
michael@0 | 1599 | |
michael@0 | 1600 | nsresult |
michael@0 | 1601 | nsFtpState::S_feat() { |
michael@0 | 1602 | return SendFTPCommand(NS_LITERAL_CSTRING("FEAT" CRLF)); |
michael@0 | 1603 | } |
michael@0 | 1604 | |
michael@0 | 1605 | FTP_STATE |
michael@0 | 1606 | nsFtpState::R_feat() { |
michael@0 | 1607 | if (mResponseCode/100 == 2) { |
michael@0 | 1608 | if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) { |
michael@0 | 1609 | // This FTP server supports UTF-8 encoding |
michael@0 | 1610 | mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); |
michael@0 | 1611 | mUseUTF8 = true; |
michael@0 | 1612 | return FTP_S_OPTS; |
michael@0 | 1613 | } |
michael@0 | 1614 | } |
michael@0 | 1615 | |
michael@0 | 1616 | mUseUTF8 = false; |
michael@0 | 1617 | return FTP_S_PWD; |
michael@0 | 1618 | } |
michael@0 | 1619 | |
michael@0 | 1620 | nsresult |
michael@0 | 1621 | nsFtpState::S_opts() { |
michael@0 | 1622 | // This command is for compatibility of old FTP spec (IETF Draft) |
michael@0 | 1623 | return SendFTPCommand(NS_LITERAL_CSTRING("OPTS UTF8 ON" CRLF)); |
michael@0 | 1624 | } |
michael@0 | 1625 | |
michael@0 | 1626 | FTP_STATE |
michael@0 | 1627 | nsFtpState::R_opts() { |
michael@0 | 1628 | // Ignore error code because "OPTS UTF8 ON" is for compatibility of |
michael@0 | 1629 | // FTP server using IETF draft |
michael@0 | 1630 | return FTP_S_PWD; |
michael@0 | 1631 | } |
michael@0 | 1632 | |
michael@0 | 1633 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 1634 | // nsIRequest methods: |
michael@0 | 1635 | |
michael@0 | 1636 | static inline |
michael@0 | 1637 | uint32_t GetFtpTime() |
michael@0 | 1638 | { |
michael@0 | 1639 | return uint32_t(PR_Now() / PR_USEC_PER_SEC); |
michael@0 | 1640 | } |
michael@0 | 1641 | |
michael@0 | 1642 | uint32_t nsFtpState::mSessionStartTime = GetFtpTime(); |
michael@0 | 1643 | |
michael@0 | 1644 | /* Is this cache entry valid to use for reading? |
michael@0 | 1645 | * Since we make up an expiration time for ftp, use the following rules: |
michael@0 | 1646 | * (see bug 103726) |
michael@0 | 1647 | * |
michael@0 | 1648 | * LOAD_FROM_CACHE : always use cache entry, even if expired |
michael@0 | 1649 | * LOAD_BYPASS_CACHE : overwrite cache entry |
michael@0 | 1650 | * LOAD_NORMAL|VALIDATE_ALWAYS : overwrite cache entry |
michael@0 | 1651 | * LOAD_NORMAL : honor expiration time |
michael@0 | 1652 | * LOAD_NORMAL|VALIDATE_ONCE_PER_SESSION : overwrite cache entry if first access |
michael@0 | 1653 | * this session, otherwise use cache entry |
michael@0 | 1654 | * even if expired. |
michael@0 | 1655 | * LOAD_NORMAL|VALIDATE_NEVER : always use cache entry, even if expired |
michael@0 | 1656 | * |
michael@0 | 1657 | * Note that in theory we could use the mdtm time on the directory |
michael@0 | 1658 | * In practice, the lack of a timezone plus the general lack of support for that |
michael@0 | 1659 | * on directories means that its not worth it, I suspect. Revisit if we start |
michael@0 | 1660 | * caching files - bbaetz |
michael@0 | 1661 | */ |
michael@0 | 1662 | bool |
michael@0 | 1663 | nsFtpState::CanReadCacheEntry() |
michael@0 | 1664 | { |
michael@0 | 1665 | NS_ASSERTION(mCacheEntry, "must have a cache entry"); |
michael@0 | 1666 | |
michael@0 | 1667 | nsCacheAccessMode access; |
michael@0 | 1668 | nsresult rv = mCacheEntry->GetAccessGranted(&access); |
michael@0 | 1669 | if (NS_FAILED(rv)) |
michael@0 | 1670 | return false; |
michael@0 | 1671 | |
michael@0 | 1672 | // If I'm not granted read access, then I can't reuse it... |
michael@0 | 1673 | if (!(access & nsICache::ACCESS_READ)) |
michael@0 | 1674 | return false; |
michael@0 | 1675 | |
michael@0 | 1676 | if (mChannel->HasLoadFlag(nsIRequest::LOAD_FROM_CACHE)) |
michael@0 | 1677 | return true; |
michael@0 | 1678 | |
michael@0 | 1679 | if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) |
michael@0 | 1680 | return false; |
michael@0 | 1681 | |
michael@0 | 1682 | if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ALWAYS)) |
michael@0 | 1683 | return false; |
michael@0 | 1684 | |
michael@0 | 1685 | uint32_t time; |
michael@0 | 1686 | if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ONCE_PER_SESSION)) { |
michael@0 | 1687 | rv = mCacheEntry->GetLastModified(&time); |
michael@0 | 1688 | if (NS_FAILED(rv)) |
michael@0 | 1689 | return false; |
michael@0 | 1690 | return (mSessionStartTime > time); |
michael@0 | 1691 | } |
michael@0 | 1692 | |
michael@0 | 1693 | if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_NEVER)) |
michael@0 | 1694 | return true; |
michael@0 | 1695 | |
michael@0 | 1696 | // OK, now we just check the expiration time as usual |
michael@0 | 1697 | rv = mCacheEntry->GetExpirationTime(&time); |
michael@0 | 1698 | if (NS_FAILED(rv)) |
michael@0 | 1699 | return false; |
michael@0 | 1700 | |
michael@0 | 1701 | return (GetFtpTime() <= time); |
michael@0 | 1702 | } |
michael@0 | 1703 | |
michael@0 | 1704 | nsresult |
michael@0 | 1705 | nsFtpState::InstallCacheListener() |
michael@0 | 1706 | { |
michael@0 | 1707 | NS_ASSERTION(mCacheEntry, "must have a cache entry"); |
michael@0 | 1708 | |
michael@0 | 1709 | nsCOMPtr<nsIOutputStream> out; |
michael@0 | 1710 | mCacheEntry->OpenOutputStream(0, getter_AddRefs(out)); |
michael@0 | 1711 | NS_ENSURE_STATE(out); |
michael@0 | 1712 | |
michael@0 | 1713 | nsCOMPtr<nsIStreamListenerTee> tee = |
michael@0 | 1714 | do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID); |
michael@0 | 1715 | NS_ENSURE_STATE(tee); |
michael@0 | 1716 | |
michael@0 | 1717 | nsresult rv = tee->Init(mChannel->StreamListener(), out, nullptr); |
michael@0 | 1718 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1719 | |
michael@0 | 1720 | mChannel->SetStreamListener(tee); |
michael@0 | 1721 | return NS_OK; |
michael@0 | 1722 | } |
michael@0 | 1723 | |
michael@0 | 1724 | nsresult |
michael@0 | 1725 | nsFtpState::OpenCacheDataStream() |
michael@0 | 1726 | { |
michael@0 | 1727 | NS_ASSERTION(mCacheEntry, "must have a cache entry"); |
michael@0 | 1728 | |
michael@0 | 1729 | // Get a transport to the cached data... |
michael@0 | 1730 | nsCOMPtr<nsIInputStream> input; |
michael@0 | 1731 | mCacheEntry->OpenInputStream(0, getter_AddRefs(input)); |
michael@0 | 1732 | NS_ENSURE_STATE(input); |
michael@0 | 1733 | |
michael@0 | 1734 | nsCOMPtr<nsIStreamTransportService> sts = |
michael@0 | 1735 | do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); |
michael@0 | 1736 | NS_ENSURE_STATE(sts); |
michael@0 | 1737 | |
michael@0 | 1738 | nsCOMPtr<nsITransport> transport; |
michael@0 | 1739 | sts->CreateInputTransport(input, -1, -1, true, |
michael@0 | 1740 | getter_AddRefs(transport)); |
michael@0 | 1741 | NS_ENSURE_STATE(transport); |
michael@0 | 1742 | |
michael@0 | 1743 | nsresult rv = transport->SetEventSink(this, NS_GetCurrentThread()); |
michael@0 | 1744 | NS_ENSURE_SUCCESS(rv, rv); |
michael@0 | 1745 | |
michael@0 | 1746 | // Open a non-blocking, buffered input stream... |
michael@0 | 1747 | nsCOMPtr<nsIInputStream> transportInput; |
michael@0 | 1748 | transport->OpenInputStream(0, |
michael@0 | 1749 | nsIOService::gDefaultSegmentSize, |
michael@0 | 1750 | nsIOService::gDefaultSegmentCount, |
michael@0 | 1751 | getter_AddRefs(transportInput)); |
michael@0 | 1752 | NS_ENSURE_STATE(transportInput); |
michael@0 | 1753 | |
michael@0 | 1754 | mDataStream = do_QueryInterface(transportInput); |
michael@0 | 1755 | NS_ENSURE_STATE(mDataStream); |
michael@0 | 1756 | |
michael@0 | 1757 | mDataTransport = transport; |
michael@0 | 1758 | return NS_OK; |
michael@0 | 1759 | } |
michael@0 | 1760 | |
michael@0 | 1761 | nsresult |
michael@0 | 1762 | nsFtpState::Init(nsFtpChannel *channel) |
michael@0 | 1763 | { |
michael@0 | 1764 | // parameter validation |
michael@0 | 1765 | NS_ASSERTION(channel, "FTP: needs a channel"); |
michael@0 | 1766 | |
michael@0 | 1767 | mChannel = channel; // a straight ref ptr to the channel |
michael@0 | 1768 | |
michael@0 | 1769 | // initialize counter for network metering |
michael@0 | 1770 | mCountRecv = 0; |
michael@0 | 1771 | |
michael@0 | 1772 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 1773 | nsCOMPtr<nsINetworkInterface> activeNetwork; |
michael@0 | 1774 | GetActiveNetworkInterface(activeNetwork); |
michael@0 | 1775 | mActiveNetwork = |
michael@0 | 1776 | new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork); |
michael@0 | 1777 | #endif |
michael@0 | 1778 | |
michael@0 | 1779 | mKeepRunning = true; |
michael@0 | 1780 | mSuppliedEntityID = channel->EntityID(); |
michael@0 | 1781 | |
michael@0 | 1782 | if (channel->UploadStream()) |
michael@0 | 1783 | mAction = PUT; |
michael@0 | 1784 | |
michael@0 | 1785 | nsresult rv; |
michael@0 | 1786 | nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI()); |
michael@0 | 1787 | |
michael@0 | 1788 | nsAutoCString host; |
michael@0 | 1789 | if (url) { |
michael@0 | 1790 | rv = url->GetAsciiHost(host); |
michael@0 | 1791 | } else { |
michael@0 | 1792 | rv = mChannel->URI()->GetAsciiHost(host); |
michael@0 | 1793 | } |
michael@0 | 1794 | if (NS_FAILED(rv) || host.IsEmpty()) { |
michael@0 | 1795 | return NS_ERROR_MALFORMED_URI; |
michael@0 | 1796 | } |
michael@0 | 1797 | |
michael@0 | 1798 | nsAutoCString path; |
michael@0 | 1799 | if (url) { |
michael@0 | 1800 | rv = url->GetFilePath(path); |
michael@0 | 1801 | } else { |
michael@0 | 1802 | rv = mChannel->URI()->GetPath(path); |
michael@0 | 1803 | } |
michael@0 | 1804 | if (NS_FAILED(rv)) |
michael@0 | 1805 | return rv; |
michael@0 | 1806 | |
michael@0 | 1807 | removeParamsFromPath(path); |
michael@0 | 1808 | |
michael@0 | 1809 | // FTP parameters such as type=i are ignored |
michael@0 | 1810 | if (url) { |
michael@0 | 1811 | url->SetFilePath(path); |
michael@0 | 1812 | } else { |
michael@0 | 1813 | mChannel->URI()->SetPath(path); |
michael@0 | 1814 | } |
michael@0 | 1815 | |
michael@0 | 1816 | // Skip leading slash |
michael@0 | 1817 | char *fwdPtr = path.BeginWriting(); |
michael@0 | 1818 | if (!fwdPtr) |
michael@0 | 1819 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1820 | if (*fwdPtr == '/') |
michael@0 | 1821 | fwdPtr++; |
michael@0 | 1822 | if (*fwdPtr != '\0') { |
michael@0 | 1823 | // now unescape it... %xx reduced inline to resulting character |
michael@0 | 1824 | int32_t len = NS_UnescapeURL(fwdPtr); |
michael@0 | 1825 | mPath.Assign(fwdPtr, len); |
michael@0 | 1826 | |
michael@0 | 1827 | #ifdef DEBUG |
michael@0 | 1828 | if (mPath.FindCharInSet(CRLF) >= 0) |
michael@0 | 1829 | NS_ERROR("NewURI() should've prevented this!!!"); |
michael@0 | 1830 | #endif |
michael@0 | 1831 | } |
michael@0 | 1832 | |
michael@0 | 1833 | // pull any username and/or password out of the uri |
michael@0 | 1834 | nsAutoCString uname; |
michael@0 | 1835 | rv = mChannel->URI()->GetUsername(uname); |
michael@0 | 1836 | if (NS_FAILED(rv)) |
michael@0 | 1837 | return rv; |
michael@0 | 1838 | |
michael@0 | 1839 | if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) { |
michael@0 | 1840 | mAnonymous = false; |
michael@0 | 1841 | CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername); |
michael@0 | 1842 | |
michael@0 | 1843 | // return an error if we find a CR or LF in the username |
michael@0 | 1844 | if (uname.FindCharInSet(CRLF) >= 0) |
michael@0 | 1845 | return NS_ERROR_MALFORMED_URI; |
michael@0 | 1846 | } |
michael@0 | 1847 | |
michael@0 | 1848 | nsAutoCString password; |
michael@0 | 1849 | rv = mChannel->URI()->GetPassword(password); |
michael@0 | 1850 | if (NS_FAILED(rv)) |
michael@0 | 1851 | return rv; |
michael@0 | 1852 | |
michael@0 | 1853 | CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword); |
michael@0 | 1854 | |
michael@0 | 1855 | // return an error if we find a CR or LF in the password |
michael@0 | 1856 | if (mPassword.FindCharInSet(CRLF) >= 0) |
michael@0 | 1857 | return NS_ERROR_MALFORMED_URI; |
michael@0 | 1858 | |
michael@0 | 1859 | // setup the connection cache key |
michael@0 | 1860 | |
michael@0 | 1861 | int32_t port; |
michael@0 | 1862 | rv = mChannel->URI()->GetPort(&port); |
michael@0 | 1863 | if (NS_FAILED(rv)) |
michael@0 | 1864 | return rv; |
michael@0 | 1865 | |
michael@0 | 1866 | if (port > 0) |
michael@0 | 1867 | mPort = port; |
michael@0 | 1868 | |
michael@0 | 1869 | // Lookup Proxy information asynchronously if it isn't already set |
michael@0 | 1870 | // on the channel and if we aren't configured explicitly to go directly |
michael@0 | 1871 | nsCOMPtr<nsIProtocolProxyService> pps = |
michael@0 | 1872 | do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); |
michael@0 | 1873 | |
michael@0 | 1874 | if (pps && !mChannel->ProxyInfo()) { |
michael@0 | 1875 | pps->AsyncResolve(mChannel, 0, this, |
michael@0 | 1876 | getter_AddRefs(mProxyRequest)); |
michael@0 | 1877 | } |
michael@0 | 1878 | |
michael@0 | 1879 | return NS_OK; |
michael@0 | 1880 | } |
michael@0 | 1881 | |
michael@0 | 1882 | void |
michael@0 | 1883 | nsFtpState::Connect() |
michael@0 | 1884 | { |
michael@0 | 1885 | mState = FTP_COMMAND_CONNECT; |
michael@0 | 1886 | mNextState = FTP_S_USER; |
michael@0 | 1887 | |
michael@0 | 1888 | nsresult rv = Process(); |
michael@0 | 1889 | |
michael@0 | 1890 | // check for errors. |
michael@0 | 1891 | if (NS_FAILED(rv)) { |
michael@0 | 1892 | LOG(("FTP:Process() failed: %x\n", rv)); |
michael@0 | 1893 | mInternalError = NS_ERROR_FAILURE; |
michael@0 | 1894 | mState = FTP_ERROR; |
michael@0 | 1895 | CloseWithStatus(mInternalError); |
michael@0 | 1896 | } |
michael@0 | 1897 | } |
michael@0 | 1898 | |
michael@0 | 1899 | void |
michael@0 | 1900 | nsFtpState::KillControlConnection() |
michael@0 | 1901 | { |
michael@0 | 1902 | mControlReadCarryOverBuf.Truncate(0); |
michael@0 | 1903 | |
michael@0 | 1904 | mAddressChecked = false; |
michael@0 | 1905 | mServerIsIPv6 = false; |
michael@0 | 1906 | |
michael@0 | 1907 | // if everything went okay, save the connection. |
michael@0 | 1908 | // FIX: need a better way to determine if we can cache the connections. |
michael@0 | 1909 | // there are some errors which do not mean that we need to kill the connection |
michael@0 | 1910 | // e.g. fnf. |
michael@0 | 1911 | |
michael@0 | 1912 | if (!mControlConnection) |
michael@0 | 1913 | return; |
michael@0 | 1914 | |
michael@0 | 1915 | // kill the reference to ourselves in the control connection. |
michael@0 | 1916 | mControlConnection->WaitData(nullptr); |
michael@0 | 1917 | |
michael@0 | 1918 | if (NS_SUCCEEDED(mInternalError) && |
michael@0 | 1919 | NS_SUCCEEDED(mControlStatus) && |
michael@0 | 1920 | mControlConnection->IsAlive() && |
michael@0 | 1921 | mCacheConnection) { |
michael@0 | 1922 | |
michael@0 | 1923 | LOG_ALWAYS(("FTP:(%p) caching CC(%p)", this, mControlConnection.get())); |
michael@0 | 1924 | |
michael@0 | 1925 | // Store connection persistent data |
michael@0 | 1926 | mControlConnection->mServerType = mServerType; |
michael@0 | 1927 | mControlConnection->mPassword = mPassword; |
michael@0 | 1928 | mControlConnection->mPwd = mPwd; |
michael@0 | 1929 | mControlConnection->mUseUTF8 = mUseUTF8; |
michael@0 | 1930 | |
michael@0 | 1931 | nsresult rv = NS_OK; |
michael@0 | 1932 | // Don't cache controlconnection if anonymous (bug #473371) |
michael@0 | 1933 | if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) |
michael@0 | 1934 | rv = gFtpHandler->InsertConnection(mChannel->URI(), |
michael@0 | 1935 | mControlConnection); |
michael@0 | 1936 | // Can't cache it? Kill it then. |
michael@0 | 1937 | mControlConnection->Disconnect(rv); |
michael@0 | 1938 | } else { |
michael@0 | 1939 | mControlConnection->Disconnect(NS_BINDING_ABORTED); |
michael@0 | 1940 | } |
michael@0 | 1941 | |
michael@0 | 1942 | mControlConnection = nullptr; |
michael@0 | 1943 | } |
michael@0 | 1944 | |
michael@0 | 1945 | class nsFtpAsyncAlert : public nsRunnable |
michael@0 | 1946 | { |
michael@0 | 1947 | public: |
michael@0 | 1948 | nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg) |
michael@0 | 1949 | : mPrompter(aPrompter) |
michael@0 | 1950 | , mResponseMsg(aResponseMsg) |
michael@0 | 1951 | { |
michael@0 | 1952 | MOZ_COUNT_CTOR(nsFtpAsyncAlert); |
michael@0 | 1953 | } |
michael@0 | 1954 | virtual ~nsFtpAsyncAlert() |
michael@0 | 1955 | { |
michael@0 | 1956 | MOZ_COUNT_DTOR(nsFtpAsyncAlert); |
michael@0 | 1957 | } |
michael@0 | 1958 | NS_IMETHOD Run() |
michael@0 | 1959 | { |
michael@0 | 1960 | if (mPrompter) { |
michael@0 | 1961 | mPrompter->Alert(nullptr, mResponseMsg.get()); |
michael@0 | 1962 | } |
michael@0 | 1963 | return NS_OK; |
michael@0 | 1964 | } |
michael@0 | 1965 | private: |
michael@0 | 1966 | nsCOMPtr<nsIPrompt> mPrompter; |
michael@0 | 1967 | nsString mResponseMsg; |
michael@0 | 1968 | }; |
michael@0 | 1969 | |
michael@0 | 1970 | |
michael@0 | 1971 | nsresult |
michael@0 | 1972 | nsFtpState::StopProcessing() |
michael@0 | 1973 | { |
michael@0 | 1974 | // Only do this function once. |
michael@0 | 1975 | if (!mKeepRunning) |
michael@0 | 1976 | return NS_OK; |
michael@0 | 1977 | mKeepRunning = false; |
michael@0 | 1978 | |
michael@0 | 1979 | LOG_ALWAYS(("FTP:(%x) nsFtpState stopping", this)); |
michael@0 | 1980 | |
michael@0 | 1981 | #ifdef DEBUG_dougt |
michael@0 | 1982 | printf("FTP Stopped: [response code %d] [response msg follows:]\n%s\n", mResponseCode, mResponseMsg.get()); |
michael@0 | 1983 | #endif |
michael@0 | 1984 | |
michael@0 | 1985 | if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) { |
michael@0 | 1986 | // check to see if the control status is bad. |
michael@0 | 1987 | // web shell wont throw an alert. we better: |
michael@0 | 1988 | |
michael@0 | 1989 | // XXX(darin): this code should not be dictating UI like this! |
michael@0 | 1990 | nsCOMPtr<nsIPrompt> prompter; |
michael@0 | 1991 | mChannel->GetCallback(prompter); |
michael@0 | 1992 | if (prompter) { |
michael@0 | 1993 | nsCOMPtr<nsIRunnable> alertEvent; |
michael@0 | 1994 | if (mUseUTF8) { |
michael@0 | 1995 | alertEvent = new nsFtpAsyncAlert(prompter, |
michael@0 | 1996 | NS_ConvertUTF8toUTF16(mResponseMsg)); |
michael@0 | 1997 | } else { |
michael@0 | 1998 | alertEvent = new nsFtpAsyncAlert(prompter, |
michael@0 | 1999 | NS_ConvertASCIItoUTF16(mResponseMsg)); |
michael@0 | 2000 | } |
michael@0 | 2001 | NS_DispatchToMainThread(alertEvent, NS_DISPATCH_NORMAL); |
michael@0 | 2002 | } |
michael@0 | 2003 | } |
michael@0 | 2004 | |
michael@0 | 2005 | nsresult broadcastErrorCode = mControlStatus; |
michael@0 | 2006 | if (NS_SUCCEEDED(broadcastErrorCode)) |
michael@0 | 2007 | broadcastErrorCode = mInternalError; |
michael@0 | 2008 | |
michael@0 | 2009 | mInternalError = broadcastErrorCode; |
michael@0 | 2010 | |
michael@0 | 2011 | KillControlConnection(); |
michael@0 | 2012 | |
michael@0 | 2013 | // XXX This can fire before we are done loading data. Is that a problem? |
michael@0 | 2014 | OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0); |
michael@0 | 2015 | |
michael@0 | 2016 | if (NS_FAILED(broadcastErrorCode)) |
michael@0 | 2017 | CloseWithStatus(broadcastErrorCode); |
michael@0 | 2018 | |
michael@0 | 2019 | return NS_OK; |
michael@0 | 2020 | } |
michael@0 | 2021 | |
michael@0 | 2022 | nsresult |
michael@0 | 2023 | nsFtpState::SendFTPCommand(const nsCSubstring& command) |
michael@0 | 2024 | { |
michael@0 | 2025 | NS_ASSERTION(mControlConnection, "null control connection"); |
michael@0 | 2026 | |
michael@0 | 2027 | // we don't want to log the password: |
michael@0 | 2028 | nsAutoCString logcmd(command); |
michael@0 | 2029 | if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS "))) |
michael@0 | 2030 | logcmd = "PASS xxxxx"; |
michael@0 | 2031 | |
michael@0 | 2032 | LOG(("FTP:(%x) writing \"%s\"\n", this, logcmd.get())); |
michael@0 | 2033 | |
michael@0 | 2034 | nsCOMPtr<nsIFTPEventSink> ftpSink; |
michael@0 | 2035 | mChannel->GetFTPEventSink(ftpSink); |
michael@0 | 2036 | if (ftpSink) |
michael@0 | 2037 | ftpSink->OnFTPControlLog(false, logcmd.get()); |
michael@0 | 2038 | |
michael@0 | 2039 | if (mControlConnection) |
michael@0 | 2040 | return mControlConnection->Write(command); |
michael@0 | 2041 | |
michael@0 | 2042 | return NS_ERROR_FAILURE; |
michael@0 | 2043 | } |
michael@0 | 2044 | |
michael@0 | 2045 | // Convert a unix-style filespec to VMS format |
michael@0 | 2046 | // /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt |
michael@0 | 2047 | // /foo/file.txt -> foo:[000000]file.txt |
michael@0 | 2048 | void |
michael@0 | 2049 | nsFtpState::ConvertFilespecToVMS(nsCString& fileString) |
michael@0 | 2050 | { |
michael@0 | 2051 | int ntok=1; |
michael@0 | 2052 | char *t, *nextToken; |
michael@0 | 2053 | nsAutoCString fileStringCopy; |
michael@0 | 2054 | |
michael@0 | 2055 | // Get a writeable copy we can strtok with. |
michael@0 | 2056 | fileStringCopy = fileString; |
michael@0 | 2057 | t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken); |
michael@0 | 2058 | if (t) |
michael@0 | 2059 | while (nsCRT::strtok(nextToken, "/", &nextToken)) |
michael@0 | 2060 | ntok++; // count number of terms (tokens) |
michael@0 | 2061 | LOG(("FTP:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok)); |
michael@0 | 2062 | LOG(("FTP:(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get())); |
michael@0 | 2063 | |
michael@0 | 2064 | if (fileString.First() == '/') { |
michael@0 | 2065 | // absolute filespec |
michael@0 | 2066 | // / -> [] |
michael@0 | 2067 | // /a -> a (doesn't really make much sense) |
michael@0 | 2068 | // /a/b -> a:[000000]b |
michael@0 | 2069 | // /a/b/c -> a:[b]c |
michael@0 | 2070 | // /a/b/c/d -> a:[b.c]d |
michael@0 | 2071 | if (ntok == 1) { |
michael@0 | 2072 | if (fileString.Length() == 1) { |
michael@0 | 2073 | // Just a slash |
michael@0 | 2074 | fileString.Truncate(); |
michael@0 | 2075 | fileString.AppendLiteral("[]"); |
michael@0 | 2076 | } else { |
michael@0 | 2077 | // just copy the name part (drop the leading slash) |
michael@0 | 2078 | fileStringCopy = fileString; |
michael@0 | 2079 | fileString = Substring(fileStringCopy, 1, |
michael@0 | 2080 | fileStringCopy.Length()-1); |
michael@0 | 2081 | } |
michael@0 | 2082 | } else { |
michael@0 | 2083 | // Get another copy since the last one was written to. |
michael@0 | 2084 | fileStringCopy = fileString; |
michael@0 | 2085 | fileString.Truncate(); |
michael@0 | 2086 | fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), |
michael@0 | 2087 | "/", &nextToken)); |
michael@0 | 2088 | fileString.AppendLiteral(":["); |
michael@0 | 2089 | if (ntok > 2) { |
michael@0 | 2090 | for (int i=2; i<ntok; i++) { |
michael@0 | 2091 | if (i > 2) fileString.Append('.'); |
michael@0 | 2092 | fileString.Append(nsCRT::strtok(nextToken, |
michael@0 | 2093 | "/", &nextToken)); |
michael@0 | 2094 | } |
michael@0 | 2095 | } else { |
michael@0 | 2096 | fileString.AppendLiteral("000000"); |
michael@0 | 2097 | } |
michael@0 | 2098 | fileString.Append(']'); |
michael@0 | 2099 | fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); |
michael@0 | 2100 | } |
michael@0 | 2101 | } else { |
michael@0 | 2102 | // relative filespec |
michael@0 | 2103 | // a -> a |
michael@0 | 2104 | // a/b -> [.a]b |
michael@0 | 2105 | // a/b/c -> [.a.b]c |
michael@0 | 2106 | if (ntok == 1) { |
michael@0 | 2107 | // no slashes, just use the name as is |
michael@0 | 2108 | } else { |
michael@0 | 2109 | // Get another copy since the last one was written to. |
michael@0 | 2110 | fileStringCopy = fileString; |
michael@0 | 2111 | fileString.Truncate(); |
michael@0 | 2112 | fileString.AppendLiteral("[."); |
michael@0 | 2113 | fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), |
michael@0 | 2114 | "/", &nextToken)); |
michael@0 | 2115 | if (ntok > 2) { |
michael@0 | 2116 | for (int i=2; i<ntok; i++) { |
michael@0 | 2117 | fileString.Append('.'); |
michael@0 | 2118 | fileString.Append(nsCRT::strtok(nextToken, |
michael@0 | 2119 | "/", &nextToken)); |
michael@0 | 2120 | } |
michael@0 | 2121 | } |
michael@0 | 2122 | fileString.Append(']'); |
michael@0 | 2123 | fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); |
michael@0 | 2124 | } |
michael@0 | 2125 | } |
michael@0 | 2126 | LOG(("FTP:(%x) ConvertFilespecToVMS to: \"%s\"\n", this, fileString.get())); |
michael@0 | 2127 | } |
michael@0 | 2128 | |
michael@0 | 2129 | // Convert a unix-style dirspec to VMS format |
michael@0 | 2130 | // /foo/fred/barney/rubble -> foo:[fred.barney.rubble] |
michael@0 | 2131 | // /foo/fred -> foo:[fred] |
michael@0 | 2132 | // /foo -> foo:[000000] |
michael@0 | 2133 | // (null) -> (null) |
michael@0 | 2134 | void |
michael@0 | 2135 | nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec) |
michael@0 | 2136 | { |
michael@0 | 2137 | LOG(("FTP:(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get())); |
michael@0 | 2138 | if (!dirSpec.IsEmpty()) { |
michael@0 | 2139 | if (dirSpec.Last() != '/') |
michael@0 | 2140 | dirSpec.Append('/'); |
michael@0 | 2141 | // we can use the filespec routine if we make it look like a file name |
michael@0 | 2142 | dirSpec.Append('x'); |
michael@0 | 2143 | ConvertFilespecToVMS(dirSpec); |
michael@0 | 2144 | dirSpec.Truncate(dirSpec.Length()-1); |
michael@0 | 2145 | } |
michael@0 | 2146 | LOG(("FTP:(%x) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get())); |
michael@0 | 2147 | } |
michael@0 | 2148 | |
michael@0 | 2149 | // Convert an absolute VMS style dirspec to UNIX format |
michael@0 | 2150 | void |
michael@0 | 2151 | nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec) |
michael@0 | 2152 | { |
michael@0 | 2153 | LOG(("FTP:(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get())); |
michael@0 | 2154 | if (dirSpec.IsEmpty()) { |
michael@0 | 2155 | dirSpec.Insert('.', 0); |
michael@0 | 2156 | } else { |
michael@0 | 2157 | dirSpec.Insert('/', 0); |
michael@0 | 2158 | dirSpec.ReplaceSubstring(":[", "/"); |
michael@0 | 2159 | dirSpec.ReplaceChar('.', '/'); |
michael@0 | 2160 | dirSpec.ReplaceChar(']', '/'); |
michael@0 | 2161 | } |
michael@0 | 2162 | LOG(("FTP:(%x) ConvertDirspecFromVMS to: \"%s\"\n", this, dirSpec.get())); |
michael@0 | 2163 | } |
michael@0 | 2164 | |
michael@0 | 2165 | //----------------------------------------------------------------------------- |
michael@0 | 2166 | |
michael@0 | 2167 | NS_IMETHODIMP |
michael@0 | 2168 | nsFtpState::OnTransportStatus(nsITransport *transport, nsresult status, |
michael@0 | 2169 | uint64_t progress, uint64_t progressMax) |
michael@0 | 2170 | { |
michael@0 | 2171 | // Mix signals from both the control and data connections. |
michael@0 | 2172 | |
michael@0 | 2173 | // Ignore data transfer events on the control connection. |
michael@0 | 2174 | if (mControlConnection && transport == mControlConnection->Transport()) { |
michael@0 | 2175 | switch (status) { |
michael@0 | 2176 | case NS_NET_STATUS_RESOLVING_HOST: |
michael@0 | 2177 | case NS_NET_STATUS_RESOLVED_HOST: |
michael@0 | 2178 | case NS_NET_STATUS_CONNECTING_TO: |
michael@0 | 2179 | case NS_NET_STATUS_CONNECTED_TO: |
michael@0 | 2180 | break; |
michael@0 | 2181 | default: |
michael@0 | 2182 | return NS_OK; |
michael@0 | 2183 | } |
michael@0 | 2184 | } |
michael@0 | 2185 | |
michael@0 | 2186 | // Ignore the progressMax value from the socket. We know the true size of |
michael@0 | 2187 | // the file based on the response from our SIZE request. Additionally, only |
michael@0 | 2188 | // report the max progress based on where we started/resumed. |
michael@0 | 2189 | mChannel->OnTransportStatus(nullptr, status, progress, |
michael@0 | 2190 | mFileSize - mChannel->StartPos()); |
michael@0 | 2191 | return NS_OK; |
michael@0 | 2192 | } |
michael@0 | 2193 | |
michael@0 | 2194 | //----------------------------------------------------------------------------- |
michael@0 | 2195 | |
michael@0 | 2196 | |
michael@0 | 2197 | NS_IMETHODIMP |
michael@0 | 2198 | nsFtpState::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, |
michael@0 | 2199 | nsCacheAccessMode access, |
michael@0 | 2200 | nsresult status) |
michael@0 | 2201 | { |
michael@0 | 2202 | // We may have been closed while we were waiting for this cache entry. |
michael@0 | 2203 | if (IsClosed()) |
michael@0 | 2204 | return NS_OK; |
michael@0 | 2205 | |
michael@0 | 2206 | if (NS_SUCCEEDED(status) && entry) { |
michael@0 | 2207 | mDoomCache = true; |
michael@0 | 2208 | mCacheEntry = entry; |
michael@0 | 2209 | if (CanReadCacheEntry() && ReadCacheEntry()) { |
michael@0 | 2210 | mState = FTP_READ_CACHE; |
michael@0 | 2211 | return NS_OK; |
michael@0 | 2212 | } |
michael@0 | 2213 | } |
michael@0 | 2214 | |
michael@0 | 2215 | Connect(); |
michael@0 | 2216 | return NS_OK; |
michael@0 | 2217 | } |
michael@0 | 2218 | |
michael@0 | 2219 | //----------------------------------------------------------------------------- |
michael@0 | 2220 | |
michael@0 | 2221 | NS_IMETHODIMP |
michael@0 | 2222 | nsFtpState::OnCacheEntryDoomed(nsresult status) |
michael@0 | 2223 | { |
michael@0 | 2224 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 2225 | } |
michael@0 | 2226 | |
michael@0 | 2227 | //----------------------------------------------------------------------------- |
michael@0 | 2228 | |
michael@0 | 2229 | NS_IMETHODIMP |
michael@0 | 2230 | nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context) |
michael@0 | 2231 | { |
michael@0 | 2232 | mStorReplyReceived = false; |
michael@0 | 2233 | return NS_OK; |
michael@0 | 2234 | } |
michael@0 | 2235 | |
michael@0 | 2236 | NS_IMETHODIMP |
michael@0 | 2237 | nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context, |
michael@0 | 2238 | nsresult status) |
michael@0 | 2239 | { |
michael@0 | 2240 | mUploadRequest = nullptr; |
michael@0 | 2241 | |
michael@0 | 2242 | // Close() will be called when reply to STOR command is received |
michael@0 | 2243 | // see bug #389394 |
michael@0 | 2244 | if (!mStorReplyReceived) |
michael@0 | 2245 | return NS_OK; |
michael@0 | 2246 | |
michael@0 | 2247 | // We're done uploading. Let our consumer know that we're done. |
michael@0 | 2248 | Close(); |
michael@0 | 2249 | return NS_OK; |
michael@0 | 2250 | } |
michael@0 | 2251 | |
michael@0 | 2252 | //----------------------------------------------------------------------------- |
michael@0 | 2253 | |
michael@0 | 2254 | NS_IMETHODIMP |
michael@0 | 2255 | nsFtpState::Available(uint64_t *result) |
michael@0 | 2256 | { |
michael@0 | 2257 | if (mDataStream) |
michael@0 | 2258 | return mDataStream->Available(result); |
michael@0 | 2259 | |
michael@0 | 2260 | return nsBaseContentStream::Available(result); |
michael@0 | 2261 | } |
michael@0 | 2262 | |
michael@0 | 2263 | NS_IMETHODIMP |
michael@0 | 2264 | nsFtpState::ReadSegments(nsWriteSegmentFun writer, void *closure, |
michael@0 | 2265 | uint32_t count, uint32_t *result) |
michael@0 | 2266 | { |
michael@0 | 2267 | // Insert a thunk here so that the input stream passed to the writer is this |
michael@0 | 2268 | // input stream instead of mDataStream. |
michael@0 | 2269 | |
michael@0 | 2270 | if (mDataStream) { |
michael@0 | 2271 | nsWriteSegmentThunk thunk = { this, writer, closure }; |
michael@0 | 2272 | nsresult rv; |
michael@0 | 2273 | rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count, |
michael@0 | 2274 | result); |
michael@0 | 2275 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 2276 | CountRecvBytes(*result); |
michael@0 | 2277 | } |
michael@0 | 2278 | return rv; |
michael@0 | 2279 | } |
michael@0 | 2280 | |
michael@0 | 2281 | return nsBaseContentStream::ReadSegments(writer, closure, count, result); |
michael@0 | 2282 | } |
michael@0 | 2283 | |
michael@0 | 2284 | nsresult |
michael@0 | 2285 | nsFtpState::SaveNetworkStats(bool enforce) |
michael@0 | 2286 | { |
michael@0 | 2287 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 2288 | // Obtain app id |
michael@0 | 2289 | uint32_t appId; |
michael@0 | 2290 | bool isInBrowser; |
michael@0 | 2291 | NS_GetAppInfo(mChannel, &appId, &isInBrowser); |
michael@0 | 2292 | |
michael@0 | 2293 | // Check if active network and appid are valid. |
michael@0 | 2294 | if (!mActiveNetwork || appId == NECKO_NO_APP_ID) { |
michael@0 | 2295 | return NS_OK; |
michael@0 | 2296 | } |
michael@0 | 2297 | |
michael@0 | 2298 | if (mCountRecv <= 0) { |
michael@0 | 2299 | // There is no traffic, no need to save. |
michael@0 | 2300 | return NS_OK; |
michael@0 | 2301 | } |
michael@0 | 2302 | |
michael@0 | 2303 | // If |enforce| is false, the traffic amount is saved |
michael@0 | 2304 | // only when the total amount exceeds the predefined |
michael@0 | 2305 | // threshold. |
michael@0 | 2306 | if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) { |
michael@0 | 2307 | return NS_OK; |
michael@0 | 2308 | } |
michael@0 | 2309 | |
michael@0 | 2310 | // Create the event to save the network statistics. |
michael@0 | 2311 | // the event is then dispathed to the main thread. |
michael@0 | 2312 | nsRefPtr<nsRunnable> event = |
michael@0 | 2313 | new SaveNetworkStatsEvent(appId, mActiveNetwork, mCountRecv, 0, false); |
michael@0 | 2314 | NS_DispatchToMainThread(event); |
michael@0 | 2315 | |
michael@0 | 2316 | // Reset the counters after saving. |
michael@0 | 2317 | mCountRecv = 0; |
michael@0 | 2318 | |
michael@0 | 2319 | return NS_OK; |
michael@0 | 2320 | #else |
michael@0 | 2321 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 2322 | #endif |
michael@0 | 2323 | } |
michael@0 | 2324 | |
michael@0 | 2325 | NS_IMETHODIMP |
michael@0 | 2326 | nsFtpState::CloseWithStatus(nsresult status) |
michael@0 | 2327 | { |
michael@0 | 2328 | LOG(("FTP:(%p) close [%x]\n", this, status)); |
michael@0 | 2329 | |
michael@0 | 2330 | // Shutdown the control connection processing if we are being closed with an |
michael@0 | 2331 | // error. Note: This method may be called several times. |
michael@0 | 2332 | if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) { |
michael@0 | 2333 | if (NS_SUCCEEDED(mInternalError)) |
michael@0 | 2334 | mInternalError = status; |
michael@0 | 2335 | StopProcessing(); |
michael@0 | 2336 | } |
michael@0 | 2337 | |
michael@0 | 2338 | if (mUploadRequest) { |
michael@0 | 2339 | mUploadRequest->Cancel(NS_ERROR_ABORT); |
michael@0 | 2340 | mUploadRequest = nullptr; |
michael@0 | 2341 | } |
michael@0 | 2342 | |
michael@0 | 2343 | if (mDataTransport) { |
michael@0 | 2344 | // Save the network stats before data transport is closing. |
michael@0 | 2345 | SaveNetworkStats(true); |
michael@0 | 2346 | |
michael@0 | 2347 | // Shutdown the data transport. |
michael@0 | 2348 | mDataTransport->Close(NS_ERROR_ABORT); |
michael@0 | 2349 | mDataTransport = nullptr; |
michael@0 | 2350 | } |
michael@0 | 2351 | |
michael@0 | 2352 | mDataStream = nullptr; |
michael@0 | 2353 | if (mDoomCache && mCacheEntry) |
michael@0 | 2354 | mCacheEntry->AsyncDoom(nullptr); |
michael@0 | 2355 | mCacheEntry = nullptr; |
michael@0 | 2356 | |
michael@0 | 2357 | return nsBaseContentStream::CloseWithStatus(status); |
michael@0 | 2358 | } |
michael@0 | 2359 | |
michael@0 | 2360 | static nsresult |
michael@0 | 2361 | CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsIChannel **newChannel) |
michael@0 | 2362 | { |
michael@0 | 2363 | nsresult rv; |
michael@0 | 2364 | nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); |
michael@0 | 2365 | if (NS_FAILED(rv)) |
michael@0 | 2366 | return rv; |
michael@0 | 2367 | |
michael@0 | 2368 | nsCOMPtr<nsIProtocolHandler> handler; |
michael@0 | 2369 | rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler)); |
michael@0 | 2370 | if (NS_FAILED(rv)) |
michael@0 | 2371 | return rv; |
michael@0 | 2372 | |
michael@0 | 2373 | nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv); |
michael@0 | 2374 | if (NS_FAILED(rv)) |
michael@0 | 2375 | return rv; |
michael@0 | 2376 | |
michael@0 | 2377 | nsCOMPtr<nsIURI> uri; |
michael@0 | 2378 | channel->GetURI(getter_AddRefs(uri)); |
michael@0 | 2379 | |
michael@0 | 2380 | return pph->NewProxiedChannel(uri, pi, 0, nullptr, newChannel); |
michael@0 | 2381 | } |
michael@0 | 2382 | |
michael@0 | 2383 | NS_IMETHODIMP |
michael@0 | 2384 | nsFtpState::OnProxyAvailable(nsICancelable *request, nsIChannel *channel, |
michael@0 | 2385 | nsIProxyInfo *pi, nsresult status) |
michael@0 | 2386 | { |
michael@0 | 2387 | mProxyRequest = nullptr; |
michael@0 | 2388 | |
michael@0 | 2389 | // failed status code just implies DIRECT processing |
michael@0 | 2390 | |
michael@0 | 2391 | if (NS_SUCCEEDED(status)) { |
michael@0 | 2392 | nsAutoCString type; |
michael@0 | 2393 | if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) { |
michael@0 | 2394 | // Proxy the FTP url via HTTP |
michael@0 | 2395 | // This would have been easier to just return a HTTP channel directly |
michael@0 | 2396 | // from nsIIOService::NewChannelFromURI(), but the proxy type cannot |
michael@0 | 2397 | // be reliabliy determined synchronously without jank due to pac, etc.. |
michael@0 | 2398 | LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this)); |
michael@0 | 2399 | |
michael@0 | 2400 | nsCOMPtr<nsIChannel> newChannel; |
michael@0 | 2401 | if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi, |
michael@0 | 2402 | getter_AddRefs(newChannel))) && |
michael@0 | 2403 | NS_SUCCEEDED(mChannel->Redirect(newChannel, |
michael@0 | 2404 | nsIChannelEventSink::REDIRECT_INTERNAL, |
michael@0 | 2405 | true))) { |
michael@0 | 2406 | LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this)); |
michael@0 | 2407 | return NS_OK; |
michael@0 | 2408 | } |
michael@0 | 2409 | } |
michael@0 | 2410 | else if (pi) { |
michael@0 | 2411 | // Proxy using the FTP protocol routed through a socks proxy |
michael@0 | 2412 | LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this)); |
michael@0 | 2413 | mChannel->SetProxyInfo(pi); |
michael@0 | 2414 | } |
michael@0 | 2415 | } |
michael@0 | 2416 | |
michael@0 | 2417 | if (mDeferredCallbackPending) { |
michael@0 | 2418 | mDeferredCallbackPending = false; |
michael@0 | 2419 | OnCallbackPending(); |
michael@0 | 2420 | } |
michael@0 | 2421 | return NS_OK; |
michael@0 | 2422 | } |
michael@0 | 2423 | |
michael@0 | 2424 | void |
michael@0 | 2425 | nsFtpState::OnCallbackPending() |
michael@0 | 2426 | { |
michael@0 | 2427 | // If this is the first call, then see if we could use the cache. If we |
michael@0 | 2428 | // aren't going to read from (or write to) the cache, then just proceed to |
michael@0 | 2429 | // connect to the server. |
michael@0 | 2430 | |
michael@0 | 2431 | if (mState == FTP_INIT) { |
michael@0 | 2432 | if (mProxyRequest) { |
michael@0 | 2433 | mDeferredCallbackPending = true; |
michael@0 | 2434 | return; |
michael@0 | 2435 | } |
michael@0 | 2436 | |
michael@0 | 2437 | if (CheckCache()) { |
michael@0 | 2438 | mState = FTP_WAIT_CACHE; |
michael@0 | 2439 | return; |
michael@0 | 2440 | } |
michael@0 | 2441 | if (mCacheEntry && CanReadCacheEntry() && ReadCacheEntry()) { |
michael@0 | 2442 | mState = FTP_READ_CACHE; |
michael@0 | 2443 | return; |
michael@0 | 2444 | } |
michael@0 | 2445 | Connect(); |
michael@0 | 2446 | } else if (mDataStream) { |
michael@0 | 2447 | mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); |
michael@0 | 2448 | } |
michael@0 | 2449 | } |
michael@0 | 2450 | |
michael@0 | 2451 | bool |
michael@0 | 2452 | nsFtpState::ReadCacheEntry() |
michael@0 | 2453 | { |
michael@0 | 2454 | NS_ASSERTION(mCacheEntry, "should have a cache entry"); |
michael@0 | 2455 | |
michael@0 | 2456 | // make sure the channel knows wassup |
michael@0 | 2457 | SetContentType(); |
michael@0 | 2458 | |
michael@0 | 2459 | nsXPIDLCString serverType; |
michael@0 | 2460 | mCacheEntry->GetMetaDataElement("servertype", getter_Copies(serverType)); |
michael@0 | 2461 | nsAutoCString serverNum(serverType.get()); |
michael@0 | 2462 | nsresult err; |
michael@0 | 2463 | mServerType = serverNum.ToInteger(&err); |
michael@0 | 2464 | |
michael@0 | 2465 | nsXPIDLCString charset; |
michael@0 | 2466 | mCacheEntry->GetMetaDataElement("useUTF8", getter_Copies(charset)); |
michael@0 | 2467 | const char *useUTF8 = charset.get(); |
michael@0 | 2468 | if (useUTF8 && atoi(useUTF8) == 1) |
michael@0 | 2469 | mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); |
michael@0 | 2470 | |
michael@0 | 2471 | mChannel->PushStreamConverter("text/ftp-dir", |
michael@0 | 2472 | APPLICATION_HTTP_INDEX_FORMAT); |
michael@0 | 2473 | |
michael@0 | 2474 | mChannel->SetEntityID(EmptyCString()); |
michael@0 | 2475 | |
michael@0 | 2476 | if (NS_FAILED(OpenCacheDataStream())) |
michael@0 | 2477 | return false; |
michael@0 | 2478 | |
michael@0 | 2479 | if (mDataStream && HasPendingCallback()) |
michael@0 | 2480 | mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); |
michael@0 | 2481 | |
michael@0 | 2482 | mDoomCache = false; |
michael@0 | 2483 | return true; |
michael@0 | 2484 | } |
michael@0 | 2485 | |
michael@0 | 2486 | bool |
michael@0 | 2487 | nsFtpState::CheckCache() |
michael@0 | 2488 | { |
michael@0 | 2489 | // This function is responsible for setting mCacheEntry if there is a cache |
michael@0 | 2490 | // entry that we can use. It returns true if we end up waiting for access |
michael@0 | 2491 | // to the cache. |
michael@0 | 2492 | |
michael@0 | 2493 | // In some cases, we don't want to use the cache: |
michael@0 | 2494 | if (mChannel->UploadStream() || mChannel->ResumeRequested()) |
michael@0 | 2495 | return false; |
michael@0 | 2496 | |
michael@0 | 2497 | nsCOMPtr<nsICacheService> cache = do_GetService(NS_CACHESERVICE_CONTRACTID); |
michael@0 | 2498 | if (!cache) |
michael@0 | 2499 | return false; |
michael@0 | 2500 | |
michael@0 | 2501 | bool isPrivate = NS_UsePrivateBrowsing(mChannel); |
michael@0 | 2502 | const char* sessionName = isPrivate ? "FTP-private" : "FTP"; |
michael@0 | 2503 | nsCacheStoragePolicy policy = |
michael@0 | 2504 | isPrivate ? nsICache::STORE_IN_MEMORY : nsICache::STORE_ANYWHERE; |
michael@0 | 2505 | nsCOMPtr<nsICacheSession> session; |
michael@0 | 2506 | cache->CreateSession(sessionName, |
michael@0 | 2507 | policy, |
michael@0 | 2508 | nsICache::STREAM_BASED, |
michael@0 | 2509 | getter_AddRefs(session)); |
michael@0 | 2510 | if (!session) |
michael@0 | 2511 | return false; |
michael@0 | 2512 | session->SetDoomEntriesIfExpired(false); |
michael@0 | 2513 | session->SetIsPrivate(isPrivate); |
michael@0 | 2514 | |
michael@0 | 2515 | // Set cache access requested: |
michael@0 | 2516 | nsCacheAccessMode accessReq; |
michael@0 | 2517 | if (NS_IsOffline()) { |
michael@0 | 2518 | accessReq = nsICache::ACCESS_READ; // can only read |
michael@0 | 2519 | } else if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) { |
michael@0 | 2520 | accessReq = nsICache::ACCESS_WRITE; // replace cache entry |
michael@0 | 2521 | } else { |
michael@0 | 2522 | accessReq = nsICache::ACCESS_READ_WRITE; // normal browsing |
michael@0 | 2523 | } |
michael@0 | 2524 | |
michael@0 | 2525 | // Check to see if we are not allowed to write to the cache: |
michael@0 | 2526 | if (mChannel->HasLoadFlag(nsIRequest::INHIBIT_CACHING)) { |
michael@0 | 2527 | accessReq &= ~nsICache::ACCESS_WRITE; |
michael@0 | 2528 | if (accessReq == nsICache::ACCESS_NONE) |
michael@0 | 2529 | return false; |
michael@0 | 2530 | } |
michael@0 | 2531 | |
michael@0 | 2532 | // Generate cache key (remove trailing #ref if any): |
michael@0 | 2533 | nsAutoCString key; |
michael@0 | 2534 | mChannel->URI()->GetAsciiSpec(key); |
michael@0 | 2535 | int32_t pos = key.RFindChar('#'); |
michael@0 | 2536 | if (pos != kNotFound) |
michael@0 | 2537 | key.Truncate(pos); |
michael@0 | 2538 | NS_ENSURE_FALSE(key.IsEmpty(), false); |
michael@0 | 2539 | |
michael@0 | 2540 | nsresult rv = session->AsyncOpenCacheEntry(key, accessReq, this, false); |
michael@0 | 2541 | return NS_SUCCEEDED(rv); |
michael@0 | 2542 | |
michael@0 | 2543 | } |