From 0062e5215024465d7a6c6c1ca64ddd5f0816dbf4 Mon Sep 17 00:00:00 2001 From: giraffedata Date: Fri, 16 Feb 2007 03:43:42 +0000 Subject: handle more input image corruptions git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@222 9d0c8265-081b-0410-96cb-a4ca84ce46f8 --- converter/other/giftopnm.c | 527 ++++++++++++++++++++++++++++----------------- 1 file changed, 331 insertions(+), 196 deletions(-) diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c index 7f2c7fc4..0e261da4 100644 --- a/converter/other/giftopnm.c +++ b/converter/other/giftopnm.c @@ -23,10 +23,10 @@ #include #include -#include "pnm.h" -#include "shhopt.h" #include "mallocvar.h" #include "nstring.h" +#include "shhopt.h" +#include "pnm.h" #define GIFMAXVAL 255 #define MAXCOLORMAPSIZE 256 @@ -54,6 +54,32 @@ ReadOK(FILE * const fileP, } + +static void +readFile(FILE * const ifP, + unsigned char * const buffer, + size_t const len, + const char ** const errorP) { + + size_t bytesRead; + + bytesRead = fread(buffer, len, 1, ifP); + + if (bytesRead == len) + *errorP = NULL; + else { + if (ferror(ifP)) + asprintfN(errorP, "Error reading file. errno=%d (%s)", + errno, strerror(errno)); + else if (feof(ifP)) + asprintfN(errorP, "End of file encountered"); + else + asprintfN(errorP, "Short read -- %u bytes of %u", bytesRead, len); + } +} + + + #define LM_to_uint(a,b) (((b)<<8)|(a)) static int const maxnum_lzwCode = (1<buf[0] = 0; - getCodeStateP->buf[1] = 0; - getCodeStateP->bufCount = 2; - getCodeStateP->curbit = getCodeStateP->bufCount * 8; - getCodeStateP->streamExhausted = FALSE; -} - - - -static void -getAnotherBlock(FILE * const ifP, - struct getCodeState * const gsP) { +getAnotherBlock(FILE * const ifP, + struct getCodeState * const gsP, + const char ** const errorP) { unsigned int count; unsigned int assumed_count; @@ -494,91 +522,104 @@ getAnotherBlock(FILE * const ifP, gsP->bufCount = 2; /* Add the next block to the buffer */ - getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count); - if (eof) { - pm_message("EOF encountered in image " - "before EOD marker. The GIF " - "file is malformed, but we are proceeding " - "anyway as if an EOD marker were at the end " - "of the file."); - assumed_count = 0; - } else - assumed_count = count; - - gsP->streamExhausted = (assumed_count == 0); + getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count, errorP); + if (*errorP) + gsP->streamExhausted = TRUE; + else { + if (eof) { + pm_message("EOF encountered in image " + "before EOD marker. The GIF " + "file is malformed, but we are proceeding " + "anyway as if an EOD marker were at the end " + "of the file."); + assumed_count = 0; + } else + assumed_count = count; - gsP->bufCount += assumed_count; + gsP->streamExhausted = (assumed_count == 0); + + gsP->bufCount += assumed_count; + } } -static void -doGetCode(FILE * const ifP, - int const codeSize, - struct getCodeState * const gsP, - int * const retvalP) { - - if ((gsP->curbit+codeSize) > gsP->bufCount*8 && !gsP->streamExhausted) - /* Not enough left in buffer to satisfy request. Get the next - data block into the buffer. - */ - getAnotherBlock(ifP, gsP); +static struct getCodeState getCodeState; - if ((gsP->curbit+codeSize) > gsP->bufCount*8) { - /* If the buffer still doesn't have enough bits in it, that means - there were no data blocks left to read. - */ - *retvalP = -1; /* EOF */ - - { - int const bitsUnused = gsP->bufCount*8 - gsP->curbit; - if (bitsUnused > 0) - pm_message("Stream ends with a partial code " - "(%d bits left in file; " - "expected a %d bit code). Ignoring.", - bitsUnused, codeSize); - } - } else { - int i, j; - int code; - unsigned char * const buf = gsP->buf; - - code = 0; /* initial value */ - for (i = gsP->curbit, j = 0; j < codeSize; ++i, ++j) - code |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j; - gsP->curbit += codeSize; - *retvalP = code; - } +static void +getCode_init(struct getCodeState * const getCodeStateP) { + + /* Fake a previous data block */ + getCodeStateP->buf[0] = 0; + getCodeStateP->buf[1] = 0; + getCodeStateP->bufCount = 2; + getCodeStateP->curbit = getCodeStateP->bufCount * 8; + getCodeStateP->streamExhausted = FALSE; } -static int -getCode(FILE * const ifP, - int const codeSize, - bool const init) -{ +static void +getCode_get(struct getCodeState * const gsP, + FILE * const ifP, + int const codeSize, + bool * const eofP, + unsigned int * const codeP, + const char ** const errorP) { /*---------------------------------------------------------------------------- - If 'init', initialize the code getter. + Read and return the next lzw code from the file *ifP. + + 'codeSize' is the number of bits in the code we are to get. - Otherwise, read and return the next lzw code from the file *ifP. + Return *eofP == TRUE iff we hit the end of the stream. That means a legal + end of stream, marked by an EOD marker, not just end of file. An end of + file in the middle of the GIF stream is an error. - 'codeSize' is the number of bits in the code we are to get. + If there are bits left in the stream, but not 'codeSize' of them, we + call that a success with *eofP == TRUE. - Return -1 instead of a code if we encounter the end of the file. + Return the code read (assuming *eofP == FALSE and *errorP == NULL) + as *codeP. -----------------------------------------------------------------------------*/ - static struct getCodeState getCodeState; + if ((gsP->curbit+codeSize) > gsP->bufCount*8 && !gsP->streamExhausted) + /* Not enough left in buffer to satisfy request. Get the next + data block into the buffer. + */ + getAnotherBlock(ifP, gsP, errorP); + else + *errorP = NULL; - int retval; + if (!*errorP) { + if ((gsP->curbit+codeSize) > gsP->bufCount*8) { + /* The buffer still doesn't have enough bits in it; that means + there were no data blocks left to read. + */ + *eofP = TRUE; + + { + int const bitsUnused = gsP->bufCount * 8 - gsP->curbit; + if (bitsUnused > 0) + pm_message("Stream ends with a partial code " + "(%d bits left in file; " + "expected a %d bit code). Ignoring.", + bitsUnused, codeSize); + } + } else { + int i, j; + unsigned int code; + unsigned char * const buf = gsP->buf; + + code = 0; /* initial value */ + for (i = gsP->curbit, j = 0; j < codeSize; ++i, ++j) + code |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j; + gsP->curbit += codeSize; + *eofP = FALSE; + *codeP = code; + } + } +} - if (init) { - initGetCode(&getCodeState); - retval = 0; - } else - doGetCode(ifP, codeSize, &getCodeState, &retval); - return retval; -} struct stack { @@ -694,7 +735,7 @@ struct decompressor { */ int next_tableSlot; /* Index in the code translation table of the next free entry */ - int firstcode; + unsigned int firstcode; /* This is always a true data element code */ int prevcode; /* The code just before, in the image, the one we're processing now */ @@ -759,7 +800,7 @@ lzwInit(struct decompressor * const decompP, } resetDecompressor(decompP); - getCode(decompP->ifP, 0, TRUE); + getCode_init(&getCodeState); decompP->fresh = TRUE; @@ -855,9 +896,43 @@ expandCodeOntoStack(struct decompressor * const decompP, +static void +lzwReadByteFresh(struct getCodeState * const getCodeStateP, + struct decompressor * const decompP, + bool * const endOfImageP, + unsigned int * const dataReadP, + const char ** const errorP) { + + /* Read off all initial clear codes, read the first non-clear code, + and return it. There are no strings in the table yet, so the next + code must be a direct true data code. + */ + bool eof; + do { + getCode_get(getCodeStateP, decompP->ifP, decompP->codeSize, + &eof, &decompP->firstcode, errorP); + decompP->prevcode = decompP->firstcode; + } while (decompP->firstcode == decompP->clear_code && !*errorP && !eof); + + if (!*errorP) { + if (eof) + *endOfImageP = TRUE; + else if (decompP->firstcode == decompP->end_code) { + if (!zeroDataBlock) + readThroughEod(decompP->ifP); + *endOfImageP = TRUE; + } else { + *endOfImageP = FALSE; + *dataReadP = decompP->firstcode; + } + } +} + + + static void lzwReadByte(struct decompressor * const decompP, - int * const dataReadP, + unsigned int * const dataReadP, bool * const endOfImageP, const char ** const errorP) { /*---------------------------------------------------------------------------- @@ -881,43 +956,34 @@ lzwReadByte(struct decompressor * const decompP, *endOfImageP = FALSE; *dataReadP = popStack(&decompP->stack); } else if (decompP->fresh) { - *errorP = NULL; decompP->fresh = FALSE; - /* Read off all initial clear codes, read the first non-clear code, - and return it. There are no strings in the table yet, so the next - code must be a direct true data code. - */ - do { - decompP->firstcode = - getCode(decompP->ifP, decompP->codeSize, FALSE); - decompP->prevcode = decompP->firstcode; - } while (decompP->firstcode == decompP->clear_code); - if (decompP->firstcode == decompP->end_code) { - if (!zeroDataBlock) - readThroughEod(decompP->ifP); - *endOfImageP = TRUE; - } else - *dataReadP = decompP->firstcode; + + lzwReadByteFresh(&getCodeState, decompP, endOfImageP, dataReadP, + errorP); } else { - int code; - code = getCode(decompP->ifP, decompP->codeSize, FALSE); - if (code == -1) - asprintfN(errorP, "Premature end of file; no proper GIF closing"); - else { - assert(code >= 0); /* -1 is only possible error return */ - if (code == decompP->clear_code) { - resetDecompressor(decompP); - lzwReadByte(decompP, dataReadP, endOfImageP, errorP); - } else { - if (code == decompP->end_code) { - if (!zeroDataBlock) - readThroughEod(decompP->ifP); - *endOfImageP = TRUE; - *errorP = NULL; + unsigned int code; + bool eof; + getCode_get(&getCodeState, decompP->ifP, decompP->codeSize, + &eof, &code, errorP); + if (!*errorP) { + if (eof) + asprintfN(errorP, + "Premature end of file; no proper GIF closing"); + else { + if (code == decompP->clear_code) { + resetDecompressor(decompP); + lzwReadByte(decompP, dataReadP, endOfImageP, errorP); } else { - expandCodeOntoStack(decompP, code, errorP); - if (!*errorP) - *dataReadP = popStack(&decompP->stack); + if (code == decompP->end_code) { + if (!zeroDataBlock) + readThroughEod(decompP->ifP); + *endOfImageP = TRUE; + *errorP = NULL; + } else { + expandCodeOntoStack(decompP, code, errorP); + if (!*errorP) + *dataReadP = popStack(&decompP->stack); + } } } } @@ -1054,10 +1120,75 @@ verifyPixelRead(bool const endOfImage, +static void +readRaster(struct decompressor * const decompP, + xel ** const xels, + unsigned int const cols, + unsigned int const rows, + gifColorMap cmap, + unsigned int const cmapSize, + bool const interlace, + int const transparentIndex, + bit ** const alphabits, + bool const tolerateBadInput) { + + struct pnmBuffer pnmBuffer; + enum pass pass; + bool fillingMissingPixels; + + pass = MULT8PLUS0; + pnmBuffer.xels = xels; + pnmBuffer.col = 0; + pnmBuffer.row = 0; + fillingMissingPixels = false; /* initial value */ + + while (pnmBuffer.row < rows) { + unsigned int colorIndex; + + if (fillingMissingPixels) + colorIndex = 0; + else { + const char * error; + + const char * readError; + unsigned int readColorIndex; + bool endOfImage; + + lzwReadByte(decompP, &readColorIndex, &endOfImage, &readError); + + verifyPixelRead(endOfImage, readError, cols, rows, pnmBuffer.row, + &error); + + if (readError) + strfree(readError); + + if (error) { + if (tolerateBadInput) { + pm_message("WARNING: %s. " + "Filling bottom %u rows with arbitrary color", + error, rows - pnmBuffer.row); + fillingMissingPixels = true; + } else + pm_error("Unable to read input image. %s. Use the " + "-repair option to try to salvage some of " + "the image", + error); + + colorIndex = 0; + } else + colorIndex = readColorIndex; + } + addPixelToRaster(colorIndex, &pnmBuffer, cols, rows, cmap, cmapSize, + interlace, transparentIndex, alphabits, &pass); + } +} + + + static void skipExtraneousData(struct decompressor * const decompP) { - int byteRead; + unsigned int byteRead; bool endOfImage; const char * error; @@ -1095,17 +1226,9 @@ readImageData(FILE * const ifP, bool const tolerateBadInput) { unsigned char lzwMinCodeSize; - enum pass pass; struct decompressor decomp; - struct pnmBuffer pnmBuffer; bool gotMinCodeSize; - pass = MULT8PLUS0; - - pnmBuffer.xels = xels; - pnmBuffer.col = 0; - pnmBuffer.row = 0; - gotMinCodeSize = ReadOK(ifP, &lzwMinCodeSize, 1); if (!gotMinCodeSize) pm_error("GIF stream ends (or read error) " @@ -1119,38 +1242,8 @@ readImageData(FILE * const ifP, lzwInit(&decomp, ifP, lzwMinCodeSize); - while (pnmBuffer.row < rows) { - const char * readError; - const char * error; - int readColorIndex; - int colorIndex; - bool endOfImage; - - lzwReadByte(&decomp, &readColorIndex, &endOfImage, &readError); - - verifyPixelRead(endOfImage, readError, cols, rows, pnmBuffer.row, - &error); - - if (readError) - strfree(readError); - - if (error) { - if (tolerateBadInput) - pm_message("WARNING: %s. " - "Filling bottom %u rows with arbitrary color", - error, rows - pnmBuffer.row); - else - pm_error("Unable to read input image. %s. Use the " - "-repair option to try to salvage some of the image", - error); - - colorIndex = 0; - } else - colorIndex = readColorIndex; - - addPixelToRaster(colorIndex, &pnmBuffer, cols, rows, cmap, cmapSize, - interlace, transparentIndex, alphabits, &pass); - } + readRaster(&decomp, xels, cols, rows, cmap, cmapSize, interlace, + transparentIndex, alphabits, tolerateBadInput); skipExtraneousData(&decomp); @@ -1294,7 +1387,8 @@ readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) { static void readExtensions(FILE* const ifP, struct gif89 * const gif89P, - bool * const eodP) { + bool * const eodP, + const char ** const errorP) { /*---------------------------------------------------------------------------- Read extension blocks from the GIF stream to which the file *ifP is positioned. Read up through the image separator that begins the @@ -1303,33 +1397,50 @@ readExtensions(FILE* const ifP, If we encounter EOD (end of GIF stream) before we find an image separator, we return *eodP == TRUE. Else *eodP == FALSE. - If we hit end of file before an EOD marker, we abort the program with - an error message. + If we hit end of file before an EOD marker, we fail. -----------------------------------------------------------------------------*/ bool imageStart; bool eod; + *errorP = NULL; /* initial value */ + eod = FALSE; imageStart = FALSE; /* Read the image descriptor */ - while (!imageStart && !eod) { + while (!imageStart && !eod && !*errorP) { unsigned char c; + const char * error; - if (! ReadOK(ifP,&c,1)) - pm_error("EOF / read error on image data" ); + readFile(ifP, &c, 1, &error); - if (c == ';') { /* GIF terminator */ - eod = TRUE; - } else if (c == '!') { /* Extension */ - if (! ReadOK(ifP,&c,1)) - pm_error("EOF / " - "read error on extension function code"); - doExtension(ifP, c, gif89P); - } else if (c == ',') - imageStart = TRUE; - else - pm_message("bogus character 0x%02x, ignoring", (int) c ); + if (error) { + asprintfN(errorP, "File read error where start of image " + "descriptor or end of GIF expected. %s", + error); + strfree(error); + } else { + if (c == ';') { /* GIF terminator */ + eod = TRUE; + } else if (c == '!') { /* Extension */ + unsigned char functionCode; + const char * error; + + readFile(ifP, &functionCode, 1, &error); + + if (error) { + asprintfN(errorP, "Failed to read function code " + "of GIF extension (immediately after the '!' " + "extension delimiter) from input. %s", error); + strfree(error); + } else { + doExtension(ifP, functionCode, gif89P); + } + } else if (c == ',') + imageStart = TRUE; + else + pm_message("bogus character 0x%02x, ignoring", (int)c); + } } *eodP = eod; } @@ -1440,6 +1551,26 @@ convertImage(FILE * const ifP, +static void +disposeOfReadExtensionsError(const char * const error, + bool const tolerateBadInput, + unsigned int const imageSeq, + bool * const eodP) { + if (error) { + if (tolerateBadInput) + pm_message("Error accessing Image %u of stream; no further " + "images can be accessed. %s", + imageSeq, error); + else + pm_error("Error accessing Image %u of stream. %s", + imageSeq, error); + strfree(error); + *eodP = TRUE; + } +} + + + static void convertImages(FILE * const ifP, bool const allImages, @@ -1482,15 +1613,19 @@ convertImages(FILE * const ifP, !eod && (imageSeq <= requestedImageSeq || allImages || drainStream); ++imageSeq) { - readExtensions(ifP, &gif89, &eod); + const char * error; + + readExtensions(ifP, &gif89, &eod, &error); + + disposeOfReadExtensionsError(error, tolerateBadInput, imageSeq, &eod); if (eod) { /* GIF stream ends before image with sequence imageSeq */ if (!allImages && (imageSeq <= requestedImageSeq)) pm_error("You requested Image %d, but " "only %d image%s found in GIF stream", - requestedImageSeq+1, - imageSeq, imageSeq>1?"s":"" ); + requestedImageSeq + 1, + imageSeq, imageSeq > 1 ? "s" : ""); } else { if (verbose) pm_message("Reading Image Sequence %d", imageSeq); -- cgit 1.4.1