Repository navigation
fix: harden input validation and edge-case handling in filesize() #343
Copy link
Copy link
Closed
Labels
Description
Summary
The filesize() function has a broad set of unhandled edge cases in input validation, option handling, and formatting that produce silent wrong output, raw native errors, or inconsistent results between equivalent inputs. This issue inventories every confirmed edge case in one place at equal priority.
Edge Cases (Complete Inventory)
Every case below is confirmed against the live code. All are treated at equal priority.
| # | Call | Observed | Expected / Note |
|---|---|---|---|
| 1 | filesize(BigInt("1" + "0".repeat(400))) |
"Infinity YB" |
throw TypeError |
| 2 | filesize(1000, { exponent: 1.5 }) |
"NaN undefined" |
throw TypeError or clamp |
| 3 | filesize(1000, { exponent: "1" }) |
"1 KB" |
"1 kB" (match number path) |
| 4 | filesize(1000, { precision: 101 }) |
raw RangeError |
throw TypeError |
| 5 | filesize(1000, { output: "foo" }) |
"1 kB" |
throw TypeError |
| 6 | filesize("1_000") |
throw TypeError |
document or parse |
| 7 | filesize("1000n") |
throw TypeError |
document or parse |
| 8 | filesize(undefined) |
throw TypeError |
document |
| 9 | filesize(null) |
"0 B" |
document coercion |
| 10 | filesize(true) |
"1 B" |
document coercion |
| 11 | filesize(false) |
"0 B" |
document coercion |
| 12 | filesize("") |
"0 B" |
document coercion |
| 13 | filesize(" ") |
"0 B" |
document coercion |
| 14 | filesize([1000]) |
"1 kB" |
document coercion |
| 15 | filesize("0x1F") |
"31 B" |
document coercion |
| 16 | filesize("0b101") |
"5 B" |
document coercion |
| 17 | filesize("0o17") |
"15 B" |
document coercion |
| 18 | filesize(-0.4) |
"0 B" |
"-0 B" (sign preserved) |
| 19 | filesize(-0.4, { precision: 3 }) |
"-0.00 B" |
consistent with above |
| 20 | filesize(-1, { fullform: true }) |
"-1 bytes" |
"-1 byte" (singular) |
| 21 | filesize(-1, { fullform: true, precision: 3 }) |
"-1.00 bytes" |
"-1.00 byte" |
| 22 | filesize(-0) |
"0 B" |
"-0 B" or document |
| 23 | filesize(Number.MAX_VALUE) |
"1.797...e+284 YB" |
no scientific notation |
| 24 | filesize(Number.MIN_VALUE) |
"0 B" |
document |
| 25 | filesize(1024, { standard: "iec", base: 10 }) |
"1 KiB" |
standard wins over base |
| 26 | filesize(1024, { standard: "si", base: 2 }) |
"1.02 kB" |
standard wins over base |
| 27 | filesize(1000, { base: 8 }) |
"1 kB" |
base 8 falls to decimal default |
| 28 | filesize(1000, { base: 16 }) |
"1 kB" |
base 16 falls to decimal default |
| 29 | filesize(1000, { symbols: { kB: "kilobyte" }, fullform: true }) |
"1 kilobyte" |
fullform overrides symbols |
| 30 | filesize(1536, { locale: "de-DE", separator: "_" }) |
"1,54 kB" |
locale overrides separator |
| 31 | filesize(1536, { locale: true, separator: "_" }) |
"1.54 kB" |
separator ignored when locale: true |
| 32 | filesize(1000000, { fullform: true, fullforms: ["custom"] }) |
"1 megabyte" |
fullforms[e] undefined, falls back |
| 33 | filesize(1536, { round: -1 }) |
"2 kB" |
negative round treated as 0 |
| 34 | filesize(1536, { round: -1, pad: true }) |
"2 kB" |
same |
| 35 | filesize(999.5, { round: 0 }) |
"1 kB" |
rounds to 1000, auto-increments |
| 36 | filesize(999.999, { round: 2 }) |
"1 kB" |
rounds to 1000, auto-increments |
| 37 | filesize(1000, { precision: 2.5 }) |
"1.0 kB" |
non-integer precision truncated |
| 38 | filesize(0.4) |
"0 B" |
sub-byte rounds to zero |
| 39 | filesize(0.5) |
"1 B" |
sub-byte rounds up |
| 40 | filesize(1.4) |
"1 B" |
rounds down |
| 41 | filesize(1.5) |
"2 B" |
rounds up |
| 42 | filesize(125, { bits: true }) |
"1 kbit" |
125 bytes = 1000 bits, auto-increments |
| 43 | filesize(124, { bits: true }) |
"992 bit" |
just below boundary |
| 44 | filesize(125, { bits: true, exponent: 0 }) |
"1000 bit" |
forced exponent prevents increment |
| 45 | filesize(124, { bits: true, exponent: 0 }) |
"992 bit" |
same |
| 46 | filesize(1234567890, { precision: 2, output: "array" }) |
["1.2", "GB"] |
value is a string |
| 47 | filesize(1234567890, { precision: 2, output: "object" }) |
{ value: "1.2", ... } |
value is a string |
| 48 | filesize(0.125, { bits: true, fullform: true, fullforms: ["", "custom-bit"] }) |
"1 bit" |
fullforms[0] empty, default used |
| 49 | filesize(1024, { bits: true, fullform: true, fullforms: ["", "customkbit"] }) |
"8.19 customkbit" |
custom applied |
| 50 | filesize(-1000, { bits: true }) |
"-8 kbit" |
|
| 51 | filesize(-1000, { fullform: true, bits: true }) |
"-8 kilobits" |
|
| 52 | filesize(1536, { locale: true, localeOptions: { maximumFractionDigits: 1 } }) |
"1.54 kB" |
localeOptions ignored when locale: true |
| 53 | filesize(1536, { locale: "de-DE", localeOptions: { useGrouping: false }, pad: true, round: 2 }) |
"1,54 kB" |
|
| 54 | filesize(1000, { symbols: {} }) |
"1 kB" |
empty symbols object |
| 55 | filesize(1000, { symbols: { MB: "megabyte" } }) |
"1 kB" |
non-matching key ignored |
| 56 | filesize(1000, { spacer: " - " }) |
"1 - kB" |
multi-char spacer |
| 57 | filesize(1000, { spacer: "", output: "array" }) |
[1, "kB"] |
spacer ignored for array |
Root Cause Analysis
- BigInt overflow:
filesize()converts BigInt viaNumber(arg)atsrc/filesize.js:77-79but skips theisFinitecheck at line 86 that the number path applies. An overflowing BigInt becomesInfinityand is never rejected. - Float exponent:
calculateExponent()atsrc/helpers.js:288-313only handlese === -1/isNaN(line 289) ande < 0(line 298). A positive non-integer exponent indexes the power lookup tables out of bounds, producingNaN. - String exponent:
resolveSymbol()atsrc/helpers.js:356-369uses stricte === 1(line 359) for the SI special case. String"1"fails strict equality and falls through to the JEDEC symbol. - Negative fullform singular: the singular/plural check at
src/helpers.js:445usesnumericValue === 1, which fails for-1. - Sign loss on round-to-zero:
negis captured atsrc/filesize.js:94before rounding. When a negative value rounds to 0, the sign is dropped in the non-precision path but preserved in the precision path (decorateResultline 409 prefixes the sign only for string values). - Precision out of range:
value.toPrecision(precision)atsrc/helpers.js:195is called with no range validation;precision: 101throws a raw nativeRangeError. - Invalid output:
formatOutput()atsrc/helpers.js:468-489only checksoutput === ARRAY(line 469) andoutput === OBJECT(line 473); any other value silently falls through to string. - Scientific notation leak: the precision path strips scientific notation but the non-precision path does not, so
Number.MAX_VALUEleakse+284.
Testing Strategy
- Add regression tests for every case in the table above to
tests/unit/filesize.test.js. - Add targeted helper tests in
tests/unit/filesize-helpers.test.jsforcalculateExponent,resolveSymbol,applyRounding, andapplyNumberFormatting. - Verify with
npm run testandnpm run coverage(currently 100% line/branch/function coverage).
Security Considerations
- Input validation for
exponent,precision, andoutputoptions prevents silent wrong output and unhandled native errors. - No credential or sensitive-data handling is involved; this is pure input-validation hardening.
Fix Steps
- Validate BigInt overflow — In
src/filesize.js, after the BigInt conversion at line 77-79, apply the sameisFinitecheck used at line 86 so overflowing BigInts throwTypeError. - Validate exponent — In
src/helpers.jscalculateExponent(), reject or clamp non-integer positive exponents before they index the power tables. - Normalize string exponent — In
src/helpers.jsresolveSymbol(), coerceeto a number before the stricte === 1check so string"1"matches the SI special case. - Fix negative fullform singular — In
src/helpers.jsdecorateResult(), useMath.abs(numericValue) === 1for the singular/plural check so-1uses the singular unit. - Preserve sign on round-to-zero — In
src/filesize.js, ensure negative values that round to 0 keep their sign consistently across the precision and non-precision paths. - Validate precision range — In
src/helpers.js, validateprecisionis 1-100 before callingtoPrecision()at line 195, throwing a cleanTypeErrorfor out-of-range values. - Validate output option — In
src/helpers.jsformatOutput(), throwTypeErrorfor invalidoutputvalues instead of silently falling through to string. - Strip scientific notation in non-precision path — Ensure
Number.MAX_VALUEand similar large values don't leake+notation. - Document or validate coercion contract — Decide whether
null,true,false,"",[1000], and hex/binary/octal strings are intended; if so, document them; if not, validate and throw. - Document option precedence — Lock in and document
standard>base,fullform>symbols,locale>separator, andfullformsfallback behavior. - Add regression tests — Add cases to
tests/unit/filesize.test.jsandtests/unit/filesize-helpers.test.jsfor every case in the table above. Verify withnpm run testandnpm run coverage.
Reactions are currently unavailable
Activity
avoidwork commentedon Sep 15, 2026
Fixed in #344.
chore: remove working task file for issue #343
fix: harden input validation and edge-case handling in filesize() (#344)