Bug 1305162: Part 1a - Separate nsIMIMEInputStream headers from stream data. r=dragana

MozReview-Commit-ID: F1qZCBWUNRG

--HG--
extra : rebase_source : 7a4fdab3e4843a042a3d1101c5e58a6bb556ef7a
extra : source : de131f7c1fc1f60db42509aea646ef4540e6c5fe
This commit is contained in:
Kris Maglione 2017-01-20 15:43:07 -08:00
Родитель 74f10163fe
Коммит 440d6fcd02
6 изменённых файлов: 213 добавлений и 218 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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.

Просмотреть файл

@ -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<nsIStringInputStream> mHeaderStream;
nsCString mContentLength;
nsCOMPtr<nsIStringInputStream> mCLStream;
nsCOMPtr<nsIInputStream> mData;
nsCOMPtr<nsIMultiplexInputStream> mStream;
bool mAddContentLength;
nsTArray<HeaderEntry> mHeaders;
nsCOMPtr<nsIInputStream> 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<nsISeekableStream> 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<nsISeekableStream> 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<nsMIMEInputStream> 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<nsIInputStream> 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<nsIInputStream> stream;
if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
nsCOMPtr<nsIInputStream> 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;
}

Просмотреть файл

@ -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<nsIHttpChannel> 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<nsIMIMEInputStream> 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<nsIHttpHeaderVisitor> 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<nsCOMPtr<nsIMIMEInputStream>> 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<uint64_t*>(&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<nsIHttpChannel> 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<nsIHttpHeaderVisitor> visitor =
new SetupReplacementChannelHeaderVisitor(httpChannel);
new AddHeadersToChannelVisitor(httpChannel);
mRequestHead.VisitHeaders(visitor);
}

Просмотреть файл

@ -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 += `
<input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
<input id="post_data" value="${btoa(postData)}">
<input id="upload_headers" value='${JSON.stringify(headers)}'>
`;
}
@ -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();
});

Просмотреть файл

@ -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;