Skip to content

fix(isByteLength): handle unpaired surrogates instead of throwing#2742

Open
sarathfrancis90 wants to merge 1 commit into
validatorjs:masterfrom
sarathfrancis90:fix-isbytelength-unpaired-surrogates
Open

fix(isByteLength): handle unpaired surrogates instead of throwing#2742
sarathfrancis90 wants to merge 1 commit into
validatorjs:masterfrom
sarathfrancis90:fix-isbytelength-unpaired-surrogates

Conversation

@sarathfrancis90

Copy link
Copy Markdown

isByteLength throws a URIError instead of returning a boolean when the input contains an unpaired surrogate:

validator.isByteLength('\uD835', { max: 3 }); // URIError: URI malformed
validator.isEmail('\uD835');                  // URIError (isEmail runs the local part through isByteLength)

encodeURI() rejects unpaired surrogates, so the byte-count line blows up. Every other validator just returns false on this kind of input, so the throw is both surprising and a small hazard when validating untrusted strings.

The fix replaces unpaired surrogates with U+FFFD before encoding — that's the same substitution a UTF-8 encoder makes (3 bytes), so the byte count stays correct and valid surrogate pairs are left exactly as they were. I added a regression test for both isByteLength and isEmail; the full suite passes.

isByteLength() computes the byte count with encodeURI(), which throws a
URIError on a string containing an unpaired surrogate (e.g. '\uD835').
isEmail() runs the local part through isByteLength(), so it threw too,
while every other validator just returns false on such input.

Replace unpaired surrogates with U+FFFD (3 UTF-8 bytes, the same
substitution a UTF-8 encoder makes) before encoding, leaving valid
surrogate pairs untouched so the byte count is unchanged for them.
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7fdc788) to head (9492b89).

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #2742   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          114       114           
  Lines         2587      2595    +8     
  Branches       656       659    +3     
=========================================
+ Hits          2587      2595    +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sarathfrancis90

Copy link
Copy Markdown
Author

Flagging that this overlaps with #2822, which fixes the same encodeURI throw. The semantics end up identical — an unpaired surrogate counted as the 3-byte U+FFFD — the difference is that I sanitize the string and keep encodeURI, while #2822 replaces it with a direct UTF-8 byte count. That one also avoids building the intermediate encoded string, so it is arguably the better end state.

This one has been open since early June and is green, but I have no stake in which lands. Happy to close it if you prefer the #2822 approach, or to rebase mine. I just did not want two PRs sitting on the same function without anyone saying so.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant