Skip to content

fix: harden input validation and edge-case handling in filesize() #343

Description

@avoidwork

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 via Number(arg) at src/filesize.js:77-79 but skips the isFinite check at line 86 that the number path applies. An overflowing BigInt becomes Infinity and is never rejected.
  • Float exponent: calculateExponent() at src/helpers.js:288-313 only handles e === -1/isNaN (line 289) and e < 0 (line 298). A positive non-integer exponent indexes the power lookup tables out of bounds, producing NaN.
  • String exponent: resolveSymbol() at src/helpers.js:356-369 uses strict e === 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:445 uses numericValue === 1, which fails for -1.
  • Sign loss on round-to-zero: neg is captured at src/filesize.js:94 before rounding. When a negative value rounds to 0, the sign is dropped in the non-precision path but preserved in the precision path (decorateResult line 409 prefixes the sign only for string values).
  • Precision out of range: value.toPrecision(precision) at src/helpers.js:195 is called with no range validation; precision: 101 throws a raw native RangeError.
  • Invalid output: formatOutput() at src/helpers.js:468-489 only checks output === ARRAY (line 469) and output === 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_VALUE leaks e+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.js for calculateExponent, resolveSymbol, applyRounding, and applyNumberFormatting.
  • Verify with npm run test and npm run coverage (currently 100% line/branch/function coverage).

Security Considerations

  • Input validation for exponent, precision, and output options prevents silent wrong output and unhandled native errors.
  • No credential or sensitive-data handling is involved; this is pure input-validation hardening.

Fix Steps

  1. Validate BigInt overflow — In src/filesize.js, after the BigInt conversion at line 77-79, apply the same isFinite check used at line 86 so overflowing BigInts throw TypeError.
  2. Validate exponent — In src/helpers.js calculateExponent(), reject or clamp non-integer positive exponents before they index the power tables.
  3. Normalize string exponent — In src/helpers.js resolveSymbol(), coerce e to a number before the strict e === 1 check so string "1" matches the SI special case.
  4. Fix negative fullform singular — In src/helpers.js decorateResult(), use Math.abs(numericValue) === 1 for the singular/plural check so -1 uses the singular unit.
  5. 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.
  6. Validate precision range — In src/helpers.js, validate precision is 1-100 before calling toPrecision() at line 195, throwing a clean TypeError for out-of-range values.
  7. Validate output option — In src/helpers.js formatOutput(), throw TypeError for invalid output values instead of silently falling through to string.
  8. Strip scientific notation in non-precision path — Ensure Number.MAX_VALUE and similar large values don't leak e+ notation.
  9. 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.
  10. Document option precedence — Lock in and document standard > base, fullform > symbols, locale > separator, and fullforms fallback behavior.
  11. Add regression tests — Add cases to tests/unit/filesize.test.js and tests/unit/filesize-helpers.test.js for every case in the table above. Verify with npm run test and npm run coverage.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions