Europass import: a JSON address with no country, an out-of-range year or an over-long number ends in a server error #634
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#634
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 at
95348908d, running the Europass reader on files with missing and out-of-range values.Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atplugins/europass/reader.py:134-136(_json_country_code, called from:656; its XML twin_country_codeis at:122-131) and:578-587(read_json), and atplugins/europass/common.py:68-77(_make_date) and:88-94(_orcid_from).resume/views.py:441-461is unchanged.The reader's docstring promises that "no key is assumed to be present, and no value is assumed to have the type it should", and the import view catches only
ImportRefused. Four kinds of input raise something else, so the upload answers 500 and the person is told nothing.Where:
resume/views.py:441-461(EuropassImportView.post, whose only handler isexcept base.ImportRefused), and inplugins/europass/reader.py::180-182(_json_country_code), called from:780;:704-711(read_json);:156-165(_make_date);:197-203(_orcid_from).What happens:
_json_country_codecalls_obj(address.get("Country")), which isNonewhen the address has noCountry, or when it isnullor a string, and thenNone.get("Code"). A SkillsPassport JSON whose address is{"Contact": {"Municipality": "Lisboa"}}raisesAttributeError: 'NoneType' object has no attribute 'get'atreader.py:182. The XML twin,_country_code(:168-177), checks forNonefirst.read_jsoncatchesjson.JSONDecodeErrorandRecursionError. A number longer than Python's integer conversion limit raises a plainValueErrorfromjson.loads("Exceeds the limit (4300 digits) for integer string conversion")._make_datecatchesTypeErrorandValueError. A JSON year ofInfinityor1e400(whichjson.loadsaccepts) raisesOverflowErrorfromint()at:157. A year too large for a C integer, for example99999999999, raisesOverflowErrorfromdt.dateat:163; this one is reachable from the XML as well (<From year="99999999999"/>).urlsplitrejects._orcid_fromcatchesValidationError; a website containingorcid.orgwith an unbalanced[in its host raisesValueError: Invalid IPv6 URLfromidentifiers.clean, in the JSON and the XML alike.All four were run against the snapshot. The first is the plausible one in a real file; the others need an unusual or hand-edited file. Nothing is written in any of them.
Why it matters: a file that is half right should import the half that is right, as the docstring says. A 500 gives the person no sentence to act on.
Fix: in
_json_country_code, use_obj(...) or {}(or_json_text(address, "Country", "Code")). Inread_json, catchValueError(which coversJSONDecodeError) and refuse with the existing "not readable JSON" sentence. In_make_date, catchOverflowErrorin bothtryblocks. In_orcid_from, catchValueErroras well asValidationError.Two other findings of this review touch the same paths: the import view catching only
ImportRefused(a catch-all aroundread()would turn these into a message), andcore/identifiers.pylettingurlsplit'sValueErrorthrough (which is the root of case 4). These fixes make the reader keep its own promise whatever happens to those.Done when
tests/test_europass.pyhas one test per case (an address without a country, a 5000-digit number, a year ofInfinity, a year of99999999999in JSON and in XML, a website with an unbalanced bracket). Each fails today with an exception other thanImportRefused; afterwards each reads what it can or is refused with a sentence.