more robust PNG loader, fuzz tested

This commit is contained in:
jangko 2020-05-28 21:10:44 +07:00
parent d7d413c837
commit 7ea9a7a941
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
11 changed files with 177 additions and 203 deletions

53
.gitignore vendored
View File

@ -1,52 +1 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*.exe
nimcache
bug
tester/temp.png
tester/rainbow.png
tester/test
tester/testCodec
tester/testSuite
tests/corpus_dir

View File

@ -25,7 +25,7 @@
# part of nimPDF sister projects
#-------------------------------------
import streams, endians, tables, hashes, math
import streams, endians, tables, hashes, math, typetraits
import nimPNG/[buffer, nimz, filters]
import strutils
@ -69,7 +69,7 @@ type
PNGHeader = ref object of PNGChunk
width, height: int #range[1..0x7FFFFFFF]
bitDepth: int
colorType: PNGcolorType
colorType: PNGColorType
compressionMethod: int
filterMethod: int
interlaceMethod: PNGInterlace
@ -181,7 +181,7 @@ type
frameDataPos: int
PNGColorMode* = ref object
colorType*: PNGcolorType
colorType*: PNGColorType
bitDepth*: int
paletteSize*: int
palette*: seq[RGBA8]
@ -233,6 +233,8 @@ type
DataBuf = Buffer[string]
PNGError* = object of CatchableError
proc signatureMaker(): string {. compiletime .} =
const signatureBytes = [137, 80, 78, 71, 13, 10, 26, 10]
result = ""
@ -293,9 +295,8 @@ const
fcTL = makeChunkType("fcTL")
fdAT = makeChunkType("fdAT")
proc PNGError(msg: string): ref Exception =
new(result)
result.msg = msg
template PNGFatal(msg: string): untyped =
newException(PNGError, msg)
proc newColorMode*(colorType=LCT_RGBA, bitDepth=8): PNGColorMode =
new(result)
@ -342,7 +343,7 @@ proc `==`(a, b: PNGColorMode): bool =
proc `!=`(a, b: PNGColorMode): bool = not (a == b)
proc readInt32(s: PNGChunk): int =
if s.pos + 4 > s.data.len: raise PNGError("index out of bound 4")
if s.pos + 4 > s.data.len: raise PNGFatal("index out of bound 4")
result = ord(s.data[s.pos]) shl 8
result = (result + ord(s.data[s.pos + 1])) shl 8
result = (result + ord(s.data[s.pos + 2])) shl 8
@ -350,7 +351,7 @@ proc readInt32(s: PNGChunk): int =
inc(s.pos, 4)
proc readInt16(s: PNGChunk): int =
if s.pos + 2 > s.data.len: raise PNGError("index out of bound 2")
if s.pos + 2 > s.data.len: raise PNGFatal("index out of bound 2")
result = ord(s.data[s.pos]) shl 8
result = result + ord(s.data[s.pos + 1])
inc(s.pos, 2)
@ -373,12 +374,18 @@ proc readInt32BE(s: Stream): int =
result = tmp
proc readByte(s: PNGChunk): int =
if s.pos + 1 > s.data.len: raise PNGError("index out of bound 1")
if s.pos + 1 > s.data.len: raise PNGFatal("index out of bound 1")
result = ord(s.data[s.pos])
inc s.pos
template readEnum(s: PNGChunk, T: type): untyped =
let typ = s.readByte.int
if typ < low(T).int or typ > high(T).int:
raise PNGFatal("Wrong " & T.name & " value " & $typ)
T(typ)
proc setPosition(s: PNGChunk, pos: int) =
if pos < 0 or pos > s.data.len: raise PNGError("set position error")
if pos < 0 or pos > s.data.len: raise PNGFatal("set position error")
s.pos = pos
proc hasChunk*(png: PNG, chunkType: PNGChunkType): bool =
@ -399,7 +406,7 @@ proc apngGetChunk*(png: PNG, chunkType: PNGChunkType): PNGChunk =
for c in png.apngChunks:
if c.chunkType == chunkType: return c
proc bitDepthAllowed(colorType: PNGcolorType, bitDepth: int): bool =
proc bitDepthAllowed(colorType: PNGColorType, bitDepth: int): bool =
case colorType
of LCT_GREY : result = bitDepth in {1, 2, 4, 8, 16}
of LCT_PALETTE: result = bitDepth in {1, 2, 4, 8}
@ -410,19 +417,19 @@ method parseChunk(chunk: PNGChunk, png: PNG): bool {.base, gcsafe.} = true
method validateChunk(header: PNGHeader, png: PNG): bool =
if header.width < 1 or header.width > 0x7FFFFFFF:
raise PNGError("image width not allowed: " & $header.width)
raise PNGFatal("image width not allowed: " & $header.width)
if header.height < 1 or header.height > 0x7FFFFFFF:
raise PNGError("image width not allowed: " & $header.height)
raise PNGFatal("image width not allowed: " & $header.height)
if header.colorType notin {LCT_GREY, LCT_RGB, LCT_PALETTE, LCT_GREY_ALPHA, LCT_RGBA}:
raise PNGError("color type not allowed: " & $int(header.colorType))
raise PNGFatal("color type not allowed: " & $int(header.colorType))
if not bitDepthAllowed(header.colorType, header.bitDepth):
raise PNGError("bit depth not allowed: " & $header.bitDepth)
raise PNGFatal("bit depth not allowed: " & $header.bitDepth)
if header.compressionMethod != 0:
raise PNGError("unsupported compression method")
raise PNGFatal("unsupported compression method")
if header.filterMethod != 0:
raise PNGError("unsupported filter method")
raise PNGFatal("unsupported filter method")
if header.interlaceMethod notin {IM_NONE, IM_INTERLACED}:
raise PNGError("unsupported interlace method")
raise PNGFatal("unsupported interlace method")
result = true
method parseChunk(chunk: PNGHeader, png: PNG): bool =
@ -430,7 +437,7 @@ method parseChunk(chunk: PNGHeader, png: PNG): bool =
chunk.width = chunk.readInt32()
chunk.height = chunk.readInt32()
chunk.bitDepth = chunk.readByte()
chunk.colorType = PNGcolorType(chunk.readByte())
chunk.colorType = chunk.readEnum(PNGColorType)
chunk.compressionMethod = chunk.readByte()
chunk.filterMethod = chunk.readByte()
chunk.interlaceMethod = PNGInterlace(chunk.readByte())
@ -438,7 +445,7 @@ method parseChunk(chunk: PNGHeader, png: PNG): bool =
method parseChunk(chunk: PNGPalette, png: PNG): bool =
let paletteSize = chunk.length div 3
if paletteSize > 256: raise PNGError("palette size to big")
if paletteSize > 256: raise PNGFatal("palette size to big")
newSeq(chunk.palette, paletteSize)
for px in mitems(chunk.palette):
px.r = chr(chunk.readByte())
@ -447,7 +454,7 @@ method parseChunk(chunk: PNGPalette, png: PNG): bool =
px.a = chr(255)
result = true
proc numChannels(colorType: PNGcolorType): int =
proc numChannels(colorType: PNGColorType): int =
case colorType
of LCT_GREY: result = 1
of LCT_RGB : result = 3
@ -455,7 +462,7 @@ proc numChannels(colorType: PNGcolorType): int =
of LCT_GREY_ALPHA: result = 2
of LCT_RGBA: result = 4
proc LCTBPP(colorType: PNGcolorType, bitDepth: int): int =
proc LCTBPP(colorType: PNGColorType, bitDepth: int): int =
# bits per pixel is amount of channels * bits per channel
result = numChannels(colorType) * bitDepth
@ -473,7 +480,7 @@ proc idatRawSize(w, h: int, header: PNGHeader): int =
proc getRawSize(w, h: int, color: PNGColorMode): int =
result = (w * h * getBPP(color) + 7) div 8
#proc getRawSizeLct(w, h: int, colorType: PNGcolorType, bitDepth: int): int =
#proc getRawSizeLct(w, h: int, colorType: PNGColorType, bitDepth: int): int =
# result = (w * h * LCTBPP(colorType, bitDepth) + 7) div 8
method validateChunk(chunk: PNGData, png: PNG): bool =
@ -495,7 +502,7 @@ method validateChunk(chunk: PNGData, png: PNG): bool =
if w > 1: predict += idatRawSize((w + 0) div 2, (h + 1) div 2, header) + (h + 1) div 2
predict += idatRawSize((w + 0) div 1, (h + 0) div 2, header) + (h + 0) div 2
if chunk.idat.len != predict: raise PNGError("Decompress size doesn't match predict")
if chunk.idat.len != predict: raise PNGFatal("Decompress size doesn't match predict")
result = true
method parseChunk(chunk: PNGData, png: PNG): bool =
@ -513,23 +520,23 @@ method parseChunk(chunk: PNGTrans, png: PNG): bool =
if plte == nil: return false
# error: more alpha values given than there are palette entries
if chunk.length > plte.palette.len:
raise PNGError("more alpha value than palette entries")
raise PNGFatal("more alpha value than palette entries")
#can contain fewer values than palette entries
for i in 0..chunk.length-1: plte.palette[i].a = chr(chunk.readByte())
elif header.colorType == LCT_GREY:
# error: this chunk must be 2 bytes for greyscale image
if chunk.length != 2: raise PNGError("tRNS must be 2 bytes")
if chunk.length != 2: raise PNGFatal("tRNS must be 2 bytes")
chunk.keyR = chunk.readInt16()
chunk.keyG = chunk.keyR
chunk.keyB = chunk.keyR
elif header.colorType == LCT_RGB:
# error: this chunk must be 6 bytes for RGB image
if chunk.length != 6: raise PNGError("tRNS must be 6 bytes")
if chunk.length != 6: raise PNGFatal("tRNS must be 6 bytes")
chunk.keyR = chunk.readInt16()
chunk.keyG = chunk.readInt16()
chunk.keyB = chunk.readInt16()
else:
raise PNGError("tRNS chunk not allowed for other color models")
raise PNGFatal("tRNS chunk not allowed for other color models")
result = true
@ -537,19 +544,19 @@ method parseChunk(chunk: PNGBackground, png: PNG): bool =
var header = PNGHeader(png.getChunk(IHDR))
if header.colorType == LCT_PALETTE:
# error: this chunk must be 1 byte for indexed color image
if chunk.length != 1: raise PNGError("bkgd must be 1 byte")
if chunk.length != 1: raise PNGFatal("bkgd must be 1 byte")
chunk.bkgdR = chunk.readByte()
chunk.bkgdG = chunk.bkgdR
chunk.bkgdB = chunk.bkgdR
elif header.colorType in {LCT_GREY, LCT_GREY_ALPHA}:
# error: this chunk must be 2 bytes for greyscale image
if chunk.length != 2: raise PNGError("bkgd must be 2 byte")
if chunk.length != 2: raise PNGFatal("bkgd must be 2 byte")
chunk.bkgdR = chunk.readInt16()
chunk.bkgdG = chunk.bkgdR
chunk.bkgdB = chunk.bkgdR
elif header.colorType in {LCT_RGB, LCT_RGBA}:
# error: this chunk must be 6 bytes for greyscale image
if chunk.length != 6: raise PNGError("bkgd must be 6 byte")
if chunk.length != 6: raise PNGFatal("bkgd must be 6 byte")
chunk.bkgdR = chunk.readInt16()
chunk.bkgdG = chunk.readInt16()
chunk.bkgdB = chunk.readInt16()
@ -563,17 +570,17 @@ proc initChunk(chunk: PNGChunk, chunkType: PNGChunkType, data: string, crc: uint
chunk.pos = 0
method validateChunk(chunk: PNGTime, png: PNG): bool =
if chunk.year < 0 or chunk.year > 65535: raise PNGError("invalid year range[0..65535]")
if chunk.month < 1 or chunk.month > 12: raise PNGError("invalid month range[1..12]")
if chunk.day < 1 or chunk.day > 31: raise PNGError("invalid day range[1..32]")
if chunk.hour < 0 or chunk.hour > 23: raise PNGError("invalid hour range[0..23]")
if chunk.minute < 0 or chunk.minute > 59: raise PNGError("invalid minute range[0..59]")
if chunk.year < 0 or chunk.year > 65535: raise PNGFatal("invalid year range[0..65535]")
if chunk.month < 1 or chunk.month > 12: raise PNGFatal("invalid month range[1..12]")
if chunk.day < 1 or chunk.day > 31: raise PNGFatal("invalid day range[1..32]")
if chunk.hour < 0 or chunk.hour > 23: raise PNGFatal("invalid hour range[0..23]")
if chunk.minute < 0 or chunk.minute > 59: raise PNGFatal("invalid minute range[0..59]")
#to allow for leap seconds
if chunk.second < 0 or chunk.second > 60: raise PNGError("invalid second range[0..60]")
if chunk.second < 0 or chunk.second > 60: raise PNGFatal("invalid second range[0..60]")
result = true
method parseChunk(chunk: PNGTime, png: PNG): bool =
if chunk.length != 7: raise PNGError("tIME must be 7 bytes")
if chunk.length != 7: raise PNGFatal("tIME must be 7 bytes")
chunk.year = chunk.readInt16()
chunk.month = chunk.readByte()
chunk.day = chunk.readByte()
@ -583,7 +590,7 @@ method parseChunk(chunk: PNGTime, png: PNG): bool =
result = true
method parseChunk(chunk: PNGPhys, png: PNG): bool =
if chunk.length != 9: raise PNGError("pHYs must be 9 bytes")
if chunk.length != 9: raise PNGFatal("pHYs must be 9 bytes")
chunk.physX = chunk.readInt32()
chunk.physY = chunk.readInt32()
chunk.unit = chunk.readByte()
@ -591,13 +598,13 @@ method parseChunk(chunk: PNGPhys, png: PNG): bool =
method validateChunk(chunk: PNGText, png: PNG): bool =
if(chunk.keyword.len < 1) or (chunk.keyword.len > 79):
raise PNGError("keyword too short or too long")
raise PNGFatal("keyword too short or too long")
result = true
method parseChunk(chunk: PNGText, png: PNG): bool =
var len = 0
while(len < chunk.length) and (chunk.data[len] != chr(0)): inc len
if(len < 1) or (len > 79): raise PNGError("keyword too short or too long")
if(len < 1) or (len > 79): raise PNGFatal("keyword too short or too long")
chunk.keyword = chunk.data.substr(0, len)
var textBegin = len + 1 # skip keyword null terminator
@ -606,17 +613,17 @@ method parseChunk(chunk: PNGText, png: PNG): bool =
method validateChunk(chunk: PNGZtxt, png: PNG): bool =
if(chunk.keyword.len < 1) or (chunk.keyword.len > 79):
raise PNGError("keyword too short or too long")
raise PNGFatal("keyword too short or too long")
result = true
method parseChunk(chunk: PNGZtxt, png: PNG): bool =
var len = 0
while(len < chunk.length) and (chunk.data[len] != chr(0)): inc len
if(len < 1) or (len > 79): raise PNGError("keyword too short or too long")
if(len < 1) or (len > 79): raise PNGFatal("keyword too short or too long")
chunk.keyword = chunk.data.substr(0, len)
var compMethod = ord(chunk.data[len + 1]) # skip keyword null terminator
if compMethod != 0: raise PNGError("unsupported comp method")
if compMethod != 0: raise PNGFatal("unsupported comp method")
var nz = nzInflateInit(chunk.data.substr(len + 2))
nz.ignoreAdler32 = PNGDecoder(png.settings).ignoreAdler32
@ -626,22 +633,22 @@ method parseChunk(chunk: PNGZtxt, png: PNG): bool =
method validateChunk(chunk: PNGItxt, png: PNG): bool =
if(chunk.keyword.len < 1) or (chunk.keyword.len > 79):
raise PNGError("keyword too short or too long")
raise PNGFatal("keyword too short or too long")
result = true
method parseChunk(chunk: PNGItxt, png: PNG): bool =
if chunk.length < 5: raise PNGError("iTXt len too short")
if chunk.length < 5: raise PNGFatal("iTXt len too short")
var len = 0
while(len < chunk.length) and (chunk.data[len] != chr(0)): inc len
if(len + 3) >= chunk.length: raise PNGError("no null termination char, corrupt?")
if(len < 1) or (len > 79): raise PNGError("keyword too short or too long")
if(len + 3) >= chunk.length: raise PNGFatal("no null termination char, corrupt?")
if(len < 1) or (len > 79): raise PNGFatal("keyword too short or too long")
chunk.keyword = chunk.data.substr(0, len)
var compressed = ord(chunk.data[len + 1]) == 1 # skip keyword null terminator
var compMethod = ord(chunk.data[len + 2])
if compMethod != 0: raise PNGError("unsupported comp method")
if compMethod != 0: raise PNGFatal("unsupported comp method")
len = 0
var i = len + 3
@ -669,12 +676,12 @@ method parseChunk(chunk: PNGItxt, png: PNG): bool =
result = true
method parseChunk(chunk: PNGGamma, png: PNG): bool =
if chunk.length != 4: raise PNGError("invalid gAMA length")
if chunk.length != 4: raise PNGFatal("invalid gAMA length")
chunk.gamma = chunk.readInt32()
result = true
method parseChunk(chunk: PNGChroma, png: PNG): bool =
if chunk.length != 32: raise PNGError("invalid Chroma length")
if chunk.length != 32: raise PNGFatal("invalid Chroma length")
chunk.whitePointX = chunk.readInt32()
chunk.whitePointY = chunk.readInt32()
chunk.redX = chunk.readInt32()
@ -686,23 +693,23 @@ method parseChunk(chunk: PNGChroma, png: PNG): bool =
result = true
method parseChunk(chunk: PNGStandarRGB, png: PNG): bool =
if chunk.length != 1: raise PNGError("invalid sRGB length")
if chunk.length != 1: raise PNGFatal("invalid sRGB length")
chunk.renderingIntent = chunk.readByte()
result = true
method validateChunk(chunk: PNGICCProfile, png: PNG): bool =
if(chunk.profileName.len < 1) or (chunk.profileName.len > 79):
raise PNGError("keyword too short or too long")
raise PNGFatal("keyword too short or too long")
result = true
method parseChunk(chunk: PNGICCProfile, png: PNG): bool =
var len = 0
while(len < chunk.length) and (chunk.data[len] != chr(0)): inc len
if(len < 1) or (len > 79): raise PNGError("keyword too short or too long")
if(len < 1) or (len > 79): raise PNGFatal("keyword too short or too long")
chunk.profileName = chunk.data.substr(0, len)
var compMethod = ord(chunk.data[len + 1]) # skip keyword null terminator
if compMethod != 0: raise PNGError("unsupported comp method")
if compMethod != 0: raise PNGFatal("unsupported comp method")
var nz = nzInflateInit(chunk.data.substr(len + 2))
nz.ignoreAdler32 = PNGDecoder(png.settings).ignoreAdler32
@ -712,15 +719,15 @@ method parseChunk(chunk: PNGICCProfile, png: PNG): bool =
method parseChunk(chunk: PNGSPalette, png: PNG): bool =
var len = 0
while(len < chunk.length) and (chunk.data[len] != chr(0)): inc len
if(len < 1) or (len > 79): raise PNGError("keyword too short or too long")
if(len < 1) or (len > 79): raise PNGFatal("keyword too short or too long")
chunk.paletteName = chunk.data.substr(0, len)
chunk.setPosition(len + 1)
chunk.sampleDepth = chunk.readByte()
if chunk.sampleDepth notin {8, 16}: raise PNGError("palette sample depth error")
if chunk.sampleDepth notin {8, 16}: raise PNGFatal("palette sample depth error")
let remainingLength = (chunk.length - (len + 2))
if chunk.sampleDepth == 8:
if (remainingLength mod 6) != 0: raise PNGError("palette length not divisible by 6")
if (remainingLength mod 6) != 0: raise PNGFatal("palette length not divisible by 6")
let numSamples = remainingLength div 6
newSeq(chunk.palette, numSamples)
for p in mitems(chunk.palette):
@ -730,7 +737,7 @@ method parseChunk(chunk: PNGSPalette, png: PNG): bool =
p.alpha = chunk.readByte()
p.frequency = chunk.readInt16()
else: # chunk.sampleDepth == 16:
if (remainingLength mod 10) != 0: raise PNGError("palette length not divisible by 10")
if (remainingLength mod 10) != 0: raise PNGFatal("palette length not divisible by 10")
let numSamples = remainingLength div 10
newSeq(chunk.palette, numSamples)
for p in mitems(chunk.palette):
@ -743,9 +750,9 @@ method parseChunk(chunk: PNGSPalette, png: PNG): bool =
result = true
method parseChunk(chunk: PNGHist, png: PNG): bool =
if not png.hasChunk(PLTE): raise PNGError("Histogram need PLTE")
if not png.hasChunk(PLTE): raise PNGFatal("Histogram need PLTE")
var plte = PNGPalette(png.getChunk(PLTE))
if plte.palette.len != (chunk.length div 2): raise PNGError("invalid histogram length")
if plte.palette.len != (chunk.length div 2): raise PNGFatal("invalid histogram length")
newSeq(chunk.histogram, plte.palette.len)
for i in 0..chunk.histogram.high:
chunk.histogram[i] = chunk.readInt16()
@ -761,11 +768,11 @@ method parseChunk(chunk: PNGSbit, png: PNG): bool =
of LCT_PALETTE: expectedLen = 3
of LCT_GREY_ALPHA: expectedLen = 2
of LCT_RGBA: expectedLen = 4
if chunk.length != expectedLen: raise PNGError("invalid sBIT length")
if chunk.length != expectedLen: raise PNGFatal("invalid sBIT length")
var expectedDepth = 8 #LCT_PALETTE
if header.colorType != LCT_PALETTE: expectedDepth = header.bitDepth
for c in chunk.data:
if (ord(c) == 0) or (ord(c) > expectedDepth): raise PNGError("invalid sBIT value")
if (ord(c) == 0) or (ord(c) > expectedDepth): raise PNGFatal("invalid sBIT value")
result = true
@ -873,38 +880,38 @@ proc parsePNG(s: Stream, settings: PNGDecoder): PNG =
let signature = s.readStr(8)
if signature != PNGSignature:
raise PNGError("signature mismatch")
raise PNGFatal("signature mismatch")
while not s.atEnd():
let length = s.readInt32BE()
let chunkType = PNGChunkType(s.readInt32BE())
let data = if length == 0: "" else: s.readStr(length)
let data = if length <= 0: "" else: s.readStr(length)
let crc = cast[uint32](s.readInt32BE())
let calculatedCRC = crc32(crc32(0, $chunkType), data)
if calculatedCRC != crc and not PNGDecoder(png.settings).ignoreCRC:
raise PNGError("wrong crc for: " & $chunkType)
raise PNGFatal("wrong crc for: " & $chunkType)
var chunk = png.createChunk(chunkType, data, crc)
if chunkType != IDAT and chunk != nil:
if not chunk.parseChunk(png): raise PNGError("error parse chunk: " & $chunkType)
if not chunk.validateChunk(png): raise PNGError("invalid chunk: " & $chunkType)
if not chunk.parseChunk(png): raise PNGFatal("error parse chunk: " & $chunkType)
if not chunk.validateChunk(png): raise PNGFatal("invalid chunk: " & $chunkType)
if chunk != nil:
if chunkType == fcTL or chunkType == fdAT:
png.apngChunks.add APNGFrameChunk(chunk)
else: png.chunks.add chunk
if chunkType == IEND: break
if not png.hasChunk(IHDR): raise PNGError("no IHDR found")
if not png.hasChunk(IDAT): raise PNGError("no IDAT found")
if not png.hasChunk(IHDR): raise PNGFatal("no IHDR found")
if not png.hasChunk(IDAT): raise PNGFatal("no IDAT found")
var header = PNGHeader(png.getChunk(IHDR))
if header.colorType == LCT_PALETTE and not png.hasChunk(PLTE):
raise PNGError("expected PLTE not found")
raise PNGFatal("expected PLTE not found")
# IDAT get special treatment because it can appear in multiple chunk
var idat = PNGData(png.getChunk(IDAT))
if not idat.parseChunk(png): raise PNGError("IDAT parse error")
if not idat.validateChunk(png): raise PNGError("bad IDAT")
if not idat.parseChunk(png): raise PNGFatal("IDAT parse error")
if not idat.validateChunk(png): raise PNGFatal("bad IDAT")
result = png
proc postProcessScanLines[T](png: PNG; header: PNGHeader, w, h: int; input, output: var openArray[T]) =
@ -1526,14 +1533,14 @@ proc getColorRGBA16[T](mode: PNGColorMode): convertRGBA16[T] =
elif mode.colorType == LCT_RGB: return RGBA16FromRGB[T]
elif mode.colorType == LCT_GREY_ALPHA: return RGBA16FromGreyAlpha[T]
elif mode.colorType == LCT_RGBA: return RGBA16FromRGBA[T]
else: raise PNGError("unsupported converter16")
else: raise PNGFatal("unsupported converter16")
proc getPixelRGBA16[T](mode: PNGColorMode): pixelRGBA16[T] =
if mode.colorType == LCT_GREY: return RGBA16ToGrey[T]
elif mode.colorType == LCT_RGB: return RGBA16ToRGB[T]
elif mode.colorType == LCT_GREY_ALPHA: return RGBA16ToGreyAlpha[T]
elif mode.colorType == LCT_RGBA: return RGBA16ToRGBA[T]
else: raise PNGError("unsupported pixel16 converter")
else: raise PNGFatal("unsupported pixel16 converter")
proc getColorRGBA8[T](mode: PNGColorMode): convertRGBA8[T] =
if mode.colorType == LCT_GREY:
@ -1552,7 +1559,7 @@ proc getColorRGBA8[T](mode: PNGColorMode): convertRGBA8[T] =
elif mode.colorType == LCT_RGBA:
if mode.bitDepth == 8: return RGBA8FromRGBA8[T]
else: return RGBA8FromRGBA16[T]
else: raise PNGError("unsupported converter8")
else: raise PNGFatal("unsupported converter8")
proc getPixelRGBA8[T](mode: PNGColorMode): pixelRGBA8[T] =
if mode.colorType == LCT_GREY:
@ -1571,7 +1578,7 @@ proc getPixelRGBA8[T](mode: PNGColorMode): pixelRGBA8[T] =
elif mode.colorType == LCT_RGBA:
if mode.bitDepth == 8: return RGBA8ToRGBA8[T]
else: return RGBA8ToRGBA16[T]
else: raise PNGError("unsupported pixel8 converter")
else: raise PNGFatal("unsupported pixel8 converter")
proc getConverterRGB[T](mode: PNGColorMode): convertRGBA[T] =
if mode.colorType == LCT_GREY:
@ -1590,7 +1597,7 @@ proc getConverterRGB[T](mode: PNGColorMode): convertRGBA[T] =
elif mode.colorType == LCT_RGBA:
if mode.bitDepth == 8: return RGBFromRGBA8[T]
else: return RGBFromRGBA16[T]
else: raise PNGError("unsupported RGB converter")
else: raise PNGFatal("unsupported RGB converter")
proc getConverterRGBA[T](mode: PNGColorMode): convertRGBA[T] =
if mode.colorType == LCT_GREY:
@ -1609,7 +1616,7 @@ proc getConverterRGBA[T](mode: PNGColorMode): convertRGBA[T] =
elif mode.colorType == LCT_RGBA:
if mode.bitDepth == 8: return RGBAFromRGBA8[T]
else: return RGBAFromRGBA16[T]
else: raise PNGError("unsupported RGBA converter")
else: raise PNGFatal("unsupported RGBA converter")
proc convert*[T](output: var openArray[T], input: openArray[T], modeOut, modeIn: PNGColorMode, numPixels: int) =
var tree: ColorTree8
@ -1653,11 +1660,11 @@ proc convert*[T](output: var openArray[T], input: openArray[T], modeOut, modeIn:
cvt(p, input, px, modeIn)
pxl(p, output, px, modeOut, tree)
proc convert*(png: PNG, colorType: PNGcolorType, bitDepth: int): PNGResult =
proc convert*(png: PNG, colorType: PNGColorType, bitDepth: int): PNGResult =
# TODO: check if this works according to the statement in the documentation: "The converter can convert
# from greyscale input color type, to 8-bit greyscale or greyscale with alpha"
# if(colorType notin {LCT_RGB, LCT_RGBA}) and (bitDepth != 8):
# raise PNGError("unsupported color mode conversion")
# raise PNGFatal("unsupported color mode conversion")
let header = PNGHeader(png.getChunk(IHDR))
let modeIn = png.getColorMode()
@ -1678,7 +1685,7 @@ proc convert*(png: PNG, colorType: PNGcolorType, bitDepth: int): PNGResult =
png.pixels.toOpenArray(0, png.pixels.len-1),
modeOut, modeIn, numPixels)
proc convert*(png: PNG, colorType: PNGcolorType, bitDepth: int, ctl: APNGFrameControl, data: string): APNGFrame =
proc convert*(png: PNG, colorType: PNGColorType, bitDepth: int, ctl: APNGFrameControl, data: string): APNGFrame =
let modeIn = png.getColorMode()
let modeOut = newColorMode(colorType, bitDepth)
let size = getRawSize(ctl.width, ctl.height, modeOut)
@ -1707,7 +1714,7 @@ type
png: PNG
result: PNGResult
proc processingAPNG(apng: APNG, colorType: PNGcolorType, bitDepth: int) =
proc processingAPNG(apng: APNG, colorType: PNGColorType, bitDepth: int) =
let header = PNGHeader(apng.png.getChunk(IHDR))
var
actl = APNGAnimationControl(apng.png.getChunk(acTL))
@ -1736,7 +1743,7 @@ proc processingAPNG(apng: APNG, colorType: PNGcolorType, bitDepth: int) =
lastChunkType = fdAT
if actl.numFrames == 0 or actl.numFrames != numFrames or actl.numFrames != frameData.len:
raise PNGError("animation numFrames error")
raise PNGFatal("animation numFrames error")
apng.png.apngPixels = newSeqOfCap[string](numFrames)
@ -1746,9 +1753,9 @@ proc processingAPNG(apng: APNG, colorType: PNGcolorType, bitDepth: int) =
if apng.png.firstFrameIsDefaultImage:
let ctl = frameControl[0]
if ctl.width != header.width or ctl.height != header.height:
raise PNGError("animation control error: dimension")
raise PNGFatal("animation control error: dimension")
if ctl.xOffset != 0 or ctl.xOffset != 0:
raise PNGError("animation control error: offset")
raise PNGFatal("animation control error: offset")
if apng.result != nil:
var frame = new(APNGFrame)
@ -1772,9 +1779,9 @@ proc processingAPNG(apng: APNG, colorType: PNGcolorType, bitDepth: int) =
frame.data = apng.png.apngPixels[^1]
apng.result.frames.add frame
proc decodePNG*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings = PNGDecoder(nil)): PNGResult =
proc decodePNG*(s: Stream, colorType: PNGColorType, bitDepth: int, settings = PNGDecoder(nil)): PNGResult =
if not bitDepthAllowed(colorType, bitDepth):
raise PNGError("colorType and bitDepth combination not allowed")
raise PNGFatal("colorType and bitDepth combination not allowed")
var png = s.parsePNG(settings)
png.postProcessScanLines()
@ -1809,7 +1816,7 @@ when not defined(js):
if s == nil: return nil
result = s.decodePNG(colorType, bitDepth, settings)
s.close()
except:
except PNGError, IOError, NZError:
debugEcho getCurrentExceptionMsg()
result = nil
@ -1824,7 +1831,7 @@ proc decodePNG32*(input: string, settings = PNGDecoder(nil)): PNGResult =
var s = newStringStream(input)
if s == nil: return nil
result = s.decodePNG(LCT_RGBA, 8, settings)
except:
except PNGError, IOError, NZError:
debugEcho getCurrentExceptionMsg()
result = nil
@ -1833,7 +1840,7 @@ proc decodePNG24*(input: string, settings = PNGDecoder(nil)): PNGResult =
var s = newStringStream(input)
if s == nil: return nil
result = s.decodePNG(LCT_RGB, 8, settings)
except:
except PNGError, IOError, NZError:
debugEcho getCurrentExceptionMsg()
result = nil
@ -2053,7 +2060,7 @@ method writeChunk(chunk: PNGTrans, png: PNG): bool =
chunk.writeInt16(chunk.keyG)
chunk.writeInt16(chunk.keyB)
else:
raise PNGError("tRNS chunk not allowed for other color models")
raise PNGFatal("tRNS chunk not allowed for other color models")
result = true
method writeChunk(chunk: PNGBackground, png: PNG): bool =
@ -2123,7 +2130,7 @@ method writeChunk(chunk: PNGSPalette, png: PNG): bool =
#else: estimate += chunk.palette.len * 10
chunk.writeString chunk.paletteName
chunk.writeByte 0 #null separator
if chunk.sampleDepth notin {8, 16}: raise PNGError("palette sample depth error")
if chunk.sampleDepth notin {8, 16}: raise PNGFatal("palette sample depth error")
chunk.writeByte chunk.sampleDepth
if chunk.sampleDepth == 8:
@ -2462,7 +2469,7 @@ proc filter[T](output: var openArray[T], input: openArray[T], w, h: int, modeOut
(modeOut.colorType == LCT_PALETTE or modeOut.bitDepth < 8): strategy = LFS_ZERO
if bpp == 0:
raise PNGError("invalid color type")
raise PNGFatal("invalid color type")
case strategy
of LFS_ZERO: filterZero(output, input, w, h, bpp)
@ -2690,31 +2697,31 @@ proc encoderCore(png: PNG) =
var sequenceNumber = 0
if not bitDepthAllowed(modeIn.colorType, modeIn.bitDepth):
raise PNGError("modeIn colorType and bitDepth combination not allowed")
raise PNGFatal("modeIn colorType and bitDepth combination not allowed")
if not bitDepthAllowed(modeOut.colorType, modeOut.bitDepth):
raise PNGError("modeOut colorType and bitDepth combination not allowed")
raise PNGFatal("modeOut colorType and bitDepth combination not allowed")
if(modeOut.colorType == LCT_PALETTE or state.forcePalette) and
(modeOut.paletteSize == 0 or modeOut.paletteSize > 256):
raise PNGError("invalid palette size, it is only allowed to be 1-256")
raise PNGFatal("invalid palette size, it is only allowed to be 1-256")
if state.filterStrategy == LFS_PREDEFINED:
if state.predefinedFilters.len < png.width:
raise PNGError("predefinedFilters contains not enough filterType compared to image height")
raise PNGFatal("predefinedFilters contains not enough filterType compared to image height")
let inputSize = getRawSize(png.width, png.height, modeIn)
if png.pixels.len < inputSize:
raise PNGError("not enough input to encode")
raise PNGFatal("not enough input to encode")
if state.autoConvert:
png.autoChooseColor(modeOut, modeIn)
if state.interlaceMethod notin {IM_NONE, IM_INTERLACED}:
raise PNGError("unexisting interlace mode")
raise PNGFatal("unexisting interlace mode")
if not bitDepthAllowed(modeOut.colorType, modeOut.bitDepth):
raise PNGError("colorType and bitDepth combination not allowed")
raise PNGFatal("colorType and bitDepth combination not allowed")
if not png.isAPNG: png.apngPixels = @[""]
shallowCopy(png.apngPixels[0], png.pixels)
@ -2747,9 +2754,9 @@ proc encoderCore(png: PNG) =
if png.isAPNG:
if png.apngPixels.len != png.apngChunks.len:
raise PNGError("APNG encoder frame error")
raise PNGFatal("APNG encoder frame error")
if png.apngPixels.len == 0:
raise PNGError("APNG encoder no frame")
raise PNGFatal("APNG encoder no frame")
png.addChunkacTL(png.apngPixels.len, state.numPlays)
if png.firstFrameIsDefaultImage:
png.addChunkfcTL(APNGFrameControl(png.apngChunks[0]), sequenceNumber)
@ -2802,9 +2809,9 @@ proc encodePNG*(input: string, w, h: int, settings = PNGEncoder(nil)): PNG =
png.encoderCore()
result = png
proc encodePNG*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, settings = PNGEncoder(nil)): PNG =
proc encodePNG*(input: string, colorType: PNGColorType, bitDepth, w, h: int, settings = PNGEncoder(nil)): PNG =
if not bitDepthAllowed(colorType, bitDepth):
raise PNGError("colorType and bitDepth combination not allowed")
raise PNGFatal("colorType and bitDepth combination not allowed")
var state: PNGEncoder
if settings == nil: state = makePNGEncoder()
@ -2824,8 +2831,8 @@ proc writeChunks*(png: PNG, s: Stream) =
s.write PNGSignature
for chunk in png.chunks:
if not chunk.validateChunk(png): raise PNGError("combine chunk validation error")
if not chunk.writeChunk(png): raise PNGError("combine chunk write error")
if not chunk.validateChunk(png): raise PNGFatal("combine chunk validation error")
if not chunk.writeChunk(png): raise PNGFatal("combine chunk write error")
chunk.length = chunk.data.len
chunk.crc = crc32(crc32(0, $chunk.chunkType), chunk.data)
@ -2835,14 +2842,14 @@ proc writeChunks*(png: PNG, s: Stream) =
s.writeInt32BE cast[int](chunk.crc)
when not defined(js):
proc savePNG*(fileName, input: string, colorType: PNGcolorType, bitDepth, w, h: int): bool =
proc savePNG*(fileName, input: string, colorType: PNGColorType, bitDepth, w, h: int): bool =
try:
var png = encodePNG(input, colorType, bitDepth, w, h)
var s = newFileStream(fileName, fmWrite)
png.writeChunks s
s.close()
result = true
except:
except PNGError, IOError, NZError:
debugEcho getCurrentExceptionMsg()
result = false
@ -2852,7 +2859,7 @@ when not defined(js):
proc savePNG24*(fileName, input: string, w, h: int): bool =
result = savePNG(fileName, input, LCT_RGB, 8, w, h)
proc prepareAPNG*(colorType: PNGcolorType, bitDepth, numPlays: int, settings = PNGEncoder(nil)): PNG =
proc prepareAPNG*(colorType: PNGColorType, bitDepth, numPlays: int, settings = PNGEncoder(nil)): PNG =
var state: PNGEncoder
if settings == nil: state = makePNGEncoder()
else: state = settings
@ -2922,7 +2929,7 @@ proc encodeAPNG*(png: PNG): string =
var s = newStringStream()
png.writeChunks s
result = s.data
except:
except PNGError, IOError, NZError:
debugEcho getCurrentExceptionMsg()
result = ""
@ -2934,7 +2941,7 @@ when not defined(js):
png.writeChunks s
s.close()
result = true
except:
except PNGError, IOError, NZError:
debugEcho getCurrentExceptionMsg()
result = false

View File

@ -58,7 +58,7 @@ type
data: string
databitlen: int
NZError = ref object of Exception
NZError* = object of CatchableError
NZHash = object
head: seq[int] #hash value to head circular pos
@ -133,9 +133,8 @@ type
mode: nzStreamMode
ignoreAdler32*: bool
proc newNZError(msg: string): NZError =
new(result)
result.msg = msg
template NZFatal(msg: string): untyped =
newException(NZError, msg)
proc readBit(s: BitStream): int {.inline.} =
result = (ord(s.data[s.bitpointer shr 3]) shr (s.bitpointer and 0x07)) and 0x01
@ -151,7 +150,7 @@ proc readBitsFromStream(s: var BitStream, nbits: int): int =
proc readBitsSafe(s: var BitStream, nbits: int): int =
if s.bitpointer + nbits > s.databitlen:
raise newNZError("bit pointer jumps past memory")
raise NZFatal("bit pointer jumps past memory")
for i in 0..nbits-1:
inc(result, s.readBit shl i)
@ -181,7 +180,7 @@ proc HuffmanTree_make2DTree(tree: var HuffmanTree) =
let branch = 2 * treepos + bit
#oversubscribed, see comment in lodepng_error_text
if treepos > 2147483647 or treepos + 2 > tree.numcodes:
raise newNZError("oversubscribed")
raise NZFatal("oversubscribed")
if tree.tree2d[branch] != 32767: #not yet filled in
treepos = tree.tree2d[branch] - tree.numcodes
@ -301,7 +300,7 @@ proc huffman_code_lengths(frequencies: openarray[int], numcodes, maxbitlen: int)
coinmem, numcoins: int
if numcodes == 0:
raise newNZError("a tree of 0 symbols is not supposed to be made")
raise NZFatal("a tree of 0 symbols is not supposed to be made")
for i in 0..numcodes-1:
if frequencies[i] > 0:
@ -410,7 +409,7 @@ proc readInt16(s: var BitStream): int =
#go to first boundary of byte
while (s.bitpointer and 0x7) != 0: inc s.bitpointer
var p = s.bitpointer div 8 #byte position
if p + 2 > s.data.len: raise newNZError("bit pointer will jump past memory")
if p + 2 > s.data.len: raise NZFatal("bit pointer will jump past memory")
result = ord(s.data[p]) + 256 * ord(s.data[p + 1])
inc(s.bitpointer, 16)
@ -420,7 +419,7 @@ proc getBytePosition(s: var BitStream): int =
proc readByte(s: var BitStream): int =
while (s.bitpointer and 0x7) != 0: inc s.bitpointer
var p = s.bitpointer div 8 #byte position
if p + 1 >= s.data.len: raise newNZError("bit pointer will jump past memory")
if p + 1 >= s.data.len: raise NZFatal("bit pointer will jump past memory")
result = ord(s.data[p])
inc(s.bitpointer, 8)
@ -433,12 +432,12 @@ proc inflateNoCompression(nz: nzStream) =
#check if 16-bit NLEN is really the one's complement of LEN
if LEN + NLEN != 65535:
raise newNZError("NLEN is not one's complement of LEN")
raise NZFatal("NLEN is not one's complement of LEN")
#read the literal data: LEN bytes are now stored in the out buffer
var p = nz.bits.getBytePosition
if p + LEN > inlength:
raise newNZError("reading outside of input buffer")
raise NZFatal("reading outside of input buffer")
var pos = nz.data.len
nz.data.setLen(pos + LEN)
@ -495,7 +494,7 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
var tree_cl: HuffmanTree
if s.bitpointer + 14 > inbitlength:
raise newNZError("the bit pointer is or will go past the memory")
raise NZFatal("the bit pointer is or will go past the memory")
#number of literal/length codes + 257.
#Unlike the spec, the value 257 is added to it here already
@ -509,7 +508,7 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
let HCLEN = s.readBitsFromStream(4) + 4
if s.bitpointer + HCLEN * 3 > inbitlength:
raise newNZError("the bit pointer is or will go past the memory")
raise NZFatal("the bit pointer is or will go past the memory")
#read the code length codes out of 3 * (amount of code length codes) bits
for i in 0..NUM_CODE_LENGTH_CODES-1:
@ -533,7 +532,7 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
var replength = 3 #read in the 2 bits that indicate repeat length (3-6)
var value = 0 #set value to the previous code
if i == 0: raise newNZError("can't repeat previous if i is 0")
if i == 0: raise NZFatal("can't repeat previous if i is 0")
replength += s.readBitsSafe(2)
if i < HLIT + 1: value = bitlen_ll[i - 1]
@ -541,7 +540,7 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
#repeat this value in the next lengths
for n in 0..replength-1:
if i >= HLIT + HDIST: raise newNZError("i is larger than the amount of codes")
if i >= HLIT + HDIST: raise NZFatal("i is larger than the amount of codes")
if i < HLIT: bitlen_ll[i] = value
else: bitlen_d[i - HLIT] = value
inc(i)
@ -551,7 +550,7 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
#repeat this value in the next lengths
for n in 0..replength-1:
if i >= HLIT + HDIST: raise newNZError("i is larger than the amount of codes")
if i >= HLIT + HDIST: raise NZFatal("i is larger than the amount of codes")
if i < HLIT: bitlen_ll[i] = 0
else: bitlen_d[i - HLIT] = 0
inc(i)
@ -561,7 +560,7 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
#repeat this value in the next lengths
for n in 0..replength-1:
if i >= HLIT + HDIST: raise newNZError("i is larger than the amount of codes")
if i >= HLIT + HDIST: raise NZFatal("i is larger than the amount of codes")
if i < HLIT: bitlen_ll[i] = 0
else: bitlen_d[i - HLIT] = 0
inc(i)
@ -569,14 +568,14 @@ proc getTreeInflateDynamic(s: var BitStream, tree_ll, tree_d: var HuffmanTree) =
if code == -1:
#return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
#(10=no endcode, 11=wrong jump outside of tree)
if s.bitpointer > inbitlength: raise newNZError("no endcode")
else: raise newNZError("wrong jump outside of tree")
if s.bitpointer > inbitlength: raise NZFatal("no endcode")
else: raise NZFatal("wrong jump outside of tree")
else:
raise newNZError("unexisting code, this can never happen")
raise NZFatal("unexisting code, this can never happen")
break
if bitlen_ll[256] == 0:
raise newNZError("the length of the end code 256 must be larger than 0")
raise NZFatal("the length of the end code 256 must be larger than 0")
#now we've finally got HLIT and HDIST,
#so generate the code trees, and the function is done
@ -613,10 +612,10 @@ proc inflateHuffmanBlock(nz: nzStream, blockType: int) =
if code_ll == -1: #huffmanDecodeSymbol returns -1 in case of error
#return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
#(10=no endcode, 11=wrong jump outside of tree)
if nz.bits.bitpointer > inbitlength: raise newNZError("no endcode")
else: raise newNZError("wrong jump outside of tree")
if nz.bits.bitpointer > inbitlength: raise NZFatal("no endcode")
else: raise NZFatal("wrong jump outside of tree")
else:
raise newNZError("invalid distance code (30-31 are never used)")
raise NZFatal("invalid distance code (30-31 are never used)")
break
var distance = DISTANCEBASE[code_d]
@ -627,7 +626,7 @@ proc inflateHuffmanBlock(nz: nzStream, blockType: int) =
#part 5: fill in all the out[n] values based on the length and dist
let start = nz.data.len
if distance > start:
raise newNZError("too long backward distance")
raise NZFatal("too long backward distance")
var backward = start - distance
nz.data.setLen(start + length)
@ -640,8 +639,8 @@ proc inflateHuffmanBlock(nz: nzStream, blockType: int) =
else: #if(code == -1) huffmanDecodeSymbol returns -1 in case of error
#return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
#(10=no endcode, 11=wrong jump outside of tree)
if nz.bits.bitpointer > inbitlength: raise newNZError("no endcode")
else: raise newNZError("wrong jump outside of tree")
if nz.bits.bitpointer > inbitlength: raise NZFatal("no endcode")
else: raise NZFatal("wrong jump outside of tree")
break
proc nzInflate(nz: nzStream) =
@ -655,7 +654,7 @@ proc nzInflate(nz: nzStream) =
finalBlock = nz.bits.readBitFromStream != 0
let blockType = nz.bits.readBitFromStream + 2 * nz.bits.readBitFromStream
if blockType == 3: raise newNZError("invalid blockType")
if blockType == 3: raise NZFatal("invalid blockType")
elif blockType == 0: nz.inflateNoCompression #no compression
else: nz.inflateHuffmanBlock(blockType) #compression, blockType 01 or 10
@ -858,9 +857,9 @@ proc encodeLZ77(nz: nzStream, hash: var NZHash, inpos, insize: int): seq[int] =
current_offset, current_length: int
if (nz.windowsize == 0) or (nz.windowsize > 32768):
raise newNZError("windowsize smaller/larger than allowed")
raise NZFatal("windowsize smaller/larger than allowed")
if (nz.windowsize and (nz.windowsize - 1)) != 0:
raise newNZError("must be power of two")
raise NZFatal("must be power of two")
var nicematch = min(nz.nicematch, MAX_SUPPORTED_DEFLATE_LENGTH)
var pos = inpos
@ -940,7 +939,7 @@ proc encodeLZ77(nz: nzStream, hash: var NZHash, inpos, insize: int): seq[int] =
if lazy != 0:
lazy = 0
if pos == 0: raise newNZError("lazy matching at pos 0 is impossible")
if pos == 0: raise NZFatal("lazy matching at pos 0 is impossible")
if length > lazylength + 1:
#push the previous character as literal
result.add ord(nz.data[pos - 1])
@ -952,7 +951,7 @@ proc encodeLZ77(nz: nzStream, hash: var NZHash, inpos, insize: int): seq[int] =
dec pos
if(length >= 3) and (offset > nz.windowsize):
raise newNZError("too big (or overflown negative) offset")
raise NZFatal("too big (or overflown negative) offset")
#encode it as length/distance pair or literal value
if length < 3: #only lengths of 3 or higher are supported as length/distance pair
@ -1169,7 +1168,7 @@ proc deflateDynamic(nz: nzStream, hash: var NZHash, datapos, dataend: int, final
nz.bits.writeLZ77data(lz77, tree_ll, tree_d)
if HuffmanTree_getLength(tree_ll, 256) == 0:
raise newNZError("the length of the end code 256 must be larger than 0")
raise NZFatal("the length of the end code 256 must be larger than 0")
#write the end code
nz.bits.addHuffmanSymbol(tree_ll, 256)
@ -1179,7 +1178,7 @@ proc nzDeflate(nz: nzStream) =
var blocksize = 0
var insize = nz.data.len
if nz.btype > 2: raise newNZError("invalid block type")
if nz.btype > 2: raise NZFatal("invalid block type")
elif nz.btype == 0:
nz.deflateNoCompression
return
@ -1229,7 +1228,7 @@ template nzCompressInit*(input: seq[byte]): nzStream =
template nzCompressInit*(input: seq[char]): nzStream =
nzDeflateInit(cast[string](input))
proc nzInflateInit*(input: string): nzStream =
var nz = nzInit()
nz.data = newStringOfCap(1024 * 1024 * 5) # Allocate 5MB in advance
@ -1308,14 +1307,14 @@ proc readInt32(input: string): uint32 =
proc zlib_decompress*(nz: nzStream): string =
let insize = nz.bits.data.len
if insize < 2: raise newNZError("size of zlib data too small")
if insize < 2: raise NZFatal("size of zlib data too small")
#read information from zlib header
let CMF = nz.bits.readByte
let FLG = nz.bits.readByte
if ((CMF * 256 + FLG) mod 31) != 0:
raise newNZError(" zlib header must be a multiple of 31")
raise NZFatal(" zlib header must be a multiple of 31")
#the FCHECK value is supposed to be made that way
#let CM = CMF and 15
@ -1334,12 +1333,13 @@ proc zlib_decompress*(nz: nzStream): string =
let checksum = nz.bits.data.substr(insize-4, insize-1).readInt32
nz.bits.data.setLen(insize-4)
nz.bits.databitlen = (insize-4) * 8 # reset databitlen
nz.nzInflate
if not nz.ignoreAdler32:
let adler32 = nzAdler32(1, nz.data)
if checksum != adler32:
raise newNZError("adler checksum not correct, data must be corrupted")
raise NZFatal("adler checksum not correct, data must be corrupted")
result = nz.nzGetResult

View File

@ -3,4 +3,5 @@ import
test_codec,
test_suite,
test_nimz,
test_filters
test_filters,
test_crash

9
tests/fuzz_codec.nim Normal file
View File

@ -0,0 +1,9 @@
import testutils/fuzzing, ../nimPNG
proc toString(x: openArray[byte]): string =
result = newString(x.len)
if x.len != 0:
copyMem(result[0].addr, x[0].unsafeAddr, x.len)
test:
let png = decodePNG32(toString(payload))

BIN
tests/invalidInput/crash-1 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

BIN
tests/invalidInput/crash-2 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

BIN
tests/invalidInput/crash-3 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

BIN
tests/invalidInput/crash-4 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
tests/invalidInput/crash-5 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

8
tests/test_crash.nim Normal file
View File

@ -0,0 +1,8 @@
import ../nimPNG, unittest, os
suite "parse invalid input":
for x in walkDirRec("tests" / "invalidInput"):
let y = splitPath(x)
test y.tail:
discard loadPNG32(x)
check true