{"record":{"id":"047d215c76a6c6b2","repo":"perwendel/spark","slug":"hex-c","errorCode":null,"errorMessage":"!hex ${c}","messagePattern":"!hex \\$\\{c\\}","errorType":"exception","errorClass":"java.lang.NumberFormatException","httpStatus":null,"severity":"error","filePath":"src/main/java/spark/utils/urldecoding/TypeUtil.java","lineNumber":209,"sourceCode":"            c = '0' + bi % base;\n            if (c > '9') {\n                c = 'a' + (c - '0' - 10);\n            }\n            buf.append((char) c);\n        }\n        return buf.toString();\n    }\n\n    /* ------------------------------------------------------------ */\n\n    /**\n     * @param c An ASCII encoded character 0-9 a-f A-F\n     * @return The byte value of the character 0-16.\n     */\n    public static int convertHexDigit(char c) {\n        int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);\n        if (d < 0 || d > 15) {\n            throw new NumberFormatException(\"!hex \" + c);\n        }\n        return d;\n    }\n\n    /* ------------------------------------------------------------ */\n\n    /**\n     * @param c An ASCII encoded character 0-9 a-f A-F\n     * @return The byte value of the character 0-16.\n     */\n    public static int convertHexDigit(int c) {\n        int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);\n        if (d < 0 || d > 15) {\n            throw new NumberFormatException(\"!hex \" + c);\n        }\n        return d;\n    }\n","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/perwendel/spark/blob/1973e402f5d4c1442ad34a1d38ed0758079f7773/src/main/java/spark/utils/urldecoding/TypeUtil.java#L191-L227","documentation":"TypeUtil.convertHexDigit(char) maps an ASCII hex character (0-9, a-f, A-F) to its 0-15 value; when the bit arithmetic yields a value outside 0..15 the character was not a hex digit and a NumberFormatException prefixed with '!hex ' is thrown.","triggerScenarios":"Passing a char that is not an ASCII hex digit, e.g. 'g', '%', or any non-ASCII character, into convertHexDigit(char) — usually from a malformed %XX escape sequence in a URL.","commonSituations":"Users hitting endpoints with hand-written percent-escapes like '%zz'; truncated escape sequences ('%2' followed by a letter); probes with random bytes in paths.","solutions":["Validate the two characters following '%' are hex digits before decoding (or catch the NumberFormatException).","Catch NumberFormatException at the decode layer and return HTTP 400 for the malformed URI.","Percent-encode strings properly on the client side (java.net.URLEncoder / encodeURIComponent) so only valid escapes are produced.","Log the offending character to help identify the misbehaving client."],"exampleFix":"// before\nint hi = TypeUtil.convertHexDigit(s.charAt(i + 1));\n// after\nchar c1 = s.charAt(i + 1);\nif (!isHexDigit(c1)) throw new IllegalArgumentException(\"Bad % escape at \" + i);\nint hi = TypeUtil.convertHexDigit(c1);","handlingStrategy":"validation","validationCode":"boolean isHexDigit(char c) {\n    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');\n}","typeGuard":"boolean isHexChar(char c) { return Character.digit(c, 16) >= 0; }","tryCatchPattern":"try {\n    int hi = TypeUtil.convertHexDigit(s.charAt(i + 1));\n} catch (NumberFormatException e) {\n    throw new IllegalArgumentException(\"Malformed %XX escape in URI\");\n}","preventionTips":["Check both characters after '%' are hex digits before decoding.","Encode URIs client-side with URLEncoder/encodeURIComponent.","Return HTTP 400 for malformed escapes instead of letting the exception escape.","Log offending characters for client debugging."],"tags":["numberformat","hex","url-decoding"],"backgroundTag":"invalid-argument-format","analyzedSha":"1973e402f5d4c1442ad34a1d38ed0758079f7773","analyzedAt":"2026-09-10T14:38:22.866Z","contentChangedAt":"2026-09-10T14:38:22.866Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}