diff --git a/dom/base/FormData.cpp b/dom/base/FormData.cpp index de4d71a676..d47a970fc8 100644 --- a/dom/base/FormData.cpp +++ b/dom/base/FormData.cpp @@ -398,7 +398,7 @@ FormData::Constructor(const GlobalObject& aGlobal, NS_IMETHODIMP FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) + nsACString& aContentTypeWithCharset, nsACString& aCharset) { FSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr); @@ -419,7 +419,7 @@ FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, } } - fs.GetContentType(aContentType); + fs.GetContentType(aContentTypeWithCharset); aCharset.Truncate(); *aContentLength = 0; NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength)); diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index ef8a453742..894e0a162e 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -11,7 +11,9 @@ #include "nsPluginArray.h" #include "nsMimeTypeArray.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/dom/BodyExtractor.h" #include "mozilla/dom/DesktopNotification.h" +#include "mozilla/dom/FetchBinding.h" #include "mozilla/dom/File.h" #include "nsGeolocation.h" #include "nsIClassOfService.h" @@ -857,8 +859,52 @@ BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest, bool Navigator::SendBeacon(const nsAString& aUrl, - const Nullable& aData, + const Nullable& aData, ErrorResult& aRv) +{ + if (aData.IsNull()) { + return SendBeaconInternal(aUrl, nullptr, /* isBlob */ false, aRv); + } + + if (aData.Value().IsArrayBuffer()) { + BodyExtractor body(&aData.Value().GetAsArrayBuffer()); + return SendBeaconInternal(aUrl, &body, /* isBlob*/ false, aRv); + } + + if (aData.Value().IsArrayBufferView()) { + BodyExtractor body(&aData.Value().GetAsArrayBufferView()); + return SendBeaconInternal(aUrl, &body, /* isBlob*/ false, aRv); + } + + if (aData.Value().IsBlob()) { + BodyExtractor body(&aData.Value().GetAsBlob()); + return SendBeaconInternal(aUrl, &body, /* isBlob */ true, aRv); + } + + if (aData.Value().IsFormData()) { + BodyExtractor body(&aData.Value().GetAsFormData()); + return SendBeaconInternal(aUrl, &body, /* isBlob */ false, aRv); + } + + if (aData.Value().IsUSVString()) { + BodyExtractor body(&aData.Value().GetAsUSVString()); + return SendBeaconInternal(aUrl, &body, /* isBlob */ false, aRv); + } + + if (aData.Value().IsURLSearchParams()) { + BodyExtractor body(&aData.Value().GetAsURLSearchParams()); + return SendBeaconInternal(aUrl, &body, /* isBlob */ false, aRv); + } + + MOZ_CRASH("Invalid data type."); + return false; +} + +bool +Navigator::SendBeaconInternal(const nsAString& aUrl, + BodyExtractorBase* aBody, + bool aIsBlob, + ErrorResult& aRv) { if (!mWindow) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); @@ -900,9 +946,9 @@ Navigator::SendBeacon(const nsAString& aUrl, nsIChannel::LOAD_CLASSIFY_URI; // No need to use CORS for sendBeacon unless it's a BLOB - nsSecurityFlags securityFlags = (!aData.IsNull() && aData.Value().IsBlob()) - ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS - : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; + nsSecurityFlags securityFlags = aIsBlob + ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE; nsCOMPtr channel; @@ -928,67 +974,15 @@ Navigator::SendBeacon(const nsAString& aUrl, } httpChannel->SetReferrer(documentURI); - nsCString mimeType; - if (!aData.IsNull()) { - nsCOMPtr in; + nsCOMPtr in; + nsAutoCString contentTypeWithCharset; + nsAutoCString charset; + uint64_t length = 0; - if (aData.Value().IsString()) { - nsCString stringData = NS_ConvertUTF16toUTF8(aData.Value().GetAsString()); - nsCOMPtr strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); - if (NS_FAILED(rv)) { - aRv.Throw(NS_ERROR_FAILURE); - return false; - } - rv = strStream->SetData(stringData.BeginReading(), stringData.Length()); - if (NS_FAILED(rv)) { - aRv.Throw(NS_ERROR_FAILURE); - return false; - } - mimeType.AssignLiteral("text/plain;charset=UTF-8"); - in = strStream; - - } else if (aData.Value().IsArrayBufferView()) { - - nsCOMPtr strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); - if (NS_FAILED(rv)) { - aRv.Throw(NS_ERROR_FAILURE); - return false; - } - - const ArrayBufferView& view = aData.Value().GetAsArrayBufferView(); - view.ComputeLengthAndData(); - rv = strStream->SetData(reinterpret_cast(view.Data()), - view.Length()); - - if (NS_FAILED(rv)) { - aRv.Throw(NS_ERROR_FAILURE); - return false; - } - mimeType.AssignLiteral("application/octet-stream"); - in = strStream; - - } else if (aData.Value().IsBlob()) { - Blob& blob = aData.Value().GetAsBlob(); - blob.GetInternalStream(getter_AddRefs(in), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return false; - } - - nsAutoString type; - blob.GetType(type); - mimeType = NS_ConvertUTF16toUTF8(type); - - } else if (aData.Value().IsFormData()) { - FormData& form = aData.Value().GetAsFormData(); - uint64_t len; - nsAutoCString charset; - form.GetSendInfo(getter_AddRefs(in), - &len, - mimeType, - charset); - } else { - MOZ_ASSERT(false, "switch statements not in sync"); - aRv.Throw(NS_ERROR_FAILURE); + if (aBody) { + aRv = aBody->GetAsStream(getter_AddRefs(in), &length, + contentTypeWithCharset, charset); + if (NS_WARN_IF(aRv.Failed())) { return false; } @@ -997,7 +991,7 @@ Navigator::SendBeacon(const nsAString& aUrl, aRv.Throw(NS_ERROR_FAILURE); return false; } - uploadChannel->ExplicitSetUploadStream(in, mimeType, -1, + uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length, NS_LITERAL_CSTRING("POST"), false); } else { diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index df99844ca9..16fcd4d440 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -30,12 +30,13 @@ class nsIURI; namespace mozilla { namespace dom { +class BodyExtractorBase; class Geolocation; class systemMessageCallback; class MediaDevices; struct MediaStreamConstraints; class WakeLock; -class ArrayBufferViewOrBlobOrStringOrFormData; +class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams; class ServiceWorkerContainer; class DOMRequest; } // namespace dom @@ -196,7 +197,7 @@ public: #endif // MOZ_AUDIO_CHANNEL_MANAGER bool SendBeacon(const nsAString& aUrl, - const Nullable& aData, + const Nullable& aData, ErrorResult& aRv); void MozGetUserMedia(const MediaStreamConstraints& aConstraints, @@ -255,6 +256,11 @@ private: bool CheckPermission(const char* type); static bool CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType); + bool SendBeaconInternal(const nsAString& aUrl, + BodyExtractorBase* aBody, + bool aIsBlob, + ErrorResult& aRv); + RefPtr mMimeTypes; RefPtr mPlugins; RefPtr mPermissions; diff --git a/dom/fetch/BodyExtractor.cpp b/dom/fetch/BodyExtractor.cpp new file mode 100644 index 0000000000..889526314e --- /dev/null +++ b/dom/fetch/BodyExtractor.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 "BodyExtractor.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FormData.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/URLSearchParams.h" +#include "mozilla/dom/XMLHttpRequest.h" +#include "nsContentUtils.h" +#include "nsIDOMDocument.h" +#include "nsIDOMSerializer.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIStorageStream.h" +#include "nsStringStream.h" +#include "nsIUnicodeEncoder.h" + +namespace mozilla { +namespace dom { + +static nsresult +GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength, + nsIInputStream** aResult, uint64_t* aContentLength, + nsACString& aContentType, nsACString& aCharset) +{ + aContentType.SetIsVoid(true); + aCharset.Truncate(); + + *aContentLength = aDataLength; + const char* data = reinterpret_cast(aData); + + nsCOMPtr stream; + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength, + NS_ASSIGNMENT_COPY); + NS_ENSURE_SUCCESS(rv, rv); + + stream.forget(aResult); + + return NS_OK; +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + mBody->ComputeLengthAndData(); + return GetBufferDataAsStream(mBody->Data(), mBody->Length(), + aResult, aContentLength, aContentTypeWithCharset, + aCharset); +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + mBody->ComputeLengthAndData(); + return GetBufferDataAsStream(mBody->Data(), mBody->Length(), + aResult, aContentLength, aContentTypeWithCharset, + aCharset); +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + nsCOMPtr domdoc(do_QueryInterface(mBody)); + NS_ENSURE_STATE(domdoc); + aCharset.AssignLiteral("UTF-8"); + + nsresult rv; + nsCOMPtr storStream; + rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr output; + rv = storStream->GetOutputStream(0, getter_AddRefs(output)); + NS_ENSURE_SUCCESS(rv, rv); + + if (mBody->IsHTMLDocument()) { + aContentTypeWithCharset.AssignLiteral("text/html;charset=UTF-8"); + + nsString serialized; + if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAutoCString utf8Serialized; + if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t written; + rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ASSERT(written == utf8Serialized.Length()); + } else { + aContentTypeWithCharset.AssignLiteral("application/xml;charset=UTF-8"); + + nsCOMPtr serializer = + do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure to use the encoding we'll send + rv = serializer->SerializeToStream(domdoc, output, aCharset); + NS_ENSURE_SUCCESS(rv, rv); + } + + output->Close(); + + uint32_t length; + rv = storStream->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + *aContentLength = length; + + rv = storStream->NewInputStream(0, aResult); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + nsCOMPtr encoder = + EncodingUtils::EncoderForEncoding("UTF-8"); + if (!encoder) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t destBufferLen; + nsresult rv = encoder->GetMaxLength(mBody->BeginReading(), mBody->Length(), + &destBufferLen); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString encoded; + if (!encoded.SetCapacity(destBufferLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* destBuffer = encoded.BeginWriting(); + int32_t srcLen = (int32_t) mBody->Length(); + int32_t outLen = destBufferLen; + rv = encoder->Convert(mBody->BeginReading(), &srcLen, destBuffer, &outLen); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(outLen <= destBufferLen); + encoded.SetLength(outLen); + + rv = NS_NewCStringInputStream(aResult, encoded); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aContentLength = outLen; + aContentTypeWithCharset.AssignLiteral("text/plain;charset=UTF-8"); + aCharset.AssignLiteral("UTF-8"); + return NS_OK; +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + aContentTypeWithCharset.AssignLiteral("text/plain"); + aCharset.Truncate(); + + nsresult rv = mBody->Available(aContentLength); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr stream(mBody); + stream.forget(aResult); + return NS_OK; +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset, + aCharset); +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset, + aCharset); +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset, + aCharset); +} + +template<> nsresult +BodyExtractor::GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const +{ + return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset, + aCharset); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/BodyExtractor.h b/dom/fetch/BodyExtractor.h new file mode 100644 index 0000000000..7d4bfd068e --- /dev/null +++ b/dom/fetch/BodyExtractor.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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_dom_BodyExtractor_h +#define mozilla_dom_BodyExtractor_h + +#include "nsString.h" + +class nsIInputStream; + +namespace mozilla { +namespace dom { + +class BodyExtractorBase +{ +public: + virtual nsresult GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const = 0; +}; + +// The implementation versions of this template are: +// ArrayBuffer, ArrayBufferView, Blob, FormData, nsAString, nsIDocument, +// nsIInputStream, nsIXHRSendable, URLSearchParams +template +class BodyExtractor final : public BodyExtractorBase +{ + Type* mBody; +public: + explicit BodyExtractor(Type* aBody) : mBody(aBody) + {} + + nsresult GetAsStream(nsIInputStream** aResult, + uint64_t* aContentLength, + nsACString& aContentTypeWithCharset, + nsACString& aCharset) const override; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_BodyExtractor_h diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index 63e2c83eaa..de54b6094e 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -12,7 +12,6 @@ #include "nsIStreamLoader.h" #include "nsIThreadRetargetableRequest.h" #include "nsIUnicodeDecoder.h" -#include "nsIUnicodeEncoder.h" #include "nsDOMString.h" #include "nsNetUtil.h" @@ -24,7 +23,6 @@ #include "mozilla/ErrorResult.h" #include "mozilla/dom/BodyUtil.h" #include "mozilla/dom/DOMError.h" -#include "mozilla/dom/EncodingUtils.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/FetchDriver.h" #include "mozilla/dom/File.h" @@ -40,6 +38,7 @@ #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "BodyExtractor.h" #include "FetchObserver.h" #include "InternalRequest.h" #include "InternalResponse.h" @@ -725,154 +724,48 @@ WorkerFetchResolver::FlushConsoleReport() mReporter->FlushConsoleReports(worker->GetDocument()); } -namespace { - -nsresult -ExtractFromArrayBuffer(const ArrayBuffer& aBuffer, - nsIInputStream** aStream, - uint64_t& aContentLength) -{ - aBuffer.ComputeLengthAndData(); - aContentLength = aBuffer.Length(); - //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok. - return NS_NewByteInputStream(aStream, - reinterpret_cast(aBuffer.Data()), - aBuffer.Length(), NS_ASSIGNMENT_COPY); -} - -nsresult -ExtractFromArrayBufferView(const ArrayBufferView& aBuffer, - nsIInputStream** aStream, - uint64_t& aContentLength) -{ - aBuffer.ComputeLengthAndData(); - aContentLength = aBuffer.Length(); - //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok. - return NS_NewByteInputStream(aStream, - reinterpret_cast(aBuffer.Data()), - aBuffer.Length(), NS_ASSIGNMENT_COPY); -} - -nsresult -ExtractFromBlob(const Blob& aBlob, - nsIInputStream** aStream, - nsCString& aContentType, - uint64_t& aContentLength) -{ - RefPtr impl = aBlob.Impl(); - ErrorResult rv; - aContentLength = impl->GetSize(rv); - if (NS_WARN_IF(rv.Failed())) { - return rv.StealNSResult(); - } - - impl->GetInternalStream(aStream, rv); - if (NS_WARN_IF(rv.Failed())) { - return rv.StealNSResult(); - } - - nsAutoString type; - impl->GetType(type); - aContentType = NS_ConvertUTF16toUTF8(type); - return NS_OK; -} - -nsresult -ExtractFromFormData(FormData& aFormData, - nsIInputStream** aStream, - nsCString& aContentType, - uint64_t& aContentLength) -{ - nsAutoCString unusedCharset; - return aFormData.GetSendInfo(aStream, &aContentLength, - aContentType, unusedCharset); -} - -nsresult -ExtractFromUSVString(const nsString& aStr, - nsIInputStream** aStream, - nsCString& aContentType, - uint64_t& aContentLength) -{ - nsCOMPtr encoder = EncodingUtils::EncoderForEncoding("UTF-8"); - if (!encoder) { - return NS_ERROR_OUT_OF_MEMORY; - } - - int32_t destBufferLen; - nsresult rv = encoder->GetMaxLength(aStr.get(), aStr.Length(), &destBufferLen); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - nsCString encoded; - if (!encoded.SetCapacity(destBufferLen, fallible)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - char* destBuffer = encoded.BeginWriting(); - int32_t srcLen = (int32_t) aStr.Length(); - int32_t outLen = destBufferLen; - rv = encoder->Convert(aStr.get(), &srcLen, destBuffer, &outLen); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - MOZ_ASSERT(outLen <= destBufferLen); - encoded.SetLength(outLen); - - aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8"); - aContentLength = outLen; - - return NS_NewCStringInputStream(aStream, encoded); -} - -nsresult -ExtractFromURLSearchParams(const URLSearchParams& aParams, - nsIInputStream** aStream, - nsCString& aContentType, - uint64_t& aContentLength) -{ - nsAutoString serialized; - aParams.Stringify(serialized); - aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8"); - aContentLength = serialized.Length(); - return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized)); -} -} // namespace - nsresult ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, - nsCString& aContentType, + nsCString& aContentTypeWithCharset, uint64_t& aContentLength) { MOZ_ASSERT(aStream); + nsAutoCString charset; + aContentTypeWithCharset.SetIsVoid(true); if (aBodyInit.IsArrayBuffer()) { - const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer(); - return ExtractFromArrayBuffer(buf, aStream, aContentLength); + BodyExtractor body(&aBodyInit.GetAsArrayBuffer()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsArrayBufferView()) { - const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView(); - return ExtractFromArrayBufferView(buf, aStream, aContentLength); + BodyExtractor body(&aBodyInit.GetAsArrayBufferView()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsBlob()) { - const Blob& blob = aBodyInit.GetAsBlob(); - return ExtractFromBlob(blob, aStream, aContentType, aContentLength); + Blob& blob = aBodyInit.GetAsBlob(); + BodyExtractor body(&blob); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsFormData()) { - FormData& form = aBodyInit.GetAsFormData(); - return ExtractFromFormData(form, aStream, aContentType, aContentLength); + FormData& formData = aBodyInit.GetAsFormData(); + BodyExtractor body(&formData); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsUSVString()) { - nsAutoString str; - str.Assign(aBodyInit.GetAsUSVString()); - return ExtractFromUSVString(str, aStream, aContentType, aContentLength); + BodyExtractor body(&aBodyInit.GetAsUSVString()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsURLSearchParams()) { - URLSearchParams& params = aBodyInit.GetAsURLSearchParams(); - return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength); + URLSearchParams& usp = aBodyInit.GetAsURLSearchParams(); + BodyExtractor body(&usp); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } NS_NOTREACHED("Should never reach here"); @@ -882,36 +775,47 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa nsresult ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, - nsCString& aContentType, + nsCString& aContentTypeWithCharset, uint64_t& aContentLength) { MOZ_ASSERT(aStream); MOZ_ASSERT(!*aStream); + nsAutoCString charset; + aContentTypeWithCharset.SetIsVoid(true); + if (aBodyInit.IsArrayBuffer()) { - const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer(); - return ExtractFromArrayBuffer(buf, aStream, aContentLength); + BodyExtractor body(&aBodyInit.GetAsArrayBuffer()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsArrayBufferView()) { - const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView(); - return ExtractFromArrayBufferView(buf, aStream, aContentLength); + BodyExtractor body(&aBodyInit.GetAsArrayBufferView()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsBlob()) { - const Blob& blob = aBodyInit.GetAsBlob(); - return ExtractFromBlob(blob, aStream, aContentType, aContentLength); + Blob& blob = aBodyInit.GetAsBlob(); + BodyExtractor body(&blob); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsFormData()) { - FormData& form = aBodyInit.GetAsFormData(); - return ExtractFromFormData(form, aStream, aContentType, aContentLength); + FormData& formData = aBodyInit.GetAsFormData(); + BodyExtractor body(&formData); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsUSVString()) { - nsAutoString str; - str.Assign(aBodyInit.GetAsUSVString()); - return ExtractFromUSVString(str, aStream, aContentType, aContentLength); + BodyExtractor body(&aBodyInit.GetAsUSVString()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } if (aBodyInit.IsURLSearchParams()) { - URLSearchParams& params = aBodyInit.GetAsURLSearchParams(); - return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength); + URLSearchParams& usp = aBodyInit.GetAsURLSearchParams(); + BodyExtractor body(&usp); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); } NS_NOTREACHED("Should never reach here"); diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 47fb62e885..4d9f6fc342 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -580,11 +580,11 @@ Request::Constructor(const GlobalObject& aGlobal, const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = bodyInitNullable.Value(); nsCOMPtr stream; - nsAutoCString contentType; + nsAutoCString contentTypeWithCharset; uint64_t contentLengthUnused; aRv = ExtractByteStreamFromBody(bodyInit, getter_AddRefs(stream), - contentType, + contentTypeWithCharset, contentLengthUnused); if (NS_WARN_IF(aRv.Failed())) { return nullptr; @@ -592,10 +592,10 @@ Request::Constructor(const GlobalObject& aGlobal, temporaryBody = stream; - if (!contentType.IsVoid() && + if (!contentTypeWithCharset.IsVoid() && !requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) { requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"), - contentType, aRv); + contentTypeWithCharset, aRv); } if (NS_WARN_IF(aRv.Failed())) { diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp index 20487906ba..337139e3d2 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -224,22 +224,23 @@ Response::Constructor(const GlobalObject& aGlobal, } nsCOMPtr bodyStream; - nsCString contentType; + nsCString contentTypeWithCharset; uint64_t bodySize = 0; aRv = ExtractByteStreamFromBody(aBody.Value().Value(), getter_AddRefs(bodyStream), - contentType, + contentTypeWithCharset, bodySize); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } internalResponse->SetBody(bodyStream, bodySize); - if (!contentType.IsVoid() && + if (!contentTypeWithCharset.IsVoid() && !internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) { // Ignore Append() failing here. ErrorResult error; - internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error); + internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), + contentTypeWithCharset, error); error.SuppressException(); } diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index cd41620f93..d08bdf0cf2 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -4,6 +4,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXPORTS.mozilla.dom += [ + 'BodyExtractor.h', 'ChannelInfo.h', 'Fetch.h', 'FetchDriver.h', @@ -19,6 +20,7 @@ EXPORTS.mozilla.dom += [ ] UNIFIED_SOURCES += [ + 'BodyExtractor.cpp', 'ChannelInfo.cpp', 'Fetch.cpp', 'FetchConsumer.cpp', diff --git a/dom/tests/mochitest/beacon/beacon-handler.sjs b/dom/tests/mochitest/beacon/beacon-handler.sjs index 4c51d371d8..e4a98f7202 100644 --- a/dom/tests/mochitest/beacon/beacon-handler.sjs +++ b/dom/tests/mochitest/beacon/beacon-handler.sjs @@ -88,7 +88,8 @@ function handleRequest(request, response) { data += charcode; } - var mimetype = request.getHeader("Content-Type"); + var mimetype = request.hasHeader("Content-Type") + ? request.getHeader("Content-Type") : "application/octet-stream"; // check to see if this is form data. if (mimetype.indexOf("multipart/form-data") != -1) { diff --git a/dom/url/URLSearchParams.cpp b/dom/url/URLSearchParams.cpp index 3a0311aabd..19cc3cda6b 100644 --- a/dom/url/URLSearchParams.cpp +++ b/dom/url/URLSearchParams.cpp @@ -575,9 +575,10 @@ URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader) NS_IMETHODIMP URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) + nsACString& aContentTypeWithCharset, + nsACString& aCharset) { - aContentType.AssignLiteral("application/x-www-form-urlencoded"); + aContentTypeWithCharset.AssignLiteral("application/x-www-form-urlencoded;charset=UTF-8"); aCharset.AssignLiteral("UTF-8"); nsAutoString serialized; diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 43d53cbac0..37c853da94 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -294,7 +294,7 @@ partial interface Navigator { partial interface Navigator { [Throws, Pref="beacon.enabled"] boolean sendBeacon(DOMString url, - optional (ArrayBufferView or Blob or DOMString or FormData)? data = null); + optional BodyInit? data = null); }; partial interface Navigator { diff --git a/dom/xhr/XMLHttpRequestMainThread.cpp b/dom/xhr/XMLHttpRequestMainThread.cpp index c7a4f6e6aa..48eb494747 100644 --- a/dom/xhr/XMLHttpRequestMainThread.cpp +++ b/dom/xhr/XMLHttpRequestMainThread.cpp @@ -2298,175 +2298,6 @@ XMLHttpRequestMainThread::ChangeStateToDone() } } -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - nsCOMPtr domdoc(do_QueryInterface(mBody)); - NS_ENSURE_STATE(domdoc); - aCharset.AssignLiteral("UTF-8"); - - nsresult rv; - nsCOMPtr storStream; - rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr output; - rv = storStream->GetOutputStream(0, getter_AddRefs(output)); - NS_ENSURE_SUCCESS(rv, rv); - - if (mBody->IsHTMLDocument()) { - aContentType.AssignLiteral("text/html"); - - nsString serialized; - if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - nsAutoCString utf8Serialized; - if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - uint32_t written; - rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written); - NS_ENSURE_SUCCESS(rv, rv); - - MOZ_ASSERT(written == utf8Serialized.Length()); - } else { - aContentType.AssignLiteral("application/xml"); - - nsCOMPtr serializer = - do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - // Make sure to use the encoding we'll send - rv = serializer->SerializeToStream(domdoc, output, aCharset); - NS_ENSURE_SUCCESS(rv, rv); - } - - output->Close(); - - uint32_t length; - rv = storStream->GetLength(&length); - NS_ENSURE_SUCCESS(rv, rv); - *aContentLength = length; - - rv = storStream->NewInputStream(0, aResult); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - aContentType.AssignLiteral("text/plain"); - aCharset.AssignLiteral("UTF-8"); - - nsAutoCString converted; - if (!AppendUTF16toUTF8(*mBody, converted, fallible)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - *aContentLength = converted.Length(); - nsresult rv = NS_NewCStringInputStream(aResult, converted); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - aContentType.AssignLiteral("text/plain"); - aCharset.Truncate(); - - nsresult rv = mBody->Available(aContentLength); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr stream(mBody); - stream.forget(aResult); - return NS_OK; -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset); -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset); -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset); -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset); -} - -static nsresult -GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength, - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) -{ - aContentType.SetIsVoid(true); - aCharset.Truncate(); - - *aContentLength = aDataLength; - const char* data = reinterpret_cast(aData); - - nsCOMPtr stream; - nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength, - NS_ASSIGNMENT_COPY); - NS_ENSURE_SUCCESS(rv, rv); - - stream.forget(aResult); - - return NS_OK; -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - mBody->ComputeLengthAndData(); - return GetBufferDataAsStream(mBody->Data(), mBody->Length(), - aResult, aContentLength, aContentType, aCharset); -} - -template<> nsresult -XMLHttpRequestMainThread::RequestBody::GetAsStream( - nsIInputStream** aResult, uint64_t* aContentLength, - nsACString& aContentType, nsACString& aCharset) const -{ - mBody->ComputeLengthAndData(); - return GetBufferDataAsStream(mBody->Data(), mBody->Length(), - aResult, aContentLength, aContentType, aCharset); -} - - nsresult XMLHttpRequestMainThread::CreateChannel() { @@ -2793,7 +2624,7 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant) // document? nsCOMPtr doc = do_QueryInterface(supports); if (doc) { - RequestBody body(doc); + BodyExtractor body(doc); return SendInternal(&body); } @@ -2802,21 +2633,21 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant) if (wstr) { nsAutoString string; wstr->GetData(string); - RequestBody body(&string); + BodyExtractor body(&string); return SendInternal(&body); } // nsIInputStream? nsCOMPtr stream = do_QueryInterface(supports); if (stream) { - RequestBody body(stream); + BodyExtractor body(stream); return SendInternal(&body); } // nsIXHRSendable? nsCOMPtr sendable = do_QueryInterface(supports); if (sendable) { - RequestBody body(sendable); + BodyExtractor body(sendable); return SendInternal(&body); } @@ -2829,7 +2660,7 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant) JS::Rooted obj(rootingCx, realVal.toObjectOrNull()); RootedSpiderMonkeyInterface buf(rootingCx); if (buf.Init(obj)) { - RequestBody body(&buf); + BodyExtractor body(&buf); return SendInternal(&body); } } @@ -2846,12 +2677,12 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant) nsString string; string.Adopt(data, len); - RequestBody body(&string); + BodyExtractor body(&string); return SendInternal(&body); } nsresult -XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody) +XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody) { NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED); @@ -2917,13 +2748,6 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody) mAuthorRequestHeaders.Get("content-type", uploadContentType); if (uploadContentType.IsVoid()) { uploadContentType = defaultContentType; - - if (!charset.IsEmpty()) { - // If we are providing the default content type, then we also need to - // provide a charset declaration. - uploadContentType.Append(NS_LITERAL_CSTRING(";charset=")); - uploadContentType.Append(charset); - } } // We don't want to set a charset for streams. diff --git a/dom/xhr/XMLHttpRequestMainThread.h b/dom/xhr/XMLHttpRequestMainThread.h index 4a6eeec2ef..ed02fd1da4 100644 --- a/dom/xhr/XMLHttpRequestMainThread.h +++ b/dom/xhr/XMLHttpRequestMainThread.h @@ -34,6 +34,7 @@ #include "mozilla/MemoryReporting.h" #include "mozilla/NotNull.h" #include "mozilla/dom/MutableBlobStorage.h" +#include "mozilla/dom/BodyExtractor.h" #include "mozilla/dom/TypedArray.h" #include "mozilla/dom/XMLHttpRequest.h" #include "mozilla/dom/XMLHttpRequestBinding.h" @@ -288,34 +289,7 @@ public: private: virtual ~XMLHttpRequestMainThread(); - class RequestBodyBase - { - public: - virtual nsresult GetAsStream(nsIInputStream** aResult, - uint64_t* aContentLength, - nsACString& aContentType, - nsACString& aCharset) const - { - NS_ASSERTION(false, "RequestBodyBase should not be used directly."); - return NS_ERROR_FAILURE; - } - }; - - template - class RequestBody final : public RequestBodyBase - { - Type* mBody; - public: - explicit RequestBody(Type* aBody) : mBody(aBody) - { - } - nsresult GetAsStream(nsIInputStream** aResult, - uint64_t* aContentLength, - nsACString& aContentType, - nsACString& aCharset) const override; - }; - - nsresult SendInternal(const RequestBodyBase* aBody); + nsresult SendInternal(const BodyExtractorBase* aBody); bool IsCrossSiteCORSRequest() const; bool IsDeniedCrossSiteCORSRequest(); @@ -336,7 +310,7 @@ public: Send(JSContext* /*aCx*/, const ArrayBuffer& aArrayBuffer, ErrorResult& aRv) override { - RequestBody body(&aArrayBuffer); + BodyExtractor body(&aArrayBuffer); aRv = SendInternal(&body); } @@ -344,28 +318,28 @@ public: Send(JSContext* /*aCx*/, const ArrayBufferView& aArrayBufferView, ErrorResult& aRv) override { - RequestBody body(&aArrayBufferView); + BodyExtractor body(&aArrayBufferView); aRv = SendInternal(&body); } virtual void Send(JSContext* /*aCx*/, Blob& aBlob, ErrorResult& aRv) override { - RequestBody body(&aBlob); + BodyExtractor body(&aBlob); aRv = SendInternal(&body); } virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams, ErrorResult& aRv) override { - RequestBody body(&aURLSearchParams); + BodyExtractor body(&aURLSearchParams); aRv = SendInternal(&body); } virtual void Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override { - RequestBody body(&aDoc); + BodyExtractor body(&aDoc); aRv = SendInternal(&body); } @@ -375,7 +349,7 @@ public: if (DOMStringIsNull(aString)) { Send(aCx, aRv); } else { - RequestBody body(&aString); + BodyExtractor body(&aString); aRv = SendInternal(&body); } } @@ -383,7 +357,7 @@ public: virtual void Send(JSContext* /*aCx*/, FormData& aFormData, ErrorResult& aRv) override { - RequestBody body(&aFormData); + BodyExtractor body(&aFormData); aRv = SendInternal(&body); } @@ -391,7 +365,7 @@ public: Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) override { NS_ASSERTION(aStream, "Null should go to string version"); - RequestBody body(aStream); + BodyExtractor body(aStream); aRv = SendInternal(&body); } diff --git a/dom/xhr/nsIXMLHttpRequest.idl b/dom/xhr/nsIXMLHttpRequest.idl index 53e80bab70..5505bd47ee 100644 --- a/dom/xhr/nsIXMLHttpRequest.idl +++ b/dom/xhr/nsIXMLHttpRequest.idl @@ -326,9 +326,12 @@ interface nsIXMLHttpRequest : nsISupports [uuid(840d0d00-e83e-4a29-b3c7-67e96e90a499)] interface nsIXHRSendable : nsISupports { + // contentTypeWithCharset can be set to the contentType or + // contentType+charset based on what the spec says. + // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract void getSendInfo(out nsIInputStream body, out uint64_t contentLength, - out ACString contentType, + out ACString contentTypeWithCharset, out ACString charset); };