{"record":{"id":"f69701522bf4d013","repo":"apache/dubbo","slug":"unterminated-escape-sequence-at-index-i-of-s","errorCode":null,"errorMessage":"unterminated escape sequence at index ${i} of: ${str}","messagePattern":"unterminated escape sequence at index (.+?) of: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java","lineNumber":239,"sourceCode":"        return parseURLBody(encodedURLStr, decodedBody, parameters);\n    }\n\n    private static Map<String, String> parseEncodedParams(String str, int from) {\n        int len = str.length();\n        if (from >= len) {\n            return Collections.emptyMap();\n        }\n\n        TempBuf tempBuf = DECODE_TEMP_BUF.get();\n        Map<String, String> params = new HashMap<>();\n        int nameStart = from;\n        int valueStart = -1;\n        int i;\n        for (i = from; i < len; i++) {\n            char ch = str.charAt(i);\n            if (ch == '%') {\n                if (i + 3 > len) {\n                    throw new IllegalArgumentException(\"unterminated escape sequence at index \" + i + \" of: \" + str);\n                }\n                ch = (char) decodeHexByte(str, i + 1);\n                i += 2;\n            }\n\n            switch (ch) {\n                case '=':\n                    if (nameStart == i) {\n                        nameStart = i + 1;\n                    } else if (valueStart < nameStart) {\n                        valueStart = i + 1;\n                    }\n                    break;\n                case ';':\n                case '&':\n                    addParam(str, true, nameStart, valueStart, i - 2, params, tempBuf);\n                    nameStart = i + 1;\n                    break;","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/apache/dubbo/blob/3a3043227f5571d25eb2889de5bca22f2914843b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java#L221-L257","documentation":"Thrown by URLStrParser.parseEncodedParams while scanning a percent-encoded query parameter string. When a '%' is encountered but fewer than two hex digits remain before the end of the string (i + 3 > len), the escape sequence is incomplete and cannot be decoded. This protects the hex decoder from reading past the buffer and surfaces malformed URL-encoded input explicitly.","triggerScenarios":"Passing an encoded parameter string ending in a stray or truncated percent escape to URLStrParser.parseEncodedParams / parseEncodedStr, e.g. \"k=v%2\", \"token=abc%\", or a value containing a lone '%' that was not URL-encoded. Also triggered by manual string concatenation of encoded params that drops trailing hex digits.","commonSituations":"Hand-built query strings where '%' was used literally instead of \"%25\"; truncation of an encoded URL by loggers/proxies; custom encoders that emit '%' for non-ASCII without following hex; corrupt serialized Dubbo URLs read from registry/zookeeper.","solutions":["Find the malformed parameter substring reported in the message and fix the truncation, e.g. \"k=v%2\" -> \"k=v%20\".","If a literal '%' is intended in a value, URL-encode it as \"%25\" before building the query string.","Use a well-tested encoder (java.net.URLEncoder or Dubbo's URL.encode) instead of manually inserting '%'.","If the string comes from an external store (registry/DB), re-encode or re-serialize the affected URL entry."],"exampleFix":"// before\nString params = \"version=1.0%&timeout=1000\";  // stray %\nMap<String,String> p = URLStrParser.parseEncodedParams(params, 0);\n// after\nString params = \"version=1.0%25&timeout=1000\";  // % encoded as %25","handlingStrategy":"validation","validationCode":"// Reject parameter strings with a truncated percent escape before parsing\nprivate static final java.util.regex.Pattern BAD_ESCAPE =\n    java.util.regex.Pattern.compile(\"%(?![0-9A-Fa-f]{2})\");\n\nboolean escapesAreComplete(String encodedParams) {\n    return !BAD_ESCAPE.matcher(encodedParams).find();\n}\n// if (escapesAreComplete(s)) URLStrParser.parseEncodedParams(s, 0); else fix(s);","typeGuard":null,"tryCatchPattern":"try {\n    Map<String,String> p = URLStrParser.parseEncodedParams(s, 0);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"unterminated escape sequence\")) {\n        // s is malformed; log, repair, or reject the offending parameter string\n    } else throw e;\n}","preventionTips":["Never build percent-encoded strings by hand; use java.net.URLEncoder or Dubbo's URL.encode.","If you need a literal '%' in a value, encode it as \"%25\".","Validate encoded input with a regex like \"^(?:[^%]|%[0-9A-Fa-f]{2})*$\" before decoding.","Treat truncated escapes from external stores (registry/DB) as data corruption and re-serialize."],"tags":["url-parsing","encoding","validation","dubbo-common"],"backgroundTag":null,"analyzedSha":"3a3043227f5571d25eb2889de5bca22f2914843b","analyzedAt":"2026-08-14T00:43:19.853Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}