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