Skip to content

Commit

Permalink
Bug 1845006 - store the fully-serialized MimeType on data url channel…
Browse files Browse the repository at this point in the history
…s so XHR and fetch may use it for content-type response headers, and clean up the data url parsing code to better match the spec. r=kershaw,sunil,necko-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D184713
  • Loading branch information
wisniewskit committed Aug 2, 2023
1 parent 173c3a0 commit 6948e36
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 215 deletions.
77 changes: 36 additions & 41 deletions dom/base/MimeType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "MimeType.h"
#include "nsNetUtil.h"
#include "nsUnicharUtils.h"

namespace {
template <typename Char>
constexpr bool IsHTTPTokenPoint(Char aChar) {
using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
auto c = static_cast<UnsignedChar>(aChar);
return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ||
mozilla::IsAsciiAlphanumeric(c);
}

template <typename Char>
constexpr bool IsHTTPQuotedStringTokenPoint(Char aChar) {
using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
auto c = static_cast<UnsignedChar>(aChar);
return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c);
}

template <typename Char>
constexpr bool IsHTTPWhitespace(Char aChar) {
using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
auto c = static_cast<UnsignedChar>(aChar);
return c == 0x9 || c == 0xA || c == 0xD || c == 0x20;
}
} // namespace

template <typename char_type>
/* static */ mozilla::UniquePtr<TMimeType<char_type>>
TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
Expand All @@ -41,20 +16,20 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
// Steps 1-2
const char_type* pos = aMimeType.BeginReading();
const char_type* end = aMimeType.EndReading();
while (pos < end && IsHTTPWhitespace(*pos)) {
while (pos < end && NS_IsHTTPWhitespace(*pos)) {
++pos;
}
if (pos == end) {
return nullptr;
}
while (end > pos && IsHTTPWhitespace(*(end - 1))) {
while (end > pos && NS_IsHTTPWhitespace(*(end - 1))) {
--end;
}

// Steps 3-4
const char_type* typeStart = pos;
while (pos < end && *pos != '/') {
if (!IsHTTPTokenPoint(*pos)) {
if (!NS_IsHTTPTokenPoint(*pos)) {
return nullptr;
}
++pos;
Expand All @@ -76,14 +51,14 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
const char_type* subtypeStart = pos;
const char_type* subtypeEnd = nullptr;
while (pos < end && *pos != ';') {
if (!IsHTTPTokenPoint(*pos)) {
if (!NS_IsHTTPTokenPoint(*pos)) {
// If we hit a whitespace, check that the rest of
// the subtype is whitespace, otherwise fail.
if (IsHTTPWhitespace(*pos)) {
if (NS_IsHTTPWhitespace(*pos)) {
subtypeEnd = pos;
++pos;
while (pos < end && *pos != ';') {
if (!IsHTTPWhitespace(*pos)) {
if (!NS_IsHTTPWhitespace(*pos)) {
return nullptr;
}
++pos;
Expand Down Expand Up @@ -119,21 +94,41 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
++pos;

// Step 11.2
while (pos < end && IsHTTPWhitespace(*pos)) {
while (pos < end && NS_IsHTTPWhitespace(*pos)) {
++pos;
}

const char_type* namePos = pos;

// Steps 11.3 and 11.4
nsTString<char_type> paramName;
bool paramNameHadInvalidChars = false;
while (pos < end && *pos != ';' && *pos != '=') {
if (!IsHTTPTokenPoint(*pos)) {
if (!NS_IsHTTPTokenPoint(*pos)) {
paramNameHadInvalidChars = true;
}
paramName.Append(ToLowerCaseASCII(*pos));
++pos;
}

// Might as well check for base64 now
if (*pos != '=') {
// trim leading and trailing spaces
while (namePos < pos && NS_IsHTTPWhitespace(*namePos)) {
++namePos;
}
if (namePos < pos && ToLowerCaseASCII(*namePos) == 'b' &&
++namePos < pos && ToLowerCaseASCII(*namePos) == 'a' &&
++namePos < pos && ToLowerCaseASCII(*namePos) == 's' &&
++namePos < pos && ToLowerCaseASCII(*namePos) == 'e' &&
++namePos < pos && ToLowerCaseASCII(*namePos) == '6' &&
++namePos < pos && ToLowerCaseASCII(*namePos) == '4') {
while (++namePos < pos && NS_IsHTTPWhitespace(*namePos)) {
}
mimeType->mIsBase64 = namePos == pos;
}
}

// Step 11.5
if (pos < end) {
if (*pos == ';') {
Expand All @@ -160,10 +155,10 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
while (true) {
// Step 11.8.2.1
while (pos < end && *pos != '"' && *pos != '\\') {
if (!IsHTTPQuotedStringTokenPoint(*pos)) {
if (!NS_IsHTTPQuotedStringTokenPoint(*pos)) {
paramValueHadInvalidChars = true;
}
if (!IsHTTPTokenPoint(*pos)) {
if (!NS_IsHTTPTokenPoint(*pos)) {
paramValue.mRequiresQuoting = true;
}
paramValue.Append(*pos);
Expand All @@ -177,10 +172,10 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {

// Step 11.8.2.2.2
if (pos < end) {
if (!IsHTTPQuotedStringTokenPoint(*pos)) {
if (!NS_IsHTTPQuotedStringTokenPoint(*pos)) {
paramValueHadInvalidChars = true;
}
if (!IsHTTPTokenPoint(*pos)) {
if (!NS_IsHTTPTokenPoint(*pos)) {
paramValue.mRequiresQuoting = true;
}
paramValue.Append(*pos);
Expand Down Expand Up @@ -213,7 +208,7 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
// Step 11.9.2
const char_type* paramValueLastChar = pos - 1;
while (paramValueLastChar >= paramValueStart &&
IsHTTPWhitespace(*paramValueLastChar)) {
NS_IsHTTPWhitespace(*paramValueLastChar)) {
--paramValueLastChar;
}

Expand All @@ -223,10 +218,10 @@ TMimeType<char_type>::Parse(const nsTSubstring<char_type>& aMimeType) {
}

for (const char_type* c = paramValueStart; c <= paramValueLastChar; ++c) {
if (!IsHTTPQuotedStringTokenPoint(*c)) {
if (!NS_IsHTTPQuotedStringTokenPoint(*c)) {
paramValueHadInvalidChars = true;
}
if (!IsHTTPTokenPoint(*c)) {
if (!NS_IsHTTPTokenPoint(*c)) {
paramValue.mRequiresQuoting = true;
}
paramValue.Append(*c);
Expand Down
3 changes: 3 additions & 0 deletions dom/base/MimeType.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class TMimeType final {
ParameterValue() : mRequiresQuoting(false) {}
};

bool mIsBase64{false};
nsTString<char_type> mType;
nsTString<char_type> mSubtype;
nsTHashMap<typename HashKeyType<char_type>::HashType, ParameterValue>
Expand All @@ -52,6 +53,8 @@ class TMimeType final {
// Returns the `<mType>/<mSubtype>`
void GetFullType(nsTSubstring<char_type>& aStr) const;

bool IsBase64() const { return mIsBase64; }

// @param aName - the name of the parameter
// @return true if the parameter name is found, false otherwise.
bool HasParameter(const nsTSubstring<char_type>& aName) const;
Expand Down
23 changes: 17 additions & 6 deletions dom/fetch/FetchDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

#include "nsBaseChannel.h"
#include "nsContentPolicyUtils.h"
#include "nsDataChannel.h"
#include "nsDataHandler.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
Expand Down Expand Up @@ -1017,14 +1018,15 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest) {
bool foundOpaqueRedirect = false;

nsAutoCString contentType;
channel->GetContentType(contentType);

int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE;
rv = channel->GetContentLength(&contentLength);
MOZ_ASSERT_IF(NS_FAILED(rv),
contentLength == InternalResponse::UNKNOWN_BODY_SIZE);

if (httpChannel) {
channel->GetContentType(contentType);

uint32_t responseStatus = 0;
rv = httpChannel->GetResponseStatus(&responseStatus);
if (NS_FAILED(rv)) {
Expand Down Expand Up @@ -1098,11 +1100,20 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest) {
MOZ_ASSERT(!result.Failed());
}

if (!contentType.IsEmpty()) {
nsAutoCString contentCharset;
channel->GetContentCharset(contentCharset);
if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) {
contentType += ";charset="_ns + contentCharset;
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(channel->GetURI(getter_AddRefs(uri))) &&
uri->SchemeIs("data")) {
nsDataChannel* dchan = static_cast<nsDataChannel*>(channel.get());
MOZ_ASSERT(dchan);
contentType.Assign(dchan->MimeType());
} else {
channel->GetContentType(contentType);
if (!contentType.IsEmpty()) {
nsAutoCString contentCharset;
channel->GetContentCharset(contentCharset);
if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) {
contentType += ";charset="_ns + contentCharset;
}
}
}

Expand Down
36 changes: 23 additions & 13 deletions dom/xhr/XMLHttpRequestMainThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/dom/ProgressEvent.h"
#include "nsDataChannel.h"
#include "nsIJARChannel.h"
#include "nsIJARURI.h"
#include "nsLayoutCID.h"
Expand Down Expand Up @@ -1144,6 +1145,26 @@ bool XMLHttpRequestMainThread::IsSafeHeader(
return isSafe;
}

bool XMLHttpRequestMainThread::GetContentType(nsACString& aValue) const {
MOZ_ASSERT(mChannel);
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(mChannel->GetURI(getter_AddRefs(uri))) &&
uri->SchemeIs("data")) {
nsDataChannel* dchan = static_cast<nsDataChannel*>(mChannel.get());
MOZ_ASSERT(dchan);
aValue.Assign(dchan->MimeType());
return true;
}
if (NS_SUCCEEDED(mChannel->GetContentType(aValue))) {
nsCString value;
if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
aValue.AppendLiteral(";charset=");
aValue.Append(value);
}
return true;
}
return false;
}
void XMLHttpRequestMainThread::GetAllResponseHeaders(
nsACString& aResponseHeaders, ErrorResult& aRv) {
NOT_CALLABLE_IN_SYNC_SEND_RV
Expand Down Expand Up @@ -1176,13 +1197,9 @@ void XMLHttpRequestMainThread::GetAllResponseHeaders(

// Even non-http channels supply content type.
nsAutoCString value;
if (NS_SUCCEEDED(mChannel->GetContentType(value))) {
if (GetContentType(value)) {
aResponseHeaders.AppendLiteral("Content-Type: ");
aResponseHeaders.Append(value);
if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
aResponseHeaders.AppendLiteral(";charset=");
aResponseHeaders.Append(value);
}
aResponseHeaders.AppendLiteral("\r\n");
}

Expand Down Expand Up @@ -1240,18 +1257,11 @@ void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,

// Content Type:
if (header.LowerCaseEqualsASCII("content-type")) {
if (NS_FAILED(mChannel->GetContentType(_retval))) {
if (!GetContentType(_retval)) {
// Means no content type
_retval.SetIsVoid(true);
return;
}

nsCString value;
if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) &&
!value.IsEmpty()) {
_retval.AppendLiteral(";charset=");
_retval.Append(value);
}
}

// Content Length:
Expand Down
3 changes: 3 additions & 0 deletions dom/xhr/XMLHttpRequestMainThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest,
// set to "text/xml".
void EnsureChannelContentType();

// Gets the value of the final content-type header from the channel.
bool GetContentType(nsACString& aValue) const;

already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
already_AddRefed<nsIJARChannel> GetCurrentJARChannel();

Expand Down
24 changes: 24 additions & 0 deletions netwerk/base/nsNetUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,30 @@ bool NS_IsValidHTTPToken(const nsACString& aToken);
*/
void NS_TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest);

template <typename Char>
constexpr bool NS_IsHTTPTokenPoint(Char aChar) {
using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
auto c = static_cast<UnsignedChar>(aChar);
return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ||
mozilla::IsAsciiAlphanumeric(c);
}

template <typename Char>
constexpr bool NS_IsHTTPQuotedStringTokenPoint(Char aChar) {
using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
auto c = static_cast<UnsignedChar>(aChar);
return c == 0x9 || (c >= ' ' && c <= '~') || mozilla::IsNonAsciiLatin1(c);
}

template <typename Char>
constexpr bool NS_IsHTTPWhitespace(Char aChar) {
using UnsignedChar = typename mozilla::detail::MakeUnsignedChar<Char>::Type;
auto c = static_cast<UnsignedChar>(aChar);
return c == 0x9 || c == 0xA || c == 0xD || c == 0x20;
}

/**
* Return true if the given request must be upgraded to HTTPS.
* If |aResultCallback| is provided and the storage is not ready to read, the
Expand Down
2 changes: 1 addition & 1 deletion netwerk/protocol/data/nsDataChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ nsresult nsDataChannel::OpenContentStream(bool async, nsIInputStream** result,
nsDependentCSubstring dataRange;
bool lBase64;
rv = nsDataHandler::ParsePathWithoutRef(path, contentType, &contentCharset,
lBase64, &dataRange);
lBase64, &dataRange, &mMimeType);
if (NS_FAILED(rv)) return rv;

// This will avoid a copy if nothing needs to be unescaped.
Expand Down
4 changes: 4 additions & 0 deletions netwerk/protocol/data/nsDataChannel.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ 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;
};

#endif /* nsDataChannel_h___ */
Loading

0 comments on commit 6948e36

Please sign in to comment.