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