diff --git a/.gitignore b/.gitignore index c792981..20ce230 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +tests/corpus_dir \ No newline at end of file diff --git a/nimPNG.nim b/nimPNG.nim index eda7722..ba8a1ee 100644 --- a/nimPNG.nim +++ b/nimPNG.nim @@ -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 diff --git a/nimPNG/nimz.nim b/nimPNG/nimz.nim index dacacf6..744465c 100644 --- a/nimPNG/nimz.nim +++ b/nimPNG/nimz.nim @@ -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 diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 114ddb0..5ce528d 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -3,4 +3,5 @@ import test_codec, test_suite, test_nimz, - test_filters + test_filters, + test_crash diff --git a/tests/fuzz_codec.nim b/tests/fuzz_codec.nim new file mode 100644 index 0000000..f96b6dd --- /dev/null +++ b/tests/fuzz_codec.nim @@ -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)) diff --git a/tests/invalidInput/crash-1 b/tests/invalidInput/crash-1 new file mode 100644 index 0000000..b11c2a7 Binary files /dev/null and b/tests/invalidInput/crash-1 differ diff --git a/tests/invalidInput/crash-2 b/tests/invalidInput/crash-2 new file mode 100644 index 0000000..55fc5ba Binary files /dev/null and b/tests/invalidInput/crash-2 differ diff --git a/tests/invalidInput/crash-3 b/tests/invalidInput/crash-3 new file mode 100644 index 0000000..c874130 Binary files /dev/null and b/tests/invalidInput/crash-3 differ diff --git a/tests/invalidInput/crash-4 b/tests/invalidInput/crash-4 new file mode 100644 index 0000000..696213a Binary files /dev/null and b/tests/invalidInput/crash-4 differ diff --git a/tests/invalidInput/crash-5 b/tests/invalidInput/crash-5 new file mode 100644 index 0000000..0cdc37f Binary files /dev/null and b/tests/invalidInput/crash-5 differ diff --git a/tests/test_crash.nim b/tests/test_crash.nim new file mode 100644 index 0000000..a6025a6 --- /dev/null +++ b/tests/test_crash.nim @@ -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