summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/file
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/protocol/file
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/file/FileChannelChild.cpp52
-rw-r--r--netwerk/protocol/file/FileChannelChild.h35
-rw-r--r--netwerk/protocol/file/FileChannelParent.cpp105
-rw-r--r--netwerk/protocol/file/FileChannelParent.h39
-rw-r--r--netwerk/protocol/file/moz.build40
-rw-r--r--netwerk/protocol/file/nsFileChannel.cpp569
-rw-r--r--netwerk/protocol/file/nsFileChannel.h59
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.cpp266
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.h28
-rw-r--r--netwerk/protocol/file/nsIFileChannel.idl17
-rw-r--r--netwerk/protocol/file/nsIFileProtocolHandler.idl102
11 files changed, 1312 insertions, 0 deletions
diff --git a/netwerk/protocol/file/FileChannelChild.cpp b/netwerk/protocol/file/FileChannelChild.cpp
new file mode 100644
index 0000000000..fa9cc00584
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelChild.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileChannelChild.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(FileChannelChild, nsFileChannel, nsIChildChannel)
+
+FileChannelChild::FileChannelChild(nsIURI* uri) : nsFileChannel(uri) {}
+
+NS_IMETHODIMP
+FileChannelChild::ConnectParent(uint32_t id) {
+ mozilla::dom::ContentChild* cc =
+ static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gNeckoChild->SendPFileChannelConstructor(this, id)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelChild::CompleteRedirectSetup(nsIStreamListener* listener) {
+ nsresult rv;
+
+ rv = AsyncOpen(listener);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (CanSend()) {
+ Unused << Send__delete__(this);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/file/FileChannelChild.h b/netwerk/protocol/file/FileChannelChild.h
new file mode 100644
index 0000000000..e6d67b7d1c
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelChild.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__FileChannelChild_h
+#define mozilla__net__FileChannelChild_h
+
+#include "nsFileChannel.h"
+#include "nsIChildChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PFileChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+class FileChannelChild : public nsFileChannel,
+ public nsIChildChannel,
+ public PFileChannelChild {
+ public:
+ explicit FileChannelChild(nsIURI* uri);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICHILDCHANNEL
+
+ private:
+ ~FileChannelChild() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* mozilla__net__FileChannelChild_h */
diff --git a/netwerk/protocol/file/FileChannelParent.cpp b/netwerk/protocol/file/FileChannelParent.cpp
new file mode 100644
index 0000000000..e54fb11260
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelParent.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileChannelParent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+
+#ifdef FUZZING_SNAPSHOT
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) (void)__VA_ARGS__
+#else
+# define MOZ_ALWAYS_SUCCEEDS_FUZZING(...) MOZ_ALWAYS_SUCCEEDS(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(FileChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool FileChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+
+ MOZ_ALWAYS_SUCCEEDS_FUZZING(
+ NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel)));
+
+ return true;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // nothing to do
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::Delete() {
+ // Nothing to do.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+void FileChannelParent::ActorDestroy(ActorDestroyReason why) {}
+
+NS_IMETHODIMP
+FileChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ // We don't have a way to prevent nsBaseChannel from calling AsyncOpen on
+ // the created nsDataChannel. We don't have anywhere to send the data in the
+ // parent, so abort the binding.
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+FileChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // See above.
+ MOZ_ASSERT(NS_FAILED(aStatusCode));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // See above.
+ MOZ_CRASH("Should never be called");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/file/FileChannelParent.h b/netwerk/protocol/file/FileChannelParent.h
new file mode 100644
index 0000000000..f2a10d8f87
--- /dev/null
+++ b/netwerk/protocol/file/FileChannelParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla__net__FileChannelParent_h
+#define mozilla__net__FileChannelParent_h
+
+#include "nsIParentChannel.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/net/PFileChannelParent.h"
+
+namespace mozilla {
+namespace net {
+
+// In order to support HTTP redirects to file:, we need to implement the HTTP
+// redirection API, which requires a class that implements nsIParentChannel
+// and which calls NS_LinkRedirectChannels.
+class FileChannelParent : public nsIParentChannel, public PFileChannelParent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ [[nodiscard]] bool Init(const uint64_t& aChannelId);
+
+ private:
+ ~FileChannelParent() = default;
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* mozilla__net__FileChannelParent_h */
diff --git a/netwerk/protocol/file/moz.build b/netwerk/protocol/file/moz.build
new file mode 100644
index 0000000000..821e20e1c5
--- /dev/null
+++ b/netwerk/protocol/file/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: File")
+
+EXPORTS.mozilla.net += [
+ "FileChannelChild.h",
+ "FileChannelParent.h",
+ "nsFileProtocolHandler.h",
+]
+
+EXPORTS += [
+ "nsFileChannel.h",
+]
+
+XPIDL_SOURCES += [
+ "nsIFileChannel.idl",
+ "nsIFileProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_file"
+
+UNIFIED_SOURCES += [
+ "FileChannelChild.cpp",
+ "FileChannelParent.cpp",
+ "nsFileChannel.cpp",
+ "nsFileProtocolHandler.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/netwerk/protocol/file/nsFileChannel.cpp b/netwerk/protocol/file/nsFileChannel.cpp
new file mode 100644
index 0000000000..096f8807ba
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOService.h"
+#include "nsFileChannel.h"
+#include "nsBaseContentStream.h"
+#include "nsDirectoryIndexStream.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIOutputStream.h"
+#include "nsIFileStreams.h"
+#include "nsFileProtocolHandler.h"
+#include "nsProxyRelease.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "../protocol/http/nsHttpHandler.h"
+
+#include "nsIFileURL.h"
+#include "nsIURIMutator.h"
+#include "nsIFile.h"
+#include "nsIMIMEService.h"
+#include "prio.h"
+#include <algorithm>
+
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+//-----------------------------------------------------------------------------
+
+class nsFileCopyEvent : public Runnable {
+ public:
+ nsFileCopyEvent(nsIOutputStream* dest, nsIInputStream* source, int64_t len)
+ : mozilla::Runnable("nsFileCopyEvent"),
+ mDest(dest),
+ mSource(source),
+ mLen(len),
+ mStatus(NS_OK),
+ mInterruptStatus(NS_OK) {}
+
+ // Read the current status of the file copy operation.
+ nsresult Status() { return mStatus; }
+
+ // Call this method to perform the file copy synchronously.
+ void DoCopy();
+
+ // Call this method to perform the file copy on a background thread. The
+ // callback is dispatched when the file copy completes.
+ nsresult Dispatch(nsIRunnable* callback, nsITransportEventSink* sink,
+ nsIEventTarget* target);
+
+ // Call this method to interrupt a file copy operation that is occuring on
+ // a background thread. The status parameter passed to this function must
+ // be a failure code and is set as the status of this file copy operation.
+ void Interrupt(nsresult status) {
+ NS_ASSERTION(NS_FAILED(status), "must be a failure code");
+ mInterruptStatus = status;
+ }
+
+ NS_IMETHOD Run() override {
+ DoCopy();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+ nsCOMPtr<nsIRunnable> mCallback;
+ nsCOMPtr<nsITransportEventSink> mSink;
+ nsCOMPtr<nsIOutputStream> mDest;
+ nsCOMPtr<nsIInputStream> mSource;
+ int64_t mLen;
+ nsresult mStatus; // modified on i/o thread only
+ nsresult mInterruptStatus; // modified on main thread only
+};
+
+void nsFileCopyEvent::DoCopy() {
+ // We'll copy in chunks this large by default. This size affects how
+ // frequently we'll check for interrupts.
+ const int32_t chunk =
+ nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount;
+
+ nsresult rv = NS_OK;
+
+ int64_t len = mLen, progress = 0;
+ while (len) {
+ // If we've been interrupted, then stop copying.
+ rv = mInterruptStatus;
+ if (NS_FAILED(rv)) break;
+
+ int32_t num = std::min((int32_t)len, chunk);
+
+ uint32_t result;
+ rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result);
+ if (NS_FAILED(rv)) break;
+ if (result != (uint32_t)num) {
+ // stopped prematurely (out of disk space)
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ }
+
+ // Dispatch progress notification
+ if (mSink) {
+ progress += num;
+ mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress, mLen);
+ }
+
+ len -= num;
+ }
+
+ if (NS_FAILED(rv)) mStatus = rv;
+
+ // Close the output stream before notifying our callback so that others may
+ // freely "play" with the file.
+ mDest->Close();
+
+ // Notify completion
+ if (mCallback) {
+ mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL);
+
+ // Release the callback on the target thread to avoid destroying stuff on
+ // the wrong thread.
+ NS_ProxyRelease("nsFileCopyEvent::mCallback", mCallbackTarget,
+ mCallback.forget());
+ }
+}
+
+nsresult nsFileCopyEvent::Dispatch(nsIRunnable* callback,
+ nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ // Use the supplied event target for all asynchronous operations.
+
+ mCallback = callback;
+ mCallbackTarget = target;
+
+ // Build a coalescing proxy for progress events
+ nsresult rv =
+ net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, target);
+
+ if (NS_FAILED(rv)) return rv;
+
+ // Dispatch ourselves to I/O thread pool...
+ nsCOMPtr<nsIEventTarget> pool =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ return pool->Dispatch(this, NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+
+// This is a dummy input stream that when read, performs the file copy. The
+// copy happens on a background thread via mCopyEvent.
+
+class nsFileUploadContentStream : public nsBaseContentStream {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsFileUploadContentStream,
+ nsBaseContentStream)
+
+ nsFileUploadContentStream(bool nonBlocking, nsIOutputStream* dest,
+ nsIInputStream* source, int64_t len,
+ nsITransportEventSink* sink)
+ : nsBaseContentStream(nonBlocking),
+ mCopyEvent(new nsFileCopyEvent(dest, source, len)),
+ mSink(sink) {}
+
+ bool IsInitialized() { return mCopyEvent != nullptr; }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void* closure, uint32_t count,
+ uint32_t* result) override;
+ NS_IMETHOD AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t count, nsIEventTarget* target) override;
+
+ private:
+ virtual ~nsFileUploadContentStream() = default;
+
+ void OnCopyComplete();
+
+ RefPtr<nsFileCopyEvent> mCopyEvent;
+ nsCOMPtr<nsITransportEventSink> mSink;
+};
+
+NS_IMETHODIMP
+nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0; // nothing is ever actually read from this stream
+
+ if (IsClosed()) return NS_OK;
+
+ if (IsNonBlocking()) {
+ // Inform the caller that they will have to wait for the copy operation to
+ // complete asynchronously. We'll kick of the copy operation once they
+ // call AsyncWait.
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // Perform copy synchronously, and then close out the stream.
+ mCopyEvent->DoCopy();
+ nsresult status = mCopyEvent->Status();
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+ return status;
+}
+
+NS_IMETHODIMP
+nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback* callback,
+ uint32_t flags, uint32_t count,
+ nsIEventTarget* target) {
+ nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target);
+ if (NS_FAILED(rv) || IsClosed()) return rv;
+
+ if (IsNonBlocking()) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod("nsFileUploadContentStream::OnCopyComplete", this,
+ &nsFileUploadContentStream::OnCopyComplete);
+ mCopyEvent->Dispatch(callback, mSink, target);
+ }
+
+ return NS_OK;
+}
+
+void nsFileUploadContentStream::OnCopyComplete() {
+ // This method is being called to indicate that we are done copying.
+ nsresult status = mCopyEvent->Status();
+
+ CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
+}
+
+//-----------------------------------------------------------------------------
+
+nsFileChannel::nsFileChannel(nsIURI* uri) : mUploadLength(0), mFileURI(uri) {}
+
+nsresult nsFileChannel::Init() {
+ NS_ENSURE_STATE(mLoadInfo);
+
+ RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
+ MOZ_ALWAYS_SUCCEEDS(handler->NewChannelId(mChannelId));
+
+ // If we have a link file, we should resolve its target right away.
+ // This is to protect against a same origin attack where the same link file
+ // can point to different resources right after the first resource is loaded.
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIURI> targetURI;
+#ifdef XP_WIN
+ nsAutoString fileTarget;
+#else
+ nsAutoCString fileTarget;
+#endif
+ nsCOMPtr<nsIFile> resolvedFile;
+ bool symLink;
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mFileURI);
+ if (fileURL && NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->IsSymlink(&symLink)) && symLink &&
+#ifdef XP_WIN
+ NS_SUCCEEDED(file->GetTarget(fileTarget)) &&
+ NS_SUCCEEDED(
+ NS_NewLocalFile(fileTarget, true, getter_AddRefs(resolvedFile))) &&
+#else
+ NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) &&
+ NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, true,
+ getter_AddRefs(resolvedFile))) &&
+#endif
+ NS_SUCCEEDED(
+ NS_NewFileURI(getter_AddRefs(targetURI), resolvedFile, nullptr))) {
+ // Make an effort to match up the query strings.
+ nsCOMPtr<nsIURL> origURL = do_QueryInterface(mFileURI);
+ nsCOMPtr<nsIURL> targetURL = do_QueryInterface(targetURI);
+ nsAutoCString queryString;
+ if (origURL && targetURL && NS_SUCCEEDED(origURL->GetQuery(queryString))) {
+ Unused
+ << NS_MutateURI(targetURI).SetQuery(queryString).Finalize(targetURI);
+ }
+
+ SetURI(targetURI);
+ SetOriginalURI(mFileURI);
+ mLoadInfo->SetResultPrincipalURI(targetURI);
+ } else {
+ SetURI(mFileURI);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFileChannel::MakeFileInputStream(nsIFile* file,
+ nsCOMPtr<nsIInputStream>& stream,
+ nsCString& contentType,
+ bool async) {
+ // we accept that this might result in a disk hit to stat the file
+ bool isDir;
+ nsresult rv = file->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ CheckForBrokenChromeURL(mLoadInfo, OriginalURI());
+ }
+
+ if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) {
+ // We don't return "Not Found" errors here. Since we could not find
+ // the file, it's not a directory anyway.
+ isDir = false;
+ } else {
+ return rv;
+ }
+ }
+
+ if (isDir) {
+ rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream));
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
+ }
+ } else {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
+ async ? nsIFileInputStream::DEFER_OPEN : 0);
+ if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
+ // Use file extension to infer content type
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mime->GetTypeFromFile(file, contentType);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsFileChannel::OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ // NOTE: the resulting file is a clone, so it is safe to pass it to the
+ // file input stream which will be read on a background thread.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFileProtocolHandler> fileHandler;
+ rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> newURI;
+ if (NS_SUCCEEDED(fileHandler->ReadURLFile(file, getter_AddRefs(newURI))) ||
+ NS_SUCCEEDED(fileHandler->ReadShellLink(file, getter_AddRefs(newURI)))) {
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = NS_NewChannel(getter_AddRefs(newChannel), newURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) return rv;
+
+ *result = nullptr;
+ newChannel.forget(channel);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+
+ if (mUploadStream) {
+ // Pass back a nsFileUploadContentStream instance that knows how to perform
+ // the file copy when "read" (the resulting stream in this case does not
+ // actually return any data).
+
+ nsCOMPtr<nsIOutputStream> fileStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsFileUploadContentStream> uploadStream =
+ new nsFileUploadContentStream(async, fileStream, mUploadStream,
+ mUploadLength, this);
+ if (!uploadStream || !uploadStream->IsInitialized()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ stream = std::move(uploadStream);
+
+ mContentLength = 0;
+
+ // Since there isn't any content to speak of we just set the content-type
+ // to something other than "unknown" to avoid triggering the content-type
+ // sniffer code in nsBaseChannel.
+ // However, don't override explicitly set types.
+ if (!HasContentTypeHint()) {
+ SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
+ }
+ } else {
+ nsAutoCString contentType;
+ rv = MakeFileInputStream(file, stream, contentType, async);
+ if (NS_FAILED(rv)) return rv;
+
+ EnableSynthesizedProgressEvents(true);
+
+ // fixup content length and type
+
+ // when we are called from asyncOpen, the content length fixup will be
+ // performed on a background thread and block the listener invocation via
+ // ListenerBlockingPromise method
+ if (!async && mContentLength < 0) {
+ rv = FixupContentLength(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!contentType.IsEmpty()) {
+ SetContentType(contentType);
+ }
+ }
+
+ // notify "file-channel-opened" observers
+ MaybeSendFileOpenNotification();
+
+ *result = nullptr;
+ stream.swap(*result);
+ return NS_OK;
+}
+
+nsresult nsFileChannel::ListenerBlockingPromise(BlockingPromise** aPromise) {
+ NS_ENSURE_ARG(aPromise);
+ *aPromise = nullptr;
+
+ if (mContentLength >= 0) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEventTarget> sts(
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+ if (!sts) {
+ return FixupContentLength(true);
+ }
+
+ RefPtr<TaskQueue> taskQueue = TaskQueue::Create(sts.forget(), "FileChannel");
+ RefPtr<nsFileChannel> self = this;
+ RefPtr<BlockingPromise> promise =
+ mozilla::InvokeAsync(taskQueue, __func__, [self{std::move(self)}]() {
+ nsresult rv = self->FixupContentLength(true);
+ if (NS_FAILED(rv)) {
+ return BlockingPromise::CreateAndReject(rv, __func__);
+ }
+ return BlockingPromise::CreateAndResolve(NS_OK, __func__);
+ });
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+nsresult nsFileChannel::FixupContentLength(bool async) {
+ MOZ_ASSERT(mContentLength < 0);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int64_t size;
+ rv = file->GetFileSize(&size);
+ if (NS_FAILED(rv)) {
+ if (async && NS_ERROR_FILE_NOT_FOUND == rv) {
+ size = 0;
+ } else {
+ return rv;
+ }
+ }
+ mContentLength = size;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, nsBaseChannel, nsIUploadChannel,
+ nsIFileChannel, nsIIdentChannel)
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIFileChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetFile(nsIFile** file) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(URI());
+ NS_ENSURE_STATE(fileURL);
+
+ // This returns a cloned nsIFile
+ return fileURL->GetFile(file);
+}
+
+nsresult nsFileChannel::MaybeSendFileOpenNotification() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (!obsService) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = GetLoadInfo(getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isTopLevel;
+ rv = loadInfo->GetIsTopLevelLoad(&isTopLevel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint64_t browsingContextID;
+ rv = loadInfo->GetBrowsingContextID(&browsingContextID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if ((browsingContextID != 0 && isTopLevel) ||
+ !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ obsService->NotifyObservers(static_cast<nsIIdentChannel*>(this),
+ "file-channel-opened", nullptr);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIUploadChannel
+
+NS_IMETHODIMP
+nsFileChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentType,
+ int64_t contentLength) {
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+
+ if ((mUploadStream = stream)) {
+ mUploadLength = contentLength;
+ if (mUploadLength < 0) {
+ // Make sure we know how much data we are uploading.
+ uint64_t avail;
+ nsresult rv = mUploadStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ // if this doesn't fit in the javascript MAX_SAFE_INTEGER
+ // pretend we don't know the size
+ mUploadLength = InScriptableRange(avail) ? avail : -1;
+ }
+ } else {
+ mUploadLength = -1;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::GetUploadStream(nsIInputStream** result) {
+ *result = do_AddRef(mUploadStream).take();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsIIdentChannel
+
+NS_IMETHODIMP
+nsFileChannel::GetChannelId(uint64_t* aChannelId) {
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
diff --git a/netwerk/protocol/file/nsFileChannel.h b/netwerk/protocol/file/nsFileChannel.h
new file mode 100644
index 0000000000..781ce5b7af
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileChannel_h__
+#define nsFileChannel_h__
+
+#include "nsBaseChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIUploadChannel.h"
+
+class nsFileChannel : public nsBaseChannel,
+ public nsIFileChannel,
+ public nsIUploadChannel,
+ public nsIIdentChannel {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILECHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_FORWARD_NSIREQUEST(nsBaseChannel::)
+ NS_FORWARD_NSICHANNEL(nsBaseChannel::)
+ NS_DECL_NSIIDENTCHANNEL
+
+ explicit nsFileChannel(nsIURI* uri);
+
+ nsresult Init();
+
+ protected:
+ ~nsFileChannel() = default;
+
+ // Called to construct a blocking file input stream for the given file. This
+ // method also returns a best guess at the content-type for the data stream.
+ // NOTE: If the channel has a type hint set, contentType will be left
+ // untouched. The caller should not use it in that case.
+ [[nodiscard]] nsresult MakeFileInputStream(nsIFile* file,
+ nsCOMPtr<nsIInputStream>& stream,
+ nsCString& contentType,
+ bool async);
+
+ [[nodiscard]] virtual nsresult OpenContentStream(
+ bool async, nsIInputStream** result, nsIChannel** channel) override;
+
+ // Implementing the pump blocking promise to fixup content length on a
+ // background thread prior to calling on mListener
+ virtual nsresult ListenerBlockingPromise(BlockingPromise** promise) override;
+
+ private:
+ nsresult FixupContentLength(bool async);
+ nsresult MaybeSendFileOpenNotification();
+
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ int64_t mUploadLength;
+ nsCOMPtr<nsIURI> mFileURI;
+ uint64_t mChannelId = 0;
+};
+
+#endif // !nsFileChannel_h__
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp
new file mode 100644
index 0000000000..10a1b9c029
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:ts=4 sw=2 sts=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.h"
+#include "nsFileProtocolHandler.h"
+#include "nsFileChannel.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "nsIURIMutator.h"
+
+#include "nsNetUtil.h"
+
+#include "FileChannelChild.h"
+
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/net/NeckoCommon.h"
+
+// URL file handling, copied and modified from
+// xpfe/components/bookmarks/src/nsBookmarksService.cpp
+#ifdef XP_WIN
+# include <shlobj.h>
+# include <intshcut.h>
+# include "nsIFileURL.h"
+# ifdef CompareString
+# undef CompareString
+# endif
+#endif
+
+// URL file handling for freedesktop.org
+#ifdef XP_UNIX
+# include "nsINIParser.h"
+# define DESKTOP_ENTRY_SECTION "Desktop Entry"
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult nsFileProtocolHandler::Init() { return NS_OK; }
+
+NS_IMPL_ISUPPORTS(nsFileProtocolHandler, nsIFileProtocolHandler,
+ nsIProtocolHandler, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsIProtocolHandler methods:
+
+#if defined(XP_WIN)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ nsAutoString path;
+ nsresult rv = aFile->GetPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ if (path.Length() < 4) return NS_ERROR_NOT_AVAILABLE;
+ if (!StringTail(path, 4).LowerCaseEqualsLiteral(".url"))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HRESULT result;
+
+ rv = NS_ERROR_NOT_AVAILABLE;
+
+ IUniformResourceLocatorW* urlLink = nullptr;
+ result =
+ ::CoCreateInstance(CLSID_InternetShortcut, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IUniformResourceLocatorW, (void**)&urlLink);
+ if (SUCCEEDED(result) && urlLink) {
+ IPersistFile* urlFile = nullptr;
+ result = urlLink->QueryInterface(IID_IPersistFile, (void**)&urlFile);
+ if (SUCCEEDED(result) && urlFile) {
+ result = urlFile->Load(path.get(), STGM_READ);
+ if (SUCCEEDED(result)) {
+ LPWSTR lpTemp = nullptr;
+
+ // The URL this method will give us back seems to be already
+ // escaped. Hence, do not do escaping of our own.
+ result = urlLink->GetURL(&lpTemp);
+ if (SUCCEEDED(result) && lpTemp) {
+ rv = NS_NewURI(aURI, nsDependentString(lpTemp));
+ // free the string that GetURL alloc'd
+ CoTaskMemFree(lpTemp);
+ }
+ }
+ urlFile->Release();
+ }
+ urlLink->Release();
+ }
+ return rv;
+}
+
+#elif defined(XP_UNIX)
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ // We only support desktop files that end in ".desktop" like the spec says:
+ // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html
+ nsAutoCString leafName;
+ nsresult rv = aFile->GetNativeLeafName(leafName);
+ if (NS_FAILED(rv) || !StringEndsWith(leafName, ".desktop"_ns)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool isFile = false;
+ rv = aFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(aFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ parser.GetString(DESKTOP_ENTRY_SECTION, "Type", type);
+ if (!type.EqualsLiteral("Link")) return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString url;
+ rv = parser.GetString(DESKTOP_ENTRY_SECTION, "URL", url);
+ if (NS_FAILED(rv) || url.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_NewURI(aURI, url);
+}
+
+#else // other platforms
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadURLFile(nsIFile* aFile, nsIURI** aURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+#endif // ReadURLFile()
+
+NS_IMETHODIMP
+nsFileProtocolHandler::ReadShellLink(nsIFile* aFile, nsIURI** aURI) {
+#if defined(XP_WIN)
+ nsAutoString path;
+ MOZ_TRY(aFile->GetPath(path));
+
+ if (path.Length() < 4 ||
+ !StringTail(path, 4).LowerCaseEqualsLiteral(".lnk")) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<IPersistFile> persistFile;
+ RefPtr<IShellLinkW> shellLink;
+ WCHAR lpTemp[MAX_PATH];
+ // Get a pointer to the IPersistFile interface.
+ if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, getter_AddRefs(shellLink))) ||
+ FAILED(shellLink->QueryInterface(IID_IPersistFile,
+ getter_AddRefs(persistFile))) ||
+ FAILED(persistFile->Load(path.get(), STGM_READ)) ||
+ FAILED(shellLink->Resolve(nullptr, SLR_NO_UI)) ||
+ FAILED(shellLink->GetPath(lpTemp, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIFile> linkedFile;
+ MOZ_TRY(NS_NewLocalFile(nsDependentString(lpTemp), false,
+ getter_AddRefs(linkedFile)));
+ return NS_NewFileURI(aURI, linkedFile);
+#else
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("file");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ nsresult rv;
+
+ RefPtr<nsFileChannel> chan;
+ if (mozilla::net::IsNeckoChild()) {
+ chan = new mozilla::net::FileChannelChild(uri);
+ } else {
+ chan = new nsFileChannel(uri);
+ }
+
+ // set the loadInfo on the new channel ; must do this
+ // before calling Init() on it, since it needs the load
+ // info be already set.
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = chan->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *result = chan.forget().downcast<nsBaseChannel>().take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* result) {
+ // don't override anything.
+ *result = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIFileProtocolHandler methods:
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURI(nsIFile* aFile, nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ RefPtr<nsIFile> file(aFile);
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ return NS_MutateURI(new mozilla::net::nsStandardURL::Mutator())
+ .Apply(&nsIFileURLMutator::SetFile, file)
+ .Finalize(aResult);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURIMutator(nsIFile* aFile,
+ nsIURIMutator** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+
+ nsCOMPtr<nsIURIMutator> mutator = new mozilla::net::nsStandardURL::Mutator();
+ nsCOMPtr<nsIFileURLMutator> fileMutator = do_QueryInterface(mutator, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // NOTE: the origin charset is assigned the value of the platform
+ // charset by the SetFile method.
+ rv = fileMutator->SetFile(aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mutator.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromFile(nsIFile* file, nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromActualFile(nsIFile* file,
+ nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromActualFile(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetURLSpecFromDir(nsIFile* file, nsACString& result) {
+ NS_ENSURE_ARG_POINTER(file);
+ return net_GetURLSpecFromDir(file, result);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetFileFromURLSpec(const nsACString& spec,
+ nsIFile** result) {
+ return net_GetFileFromURLSpec(spec, result);
+}
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.h b/netwerk/protocol/file/nsFileProtocolHandler.h
new file mode 100644
index 0000000000..ff2b30634a
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFileProtocolHandler_h__
+#define nsFileProtocolHandler_h__
+
+#include "nsIFileProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsIURIMutator;
+
+class nsFileProtocolHandler : public nsIFileProtocolHandler,
+ public nsSupportsWeakReference {
+ virtual ~nsFileProtocolHandler() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIFILEPROTOCOLHANDLER
+
+ nsFileProtocolHandler() = default;
+
+ [[nodiscard]] nsresult Init();
+};
+
+#endif // !nsFileProtocolHandler_h__
diff --git a/netwerk/protocol/file/nsIFileChannel.idl b/netwerk/protocol/file/nsIFileChannel.idl
new file mode 100644
index 0000000000..e5fcdecb4c
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileChannel.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+/**
+ * nsIFileChannel
+ */
+[scriptable, uuid(06169120-136d-45a5-b535-498f1f755ab7)]
+interface nsIFileChannel : nsISupports
+{
+ readonly attribute nsIFile file;
+};
diff --git a/netwerk/protocol/file/nsIFileProtocolHandler.idl b/netwerk/protocol/file/nsIFileProtocolHandler.idl
new file mode 100644
index 0000000000..d470dcee2a
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIFile;
+interface nsIURIMutator;
+
+[scriptable, uuid(1fb25bd5-4354-4dcd-8d97-621b7b3ed2e4)]
+interface nsIFileProtocolHandler : nsIProtocolHandler
+{
+ /**
+ * This method constructs a new file URI
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURI object
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * This method constructs a new file URI, and returns a URI mutator
+ * that has not yet been finalized, allowing the URI to be changed without
+ * being cloned.
+ *
+ * @param aFile nsIFile
+ * @return reference to a new nsIURIMutator object
+ */
+ nsIURIMutator newFileURIMutator(in nsIFile file);
+
+ /**
+ * DEPRECATED, AVOID IF AT ALL POSSIBLE.
+ *
+ * Calling this will cause IO on the calling thread, to determine
+ * if the file is a directory or file, and based on that behaves as
+ * if you called getURLSpecFromDir or getURLSpecFromActualFile,
+ * respectively. This IO may take multiple seconds (e.g. for network
+ * paths, slow external drives that need to be woken up, etc.).
+ *
+ * Usually, the caller should *know* that the `file` argument is
+ * either a directory (in which case it should call getURLSpecFromDir)
+ * or a non-directory file (in which case it should call
+ * getURLSpecFromActualFile), and not need to call this method.
+ */
+ [noscript] AUTF8String getURLSpecFromFile(in nsIFile file);
+
+ /**
+ * Converts a non-directory nsIFile to the corresponding URL string.
+ * NOTE: under some platforms this is a lossy conversion (e.g., Mac
+ * Carbon build). If the nsIFile is a local file, then the result
+ * will be a file:// URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ *
+ * Should only be called on files which are not directories. If
+ * called on directories, the resulting URL may lack a trailing slash
+ * and cause relative URLs in such a document to misbehave.
+ */
+ AUTF8String getURLSpecFromActualFile(in nsIFile file);
+
+ /**
+ * Converts a directory nsIFile to the corresponding URL string.
+ * NOTE: under some platforms this is a lossy conversion (e.g., Mac
+ * Carbon build). If the nsIFile is a local file, then the result
+ * will be a file:// URL string.
+ *
+ * The resulting string may contain URL-escaped characters.
+ *
+ * Should only be called on files which are directories (will enforce
+ * the URL ends with a slash).
+ */
+ AUTF8String getURLSpecFromDir(in nsIFile file);
+
+ /**
+ * Converts the URL string into the corresponding nsIFile if possible.
+ * A local file will be created if the URL string begins with file://.
+ */
+ nsIFile getFileFromURLSpec(in AUTF8String url);
+
+ /**
+ * Takes a local file and tries to interpret it as an internet shortcut
+ * (e.g. .url files on windows).
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not an internet shortcut.
+ */
+ nsIURI readURLFile(in nsIFile file);
+
+ /**
+ * Takes a local file and tries to interpret it as a shell link file
+ * (.lnk files on Windows)
+ * @param file The local file to read
+ * @return The URI the file refers to
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE if the OS does not support such files.
+ * @throw NS_ERROR_NOT_AVAILABLE if this file is not a shell link.
+ */
+ nsIURI readShellLink(in nsIFile file);
+};