|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
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 "pk11pub.h" |
|
8 #include "prlog.h" |
|
9 #include "ScopedNSSTypes.h" |
|
10 #include "secoidt.h" |
|
11 |
|
12 #include "nsIAsyncInputStream.h" |
|
13 #include "nsIFile.h" |
|
14 #include "nsIMutableArray.h" |
|
15 #include "nsIPipe.h" |
|
16 #include "nsIX509Cert.h" |
|
17 #include "nsIX509CertDB.h" |
|
18 #include "nsIX509CertList.h" |
|
19 #include "nsCOMArray.h" |
|
20 #include "nsNetUtil.h" |
|
21 #include "nsThreadUtils.h" |
|
22 |
|
23 #include "BackgroundFileSaver.h" |
|
24 #include "mozilla/Telemetry.h" |
|
25 |
|
26 #ifdef XP_WIN |
|
27 #include <windows.h> |
|
28 #include <softpub.h> |
|
29 #include <wintrust.h> |
|
30 #endif // XP_WIN |
|
31 |
|
32 namespace mozilla { |
|
33 namespace net { |
|
34 |
|
35 // NSPR_LOG_MODULES=BackgroundFileSaver:5 |
|
36 #if defined(PR_LOGGING) |
|
37 PRLogModuleInfo *BackgroundFileSaver::prlog = nullptr; |
|
38 #define LOG(args) PR_LOG(BackgroundFileSaver::prlog, PR_LOG_DEBUG, args) |
|
39 #define LOG_ENABLED() PR_LOG_TEST(BackgroundFileSaver::prlog, 4) |
|
40 #else |
|
41 #define LOG(args) |
|
42 #define LOG_ENABLED() (false) |
|
43 #endif |
|
44 |
|
45 //////////////////////////////////////////////////////////////////////////////// |
|
46 //// Globals |
|
47 |
|
48 /** |
|
49 * Buffer size for writing to the output file or reading from the input file. |
|
50 */ |
|
51 #define BUFFERED_IO_SIZE (1024 * 32) |
|
52 |
|
53 /** |
|
54 * When this upper limit is reached, the original request is suspended. |
|
55 */ |
|
56 #define REQUEST_SUSPEND_AT (1024 * 1024 * 4) |
|
57 |
|
58 /** |
|
59 * When this lower limit is reached, the original request is resumed. |
|
60 */ |
|
61 #define REQUEST_RESUME_AT (1024 * 1024 * 2) |
|
62 |
|
63 //////////////////////////////////////////////////////////////////////////////// |
|
64 //// NotifyTargetChangeRunnable |
|
65 |
|
66 /** |
|
67 * Runnable object used to notify the control thread that file contents will now |
|
68 * be saved to the specified file. |
|
69 */ |
|
70 class NotifyTargetChangeRunnable MOZ_FINAL : public nsRunnable |
|
71 { |
|
72 public: |
|
73 NotifyTargetChangeRunnable(BackgroundFileSaver *aSaver, nsIFile *aTarget) |
|
74 : mSaver(aSaver) |
|
75 , mTarget(aTarget) |
|
76 { |
|
77 } |
|
78 |
|
79 NS_IMETHODIMP Run() |
|
80 { |
|
81 return mSaver->NotifyTargetChange(mTarget); |
|
82 } |
|
83 |
|
84 private: |
|
85 nsRefPtr<BackgroundFileSaver> mSaver; |
|
86 nsCOMPtr<nsIFile> mTarget; |
|
87 }; |
|
88 |
|
89 //////////////////////////////////////////////////////////////////////////////// |
|
90 //// BackgroundFileSaver |
|
91 |
|
92 uint32_t BackgroundFileSaver::sThreadCount = 0; |
|
93 uint32_t BackgroundFileSaver::sTelemetryMaxThreadCount = 0; |
|
94 |
|
95 BackgroundFileSaver::BackgroundFileSaver() |
|
96 : mControlThread(nullptr) |
|
97 , mWorkerThread(nullptr) |
|
98 , mPipeOutputStream(nullptr) |
|
99 , mPipeInputStream(nullptr) |
|
100 , mObserver(nullptr) |
|
101 , mLock("BackgroundFileSaver.mLock") |
|
102 , mWorkerThreadAttentionRequested(false) |
|
103 , mFinishRequested(false) |
|
104 , mComplete(false) |
|
105 , mStatus(NS_OK) |
|
106 , mAppend(false) |
|
107 , mInitialTarget(nullptr) |
|
108 , mInitialTargetKeepPartial(false) |
|
109 , mRenamedTarget(nullptr) |
|
110 , mRenamedTargetKeepPartial(false) |
|
111 , mAsyncCopyContext(nullptr) |
|
112 , mSha256Enabled(false) |
|
113 , mSignatureInfoEnabled(false) |
|
114 , mActualTarget(nullptr) |
|
115 , mActualTargetKeepPartial(false) |
|
116 , mDigestContext(nullptr) |
|
117 { |
|
118 #if defined(PR_LOGGING) |
|
119 if (!prlog) |
|
120 prlog = PR_NewLogModule("BackgroundFileSaver"); |
|
121 #endif |
|
122 LOG(("Created BackgroundFileSaver [this = %p]", this)); |
|
123 } |
|
124 |
|
125 BackgroundFileSaver::~BackgroundFileSaver() |
|
126 { |
|
127 LOG(("Destroying BackgroundFileSaver [this = %p]", this)); |
|
128 nsNSSShutDownPreventionLock lock; |
|
129 if (isAlreadyShutDown()) { |
|
130 return; |
|
131 } |
|
132 destructorSafeDestroyNSSReference(); |
|
133 shutdown(calledFromObject); |
|
134 } |
|
135 |
|
136 void |
|
137 BackgroundFileSaver::destructorSafeDestroyNSSReference() |
|
138 { |
|
139 if (mDigestContext) { |
|
140 mozilla::psm::PK11_DestroyContext_true(mDigestContext.forget()); |
|
141 mDigestContext = nullptr; |
|
142 } |
|
143 } |
|
144 |
|
145 void |
|
146 BackgroundFileSaver::virtualDestroyNSSReference() |
|
147 { |
|
148 destructorSafeDestroyNSSReference(); |
|
149 } |
|
150 |
|
151 // Called on the control thread. |
|
152 nsresult |
|
153 BackgroundFileSaver::Init() |
|
154 { |
|
155 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); |
|
156 |
|
157 nsresult rv; |
|
158 |
|
159 rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream), |
|
160 getter_AddRefs(mPipeOutputStream), true, true, 0, |
|
161 HasInfiniteBuffer() ? UINT32_MAX : 0); |
|
162 NS_ENSURE_SUCCESS(rv, rv); |
|
163 |
|
164 rv = NS_GetCurrentThread(getter_AddRefs(mControlThread)); |
|
165 NS_ENSURE_SUCCESS(rv, rv); |
|
166 |
|
167 rv = NS_NewThread(getter_AddRefs(mWorkerThread)); |
|
168 NS_ENSURE_SUCCESS(rv, rv); |
|
169 |
|
170 sThreadCount++; |
|
171 if (sThreadCount > sTelemetryMaxThreadCount) { |
|
172 sTelemetryMaxThreadCount = sThreadCount; |
|
173 } |
|
174 |
|
175 return NS_OK; |
|
176 } |
|
177 |
|
178 // Called on the control thread. |
|
179 NS_IMETHODIMP |
|
180 BackgroundFileSaver::GetObserver(nsIBackgroundFileSaverObserver **aObserver) |
|
181 { |
|
182 NS_ENSURE_ARG_POINTER(aObserver); |
|
183 *aObserver = mObserver; |
|
184 NS_IF_ADDREF(*aObserver); |
|
185 return NS_OK; |
|
186 } |
|
187 |
|
188 // Called on the control thread. |
|
189 NS_IMETHODIMP |
|
190 BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver *aObserver) |
|
191 { |
|
192 mObserver = aObserver; |
|
193 return NS_OK; |
|
194 } |
|
195 |
|
196 // Called on the control thread. |
|
197 NS_IMETHODIMP |
|
198 BackgroundFileSaver::EnableAppend() |
|
199 { |
|
200 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); |
|
201 |
|
202 MutexAutoLock lock(mLock); |
|
203 mAppend = true; |
|
204 |
|
205 return NS_OK; |
|
206 } |
|
207 |
|
208 // Called on the control thread. |
|
209 NS_IMETHODIMP |
|
210 BackgroundFileSaver::SetTarget(nsIFile *aTarget, bool aKeepPartial) |
|
211 { |
|
212 NS_ENSURE_ARG(aTarget); |
|
213 { |
|
214 MutexAutoLock lock(mLock); |
|
215 if (!mInitialTarget) { |
|
216 aTarget->Clone(getter_AddRefs(mInitialTarget)); |
|
217 mInitialTargetKeepPartial = aKeepPartial; |
|
218 } else { |
|
219 aTarget->Clone(getter_AddRefs(mRenamedTarget)); |
|
220 mRenamedTargetKeepPartial = aKeepPartial; |
|
221 } |
|
222 } |
|
223 |
|
224 // After the worker thread wakes up because attention is requested, it will |
|
225 // rename or create the target file as requested, and start copying data. |
|
226 return GetWorkerThreadAttention(true); |
|
227 } |
|
228 |
|
229 // Called on the control thread. |
|
230 NS_IMETHODIMP |
|
231 BackgroundFileSaver::Finish(nsresult aStatus) |
|
232 { |
|
233 nsresult rv; |
|
234 |
|
235 // This will cause the NS_AsyncCopy operation, if it's in progress, to consume |
|
236 // all the data that is still in the pipe, and then finish. |
|
237 rv = mPipeOutputStream->Close(); |
|
238 NS_ENSURE_SUCCESS(rv, rv); |
|
239 |
|
240 // Ensure that, when we get attention from the worker thread, if no pending |
|
241 // rename operation is waiting, the operation will complete. |
|
242 { |
|
243 MutexAutoLock lock(mLock); |
|
244 mFinishRequested = true; |
|
245 if (NS_SUCCEEDED(mStatus)) { |
|
246 mStatus = aStatus; |
|
247 } |
|
248 } |
|
249 |
|
250 // After the worker thread wakes up because attention is requested, it will |
|
251 // process the completion conditions, detect that completion is requested, and |
|
252 // notify the main thread of the completion. If this function was called with |
|
253 // a success code, we wait for the copy to finish before processing the |
|
254 // completion conditions, otherwise we interrupt the copy immediately. |
|
255 return GetWorkerThreadAttention(NS_FAILED(aStatus)); |
|
256 } |
|
257 |
|
258 NS_IMETHODIMP |
|
259 BackgroundFileSaver::EnableSha256() |
|
260 { |
|
261 MOZ_ASSERT(NS_IsMainThread(), |
|
262 "Can't enable sha256 or initialize NSS off the main thread"); |
|
263 // Ensure Personal Security Manager is initialized. This is required for |
|
264 // PK11_* operations to work. |
|
265 nsresult rv; |
|
266 nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv); |
|
267 NS_ENSURE_SUCCESS(rv, rv); |
|
268 mSha256Enabled = true; |
|
269 return NS_OK; |
|
270 } |
|
271 |
|
272 NS_IMETHODIMP |
|
273 BackgroundFileSaver::GetSha256Hash(nsACString& aHash) |
|
274 { |
|
275 MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread"); |
|
276 // We acquire a lock because mSha256 is written on the worker thread. |
|
277 MutexAutoLock lock(mLock); |
|
278 if (mSha256.IsEmpty()) { |
|
279 return NS_ERROR_NOT_AVAILABLE; |
|
280 } |
|
281 aHash = mSha256; |
|
282 return NS_OK; |
|
283 } |
|
284 |
|
285 NS_IMETHODIMP |
|
286 BackgroundFileSaver::EnableSignatureInfo() |
|
287 { |
|
288 MOZ_ASSERT(NS_IsMainThread(), |
|
289 "Can't enable signature extraction off the main thread"); |
|
290 // Ensure Personal Security Manager is initialized. |
|
291 nsresult rv; |
|
292 nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv); |
|
293 NS_ENSURE_SUCCESS(rv, rv); |
|
294 mSignatureInfoEnabled = true; |
|
295 return NS_OK; |
|
296 } |
|
297 |
|
298 NS_IMETHODIMP |
|
299 BackgroundFileSaver::GetSignatureInfo(nsIArray** aSignatureInfo) |
|
300 { |
|
301 MOZ_ASSERT(NS_IsMainThread(), "Can't inspect signature off the main thread"); |
|
302 // We acquire a lock because mSignatureInfo is written on the worker thread. |
|
303 MutexAutoLock lock(mLock); |
|
304 if (!mComplete || !mSignatureInfoEnabled) { |
|
305 return NS_ERROR_NOT_AVAILABLE; |
|
306 } |
|
307 nsCOMPtr<nsIMutableArray> sigArray = do_CreateInstance(NS_ARRAY_CONTRACTID); |
|
308 for (int i = 0; i < mSignatureInfo.Count(); ++i) { |
|
309 sigArray->AppendElement(mSignatureInfo[i], false); |
|
310 } |
|
311 *aSignatureInfo = sigArray; |
|
312 NS_IF_ADDREF(*aSignatureInfo); |
|
313 return NS_OK; |
|
314 } |
|
315 |
|
316 // Called on the control thread. |
|
317 nsresult |
|
318 BackgroundFileSaver::GetWorkerThreadAttention(bool aShouldInterruptCopy) |
|
319 { |
|
320 nsresult rv; |
|
321 |
|
322 MutexAutoLock lock(mLock); |
|
323 |
|
324 // We only require attention one time. If this function is called two times |
|
325 // before the worker thread wakes up, and the first has aShouldInterruptCopy |
|
326 // false and the second true, we won't forcibly interrupt the copy from the |
|
327 // control thread. However, that never happens, because calling Finish with a |
|
328 // success code is the only case that may result in aShouldInterruptCopy being |
|
329 // false. In that case, we won't call this function again, because consumers |
|
330 // should not invoke other methods on the control thread after calling Finish. |
|
331 // And in any case, Finish already closes one end of the pipe, causing the |
|
332 // copy to finish properly on its own. |
|
333 if (mWorkerThreadAttentionRequested) { |
|
334 return NS_OK; |
|
335 } |
|
336 |
|
337 if (!mAsyncCopyContext) { |
|
338 // Copy is not in progress, post an event to handle the change manually. |
|
339 nsCOMPtr<nsIRunnable> event = |
|
340 NS_NewRunnableMethod(this, &BackgroundFileSaver::ProcessAttention); |
|
341 NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); |
|
342 |
|
343 rv = mWorkerThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
344 NS_ENSURE_SUCCESS(rv, rv); |
|
345 } else if (aShouldInterruptCopy) { |
|
346 // Interrupt the copy. The copy will be resumed, if needed, by the |
|
347 // ProcessAttention function, invoked by the AsyncCopyCallback function. |
|
348 NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); |
|
349 } |
|
350 |
|
351 // Indicate that attention has been requested successfully, there is no need |
|
352 // to post another event until the worker thread processes the current one. |
|
353 mWorkerThreadAttentionRequested = true; |
|
354 |
|
355 return NS_OK; |
|
356 } |
|
357 |
|
358 // Called on the worker thread. |
|
359 // static |
|
360 void |
|
361 BackgroundFileSaver::AsyncCopyCallback(void *aClosure, nsresult aStatus) |
|
362 { |
|
363 BackgroundFileSaver *self = (BackgroundFileSaver *)aClosure; |
|
364 { |
|
365 MutexAutoLock lock(self->mLock); |
|
366 |
|
367 // Now that the copy was interrupted or terminated, any notification from |
|
368 // the control thread requires an event to be posted to the worker thread. |
|
369 self->mAsyncCopyContext = nullptr; |
|
370 |
|
371 // When detecting failures, ignore the status code we use to interrupt. |
|
372 if (NS_FAILED(aStatus) && aStatus != NS_ERROR_ABORT && |
|
373 NS_SUCCEEDED(self->mStatus)) { |
|
374 self->mStatus = aStatus; |
|
375 } |
|
376 } |
|
377 |
|
378 (void)self->ProcessAttention(); |
|
379 |
|
380 // We called NS_ADDREF_THIS when NS_AsyncCopy started, to keep the object |
|
381 // alive even if other references disappeared. At this point, we've finished |
|
382 // using the object and can safely release our reference. |
|
383 NS_RELEASE(self); |
|
384 } |
|
385 |
|
386 // Called on the worker thread. |
|
387 nsresult |
|
388 BackgroundFileSaver::ProcessAttention() |
|
389 { |
|
390 nsresult rv; |
|
391 |
|
392 // This function is called whenever the attention of the worker thread has |
|
393 // been requested. This may happen in these cases: |
|
394 // * We are about to start the copy for the first time. In this case, we are |
|
395 // called from an event posted on the worker thread from the control thread |
|
396 // by GetWorkerThreadAttention, and mAsyncCopyContext is null. |
|
397 // * We have interrupted the copy for some reason. In this case, we are |
|
398 // called by AsyncCopyCallback, and mAsyncCopyContext is null. |
|
399 // * We are currently executing ProcessStateChange, and attention is requested |
|
400 // by the control thread, for example because SetTarget or Finish have been |
|
401 // called. In this case, we are called from from an event posted through |
|
402 // GetWorkerThreadAttention. While mAsyncCopyContext was always null when |
|
403 // the event was posted, at this point mAsyncCopyContext may not be null |
|
404 // anymore, because ProcessStateChange may have started the copy before the |
|
405 // event that called this function was processed on the worker thread. |
|
406 // If mAsyncCopyContext is not null, we interrupt the copy and re-enter |
|
407 // through AsyncCopyCallback. This allows us to check if, for instance, we |
|
408 // should rename the target file. We will then restart the copy if needed. |
|
409 if (mAsyncCopyContext) { |
|
410 NS_CancelAsyncCopy(mAsyncCopyContext, NS_ERROR_ABORT); |
|
411 return NS_OK; |
|
412 } |
|
413 // Use the current shared state to determine the next operation to execute. |
|
414 rv = ProcessStateChange(); |
|
415 if (NS_FAILED(rv)) { |
|
416 // If something failed while processing, terminate the operation now. |
|
417 { |
|
418 MutexAutoLock lock(mLock); |
|
419 |
|
420 if (NS_SUCCEEDED(mStatus)) { |
|
421 mStatus = rv; |
|
422 } |
|
423 } |
|
424 // Ensure we notify completion now that the operation failed. |
|
425 CheckCompletion(); |
|
426 } |
|
427 |
|
428 return NS_OK; |
|
429 } |
|
430 |
|
431 // Called on the worker thread. |
|
432 nsresult |
|
433 BackgroundFileSaver::ProcessStateChange() |
|
434 { |
|
435 nsresult rv; |
|
436 |
|
437 // We might have been notified because the operation is complete, verify. |
|
438 if (CheckCompletion()) { |
|
439 return NS_OK; |
|
440 } |
|
441 |
|
442 // Get a copy of the current shared state for the worker thread. |
|
443 nsCOMPtr<nsIFile> initialTarget; |
|
444 bool initialTargetKeepPartial; |
|
445 nsCOMPtr<nsIFile> renamedTarget; |
|
446 bool renamedTargetKeepPartial; |
|
447 bool sha256Enabled; |
|
448 bool append; |
|
449 { |
|
450 MutexAutoLock lock(mLock); |
|
451 |
|
452 initialTarget = mInitialTarget; |
|
453 initialTargetKeepPartial = mInitialTargetKeepPartial; |
|
454 renamedTarget = mRenamedTarget; |
|
455 renamedTargetKeepPartial = mRenamedTargetKeepPartial; |
|
456 sha256Enabled = mSha256Enabled; |
|
457 append = mAppend; |
|
458 |
|
459 // From now on, another attention event needs to be posted if state changes. |
|
460 mWorkerThreadAttentionRequested = false; |
|
461 } |
|
462 |
|
463 // The initial target can only be null if it has never been assigned. In this |
|
464 // case, there is nothing to do since we never created any output file. |
|
465 if (!initialTarget) { |
|
466 return NS_OK; |
|
467 } |
|
468 |
|
469 // Determine if we are processing the attention request for the first time. |
|
470 bool isContinuation = !!mActualTarget; |
|
471 if (!isContinuation) { |
|
472 // Assign the target file for the first time. |
|
473 mActualTarget = initialTarget; |
|
474 mActualTargetKeepPartial = initialTargetKeepPartial; |
|
475 } |
|
476 |
|
477 // Verify whether we have actually been instructed to use a different file. |
|
478 // This may happen the first time this function is executed, if SetTarget was |
|
479 // called two times before the worker thread processed the attention request. |
|
480 bool equalToCurrent = false; |
|
481 if (renamedTarget) { |
|
482 rv = mActualTarget->Equals(renamedTarget, &equalToCurrent); |
|
483 NS_ENSURE_SUCCESS(rv, rv); |
|
484 if (!equalToCurrent) |
|
485 { |
|
486 // If we were asked to rename the file but the initial file did not exist, |
|
487 // we simply create the file in the renamed location. We avoid this check |
|
488 // if we have already started writing the output file ourselves. |
|
489 bool exists = true; |
|
490 if (!isContinuation) { |
|
491 rv = mActualTarget->Exists(&exists); |
|
492 NS_ENSURE_SUCCESS(rv, rv); |
|
493 } |
|
494 if (exists) { |
|
495 // We are moving the previous target file to a different location. |
|
496 nsCOMPtr<nsIFile> renamedTargetParentDir; |
|
497 rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir)); |
|
498 NS_ENSURE_SUCCESS(rv, rv); |
|
499 |
|
500 nsAutoString renamedTargetName; |
|
501 rv = renamedTarget->GetLeafName(renamedTargetName); |
|
502 NS_ENSURE_SUCCESS(rv, rv); |
|
503 |
|
504 // We must delete any existing target file before moving the current |
|
505 // one. |
|
506 rv = renamedTarget->Exists(&exists); |
|
507 NS_ENSURE_SUCCESS(rv, rv); |
|
508 if (exists) { |
|
509 rv = renamedTarget->Remove(false); |
|
510 NS_ENSURE_SUCCESS(rv, rv); |
|
511 } |
|
512 |
|
513 // Move the file. If this fails, we still reference the original file |
|
514 // in mActualTarget, so that it is deleted if requested. If this |
|
515 // succeeds, the nsIFile instance referenced by mActualTarget mutates |
|
516 // and starts pointing to the new file, but we'll discard the reference. |
|
517 rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName); |
|
518 NS_ENSURE_SUCCESS(rv, rv); |
|
519 } |
|
520 |
|
521 // Now we can update the actual target file name. |
|
522 mActualTarget = renamedTarget; |
|
523 mActualTargetKeepPartial = renamedTargetKeepPartial; |
|
524 } |
|
525 } |
|
526 |
|
527 // Notify if the target file name actually changed. |
|
528 if (!equalToCurrent) { |
|
529 // We must clone the nsIFile instance because mActualTarget is not |
|
530 // immutable, it may change if the target is renamed later. |
|
531 nsCOMPtr<nsIFile> actualTargetToNotify; |
|
532 rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify)); |
|
533 NS_ENSURE_SUCCESS(rv, rv); |
|
534 |
|
535 nsRefPtr<NotifyTargetChangeRunnable> event = |
|
536 new NotifyTargetChangeRunnable(this, actualTargetToNotify); |
|
537 NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); |
|
538 |
|
539 rv = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
540 NS_ENSURE_SUCCESS(rv, rv); |
|
541 } |
|
542 |
|
543 if (isContinuation) { |
|
544 // The pending rename operation might be the last task before finishing. We |
|
545 // may return here only if we have already created the target file. |
|
546 if (CheckCompletion()) { |
|
547 return NS_OK; |
|
548 } |
|
549 |
|
550 // Even if the operation did not complete, the pipe input stream may be |
|
551 // empty and may have been closed already. We detect this case using the |
|
552 // Available property, because it never returns an error if there is more |
|
553 // data to be consumed. If the pipe input stream is closed, we just exit |
|
554 // and wait for more calls like SetTarget or Finish to be invoked on the |
|
555 // control thread. However, we still truncate the file or create the |
|
556 // initial digest context if we are expected to do that. |
|
557 uint64_t available; |
|
558 rv = mPipeInputStream->Available(&available); |
|
559 if (NS_FAILED(rv)) { |
|
560 return NS_OK; |
|
561 } |
|
562 } |
|
563 |
|
564 // Create the digest context if requested and NSS hasn't been shut down. |
|
565 if (sha256Enabled && !mDigestContext) { |
|
566 nsNSSShutDownPreventionLock lock; |
|
567 if (!isAlreadyShutDown()) { |
|
568 mDigestContext = |
|
569 PK11_CreateDigestContext(static_cast<SECOidTag>(SEC_OID_SHA256)); |
|
570 NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY); |
|
571 } |
|
572 } |
|
573 |
|
574 // When we are requested to append to an existing file, we should read the |
|
575 // existing data and ensure we include it as part of the final hash. |
|
576 if (mDigestContext && append && !isContinuation) { |
|
577 nsCOMPtr<nsIInputStream> inputStream; |
|
578 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), |
|
579 mActualTarget, |
|
580 PR_RDONLY | nsIFile::OS_READAHEAD); |
|
581 if (rv != NS_ERROR_FILE_NOT_FOUND) { |
|
582 NS_ENSURE_SUCCESS(rv, rv); |
|
583 |
|
584 char buffer[BUFFERED_IO_SIZE]; |
|
585 while (true) { |
|
586 uint32_t count; |
|
587 rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count); |
|
588 NS_ENSURE_SUCCESS(rv, rv); |
|
589 |
|
590 if (count == 0) { |
|
591 // We reached the end of the file. |
|
592 break; |
|
593 } |
|
594 |
|
595 nsNSSShutDownPreventionLock lock; |
|
596 if (isAlreadyShutDown()) { |
|
597 return NS_ERROR_NOT_AVAILABLE; |
|
598 } |
|
599 |
|
600 nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext, |
|
601 uint8_t_ptr_cast(buffer), |
|
602 count)); |
|
603 NS_ENSURE_SUCCESS(rv, rv); |
|
604 } |
|
605 |
|
606 rv = inputStream->Close(); |
|
607 NS_ENSURE_SUCCESS(rv, rv); |
|
608 } |
|
609 } |
|
610 |
|
611 // We will append to the initial target file only if it was requested by the |
|
612 // caller, but we'll always append on subsequent accesses to the target file. |
|
613 int32_t creationIoFlags; |
|
614 if (isContinuation) { |
|
615 creationIoFlags = PR_APPEND; |
|
616 } else { |
|
617 creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE; |
|
618 } |
|
619 |
|
620 // Create the target file, or append to it if we already started writing it. |
|
621 nsCOMPtr<nsIOutputStream> outputStream; |
|
622 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), |
|
623 mActualTarget, |
|
624 PR_WRONLY | creationIoFlags, 0644); |
|
625 NS_ENSURE_SUCCESS(rv, rv); |
|
626 |
|
627 outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE); |
|
628 if (!outputStream) { |
|
629 return NS_ERROR_FAILURE; |
|
630 } |
|
631 |
|
632 // Wrap the output stream so that it feeds the digest context if needed. |
|
633 if (mDigestContext) { |
|
634 // No need to acquire the NSS lock here, DigestOutputStream must acquire it |
|
635 // in any case before each asynchronous write. Constructing the |
|
636 // DigestOutputStream cannot fail. Passing mDigestContext to |
|
637 // DigestOutputStream is safe, because BackgroundFileSaver always outlives |
|
638 // the outputStream. BackgroundFileSaver is reference-counted before the |
|
639 // call to AsyncCopy, and mDigestContext is never destroyed before |
|
640 // AsyncCopyCallback. |
|
641 outputStream = new DigestOutputStream(outputStream, mDigestContext); |
|
642 } |
|
643 |
|
644 // Start copying our input to the target file. No errors can be raised past |
|
645 // this point if the copy starts, since they should be handled by the thread. |
|
646 { |
|
647 MutexAutoLock lock(mLock); |
|
648 |
|
649 rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread, |
|
650 NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback, |
|
651 this, false, true, getter_AddRefs(mAsyncCopyContext), |
|
652 GetProgressCallback()); |
|
653 if (NS_FAILED(rv)) { |
|
654 NS_WARNING("NS_AsyncCopy failed."); |
|
655 mAsyncCopyContext = nullptr; |
|
656 return rv; |
|
657 } |
|
658 } |
|
659 |
|
660 // If the operation succeeded, we must ensure that we keep this object alive |
|
661 // for the entire duration of the copy, since only the raw pointer will be |
|
662 // provided as the argument of the AsyncCopyCallback function. We can add the |
|
663 // reference now, after NS_AsyncCopy returned, because it always starts |
|
664 // processing asynchronously, and there is no risk that the callback is |
|
665 // invoked before we reach this point. If the operation failed instead, then |
|
666 // AsyncCopyCallback will never be called. |
|
667 NS_ADDREF_THIS(); |
|
668 |
|
669 return NS_OK; |
|
670 } |
|
671 |
|
672 // Called on the worker thread. |
|
673 bool |
|
674 BackgroundFileSaver::CheckCompletion() |
|
675 { |
|
676 nsresult rv; |
|
677 |
|
678 MOZ_ASSERT(!mAsyncCopyContext, |
|
679 "Should not be copying when checking completion conditions."); |
|
680 |
|
681 bool failed = true; |
|
682 { |
|
683 MutexAutoLock lock(mLock); |
|
684 |
|
685 if (mComplete) { |
|
686 return true; |
|
687 } |
|
688 |
|
689 // If an error occurred, we don't need to do the checks in this code block, |
|
690 // and the operation can be completed immediately with a failure code. |
|
691 if (NS_SUCCEEDED(mStatus)) { |
|
692 failed = false; |
|
693 |
|
694 // We did not incur in an error, so we must determine if we can stop now. |
|
695 // If the Finish method has not been called, we can just continue now. |
|
696 if (!mFinishRequested) { |
|
697 return false; |
|
698 } |
|
699 |
|
700 // We can only stop when all the operations requested by the control |
|
701 // thread have been processed. First, we check whether we have processed |
|
702 // the first SetTarget call, if any. Then, we check whether we have |
|
703 // processed any rename requested by subsequent SetTarget calls. |
|
704 if ((mInitialTarget && !mActualTarget) || |
|
705 (mRenamedTarget && mRenamedTarget != mActualTarget)) { |
|
706 return false; |
|
707 } |
|
708 |
|
709 // If we still have data to write to the output file, allow the copy |
|
710 // operation to resume. The Available getter may return an error if one |
|
711 // of the pipe's streams has been already closed. |
|
712 uint64_t available; |
|
713 rv = mPipeInputStream->Available(&available); |
|
714 if (NS_SUCCEEDED(rv) && available != 0) { |
|
715 return false; |
|
716 } |
|
717 } |
|
718 |
|
719 mComplete = true; |
|
720 } |
|
721 |
|
722 // Ensure we notify completion now that the operation finished. |
|
723 // Do a best-effort attempt to remove the file if required. |
|
724 if (failed && mActualTarget && !mActualTargetKeepPartial) { |
|
725 (void)mActualTarget->Remove(false); |
|
726 } |
|
727 |
|
728 // Finish computing the hash |
|
729 if (!failed && mDigestContext) { |
|
730 nsNSSShutDownPreventionLock lock; |
|
731 if (!isAlreadyShutDown()) { |
|
732 Digest d; |
|
733 rv = d.End(SEC_OID_SHA256, mDigestContext); |
|
734 if (NS_SUCCEEDED(rv)) { |
|
735 MutexAutoLock lock(mLock); |
|
736 mSha256 = nsDependentCSubstring(char_ptr_cast(d.get().data), |
|
737 d.get().len); |
|
738 } |
|
739 } |
|
740 } |
|
741 |
|
742 // Compute the signature of the binary. ExtractSignatureInfo doesn't do |
|
743 // anything on non-Windows platforms except return an empty nsIArray. |
|
744 if (!failed && mActualTarget) { |
|
745 nsString filePath; |
|
746 mActualTarget->GetTarget(filePath); |
|
747 nsresult rv = ExtractSignatureInfo(filePath); |
|
748 if (NS_FAILED(rv)) { |
|
749 LOG(("Unable to extract signature information [this = %p].", this)); |
|
750 } else { |
|
751 LOG(("Signature extraction success! [this = %p]", this)); |
|
752 } |
|
753 } |
|
754 |
|
755 // Post an event to notify that the operation completed. |
|
756 nsCOMPtr<nsIRunnable> event = |
|
757 NS_NewRunnableMethod(this, &BackgroundFileSaver::NotifySaveComplete); |
|
758 if (!event || |
|
759 NS_FAILED(mControlThread->Dispatch(event, NS_DISPATCH_NORMAL))) { |
|
760 NS_WARNING("Unable to post completion event to the control thread."); |
|
761 } |
|
762 |
|
763 return true; |
|
764 } |
|
765 |
|
766 // Called on the control thread. |
|
767 nsresult |
|
768 BackgroundFileSaver::NotifyTargetChange(nsIFile *aTarget) |
|
769 { |
|
770 if (mObserver) { |
|
771 (void)mObserver->OnTargetChange(this, aTarget); |
|
772 } |
|
773 |
|
774 return NS_OK; |
|
775 } |
|
776 |
|
777 // Called on the control thread. |
|
778 nsresult |
|
779 BackgroundFileSaver::NotifySaveComplete() |
|
780 { |
|
781 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); |
|
782 |
|
783 nsresult status; |
|
784 { |
|
785 MutexAutoLock lock(mLock); |
|
786 status = mStatus; |
|
787 } |
|
788 |
|
789 if (mObserver) { |
|
790 (void)mObserver->OnSaveComplete(this, status); |
|
791 } |
|
792 |
|
793 // At this point, the worker thread will not process any more events, and we |
|
794 // can shut it down. Shutting down a thread may re-enter the event loop on |
|
795 // this thread. This is not a problem in this case, since this function is |
|
796 // called by a top-level event itself, and we have already invoked the |
|
797 // completion observer callback. Re-entering the loop can only delay the |
|
798 // final release and destruction of this saver object, since we are keeping a |
|
799 // reference to it through the event object. |
|
800 mWorkerThread->Shutdown(); |
|
801 |
|
802 sThreadCount--; |
|
803 |
|
804 // When there are no more active downloads, we consider the download session |
|
805 // finished. We record the maximum number of concurrent downloads reached |
|
806 // during the session in a telemetry histogram, and we reset the maximum |
|
807 // thread counter for the next download session |
|
808 if (sThreadCount == 0) { |
|
809 Telemetry::Accumulate(Telemetry::BACKGROUNDFILESAVER_THREAD_COUNT, |
|
810 sTelemetryMaxThreadCount); |
|
811 sTelemetryMaxThreadCount = 0; |
|
812 } |
|
813 |
|
814 return NS_OK; |
|
815 } |
|
816 |
|
817 nsresult |
|
818 BackgroundFileSaver::ExtractSignatureInfo(const nsAString& filePath) |
|
819 { |
|
820 MOZ_ASSERT(!NS_IsMainThread(), "Cannot extract signature on main thread"); |
|
821 |
|
822 nsNSSShutDownPreventionLock nssLock; |
|
823 if (isAlreadyShutDown()) { |
|
824 return NS_ERROR_NOT_AVAILABLE; |
|
825 } |
|
826 { |
|
827 MutexAutoLock lock(mLock); |
|
828 if (!mSignatureInfoEnabled) { |
|
829 return NS_OK; |
|
830 } |
|
831 } |
|
832 nsresult rv; |
|
833 nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv); |
|
834 NS_ENSURE_SUCCESS(rv, rv); |
|
835 #ifdef XP_WIN |
|
836 // Setup the file to check. |
|
837 WINTRUST_FILE_INFO fileToCheck = {0}; |
|
838 fileToCheck.cbStruct = sizeof(WINTRUST_FILE_INFO); |
|
839 fileToCheck.pcwszFilePath = filePath.Data(); |
|
840 fileToCheck.hFile = nullptr; |
|
841 fileToCheck.pgKnownSubject = nullptr; |
|
842 |
|
843 // We want to check it is signed and trusted. |
|
844 WINTRUST_DATA trustData = {0}; |
|
845 trustData.cbStruct = sizeof(trustData); |
|
846 trustData.pPolicyCallbackData = nullptr; |
|
847 trustData.pSIPClientData = nullptr; |
|
848 trustData.dwUIChoice = WTD_UI_NONE; |
|
849 trustData.fdwRevocationChecks = WTD_REVOKE_NONE; |
|
850 trustData.dwUnionChoice = WTD_CHOICE_FILE; |
|
851 trustData.dwStateAction = WTD_STATEACTION_VERIFY; |
|
852 trustData.hWVTStateData = nullptr; |
|
853 trustData.pwszURLReference = nullptr; |
|
854 // Disallow revocation checks over the network |
|
855 trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; |
|
856 // no UI |
|
857 trustData.dwUIContext = 0; |
|
858 trustData.pFile = &fileToCheck; |
|
859 |
|
860 // The WINTRUST_ACTION_GENERIC_VERIFY_V2 policy verifies that the certificate |
|
861 // chains up to a trusted root CA and has appropriate permissions to sign |
|
862 // code. |
|
863 GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; |
|
864 // Check if the file is signed by something that is trusted. If the file is |
|
865 // not signed, this is a no-op. |
|
866 LONG ret = WinVerifyTrust(nullptr, &policyGUID, &trustData); |
|
867 CRYPT_PROVIDER_DATA* cryptoProviderData = nullptr; |
|
868 // According to the Windows documentation, we should check against 0 instead |
|
869 // of ERROR_SUCCESS, which is an HRESULT. |
|
870 if (ret == 0) { |
|
871 cryptoProviderData = WTHelperProvDataFromStateData(trustData.hWVTStateData); |
|
872 } |
|
873 if (cryptoProviderData) { |
|
874 // Lock because signature information is read on the main thread. |
|
875 MutexAutoLock lock(mLock); |
|
876 LOG(("Downloaded trusted and signed file [this = %p].", this)); |
|
877 // A binary may have multiple signers. Each signer may have multiple certs |
|
878 // in the chain. |
|
879 for (DWORD i = 0; i < cryptoProviderData->csSigners; ++i) { |
|
880 const CERT_CHAIN_CONTEXT* certChainContext = |
|
881 cryptoProviderData->pasSigners[i].pChainContext; |
|
882 if (!certChainContext) { |
|
883 break; |
|
884 } |
|
885 for (DWORD j = 0; j < certChainContext->cChain; ++j) { |
|
886 const CERT_SIMPLE_CHAIN* certSimpleChain = |
|
887 certChainContext->rgpChain[j]; |
|
888 if (!certSimpleChain) { |
|
889 break; |
|
890 } |
|
891 nsCOMPtr<nsIX509CertList> nssCertList = |
|
892 do_CreateInstance(NS_X509CERTLIST_CONTRACTID); |
|
893 if (!nssCertList) { |
|
894 break; |
|
895 } |
|
896 bool extractionSuccess = true; |
|
897 for (DWORD k = 0; k < certSimpleChain->cElement; ++k) { |
|
898 CERT_CHAIN_ELEMENT* certChainElement = certSimpleChain->rgpElement[k]; |
|
899 if (certChainElement->pCertContext->dwCertEncodingType != |
|
900 X509_ASN_ENCODING) { |
|
901 continue; |
|
902 } |
|
903 nsCOMPtr<nsIX509Cert> nssCert = nullptr; |
|
904 rv = certDB->ConstructX509( |
|
905 reinterpret_cast<char *>( |
|
906 certChainElement->pCertContext->pbCertEncoded), |
|
907 certChainElement->pCertContext->cbCertEncoded, |
|
908 getter_AddRefs(nssCert)); |
|
909 if (!nssCert) { |
|
910 extractionSuccess = false; |
|
911 LOG(("Couldn't create NSS cert [this = %p]", this)); |
|
912 break; |
|
913 } |
|
914 nssCertList->AddCert(nssCert); |
|
915 nsString subjectName; |
|
916 nssCert->GetSubjectName(subjectName); |
|
917 LOG(("Adding cert %s [this = %p]", |
|
918 NS_ConvertUTF16toUTF8(subjectName).get(), this)); |
|
919 } |
|
920 if (extractionSuccess) { |
|
921 mSignatureInfo.AppendObject(nssCertList); |
|
922 } |
|
923 } |
|
924 } |
|
925 // Free the provider data if cryptoProviderData is not null. |
|
926 trustData.dwStateAction = WTD_STATEACTION_CLOSE; |
|
927 WinVerifyTrust(nullptr, &policyGUID, &trustData); |
|
928 } else { |
|
929 LOG(("Downloaded unsigned or untrusted file [this = %p].", this)); |
|
930 } |
|
931 #endif |
|
932 return NS_OK; |
|
933 } |
|
934 |
|
935 //////////////////////////////////////////////////////////////////////////////// |
|
936 //// BackgroundFileSaverOutputStream |
|
937 |
|
938 NS_IMPL_ISUPPORTS(BackgroundFileSaverOutputStream, |
|
939 nsIBackgroundFileSaver, |
|
940 nsIOutputStream, |
|
941 nsIAsyncOutputStream, |
|
942 nsIOutputStreamCallback) |
|
943 |
|
944 BackgroundFileSaverOutputStream::BackgroundFileSaverOutputStream() |
|
945 : BackgroundFileSaver() |
|
946 , mAsyncWaitCallback(nullptr) |
|
947 { |
|
948 } |
|
949 |
|
950 BackgroundFileSaverOutputStream::~BackgroundFileSaverOutputStream() |
|
951 { |
|
952 } |
|
953 |
|
954 bool |
|
955 BackgroundFileSaverOutputStream::HasInfiniteBuffer() |
|
956 { |
|
957 return false; |
|
958 } |
|
959 |
|
960 nsAsyncCopyProgressFun |
|
961 BackgroundFileSaverOutputStream::GetProgressCallback() |
|
962 { |
|
963 return nullptr; |
|
964 } |
|
965 |
|
966 NS_IMETHODIMP |
|
967 BackgroundFileSaverOutputStream::Close() |
|
968 { |
|
969 return mPipeOutputStream->Close(); |
|
970 } |
|
971 |
|
972 NS_IMETHODIMP |
|
973 BackgroundFileSaverOutputStream::Flush() |
|
974 { |
|
975 return mPipeOutputStream->Flush(); |
|
976 } |
|
977 |
|
978 NS_IMETHODIMP |
|
979 BackgroundFileSaverOutputStream::Write(const char *aBuf, uint32_t aCount, |
|
980 uint32_t *_retval) |
|
981 { |
|
982 return mPipeOutputStream->Write(aBuf, aCount, _retval); |
|
983 } |
|
984 |
|
985 NS_IMETHODIMP |
|
986 BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream *aFromStream, |
|
987 uint32_t aCount, uint32_t *_retval) |
|
988 { |
|
989 return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval); |
|
990 } |
|
991 |
|
992 NS_IMETHODIMP |
|
993 BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader, |
|
994 void *aClosure, uint32_t aCount, |
|
995 uint32_t *_retval) |
|
996 { |
|
997 return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval); |
|
998 } |
|
999 |
|
1000 NS_IMETHODIMP |
|
1001 BackgroundFileSaverOutputStream::IsNonBlocking(bool *_retval) |
|
1002 { |
|
1003 return mPipeOutputStream->IsNonBlocking(_retval); |
|
1004 } |
|
1005 |
|
1006 NS_IMETHODIMP |
|
1007 BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) |
|
1008 { |
|
1009 return mPipeOutputStream->CloseWithStatus(reason); |
|
1010 } |
|
1011 |
|
1012 NS_IMETHODIMP |
|
1013 BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback, |
|
1014 uint32_t aFlags, |
|
1015 uint32_t aRequestedCount, |
|
1016 nsIEventTarget *aEventTarget) |
|
1017 { |
|
1018 NS_ENSURE_STATE(!mAsyncWaitCallback); |
|
1019 |
|
1020 mAsyncWaitCallback = aCallback; |
|
1021 |
|
1022 return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount, |
|
1023 aEventTarget); |
|
1024 } |
|
1025 |
|
1026 NS_IMETHODIMP |
|
1027 BackgroundFileSaverOutputStream::OnOutputStreamReady( |
|
1028 nsIAsyncOutputStream *aStream) |
|
1029 { |
|
1030 NS_ENSURE_STATE(mAsyncWaitCallback); |
|
1031 |
|
1032 nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr; |
|
1033 asyncWaitCallback.swap(mAsyncWaitCallback); |
|
1034 |
|
1035 return asyncWaitCallback->OnOutputStreamReady(this); |
|
1036 } |
|
1037 |
|
1038 //////////////////////////////////////////////////////////////////////////////// |
|
1039 //// BackgroundFileSaverStreamListener |
|
1040 |
|
1041 NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, |
|
1042 nsIBackgroundFileSaver, |
|
1043 nsIRequestObserver, |
|
1044 nsIStreamListener) |
|
1045 |
|
1046 BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener() |
|
1047 : BackgroundFileSaver() |
|
1048 , mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock") |
|
1049 , mReceivedTooMuchData(false) |
|
1050 , mRequest(nullptr) |
|
1051 , mRequestSuspended(false) |
|
1052 { |
|
1053 } |
|
1054 |
|
1055 BackgroundFileSaverStreamListener::~BackgroundFileSaverStreamListener() |
|
1056 { |
|
1057 } |
|
1058 |
|
1059 bool |
|
1060 BackgroundFileSaverStreamListener::HasInfiniteBuffer() |
|
1061 { |
|
1062 return true; |
|
1063 } |
|
1064 |
|
1065 nsAsyncCopyProgressFun |
|
1066 BackgroundFileSaverStreamListener::GetProgressCallback() |
|
1067 { |
|
1068 return AsyncCopyProgressCallback; |
|
1069 } |
|
1070 |
|
1071 NS_IMETHODIMP |
|
1072 BackgroundFileSaverStreamListener::OnStartRequest(nsIRequest *aRequest, |
|
1073 nsISupports *aContext) |
|
1074 { |
|
1075 NS_ENSURE_ARG(aRequest); |
|
1076 |
|
1077 return NS_OK; |
|
1078 } |
|
1079 |
|
1080 NS_IMETHODIMP |
|
1081 BackgroundFileSaverStreamListener::OnStopRequest(nsIRequest *aRequest, |
|
1082 nsISupports *aContext, |
|
1083 nsresult aStatusCode) |
|
1084 { |
|
1085 // If an error occurred, cancel the operation immediately. On success, wait |
|
1086 // until the caller has determined whether the file should be renamed. |
|
1087 if (NS_FAILED(aStatusCode)) { |
|
1088 Finish(aStatusCode); |
|
1089 } |
|
1090 |
|
1091 return NS_OK; |
|
1092 } |
|
1093 |
|
1094 NS_IMETHODIMP |
|
1095 BackgroundFileSaverStreamListener::OnDataAvailable(nsIRequest *aRequest, |
|
1096 nsISupports *aContext, |
|
1097 nsIInputStream *aInputStream, |
|
1098 uint64_t aOffset, |
|
1099 uint32_t aCount) |
|
1100 { |
|
1101 nsresult rv; |
|
1102 |
|
1103 NS_ENSURE_ARG(aRequest); |
|
1104 |
|
1105 // Read the requested data. Since the pipe has an infinite buffer, we don't |
|
1106 // expect any write error to occur here. |
|
1107 uint32_t writeCount; |
|
1108 rv = mPipeOutputStream->WriteFrom(aInputStream, aCount, &writeCount); |
|
1109 NS_ENSURE_SUCCESS(rv, rv); |
|
1110 |
|
1111 // If reading from the input stream fails for any reason, the pipe will return |
|
1112 // a success code, but without reading all the data. Since we should be able |
|
1113 // to read the requested data when OnDataAvailable is called, raise an error. |
|
1114 if (writeCount < aCount) { |
|
1115 NS_WARNING("Reading from the input stream should not have failed."); |
|
1116 return NS_ERROR_UNEXPECTED; |
|
1117 } |
|
1118 |
|
1119 bool stateChanged = false; |
|
1120 { |
|
1121 MutexAutoLock lock(mSuspensionLock); |
|
1122 |
|
1123 if (!mReceivedTooMuchData) { |
|
1124 uint64_t available; |
|
1125 nsresult rv = mPipeInputStream->Available(&available); |
|
1126 if (NS_SUCCEEDED(rv) && available > REQUEST_SUSPEND_AT) { |
|
1127 mReceivedTooMuchData = true; |
|
1128 mRequest = aRequest; |
|
1129 stateChanged = true; |
|
1130 } |
|
1131 } |
|
1132 } |
|
1133 |
|
1134 if (stateChanged) { |
|
1135 NotifySuspendOrResume(); |
|
1136 } |
|
1137 |
|
1138 return NS_OK; |
|
1139 } |
|
1140 |
|
1141 // Called on the worker thread. |
|
1142 // static |
|
1143 void |
|
1144 BackgroundFileSaverStreamListener::AsyncCopyProgressCallback(void *aClosure, |
|
1145 uint32_t aCount) |
|
1146 { |
|
1147 BackgroundFileSaverStreamListener *self = |
|
1148 (BackgroundFileSaverStreamListener *)aClosure; |
|
1149 |
|
1150 // Wait if the control thread is in the process of suspending or resuming. |
|
1151 MutexAutoLock lock(self->mSuspensionLock); |
|
1152 |
|
1153 // This function is called when some bytes are consumed by NS_AsyncCopy. Each |
|
1154 // time this happens, verify if a suspended request should be resumed, because |
|
1155 // we have now consumed enough data. |
|
1156 if (self->mReceivedTooMuchData) { |
|
1157 uint64_t available; |
|
1158 nsresult rv = self->mPipeInputStream->Available(&available); |
|
1159 if (NS_FAILED(rv) || available < REQUEST_RESUME_AT) { |
|
1160 self->mReceivedTooMuchData = false; |
|
1161 |
|
1162 // Post an event to verify if the request should be resumed. |
|
1163 nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(self, |
|
1164 &BackgroundFileSaverStreamListener::NotifySuspendOrResume); |
|
1165 if (!event || NS_FAILED(self->mControlThread->Dispatch(event, |
|
1166 NS_DISPATCH_NORMAL))) { |
|
1167 NS_WARNING("Unable to post resume event to the control thread."); |
|
1168 } |
|
1169 } |
|
1170 } |
|
1171 } |
|
1172 |
|
1173 // Called on the control thread. |
|
1174 nsresult |
|
1175 BackgroundFileSaverStreamListener::NotifySuspendOrResume() |
|
1176 { |
|
1177 // Prevent the worker thread from changing state while processing. |
|
1178 MutexAutoLock lock(mSuspensionLock); |
|
1179 |
|
1180 if (mReceivedTooMuchData) { |
|
1181 if (!mRequestSuspended) { |
|
1182 // Try to suspend the request. If this fails, don't try to resume later. |
|
1183 if (NS_SUCCEEDED(mRequest->Suspend())) { |
|
1184 mRequestSuspended = true; |
|
1185 } else { |
|
1186 NS_WARNING("Unable to suspend the request."); |
|
1187 } |
|
1188 } |
|
1189 } else { |
|
1190 if (mRequestSuspended) { |
|
1191 // Resume the request only if we succeeded in suspending it. |
|
1192 if (NS_SUCCEEDED(mRequest->Resume())) { |
|
1193 mRequestSuspended = false; |
|
1194 } else { |
|
1195 NS_WARNING("Unable to resume the request."); |
|
1196 } |
|
1197 } |
|
1198 } |
|
1199 |
|
1200 return NS_OK; |
|
1201 } |
|
1202 |
|
1203 //////////////////////////////////////////////////////////////////////////////// |
|
1204 //// DigestOutputStream |
|
1205 NS_IMPL_ISUPPORTS(DigestOutputStream, |
|
1206 nsIOutputStream) |
|
1207 |
|
1208 DigestOutputStream::DigestOutputStream(nsIOutputStream* aStream, |
|
1209 PK11Context* aContext) : |
|
1210 mOutputStream(aStream) |
|
1211 , mDigestContext(aContext) |
|
1212 { |
|
1213 MOZ_ASSERT(mDigestContext, "Can't have null digest context"); |
|
1214 MOZ_ASSERT(mOutputStream, "Can't have null output stream"); |
|
1215 } |
|
1216 |
|
1217 DigestOutputStream::~DigestOutputStream() |
|
1218 { |
|
1219 shutdown(calledFromObject); |
|
1220 } |
|
1221 |
|
1222 NS_IMETHODIMP |
|
1223 DigestOutputStream::Close() |
|
1224 { |
|
1225 return mOutputStream->Close(); |
|
1226 } |
|
1227 |
|
1228 NS_IMETHODIMP |
|
1229 DigestOutputStream::Flush() |
|
1230 { |
|
1231 return mOutputStream->Flush(); |
|
1232 } |
|
1233 |
|
1234 NS_IMETHODIMP |
|
1235 DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) |
|
1236 { |
|
1237 nsNSSShutDownPreventionLock lock; |
|
1238 if (isAlreadyShutDown()) { |
|
1239 return NS_ERROR_NOT_AVAILABLE; |
|
1240 } |
|
1241 |
|
1242 nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext, |
|
1243 uint8_t_ptr_cast(aBuf), aCount)); |
|
1244 NS_ENSURE_SUCCESS(rv, rv); |
|
1245 |
|
1246 return mOutputStream->Write(aBuf, aCount, retval); |
|
1247 } |
|
1248 |
|
1249 NS_IMETHODIMP |
|
1250 DigestOutputStream::WriteFrom(nsIInputStream* aFromStream, |
|
1251 uint32_t aCount, uint32_t* retval) |
|
1252 { |
|
1253 // Not supported. We could read the stream to a buf, call DigestOp on the |
|
1254 // result, seek back and pass the stream on, but it's not worth it since our |
|
1255 // application (NS_AsyncCopy) doesn't invoke this on the sink. |
|
1256 MOZ_CRASH("DigestOutputStream::WriteFrom not implemented"); |
|
1257 } |
|
1258 |
|
1259 NS_IMETHODIMP |
|
1260 DigestOutputStream::WriteSegments(nsReadSegmentFun aReader, |
|
1261 void *aClosure, uint32_t aCount, |
|
1262 uint32_t *retval) |
|
1263 { |
|
1264 MOZ_CRASH("DigestOutputStream::WriteSegments not implemented"); |
|
1265 } |
|
1266 |
|
1267 NS_IMETHODIMP |
|
1268 DigestOutputStream::IsNonBlocking(bool *retval) |
|
1269 { |
|
1270 return mOutputStream->IsNonBlocking(retval); |
|
1271 } |
|
1272 |
|
1273 } // namespace net |
|
1274 } // namespace mozilla |