/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const PREF_LOGLEVEL = "browser.policies.loglevel";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    prefix: "WindowsGPOParser",
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
    maxLogLevel: "error",
    maxLogLevelPref: PREF_LOGLEVEL,
  });
});

export var WindowsGPOParser = {
  readPolicies(wrk, policies) {
    let childWrk = wrk.openChild(
      "Mozilla\\" + Services.appinfo.name,
      wrk.ACCESS_READ
    );
    if (!policies) {
      policies = {};
    }
    try {
      policies = registryToObject(childWrk, policies);
    } catch (e) {
      lazy.log.error(e);
    } finally {
      childWrk.close();
    }
    // Need an extra check here so we don't
    // JSON.stringify if we aren't in debug mode
    if (lazy.log._maxLogLevel == "debug") {
      lazy.log.debug(JSON.stringify(policies, null, 2));
    }
    return policies;
  },
};

function registryToObject(wrk, policies) {
  if (!policies) {
    policies = {};
  }
  if (wrk.valueCount > 0) {
    if (wrk.getValueName(0) == "1") {
      // If the first item is 1, just assume it is an array
      let array = [];
      for (let i = 0; i < wrk.valueCount; i++) {
        array.push(readRegistryValue(wrk, wrk.getValueName(i)));
      }
      // If it's an array, it shouldn't have any children
      return array;
    }
    for (let i = 0; i < wrk.valueCount; i++) {
      let name = wrk.getValueName(i);
      let value = readRegistryValue(wrk, name);
      if (value != undefined) {
        policies[name] = value;
      }
    }
  }
  if (wrk.childCount > 0) {
    if (wrk.getChildName(0) == "1") {
      // If the first item is 1, it's an array of objects
      let array = [];
      for (let i = 0; i < wrk.childCount; i++) {
        let name = wrk.getChildName(i);
        let childWrk = wrk.openChild(name, wrk.ACCESS_READ);
        array.push(registryToObject(childWrk));
        childWrk.close();
      }
      // If it's an array, it shouldn't have any children
      return array;
    }
    for (let i = 0; i < wrk.childCount; i++) {
      let name = wrk.getChildName(i);
      let childWrk = wrk.openChild(name, wrk.ACCESS_READ);
      policies[name] = registryToObject(childWrk);
      childWrk.close();
    }
  }
  return policies;
}

function readRegistryValue(wrk, value) {
  switch (wrk.getValueType(value)) {
    case 7: // REG_MULTI_SZ
      // While we support JSON in REG_SZ and REG_MULTI_SZ, if it's REG_MULTI_SZ,
      // we know it must be JSON. So we go ahead and JSON.parse it here so it goes
      // through the schema validator.
      try {
        return JSON.parse(wrk.readStringValue(value).replace(/\0/g, "\n"));
      } catch (e) {
        lazy.log.error(`Unable to parse JSON for ${value}`);
        return undefined;
      }
    case 2: // REG_EXPAND_SZ
    case wrk.TYPE_STRING:
      return wrk.readStringValue(value);
    case wrk.TYPE_BINARY:
      return wrk.readBinaryValue(value);
    case wrk.TYPE_INT:
      return wrk.readIntValue(value);
    case wrk.TYPE_INT64:
      return wrk.readInt64Value(value);
  }
  // unknown type
  return null;
}
