tor-browser

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

XPCShellEnvironment.cpp (12889B)


      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 #ifdef HAVE_IO_H
      8 #  include <io.h> /* for isatty() */
      9 #endif
     10 #ifdef HAVE_UNISTD_H
     11 #  include <unistd.h> /* for isatty() */
     12 #endif
     13 
     14 #include "jsapi.h"
     15 #include "js/CharacterEncoding.h"
     16 #include "js/CompilationAndEvaluation.h"  // JS::Compile{,Utf8File}
     17 #include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperty, JS_GetProperty
     18 #include "js/PropertySpec.h"
     19 #include "js/RealmOptions.h"
     20 #include "js/SourceText.h"  // JS::Source{Ownership,Text}
     21 
     22 #include "xpcpublic.h"
     23 
     24 #include "XPCShellEnvironment.h"
     25 
     26 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
     27 #include "mozilla/dom/AutoEntryScript.h"
     28 #include "mozilla/dom/ScriptSettings.h"
     29 
     30 #include "nsIPrincipal.h"
     31 #include "nsIScriptSecurityManager.h"
     32 #include "nsIXPConnect.h"
     33 #include "nsServiceManagerUtils.h"
     34 
     35 #include "nsJSUtils.h"
     36 
     37 #include "SystemGlobal.h"
     38 
     39 #include "TestShellChild.h"
     40 
     41 using mozilla::dom::AutoEntryScript;
     42 using mozilla::dom::AutoJSAPI;
     43 using mozilla::ipc::XPCShellEnvironment;
     44 using namespace JS;
     45 
     46 namespace {
     47 
     48 static const char kDefaultRuntimeScriptFilename[] = "xpcshell.js";
     49 
     50 inline XPCShellEnvironment* Environment(JS::Handle<JSObject*> global) {
     51  AutoJSAPI jsapi;
     52  if (!jsapi.Init(global)) {
     53    return nullptr;
     54  }
     55  JSContext* cx = jsapi.cx();
     56  Rooted<Value> v(cx);
     57  if (!JS_GetProperty(cx, global, "__XPCShellEnvironment", &v) ||
     58      !v.get().isDouble()) {
     59    return nullptr;
     60  }
     61  return static_cast<XPCShellEnvironment*>(v.get().toPrivate());
     62 }
     63 
     64 static bool Print(JSContext* cx, unsigned argc, JS::Value* vp) {
     65  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     66 
     67  for (unsigned i = 0; i < args.length(); i++) {
     68    JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
     69    if (!str) return false;
     70    JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
     71    if (!bytes) return false;
     72    fprintf(stdout, "%s%s", i ? " " : "", bytes.get());
     73    fflush(stdout);
     74  }
     75  fputc('\n', stdout);
     76  args.rval().setUndefined();
     77  return true;
     78 }
     79 
     80 static bool GetLine(char* bufp, FILE* file, const char* prompt) {
     81  char line[256];
     82  fputs(prompt, stdout);
     83  fflush(stdout);
     84  if (!fgets(line, sizeof line, file)) return false;
     85  strcpy(bufp, line);
     86  return true;
     87 }
     88 
     89 static bool Dump(JSContext* cx, unsigned argc, JS::Value* vp) {
     90  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     91 
     92  if (!args.length()) return true;
     93 
     94  JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[0]));
     95  if (!str) return false;
     96  JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
     97  if (!bytes) return false;
     98 
     99  fputs(bytes.get(), stdout);
    100  fflush(stdout);
    101  return true;
    102 }
    103 
    104 static bool Load(JSContext* cx, unsigned argc, JS::Value* vp) {
    105  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    106 
    107  JS::RootedObject thisObject(cx);
    108  if (!args.computeThis(cx, &thisObject)) return false;
    109  if (!JS_IsGlobalObject(thisObject)) {
    110    JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
    111    return false;
    112  }
    113 
    114  for (unsigned i = 0; i < args.length(); i++) {
    115    JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
    116    if (!str) {
    117      return false;
    118    }
    119    JS::UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
    120    if (!filename) {
    121      return false;
    122    }
    123 
    124    JS::CompileOptions options(cx);
    125    JS::Rooted<JSScript*> script(
    126        cx, JS::CompileUtf8Path(cx, options, filename.get()));
    127    if (!script) {
    128      return false;
    129    }
    130 
    131    if (!JS_ExecuteScript(cx, script)) {
    132      return false;
    133    }
    134  }
    135  args.rval().setUndefined();
    136  return true;
    137 }
    138 
    139 static bool Quit(JSContext* cx, unsigned argc, JS::Value* vp) {
    140  Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
    141  XPCShellEnvironment* env = Environment(global);
    142  env->SetIsQuitting();
    143 
    144  return false;
    145 }
    146 
    147 static bool DumpXPC(JSContext* cx, unsigned argc, JS::Value* vp) {
    148  JS::CallArgs args = CallArgsFromVp(argc, vp);
    149 
    150  uint16_t depth = 2;
    151  if (args.length() > 0) {
    152    if (!JS::ToUint16(cx, args[0], &depth)) return false;
    153  }
    154 
    155  nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
    156  if (xpc) xpc->DebugDump(int16_t(depth));
    157  args.rval().setUndefined();
    158  return true;
    159 }
    160 
    161 static bool GC(JSContext* cx, unsigned argc, JS::Value* vp) {
    162  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    163 
    164  JS_GC(cx);
    165 
    166  args.rval().setUndefined();
    167  return true;
    168 }
    169 
    170 #ifdef JS_GC_ZEAL
    171 static bool GCZeal(JSContext* cx, unsigned argc, JS::Value* vp) {
    172  CallArgs args = CallArgsFromVp(argc, vp);
    173 
    174  uint32_t zeal;
    175  if (!ToUint32(cx, args.get(0), &zeal)) return false;
    176 
    177  JS::SetGCZeal(cx, uint8_t(zeal), JS::ShellDefaultGCZealFrequency);
    178  return true;
    179 }
    180 #endif
    181 
    182 #ifdef ANDROID
    183 static bool ChangeTestShellDir(JSContext* cx, unsigned argc, Value* vp) {
    184  // This method should only be used by testing/xpcshell/head.js to change to
    185  // the correct directory on Android Remote XPCShell tests.
    186  //
    187  // TODO: Bug 1801725 - Find a more ergonomic way to do this than exposing
    188  // identical methods in XPCShellEnvironment and XPCShellImpl to chdir on
    189  // android for Remote XPCShell tests on Android.
    190  CallArgs args = CallArgsFromVp(argc, vp);
    191 
    192  if (args.length() != 1) {
    193    JS_ReportErrorASCII(cx, "changeTestShellDir() takes one argument");
    194    return false;
    195  }
    196 
    197  nsAutoJSCString path;
    198  if (!path.init(cx, args[0])) {
    199    JS_ReportErrorASCII(
    200        cx, "changeTestShellDir(): could not convert argument 1 to string");
    201    return false;
    202  }
    203 
    204  if (chdir(path.get())) {
    205    JS_ReportErrorASCII(cx, "changeTestShellDir(): could not change directory");
    206    return false;
    207  }
    208 
    209  return true;
    210 }
    211 #endif
    212 
    213 const JSFunctionSpec gGlobalFunctions[] = {
    214    JS_FN("print", Print, 0, 0),
    215    JS_FN("load", Load, 1, 0),
    216    JS_FN("quit", Quit, 0, 0),
    217    JS_FN("dumpXPC", DumpXPC, 1, 0),
    218    JS_FN("dump", Dump, 1, 0),
    219    JS_FN("gc", GC, 0, 0),
    220 #ifdef JS_GC_ZEAL
    221    JS_FN("gczeal", GCZeal, 1, 0),
    222 #endif
    223 #ifdef ANDROID
    224    JS_FN("changeTestShellDir", ChangeTestShellDir, 1, 0),
    225 #endif
    226    JS_FS_END};
    227 
    228 typedef enum JSShellErrNum {
    229 #define MSG_DEF(name, number, count, exception, format) name = number,
    230 #include "jsshell.msg"
    231 #undef MSG_DEF
    232  JSShellErr_Limit
    233 #undef MSGDEF
    234 } JSShellErrNum;
    235 
    236 } /* anonymous namespace */
    237 
    238 void XPCShellEnvironment::ProcessFile(JSContext* cx, const char* filename,
    239                                      FILE* file, bool forceTTY) {
    240  XPCShellEnvironment* env = this;
    241 
    242  JS::Rooted<JS::Value> result(cx);
    243  int lineno, startline;
    244  bool ok, hitEOF;
    245  char *bufp, buffer[4096];
    246  JSString* str;
    247 
    248  JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
    249  MOZ_ASSERT(global);
    250 
    251  if (forceTTY) {
    252    file = stdin;
    253  } else if (!isatty(fileno(file))) {
    254    /*
    255     * It's not interactive - just execute it.
    256     *
    257     * Support the UNIX #! shell hack; gobble the first line if it starts
    258     * with '#'.
    259     */
    260    int ch = fgetc(file);
    261    if (ch == '#') {
    262      while ((ch = fgetc(file)) != EOF) {
    263        if (ch == '\n' || ch == '\r') break;
    264      }
    265    }
    266    ungetc(ch, file);
    267 
    268    JS::CompileOptions options(cx);
    269    options.setFileAndLine(filename, 1);
    270 
    271    JS::Rooted<JSScript*> script(cx, JS::CompileUtf8File(cx, options, file));
    272    if (script) {
    273      (void)JS_ExecuteScript(cx, script, &result);
    274    }
    275 
    276    return;
    277  }
    278 
    279  /* It's an interactive filehandle; drop into read-eval-print loop. */
    280  lineno = 1;
    281  hitEOF = false;
    282  do {
    283    bufp = buffer;
    284    *bufp = '\0';
    285 
    286    /*
    287     * Accumulate lines until we get a 'compilable unit' - one that either
    288     * generates an error (before running out of source) or that compiles
    289     * cleanly.  This should be whenever we get a complete statement that
    290     * coincides with the end of a line.
    291     */
    292    startline = lineno;
    293    do {
    294      if (!GetLine(bufp, file, startline == lineno ? "js> " : "")) {
    295        hitEOF = true;
    296        break;
    297      }
    298      bufp += strlen(bufp);
    299      lineno++;
    300    } while (
    301        !JS_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer)));
    302 
    303    /* Clear any pending exception from previous failed compiles.  */
    304    JS_ClearPendingException(cx);
    305 
    306    JS::CompileOptions options(cx);
    307    options.setFileAndLine("typein", startline);
    308 
    309    JS::SourceText<mozilla::Utf8Unit> srcBuf;
    310    JS::Rooted<JSScript*> script(cx);
    311 
    312    if (srcBuf.init(cx, buffer, strlen(buffer),
    313                    JS::SourceOwnership::Borrowed) &&
    314        (script = JS::Compile(cx, options, srcBuf))) {
    315      ok = JS_ExecuteScript(cx, script, &result);
    316      if (ok && !result.isUndefined()) {
    317        /* Suppress warnings from JS::ToString(). */
    318        JS::AutoSuppressWarningReporter suppressWarnings(cx);
    319        str = JS::ToString(cx, result);
    320        JS::UniqueChars bytes;
    321        if (str) bytes = JS_EncodeStringToLatin1(cx, str);
    322 
    323        if (!!bytes)
    324          fprintf(stdout, "%s\n", bytes.get());
    325        else
    326          ok = false;
    327      }
    328    }
    329  } while (!hitEOF && !env->IsQuitting());
    330 
    331  fprintf(stdout, "\n");
    332 }
    333 
    334 // static
    335 XPCShellEnvironment* XPCShellEnvironment::CreateEnvironment() {
    336  auto* env = new XPCShellEnvironment();
    337  if (env && !env->Init()) {
    338    delete env;
    339    env = nullptr;
    340  }
    341  return env;
    342 }
    343 
    344 XPCShellEnvironment::XPCShellEnvironment() : mQuitting(false) {}
    345 
    346 XPCShellEnvironment::~XPCShellEnvironment() {
    347  if (GetGlobalObject()) {
    348    AutoJSAPI jsapi;
    349    if (!jsapi.Init(GetGlobalObject())) {
    350      return;
    351    }
    352    JS_SetAllNonReservedSlotsToUndefined(mGlobalHolder);
    353    mGlobalHolder.reset();
    354 
    355    JS_GC(jsapi.cx());
    356  }
    357 }
    358 
    359 bool XPCShellEnvironment::Init() {
    360  nsresult rv;
    361 
    362  // unbuffer stdout so that output is in the correct order; note that stderr
    363  // is unbuffered by default
    364  setbuf(stdout, 0);
    365 
    366  AutoSafeJSContext cx;
    367 
    368  mGlobalHolder.init(cx);
    369 
    370  nsCOMPtr<nsIPrincipal> principal;
    371  nsCOMPtr<nsIScriptSecurityManager> securityManager =
    372      do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
    373  if (NS_SUCCEEDED(rv) && securityManager) {
    374    rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
    375    if (NS_FAILED(rv)) {
    376      fprintf(stderr,
    377              "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager "
    378              "service.\n");
    379    }
    380  } else {
    381    fprintf(stderr,
    382            "+++ Failed to get ScriptSecurityManager service, running without "
    383            "principals");
    384  }
    385 
    386  auto systemGlobal = MakeRefPtr<SystemGlobal>();
    387 
    388  JS::RealmOptions options;
    389  options.creationOptions().setNewCompartmentInSystemZone();
    390  xpc::SetPrefableRealmOptions(options);
    391 
    392  JS::Rooted<JSObject*> globalObj(cx);
    393  rv = xpc::InitClassesWithNewWrappedGlobal(
    394      cx, static_cast<nsIGlobalObject*>(systemGlobal), principal, 0, options,
    395      &globalObj);
    396  if (NS_FAILED(rv)) {
    397    NS_ERROR("InitClassesWithNewWrappedGlobal failed!");
    398    return false;
    399  }
    400 
    401  if (!globalObj) {
    402    NS_ERROR("Failed to get global JSObject!");
    403    return false;
    404  }
    405  JSAutoRealm ar(cx, globalObj);
    406 
    407  systemGlobal->SetGlobalObject(globalObj);
    408 
    409  JS::Rooted<Value> privateVal(cx, PrivateValue(this));
    410  if (!JS_DefineProperty(cx, globalObj, "__XPCShellEnvironment", privateVal,
    411                         JSPROP_READONLY | JSPROP_PERMANENT) ||
    412      !JS_DefineFunctions(cx, globalObj, gGlobalFunctions)) {
    413    NS_ERROR("JS_DefineFunctions failed!");
    414    return false;
    415  }
    416 
    417  mGlobalHolder = globalObj;
    418 
    419  FILE* runtimeScriptFile = fopen(kDefaultRuntimeScriptFilename, "r");
    420  if (runtimeScriptFile) {
    421    fprintf(stdout, "[loading '%s'...]\n", kDefaultRuntimeScriptFilename);
    422    ProcessFile(cx, kDefaultRuntimeScriptFilename, runtimeScriptFile, false);
    423    fclose(runtimeScriptFile);
    424  }
    425 
    426  return true;
    427 }
    428 
    429 bool XPCShellEnvironment::EvaluateString(const nsAString& aString,
    430                                         nsString* aResult) {
    431  AutoEntryScript aes(GetGlobalObject(),
    432                      "ipc XPCShellEnvironment::EvaluateString");
    433  JSContext* cx = aes.cx();
    434 
    435  JS::CompileOptions options(cx);
    436  options.setFileAndLine("typein", 0);
    437 
    438  JS::SourceText<char16_t> srcBuf;
    439  if (!srcBuf.init(cx, aString.BeginReading(), aString.Length(),
    440                   JS::SourceOwnership::Borrowed)) {
    441    return false;
    442  }
    443 
    444  JS::Rooted<JSScript*> script(cx, JS::Compile(cx, options, srcBuf));
    445  if (!script) {
    446    return false;
    447  }
    448 
    449  if (aResult) {
    450    aResult->Truncate();
    451  }
    452 
    453  JS::Rooted<JS::Value> result(cx);
    454  bool ok = JS_ExecuteScript(cx, script, &result);
    455  if (ok && !result.isUndefined()) {
    456    /* Suppress warnings from JS::ToString(). */
    457    JS::AutoSuppressWarningReporter suppressWarnings(cx);
    458    JSString* str = JS::ToString(cx, result);
    459    nsAutoJSString autoStr;
    460    if (str) autoStr.init(cx, str);
    461 
    462    if (!autoStr.IsEmpty() && aResult) {
    463      aResult->Assign(autoStr);
    464    }
    465  }
    466 
    467  return true;
    468 }