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