Coverage Report

Created: 2018-09-25 14:53

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