Bug 1906169 - Error plumbing for profile backup web worker r=backup-reviewers,mconley

Some of the Firefox profile backup code is executed within a web worker, so errors thrown in the worker do not automatically maintain their full context when caught in the main process.

This change creates a BackupError for throwing errors with causes specific to Firefox profile backup. The new error type is configured to work with the PromiseWorker machinery in the Firefox codebase in order to serialize and deserialize error details across the worker boundary.

Differential Revision: https://phabricator.services.mozilla.com/D215920
This commit is contained in:
Stephen Thompson
2024-07-08 17:49:45 +00:00
parent 2bd0f056a7
commit ad5c1d8ebf
8 changed files with 283 additions and 135 deletions

View File

@@ -9,6 +9,8 @@ import { PromiseWorker } from "resource://gre/modules/workers/PromiseWorker.mjs"
/* eslint-disable mozilla/reject-import-system-module-from-non-system */
import { ArchiveUtils } from "resource:///modules/backup/ArchiveUtils.sys.mjs";
import { ArchiveEncryptor } from "resource:///modules/backup/ArchiveEncryption.sys.mjs";
import { BackupError } from "resource:///modules/backup/BackupError.mjs";
import { ERRORS } from "resource:///modules/backup/BackupConstants.mjs";
/**
* An ArchiveWorker is a PromiseWorker that tries to do most of the heavy
@@ -216,8 +218,9 @@ Content-Length: ${totalBase64Bytes}
while (currentIndex < totalBytesToRead) {
let bytesToRead = Math.min(chunkSize, totalBytesToRead - currentIndex);
if (bytesToRead <= 0) {
throw new Error(
"Failed to calculate the right number of bytes to read."
throw new BackupError(
"Failed to calculate the right number of bytes to read.",
ERRORS.FILE_SYSTEM_ERROR
);
}
@@ -305,14 +308,17 @@ ${ArchiveUtils.INLINE_MIME_END_MARKER}
/^<!DOCTYPE html>[\r\n]+<!-- Version: (\d+) -->[\r\n]+/;
let headerMatches = decodedHeader.match(EXPECTED_HEADER);
if (!headerMatches) {
throw new Error("Corrupt archive header");
throw new BackupError("Corrupt archive header", ERRORS.CORRUPTED_ARCHIVE);
}
let version = parseInt(headerMatches[1], 10);
// In the future, if we ever bump the ARCHIVE_FILE_VERSION, this is where we
// could place migrations / handlers for older archive versions.
if (version != ArchiveUtils.ARCHIVE_FILE_VERSION) {
throw new Error("Unsupported archive version: " + version);
throw new BackupError(
"Unsupported archive version: " + version,
ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}
// Now we have to scan forward, looking for the INLINE_MIME_MARKER_START
@@ -340,9 +346,10 @@ ${ArchiveUtils.INLINE_MIME_END_MARKER}
// This shouldn't happen, but better safe than sorry.
if (bytesToRead <= 0) {
throw new Error(
throw new BackupError(
"Failed to calculate the proper number of bytes to read: " +
bytesToRead
bytesToRead,
ERRORS.UNKNOWN
);
}
@@ -401,7 +408,10 @@ ${ArchiveUtils.INLINE_MIME_END_MARKER}
this.#worker = new PromiseWorker.AbstractWorker();
this.#worker.dispatch = (method, args = []) => {
if (!this[method]) {
throw new Error("Method does not exist: " + method);
throw new BackupError(
"Method does not exist: " + method,
ERRORS.INTERNAL_ERROR
);
}
return this[method](...args);
};

View File

@@ -9,7 +9,17 @@
// The ArchiveUtils module is designed to be imported in both worker and
// main thread contexts.
import { ArchiveUtils } from "resource:///modules/backup/ArchiveUtils.sys.mjs";
import { ERRORS } from "resource:///modules/backup/BackupConstants.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(
lazy,
{
BackupError: "resource:///modules/backup/BackupError.mjs",
ERRORS: "resource:///modules/backup/BackupConstants.mjs",
},
{ global: "contextual" }
);
/**
* Both ArchiveEncryptor and ArchiveDecryptor maintain an internal nonce used as
@@ -29,9 +39,10 @@ export const NonceUtils = {
*/
setLastChunkOnNonce(nonce) {
if (nonce[4] != 0) {
throw new Error("Last chunk byte on nonce already set!", {
cause: ERRORS.ENCRYPTION_FAILED,
});
throw new lazy.BackupError(
"Last chunk byte on nonce already set!",
lazy.ERRORS.ENCRYPTION_FAILED
);
}
// The nonce is 16 bytes so that we can use DataView / getBigUint64 for
@@ -71,9 +82,10 @@ export const NonceUtils = {
nonceBigInt * BigInt(ArchiveUtils.ARCHIVE_CHUNK_MAX_BYTES_SIZE) >
BigInt(ArchiveUtils.ARCHIVE_MAX_BYTES_SIZE)
) {
throw new Error("Exceeded archive maximum size.", {
cause: ERRORS.ENCRYPTION_FAILED,
});
throw new lazy.BackupError(
"Exceeded archive maximum size.",
lazy.ERRORS.ENCRYPTION_FAILED
);
}
view.setBigUint64(0, nonceBigInt);
@@ -143,7 +155,10 @@ export class ArchiveEncryptor {
*/
constructor() {
if (!ArchiveEncryptor.#isInternalConstructing) {
throw new Error("ArchiveEncryptor is not constructable.");
throw new lazy.BackupError(
"ArchiveEncryptor is not constructable.",
lazy.ERRORS.UNKNOWN
);
}
ArchiveEncryptor.#isInternalConstructing = false;
}
@@ -207,25 +222,25 @@ export class ArchiveEncryptor {
*/
async encrypt(plaintextChunk, isLastChunk = false) {
if (this.#isDone()) {
throw new Error(
throw new lazy.BackupError(
"Cannot encrypt any more chunks with this ArchiveEncryptor.",
{ cause: ERRORS.ENCRYPTION_FAILED }
lazy.ERRORS.ENCRYPTION_FAILED
);
}
if (plaintextChunk.byteLength > ArchiveUtils.ARCHIVE_CHUNK_MAX_BYTES_SIZE) {
throw new Error(
throw new lazy.BackupError(
`Chunk is too large to encrypt: ${plaintextChunk.byteLength} bytes`,
{ cause: ERRORS.ENCRYPTION_FAILED }
lazy.ERRORS.ENCRYPTION_FAILED
);
}
if (
plaintextChunk.byteLength != ArchiveUtils.ARCHIVE_CHUNK_MAX_BYTES_SIZE &&
!isLastChunk
) {
throw new Error(
throw new lazy.BackupError(
"Only last chunk can be smaller than the chunk max size",
{ cause: ERRORS.ENCRYPTION_FAILED }
lazy.ERRORS.ENCRYPTION_FAILED
);
}
@@ -247,9 +262,10 @@ export class ArchiveEncryptor {
plaintextChunk
);
} catch (e) {
throw new Error("Failed to encrypt a chunk.", {
cause: ERRORS.ENCRYPTION_FAILED,
});
throw new lazy.BackupError(
"Failed to encrypt a chunk.",
lazy.ERRORS.ENCRYPTION_FAILED
);
}
NonceUtils.incrementNonce(this.#nonce);
@@ -369,7 +385,10 @@ export class ArchiveDecryptor {
*/
constructor() {
if (!ArchiveDecryptor.#isInternalConstructing) {
throw new Error("ArchiveDecryptor is not constructable.");
throw new lazy.BackupError(
"ArchiveDecryptor is not constructable.",
lazy.ERRORS.UNKNOWN
);
}
ArchiveDecryptor.#isInternalConstructing = false;
}
@@ -381,8 +400,9 @@ export class ArchiveDecryptor {
*/
get OSKeyStoreSecret() {
if (!this.isDone()) {
throw new Error(
"Cannot access OSKeyStoreSecret until all chunks are decrypted."
throw new lazy.BackupError(
"Cannot access OSKeyStoreSecret until all chunks are decrypted.",
lazy.ERRORS.UNKNOWN
);
}
return this.#_OSKeyStoreSecret;
@@ -403,9 +423,9 @@ export class ArchiveDecryptor {
*/
async #initialize(recoveryCode, jsonBlock) {
if (jsonBlock.version > ArchiveUtils.SCHEMA_VERSION) {
throw new Error(
throw new lazy.BackupError(
`JSON block version ${jsonBlock.version} is greater than we can handle`,
{ cause: ERRORS.UNSUPPORTED_BACKUP_VERSION }
lazy.ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}
@@ -440,7 +460,7 @@ export class ArchiveDecryptor {
)
);
} catch (e) {
throw new Error("Unauthenticated", { cause: ERRORS.UNAUTHORIZED });
throw new lazy.BackupError("Unauthenticated", lazy.ERRORS.UNAUTHORIZED);
}
let textDecoder = new TextDecoder();
@@ -483,9 +503,10 @@ export class ArchiveDecryptor {
);
if (!verified) {
this.#poisonSelf();
throw new Error("Backup has been corrupted.", {
cause: ERRORS.CORRUPTED_ARCHIVE,
});
throw new lazy.BackupError(
"Backup has been corrupted.",
lazy.ERRORS.CORRUPTED_ARCHIVE
);
}
}
@@ -502,9 +523,9 @@ export class ArchiveDecryptor {
*/
async decrypt(ciphertextChunk, isLastChunk = false) {
if (this.isDone()) {
throw new Error(
throw new lazy.BackupError(
"Cannot decrypt any more chunks with this ArchiveDecryptor.",
{ cause: ERRORS.DECRYPTION_FAILED }
lazy.ERRORS.DECRYPTION_FAILED
);
}
@@ -512,9 +533,9 @@ export class ArchiveDecryptor {
ciphertextChunk.byteLength >
ArchiveUtils.ARCHIVE_CHUNK_MAX_BYTES_SIZE + ArchiveUtils.TAG_LENGTH_BYTES
) {
throw new Error(
throw new lazy.BackupError(
`Chunk is too large to decrypt: ${ciphertextChunk.byteLength} bytes`,
{ cause: ERRORS.DECRYPTION_FAILED }
lazy.ERRORS.DECRYPTION_FAILED
);
}
@@ -524,9 +545,9 @@ export class ArchiveDecryptor {
ArchiveUtils.TAG_LENGTH_BYTES &&
!isLastChunk
) {
throw new Error(
throw new lazy.BackupError(
"Only last chunk can be smaller than the chunk max size",
{ cause: ERRORS.DECRYPTION_FAILED }
lazy.ERRORS.DECRYPTION_FAILED
);
}
@@ -550,9 +571,10 @@ export class ArchiveDecryptor {
);
} catch (e) {
this.#poisonSelf();
throw new Error("Failed to decrypt a chunk.", {
cause: ERRORS.DECRYPTION_FAILED,
});
throw new lazy.BackupError(
"Failed to decrypt a chunk.",
lazy.ERRORS.DECRYPTION_FAILED
);
}
NonceUtils.incrementNonce(this.#nonce);

View File

@@ -16,6 +16,8 @@ ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
ChromeUtils.defineESModuleGetters(lazy, {
ArchiveUtils: "resource:///modules/backup/ArchiveUtils.sys.mjs",
OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
BackupError: "resource:///modules/backup/BackupError.mjs",
ERRORS: "resource:///modules/backup/BackupConstants.mjs",
});
/**
@@ -110,7 +112,10 @@ export class ArchiveEncryptionState {
constructor() {
if (!ArchiveEncryptionState.#isInternalConstructing) {
throw new Error("ArchiveEncryptionState is not constructable.");
throw new lazy.BackupError(
"ArchiveEncryptionState is not constructable.",
lazy.ERRORS.UNKNOWN
);
}
ArchiveEncryptionState.#isInternalConstructing = false;
}
@@ -278,8 +283,9 @@ export class ArchiveEncryptionState {
// have any need to do migrations just yet though, so any version that
// doesn't match the one that we can accept is rejected.
if (stateData.version != ArchiveEncryptionState.VERSION) {
throw new Error(
"The ArchiveEncryptionState version is from a newer version."
throw new lazy.BackupError(
"The ArchiveEncryptionState version is from a newer version.",
lazy.ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}

View File

@@ -36,4 +36,9 @@ export const ERRORS = Object.freeze({
ENCRYPTION_ALREADY_DISABLED: 10,
/** User supplied a new password that is not a valid password */
INVALID_PASSWORD: 12,
/**
* An error internal to the code that is likely caused by a bug
* or other programmer error.
*/
INTERNAL_ERROR: 13,
});

View File

@@ -0,0 +1,64 @@
/* 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/. */
/**
* Error class with specific backup-related error causes.
*
* Can be serialized and deserialized across a worker boundary using
* the BasePromiseWorker and PromiseWorker machinery in this codebase.
*
* @see PromiseWorker.mjs
* @see PromiseWorker.sys.mjs
*/
export class BackupError extends Error {
name = "BackupError";
/**
* @param {string} message
* Error message
* @param {number} cause
* Error cause code @see BackupConstants.ERRORS
*/
constructor(message, cause) {
super(message, { cause });
}
/**
* @typedef {object} SerializedBackupError
* @property {'BackupError'} exn
* Exception name for PromiseWorker serialization
* @property {string} message
* Error message
* @property {number} cause
* Error cause code @see BackupConstants.ERRORS
* @property {string} stack
* Stack trace of the error
*/
/**
* Used by PromiseWorker.mjs from within a web worker in order to
* serialize this error for later reconstruction in the main process.
*
* @returns {SerializedBackupError}
* @see PromiseWorker.mjs
*/
toMsg() {
return {
exn: BackupError.name,
message: this.message,
cause: this.cause,
stack: this.stack,
};
}
/**
* @param {SerializedBackupError} serialized
* Worker error serialized by PromiseWorker
* @returns {BackupError}
*/
static fromMsg(serialized) {
let error = new BackupError(serialized.message, serialized.cause);
error.stack = serialized.stack;
return error;
}
}

View File

@@ -13,6 +13,7 @@ import {
BYTES_IN_MEBIBYTE,
} from "resource:///modules/backup/MeasurementUtils.sys.mjs";
import { ERRORS } from "resource:///modules/backup/BackupConstants.mjs";
import { BackupError } from "resource:///modules/backup/BackupError.mjs";
const BACKUP_DIR_PREF_NAME = "browser.backup.location";
const SCHEDULED_BACKUPS_ENABLED_PREF_NAME = "browser.backup.scheduled.enabled";
@@ -760,9 +761,10 @@ export class BackupService extends EventTarget {
} else if (schemaType == SCHEMAS.ARCHIVE_JSON_BLOCK) {
schemaURL = `chrome://browser/content/backup/ArchiveJSONBlock.${version}.schema.json`;
} else {
throw new Error(`Did not recognize SCHEMAS constant: ${schemaType}`, {
cause: ERRORS.UNKNOWN,
});
throw new BackupError(
`Did not recognize SCHEMAS constant: ${schemaType}`,
ERRORS.UNKNOWN
);
}
let response = await fetch(schemaURL);
@@ -819,9 +821,10 @@ export class BackupService extends EventTarget {
*/
static get() {
if (!this.#instance) {
throw new Error("BackupService not initialized", {
cause: ERRORS.UNINITIALIZED,
});
throw new BackupError(
"BackupService not initialized",
ERRORS.UNINITIALIZED
);
}
return this.#instance;
}
@@ -1378,7 +1381,7 @@ export class BackupService extends EventTarget {
await IOUtils.remove(recoveryFilePath, {
retryReadonly: true,
});
throw new Error("Corrupt archive.", { cause: ERRORS.CORRUPTED_ARCHIVE });
throw new BackupError("Corrupt archive.", ERRORS.CORRUPTED_ARCHIVE);
}
await this.#decompressChildren(recoveryFolderDestPath, "", recoveryArchive);
@@ -1541,9 +1544,10 @@ export class BackupService extends EventTarget {
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/`;
if (!stylesText.includes(MPL_LICENSE)) {
throw new Error("Expected the MPL license block within archive.css", {
cause: ERRORS.UNKNOWN,
});
throw new BackupError(
"Expected the MPL license block within archive.css",
ERRORS.UNKNOWN
);
}
stylesText = stylesText.replace(MPL_LICENSE, "");
@@ -1597,6 +1601,7 @@ export class BackupService extends EventTarget {
"resource:///modules/backup/Archive.worker.mjs",
{ type: "module" }
);
worker.ExceptionHandlers[BackupError.name] = BackupError.fromMsg;
let chunkSize =
options.chunkSize || lazy.ArchiveUtils.ARCHIVE_CHUNK_MAX_BYTES_SIZE;
@@ -1612,7 +1617,8 @@ export class BackupService extends EventTarget {
}
: null;
await worker.post("constructArchive", [
await worker
.post("constructArchive", [
{
archivePath,
markup,
@@ -1621,12 +1627,13 @@ export class BackupService extends EventTarget {
encryptionArgs,
chunkSize,
},
]);
} catch (e) {
// TODO: Bug 1906169 - errors in archive service worker should be more
// specific than a general "unknown" error cause
throw new Error("Backup archive creation failed", {
cause: ERRORS.UNKNOWN,
])
.catch(e => {
lazy.logConsole.error(e);
if (!(e instanceof BackupError)) {
throw new BackupError("Failed to create archive", ERRORS.UNKNOWN);
}
throw e;
});
} finally {
worker.terminate();
@@ -1789,9 +1796,10 @@ export class BackupService extends EventTarget {
resolve(archiveMetadata);
} catch (e) {
reject(
new Error("Could not parse archive metadata.", {
cause: ERRORS.CORRUPTED_ARCHIVE,
})
new BackupError(
"Could not parse archive metadata.",
ERRORS.CORRUPTED_ARCHIVE
)
);
}
// No need to load anything else - abort reading in more
@@ -1878,18 +1886,28 @@ export class BackupService extends EventTarget {
"resource:///modules/backup/Archive.worker.mjs",
{ type: "module" }
);
worker.ExceptionHandlers[BackupError.name] = BackupError.fromMsg;
if (!(await IOUtils.exists(archivePath))) {
throw new Error("Archive file does not exist at path " + archivePath, {
cause: ERRORS.UNKNOWN,
});
throw new BackupError(
"Archive file does not exist at path " + archivePath,
ERRORS.UNKNOWN
);
}
try {
let { startByteOffset, contentType } = await worker.post(
"parseArchiveHeader",
[archivePath]
let { startByteOffset, contentType } = await worker
.post("parseArchiveHeader", [archivePath])
.catch(e => {
lazy.logConsole.error(e);
if (!(e instanceof BackupError)) {
throw new BackupError(
"Failed to parse archive header",
ERRORS.FILE_SYSTEM_ERROR
);
}
throw e;
});
let archiveFile = await IOUtils.getFile(archivePath);
let archiveJSON;
try {
@@ -1900,16 +1918,15 @@ export class BackupService extends EventTarget {
);
if (!archiveJSON.version) {
throw new Error("Missing version in the archive JSON block.", {
cause: ERRORS.CORRUPTED_ARCHIVE,
});
throw new BackupError(
"Missing version in the archive JSON block.",
ERRORS.CORRUPTED_ARCHIVE
);
}
if (archiveJSON.version > lazy.ArchiveUtils.SCHEMA_VERSION) {
throw new Error(
throw new BackupError(
`Archive JSON block is a version newer than we can interpret: ${archiveJSON.version}`,
{
cause: ERRORS.UNSUPPORTED_BACKUP_VERSION,
}
ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}
@@ -1936,9 +1953,9 @@ export class BackupService extends EventTarget {
);
// TODO: Collect telemetry for this case. (bug 1891817)
throw new Error(
throw new BackupError(
`Archive JSON block does not conform to schema version ${archiveJSON.version}`,
{ cause: ERRORS.CORRUPTED_ARCHIVE }
ERRORS.CORRUPTED_ARCHIVE
);
}
} catch (e) {
@@ -1954,6 +1971,9 @@ export class BackupService extends EventTarget {
contentType,
archiveJSON,
};
} catch (e) {
lazy.logConsole.error(e);
throw e;
} finally {
worker.terminate();
}
@@ -1988,9 +2008,9 @@ export class BackupService extends EventTarget {
let decryptor = null;
if (isEncrypted) {
if (!recoveryCode) {
throw new Error(
throw new BackupError(
"A recovery code is required to decrypt this archive.",
{ cause: ERRORS.UNAUTHORIZED }
ERRORS.UNAUTHORIZED
);
}
decryptor = await lazy.ArchiveDecryptor.initialize(
@@ -2074,9 +2094,10 @@ export class BackupService extends EventTarget {
lazy.logConsole.error(
`Something went wrong while finalizing the staging folder. ${e}`
);
throw new Error("Failed to finalize staging folder", {
cause: ERRORS.FILE_SYSTEM_ERROR,
});
throw new BackupError(
"Failed to finalize staging folder",
ERRORS.FILE_SYSTEM_ERROR
);
}
}
@@ -2275,17 +2296,16 @@ export class BackupService extends EventTarget {
);
let manifest = await IOUtils.readJSON(manifestPath);
if (!manifest.version) {
throw new Error("Backup manifest version not found", {
cause: ERRORS.CORRUPTED_ARCHIVE,
});
throw new BackupError(
"Backup manifest version not found",
ERRORS.CORRUPTED_ARCHIVE
);
}
if (manifest.version > lazy.ArchiveUtils.SCHEMA_VERSION) {
throw new Error(
throw new BackupError(
"Cannot recover from a manifest newer than the current schema version",
{
cause: ERRORS.UNSUPPORTED_BACKUP_VERSION,
}
ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}
@@ -2306,9 +2326,10 @@ export class BackupService extends EventTarget {
schemaValidationResult
);
// TODO: Collect telemetry for this case. (bug 1891817)
throw new Error("Cannot recover from an invalid backup manifest", {
cause: ERRORS.CORRUPTED_ARCHIVE,
});
throw new BackupError(
"Cannot recover from an invalid backup manifest",
ERRORS.CORRUPTED_ARCHIVE
);
}
// In the future, if we ever bump the ArchiveUtils.SCHEMA_VERSION and need
@@ -2318,22 +2339,18 @@ export class BackupService extends EventTarget {
let meta = manifest.meta;
if (meta.appName != AppConstants.MOZ_APP_NAME) {
throw new Error(
throw new BackupError(
`Cannot recover a backup from ${meta.appName} in ${AppConstants.MOZ_APP_NAME}`,
{
cause: ERRORS.UNSUPPORTED_BACKUP_VERSION,
}
ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}
if (
Services.vc.compare(AppConstants.MOZ_APP_VERSION, meta.appVersion) < 0
) {
throw new Error(
throw new BackupError(
`Cannot recover a backup created on version ${meta.appVersion} in ${AppConstants.MOZ_APP_VERSION}`,
{
cause: ERRORS.UNSUPPORTED_BACKUP_VERSION,
}
ERRORS.UNSUPPORTED_BACKUP_VERSION
);
}
@@ -2509,7 +2526,10 @@ export class BackupService extends EventTarget {
setParentDirPath(parentDirPath) {
try {
if (!parentDirPath || !PathUtils.filename(parentDirPath)) {
throw new Error("Parent directory path is invalid.");
throw new BackupError(
"Parent directory path is invalid.",
ERRORS.FILE_SYSTEM_ERROR
);
}
// Recreate the backups path with the new parent directory.
let fullPath = PathUtils.join(
@@ -2710,21 +2730,24 @@ export class BackupService extends EventTarget {
lazy.logConsole.debug("Enabling encryption.");
let encState = await this.loadEncryptionState(profilePath);
if (encState) {
throw new Error("Encryption is already enabled.", {
cause: ERRORS.ENCRYPTION_ALREADY_ENABLED,
});
throw new BackupError(
"Encryption is already enabled.",
ERRORS.ENCRYPTION_ALREADY_ENABLED
);
}
if (!password) {
throw new Error("Cannot supply a blank password.", {
cause: ERRORS.INVALID_PASSWORD,
});
throw new BackupError(
"Cannot supply a blank password.",
ERRORS.INVALID_PASSWORD
);
}
if (password.length < 8) {
throw new Error("Password must be at least 8 characters.", {
cause: ERRORS.INVALID_PASSWORD,
});
throw new BackupError(
"Password must be at least 8 characters.",
ERRORS.INVALID_PASSWORD
);
}
// TODO: Enforce other password rules here, such as ensuring that the
@@ -2733,9 +2756,10 @@ export class BackupService extends EventTarget {
password
));
if (!encState) {
throw new Error("Failed to construct ArchiveEncryptionState", {
cause: ERRORS.UNKNOWN,
});
throw new BackupError(
"Failed to construct ArchiveEncryptionState",
ERRORS.UNKNOWN
);
}
this.#encState = encState;
@@ -2766,9 +2790,10 @@ export class BackupService extends EventTarget {
lazy.logConsole.debug("Disabling encryption.");
let encState = await this.loadEncryptionState(profilePath);
if (!encState) {
throw new Error("Encryption is already disabled.", {
cause: ERRORS.ENCRYPTION_ALREADY_DISABLED,
});
throw new BackupError(
"Encryption is already disabled.",
ERRORS.ENCRYPTION_ALREADY_DISABLED
);
}
let encStateFile = PathUtils.join(

View File

@@ -27,6 +27,7 @@ EXTRA_JS_MODULES.backup += [
"ArchiveEncryptionState.sys.mjs",
"ArchiveUtils.sys.mjs",
"BackupConstants.mjs",
"BackupError.mjs",
"BackupResources.sys.mjs",
"BackupService.sys.mjs",
"MeasurementUtils.sys.mjs",

View File

@@ -8,6 +8,8 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
BackupError: "resource:///modules/backup/BackupError.mjs",
ERRORS: "resource:///modules/backup/BackupConstants.mjs",
});
// Convert from bytes to kilobytes (not kibibytes).
@@ -39,7 +41,10 @@ export class BackupResource {
* @type {string}
*/
static get key() {
throw new Error("BackupResource::key needs to be overridden.");
throw new lazy.BackupError(
"BackupResource::key needs to be overridden.",
lazy.ERRORS.INTERNAL_ERROR
);
}
/**
@@ -52,8 +57,9 @@ export class BackupResource {
* @type {boolean}
*/
static get requiresEncryption() {
throw new Error(
"BackupResource::requiresEncryption needs to be overridden."
throw new lazy.BackupError(
"BackupResource::requiresEncryption needs to be overridden.",
lazy.ERRORS.INTERNAL_ERROR
);
}
@@ -230,7 +236,10 @@ export class BackupResource {
*/
// eslint-disable-next-line no-unused-vars
async measure(profilePath) {
throw new Error("BackupResource::measure needs to be overridden.");
throw new lazy.BackupError(
"BackupResource::measure needs to be overridden.",
lazy.ERRORS.INTERNAL_ERROR
);
}
/**
@@ -259,7 +268,10 @@ export class BackupResource {
*/
// eslint-disable-next-line no-unused-vars
async backup(stagingPath, profilePath = null, isEncrypting = false) {
throw new Error("BackupResource::backup must be overridden");
throw new lazy.BackupError(
"BackupResource::backup must be overridden",
lazy.ERRORS.INTERNAL_ERROR
);
}
/**
@@ -292,7 +304,10 @@ export class BackupResource {
*/
// eslint-disable-next-line no-unused-vars
async recover(manifestEntry, recoveryPath, destProfilePath) {
throw new Error("BackupResource::recover must be overridden");
throw new lazy.BackupError(
"BackupResource::recover must be overridden",
lazy.ERRORS.INTERNAL_ERROR
);
}
/**