From 12da87ba0c11a7b46d6cdc5716f0b30523898429 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Sat, 17 Dec 2022 16:01:35 +0100 Subject: [PATCH] Adding length checks for in-memory image data (see #542) (#592) SVG is now decompressed in memory Bitmap invalid array length handling to return an error RGB Image data reader to return error if image data is too short FLUID: Add size argument to bitmap and JPEG data --- FL/Fl_Bitmap.H | 8 +- FL/Fl_Image.H | 8 +- FL/Fl_JPEG_Image.H | 4 +- FL/Fl_SVG_Image.H | 7 +- FL/Fl_Shared_Image.H | 1 + fluid/Fluid_Image.cxx | 37 +++-- src/Fl_Bitmap.cxx | 64 ++++++++ src/Fl_Image.cxx | 52 ++++++- src/Fl_JPEG_Image.cxx | 17 +- src/Fl_SVG_Image.cxx | 355 ++++++++++++++++++++++++++---------------- 10 files changed, 389 insertions(+), 164 deletions(-) diff --git a/FL/Fl_Bitmap.H b/FL/Fl_Bitmap.H index 23fd79abd..308e09022 100644 --- a/FL/Fl_Bitmap.H +++ b/FL/Fl_Bitmap.H @@ -43,12 +43,16 @@ private: int cache_w_, cache_h_; // size of bitmap when cached public: - /** The constructors create a new bitmap from the specified bitmap data */ + /** The constructors create a new bitmap from the specified bitmap data. + \see Fl_Bitmap(const uchar *bits, int bits_length, int W, int H) */ Fl_Bitmap(const uchar *bits, int W, int H) : Fl_Image(W,H,0), array(bits), alloc_array(0), id_(0), cache_w_(0),cache_h_(0) {data((const char **)&array, 1);} - /** The constructors create a new bitmap from the specified bitmap data */ + /** The constructors create a new bitmap from the specified bitmap data. + \see Fl_Bitmap(const char *bits, int bits_length, int W, int H) */ Fl_Bitmap(const char *bits, int W, int H) : Fl_Image(W,H,0), array((const uchar *)bits), alloc_array(0), id_(0), cache_w_(0),cache_h_(0) {data((const char **)&array, 1);} + Fl_Bitmap(const uchar *bits, int bits_length, int W, int H); + Fl_Bitmap(const char *bits, int bits_length, int W, int H); virtual ~Fl_Bitmap(); virtual Fl_Image *copy(int W, int H) const; Fl_Image *copy() const { return Fl_Image::copy(); } diff --git a/FL/Fl_Image.H b/FL/Fl_Image.H index 038384991..d571d9e88 100644 --- a/FL/Fl_Image.H +++ b/FL/Fl_Image.H @@ -60,9 +60,10 @@ enum Fl_RGB_Scaling { class FL_EXPORT Fl_Image { friend class Fl_Graphics_Driver; public: - static const int ERR_NO_IMAGE = -1; - static const int ERR_FILE_ACCESS = -2; - static const int ERR_FORMAT = -3; + static const int ERR_NO_IMAGE = -1; + static const int ERR_FILE_ACCESS = -2; + static const int ERR_FORMAT = -3; + static const int ERR_MEMORY_ACCESS = -4; private: int w_, h_, d_, ld_, count_; @@ -353,6 +354,7 @@ private: public: Fl_RGB_Image(const uchar *bits, int W, int H, int D=3, int LD=0); + Fl_RGB_Image(const uchar *bits, int bits_length, int W, int H, int D, int LD); Fl_RGB_Image(const Fl_Pixmap *pxm, Fl_Color bg=FL_GRAY); virtual ~Fl_RGB_Image(); virtual Fl_Image *copy(int W, int H) const; diff --git a/FL/Fl_JPEG_Image.H b/FL/Fl_JPEG_Image.H index f4eeb42cf..4e741487a 100644 --- a/FL/Fl_JPEG_Image.H +++ b/FL/Fl_JPEG_Image.H @@ -32,11 +32,11 @@ class FL_EXPORT Fl_JPEG_Image : public Fl_RGB_Image { public: Fl_JPEG_Image(const char *filename); - Fl_JPEG_Image(const char *name, const unsigned char *data); + Fl_JPEG_Image(const char *name, const unsigned char *data, int data_length=-1); protected: - void load_jpg_(const char *filename, const char *sharename, const unsigned char *data); + void load_jpg_(const char *filename, const char *sharename, const unsigned char *data, int data_length=-1); }; diff --git a/FL/Fl_SVG_Image.H b/FL/Fl_SVG_Image.H index 530c51a82..2171c2a75 100644 --- a/FL/Fl_SVG_Image.H +++ b/FL/Fl_SVG_Image.H @@ -147,14 +147,15 @@ private: float svg_scaling_(int W, int H); void rasterize_(int W, int H); virtual void cache_size_(int &width, int &height); - void init_(const char *filename, const unsigned char *filedata, const Fl_SVG_Image *copy_source, - size_t length); + void init_(const char *name, const unsigned char *filedata, size_t length); Fl_SVG_Image(const Fl_SVG_Image *source); public: /** Set this to \c false to allow image re-scaling that alters the image aspect ratio. Upon object creation, proportional is set to \c true, and the aspect ratio is kept constant.*/ bool proportional; - Fl_SVG_Image(const char *filename, const char *svg_data = NULL, size_t length = 0); + Fl_SVG_Image(const char *filename); + Fl_SVG_Image(const char *sharedname, const char *svg_data); + Fl_SVG_Image(const char *sharedname, const unsigned char *svg_data, size_t length); virtual ~Fl_SVG_Image(); virtual Fl_Image *copy(int W, int H) const; Fl_Image *copy() const { diff --git a/FL/Fl_Shared_Image.H b/FL/Fl_Shared_Image.H index 07551b70e..9307b9e58 100644 --- a/FL/Fl_Shared_Image.H +++ b/FL/Fl_Shared_Image.H @@ -94,6 +94,7 @@ class FL_EXPORT Fl_Shared_Image : public Fl_Image { friend class Fl_JPEG_Image; friend class Fl_PNG_Image; + friend class Fl_SVG_Image; friend class Fl_Graphics_Driver; protected: diff --git a/fluid/Fluid_Image.cxx b/fluid/Fluid_Image.cxx index 604ec80a1..c16ea4d00 100644 --- a/fluid/Fluid_Image.cxx +++ b/fluid/Fluid_Image.cxx @@ -94,7 +94,7 @@ void Fluid_Image::write_static() { write_c("static const unsigned char %s[] =\n", idata_name); write_cdata(img->data()[0], ((img->w() + 7) / 8) * img->h()); write_c(";\n"); - write_initializer( "Fl_Bitmap", "%s, %d, %d", idata_name, img->w(), img->h()); + write_initializer( "Fl_Bitmap", "%s, %d, %d, %d", idata_name, ((img->w() + 7) / 8) * img->h(), img->w(), img->h()); } else if (strcmp(fl_filename_ext(name()), ".jpg")==0) { // Write jpeg image data... write_c("\n"); @@ -104,6 +104,7 @@ void Fluid_Image::write_static() { } write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = 0; enter_project_dir(); FILE *f = fl_fopen(name(), "rb"); leave_project_dir(); @@ -111,7 +112,7 @@ void Fluid_Image::write_static() { write_file_error("JPEG"); } else { fseek(f, 0, SEEK_END); - size_t nData = ftell(f); + nData = ftell(f); fseek(f, 0, SEEK_SET); if (nData) { char *data = (char*)calloc(nData, 1); @@ -123,7 +124,7 @@ void Fluid_Image::write_static() { } write_c(";\n"); - write_initializer("Fl_JPEG_Image", "\"%s\", %s", fl_filename_name(name()), idata_name); + write_initializer("Fl_JPEG_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); } else if (strcmp(fl_filename_ext(name()), ".svg")==0 || strcmp(fl_filename_ext(name()), ".svgz")==0) { bool gzipped = (strcmp(fl_filename_ext(name()), ".svgz") == 0); // Write svg image data... @@ -160,9 +161,9 @@ void Fluid_Image::write_static() { write_c(";\n"); if (gzipped) - write_initializer("Fl_SVG_Image", "NULL, (const char*)%s, %ld", idata_name, nData); + write_initializer("Fl_SVG_Image", "\"%s\", (const char*)%s, %ld", fl_filename_name(name()), idata_name, nData); else - write_initializer("Fl_SVG_Image", "NULL, %s", idata_name); + write_initializer("Fl_SVG_Image", "\"%s\", %s", fl_filename_name(name()), idata_name); } else { // Write image data... write_c("\n"); @@ -188,15 +189,21 @@ void Fluid_Image::write_file_error(const char *fmt) { void Fluid_Image::write_initializer(const char *type_name, const char *format, ...) { /* Outputs code that returns (and initializes if needed) an Fl_Image as follows: static Fl_Image *'function_name_'() { - static Fl_Image *image = new 'type_name'('product of format and remaining args'); + static Fl_Image *image = NULL; + if (!image) + image = new 'type_name'('product of format and remaining args'); return image; } */ va_list ap; va_start(ap, format); - write_c("static Fl_Image *%s() {\n%sstatic Fl_Image *image = new %s(", - function_name_, indent(1), type_name); + write_c("static Fl_Image *%s() {\n", function_name_); + write_c("%sstatic Fl_Image *image = NULL;\n", indent(1)); + write_c("%sif (!image)\n", indent(1)); + write_c("%simage = new %s(", indent(2), type_name); vwrite_c(format, ap); - write_c(");\n%sreturn image;\n}\n", indent(1)); + write_c(");\n"); + write_c("%sreturn image;\n", indent(1)); + write_c("}\n"); va_end(ap); } @@ -287,9 +294,15 @@ void Fluid_Image::decrement() { Fluid_Image::~Fluid_Image() { int a; if (images) { - for (a = 0;; a++) if (images[a] == this) break; - numimages--; - for (; a < numimages; a++) images[a] = images[a+1]; + for (a = 0; arelease(); free((void*)name_); diff --git a/src/Fl_Bitmap.cxx b/src/Fl_Bitmap.cxx index cbe997827..8a336b31e 100644 --- a/src/Fl_Bitmap.cxx +++ b/src/Fl_Bitmap.cxx @@ -26,11 +26,75 @@ #include #include +#include void Fl_Bitmap::draw(int XP, int YP, int WP, int HP, int cx, int cy) { fl_graphics_driver->draw_bitmap(this, XP, YP, WP, HP, cx, cy); } + +/** The constructors create a new bitmap from the specified bitmap data. + If the provided array is too small to contain all the image data, the + constructor will not generate the bitmap to avoid illegal memory read + access and instead set \c data to NULL and \c ld to \c ERR_MEMORY_ACCESS. + \param bits bitmap data, one pixel per bit, rows are rounded to the next byte + \param bit_length length of the \p bits array in bytes + \param W image width in pixels + \param H image height in pixels + \see Fl_Bitmap(const char *bits, int bits_length, int W, int H), + Fl_Bitmap(const uchar *bits, int W, int H) +*/ +Fl_Bitmap::Fl_Bitmap(const uchar *bits, int bits_length, int W, int H) : + Fl_Image(W,H,0), + array((const uchar *)bits), + alloc_array(0), + id_(0), + cache_w_(0), + cache_h_(0) +{ + int rowBytes = (W+7)>>3; + int min_length = rowBytes * H; + if (bits_length >= min_length) { + data((const char **)&array, 1); + } else { + array = NULL; + data(NULL, 0); + ld(ERR_MEMORY_ACCESS); + } +} + + +/** The constructors create a new bitmap from the specified bitmap data. + If the provided array is too small to contain all the image data, the + constructor will not generate the bitmap to avoid illegal memory read + access and instead set \c data to NULL and \c ld to \c ERR_MEMORY_ACCESS. + \param bits bitmap data, one pixel per bit, rows are rounded to the next byte + \param bit_length length of the \p bits array in bytes + \param W image width in pixels + \param H image height in pixels + \see Fl_Bitmap(const uchar *bits, int bits_length, int W, int H), + Fl_Bitmap(const char *bits, int W, int H) + */ +Fl_Bitmap::Fl_Bitmap(const char *bits, int bits_length, int W, int H) : + Fl_Image(W,H,0), + array((const uchar *)bits), + alloc_array(0), + id_(0), + cache_w_(0), + cache_h_(0) +{ + int rowBytes = (W+7)>>3; + int min_length = rowBytes * H; + if (bits_length >= min_length) { + data((const char **)&array, 1); + } else { + array = NULL; + data(NULL, 0); + ld(ERR_MEMORY_ACCESS); + } +} + + /** The destructor frees all memory and server resources that are used by the bitmap. diff --git a/src/Fl_Image.cxx b/src/Fl_Image.cxx index a4dd6d921..5845bc101 100644 --- a/src/Fl_Image.cxx +++ b/src/Fl_Image.cxx @@ -22,6 +22,8 @@ #include #include "flstring.h" +#include + void fl_restore_clip(); // from fl_rect.cxx // @@ -187,11 +189,12 @@ void Fl_Image::label(Fl_Menu_Item* m) { box.image(jpg); \endcode - \returns Image load failure if non-zero - \retval 0 the image was loaded successfully - \retval ERR_NO_IMAGE no image was found - \retval ERR_FILE_ACCESS there was a file access related error (errno should be set) - \retval ERR_FORMAT image decoding failed + \returns Image load failure if non-zero + \retval 0 the image was loaded successfully + \retval ERR_NO_IMAGE no image was found + \retval ERR_FILE_ACCESS there was a file access related error (errno should be set) + \retval ERR_FORMAT image decoding failed + \retval ERR_MEMORY_ACCESS image decoder tried to access memory outside of given memory block */ int Fl_Image::fail() const { // if no image exists, ld_ may contain a simple error code @@ -377,6 +380,45 @@ Fl_RGB_Image::Fl_RGB_Image(const uchar *bits, int W, int H, int D, int LD) : } +/** + The constructor creates a new image from the specified data. + + If the provided array is too small to contain all the image data, the + constructor will not generate the image to avoid illegal memory read + access and instead set \c data to NULL and \c ld to \c ERR_MEMORY_ACCESS. + + \param bits image data + \param bit_length length of the \p bits array in bytes + \param W image width in pixels + \param H image height in pixels + \param D image depth in bytes, 1 for gray scale, 2 for gray with alpha, + 3 for RGB, and 4 for RGB plus alpha + \param LD line length in bytes, or 0 to use W*D. + + \see Fl_RGB_Image(const uchar *bits, int W, int H, int D, int LD) + */ +Fl_RGB_Image::Fl_RGB_Image(const uchar *bits, int bits_length, int W, int H, int D, int LD) : + Fl_Image(W,H,D), + array(bits), + alloc_array(0), + id_(0), + mask_(0), + cache_w_(0), cache_h_(0) +{ + if (D == 0) D = 3; + if (LD == 0) LD = W*D; + int min_length = LD*(H-1) + W*D; + if (bits_length >= min_length) { + data((const char **)&array, 1); + ld(LD); + } else { + array = NULL; + data(NULL, 0); + ld(ERR_MEMORY_ACCESS); + } +} + + /** The constructor creates a new RGBA image from the specified Fl_Pixmap. diff --git a/src/Fl_JPEG_Image.cxx b/src/Fl_JPEG_Image.cxx index 808461c3e..e8e32c6db 100644 --- a/src/Fl_JPEG_Image.cxx +++ b/src/Fl_JPEG_Image.cxx @@ -123,14 +123,16 @@ Fl_JPEG_Image::Fl_JPEG_Image(const char *filename) \param name A unique name or NULL \param data A pointer to the memory location of the JPEG image + \param data_length optional length of \c data. This will protect memory outside + of the \c data array from illegal read operations \see Fl_JPEG_Image::Fl_JPEG_Image(const char *filename) \see Fl_Shared_Image */ -Fl_JPEG_Image::Fl_JPEG_Image(const char *name, const unsigned char *data) +Fl_JPEG_Image::Fl_JPEG_Image(const char *name, const unsigned char *data, int data_length) : Fl_RGB_Image(0,0,0) { - load_jpg_(0L, name, data); + load_jpg_(0L, name, data, data_length); } @@ -188,7 +190,7 @@ extern "C" { } // extern "C" -static void jpeg_mem_src(j_decompress_ptr cinfo, const unsigned char *data) +static void jpeg_unprotected_mem_src(j_decompress_ptr cinfo, const unsigned char *data) { my_src_ptr src = (my_source_mgr*)malloc(sizeof(my_source_mgr)); cinfo->src = &(src->pub); @@ -209,9 +211,9 @@ static void jpeg_mem_src(j_decompress_ptr cinfo, const unsigned char *data) This method reads JPEG image data and creates an RGB or grayscale image. To avoid code duplication, we set filename if we want to read form a file or data to read from memory instead. Sharename can be set if the image is - supposed to be added to teh Fl_Shared_Image list. + supposed to be added to the Fl_Shared_Image list. */ -void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const unsigned char *data) +void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const unsigned char *data, int data_length) { #ifdef HAVE_LIBJPEG jpeg_decompress_struct dinfo; // Decompressor info @@ -299,7 +301,10 @@ void Fl_JPEG_Image::load_jpg_(const char *filename, const char *sharename, const if (*fp) { jpeg_stdio_src(&dinfo, *fp); } else { - jpeg_mem_src(&dinfo, data); + if (data_length==-1) + jpeg_unprotected_mem_src(&dinfo, data); + else + jpeg_mem_src(&dinfo, data, (size_t)data_length); } jpeg_read_header(&dinfo, TRUE); diff --git a/src/Fl_SVG_Image.cxx b/src/Fl_SVG_Image.cxx index a615ccb4a..499e0588f 100644 --- a/src/Fl_SVG_Image.cxx +++ b/src/Fl_SVG_Image.cxx @@ -19,6 +19,7 @@ #if defined(FLTK_USE_SVG) || defined(FL_DOXYGEN) #include +#include #include #include #include @@ -53,23 +54,76 @@ static double strtoll(const char *str, char **endptr, int base) { #include #endif -/** The constructor loads the SVG image from the given .svg/.svgz filename or in-memory data. - \param filename Name of a .svg or .svgz file, or NULL. - \param svg_data A pointer to the memory location of the SVG image data. - This parameter allows to load an SVG image from in-memory data, and is used when \p filename is NULL. - \param length When 0, indicates that \p svg_data contains SVG text, otherwise \p svg_data is - a buffer of \p length bytes containing GZ-compressed SVG data. - \note In-memory SVG data is parsed by the object constructor and is not used after construction. - When \p length > 0, parameter \p svg_data may safely be cast from data of type const unsigned char *. + +/** Load an SVG image from a file. + + This constructor loads the SVG image from a .svg or .svgz file. The reader + recognizes if the data is compressed, and decompresses it if zlib is available + (HAVE_LIBZ). + + \param filename the filename for a .svg or .svgz file */ -Fl_SVG_Image::Fl_SVG_Image(const char *filename, const char *svg_data, size_t length) : Fl_RGB_Image(NULL, 0, 0, 4) { - init_(filename, (const unsigned char *)svg_data, NULL, length); +Fl_SVG_Image::Fl_SVG_Image(const char *filename) : + Fl_RGB_Image(NULL, 0, 0, 4) +{ + init_(filename, NULL, 0); +} + + +/** Load an SVG image from memory. + + This constructor loads the SVG image from a block of memory. This version is + commonly used for uncompressed text data, but the reader recognizes if the data + is compressed, and decompresses it if zlib is available (HAVE_LIBZ). + + \param sharedname if not \c NULL, a shared image will be generated with this name + \param svg_data a pointer to the memory location of the SVG image data + + \note In-memory SVG data is parsed by the object constructor and is no longer + needed after construction. + */ +Fl_SVG_Image::Fl_SVG_Image(const char *sharedname, const char *svg_data) : + Fl_RGB_Image(NULL, 0, 0, 4) +{ + init_(sharedname, (const unsigned char*)svg_data, 0); +} + + +/** Load an SVG image from memory. + + This constructor loads the SVG image from a block of memory. This version is + commonly used for compressed binary data, but the reader recognizes if the data + is uncompressed, and reads it as a text block. + + \param sharedname if not \c NULL, a shared image will be generated with this name + \param svg_data a pointer to the memory location of the SVG image data + \param length optional length of \p svg_data or \c 0. This will protect memory + outside of the \p svg_data array from illegal read operations for + compressed SVG data + + \note In-memory SVG data is parsed by the object constructor and is no longer + needed after construction. + */ +Fl_SVG_Image::Fl_SVG_Image(const char *name, const unsigned char *svg_data, size_t length) : + Fl_RGB_Image(NULL, 0, 0, 4) +{ + init_(name, svg_data, length); } // private constructor -Fl_SVG_Image::Fl_SVG_Image(const Fl_SVG_Image *source) : Fl_RGB_Image(NULL, 0, 0, 4) { - init_(NULL, NULL, source, 0); +Fl_SVG_Image::Fl_SVG_Image(const Fl_SVG_Image *source) : + Fl_RGB_Image(NULL, 0, 0, 4) +{ + counted_svg_image_ = source->counted_svg_image_; + counted_svg_image_->ref_count++; + to_desaturate_ = false; + average_weight_ = 1; + proportional = true; + w(source->w()); + h(source->h()); + rasterized_ = false; + raster_w_ = raster_h_ = 0; } @@ -90,143 +144,182 @@ float Fl_SVG_Image::svg_scaling_(int W, int H) { #if defined(HAVE_LIBZ) -/* Implementation note about decompression of svgz file or in-memory data. - It seems necessary to use the gzdopen()/gzread() API to inflate a gzip'ed - file or byte buffer. Writing the in-memory gzip'ed data to an anonymous pipe - and calling gzread() on the read end of this pipe is a solution for the in-memory case. - But non-blocking write to the pipe is needed to do that in the main thread, - and that seems impossible with Windows anonymous pipes. - Therefore, the anonymous pipe is handled in 2 ways: - 1) Under Windows, a child thread writes to the write end of the pipe and - the main thread reads from the read end with gzread(). - 2) Under Posix systems, the write end of the pipe is made non-blocking - with a fcntl() call, and the main thread successively writes to the write - end and reads from the read end with gzread(). This allows to not have - libfltk_images requiring a threading library. - */ +// Decompress gzip data in memory +#define CHUNK_SIZE (2048) +static int svg_inflate(uchar *src, size_t src_length, uchar *&dst, size_t &dst_length) { + // allocate space for decompressed data in chunks + typedef struct Chunk { + Chunk() { next = NULL; } + struct Chunk *next; + uchar data[CHUNK_SIZE]; + } Chunk; + Chunk *first = NULL; + Chunk *chunk = NULL, *next_chunk; -static char *svg_inflate(gzFile gzf, // can be a file or the read end of a pipe - size_t size, // size of compressed data or of file - bool is_compressed, // true when file or byte buffer is gzip'ed - int fdwrite, // write end of pipe if >= 0 - const unsigned char *bytes // byte buffer to write to pipe - ) { - size_t rest_bytes = size; - int l; - size_t out_size = is_compressed ? 3 * size + 1 : size + 1; - char *out = (char*)malloc(out_size); - char *p = out; + z_stream stream = { }; + int err = Z_OK; + const uInt max = (uInt)-1; + + dst = 0; + dst_length = 0; + + stream.next_in = (z_const Bytef *)src; + stream.avail_in = 0; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + // initialize zlib for inflating compressed data + err = inflateInit2(&stream, 31); + if (err != Z_OK) return err; + gz_header header; + err = inflateGetHeader(&stream, &header); + if (err != Z_OK) return err; + + stream.avail_out = 0; + stream.avail_in = (uInt)(src_length ? src_length : -1); + + // inflate into as many chunks as needed do { - if (is_compressed && p + size > out + out_size) { - out_size += size; - size_t delta = (p - out); - out = (char*)realloc(out, out_size + 1); - p = out + delta; - } - if ( fdwrite >= 0 && Fl::system_driver()->write_nonblocking_fd(fdwrite, bytes, rest_bytes) ) { - free(out); - out = NULL; - is_compressed = false; - break; + if (stream.avail_out == 0) { + next_chunk = new Chunk; + if (!first) first = next_chunk; else chunk->next = next_chunk; + chunk = next_chunk; + stream.avail_out = CHUNK_SIZE; + stream.next_out = chunk->data; } + err = inflate(&stream, Z_NO_FLUSH); + } while (err == Z_OK); - l = gzread(gzf, p, (unsigned int)size); - if (l > 0) { - p += l; *p = 0; + inflateEnd(&stream); + + // copy chunk data into a new continuous data block + if (err == Z_STREAM_END) { + size_t nn = dst_length = stream.total_out; + dst = (uchar*)malloc(dst_length+1); // leave room for a trailing NUL + uchar *d = dst; + chunk = first; + while (chunk && nn>0) { + size_t n = nn > CHUNK_SIZE ? CHUNK_SIZE : nn; + memcpy(d, chunk->data, n); + d += n; + nn -= n; + chunk = chunk->next; } - } while (is_compressed && l >0); - gzclose(gzf); - if (is_compressed) out = (char*)realloc(out, (p-out)+1); - return out; + } + + // delete all the chunks that we allocated + chunk = first; + while (chunk) { + next_chunk = chunk->next; + delete chunk; + chunk = next_chunk; + } + + return (err == Z_STREAM_END) + ? Z_OK + : (err == Z_NEED_DICT) + ? Z_DATA_ERROR + : ((err == Z_BUF_ERROR) && stream.avail_out) + ? Z_DATA_ERROR + : err; } + #endif // defined(HAVE_LIBZ) -void Fl_SVG_Image::init_(const char *filename, const unsigned char *in_filedata, const Fl_SVG_Image *copy_source, size_t length) { - if (copy_source) { - filename = NULL; - in_filedata = NULL; - counted_svg_image_ = copy_source->counted_svg_image_; - counted_svg_image_->ref_count++; - } else { - counted_svg_image_ = new counted_NSVGimage; - counted_svg_image_->svg_image = NULL; - counted_svg_image_->ref_count = 1; - } - char *filedata = NULL; +void Fl_SVG_Image::init_(const char *name, const unsigned char *in_data, size_t length) { + counted_svg_image_ = new counted_NSVGimage; + counted_svg_image_->svg_image = NULL; + counted_svg_image_->ref_count = 1; to_desaturate_ = false; average_weight_ = 1; proportional = true; - bool is_compressed = true; - if (filename || length) { // process file or byte buffer -#if defined(HAVE_LIBZ) - int fdread, fdwrite = -1; - if (length) { // process gzip'ed byte buffer - // Pipe gzip'ed byte buffer into gzlib inflate algorithm. - // Under Windows, gzip'ed byte buffer is written to pipe by child thread. - // Under Posix, gzip'ed byte buffer is written to pipe by non-blocking write - // done by main thread. - Fl::system_driver()->pipe_support(fdread, fdwrite, in_filedata, length); - } else { // read or decompress a .svg or .svgz file - struct stat svg_file_stat; - fl_stat(filename, &svg_file_stat); // get file size - fdread = fl_open_ext(filename, 1, 0); - // read a possibly gzip'ed file and return result as char string - length = svg_file_stat.st_size; - is_compressed = (strcmp(filename + strlen(filename) - 5, ".svgz") == 0); - } - gzFile gzf = (fdread >= 0 ? gzdopen(fdread, "rb") : NULL); - if (gzf) { - filedata = svg_inflate(gzf, length, is_compressed, fdwrite, in_filedata); - } else { - if (fdread >= 0) Fl::system_driver()->close_fd(fdread); - if (fdwrite >= 0) Fl::system_driver()->close_fd(fdwrite); - } + // yes, this is a const cast to avoid duplicating user supplied data + uchar *data = const_cast(in_data); // 🤨 careful with this, don't overwrite user supplied data in nsvgParse() -#else // ! HAVE_LIBZ - // without libz, read .svg file - FILE *fp = fl_fopen(filename, "rb"); - if (fp) { - fseek(fp, 0, SEEK_END); - long size = ftell(fp); - fseek(fp, 0, SEEK_SET); - filedata = (char*)malloc(size+1); - if (filedata) { - if (fread(filedata, 1, size, fp) == size) { - filedata[size] = '\0'; - } else { - free(filedata); - filedata = NULL; - } - } - fclose(fp); - } -#endif // HAVE_LIBZ - if (!filedata) ld(ERR_FILE_ACCESS); - } else { // handle non-gzip'ed svg data as a char string - // XXX: Make internal copy -- nsvgParse() modifies filedata during parsing (!) - filedata = in_filedata ? fl_strdup((const char *)in_filedata) : NULL; - } + // this is to make it clear what we are doing + const char *sharedname = data ? name : NULL; + const char *filename = data ? NULL : name; - // filedata is NULL or contains SVG data as a char string - if (filedata) { - counted_svg_image_->svg_image = nsvgParse(filedata, "px", 96); - free(filedata); // made with svg_inflate|malloc|strdup - if (counted_svg_image_->svg_image->width == 0 || counted_svg_image_->svg_image->height == 0) { - d(-1); - ld(ERR_FORMAT); - } else { - w(int(counted_svg_image_->svg_image->width + 0.5)); - h(int(counted_svg_image_->svg_image->height + 0.5)); - } - } else if (copy_source) { - w(copy_source->w()); - h(copy_source->h()); - } + // prepare with error data, so we can just return if an error occurs + d(-1); + ld(ERR_FORMAT); rasterized_ = false; raster_w_ = raster_h_ = 0; + + // if we are reading from a file, just read the entire file into a memory block + if (!data) { + FILE *f = fl_fopen(filename, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + data = (uchar*)malloc(length+1); + if (data) { + if (fread((void*)data, 1, length, f) == length) { + data[length] = 0; + } else { + free((void*)data); + data = NULL; + } + } + fclose(f); + } + if (!data) return; + } + + // now if our data is compressed, we use zlib to infalte it + if (length==0 || length>10) { + if (data[0] == 0x1f && data[1] == 0x8b) { +#if defined(HAVE_LIBZ) + // this is gzip compressed data, so we decompress it and preplace the data array + uchar *uncompressed_data = NULL; + size_t uncompressed_data_length = 0; + int err = svg_inflate(data, length, uncompressed_data, uncompressed_data_length); + if (err == Z_OK) { + // replace compressed data with uncompressed data + if (in_data == NULL) free(data); + length = (size_t)uncompressed_data_length; + data = (uchar*)uncompressed_data; + data[length] = 0; + } else { + if (in_data != data) free(data); + return; + } +#else + if (in_data != data) free(data); + return; +#endif // HAVE_LIBZ + } + } + + // now our SVG data should be in text format in `data`, terminated by a NUL + // nsvgParse is destructive, so if in_data was set, we must duplicate the data first! + if (in_data == data) { + if (length) { + data = (uchar*)malloc(length+1); + memcpy(data, in_data, length); + data[length] = 0; + } else { + data = (uchar*)fl_strdup((char*)in_data); + } + } + counted_svg_image_->svg_image = nsvgParse((char*)data, "px", 96); + if (in_data != data) free(data); + if (counted_svg_image_->svg_image->width != 0 && counted_svg_image_->svg_image->height != 0) { + w(int(counted_svg_image_->svg_image->width + 0.5)); + h(int(counted_svg_image_->svg_image->height + 0.5)); + d(4); + ld(0); + } + + if (sharedname && w() && h()) { + Fl_Shared_Image *si = new Fl_Shared_Image(sharedname, this); + si->add(); + } }