netwerk/protocol/ftp/nsFtpConnectionThread.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

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 }

mercurial