tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }