1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsIOService.h"
8 #include "nsFileChannel.h"
9 #include "nsBaseContentStream.h"
10 #include "nsDirectoryIndexStream.h"
11 #include "nsThreadUtils.h"
12 #include "nsTransportUtils.h"
13 #include "nsStreamUtils.h"
14 #include "nsMimeTypes.h"
15 #include "nsNetUtil.h"
17 #include "nsIOutputStream.h"
18 #include "nsIFileStreams.h"
19 #include "nsFileProtocolHandler.h"
20 #include "nsProxyRelease.h"
21 #include "nsIContentPolicy.h"
22 #include "nsContentUtils.h"
23 #include "mozilla/dom/ContentParent.h"
25 #include "nsIFileURL.h"
26 #include "nsIURIMutator.h"
28 #include "nsIMIMEService.h"
32 #include "mozilla/TaskQueue.h"
33 #include "mozilla/Unused.h"
35 using namespace mozilla
;
36 using namespace mozilla::net
;
38 //-----------------------------------------------------------------------------
40 class nsFileCopyEvent
: public Runnable
{
42 nsFileCopyEvent(nsIOutputStream
* dest
, nsIInputStream
* source
, int64_t len
)
43 : mozilla::Runnable("nsFileCopyEvent"),
48 mInterruptStatus(NS_OK
) {}
50 // Read the current status of the file copy operation.
51 nsresult
Status() { return mStatus
; }
53 // Call this method to perform the file copy synchronously.
56 // Call this method to perform the file copy on a background thread. The
57 // callback is dispatched when the file copy completes.
58 nsresult
Dispatch(nsIRunnable
* callback
, nsITransportEventSink
* sink
,
59 nsIEventTarget
* target
);
61 // Call this method to interrupt a file copy operation that is occuring on
62 // a background thread. The status parameter passed to this function must
63 // be a failure code and is set as the status of this file copy operation.
64 void Interrupt(nsresult status
) {
65 NS_ASSERTION(NS_FAILED(status
), "must be a failure code");
66 mInterruptStatus
= status
;
69 NS_IMETHOD
Run() override
{
75 nsCOMPtr
<nsIEventTarget
> mCallbackTarget
;
76 nsCOMPtr
<nsIRunnable
> mCallback
;
77 nsCOMPtr
<nsITransportEventSink
> mSink
;
78 nsCOMPtr
<nsIOutputStream
> mDest
;
79 nsCOMPtr
<nsIInputStream
> mSource
;
81 nsresult mStatus
; // modified on i/o thread only
82 nsresult mInterruptStatus
; // modified on main thread only
85 void nsFileCopyEvent::DoCopy() {
86 // We'll copy in chunks this large by default. This size affects how
87 // frequently we'll check for interrupts.
89 nsIOService::gDefaultSegmentSize
* nsIOService::gDefaultSegmentCount
;
93 int64_t len
= mLen
, progress
= 0;
95 // If we've been interrupted, then stop copying.
96 rv
= mInterruptStatus
;
97 if (NS_FAILED(rv
)) break;
99 int32_t num
= std::min((int32_t)len
, chunk
);
102 rv
= mSource
->ReadSegments(NS_CopySegmentToStream
, mDest
, num
, &result
);
103 if (NS_FAILED(rv
)) break;
104 if (result
!= (uint32_t)num
) {
105 // stopped prematurely (out of disk space)
106 rv
= NS_ERROR_FILE_NO_DEVICE_SPACE
;
110 // Dispatch progress notification
113 mSink
->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING
, progress
, mLen
);
119 if (NS_FAILED(rv
)) mStatus
= rv
;
121 // Close the output stream before notifying our callback so that others may
122 // freely "play" with the file.
127 mCallbackTarget
->Dispatch(mCallback
, NS_DISPATCH_NORMAL
);
129 // Release the callback on the target thread to avoid destroying stuff on
131 NS_ProxyRelease("nsFileCopyEvent::mCallback", mCallbackTarget
,
136 nsresult
nsFileCopyEvent::Dispatch(nsIRunnable
* callback
,
137 nsITransportEventSink
* sink
,
138 nsIEventTarget
* target
) {
139 // Use the supplied event target for all asynchronous operations.
141 mCallback
= callback
;
142 mCallbackTarget
= target
;
144 // Build a coalescing proxy for progress events
146 net_NewTransportEventSinkProxy(getter_AddRefs(mSink
), sink
, target
);
148 if (NS_FAILED(rv
)) return rv
;
150 // Dispatch ourselves to I/O thread pool...
151 nsCOMPtr
<nsIEventTarget
> pool
=
152 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
, &rv
);
153 if (NS_FAILED(rv
)) return rv
;
155 return pool
->Dispatch(this, NS_DISPATCH_NORMAL
);
158 //-----------------------------------------------------------------------------
160 // This is a dummy input stream that when read, performs the file copy. The
161 // copy happens on a background thread via mCopyEvent.
163 class nsFileUploadContentStream
: public nsBaseContentStream
{
165 NS_INLINE_DECL_REFCOUNTING_INHERITED(nsFileUploadContentStream
,
168 nsFileUploadContentStream(bool nonBlocking
, nsIOutputStream
* dest
,
169 nsIInputStream
* source
, int64_t len
,
170 nsITransportEventSink
* sink
)
171 : nsBaseContentStream(nonBlocking
),
172 mCopyEvent(new nsFileCopyEvent(dest
, source
, len
)),
175 bool IsInitialized() { return mCopyEvent
!= nullptr; }
177 NS_IMETHOD
ReadSegments(nsWriteSegmentFun fun
, void* closure
, uint32_t count
,
178 uint32_t* result
) override
;
179 NS_IMETHOD
AsyncWait(nsIInputStreamCallback
* callback
, uint32_t flags
,
180 uint32_t count
, nsIEventTarget
* target
) override
;
183 virtual ~nsFileUploadContentStream() = default;
185 void OnCopyComplete();
187 RefPtr
<nsFileCopyEvent
> mCopyEvent
;
188 nsCOMPtr
<nsITransportEventSink
> mSink
;
192 nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun
, void* closure
,
193 uint32_t count
, uint32_t* result
) {
194 *result
= 0; // nothing is ever actually read from this stream
196 if (IsClosed()) return NS_OK
;
198 if (IsNonBlocking()) {
199 // Inform the caller that they will have to wait for the copy operation to
200 // complete asynchronously. We'll kick of the copy operation once they
202 return NS_BASE_STREAM_WOULD_BLOCK
;
205 // Perform copy synchronously, and then close out the stream.
206 mCopyEvent
->DoCopy();
207 nsresult status
= mCopyEvent
->Status();
208 CloseWithStatus(NS_FAILED(status
) ? status
: NS_BASE_STREAM_CLOSED
);
213 nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback
* callback
,
214 uint32_t flags
, uint32_t count
,
215 nsIEventTarget
* target
) {
216 nsresult rv
= nsBaseContentStream::AsyncWait(callback
, flags
, count
, target
);
217 if (NS_FAILED(rv
) || IsClosed()) return rv
;
219 if (IsNonBlocking()) {
220 nsCOMPtr
<nsIRunnable
> callback
=
221 NewRunnableMethod("nsFileUploadContentStream::OnCopyComplete", this,
222 &nsFileUploadContentStream::OnCopyComplete
);
223 mCopyEvent
->Dispatch(callback
, mSink
, target
);
229 void nsFileUploadContentStream::OnCopyComplete() {
230 // This method is being called to indicate that we are done copying.
231 nsresult status
= mCopyEvent
->Status();
233 CloseWithStatus(NS_FAILED(status
) ? status
: NS_BASE_STREAM_CLOSED
);
236 //-----------------------------------------------------------------------------
238 nsFileChannel::nsFileChannel(nsIURI
* uri
) : mUploadLength(0), mFileURI(uri
) {}
240 nsresult
nsFileChannel::Init() {
241 NS_ENSURE_STATE(mLoadInfo
);
243 // If we have a link file, we should resolve its target right away.
244 // This is to protect against a same origin attack where the same link file
245 // can point to different resources right after the first resource is loaded.
246 nsCOMPtr
<nsIFile
> file
;
247 nsCOMPtr
<nsIURI
> targetURI
;
249 nsAutoString fileTarget
;
251 nsAutoCString fileTarget
;
253 nsCOMPtr
<nsIFile
> resolvedFile
;
255 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(mFileURI
);
256 if (fileURL
&& NS_SUCCEEDED(fileURL
->GetFile(getter_AddRefs(file
))) &&
257 NS_SUCCEEDED(file
->IsSymlink(&symLink
)) && symLink
&&
259 NS_SUCCEEDED(file
->GetTarget(fileTarget
)) &&
261 NS_NewLocalFile(fileTarget
, true, getter_AddRefs(resolvedFile
))) &&
263 NS_SUCCEEDED(file
->GetNativeTarget(fileTarget
)) &&
264 NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget
, true,
265 getter_AddRefs(resolvedFile
))) &&
268 NS_NewFileURI(getter_AddRefs(targetURI
), resolvedFile
, nullptr))) {
269 // Make an effort to match up the query strings.
270 nsCOMPtr
<nsIURL
> origURL
= do_QueryInterface(mFileURI
);
271 nsCOMPtr
<nsIURL
> targetURL
= do_QueryInterface(targetURI
);
272 nsAutoCString queryString
;
273 if (origURL
&& targetURL
&& NS_SUCCEEDED(origURL
->GetQuery(queryString
))) {
275 << NS_MutateURI(targetURI
).SetQuery(queryString
).Finalize(targetURI
);
279 SetOriginalURI(mFileURI
);
280 mLoadInfo
->SetResultPrincipalURI(targetURI
);
288 nsresult
nsFileChannel::MakeFileInputStream(nsIFile
* file
,
289 nsCOMPtr
<nsIInputStream
>& stream
,
290 nsCString
& contentType
,
292 // we accept that this might result in a disk hit to stat the file
294 nsresult rv
= file
->IsDirectory(&isDir
);
296 if (rv
== NS_ERROR_FILE_NOT_FOUND
) {
297 CheckForBrokenChromeURL(mLoadInfo
, OriginalURI());
300 if (async
&& (NS_ERROR_FILE_NOT_FOUND
== rv
)) {
301 // We don't return "Not Found" errors here. Since we could not find
302 // the file, it's not a directory anyway.
310 rv
= nsDirectoryIndexStream::Create(file
, getter_AddRefs(stream
));
311 if (NS_SUCCEEDED(rv
) && !HasContentTypeHint()) {
312 contentType
.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT
);
315 rv
= NS_NewLocalFileInputStream(getter_AddRefs(stream
), file
, -1, -1,
316 async
? nsIFileInputStream::DEFER_OPEN
: 0);
317 if (NS_SUCCEEDED(rv
) && !HasContentTypeHint()) {
318 // Use file extension to infer content type
319 nsCOMPtr
<nsIMIMEService
> mime
= do_GetService("@mozilla.org/mime;1", &rv
);
320 if (NS_SUCCEEDED(rv
)) {
321 mime
->GetTypeFromFile(file
, contentType
);
328 nsresult
nsFileChannel::OpenContentStream(bool async
, nsIInputStream
** result
,
329 nsIChannel
** channel
) {
330 // NOTE: the resulting file is a clone, so it is safe to pass it to the
331 // file input stream which will be read on a background thread.
332 nsCOMPtr
<nsIFile
> file
;
333 nsresult rv
= GetFile(getter_AddRefs(file
));
334 if (NS_FAILED(rv
)) return rv
;
336 nsCOMPtr
<nsIFileProtocolHandler
> fileHandler
;
337 rv
= NS_GetFileProtocolHandler(getter_AddRefs(fileHandler
));
338 if (NS_FAILED(rv
)) return rv
;
340 nsCOMPtr
<nsIURI
> newURI
;
341 if (NS_SUCCEEDED(fileHandler
->ReadURLFile(file
, getter_AddRefs(newURI
))) ||
342 NS_SUCCEEDED(fileHandler
->ReadShellLink(file
, getter_AddRefs(newURI
)))) {
343 nsCOMPtr
<nsIChannel
> newChannel
;
344 rv
= NS_NewChannel(getter_AddRefs(newChannel
), newURI
,
345 nsContentUtils::GetSystemPrincipal(),
346 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
347 nsIContentPolicy::TYPE_OTHER
);
349 if (NS_FAILED(rv
)) return rv
;
352 newChannel
.forget(channel
);
356 nsCOMPtr
<nsIInputStream
> stream
;
359 // Pass back a nsFileUploadContentStream instance that knows how to perform
360 // the file copy when "read" (the resulting stream in this case does not
361 // actually return any data).
363 nsCOMPtr
<nsIOutputStream
> fileStream
;
364 rv
= NS_NewLocalFileOutputStream(getter_AddRefs(fileStream
), file
,
365 PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
,
366 PR_IRUSR
| PR_IWUSR
);
367 if (NS_FAILED(rv
)) return rv
;
369 RefPtr
<nsFileUploadContentStream
> uploadStream
=
370 new nsFileUploadContentStream(async
, fileStream
, mUploadStream
,
371 mUploadLength
, this);
372 if (!uploadStream
|| !uploadStream
->IsInitialized()) {
373 return NS_ERROR_OUT_OF_MEMORY
;
375 stream
= std::move(uploadStream
);
379 // Since there isn't any content to speak of we just set the content-type
380 // to something other than "unknown" to avoid triggering the content-type
381 // sniffer code in nsBaseChannel.
382 // However, don't override explicitly set types.
383 if (!HasContentTypeHint()) {
384 SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM
));
387 nsAutoCString contentType
;
388 rv
= MakeFileInputStream(file
, stream
, contentType
, async
);
389 if (NS_FAILED(rv
)) return rv
;
391 EnableSynthesizedProgressEvents(true);
393 // fixup content length and type
395 // when we are called from asyncOpen, the content length fixup will be
396 // performed on a background thread and block the listener invocation via
397 // ListenerBlockingPromise method
398 if (!async
&& mContentLength
< 0) {
399 rv
= FixupContentLength(false);
405 if (!contentType
.IsEmpty()) {
406 SetContentType(contentType
);
410 // notify "file-channel-opened" observers
411 MaybeSendFileOpenNotification();
414 stream
.swap(*result
);
418 nsresult
nsFileChannel::ListenerBlockingPromise(BlockingPromise
** aPromise
) {
419 NS_ENSURE_ARG(aPromise
);
422 if (mContentLength
>= 0) {
426 nsCOMPtr
<nsIEventTarget
> sts(
427 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
));
429 return FixupContentLength(true);
432 RefPtr
<TaskQueue
> taskQueue
= TaskQueue::Create(sts
.forget(), "FileChannel");
433 RefPtr
<nsFileChannel
> self
= this;
434 RefPtr
<BlockingPromise
> promise
=
435 mozilla::InvokeAsync(taskQueue
, __func__
, [self
{std::move(self
)}]() {
436 nsresult rv
= self
->FixupContentLength(true);
438 return BlockingPromise::CreateAndReject(rv
, __func__
);
440 return BlockingPromise::CreateAndResolve(NS_OK
, __func__
);
443 promise
.forget(aPromise
);
447 nsresult
nsFileChannel::FixupContentLength(bool async
) {
448 MOZ_ASSERT(mContentLength
< 0);
450 nsCOMPtr
<nsIFile
> file
;
451 nsresult rv
= GetFile(getter_AddRefs(file
));
457 rv
= file
->GetFileSize(&size
);
459 if (async
&& NS_ERROR_FILE_NOT_FOUND
== rv
) {
465 mContentLength
= size
;
470 //-----------------------------------------------------------------------------
471 // nsFileChannel::nsISupports
473 NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel
, nsBaseChannel
, nsIUploadChannel
,
476 //-----------------------------------------------------------------------------
477 // nsFileChannel::nsIFileChannel
480 nsFileChannel::GetFile(nsIFile
** file
) {
481 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(URI());
482 NS_ENSURE_STATE(fileURL
);
484 // This returns a cloned nsIFile
485 return fileURL
->GetFile(file
);
488 nsresult
nsFileChannel::MaybeSendFileOpenNotification() {
489 nsCOMPtr
<nsIObserverService
> obsService
= services::GetObserverService();
494 nsCOMPtr
<nsILoadInfo
> loadInfo
;
495 nsresult rv
= GetLoadInfo(getter_AddRefs(loadInfo
));
501 rv
= loadInfo
->GetIsTopLevelLoad(&isTopLevel
);
506 uint64_t browsingContextID
;
507 rv
= loadInfo
->GetBrowsingContextID(&browsingContextID
);
512 if ((browsingContextID
!= 0 && isTopLevel
) ||
513 !loadInfo
->TriggeringPrincipal()->IsSystemPrincipal()) {
514 obsService
->NotifyObservers(static_cast<nsIChannel
*>(this),
515 "file-channel-opened", nullptr);
520 //-----------------------------------------------------------------------------
521 // nsFileChannel::nsIUploadChannel
524 nsFileChannel::SetUploadStream(nsIInputStream
* stream
,
525 const nsACString
& contentType
,
526 int64_t contentLength
) {
527 NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS
);
529 if ((mUploadStream
= stream
)) {
530 mUploadLength
= contentLength
;
531 if (mUploadLength
< 0) {
532 // Make sure we know how much data we are uploading.
534 nsresult rv
= mUploadStream
->Available(&avail
);
535 if (NS_FAILED(rv
)) return rv
;
536 // if this doesn't fit in the javascript MAX_SAFE_INTEGER
537 // pretend we don't know the size
538 mUploadLength
= InScriptableRange(avail
) ? avail
: -1;
547 nsFileChannel::GetUploadStream(nsIInputStream
** result
) {
548 *result
= do_AddRef(mUploadStream
).take();