diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/file/FileChannelChild.cpp | 52 | ||||
-rw-r--r-- | netwerk/protocol/file/FileChannelChild.h | 35 | ||||
-rw-r--r-- | netwerk/protocol/file/FileChannelParent.cpp | 105 | ||||
-rw-r--r-- | netwerk/protocol/file/FileChannelParent.h | 39 | ||||
-rw-r--r-- | netwerk/protocol/file/moz.build | 40 | ||||
-rw-r--r-- | netwerk/protocol/file/nsFileChannel.cpp | 514 | ||||
-rw-r--r-- | netwerk/protocol/file/nsFileChannel.h | 53 | ||||
-rw-r--r-- | netwerk/protocol/file/nsFileProtocolHandler.cpp | 266 | ||||
-rw-r--r-- | netwerk/protocol/file/nsFileProtocolHandler.h | 28 | ||||
-rw-r--r-- | netwerk/protocol/file/nsIFileChannel.idl | 17 | ||||
-rw-r--r-- | netwerk/protocol/file/nsIFileProtocolHandler.idl | 102 |
11 files changed, 1251 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..6dc5f6780b --- /dev/null +++ b/netwerk/protocol/file/nsFileChannel.cpp @@ -0,0 +1,514 @@ +/* -*- 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 "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); + + // 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); + } + } + + *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) + +//----------------------------------------------------------------------------- +// 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); +} + +//----------------------------------------------------------------------------- +// 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; +} diff --git a/netwerk/protocol/file/nsFileChannel.h b/netwerk/protocol/file/nsFileChannel.h new file mode 100644 index 0000000000..dcf0646f03 --- /dev/null +++ b/netwerk/protocol/file/nsFileChannel.h @@ -0,0 +1,53 @@ +/* -*- 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: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFILECHANNEL + NS_DECL_NSIUPLOADCHANNEL + + 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); + + nsCOMPtr<nsIInputStream> mUploadStream; + int64_t mUploadLength; + nsCOMPtr<nsIURI> mFileURI; +}; + +#endif // !nsFileChannel_h__ diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp new file mode 100644 index 0000000000..99e2f2bcc2 --- /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; + } + + chan.forget(result); + 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); +}; |