-
Notifications
You must be signed in to change notification settings - Fork 983
Add software installer file size check before upload #50475
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
Changes from all commits
eced266
001b0f7
dc3c3a7
31896f4
11902a9
b40b4f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| - Added a software package maximum size error message in the UI to fix inconsistent errors across different browsers. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,9 @@ | ||||||||||||||||
| import React from "react"; | ||||||||||||||||
|
|
||||||||||||||||
| import { validateQuery } from "components/forms/validators/validate_query"; | ||||||||||||||||
| import { listNamesFromSelectedLabels } from "services/entities/labels"; | ||||||||||||||||
| import { getExtensionFromFileName } from "utilities/file/fileUtils"; | ||||||||||||||||
| import { encodeScriptBase64 } from "utilities/scripts_encoding"; | ||||||||||||||||
| import { getGitOpsModeTipContent } from "utilities/helpers"; | ||||||||||||||||
| import { IPackageFormData, IPackageFormValidation } from "./PackageForm"; | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -236,4 +238,47 @@ export const createTooltipContent = ( | |||||||||||||||
| ); | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| /** Calculates the size of the payload, because the server limits the whole | ||||||||||||||||
| * request and not just the installer file. Not all fields are accounted for | ||||||||||||||||
| * in this calculation so if the final payload sent is over the size limit, | ||||||||||||||||
| * the server will reject it. | ||||||||||||||||
| */ | ||||||||||||||||
| export const estimateUploadSize = (formData: IPackageFormData) => { | ||||||||||||||||
| const scripts = [ | ||||||||||||||||
| formData.installScript, | ||||||||||||||||
| formData.uninstallScript, | ||||||||||||||||
| formData.preInstallQuery, | ||||||||||||||||
| formData.postInstallScript, | ||||||||||||||||
| ]; | ||||||||||||||||
|
|
||||||||||||||||
| // The scripts are base64 encoded on the way out, so encode them to get the | ||||||||||||||||
| // length that actually goes over the wire. | ||||||||||||||||
| let scriptsSize = 0; | ||||||||||||||||
| scripts.forEach((script) => { | ||||||||||||||||
| scriptsSize += encodeScriptBase64(script)?.length || 0; | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| // The two flags are sent as "true" or "false". | ||||||||||||||||
| let fieldsSize = | ||||||||||||||||
| String(formData.selfService).length + | ||||||||||||||||
| String(formData.automaticInstall).length; | ||||||||||||||||
|
|
||||||||||||||||
| // Names are user written, so count bytes. Anything outside ASCII takes more | ||||||||||||||||
| // than one. | ||||||||||||||||
| const encoder = new TextEncoder(); | ||||||||||||||||
|
|
||||||||||||||||
| formData.categories.forEach((category) => { | ||||||||||||||||
| fieldsSize += encoder.encode(category).length; | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| // Labels are only sent when the target is Custom, and only the selected ones. | ||||||||||||||||
| if (formData.targetType === "Custom") { | ||||||||||||||||
| listNamesFromSelectedLabels(formData.labelTargets).forEach((label) => { | ||||||||||||||||
| fieldsSize += encoder.encode(label).length; | ||||||||||||||||
| }); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return (formData.software?.size || 0) + scriptsSize + fieldsSize; | ||||||||||||||||
|
Comment on lines
+241
to
+281
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Measure the multipart body that the upload sends. The server limits the raw request body before multipart parsing.
📍 Affects 2 files
🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decided to only go with estimating the size of the form and not the whole multipart request. It is always more permissive, there should never be a case where the frontend rejects something the backend would accept. The backend check is still there so it's not a big deal. If we want to improve this in the future, it should probably be done for all endpoints affected by fleet/cmd/fleet/http_middleware.go Lines 43 to 49 in 92aaf1d
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "and it counts category and label strings by JavaScript code units instead of encoded byte length" seems like a valid finding though. |
||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| export default generateFormValidation; | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,3 +102,51 @@ export interface IFileDetails { | |
| name: string; | ||
| description?: React.ReactNode; | ||
| } | ||
|
|
||
| // Both tables match the ones go-units gives the server, so the two agree at | ||
| // every magnitude rather than only up to terabytes. | ||
| const DECIMAL_ABBREVIATIONS = [ | ||
| "B", | ||
| "kB", | ||
| "MB", | ||
| "GB", | ||
| "TB", | ||
| "PB", | ||
| "EB", | ||
| "ZB", | ||
| "YB", | ||
| ]; | ||
| const BINARY_ABBREVIATIONS = [ | ||
| "B", | ||
| "KiB", | ||
| "MiB", | ||
| "GiB", | ||
| "TiB", | ||
| "PiB", | ||
| "EiB", | ||
| "ZiB", | ||
| "YiB", | ||
| ]; | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The server's |
||
| const formatWithBase = ( | ||
| bytes: number, | ||
| base: number, | ||
| abbreviations: string[] | ||
| ) => { | ||
| let size = bytes; | ||
| let abbreviationIndex = 0; | ||
| while (size >= base && abbreviationIndex < abbreviations.length - 1) { | ||
| size /= base; | ||
| abbreviationIndex += 1; | ||
| } | ||
| // 4 significant digits with trailing zeros dropped, matching Go's "%.4g" | ||
| return `${Number(size.toPrecision(4))}${abbreviations[abbreviationIndex]}`; | ||
| }; | ||
|
|
||
| // Returns a human readable size, like the server's installersize.Human function | ||
| export const formatFileSize = (bytes: number) => { | ||
| const decimal = formatWithBase(bytes, 1000, DECIMAL_ABBREVIATIONS); | ||
| const binary = formatWithBase(bytes, 1024, BINARY_ABBREVIATIONS); | ||
|
|
||
| return binary.length < decimal.length ? binary : decimal; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.