nsDataHandler.cpp (8771B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsDataChannel.h" 7 #include "nsDataHandler.h" 8 #include "nsNetCID.h" 9 #include "nsError.h" 10 #include "nsIOService.h" 11 #include "nsNetUtil.h" 12 #include "nsSimpleURI.h" 13 #include "nsUnicharUtils.h" 14 #include "mozilla/dom/MimeType.h" 15 #include "mozilla/StaticPrefs_network.h" 16 #include "mozilla/Try.h" 17 #include "DefaultURI.h" 18 19 using namespace mozilla; 20 21 //////////////////////////////////////////////////////////////////////////////// 22 23 NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference) 24 25 nsresult nsDataHandler::Create(const nsIID& aIID, void** aResult) { 26 RefPtr<nsDataHandler> ph = new nsDataHandler(); 27 return ph->QueryInterface(aIID, aResult); 28 } 29 30 //////////////////////////////////////////////////////////////////////////////// 31 // nsIProtocolHandler methods: 32 33 NS_IMETHODIMP 34 nsDataHandler::GetScheme(nsACString& result) { 35 result.AssignLiteral("data"); 36 return NS_OK; 37 } 38 39 /* static */ nsresult nsDataHandler::CreateNewURI(const nsACString& aSpec, 40 const char* aCharset, 41 nsIURI* aBaseURI, 42 nsIURI** result) { 43 nsCOMPtr<nsIURI> uri; 44 nsAutoCString contentType; 45 bool base64; 46 MOZ_TRY(ParseURI(aSpec, contentType, /* contentCharset = */ nullptr, base64, 47 /* dataBuffer = */ nullptr)); 48 49 // Strip whitespace unless this is text, where whitespace is important 50 // Don't strip escaped whitespace though (bug 391951) 51 nsresult rv; 52 if (base64) { 53 // it's ascii encoded binary, don't let any spaces in 54 rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator()) 55 .Apply(&nsISimpleURIMutator::SetSpecAndFilterWhitespace, aSpec, 56 nullptr) 57 .Finalize(uri); 58 } else { 59 rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator()) 60 .SetSpec(aSpec) 61 .Finalize(uri); 62 } 63 64 if (NS_FAILED(rv)) return rv; 65 66 // use DefaultURI to check for validity when we have possible hostnames 67 // since nsSimpleURI doesn't know about hostnames 68 auto pos = aSpec.Find("data:"); 69 if (pos != kNotFound) { 70 nsDependentCSubstring rest(aSpec, pos + sizeof("data:") - 1, -1); 71 if (StringBeginsWith(rest, "//"_ns)) { 72 nsCOMPtr<nsIURI> uriWithHost; 73 rv = NS_MutateURI(new mozilla::net::DefaultURI::Mutator()) 74 .SetSpec(aSpec) 75 .Finalize(uriWithHost); 76 NS_ENSURE_SUCCESS(rv, rv); 77 } 78 } 79 80 uri.forget(result); 81 return rv; 82 } 83 84 NS_IMETHODIMP 85 nsDataHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, 86 nsIChannel** result) { 87 NS_ENSURE_ARG_POINTER(uri); 88 RefPtr<nsDataChannel> channel = new nsDataChannel(uri); 89 90 // set the loadInfo on the new channel 91 nsresult rv = channel->SetLoadInfo(aLoadInfo); 92 NS_ENSURE_SUCCESS(rv, rv); 93 94 rv = channel->Init(); 95 NS_ENSURE_SUCCESS(rv, rv); 96 97 *result = channel.forget().downcast<nsBaseChannel>().take(); 98 return NS_OK; 99 } 100 101 NS_IMETHODIMP 102 nsDataHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) { 103 // don't override anything. 104 *_retval = false; 105 return NS_OK; 106 } 107 108 namespace { 109 110 bool TrimSpacesAndBase64(nsACString& aMimeType) { 111 const char* beg = aMimeType.BeginReading(); 112 const char* end = aMimeType.EndReading(); 113 114 // trim leading and trailing spaces 115 while (beg < end && NS_IsHTTPWhitespace(*beg)) { 116 ++beg; 117 } 118 if (beg == end) { 119 aMimeType.Truncate(); 120 return false; 121 } 122 while (end > beg && NS_IsHTTPWhitespace(*(end - 1))) { 123 --end; 124 } 125 if (beg == end) { 126 aMimeType.Truncate(); 127 return false; 128 } 129 130 // trim trailing `; base64` (if any) and remember it 131 const char* pos = end - 1; 132 bool foundBase64 = false; 133 if (pos > beg && *pos == '4' && --pos > beg && *pos == '6' && --pos > beg && 134 ToLowerCaseASCII(*pos) == 'e' && --pos > beg && 135 ToLowerCaseASCII(*pos) == 's' && --pos > beg && 136 ToLowerCaseASCII(*pos) == 'a' && --pos > beg && 137 ToLowerCaseASCII(*pos) == 'b') { 138 while (--pos > beg && NS_IsHTTPWhitespace(*pos)) { 139 } 140 if (pos >= beg && *pos == ';') { 141 end = pos; 142 foundBase64 = true; 143 } 144 } 145 146 // actually trim off the spaces and trailing base64, returning if we found it. 147 const char* s = aMimeType.BeginReading(); 148 aMimeType.Assign(Substring(aMimeType, beg - s, end - s)); 149 return foundBase64; 150 } 151 152 } // namespace 153 154 static nsresult ParsePathWithoutRef(const nsACString& aPath, 155 nsCString& aContentType, 156 nsCString* aContentCharset, bool& aIsBase64, 157 nsDependentCSubstring* aDataBuffer, 158 RefPtr<CMimeType>* aMimeType) { 159 static constexpr auto kCharset = "charset"_ns; 160 161 // This implements https://fetch.spec.whatwg.org/#data-url-processor 162 // It also returns the full mimeType in aMimeType so fetch/XHR may access it 163 // for content-length headers. The contentType and charset parameters retain 164 // our legacy behavior, as much Gecko code generally expects GetContentType 165 // to yield only the MimeType's essence, not its full value with parameters. 166 167 aIsBase64 = false; 168 169 int32_t commaIdx = aPath.FindChar(','); 170 171 // This is a hack! When creating a URL using the DOM API we want to ignore 172 // if a comma is missing. But if we're actually loading a data: URI, in which 173 // case aContentCharset is not null, then we want to return an error if a 174 // comma is missing. 175 if (aContentCharset && commaIdx == kNotFound) { 176 return NS_ERROR_MALFORMED_URI; 177 } 178 179 // "Let mimeType be the result of collecting a sequence of code points that 180 // are not equal to U+002C (,), given position." 181 nsCString mimeType(Substring(aPath, 0, commaIdx)); 182 183 // "Strip leading and trailing ASCII whitespace from mimeType." 184 // "If mimeType ends with U+003B (;), followed by zero or more U+0020 SPACE, 185 // followed by an ASCII case-insensitive match for "base64", then ..." 186 aIsBase64 = TrimSpacesAndBase64(mimeType); 187 188 // "If mimeType starts with ";", then prepend "text/plain" to mimeType." 189 if (mimeType.Length() > 0 && mimeType.CharAt(0) == ';') { 190 mimeType = "text/plain"_ns + mimeType; 191 } 192 193 // "Let mimeTypeRecord be the result of parsing mimeType." 194 // This also checks for instances of ;base64 in the middle of the MimeType. 195 // This is against the current spec, but we're doing it because we have 196 // historically seen webcompat issues relying on this (see bug 781693). 197 if (RefPtr<CMimeType> parsed = CMimeType::Parse(mimeType)) { 198 parsed->GetEssence(aContentType); 199 if (aContentCharset) { 200 parsed->GetParameterValue(kCharset, *aContentCharset); 201 } 202 if (aMimeType) { 203 *aMimeType = std::move(parsed); 204 } 205 } else { 206 // "If mimeTypeRecord is failure, then set mimeTypeRecord to 207 // text/plain;charset=US-ASCII." 208 aContentType.AssignLiteral("text/plain"); 209 if (aContentCharset) { 210 aContentCharset->AssignLiteral("US-ASCII"); 211 } 212 if (aMimeType) { 213 *aMimeType = new CMimeType("text"_ns, "plain"_ns); 214 (*aMimeType)->SetParameterValue("charset"_ns, "US-ASCII"_ns); 215 } 216 } 217 218 if (aDataBuffer) { 219 aDataBuffer->Rebind(aPath, commaIdx + 1); 220 } 221 222 return NS_OK; 223 } 224 225 static inline char ToLower(const char c) { 226 if (c >= 'A' && c <= 'Z') { 227 return char(c + ('a' - 'A')); 228 } 229 return c; 230 } 231 232 nsresult nsDataHandler::ParseURI(const nsACString& aSpec, 233 nsCString& aContentType, 234 nsCString* aContentCharset, bool& aIsBase64, 235 nsDependentCSubstring* aDataBuffer, 236 RefPtr<CMimeType>* aMimeType) { 237 static constexpr auto kDataScheme = "data:"_ns; 238 239 // move past "data:" 240 const char* pos = std::search( 241 aSpec.BeginReading(), aSpec.EndReading(), kDataScheme.BeginReading(), 242 kDataScheme.EndReading(), 243 [](const char a, const char b) { return ToLower(a) == ToLower(b); }); 244 if (pos == aSpec.EndReading()) { 245 return NS_ERROR_MALFORMED_URI; 246 } 247 248 uint32_t scheme = pos - aSpec.BeginReading(); 249 scheme += kDataScheme.Length(); 250 251 // Find the start of the hash ref if present. 252 int32_t hash = aSpec.FindChar('#', scheme); 253 254 auto pathWithoutRef = 255 Substring(aSpec, scheme, hash != kNotFound ? hash - scheme : -1); 256 return ParsePathWithoutRef(pathWithoutRef, aContentType, aContentCharset, 257 aIsBase64, aDataBuffer, aMimeType); 258 }