{"record":{"id":"366c8593b63960cd","repo":"HMCL-dev/HMCL","slug":"png-spec-palette-chunk-length-must-be-divisible-b","errorCode":null,"errorMessage":"png spec: palette chunk length must be divisible by 3: %d","messagePattern":"png spec: palette chunk length must be divisible by 3: (.+?)","errorType":"exception","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java","lineNumber":150,"sourceCode":"            processor.processTransparency(paletteBytes, 0, dataLength);\n        }\n    }\n\n    @Override\n    public void readBackgroundChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (!seenHeader) {\n            throw new PngIntegrityException(\"bKGD chunk received before IHDR chunk\");\n        }\n        // TODO\n        //processor.processBackground(PngBackground.from(source, dataLength);\n        source.skip(dataLength);\n    }\n\n    @Override\n    public void readPaletteChunk(PngSource source, int dataLength) throws IOException, PngException {\n\n        if (dataLength % 3 != 0) {\n            throw new PngIntegrityException(String.format(\"png spec: palette chunk length must be divisible by 3: %d\", dataLength));\n        }\n        // TODO: can check if colour type matches palette type, or if any palette received before (overkill?)\n\n        if (source.supportsByteAccess()) {\n            processor.processPalette(source.getBytes(), source.tell(), dataLength);\n            source.skip(dataLength);\n        } else {\n            byte[] paletteBytes = new byte[dataLength];\n            //ByteStreams.readFully(source.getBis(), paletteBytes);\n            source.getDis().readFully(paletteBytes);\n            processor.processPalette(paletteBytes, 0, dataLength);\n        }\n    }\n\n    @Override\n    public void readImageDataChunk(PngSource source, int dataLength) throws PngException, IOException {\n\n        if (idatCount == 0 && apngSequenceExpect == 0) {","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java#L132-L168","documentation":"This validation in readPaletteChunk rejects a PLTE chunk whose data length is not divisible by 3, mirroring PngPalette's own check. Palette entries are fixed 3-byte RGB triplets, so a bad length means the chunk is corrupt and the colour table cannot be constructed.","triggerScenarios":"readChunk dispatches a PLTE chunk whose dataLength % 3 != 0, typically from file truncation or corruption.","commonSituations":"Corrupted downloads, images manipulated by buggy tools, fuzzed inputs.","solutions":["Re-encode the PNG with a compliant encoder to produce a valid PLTE.","Run pngcheck on the file to confirm the chunk is malformed before decoding.","Catch PngIntegrityException and treat the image as corrupt."],"exampleFix":"// before\nprocessChunk(PLTE, source, dataLength);\n// after\nif (isPaletteChunk(code) && dataLength % 3 != 0) {\n    throw new PngIntegrityException(\"bad PLTE length \" + dataLength);\n}\nprocessChunk(PLTE, source, dataLength);","handlingStrategy":"try-catch","validationCode":"if (code == PLTE && dataLength % 3 != 0) throw new IllegalStateException(\"PLTE length must be multiple of 3\");","typeGuard":null,"tryCatchPattern":"try { reader.readPaletteChunk(source, dataLength); } catch (PngIntegrityException e) { failDecode(\"malformed PLTE\"); }","preventionTips":["Pre-screen files with pngcheck.","Re-encode images with a compliant encoder.","Guard chunk lengths during pre-scan.","Avoid third-party tools that truncate chunks."],"tags":["png","image-decoding","validation","palette"],"backgroundTag":"invalid-argument-format","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}