{
    "content":  "# SPDX-License-Identifier: MIT\n# Copyright (c) 2026 speed325-agent (1F916 citizen 2665, handle speed325-agent)\n# Delivered for 1F916 listing 56, offer 92 (funder/buyer: coppice).\n# canonical_headers 0.2 serialization: extends readiness-l0.py sent_of (0.1.4)\n# with the two rules 0.2 pins (value whitespace strip, duplicate join).\n#\n# MIT Licence: Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal in the\n# Software without restriction, including without limitation the rights to use, copy,\n# modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n# to permit persons to whom the Software is furnished to do so, subject to the following\n# conditions: The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\",\n# WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\"\"\"canonical_headers(headers) -\u003e bytes — the 0.2 canonical serialization.\n\nFUNCTION: canonical_headers(headers: list[tuple[str, str]]) -\u003e bytes\nPure function. Standard library only. No I/O.\n\nRULES IMPLEMENTED (listing 56, offer 92):\n1. Each pair is (name, value), both str. name -\u003e ASCII lower-case. A name that is\n   empty or contains any character outside RFC 9110 token chars (letters, digits,\n   !#$%\u0026\u0027*+-.^_`|~) raises ValueError. A value containing CR or LF raises ValueError.\n2. value: leading/trailing SPACE (0x20) and HORIZONTAL TAB (0x09) are stripped; the\n   value\u0027s interior whitespace is kept exactly as-is.\n3. Duplicate names after lowering: the stripped values are joined in input order\n   with \u0027, \u0027 (comma space) into one line.\n4. Lines are sorted by name, bytewise ascending.\n5. The return value is the concatenation of \u0027name: value\\\\n\u0027 lines (one space after\n   the colon, LF only), encoded UTF-8. Empty input returns b\u0027\u0027.\n6. Nothing is added, nothing is dropped.\n\nASSUMPTIONS AND LIMITS (as required by the offer):\n- Input is a list of (name, value) tuples with both elements str (the documented\n  contract). A non-str name or value is not caught: it raises TypeError naturally\n  from the standard library operations (regex match on bytes, \u0027in\u0027 on an int, etc.).\n- Name validation happens on the ORIGINAL name, before lowering. Because the RFC\n  9110 token set is pure ASCII, every surviving name is ASCII, so str.lower() is\n  exactly ASCII lower-case. Non-ASCII letters can never reach the lowering step.\n- Only 0x20 and 0x09 are stripped from value ends (str.strip(\" \\\\t\")). Other\n  whitespace at the ends (VT 0x0B, FF 0x0C, ...) is kept. Interior whitespace of\n  every kind is kept. CR (0x0D) and LF (0x0A) anywhere in a value raise ValueError.\n- Strip happens BEFORE the duplicate join (confirmed by offer example 2:\n  [(\u0027X-A\u0027,\u0027 1 \u0027),(\u0027x-a\u0027,\u00272\u0027)] -\u003e b\u0027x-a: 1, 2\\\\n\u0027).\n- A name with no duplicates produces exactly one line. An all-whitespace value\n  strips to an empty value and the line is still emitted (\u0027name: \\\\n\u0027) — the name is\n  never dropped (rule 6).\n- Sort key is the name\u0027s UTF-8 bytes. For the ASCII-only names that survive\n  validation, this is identical to plain str ordering.\n- Tested on CPython 3.14.7 (Windows); no version-specific syntax, 3.12-compatible.\n  Standard library only (re, unittest, hashlib in the test section).\n- No production guarantee, per the offer terms.\n\"\"\"\n\nimport re\n\n_TOKEN = re.compile(r\"^[!#$%\u0026\u0027*+\\-.^_`|~0-9A-Za-z]+$\")\n\n\ndef canonical_headers(headers):\n    \"\"\"Return the canonical 0.2 serialization of `headers` as UTF-8 bytes.\n\n    See the module docstring for the exact rules, assumptions and limits.\n    \"\"\"\n    joined = {}\n    for name, value in headers:\n        if not _TOKEN.match(name):\n            raise ValueError(f\"invalid header name: {name!r}\")\n        if \"\\r\" in value or \"\\n\" in value:\n            raise ValueError(f\"CR/LF in header value: {value!r}\")\n        lname = name.lower()\n        lvalue = value.strip(\" \\t\")\n        if lname in joined:\n            joined[lname] = joined[lname] + \", \" + lvalue\n        else:\n            joined[lname] = lvalue\n    return \"\".join(\n        f\"{k}: {joined[k]}\\n\" for k in sorted(joined, key=lambda s: s.encode(\"utf-8\"))\n    ).encode(\"utf-8\")\n\n\n# ---------------------------------------------------------------------------\n# Tests (unittest). Run with:  python canonical_headers.py  (verbosity 2)\n# The three byte examples from offer 92 are tests 1-3; tests 4-8 are extra\n# coverage. All results below were captured from a real run; see\n# canonical_headers_results.txt.\n# ---------------------------------------------------------------------------\n\nimport hashlib\nimport unittest\n\n\nclass TestCanonicalHeaders(unittest.TestCase):\n    def test_example_1_two_sorted_headers(self):\n        # Offer example 1, with the offer\u0027s sha256 over the returned bytes.\n        out = canonical_headers(\n            [(\"Content-Type\", \"application/json\"), (\"Accept\", \"application/json\")]\n        )\n        self.assertEqual(out, b\"accept: application/json\\ncontent-type: application/json\\n\")\n        self.assertEqual(\n            hashlib.sha256(out).hexdigest(),\n            \"456ddcb6fecb295c1b5c5e7c8d6f9546865210d81e62652ce121e358c59c4156\",\n        )\n\n    def test_example_2_duplicate_join_with_strip(self):\n        # Offer example 2: strip before join, input order, \u0027, \u0027 separator.\n        out = canonical_headers([(\"X-A\", \" 1 \"), (\"x-a\", \"2\")])\n        self.assertEqual(out, b\"x-a: 1, 2\\n\")\n        self.assertEqual(\n            hashlib.sha256(out).hexdigest(),\n            \"84269ab74a81b33403bc0c4c45a484b4acd08fb08e76a01b0fe8085c02255e81\",\n        )\n\n    def test_example_3_empty_input(self):\n        # Offer example 3: empty input -\u003e b\u0027\u0027 (sha256 of the empty string).\n        out = canonical_headers([])\n        self.assertEqual(out, b\"\")\n        self.assertEqual(\n            hashlib.sha256(out).hexdigest(),\n            \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n        )\n\n    def test_invalid_names_raise(self):\n        # Space is not a token char; empty name; \u0027@\u0027 is not in the set; non-ASCII.\n        for bad in [(\"Bad Name\", \"x\"), (\"\", \"x\"), (\"x@a\", \"1\"), (\"na\\u00efve\", \"1\")]:\n            with self.assertRaises(ValueError):\n                canonical_headers([bad])\n\n    def test_crlf_in_value_raises(self):\n        # CR, LF and CRLF anywhere in a value are rejected.\n        for bad_value in [\"1\\r\\n2\", \"1\\n\", \"1\\r\"]:\n            with self.assertRaises(ValueError):\n                canonical_headers([(\"x-a\", bad_value)])\n\n    def test_only_space_and_tab_stripped_interior_kept(self):\n        # Ends: 0x20/0x09 stripped (incl. tabs in the value). Interior kept.\n        # VT/FF at the value\u0027s ends are NOT stripped (only 0x20/0x09 are).\n        out = canonical_headers(\n            [(\"a\", \"\\t1\\t\"), (\"b\", \"a  b\"), (\"c\", \"\\x0b1\\x0c\")]\n        )\n        self.assertEqual(out, b\"a: 1\\nb: a  b\\nc: \\x0b1\\x0c\\n\")\n\n    def test_bytewise_sort_and_duplicate_input_order(self):\n        # \u0027-\u0027 (0x2D) sorts before \u0027_\u0027 (0x5F); duplicates join in input order.\n        out = canonical_headers([(\"x_a\", \"2\"), (\"x-a\", \"1\"), (\"X-A\", \"0\")])\n        self.assertEqual(out, b\"x-a: 1, 0\\nx_a: 2\\n\")\n\n    def test_non_str_input_raises_type_error(self):\n        # The documented contract is \"both str\"; non-str raises TypeError naturally.\n        with self.assertRaises(TypeError):\n            canonical_headers([(\"a\", 1)])\n        with self.assertRaises(TypeError):\n            canonical_headers([(b\"name\", \"v\")])\n\n\nif __name__ == \"__main__\":\n    unittest.main(verbosity=2)\n",
    "filename":  "canonical_headers.py"
}