diff options
Diffstat (limited to 'netwerk/protocol/data')
-rw-r--r-- | netwerk/protocol/data/DataChannelChild.cpp | 58 | ||||
-rw-r--r-- | netwerk/protocol/data/DataChannelChild.h | 40 | ||||
-rw-r--r-- | netwerk/protocol/data/DataChannelParent.cpp | 105 | ||||
-rw-r--r-- | netwerk/protocol/data/DataChannelParent.h | 39 | ||||
-rw-r--r-- | netwerk/protocol/data/moz.build | 29 | ||||
-rw-r--r-- | netwerk/protocol/data/nsDataChannel.cpp | 146 | ||||
-rw-r--r-- | netwerk/protocol/data/nsDataChannel.h | 31 | ||||
-rw-r--r-- | netwerk/protocol/data/nsDataHandler.cpp | 272 | ||||
-rw-r--r-- | netwerk/protocol/data/nsDataHandler.h | 56 | ||||
-rw-r--r-- | netwerk/protocol/data/nsDataModule.cpp | 15 |
10 files changed, 791 insertions, 0 deletions
diff --git a/netwerk/protocol/data/DataChannelChild.cpp b/netwerk/protocol/data/DataChannelChild.cpp new file mode 100644 index 0000000000..3ce6f02dd5 --- /dev/null +++ b/netwerk/protocol/data/DataChannelChild.cpp @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +#include "DataChannelChild.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/net/NeckoChild.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS_INHERITED(DataChannelChild, nsDataChannel, nsIChildChannel) + +DataChannelChild::DataChannelChild(nsIURI* aURI) + : nsDataChannel(aURI), mIPCOpen(false) {} + +NS_IMETHODIMP +DataChannelChild::ConnectParent(uint32_t aId) { + mozilla::dom::ContentChild* cc = + static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager()); + if (cc->IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + + if (!gNeckoChild->SendPDataChannelConstructor(this, aId)) { + return NS_ERROR_FAILURE; + } + + // IPC now has a ref to us. + mIPCOpen = true; + return NS_OK; +} + +NS_IMETHODIMP +DataChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { + nsresult rv; + rv = AsyncOpen(aListener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mIPCOpen) { + Unused << Send__delete__(this); + } + return NS_OK; +} + +void DataChannelChild::ActorDestroy(ActorDestroyReason why) { + MOZ_ASSERT(mIPCOpen); + mIPCOpen = false; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/protocol/data/DataChannelChild.h b/netwerk/protocol/data/DataChannelChild.h new file mode 100644 index 0000000000..8e9f91ea60 --- /dev/null +++ b/netwerk/protocol/data/DataChannelChild.h @@ -0,0 +1,40 @@ +/* -*- 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 NS_DATACHANNELCHILD_H +#define NS_DATACHANNELCHILD_H + +#include "nsDataChannel.h" +#include "nsIChildChannel.h" +#include "nsISupportsImpl.h" + +#include "mozilla/net/PDataChannelChild.h" + +namespace mozilla { +namespace net { + +class DataChannelChild : public nsDataChannel, + public nsIChildChannel, + public PDataChannelChild { + public: + explicit DataChannelChild(nsIURI* uri); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICHILDCHANNEL + + protected: + virtual void ActorDestroy(ActorDestroyReason why) override; + + private: + ~DataChannelChild() = default; + + bool mIPCOpen; +}; + +} // namespace net +} // namespace mozilla + +#endif /* NS_DATACHANNELCHILD_H */ diff --git a/netwerk/protocol/data/DataChannelParent.cpp b/netwerk/protocol/data/DataChannelParent.cpp new file mode 100644 index 0000000000..e427120a55 --- /dev/null +++ b/netwerk/protocol/data/DataChannelParent.cpp @@ -0,0 +1,105 @@ +/* -*- 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/. */ + +#include "DataChannelParent.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(DataChannelParent, nsIParentChannel, nsIStreamListener) + +bool DataChannelParent::Init(const uint64_t& aChannelId) { + nsCOMPtr<nsIChannel> channel; + + MOZ_ALWAYS_SUCCEEDS_FUZZING( + NS_LinkRedirectChannels(aChannelId, this, getter_AddRefs(channel))); + + return true; +} + +NS_IMETHODIMP +DataChannelParent::SetParentListener(ParentChannelListener* aListener) { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +DataChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +DataChannelParent::SetClassifierMatchedInfo(const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +DataChannelParent::SetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHashes) { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +DataChannelParent::Delete() { + // Nothing to do. + return NS_OK; +} + +NS_IMETHODIMP +DataChannelParent::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 DataChannelParent::ActorDestroy(ActorDestroyReason why) {} + +NS_IMETHODIMP +DataChannelParent::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 +DataChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + // See above. + MOZ_ASSERT(NS_FAILED(aStatusCode)); + return NS_OK; +} + +NS_IMETHODIMP +DataChannelParent::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/data/DataChannelParent.h b/netwerk/protocol/data/DataChannelParent.h new file mode 100644 index 0000000000..122cdda182 --- /dev/null +++ b/netwerk/protocol/data/DataChannelParent.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 NS_DATACHANNELPARENT_H +#define NS_DATACHANNELPARENT_H + +#include "nsIParentChannel.h" +#include "nsISupportsImpl.h" + +#include "mozilla/net/PDataChannelParent.h" + +namespace mozilla { +namespace net { + +// In order to support HTTP redirects to data:, we need to implement the HTTP +// redirection API, which requires a class that implements nsIParentChannel +// and which calls NS_LinkRedirectChannels. +class DataChannelParent : public nsIParentChannel, public PDataChannelParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPARENTCHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + [[nodiscard]] bool Init(const uint64_t& aChannelId); + + private: + ~DataChannelParent() = default; + + virtual void ActorDestroy(ActorDestroyReason why) override; +}; + +} // namespace net +} // namespace mozilla + +#endif /* NS_DATACHANNELPARENT_H */ diff --git a/netwerk/protocol/data/moz.build b/netwerk/protocol/data/moz.build new file mode 100644 index 0000000000..482c0b8a2f --- /dev/null +++ b/netwerk/protocol/data/moz.build @@ -0,0 +1,29 @@ +# -*- 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/. + +EXPORTS.mozilla.net += [ + "DataChannelChild.h", + "DataChannelParent.h", +] + +EXPORTS += [ + "nsDataChannel.h", + "nsDataHandler.h", +] + +UNIFIED_SOURCES += [ + "DataChannelChild.cpp", + "DataChannelParent.cpp", + "nsDataChannel.cpp", + "nsDataHandler.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "/netwerk/base", +] diff --git a/netwerk/protocol/data/nsDataChannel.cpp b/netwerk/protocol/data/nsDataChannel.cpp new file mode 100644 index 0000000000..87b00adea3 --- /dev/null +++ b/netwerk/protocol/data/nsDataChannel.cpp @@ -0,0 +1,146 @@ +/* -*- 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/. */ + +// data implementation + +#include "nsDataChannel.h" + +#include "mozilla/Base64.h" +#include "nsDataHandler.h" +#include "nsIInputStream.h" +#include "nsEscape.h" +#include "nsStringStream.h" +#include "nsIObserverService.h" +#include "mozilla/dom/ContentParent.h" + +using namespace mozilla; + +/** + * Helper for performing a fallible unescape. + * + * @param aStr The string to unescape. + * @param aBuffer Buffer to unescape into if necessary. + * @param rv Out: nsresult indicating success or failure of unescaping. + * @return Reference to the string containing the unescaped data. + */ +const nsACString& Unescape(const nsACString& aStr, nsACString& aBuffer, + nsresult* rv) { + MOZ_ASSERT(rv); + + bool appended = false; + *rv = NS_UnescapeURL(aStr.Data(), aStr.Length(), /* aFlags = */ 0, aBuffer, + appended, mozilla::fallible); + if (NS_FAILED(*rv) || !appended) { + return aStr; + } + + return aBuffer; +} + +nsresult nsDataChannel::OpenContentStream(bool async, nsIInputStream** result, + nsIChannel** channel) { + NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED); + + nsresult rv; + + // In order to avoid potentially building up a new path including the + // ref portion of the URI, which we don't care about, we clone a version + // of the URI that does not have a ref and in most cases should share + // string buffers with the original URI. + nsCOMPtr<nsIURI> uri; + rv = NS_GetURIWithoutRef(URI(), getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + nsAutoCString path; + rv = uri->GetPathQueryRef(path); + if (NS_FAILED(rv)) return rv; + + nsCString contentType, contentCharset; + nsDependentCSubstring dataRange; + bool lBase64; + rv = nsDataHandler::ParsePathWithoutRef(path, contentType, &contentCharset, + lBase64, &dataRange, &mMimeType); + if (NS_FAILED(rv)) return rv; + + // This will avoid a copy if nothing needs to be unescaped. + nsAutoCString unescapedBuffer; + const nsACString& data = Unescape(dataRange, unescapedBuffer, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + if (lBase64 && &data == &unescapedBuffer) { + // Don't allow spaces in base64-encoded content. This is only + // relevant for escaped spaces; other spaces are stripped in + // NewURI. We know there were no escaped spaces if the data buffer + // wasn't used in |Unescape|. + unescapedBuffer.StripWhitespace(); + } + + nsCOMPtr<nsIInputStream> bufInStream; + uint32_t contentLen; + if (lBase64) { + nsAutoCString decodedData; + rv = Base64Decode(data, decodedData); + if (NS_FAILED(rv)) { + // Returning this error code instead of what Base64Decode returns + // (NS_ERROR_ILLEGAL_VALUE) will prevent rendering of redirect response + // content by HTTP channels. It's also more logical error to return. + // Here we know the URL is actually corrupted. + return NS_ERROR_MALFORMED_URI; + } + + contentLen = decodedData.Length(); + rv = NS_NewCStringInputStream(getter_AddRefs(bufInStream), decodedData); + } else { + contentLen = data.Length(); + rv = NS_NewCStringInputStream(getter_AddRefs(bufInStream), data); + } + + if (NS_FAILED(rv)) return rv; + + SetContentType(contentType); + SetContentCharset(contentCharset); + mContentLength = contentLen; + + // notify "data-channel-opened" observers + MaybeSendDataChannelOpenNotification(); + + bufInStream.forget(result); + + return NS_OK; +} + +nsresult nsDataChannel::MaybeSendDataChannelOpenNotification() { + 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<nsIChannel*>(this), + "data-channel-opened", nullptr); + } + return NS_OK; +} diff --git a/netwerk/protocol/data/nsDataChannel.h b/netwerk/protocol/data/nsDataChannel.h new file mode 100644 index 0000000000..d7313d66a0 --- /dev/null +++ b/netwerk/protocol/data/nsDataChannel.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +// data implementation header + +#ifndef nsDataChannel_h___ +#define nsDataChannel_h___ + +#include "nsBaseChannel.h" + +class nsIInputStream; + +class nsDataChannel : public nsBaseChannel { + public: + explicit nsDataChannel(nsIURI* uri) { SetURI(uri); } + + const nsACString& MimeType() const { return mMimeType; } + + protected: + [[nodiscard]] virtual nsresult OpenContentStream( + bool async, nsIInputStream** result, nsIChannel** channel) override; + + nsCString mMimeType; + + private: + nsresult MaybeSendDataChannelOpenNotification(); +}; + +#endif /* nsDataChannel_h___ */ diff --git a/netwerk/protocol/data/nsDataHandler.cpp b/netwerk/protocol/data/nsDataHandler.cpp new file mode 100644 index 0000000000..d3a5743097 --- /dev/null +++ b/netwerk/protocol/data/nsDataHandler.cpp @@ -0,0 +1,272 @@ +/* -*- 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/. */ + +#include "nsDataChannel.h" +#include "nsDataHandler.h" +#include "nsNetCID.h" +#include "nsError.h" +#include "nsIOService.h" +#include "DataChannelChild.h" +#include "nsNetUtil.h" +#include "nsSimpleURI.h" +#include "nsUnicharUtils.h" +#include "mozilla/dom/MimeType.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Try.h" +#include "DefaultURI.h" + +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference) + +nsresult nsDataHandler::Create(const nsIID& aIID, void** aResult) { + RefPtr<nsDataHandler> ph = new nsDataHandler(); + return ph->QueryInterface(aIID, aResult); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsDataHandler::GetScheme(nsACString& result) { + result.AssignLiteral("data"); + return NS_OK; +} + +/* static */ nsresult nsDataHandler::CreateNewURI(const nsACString& aSpec, + const char* aCharset, + nsIURI* aBaseURI, + nsIURI** result) { + nsCOMPtr<nsIURI> uri; + nsAutoCString contentType; + bool base64; + MOZ_TRY(ParseURI(aSpec, contentType, /* contentCharset = */ nullptr, base64, + /* dataBuffer = */ nullptr)); + + // Strip whitespace unless this is text, where whitespace is important + // Don't strip escaped whitespace though (bug 391951) + nsresult rv; + if (base64 || (StaticPrefs::network_url_strip_data_url_whitespace() && + strncmp(contentType.get(), "text/", 5) != 0 && + contentType.Find("xml") == kNotFound)) { + // it's ascii encoded binary, don't let any spaces in + rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator()) + .Apply(&nsISimpleURIMutator::SetSpecAndFilterWhitespace, aSpec, + nullptr) + .Finalize(uri); + } else { + rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator()) + .SetSpec(aSpec) + .Finalize(uri); + } + + if (NS_FAILED(rv)) return rv; + + // use DefaultURI to check for validity when we have possible hostnames + // since nsSimpleURI doesn't know about hostnames + auto pos = aSpec.Find("data:"); + if (pos != kNotFound) { + nsDependentCSubstring rest(aSpec, pos + sizeof("data:") - 1, -1); + if (StringBeginsWith(rest, "//"_ns)) { + nsCOMPtr<nsIURI> uriWithHost; + rv = NS_MutateURI(new mozilla::net::DefaultURI::Mutator()) + .SetSpec(aSpec) + .Finalize(uriWithHost); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + uri.forget(result); + return rv; +} + +NS_IMETHODIMP +nsDataHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + NS_ENSURE_ARG_POINTER(uri); + RefPtr<nsDataChannel> channel; + if (XRE_IsParentProcess()) { + channel = new nsDataChannel(uri); + } else { + channel = new mozilla::net::DataChannelChild(uri); + } + + // set the loadInfo on the new channel + nsresult rv = channel->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + channel.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsDataHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) { + // don't override anything. + *_retval = false; + return NS_OK; +} + +namespace { + +bool TrimSpacesAndBase64(nsACString& aMimeType) { + const char* beg = aMimeType.BeginReading(); + const char* end = aMimeType.EndReading(); + + // trim leading and trailing spaces + while (beg < end && NS_IsHTTPWhitespace(*beg)) { + ++beg; + } + if (beg == end) { + aMimeType.Truncate(); + return false; + } + while (end > beg && NS_IsHTTPWhitespace(*(end - 1))) { + --end; + } + if (beg == end) { + aMimeType.Truncate(); + return false; + } + + // trim trailing `; base64` (if any) and remember it + const char* pos = end - 1; + bool foundBase64 = false; + if (pos > beg && *pos == '4' && --pos > beg && *pos == '6' && --pos > beg && + ToLowerCaseASCII(*pos) == 'e' && --pos > beg && + ToLowerCaseASCII(*pos) == 's' && --pos > beg && + ToLowerCaseASCII(*pos) == 'a' && --pos > beg && + ToLowerCaseASCII(*pos) == 'b') { + while (--pos > beg && NS_IsHTTPWhitespace(*pos)) { + } + if (pos >= beg && *pos == ';') { + end = pos; + foundBase64 = true; + } + } + + // actually trim off the spaces and trailing base64, returning if we found it. + const char* s = aMimeType.BeginReading(); + aMimeType.Assign(Substring(aMimeType, beg - s, end - s)); + return foundBase64; +} + +} // namespace + +nsresult nsDataHandler::ParsePathWithoutRef(const nsACString& aPath, + nsCString& aContentType, + nsCString* aContentCharset, + bool& aIsBase64, + nsDependentCSubstring* aDataBuffer, + nsCString* aMimeType) { + static constexpr auto kCharset = "charset"_ns; + + // This implements https://fetch.spec.whatwg.org/#data-url-processor + // It also returns the full mimeType in aMimeType so fetch/XHR may access it + // for content-length headers. The contentType and charset parameters retain + // our legacy behavior, as much Gecko code generally expects GetContentType + // to yield only the MimeType's essence, not its full value with parameters. + + aIsBase64 = false; + + int32_t commaIdx = aPath.FindChar(','); + + // This is a hack! When creating a URL using the DOM API we want to ignore + // if a comma is missing. But if we're actually loading a data: URI, in which + // case aContentCharset is not null, then we want to return an error if a + // comma is missing. + if (aContentCharset && commaIdx == kNotFound) { + return NS_ERROR_MALFORMED_URI; + } + + // "Let mimeType be the result of collecting a sequence of code points that + // are not equal to U+002C (,), given position." + nsCString mimeType(Substring(aPath, 0, commaIdx)); + + // "Strip leading and trailing ASCII whitespace from mimeType." + // "If mimeType ends with U+003B (;), followed by zero or more U+0020 SPACE, + // followed by an ASCII case-insensitive match for "base64", then ..." + aIsBase64 = TrimSpacesAndBase64(mimeType); + + // "If mimeType starts with ";", then prepend "text/plain" to mimeType." + if (mimeType.Length() > 0 && mimeType.CharAt(0) == ';') { + mimeType = "text/plain"_ns + mimeType; + } + + // "Let mimeTypeRecord be the result of parsing mimeType." + // This also checks for instances of ;base64 in the middle of the MimeType. + // This is against the current spec, but we're doing it because we have + // historically seen webcompat issues relying on this (see bug 781693). + if (mozilla::UniquePtr<CMimeType> parsed = CMimeType::Parse(mimeType)) { + parsed->GetEssence(aContentType); + if (aContentCharset) { + parsed->GetParameterValue(kCharset, *aContentCharset); + } + if (aMimeType) { + parsed->Serialize(*aMimeType); + } + if (parsed->IsBase64() && + !StaticPrefs::network_url_strict_data_url_base64_placement()) { + aIsBase64 = true; + } + } else { + // "If mimeTypeRecord is failure, then set mimeTypeRecord to + // text/plain;charset=US-ASCII." + aContentType.AssignLiteral("text/plain"); + if (aContentCharset) { + aContentCharset->AssignLiteral("US-ASCII"); + } + if (aMimeType) { + aMimeType->AssignLiteral("text/plain;charset=US-ASCII"); + } + } + + if (aDataBuffer) { + aDataBuffer->Rebind(aPath, commaIdx + 1); + } + + return NS_OK; +} + +static inline char ToLower(const char c) { + if (c >= 'A' && c <= 'Z') { + return char(c + ('a' - 'A')); + } + return c; +} + +nsresult nsDataHandler::ParseURI(const nsACString& spec, nsCString& contentType, + nsCString* contentCharset, bool& isBase64, + nsCString* dataBuffer) { + static constexpr auto kDataScheme = "data:"_ns; + + // move past "data:" + const char* pos = std::search( + spec.BeginReading(), spec.EndReading(), kDataScheme.BeginReading(), + kDataScheme.EndReading(), + [](const char a, const char b) { return ToLower(a) == ToLower(b); }); + if (pos == spec.EndReading()) { + return NS_ERROR_MALFORMED_URI; + } + + uint32_t scheme = pos - spec.BeginReading(); + scheme += kDataScheme.Length(); + + // Find the start of the hash ref if present. + int32_t hash = spec.FindChar('#', scheme); + + auto pathWithoutRef = Substring(spec, scheme, hash != kNotFound ? hash : -1); + nsDependentCSubstring dataRange; + nsresult rv = ParsePathWithoutRef(pathWithoutRef, contentType, contentCharset, + isBase64, &dataRange); + if (NS_SUCCEEDED(rv) && dataBuffer) { + if (!dataBuffer->Assign(dataRange, mozilla::fallible)) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + + return rv; +} diff --git a/netwerk/protocol/data/nsDataHandler.h b/netwerk/protocol/data/nsDataHandler.h new file mode 100644 index 0000000000..4796f0f453 --- /dev/null +++ b/netwerk/protocol/data/nsDataHandler.h @@ -0,0 +1,56 @@ +/* -*- 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 nsDataHandler_h___ +#define nsDataHandler_h___ + +#include "nsIProtocolHandler.h" +#include "nsWeakReference.h" + +class nsDataHandler : public nsIProtocolHandler, + public nsSupportsWeakReference { + virtual ~nsDataHandler() = default; + + public: + NS_DECL_ISUPPORTS + + // nsIProtocolHandler methods: + NS_DECL_NSIPROTOCOLHANDLER + + // nsDataHandler methods: + nsDataHandler() = default; + + static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, nsIURI** result); + + // Define a Create method to be used with a factory: + [[nodiscard]] static nsresult Create(const nsIID& aIID, void** aResult); + + // Parse a data: URI and return the individual parts + // (the given spec will temporarily be modified but will be returned + // to the original before returning) + // contentCharset and dataBuffer can be nullptr if they are not needed. + [[nodiscard]] static nsresult ParseURI(const nsACString& spec, + nsCString& contentType, + nsCString* contentCharset, + bool& isBase64, nsCString* dataBuffer); + + // Parse the path portion of a data: URI and return the individual parts. + // + // Note: The path is assumed *not* to have a ref portion. + // + // @arg aPath The path portion of the spec. Must not have ref portion. + // @arg aContentType Out param, will hold the parsed content type. + // @arg aContentCharset Optional, will hold the charset if specified. + // @arg aIsBase64 Out param, indicates if the data is base64 encoded. + // @arg aDataBuffer Optional, will reference the substring in |aPath| that + // contains the data portion of the path. No copy is made. + [[nodiscard]] static nsresult ParsePathWithoutRef( + const nsACString& aPath, nsCString& aContentType, + nsCString* aContentCharset, bool& aIsBase64, + nsDependentCSubstring* aDataBuffer, nsCString* aMimeType = nullptr); +}; + +#endif /* nsDataHandler_h___ */ diff --git a/netwerk/protocol/data/nsDataModule.cpp b/netwerk/protocol/data/nsDataModule.cpp new file mode 100644 index 0000000000..8bcda94362 --- /dev/null +++ b/netwerk/protocol/data/nsDataModule.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#include "nsIGenericFactory.h" +#include "nsDataHandler.h" + +// The list of components we register +static const nsModuleComponentInfo components[] = { + {"Data Protocol Handler", NS_DATAHANDLER_CID, + NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", nsDataHandler::Create}, +}; + +NS_IMPL_NSGETMODULE(nsDataProtocolModule, components) |