diff --git a/ipc/glue/InputStreamParams.ipdlh b/ipc/glue/InputStreamParams.ipdlh index eb6869c17028..f6de7e071461 100644 --- a/ipc/glue/InputStreamParams.ipdlh +++ b/ipc/glue/InputStreamParams.ipdlh @@ -11,6 +11,12 @@ using struct mozilla::void_t namespace mozilla { namespace ipc { +struct HeaderEntry +{ + nsCString name; + nsCString value; +}; + struct StringInputStreamParams { nsCString data; @@ -86,10 +92,8 @@ struct BufferedInputStreamParams struct MIMEInputStreamParams { OptionalInputStreamParams optionalStream; - nsCString headers; - nsCString contentLength; + HeaderEntry[] headers; bool startedReading; - bool addContentLength; }; } // namespace ipc diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl index 82992d939b2b..50828ab7afe1 100644 --- a/netwerk/base/nsIMIMEInputStream.idl +++ b/netwerk/base/nsIMIMEInputStream.idl @@ -3,6 +3,7 @@ * 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 "nsIHttpHeaderVisitor.idl" #include "nsIInputStream.idl" /** @@ -19,6 +20,10 @@ interface nsIMIMEInputStream : nsIInputStream * using the available() method on the data stream. The value is * recalculated every time the stream is rewinded to the start. * Not allowed to be changed once the stream has been started to be read. + * + * @deprecated A Content-Length header is automatically added when + * attaching the stream to a channel, so this setting no longer has any + * effect, and may not be set to false. */ attribute boolean addContentLength; @@ -30,6 +35,15 @@ interface nsIMIMEInputStream : nsIInputStream */ void addHeader(in string name, in string value); + /** + * Visits all headers which have been added via addHeader. Calling + * addHeader while visiting request headers has undefined behavior. + * + * @param aVisitor + * The header visitor instance. + */ + void visitHeaders(in nsIHttpHeaderVisitor visitor); + /** * Sets data-stream. May not be called once the stream has been started * to be read. diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp index ce1188ea0817..02c727b0be68 100644 --- a/netwerk/base/nsMIMEInputStream.cpp +++ b/netwerk/base/nsMIMEInputStream.cpp @@ -12,18 +12,19 @@ #include "nsCOMPtr.h" #include "nsComponentManagerUtils.h" -#include "nsIMultiplexInputStream.h" +#include "nsIHttpHeaderVisitor.h" #include "nsIMIMEInputStream.h" #include "nsISeekableStream.h" -#include "nsIStringStream.h" #include "nsString.h" #include "nsMIMEInputStream.h" #include "nsIClassInfoImpl.h" #include "nsIIPCSerializableInputStream.h" +#include "mozilla/Move.h" #include "mozilla/ipc/InputStreamUtils.h" using namespace mozilla::ipc; using mozilla::Maybe; +using mozilla::Move; class nsMIMEInputStream : public nsIMIMEInputStream, public nsISeekableStream, @@ -40,8 +41,6 @@ public: NS_DECL_NSISEEKABLESTREAM NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM - nsresult Init(); - private: void InitStreams(); @@ -55,15 +54,9 @@ private: const char* aFromRawSegment, uint32_t aToOffset, uint32_t aCount, uint32_t *aWriteCount); - nsCString mHeaders; - nsCOMPtr mHeaderStream; - - nsCString mContentLength; - nsCOMPtr mCLStream; - - nsCOMPtr mData; - nsCOMPtr mStream; - bool mAddContentLength; + nsTArray mHeaders; + + nsCOMPtr mStream; bool mStartedReading; }; @@ -83,8 +76,7 @@ NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream, nsIInputStream, nsISeekableStream) -nsMIMEInputStream::nsMIMEInputStream() : mAddContentLength(false), - mStartedReading(false) +nsMIMEInputStream::nsMIMEInputStream() : mStartedReading(false) { } @@ -92,40 +84,21 @@ nsMIMEInputStream::~nsMIMEInputStream() { } -nsresult nsMIMEInputStream::Init() -{ - nsresult rv = NS_OK; - mStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", - &rv); - NS_ENSURE_SUCCESS(rv, rv); - - mHeaderStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", - &rv); - NS_ENSURE_SUCCESS(rv, rv); - mCLStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mStream->AppendStream(mHeaderStream); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mStream->AppendStream(mCLStream); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - - NS_IMETHODIMP nsMIMEInputStream::GetAddContentLength(bool *aAddContentLength) { - *aAddContentLength = mAddContentLength; + *aAddContentLength = true; return NS_OK; } NS_IMETHODIMP nsMIMEInputStream::SetAddContentLength(bool aAddContentLength) { NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); - mAddContentLength = aAddContentLength; + if (!aAddContentLength) { + // Content-Length is automatically added by the channel when setting the + // upload stream, so setting this to false has no practical effect. + return NS_ERROR_FAILURE; + } return NS_OK; } @@ -133,30 +106,39 @@ NS_IMETHODIMP nsMIMEInputStream::AddHeader(const char *aName, const char *aValue) { NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); - mHeaders.Append(aName); - mHeaders.AppendLiteral(": "); - mHeaders.Append(aValue); - mHeaders.AppendLiteral("\r\n"); - // Just in case someone somehow uses our stream, lets at least - // let the stream have a valid pointer. The stream will be properly - // initialized in nsMIMEInputStream::InitStreams - mHeaderStream->ShareData(mHeaders.get(), 0); + HeaderEntry* entry = mHeaders.AppendElement(); + entry->name().Append(aName); + entry->value().Append(aValue); return NS_OK; } +NS_IMETHODIMP +nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor *visitor) +{ + nsresult rv; + + for (auto& header : mHeaders) { + rv = visitor->VisitHeader(header.name(), header.value()); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; +} + NS_IMETHODIMP nsMIMEInputStream::SetData(nsIInputStream *aStream) { NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE); - // Remove the old stream if there is one - if (mData) - mStream->RemoveStream(2); - mData = aStream; - if (aStream) - mStream->AppendStream(mData); + nsCOMPtr seekable = do_QueryInterface(aStream); + if (!seekable) { + return NS_ERROR_INVALID_ARG; + } + + mStream = aStream; return NS_OK; } @@ -164,7 +146,7 @@ NS_IMETHODIMP nsMIMEInputStream::GetData(nsIInputStream **aStream) { NS_ENSURE_ARG_POINTER(aStream); - *aStream = mData; + *aStream = mStream; NS_IF_ADDREF(*aStream); return NS_OK; } @@ -176,28 +158,13 @@ void nsMIMEInputStream::InitStreams() "Don't call initStreams twice without rewinding"); mStartedReading = true; - - // We'll use the content-length stream to add the final \r\n - if (mAddContentLength) { - uint64_t cl = 0; - if (mData) { - mData->Available(&cl); - } - mContentLength.AssignLiteral("Content-Length: "); - mContentLength.AppendInt(cl); - mContentLength.AppendLiteral("\r\n\r\n"); - } - else { - mContentLength.AssignLiteral("\r\n"); - } - mCLStream->ShareData(mContentLength.get(), -1); - mHeaderStream->ShareData(mHeaders.get(), -1); } #define INITSTREAMS \ if (!mStartedReading) { \ + NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \ InitStreams(); \ } @@ -205,8 +172,11 @@ if (!mStartedReading) { \ NS_IMETHODIMP nsMIMEInputStream::Seek(int32_t whence, int64_t offset) { + NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); + nsresult rv; nsCOMPtr stream = do_QueryInterface(mStream); + if (whence == NS_SEEK_SET && offset == 0) { rv = stream->Seek(whence, offset); if (NS_SUCCEEDED(rv)) @@ -284,22 +254,11 @@ nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result) if (outer) return NS_ERROR_NO_AGGREGATION; - nsMIMEInputStream *inst = new nsMIMEInputStream(); + RefPtr inst = new nsMIMEInputStream(); if (!inst) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(inst); - - nsresult rv = inst->Init(); - if (NS_FAILED(rv)) { - NS_RELEASE(inst); - return rv; - } - - rv = inst->QueryInterface(iid, result); - NS_RELEASE(inst); - - return rv; + return inst->QueryInterface(iid, result); } void @@ -308,12 +267,9 @@ nsMIMEInputStream::Serialize(InputStreamParams& aParams, { MIMEInputStreamParams params; - if (mData) { - nsCOMPtr stream = do_QueryInterface(mData); - MOZ_ASSERT(stream); - + if (mStream) { InputStreamParams wrappedParams; - SerializeInputStream(stream, wrappedParams, aFileDescriptors); + SerializeInputStream(mStream, wrappedParams, aFileDescriptors); NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None, "Wrapped stream failed to serialize!"); @@ -325,9 +281,7 @@ nsMIMEInputStream::Serialize(InputStreamParams& aParams, } params.headers() = mHeaders; - params.contentLength() = mContentLength; params.startedReading() = mStartedReading; - params.addContentLength() = mAddContentLength; aParams = params; } @@ -346,17 +300,10 @@ nsMIMEInputStream::Deserialize(const InputStreamParams& aParams, const OptionalInputStreamParams& wrappedParams = params.optionalStream(); mHeaders = params.headers(); - mContentLength = params.contentLength(); mStartedReading = params.startedReading(); - // nsMIMEInputStream::Init() already appended mHeaderStream & mCLStream - mHeaderStream->ShareData(mHeaders.get(), - mStartedReading ? mHeaders.Length() : 0); - mCLStream->ShareData(mContentLength.get(), - mStartedReading ? mContentLength.Length() : 0); - - nsCOMPtr stream; if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) { + nsCOMPtr stream; stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(), aFileDescriptors); if (!stream) { @@ -364,20 +311,13 @@ nsMIMEInputStream::Deserialize(const InputStreamParams& aParams, return false; } - mData = stream; - - if (NS_FAILED(mStream->AppendStream(mData))) { - NS_WARNING("Failed to append stream!"); - return false; - } + mStream = stream; } else { NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t, "Unknown type for OptionalInputStreamParams!"); } - mAddContentLength = params.addContentLength(); - return true; } diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index 210a6ab58736..5646c41146ea 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -52,7 +52,9 @@ #include "nsIURL.h" #include "nsIConsoleService.h" #include "mozilla/BinarySearch.h" +#include "mozilla/DebugOnly.h" #include "nsIHttpHeaderVisitor.h" +#include "nsIMIMEInputStream.h" #include "nsIXULRuntime.h" #include "nsICacheInfoChannel.h" #include "nsIDOMWindowUtils.h" @@ -63,6 +65,85 @@ namespace mozilla { namespace net { +static +bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) +{ + // IMPORTANT: keep this list ASCII-code sorted + static nsHttpAtom const* blackList[] = { + &nsHttp::Accept, + &nsHttp::Accept_Encoding, + &nsHttp::Accept_Language, + &nsHttp::Authentication, + &nsHttp::Authorization, + &nsHttp::Connection, + &nsHttp::Content_Length, + &nsHttp::Cookie, + &nsHttp::Host, + &nsHttp::If, + &nsHttp::If_Match, + &nsHttp::If_Modified_Since, + &nsHttp::If_None_Match, + &nsHttp::If_None_Match_Any, + &nsHttp::If_Range, + &nsHttp::If_Unmodified_Since, + &nsHttp::Proxy_Authenticate, + &nsHttp::Proxy_Authorization, + &nsHttp::Range, + &nsHttp::TE, + &nsHttp::Transfer_Encoding, + &nsHttp::Upgrade, + &nsHttp::User_Agent, + &nsHttp::WWW_Authenticate + }; + + class HttpAtomComparator + { + nsHttpAtom const& mTarget; + public: + explicit HttpAtomComparator(nsHttpAtom const& aTarget) + : mTarget(aTarget) {} + int operator()(nsHttpAtom const* aVal) const { + if (mTarget == *aVal) { + return 0; + } + return strcmp(mTarget._val, aVal->_val); + } + }; + + size_t unused; + return BinarySearchIf(blackList, 0, ArrayLength(blackList), + HttpAtomComparator(aHeader), &unused); +} + +class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor +{ +public: + NS_DECL_ISUPPORTS + + explicit AddHeadersToChannelVisitor(nsIHttpChannel *aChannel) + : mChannel(aChannel) + { + } + + NS_IMETHOD VisitHeader(const nsACString& aHeader, + const nsACString& aValue) override + { + nsHttpAtom atom = nsHttp::ResolveAtom(aHeader); + if (!IsHeaderBlacklistedForRedirectCopy(atom)) { + mChannel->SetRequestHeader(aHeader, aValue, false); + } + return NS_OK; + } +private: + ~AddHeadersToChannelVisitor() + { + } + + nsCOMPtr mChannel; +}; + +NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor) + HttpBaseChannel::HttpBaseChannel() : mStartPos(UINT64_MAX) , mStatus(NS_OK) @@ -653,22 +734,38 @@ HttpBaseChannel::SetUploadStream(nsIInputStream *stream, if (stream) { nsAutoCString method; - bool hasHeaders; + bool hasHeaders = false; // This method and ExplicitSetUploadStream mean different things by "empty // content type string". This method means "no header", but // ExplicitSetUploadStream means "header with empty value". So we have to // massage the contentType argument into the form ExplicitSetUploadStream // expects. - nsAutoCString contentType; - if (contentTypeArg.IsEmpty()) { - method = NS_LITERAL_CSTRING("POST"); - hasHeaders = true; + nsCOMPtr mimeStream; + nsCString contentType(contentTypeArg); + if (contentType.IsEmpty()) { contentType.SetIsVoid(true); + method = NS_LITERAL_CSTRING("POST"); + + // MIME streams are a special case, and include headers which need to be + // copied to the channel. + mimeStream = do_QueryInterface(stream); + if (mimeStream) { + // Copy non-origin related headers to the channel. + nsCOMPtr visitor = + new AddHeadersToChannelVisitor(this); + mimeStream->VisitHeaders(visitor); + + return ExplicitSetUploadStream(stream, contentType, contentLength, + method, hasHeaders); + } + + hasHeaders = true; } else { method = NS_LITERAL_CSTRING("PUT"); - hasHeaders = false; - contentType = contentTypeArg; + + MOZ_ASSERT(NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))), + "nsIMIMEInputStream should not be set with an explicit content type"); } return ExplicitSetUploadStream(stream, contentType, contentLength, method, hasHeaders); @@ -810,6 +907,13 @@ HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream, // Ensure stream is set and method is valid NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE); + { + DebugOnly> mimeStream; + MOZ_ASSERT(!aStreamHasHeaders || + NS_FAILED(CallQueryInterface(aStream, getter_AddRefs(mimeStream.value))), + "nsIMIMEInputStream should not include headers"); + } + if (aContentLength < 0 && !aStreamHasHeaders) { nsresult rv = aStream->Available(reinterpret_cast(&aContentLength)); if (NS_FAILED(rv) || aContentLength < 0) { @@ -2893,85 +2997,6 @@ HttpBaseChannel::ShouldRewriteRedirectToGET(uint32_t httpStatus, return false; } -static -bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) -{ - // IMPORTANT: keep this list ASCII-code sorted - static nsHttpAtom const* blackList[] = { - &nsHttp::Accept, - &nsHttp::Accept_Encoding, - &nsHttp::Accept_Language, - &nsHttp::Authentication, - &nsHttp::Authorization, - &nsHttp::Connection, - &nsHttp::Content_Length, - &nsHttp::Cookie, - &nsHttp::Host, - &nsHttp::If, - &nsHttp::If_Match, - &nsHttp::If_Modified_Since, - &nsHttp::If_None_Match, - &nsHttp::If_None_Match_Any, - &nsHttp::If_Range, - &nsHttp::If_Unmodified_Since, - &nsHttp::Proxy_Authenticate, - &nsHttp::Proxy_Authorization, - &nsHttp::Range, - &nsHttp::TE, - &nsHttp::Transfer_Encoding, - &nsHttp::Upgrade, - &nsHttp::User_Agent, - &nsHttp::WWW_Authenticate - }; - - class HttpAtomComparator - { - nsHttpAtom const& mTarget; - public: - explicit HttpAtomComparator(nsHttpAtom const& aTarget) - : mTarget(aTarget) {} - int operator()(nsHttpAtom const* aVal) const { - if (mTarget == *aVal) { - return 0; - } - return strcmp(mTarget._val, aVal->_val); - } - }; - - size_t unused; - return BinarySearchIf(blackList, 0, ArrayLength(blackList), - HttpAtomComparator(aHeader), &unused); -} - -class SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor -{ -public: - NS_DECL_ISUPPORTS - - explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel) - : mChannel(aChannel) - { - } - - NS_IMETHOD VisitHeader(const nsACString& aHeader, - const nsACString& aValue) override - { - nsHttpAtom atom = nsHttp::ResolveAtom(aHeader); - if (!IsHeaderBlacklistedForRedirectCopy(atom)) { - mChannel->SetRequestHeader(aHeader, aValue, false); - } - return NS_OK; - } -private: - ~SetupReplacementChannelHeaderVisitor() - { - } - - nsCOMPtr mChannel; -}; - -NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor) - nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, nsIChannel *newChannel, @@ -3267,7 +3292,7 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, nsIChannelEventSink::REDIRECT_STS_UPGRADE)) { // Copy non-origin related headers to the new channel. nsCOMPtr visitor = - new SetupReplacementChannelHeaderVisitor(httpChannel); + new AddHeadersToChannelVisitor(httpChannel); mRequestHead.VisitHeaders(visitor); } diff --git a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js index 150c4feca9b5..c8fbb87872b7 100644 --- a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js +++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js @@ -149,15 +149,23 @@ document.getElementById('form').submit(); `; } else if (this.uri.spec.startsWith(ACTION_BASE)) { var postData = ""; + var headers = {}; if (this._uploadStream) { var bstream = Cc["@mozilla.org/binaryinputstream;1"] .createInstance(Ci.nsIBinaryInputStream); bstream.setInputStream(this._uploadStream); postData = bstream.readBytes(bstream.available()); + + if (this._uploadStream instanceof Ci.nsIMIMEInputStream) { + this._uploadStream.visitHeaders((name, value) => { + headers[name] = value; + }); + } } data += ` + `; } @@ -214,8 +222,9 @@ function frameScript() { if (frame) { var upload_stream = frame.contentDocument.getElementById("upload_stream"); var post_data = frame.contentDocument.getElementById("post_data"); - if (upload_stream && post_data) { - sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value]); + var headers = frame.contentDocument.getElementById("upload_headers"); + if (upload_stream && post_data && headers) { + sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value, headers.value]); return; } } @@ -236,9 +245,9 @@ function loadTestTab(uri) { browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true); return new Promise(resolve => { - function listener({ data: [hasUploadStream, postData] }) { + function listener({ data: [hasUploadStream, postData, headers] }) { manager.removeMessageListener("Test:IFrameLoaded", listener); - resolve([hasUploadStream, atob(postData)]); + resolve([hasUploadStream, atob(postData), JSON.parse(headers)]); } manager.addMessageListener("Test:IFrameLoaded", listener); @@ -272,13 +281,14 @@ add_task(function*() { }); add_task(function*() { - var [hasUploadStream, postData] = yield loadTestTab(POST_FORM_URI); + var [hasUploadStream, postData, headers] = yield loadTestTab(POST_FORM_URI); + is(hasUploadStream, "yes", "post action should have uploadStream"); - is(postData, - "Content-Type: text/plain\r\n" + - "Content-Length: 9\r\n" + - "\r\n" + - "foo=bar\r\n", "POST data is received correctly"); + is(postData, "foo=bar\r\n", + "POST data is received correctly"); + + is(headers["Content-Type"], "text/plain", "Content-Type header is correct"); + is(headers["Content-Length"], undefined, "Content-Length header is correct"); gBrowser.removeCurrentTab(); }); diff --git a/toolkit/modules/addons/WebRequestUpload.jsm b/toolkit/modules/addons/WebRequestUpload.jsm index 0569221b28ff..2b5cdbf3766b 100644 --- a/toolkit/modules/addons/WebRequestUpload.jsm +++ b/toolkit/modules/addons/WebRequestUpload.jsm @@ -370,18 +370,20 @@ function parseFormData(stream, channel, lenient = false) { try { let headers; if (stream instanceof Ci.nsIMIMEInputStream && stream.data) { - // MIME input streams encode additional headers as a block at the - // beginning of their stream. The actual request data comes from a - // sub-stream, which is accessible via their `data` member. The - // difference in available bytes between the outer stream and the - // inner data stream tells us the size of that header block. - // - // Since we need to know at least the value of the Content-Type - // header to properly parse the request body, we need to read and - // parse the header block in order to extract it. + if (channel instanceof Ci.nsIUploadChannel2 && channel.uploadStreamHasHeaders) { + // MIME input streams encode additional headers as a block at the + // beginning of their stream. The actual request data comes from a + // sub-stream, which is accessible via their `data` member. The + // difference in available bytes between the outer stream and the + // inner data stream tells us the size of that header block. + // + // Since we need to know at least the value of the Content-Type + // header to properly parse the request body, we need to read and + // parse the header block in order to extract it. - headers = readString(createTextStream(stream), - stream.available() - stream.data.available()); + headers = readString(createTextStream(stream), + stream.available() - stream.data.available()); + } rewind(stream); stream = stream.data;