From 0ba867a88fe3ab806b1fac8d26661d356fd7169f Mon Sep 17 00:00:00 2001 From: "Victor Piryns (pivi)" Date: Mon, 4 Sep 2023 14:31:18 +0000 Subject: [PATCH] [FIX] web: truncate filename when importing custom module via studio Issue: When uploading a large custom module via studio for importing, Chrome crashes with an internal SIGKILL and Firefox manages to load, but is really sluggish. Steps to reproduce: - Install Studio - In the Apps Dashboard > Customizations > Import - Upload a large custom module (50 MiB) - Chrome crashes, while on Firefox the page is sluggish Cause: In the module `base_import_module`, the `module_file` doesn't have a corresponding `filename`, therefor the name used to be displayed in the wizard is the content of the binary file in base64 encoding... For a module that is of a large size, this is a *gigantic* string that crashes Chrome and slows down the DOM on Firefox. Fix: Truncate the maximum filename that is displayed in the template to the max length necessary to encode in base64 (0xFF) 255 bytes, as 255 bytes is the maximum filename size on Linux/Windows/MacOS. Affected versions: 16.0 up to master Reference: opw-3491998 closes odoo/odoo#134532 X-original-commit: 218c9875c0422beb2a5b199797e531ed3625a13b Signed-off-by: Aaron Bohy (aab) Signed-off-by: Piryns Victor (pivi) --- addons/web/static/src/core/utils/binary.js | 9 +++++++ .../src/views/fields/binary/binary_field.js | 7 ++++-- .../tests/views/fields/binary_field_tests.js | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/core/utils/binary.js b/addons/web/static/src/core/utils/binary.js index 94bc1c91518..1e033f9a5a2 100644 --- a/addons/web/static/src/core/utils/binary.js +++ b/addons/web/static/src/core/utils/binary.js @@ -7,3 +7,12 @@ export function isBinarySize(value) { return /^\d+(\.\d*)? [^0-9]+$/.test(value); } + +/** + * Get the length necessary for a base64 str to encode maxBytes + * @param {number} maxBytes number of bytes we want to encode in base64 + * @returns {number} number of char + */ +export function toBase64Length(maxBytes) { + return Math.ceil(maxBytes * 4 / 3); +} diff --git a/addons/web/static/src/views/fields/binary/binary_field.js b/addons/web/static/src/views/fields/binary/binary_field.js index 1ecd5a51c02..a32610e932d 100644 --- a/addons/web/static/src/views/fields/binary/binary_field.js +++ b/addons/web/static/src/views/fields/binary/binary_field.js @@ -2,13 +2,16 @@ import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; -import { isBinarySize } from "@web/core/utils/binary"; +import { isBinarySize, toBase64Length } from "@web/core/utils/binary"; import { download } from "@web/core/network/download"; import { standardFieldProps } from "../standard_field_props"; import { FileUploader } from "../file_handler"; import { _t } from "@web/core/l10n/translation"; import { Component } from "@odoo/owl"; + +export const MAX_FILENAME_SIZE_BYTES = 0xFF; // filenames do not exceed 255 bytes on Linux/Windows/MacOS + export class BinaryField extends Component { static template = "web.BinaryField"; static components = { @@ -32,7 +35,7 @@ export class BinaryField extends Component { this.props.record.data[this.props.fileNameField] || this.props.record.data[this.props.name] || "" - ); + ).slice(0, toBase64Length(MAX_FILENAME_SIZE_BYTES)); } update({ data, name }) { diff --git a/addons/web/static/tests/views/fields/binary_field_tests.js b/addons/web/static/tests/views/fields/binary_field_tests.js index bdb75a59329..12af81a9c99 100644 --- a/addons/web/static/tests/views/fields/binary_field_tests.js +++ b/addons/web/static/tests/views/fields/binary_field_tests.js @@ -15,6 +15,8 @@ import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { browser } from "@web/core/browser/browser"; import { errorService } from "@web/core/errors/error_service"; import { registry } from "@web/core/registry"; +import { MAX_FILENAME_SIZE_BYTES } from "@web/views/fields/binary/binary_field"; +import { toBase64Length } from "@web/core/utils/binary"; const BINARY_FILE = "R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7"; @@ -472,6 +474,28 @@ QUnit.module("Fields", (hooks) => { assert.containsNone(target, "button.fa-download"); }); + QUnit.test("Binary filename doesn't exceed 255 bytes", async function (assert) { + const LARGE_BINARY_FILE = BINARY_FILE.repeat(5); + assert.ok((LARGE_BINARY_FILE.length / 4 * 3) > MAX_FILENAME_SIZE_BYTES, + "The initial binary file should be larger than max bytes that can represent the filename"); + serverData.models.partner.fields.document.default = LARGE_BINARY_FILE; + await makeView({ + serverData, + type: "form", + resModel: "partner", + arch: ` +
+ + + `, + }); + assert.strictEqual( + target.querySelector(".o_field_binary input[type=text]").value.length, + toBase64Length(MAX_FILENAME_SIZE_BYTES), + "The filename shouldn't exceed the maximum size in bytes in base64" + ); + }); + QUnit.test("BinaryField filename is updated when using the pager", async function (assert) { serverData.models.partner.records.push( {