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