tor-browser

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

OriginParser.cpp (12391B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "OriginParser.h"
      8 
      9 #include <regex>
     10 
     11 #include "mozilla/OriginAttributes.h"
     12 #include "mozilla/dom/quota/Constants.h"
     13 #include "mozilla/dom/quota/QuotaCommon.h"
     14 
     15 namespace mozilla::dom::quota {
     16 
     17 // static
     18 auto OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
     19                               OriginAttributes* aAttrs,
     20                               nsCString& aOriginalSuffix) -> ResultType {
     21  MOZ_ASSERT(!aOrigin.IsEmpty());
     22  MOZ_ASSERT(aAttrs);
     23 
     24  nsCString origin(aOrigin);
     25  int32_t pos = origin.RFindChar('^');
     26 
     27  if (pos == kNotFound) {
     28    aOriginalSuffix.Truncate();
     29  } else {
     30    aOriginalSuffix = Substring(origin, pos);
     31  }
     32 
     33  OriginAttributes originAttributes;
     34 
     35  nsCString originNoSuffix;
     36  bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
     37  if (!ok) {
     38    return InvalidOrigin;
     39  }
     40 
     41  OriginParser parser(originNoSuffix);
     42 
     43  *aAttrs = originAttributes;
     44  return parser.Parse(aSpec);
     45 }
     46 
     47 auto OriginParser::Parse(nsACString& aSpec) -> ResultType {
     48  while (mTokenizer.hasMoreTokens()) {
     49    const nsDependentCSubstring& token = mTokenizer.nextToken();
     50 
     51    HandleToken(token);
     52 
     53    if (mError) {
     54      break;
     55    }
     56 
     57    if (!mHandledTokens.IsEmpty()) {
     58      mHandledTokens.AppendLiteral(", ");
     59    }
     60    mHandledTokens.Append('\'');
     61    mHandledTokens.Append(token);
     62    mHandledTokens.Append('\'');
     63  }
     64 
     65  if (!mError && mTokenizer.separatorAfterCurrentToken()) {
     66    HandleTrailingSeparator();
     67  }
     68 
     69  if (mError) {
     70    QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(),
     71               mHandledTokens.get());
     72 
     73    return (mSchemeType == eChrome || mSchemeType == eAbout) ? ObsoleteOrigin
     74                                                             : InvalidOrigin;
     75  }
     76 
     77  MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
     78 
     79  // For IPv6 URL, it should at least have three groups.
     80  MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3);
     81 
     82  nsAutoCString spec(mScheme);
     83 
     84  if (mSchemeType == eFile) {
     85    spec.AppendLiteral("://");
     86 
     87    if (mUniversalFileOrigin) {
     88      MOZ_ASSERT(mPathnameComponents.Length() == 1);
     89 
     90      spec.Append(mPathnameComponents[0]);
     91    } else {
     92      for (uint32_t count = mPathnameComponents.Length(), index = 0;
     93           index < count; index++) {
     94        spec.Append('/');
     95        spec.Append(mPathnameComponents[index]);
     96      }
     97    }
     98 
     99    aSpec = spec;
    100 
    101    return ValidOrigin;
    102  }
    103 
    104  if (mSchemeType == eAbout) {
    105    if (mMaybeObsolete) {
    106      // The "moz-safe-about+++home" was acciedntally created by a buggy nightly
    107      // and can be safely removed.
    108      return mHost.EqualsLiteral("home") ? ObsoleteOrigin : InvalidOrigin;
    109    }
    110    spec.Append(':');
    111  } else if (mSchemeType != eChrome) {
    112    spec.AppendLiteral("://");
    113  }
    114 
    115  spec.Append(mHost);
    116 
    117  if (!mPort.IsNull()) {
    118    spec.Append(':');
    119    spec.AppendInt(mPort.Value());
    120  }
    121 
    122  aSpec = spec;
    123 
    124  return mScheme.EqualsLiteral("app") ? ObsoleteOrigin : ValidOrigin;
    125 }
    126 
    127 void OriginParser::HandleScheme(const nsDependentCSubstring& aToken) {
    128  MOZ_ASSERT(!aToken.IsEmpty());
    129  MOZ_ASSERT(mState == eExpectingAppIdOrScheme || mState == eExpectingScheme);
    130 
    131  bool isAbout = false;
    132  bool isMozSafeAbout = false;
    133  bool isFile = false;
    134  bool isChrome = false;
    135  if (aToken.EqualsLiteral("http") || aToken.EqualsLiteral("https") ||
    136      (isAbout = aToken.EqualsLiteral("about") ||
    137                 (isMozSafeAbout = aToken.EqualsLiteral("moz-safe-about"))) ||
    138      aToken.EqualsLiteral("indexeddb") ||
    139      (isFile = aToken.EqualsLiteral("file")) || aToken.EqualsLiteral("app") ||
    140      aToken.EqualsLiteral("resource") ||
    141      aToken.EqualsLiteral("moz-extension") ||
    142      (isChrome = aToken.EqualsLiteral(kChromeOrigin)) ||
    143      aToken.EqualsLiteral("uuid")) {
    144    mScheme = aToken;
    145 
    146    if (isAbout) {
    147      mSchemeType = eAbout;
    148      mState = isMozSafeAbout ? eExpectingEmptyToken1OrHost : eExpectingHost;
    149    } else if (isChrome) {
    150      mSchemeType = eChrome;
    151      if (mTokenizer.hasMoreTokens()) {
    152        mError = true;
    153      }
    154      mState = eComplete;
    155    } else {
    156      if (isFile) {
    157        mSchemeType = eFile;
    158      }
    159      mState = eExpectingEmptyToken1;
    160    }
    161 
    162    return;
    163  }
    164 
    165  QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken).get());
    166 
    167  mError = true;
    168 }
    169 
    170 void OriginParser::HandlePathnameComponent(
    171    const nsDependentCSubstring& aToken) {
    172  MOZ_ASSERT(!aToken.IsEmpty());
    173  MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent ||
    174             mState == eExpectingEmptyTokenOrPathnameComponent);
    175  MOZ_ASSERT(mSchemeType == eFile);
    176 
    177  mPathnameComponents.AppendElement(aToken);
    178 
    179  mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
    180                                      : eComplete;
    181 }
    182 
    183 void OriginParser::HandleToken(const nsDependentCSubstring& aToken) {
    184  switch (mState) {
    185    case eExpectingAppIdOrScheme: {
    186      if (aToken.IsEmpty()) {
    187        QM_WARNING("Expected an app id or scheme (not an empty string)!");
    188 
    189        mError = true;
    190        return;
    191      }
    192 
    193      if (IsAsciiDigit(aToken.First())) {
    194        // nsDependentCSubstring doesn't provice ToInteger()
    195        nsCString token(aToken);
    196 
    197        nsresult rv;
    198        (void)token.ToInteger(&rv);
    199        if (NS_SUCCEEDED(rv)) {
    200          mState = eExpectingInMozBrowser;
    201          return;
    202        }
    203      }
    204 
    205      HandleScheme(aToken);
    206 
    207      return;
    208    }
    209 
    210    case eExpectingInMozBrowser: {
    211      if (aToken.Length() != 1) {
    212        QM_WARNING("'%zu' is not a valid length for the inMozBrowser flag!",
    213                   aToken.Length());
    214 
    215        mError = true;
    216        return;
    217      }
    218 
    219      if ((aToken.First() != 't') && (aToken.First() != 'f')) {
    220        QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
    221                   nsCString(aToken).get());
    222 
    223        mError = true;
    224        return;
    225      }
    226 
    227      mState = eExpectingScheme;
    228 
    229      return;
    230    }
    231 
    232    case eExpectingScheme: {
    233      if (aToken.IsEmpty()) {
    234        QM_WARNING("Expected a scheme (not an empty string)!");
    235 
    236        mError = true;
    237        return;
    238      }
    239 
    240      HandleScheme(aToken);
    241 
    242      return;
    243    }
    244 
    245    case eExpectingEmptyToken1: {
    246      if (!aToken.IsEmpty()) {
    247        QM_WARNING("Expected the first empty token!");
    248 
    249        mError = true;
    250        return;
    251      }
    252 
    253      mState = eExpectingEmptyToken2;
    254 
    255      return;
    256    }
    257 
    258    case eExpectingEmptyToken2: {
    259      if (!aToken.IsEmpty()) {
    260        QM_WARNING("Expected the second empty token!");
    261 
    262        mError = true;
    263        return;
    264      }
    265 
    266      if (mSchemeType == eFile) {
    267        mState = eExpectingEmptyTokenOrUniversalFileOrigin;
    268      } else {
    269        if (mSchemeType == eAbout) {
    270          mMaybeObsolete = true;
    271        }
    272        mState = eExpectingHost;
    273      }
    274 
    275      return;
    276    }
    277 
    278    case eExpectingEmptyTokenOrUniversalFileOrigin: {
    279      MOZ_ASSERT(mSchemeType == eFile);
    280 
    281      if (aToken.IsEmpty()) {
    282        mState = mTokenizer.hasMoreTokens()
    283                     ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
    284                     : eComplete;
    285 
    286        return;
    287      }
    288 
    289      if (aToken.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
    290        mUniversalFileOrigin = true;
    291 
    292        mPathnameComponents.AppendElement(aToken);
    293 
    294        mState = eComplete;
    295 
    296        return;
    297      }
    298 
    299      QM_WARNING(
    300          "Expected the third empty token or "
    301          "UNIVERSAL_FILE_URI_ORIGIN!");
    302 
    303      mError = true;
    304      return;
    305    }
    306 
    307    case eExpectingHost: {
    308      if (aToken.IsEmpty()) {
    309        QM_WARNING("Expected a host (not an empty string)!");
    310 
    311        mError = true;
    312        return;
    313      }
    314 
    315      mHost = aToken;
    316 
    317      if (aToken.First() == '[') {
    318        MOZ_ASSERT(mIPGroup == 0);
    319 
    320        ++mIPGroup;
    321        mState = eExpectingIPV6Token;
    322 
    323        MOZ_ASSERT(mTokenizer.hasMoreTokens());
    324        return;
    325      }
    326 
    327      if (mTokenizer.hasMoreTokens()) {
    328        if (mSchemeType == eAbout) {
    329          QM_WARNING("Expected an empty string after host!");
    330 
    331          mError = true;
    332          return;
    333        }
    334 
    335        mState = eExpectingPort;
    336 
    337        return;
    338      }
    339 
    340      mState = eComplete;
    341 
    342      return;
    343    }
    344 
    345    case eExpectingPort: {
    346      MOZ_ASSERT(mSchemeType == eNone);
    347 
    348      if (aToken.IsEmpty()) {
    349        QM_WARNING("Expected a port (not an empty string)!");
    350 
    351        mError = true;
    352        return;
    353      }
    354 
    355      // nsDependentCSubstring doesn't provice ToInteger()
    356      nsCString token(aToken);
    357 
    358      nsresult rv;
    359      uint32_t port = token.ToInteger(&rv);
    360      if (NS_SUCCEEDED(rv)) {
    361        mPort.SetValue() = port;
    362      } else {
    363        QM_WARNING("'%s' is not a valid port number!", token.get());
    364 
    365        mError = true;
    366        return;
    367      }
    368 
    369      mState = eComplete;
    370 
    371      return;
    372    }
    373 
    374    case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: {
    375      MOZ_ASSERT(mSchemeType == eFile);
    376 
    377      if (aToken.IsEmpty()) {
    378        mPathnameComponents.AppendElement(""_ns);
    379 
    380        mState = mTokenizer.hasMoreTokens()
    381                     ? eExpectingEmptyTokenOrPathnameComponent
    382                     : eComplete;
    383 
    384        return;
    385      }
    386 
    387      if (aToken.Length() == 1 && IsAsciiAlpha(aToken.First())) {
    388        mMaybeDriveLetter = true;
    389 
    390        mPathnameComponents.AppendElement(aToken);
    391 
    392        mState = mTokenizer.hasMoreTokens()
    393                     ? eExpectingEmptyTokenOrPathnameComponent
    394                     : eComplete;
    395 
    396        return;
    397      }
    398 
    399      HandlePathnameComponent(aToken);
    400 
    401      return;
    402    }
    403 
    404    case eExpectingEmptyTokenOrPathnameComponent: {
    405      MOZ_ASSERT(mSchemeType == eFile);
    406 
    407      if (aToken.IsEmpty()) {
    408        if (mMaybeDriveLetter) {
    409          MOZ_ASSERT(mPathnameComponents.Length() == 1);
    410 
    411          nsCString& pathnameComponent = mPathnameComponents[0];
    412          pathnameComponent.Append(':');
    413 
    414          mMaybeDriveLetter = false;
    415        } else {
    416          mPathnameComponents.AppendElement(""_ns);
    417        }
    418 
    419        mState = mTokenizer.hasMoreTokens()
    420                     ? eExpectingEmptyTokenOrPathnameComponent
    421                     : eComplete;
    422 
    423        return;
    424      }
    425 
    426      HandlePathnameComponent(aToken);
    427 
    428      return;
    429    }
    430 
    431    case eExpectingEmptyToken1OrHost: {
    432      MOZ_ASSERT(mSchemeType == eAbout &&
    433                 mScheme.EqualsLiteral("moz-safe-about"));
    434 
    435      if (aToken.IsEmpty()) {
    436        mState = eExpectingEmptyToken2;
    437      } else {
    438        mHost = aToken;
    439        mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
    440      }
    441 
    442      return;
    443    }
    444 
    445    case eExpectingIPV6Token: {
    446      // A safe check for preventing infinity recursion.
    447      if (++mIPGroup > 8) {
    448        mError = true;
    449        return;
    450      }
    451 
    452      mHost.AppendLiteral(":");
    453      mHost.Append(aToken);
    454      if (!aToken.IsEmpty() && aToken.Last() == ']') {
    455        mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
    456      }
    457 
    458      return;
    459    }
    460 
    461    default:
    462      MOZ_CRASH("Should never get here!");
    463  }
    464 }
    465 
    466 void OriginParser::HandleTrailingSeparator() {
    467  MOZ_ASSERT(mState == eComplete);
    468  MOZ_ASSERT(mSchemeType == eFile);
    469 
    470  mPathnameComponents.AppendElement(""_ns);
    471 
    472  mState = eHandledTrailingSeparator;
    473 }
    474 
    475 bool IsUUIDOrigin(const nsCString& aOrigin) {
    476  if (!StringBeginsWith(aOrigin, kUUIDOriginScheme)) {
    477    return false;
    478  }
    479 
    480  static const std::regex pattern(
    481      "^uuid://[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab"
    482      "][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$");
    483 
    484  return regex_match(aOrigin.get(), pattern);
    485 }
    486 
    487 bool IsUserContextSuffix(const nsACString& aSuffix, uint32_t aUserContextId) {
    488  OriginAttributes originAttributes;
    489  MOZ_ALWAYS_TRUE(originAttributes.PopulateFromSuffix(aSuffix));
    490  return originAttributes.mUserContextId == aUserContextId;
    491 }
    492 
    493 bool IsUserContextPattern(const OriginAttributesPattern& aPattern,
    494                          uint32_t aUserContextId) {
    495  const auto& userContextId = aPattern.mUserContextId;
    496 
    497  if (!userContextId.WasPassed()) {
    498    return false;
    499  }
    500 
    501  return userContextId.Value() == aUserContextId;
    502 }
    503 
    504 }  // namespace mozilla::dom::quota