Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helping prevention of accidental directory exclusion when using the --export-on-exit option #4127

6 changes: 6 additions & 0 deletions src/emulator/commandUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const EXPORT_ON_EXIT_USAGE_ERROR =
`"${FLAG_EXPORT_ON_EXIT_NAME}" must be used with "${FLAG_IMPORT}"` +
` or provide a dir directly to "${FLAG_EXPORT_ON_EXIT}"`;

export const EXPORT_ON_EXIT_CWD_DANGER = `"${FLAG_EXPORT_ON_EXIT_NAME}" must not point to the current directory or parents. Please choose a new/dedicated directory for exports.`;

export const FLAG_UI = "--ui";
export const DESC_UI = "run the Emulator UI";

Expand Down Expand Up @@ -203,6 +205,10 @@ export function setExportOnExitOptions(options: any) {
// firebase emulators:start --debug --import '' --export-on-exit ''
throw new FirebaseError(EXPORT_ON_EXIT_USAGE_ERROR);
}

if (path.resolve(".").startsWith(path.resolve(options.exportOnExit))) {
throw new FirebaseError(EXPORT_ON_EXIT_CWD_DANGER);
}
}
return;
}
Expand Down
61 changes: 60 additions & 1 deletion src/test/emulators/commandUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,73 @@
import * as commandUtils from "../../emulator/commandUtils";
import { expect } from "chai";
import { FirebaseError } from "../../error";
import { EXPORT_ON_EXIT_USAGE_ERROR } from "../../emulator/commandUtils";
import { EXPORT_ON_EXIT_USAGE_ERROR, EXPORT_ON_EXIT_CWD_DANGER } from "../../emulator/commandUtils";
import { delimiter, join, resolve } from "path";
import * as sinon from "sinon";

describe("commandUtils", () => {
const testSetExportOnExitOptions = (options: any): any => {
commandUtils.setExportOnExitOptions(options);
return options;
};

describe("Mocked path resolve", () => {
const mockCWD = "/a/resolved/path/example";
const mockDestinationDir = "/path/example";

let pathStub: sinon.SinonStub;
beforeEach(() => {
pathStub = sinon.stub(require("path"), "resolve").callsFake((path) => {
return path === "." ? mockCWD : mockDestinationDir;
});
});

afterEach(() => {
pathStub.reset();
});

it("Should not block if destination contains a match to the CWD", () => {
const directoryToAllow = mockDestinationDir;
expect(testSetExportOnExitOptions({ exportOnExit: directoryToAllow }).exportOnExit).to.equal(
directoryToAllow
);
});
});

/**
* Currently, setting the --export-on-exit as the current CWD can inflict on
* full directory deletion
*/
const directoriesThatShouldFail = [
".", // The current dir
"./", // The current dir with /
resolve("."), // An absolute path
resolve(".."), // A folder that directs to the CWD
resolve("../.."), // Another folder that directs to the CWD
];

directoriesThatShouldFail.forEach((dir) => {
it(`Should disallow the user to set the current folder (ex: ${dir}) as --export-on-exit option`, () => {
expect(() => testSetExportOnExitOptions({ exportOnExit: dir })).to.throw(
EXPORT_ON_EXIT_CWD_DANGER
);
const cwdSubDir = join(dir, "some-dir");
expect(testSetExportOnExitOptions({ exportOnExit: cwdSubDir }).exportOnExit).to.equal(
cwdSubDir
);
});
});

it("Should disallow the user to set the current folder via the --import flag", () => {
expect(() => testSetExportOnExitOptions({ import: ".", exportOnExit: true })).to.throw(
EXPORT_ON_EXIT_CWD_DANGER
);
const cwdSubDir = join(".", "some-dir");
expect(
testSetExportOnExitOptions({ import: cwdSubDir, exportOnExit: true }).exportOnExit
).to.equal(cwdSubDir);
});

it("should validate --export-on-exit options", () => {
expect(testSetExportOnExitOptions({ import: "./data" }).exportOnExit).to.be.undefined;
expect(
Expand Down