From 1eab6170b1ef9b993c1bc25663015b27f9a35a8a Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Sun, 22 Nov 2020 20:57:19 -0800 Subject: [PATCH] #632: M1249352(remaining) M1358297(backbugs) M1369317(pp1,3,4) M1426996 --- netwerk/base/nsSimpleURI.cpp | 45 +++++++-------- netwerk/base/nsURLHelper.cpp | 40 ++++++++++--- netwerk/base/nsURLHelper.h | 12 ++++ xpcom/io/nsEscape.cpp | 105 ++++++++++++++++++++++++++++++++--- xpcom/io/nsEscape.h | 22 ++++++++ 5 files changed, 186 insertions(+), 38 deletions(-) diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp index 968f3a948..cadc340f7 100644 --- a/netwerk/base/nsSimpleURI.cpp +++ b/netwerk/base/nsSimpleURI.cpp @@ -192,27 +192,23 @@ NS_IMETHODIMP nsSimpleURI::SetSpec(const nsACString &aSpec) { NS_ENSURE_STATE(mMutable); - - // filter out unexpected chars "\r\n\t" if necessary - nsAutoCString filteredSpec; - net_FilterURIString(aSpec, filteredSpec); - const char* specPtr = filteredSpec.get(); - int32_t specLen = filteredSpec.Length(); - // nsSimpleURI currently restricts the charset to US-ASCII - nsAutoCString spec; - NS_EscapeURL(specPtr, specLen, esc_OnlyNonASCII|esc_AlwaysCopy, spec); - - int32_t colonPos = spec.FindChar(':'); - if (colonPos < 0 || !net_IsValidScheme(spec.get(), colonPos)) - return NS_ERROR_MALFORMED_URI; - - mScheme.Truncate(); - mozilla::DebugOnly n = spec.Left(mScheme, colonPos); - NS_ASSERTION(n == colonPos, "Left failed"); + nsresult rv = net_ExtractURLScheme(aSpec, mScheme); + if (NS_FAILED(rv)) { + return rv; + } ToLowerCase(mScheme); + nsAutoCString spec; + rv = net_FilterAndEscapeURI(aSpec, esc_OnlyNonASCII, spec); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t colonPos = spec.FindChar(':'); + MOZ_ASSERT(colonPos != kNotFound, "A colon should be in this string"); // This sets both mPath and mRef. + // If changed, see bug 1369317, especially part 2 -- Cameron return SetPath(Substring(spec, colonPos + 1)); } @@ -383,7 +379,13 @@ nsSimpleURI::SetRef(const nsACString &aRef) { NS_ENSURE_STATE(mMutable); - if (aRef.IsEmpty()) { + nsAutoCString ref; + nsresult rv = NS_EscapeURL(aRef, esc_OnlyNonASCII, ref, fallible); + if (NS_FAILED(rv)) { + return rv; + } + + if (ref.IsEmpty()) { // Empty string means to remove ref completely. mIsRefValid = false; mRef.Truncate(); // invariant: mRef should be empty when it's not valid @@ -513,12 +515,11 @@ nsSimpleURI::Resolve(const nsACString &relativePath, nsACString &result) } NS_IMETHODIMP -nsSimpleURI::GetAsciiSpec(nsACString &result) +nsSimpleURI::GetAsciiSpec(nsACString &aResult) { - nsAutoCString buf; - nsresult rv = GetSpec(buf); + nsresult rv = GetSpec(aResult); if (NS_FAILED(rv)) return rv; - NS_EscapeURL(buf, esc_OnlyNonASCII|esc_AlwaysCopy, result); + MOZ_ASSERT(IsASCII(aResult), "The spec should be ASCII"); return NS_OK; } diff --git a/netwerk/base/nsURLHelper.cpp b/netwerk/base/nsURLHelper.cpp index 9c12238e4..90ecc4c01 100644 --- a/netwerk/base/nsURLHelper.cpp +++ b/netwerk/base/nsURLHelper.cpp @@ -9,6 +9,7 @@ #include #include +#include "nsASCIIMask.h" #include "nsURLHelper.h" #include "nsIFile.h" #include "nsIURLParser.h" @@ -18,6 +19,7 @@ #include "mozilla/Preferences.h" #include "prnetdb.h" #include "mozilla/Tokenizer.h" +#include "nsEscape.h" #include "mozilla-config.h" #include "plvmx.h" @@ -543,7 +545,7 @@ net_ExtractURLScheme(const nsACString &inURI, } p.Claim(scheme); - scheme.StripChars("\r\n\t"); + scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab()); return NS_OK; #endif } @@ -615,8 +617,6 @@ net_IsAbsoluteURL(const nsACString& uri) void net_FilterURIString(const nsACString& input, nsACString& result) { - const char kCharsToStrip[] = "\r\n\t"; - result.Truncate(); auto start = input.BeginReading(); auto end = input.EndReading(); @@ -630,9 +630,14 @@ net_FilterURIString(const nsACString& input, nsACString& result) charFilter).base(); // Check if chars need to be stripped. - auto itr = std::find_first_of( - newStart, newEnd, std::begin(kCharsToStrip), std::end(kCharsToStrip)); - const bool needsStrip = itr != newEnd; + bool needsStrip = false; + const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab(); + for (auto itr = start; itr != end; ++itr) { + if (ASCIIMask::IsMasked(mask, *itr)) { + needsStrip = true; + break; + } + } // Just use the passed in string rather than creating new copies if no // changes are necessary. @@ -643,10 +648,31 @@ net_FilterURIString(const nsACString& input, nsACString& result) result.Assign(Substring(newStart, newEnd)); if (needsStrip) { - result.StripChars(kCharsToStrip); + result.StripTaggedASCII(mask); } } +nsresult +net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags, nsACString& aResult) +{ + aResult.Truncate(); + + auto start = aInput.BeginReading(); + auto end = aInput.EndReading(); + + // Trim off leading and trailing invalid chars. + auto charFilter = [](char c) { return static_cast(c) > 0x20; }; + auto newStart = std::find_if(start, end, charFilter); + auto newEnd = std::find_if( + std::reverse_iterator(end), + std::reverse_iterator(newStart), + charFilter).base(); + + const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab(); + return NS_EscapeAndFilterURL(Substring(newStart, newEnd), aFlags, + &mask, aResult, fallible); +} + #if defined(XP_WIN) bool net_NormalizeFileURL(const nsACString &aURL, nsCString &aResultBuf) diff --git a/netwerk/base/nsURLHelper.h b/netwerk/base/nsURLHelper.h index f30814c25..2bde59373 100644 --- a/netwerk/base/nsURLHelper.h +++ b/netwerk/base/nsURLHelper.h @@ -115,6 +115,18 @@ inline bool net_IsValidScheme(const nsAFlatCString &scheme) */ void net_FilterURIString(const nsACString& input, nsACString& result); +/** + * This function performs character stripping just like net_FilterURIString, + * with the added benefit of also performing percent escaping of dissallowed + * characters, all in one pass. Saving one pass is very important when operating + * on really large strings. + * + * @param aInput the URL spec we want to filter + * @param aFlags the flags which control which characters we escape + * @param aResult the out param to write to if filtering happens + */ +nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags, nsACString& aResult); + #if defined(XP_WIN) /** * On Win32 and OS/2 system's a back-slash in a file:// URL is equivalent to a diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp index 7936e9868..702aa15c0 100644 --- a/xpcom/io/nsEscape.cpp +++ b/xpcom/io/nsEscape.cpp @@ -11,6 +11,7 @@ #include "nsTArray.h" #include "nsCRT.h" #include "plstr.h" +#include "nsASCIIMask.h" #include "mozilla-config.h" #include "plvmx.h" @@ -381,10 +382,23 @@ static uint16_t dontNeedEscape(uint16_t aChar, uint32_t aFlags) //---------------------------------------------------------------------------------------- +/** + * Templated helper for URL escaping a portion of a string. + * + * @param aPart The pointer to the beginning of the portion of the string to + * escape. + * @param aPartLen The length of the string to escape. + * @param aFlags Flags used to configure escaping. @see EscapeMask + * @param aResult String that has the URL escaped portion appended to. Only + * altered if the string is URL escaped or |esc_AlwaysCopy| is specified. + * @param aDidAppend Indicates whether or not data was appended to |aResult|. + * @return NS_ERROR_INVALID_ARG, NS_ERROR_OUT_OF_MEMORY on failure. + */ template -static bool +static nsresult T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, - uint32_t aFlags, T& aResult) + uint32_t aFlags, const ASCIIMaskArray* aFilterMask, + T& aResult, bool& aDidAppend) { typedef nsCharTraits traits; typedef typename traits::unsigned_char_type unsigned_char_type; @@ -393,7 +407,7 @@ T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, if (!aPart) { NS_NOTREACHED("null pointer"); - return false; + return NS_ERROR_INVALID_ARG; } bool forced = !!(aFlags & esc_Forced); @@ -411,6 +425,19 @@ T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, for (size_t i = 0; i < aPartLen; ++i) { unsigned_char_type c = *src++; + // If there is a filter, we wish to skip any characters which match it. + // This is needed so we don't perform an extra pass just to extract the + // filtered characters. + if (aFilterMask && ASCIIMask::IsMasked(*aFilterMask, c)) { + if (!writing) { + if (!aResult.Append(aPart, i, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + writing = true; + } + continue; + } + // if the char has not to be escaped or whatever follows % is // a valid escaped string, just copy the char. // @@ -437,7 +464,9 @@ T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, } } else { /* do the escape magic */ if (!writing) { - aResult.Append(aPart, i); + if (!aResult.Append(aPart, i, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } writing = true; } uint32_t len = ::AppendPercentHex(tempBuffer + tempBufferPos, c); @@ -448,16 +477,21 @@ T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, // Flush the temp buffer if it doesnt't have room for another encoded char. if (tempBufferPos >= mozilla::ArrayLength(tempBuffer) - ENCODE_MAX_LEN) { NS_ASSERTION(writing, "should be writing"); - aResult.Append(tempBuffer, tempBufferPos); + if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } tempBufferPos = 0; } previousIsNonASCII = (c > 0x7f); } if (writing) { - aResult.Append(tempBuffer, tempBufferPos); + if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } } - return writing; + aDidAppend = writing; + return NS_OK; } bool @@ -467,13 +501,66 @@ NS_EscapeURL(const char* aPart, int32_t aPartLen, uint32_t aFlags, if (aPartLen < 0) { aPartLen = strlen(aPart); } - return T_EscapeURL(aPart, aPartLen, aFlags, aResult); + + bool result = false; + nsresult rv = T_EscapeURL(aPart, aPartLen, aFlags, nullptr, aResult, result); + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type)); + } + + return result; +} + +nsresult +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult, + const mozilla::fallible_t&) +{ + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, nullptr, aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + if (!aResult.Assign(aStr, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return rv; +} + +nsresult +NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags, + const ASCIIMaskArray* aFilterMask, + nsACString& aResult, const mozilla::fallible_t&) +{ + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aFilterMask, aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + aResult = aStr; + } + + return rv; } const nsSubstring& NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult) { - if (T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + bool result = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, nullptr, aResult, result); + + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsSubstring::char_type)); + } + + if (result) { return aResult; } return aStr; diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h index e45d13c53..59111f9b3 100644 --- a/xpcom/io/nsEscape.h +++ b/xpcom/io/nsEscape.h @@ -155,6 +155,28 @@ NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult) } return aStr; } + +/** + * Fallible version of NS_EscapeURL. On success aResult will point to either + * the original string or an escaped copy. + */ +nsresult +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult, + const mozilla::fallible_t&); + +// Forward declaration for nsASCIIMask.h +typedef std::array ASCIIMaskArray; + +/** + * The same as NS_EscapeURL, except it also filters out characters that match + * aFilterMask. + */ +nsresult +NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags, + const ASCIIMaskArray* aFilterMask, + nsACString& aResult, const mozilla::fallible_t&); + + inline const nsCSubstring& NS_UnescapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult) {