tor-browser

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

TestingUtility.cpp (11858B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/TestingUtility.h"
      8 
      9 #ifdef JS_HAS_INTL_API
     10 #  include "mozilla/intl/TimeZone.h"
     11 #endif
     12 
     13 #include <stdint.h>  // uint32_t
     14 #include <string.h>
     15 
     16 #include "jsapi.h"  // JS_NewPlainObject, JS_WrapValue
     17 
     18 #ifdef JS_HAS_INTL_API
     19 #  include "builtin/intl/CommonFunctions.h"
     20 #endif
     21 #include "frontend/CompilationStencil.h"  // js::frontend::CompilationStencil
     22 #include "js/CharacterEncoding.h"         // JS_EncodeStringToUTF8
     23 #include "js/ColumnNumber.h"              // JS::ColumnNumberOneOrigin
     24 #include "js/CompileOptions.h"            // JS::CompileOptions
     25 #include "js/Conversions.h"  // JS::ToBoolean, JS::ToString, JS::ToUint32, JS::ToInt32
     26 #include "js/PropertyAndElement.h"  // JS_GetProperty, JS_DefineProperty
     27 #include "js/PropertyDescriptor.h"  // JSPROP_ENUMERATE
     28 #include "js/RealmOptions.h"        // JS::RealmBehaviors
     29 #include "js/RootingAPI.h"          // JS::Rooted, JS::Handle
     30 #include "js/Utility.h"             // JS::UniqueChars
     31 #include "js/Value.h"               // JS::Value, JS::StringValue
     32 #include "vm/JSContext.h"           // JS::ReportUsageErrorASCII
     33 #include "vm/JSScript.h"
     34 #include "vm/Realm.h"  // JS::Realm
     35 
     36 bool js::ParseCompileOptions(JSContext* cx, JS::CompileOptions& options,
     37                             JS::Handle<JSObject*> opts,
     38                             JS::UniqueChars* fileNameBytes) {
     39  JS::Rooted<JS::Value> v(cx);
     40  JS::Rooted<JSString*> s(cx);
     41 
     42  if (!JS_GetProperty(cx, opts, "isRunOnce", &v)) {
     43    return false;
     44  }
     45  if (!v.isUndefined()) {
     46    options.setIsRunOnce(JS::ToBoolean(v));
     47  }
     48 
     49  if (!JS_GetProperty(cx, opts, "noScriptRval", &v)) {
     50    return false;
     51  }
     52  if (!v.isUndefined()) {
     53    options.setNoScriptRval(JS::ToBoolean(v));
     54  }
     55 
     56  if (!JS_GetProperty(cx, opts, "fileName", &v)) {
     57    return false;
     58  }
     59  if (v.isNull()) {
     60    options.setFile(nullptr);
     61  } else if (!v.isUndefined()) {
     62    s = JS::ToString(cx, v);
     63    if (!s) {
     64      return false;
     65    }
     66    if (fileNameBytes) {
     67      *fileNameBytes = JS_EncodeStringToUTF8(cx, s);
     68      if (!*fileNameBytes) {
     69        return false;
     70      }
     71      options.setFile(fileNameBytes->get());
     72    }
     73  }
     74 
     75  if (!JS_GetProperty(cx, opts, "skipFileNameValidation", &v)) {
     76    return false;
     77  }
     78  if (!v.isUndefined()) {
     79    options.setSkipFilenameValidation(JS::ToBoolean(v));
     80  }
     81 
     82  if (!JS_GetProperty(cx, opts, "lineNumber", &v)) {
     83    return false;
     84  }
     85  if (!v.isUndefined()) {
     86    uint32_t u;
     87    if (!JS::ToUint32(cx, v, &u)) {
     88      return false;
     89    }
     90    options.setLine(u);
     91  }
     92 
     93  if (!JS_GetProperty(cx, opts, "columnNumber", &v)) {
     94    return false;
     95  }
     96  if (!v.isUndefined()) {
     97    int32_t c;
     98    if (!JS::ToInt32(cx, v, &c)) {
     99      return false;
    100    }
    101    if (c < 1) {
    102      c = 1;
    103    }
    104    options.setColumn(JS::ColumnNumberOneOrigin(c));
    105  }
    106 
    107  if (!JS_GetProperty(cx, opts, "sourceIsLazy", &v)) {
    108    return false;
    109  }
    110  if (v.isBoolean()) {
    111    options.setSourceIsLazy(v.toBoolean());
    112  }
    113 
    114  if (!JS_GetProperty(cx, opts, "forceFullParse", &v)) {
    115    return false;
    116  }
    117  bool forceFullParseIsSet = !v.isUndefined();
    118  if (v.isBoolean() && v.toBoolean()) {
    119    options.setForceFullParse();
    120  }
    121 
    122  if (!JS_GetProperty(cx, opts, "eagerDelazificationStrategy", &v)) {
    123    return false;
    124  }
    125  if (forceFullParseIsSet && !v.isUndefined()) {
    126    JS_ReportErrorASCII(
    127        cx, "forceFullParse and eagerDelazificationStrategy are both set.");
    128    return false;
    129  }
    130  if (v.isString()) {
    131    s = JS::ToString(cx, v);
    132    if (!s) {
    133      return false;
    134    }
    135 
    136    JSLinearString* str = JS_EnsureLinearString(cx, s);
    137    if (!str) {
    138      return false;
    139    }
    140 
    141    bool found = false;
    142    JS::DelazificationOption strategy = JS::DelazificationOption::OnDemandOnly;
    143 
    144 #define MATCH_AND_SET_STRATEGY_(NAME)                       \
    145  if (!found && JS_LinearStringEqualsLiteral(str, #NAME)) { \
    146    strategy = JS::DelazificationOption::NAME;              \
    147    found = true;                                           \
    148  }
    149 
    150    FOREACH_DELAZIFICATION_STRATEGY(MATCH_AND_SET_STRATEGY_);
    151 #undef MATCH_AND_SET_STRATEGY_
    152 #undef FOR_STRATEGY_NAMES
    153 
    154    if (!found) {
    155      JS_ReportErrorASCII(cx,
    156                          "eagerDelazificationStrategy does not match any "
    157                          "DelazificationOption.");
    158      return false;
    159    }
    160    options.setEagerDelazificationStrategy(strategy);
    161  }
    162 
    163  return true;
    164 }
    165 
    166 bool js::ParseSourceOptions(JSContext* cx, JS::Handle<JSObject*> opts,
    167                            JS::MutableHandle<JSString*> displayURL,
    168                            JS::MutableHandle<JSString*> sourceMapURL) {
    169  JS::Rooted<JS::Value> v(cx);
    170 
    171  if (!JS_GetProperty(cx, opts, "displayURL", &v)) {
    172    return false;
    173  }
    174  if (!v.isUndefined()) {
    175    displayURL.set(ToString(cx, v));
    176    if (!displayURL) {
    177      return false;
    178    }
    179  }
    180 
    181  if (!JS_GetProperty(cx, opts, "sourceMapURL", &v)) {
    182    return false;
    183  }
    184  if (!v.isUndefined()) {
    185    sourceMapURL.set(ToString(cx, v));
    186    if (!sourceMapURL) {
    187      return false;
    188    }
    189  }
    190 
    191  return true;
    192 }
    193 
    194 bool js::SetSourceOptions(JSContext* cx, FrontendContext* fc,
    195                          ScriptSource* source,
    196                          JS::Handle<JSString*> displayURL,
    197                          JS::Handle<JSString*> sourceMapURL) {
    198  if (displayURL && !source->hasDisplayURL()) {
    199    JS::UniqueTwoByteChars chars = JS_CopyStringCharsZ(cx, displayURL);
    200    if (!chars) {
    201      return false;
    202    }
    203    if (!source->setDisplayURL(fc, std::move(chars))) {
    204      return false;
    205    }
    206  }
    207  if (sourceMapURL && !source->hasSourceMapURL()) {
    208    JS::UniqueTwoByteChars chars = JS_CopyStringCharsZ(cx, sourceMapURL);
    209    if (!chars) {
    210      return false;
    211    }
    212    if (!source->setSourceMapURL(fc, std::move(chars))) {
    213      return false;
    214    }
    215  }
    216 
    217  return true;
    218 }
    219 
    220 JSObject* js::CreateScriptPrivate(JSContext* cx,
    221                                  JS::Handle<JSString*> path /* = nullptr */) {
    222  JS::Rooted<JSObject*> info(cx, JS_NewPlainObject(cx));
    223  if (!info) {
    224    return nullptr;
    225  }
    226 
    227  if (path) {
    228    JS::Rooted<JS::Value> pathValue(cx, JS::StringValue(path));
    229    if (!JS_DefineProperty(cx, info, "path", pathValue, JSPROP_ENUMERATE)) {
    230      return nullptr;
    231    }
    232  }
    233 
    234  return info;
    235 }
    236 
    237 bool js::ParseDebugMetadata(JSContext* cx, JS::Handle<JSObject*> opts,
    238                            JS::MutableHandle<JS::Value> privateValue,
    239                            JS::MutableHandle<JSString*> elementAttributeName) {
    240  JS::Rooted<JS::Value> v(cx);
    241  JS::Rooted<JSString*> s(cx);
    242 
    243  if (!JS_GetProperty(cx, opts, "element", &v)) {
    244    return false;
    245  }
    246  if (v.isObject()) {
    247    JS::Rooted<JSObject*> infoObject(cx, CreateScriptPrivate(cx));
    248    if (!infoObject) {
    249      return false;
    250    }
    251    JS::Rooted<JS::Value> elementValue(cx, v);
    252    if (!JS_WrapValue(cx, &elementValue)) {
    253      return false;
    254    }
    255    if (!JS_DefineProperty(cx, infoObject, "element", elementValue, 0)) {
    256      return false;
    257    }
    258    privateValue.set(JS::ObjectValue(*infoObject));
    259  }
    260 
    261  if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) {
    262    return false;
    263  }
    264  if (!v.isUndefined()) {
    265    s = ToString(cx, v);
    266    if (!s) {
    267      return false;
    268    }
    269    elementAttributeName.set(s);
    270  }
    271 
    272  return true;
    273 }
    274 
    275 static JS::UniqueChars StringToAscii(JSContext* cx,
    276                                     JS::Handle<JSObject*> callee,
    277                                     JS::Handle<JSString*> str) {
    278  JS::Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
    279  if (!linear) {
    280    return nullptr;
    281  }
    282 
    283  if (!StringIsAscii(linear)) {
    284    ReportUsageErrorASCII(cx, callee,
    285                          "First argument contains non-ASCII characters");
    286    return nullptr;
    287  }
    288 
    289  return JS_EncodeStringToASCII(cx, linear);
    290 }
    291 
    292 JS::UniqueChars js::StringToLocale(JSContext* cx, JS::Handle<JSObject*> callee,
    293                                   JS::Handle<JSString*> str) {
    294  UniqueChars locale = StringToAscii(cx, callee, str);
    295  if (!locale) {
    296    return nullptr;
    297  }
    298 
    299  bool containsOnlyValidBCP47Characters =
    300      mozilla::IsAsciiAlpha(locale[0]) &&
    301      std::all_of(locale.get(), locale.get() + str->length(), [](auto c) {
    302        return mozilla::IsAsciiAlphanumeric(c) || c == '-';
    303      });
    304 
    305  if (!containsOnlyValidBCP47Characters) {
    306    ReportUsageErrorASCII(cx, callee,
    307                          "First argument should be a BCP47 language tag");
    308    return nullptr;
    309  }
    310 
    311  return locale;
    312 }
    313 
    314 /*
    315 * Validate time zone input. Accepts the following formats:
    316 *  - "America/Chicago" (raw time zone)
    317 *  - ":America/Chicago"
    318 *  - "/this-part-is-ignored/zoneinfo/America/Chicago"
    319 *  - ":/this-part-is-ignored/zoneinfo/America/Chicago"
    320 *  - "/etc/localtime"
    321 *  - ":/etc/localtime"
    322 * Once the raw time zone is parsed out of the string, it is checked
    323 * against the time zones from GetAvailableTimeZones(). Throws an
    324 * Error if the time zone is invalid.
    325 */
    326 static bool ValidateTimeZone(JSContext* cx, const char* timeZone,
    327                             js::AllowTimeZoneLink allowLink) {
    328  const char* timeZonePart;
    329  if (allowLink == js::AllowTimeZoneLink::Yes) {
    330    static constexpr char zoneInfo[] = "/zoneinfo/";
    331    static constexpr size_t zoneInfoLength = sizeof(zoneInfo) - 1;
    332 
    333    size_t i = 0;
    334    if (timeZone[i] == ':') {
    335      ++i;
    336    }
    337    const char* zoneInfoPtr = strstr(timeZone, zoneInfo);
    338    timeZonePart = timeZone[i] == '/' && zoneInfoPtr
    339                       ? zoneInfoPtr + zoneInfoLength
    340                       : timeZone + i;
    341    if (!*timeZonePart) {
    342      JS_ReportErrorASCII(cx, "Invalid time zone format");
    343      return false;
    344    }
    345 
    346    if (!strcmp(timeZonePart, "/etc/localtime")) {
    347      return true;
    348    }
    349  } else {
    350    timeZonePart = timeZone;
    351  }
    352 
    353 #if defined(JS_HAS_INTL_API) && !defined(__wasi__)
    354  auto timeZones = mozilla::intl::TimeZone::GetAvailableTimeZones();
    355  if (timeZones.isErr()) {
    356    js::intl::ReportInternalError(cx, timeZones.unwrapErr());
    357    return false;
    358  }
    359  for (auto timeZoneName : timeZones.unwrap()) {
    360    if (timeZoneName.isErr()) {
    361      js::intl::ReportInternalError(cx);
    362      return false;
    363    }
    364 
    365    if (!strcmp(timeZonePart, timeZoneName.unwrap().data())) {
    366      return true;
    367    }
    368  }
    369 
    370  JS_ReportErrorASCII(cx, "Unsupported time zone name: %s", timeZonePart);
    371  return false;
    372 #else
    373  return true;
    374 #endif
    375 }
    376 
    377 JS::UniqueChars js::StringToTimeZone(JSContext* cx,
    378                                     JS::Handle<JSObject*> callee,
    379                                     JS::Handle<JSString*> str,
    380                                     AllowTimeZoneLink allowLink) {
    381  UniqueChars timeZone = StringToAscii(cx, callee, str);
    382  if (!timeZone) {
    383    return nullptr;
    384  }
    385 
    386  if (!ValidateTimeZone(cx, timeZone.get(), allowLink)) {
    387    return nullptr;
    388  }
    389 
    390  return timeZone;
    391 }
    392 
    393 bool js::ValidateLazinessOfStencilAndGlobal(JSContext* cx,
    394                                            const JS::Stencil* stencil) {
    395  if (cx->realm()->behaviors().discardSource() && stencil->canLazilyParse()) {
    396    JS_ReportErrorASCII(cx,
    397                        "Stencil compiled with with lazy parse option cannot "
    398                        "be used in a realm with discardSource");
    399    return false;
    400  }
    401 
    402  return true;
    403 }
    404 
    405 bool js::ValidateModuleCompileOptions(JSContext* cx,
    406                                      JS::CompileOptions& options) {
    407  if (options.lineno == 0) {
    408    JS_ReportErrorASCII(cx, "Module cannot be compiled with lineNumber == 0");
    409    return false;
    410  }
    411 
    412  if (!options.filename()) {
    413    JS_ReportErrorASCII(cx, "Module should have filename");
    414    return false;
    415  }
    416 
    417  return true;
    418 }