Reading a page: cutting fields and the page by UTF-16 units can split an emoji, and Postulo refuses the capture #25
Labels
No labels
bug
documentation
enhancement
performance
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-chromium#25
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-chromium at
7998de372, readingsrc/lib/parse.jsand checking the cut against the core'sJobPostingDataat postulo 95348908d.The extension cuts every text field, and the slimmed page, with
String.prototype.slice, which counts UTF-16 code units. When the cut falls inside a character outside the Basic Multilingual Plane (an emoji, some CJK), the kept string ends in a lone high surrogate.JSON.stringifysends it as\ud83d, and the core's pydantic schemas refuse any string holding one, so the whole capture comes back refused with a message the person cannot relate to anything on screen.src/lib/parse.js:694,tidy():value.trim().slice(0, LIMITS[field] ?? (field === "description" ? Infinity : MAX_FIELD))src/lib/parse.js:698,tidy():tidied.description.slice(0, MAX_DESCRIPTION)src/lib/parse.js:663-664,pageMetadata():title.slice(0, MAX_FIELD)and the site namesrc/lib/parse.js:862,slimPage():html.slice(0, MAX_PAGE)tidy()500 units long, its last unit0xd83d(checked with node against the snapshot). On the core,JobPostingData.model_validate({"title": "Engineer \ud83d"})raisesstring_unicode("Input should be a valid string, unable to parse raw data as a unicode string"); a description does the same, andCaptureIn.htmlandCorrectionsInare plainstrfields validated the same way, so a page cut atMAX_PAGEinside a surrogate pair is refused too. The outbox marks the capturerefused(src/lib/outbox.js:75-76) and the popup shows that sentence against the field or the page.MAX_PAGEafter slimming fails the same way whatever the person corrects.0xd800–0xdbff), and use it at all four places. Counting code points also matches the core'smax_length, which counts them, so a field the extension keeps is never longer than the core accepts.Done when
test/parse.test.mjshas a case wheretidy()cuts a title of 499 characters plus an emoji, and one whereslimPage()cuts a page whoseMAX_PAGEboundary falls inside a surrogate pair, both asserting that the result contains no lone surrogate (/[\ud800-\udbff](?![\udc00-\udfff])/does not match).