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