Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 15 additions & 24 deletions src/util/submit-addon.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,6 @@ const log = createLogger(import.meta.url);

export const defaultAsyncFsReadFile = fsPromises.readFile;

// Used by fileFromSync method to make sure the form-data entry will
// include a filename derived from the file path.
//
// TODO: Get rid of this hack when we will bump the web-ext nodejs
// version required to nodejs v20 (where the native File constructor
// exists).
export class FileBlob extends Blob {
#name = '';

constructor(fileBits, fileName, options) {
super(fileBits, options);
this.#name = String(fileName);
}

get name() {
return this.#name;
}

get [Symbol.toStringTag]() {
return 'File';
}
}

export class JwtApiAuth {
#apiKey;
#apiSecret;
Expand Down Expand Up @@ -116,7 +93,21 @@ export default class Client {
// submitted and fail with the error message:
// "Unsupported file type, please upload a supported file (.crx, .xpi, .zip)."
const fileData = readFileSync(filePath);
return new FileBlob([fileData], basename(filePath));
// eslint-disable-next-line no-shadow -- File is in Node v20.0.0+.
let File = global.File;
// TODO: Use the File global directly without the fallback when we drop
// support for Node versions before v20.
if (typeof File === 'undefined') {
// Even without File being public, its interface and constructor could
// be accessed indirectly from the FormData interface. According to the
// FormData spec (that Node.js implements, via undici), the entry value
// of a FormData is always a scalar value or a File:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-entry-value
const fd = new FormData();
fd.set('x', new Blob([]));
File = fd.get('x').constructor;
}
return new File([fileData], basename(filePath));
}

nodeFetch(url, { method, headers, body, agent }) {
Expand Down
28 changes: 25 additions & 3 deletions tests/unit/test-util/test.submit-addon.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { createHash } from 'crypto';
import { promises as fsPromises, readFileSync } from 'fs';
import path from 'path';
// eslint-disable-next-line no-shadow -- TODO: Remove when we require Node v20+.
import { File } from 'node:buffer';
// ^ note: this was introduced in v18.13.0. Because of its unavailability in
// earlier versions, the actual implementation in submit-addon.js retrieves
// the File constructor in a different way, which also works in Node 18.0.0.
// Our CI tests with Node 18.19.0 as the lowest version, so this passes tests.

// eslint-disable-next-line import/no-extraneous-dependencies
import CRC32 from 'crc-32';
Expand All @@ -16,9 +22,6 @@ import Client, {
saveIdToFile,
saveUploadUuidToFile,
signAddon,

// eslint-disable-next-line no-shadow -- Not actually available under Node 20. Use global once possible.
FileBlob as File,
} from '../../../src/util/submit-addon.js';
import { withTempDir } from '../../../src/util/temp-dir.js';

Expand Down Expand Up @@ -372,6 +375,25 @@ describe('util.submit-addon', () => {
assert.equal(await fileRes.text(), FILE_CONTENT);
assert.equal(String(fileRes), '[object File]');
}));

it('should return a File whose name is preserved in FormData', () =>
withTempDir(async (tmpDir) => {
const client = new Client(clientDefaults);
const FILE_BASENAME = 'testfile.txt';
const FILE_CONTENT = 'somecontent';
const filePath = path.join(tmpDir.path(), FILE_BASENAME);
await fsPromises.writeFile(filePath, FILE_CONTENT);
const fileRes = client.fileFromSync(filePath);

// Regression test for https://github.com/mozilla/web-ext/issues/3418
const fd = new FormData();
fd.set('upload', fileRes);
const fileOut = fd.get('upload');

assert.equal(fileOut.name, FILE_BASENAME);
assert.equal(fileOut.size, FILE_CONTENT.length);
assert.equal(await fileOut.text(), FILE_CONTENT);
}));
});

describe('getPreviousUuidOrUploadXpi', () => {
Expand Down