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
This commit is contained in:
Matthias Melcher 2022-12-17 16:01:35 +01:00 committed by GitHub
parent 08f6741d7b
commit 12da87ba0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 389 additions and 164 deletions

View File

@ -43,12 +43,16 @@ private:
int cache_w_, cache_h_; // size of bitmap when cached int cache_w_, cache_h_; // size of bitmap when cached
public: 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_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);} 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_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_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_Bitmap();
virtual Fl_Image *copy(int W, int H) const; virtual Fl_Image *copy(int W, int H) const;
Fl_Image *copy() const { return Fl_Image::copy(); } Fl_Image *copy() const { return Fl_Image::copy(); }

View File

@ -60,9 +60,10 @@ enum Fl_RGB_Scaling {
class FL_EXPORT Fl_Image { class FL_EXPORT Fl_Image {
friend class Fl_Graphics_Driver; friend class Fl_Graphics_Driver;
public: public:
static const int ERR_NO_IMAGE = -1; static const int ERR_NO_IMAGE = -1;
static const int ERR_FILE_ACCESS = -2; static const int ERR_FILE_ACCESS = -2;
static const int ERR_FORMAT = -3; static const int ERR_FORMAT = -3;
static const int ERR_MEMORY_ACCESS = -4;
private: private:
int w_, h_, d_, ld_, count_; int w_, h_, d_, ld_, count_;
@ -353,6 +354,7 @@ private:
public: public:
Fl_RGB_Image(const uchar *bits, int W, int H, int D=3, int LD=0); 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); Fl_RGB_Image(const Fl_Pixmap *pxm, Fl_Color bg=FL_GRAY);
virtual ~Fl_RGB_Image(); virtual ~Fl_RGB_Image();
virtual Fl_Image *copy(int W, int H) const; virtual Fl_Image *copy(int W, int H) const;

View File

@ -32,11 +32,11 @@ class FL_EXPORT Fl_JPEG_Image : public Fl_RGB_Image {
public: public:
Fl_JPEG_Image(const char *filename); 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: 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);
}; };

View File

@ -147,14 +147,15 @@ private:
float svg_scaling_(int W, int H); float svg_scaling_(int W, int H);
void rasterize_(int W, int H); void rasterize_(int W, int H);
virtual void cache_size_(int &width, int &height); virtual void cache_size_(int &width, int &height);
void init_(const char *filename, const unsigned char *filedata, const Fl_SVG_Image *copy_source, void init_(const char *name, const unsigned char *filedata, size_t length);
size_t length);
Fl_SVG_Image(const Fl_SVG_Image *source); Fl_SVG_Image(const Fl_SVG_Image *source);
public: public:
/** Set this to \c false to allow image re-scaling that alters the image aspect ratio. /** 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.*/ Upon object creation, proportional is set to \c true, and the aspect ratio is kept constant.*/
bool proportional; 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_SVG_Image();
virtual Fl_Image *copy(int W, int H) const; virtual Fl_Image *copy(int W, int H) const;
Fl_Image *copy() const { Fl_Image *copy() const {

View File

@ -94,6 +94,7 @@ class FL_EXPORT Fl_Shared_Image : public Fl_Image {
friend class Fl_JPEG_Image; friend class Fl_JPEG_Image;
friend class Fl_PNG_Image; friend class Fl_PNG_Image;
friend class Fl_SVG_Image;
friend class Fl_Graphics_Driver; friend class Fl_Graphics_Driver;
protected: protected:

View File

@ -94,7 +94,7 @@ void Fluid_Image::write_static() {
write_c("static const unsigned char %s[] =\n", idata_name); write_c("static const unsigned char %s[] =\n", idata_name);
write_cdata(img->data()[0], ((img->w() + 7) / 8) * img->h()); write_cdata(img->data()[0], ((img->w() + 7) / 8) * img->h());
write_c(";\n"); 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) { } else if (strcmp(fl_filename_ext(name()), ".jpg")==0) {
// Write jpeg image data... // Write jpeg image data...
write_c("\n"); write_c("\n");
@ -104,6 +104,7 @@ void Fluid_Image::write_static() {
} }
write_c("static const unsigned char %s[] =\n", idata_name); write_c("static const unsigned char %s[] =\n", idata_name);
size_t nData = 0;
enter_project_dir(); enter_project_dir();
FILE *f = fl_fopen(name(), "rb"); FILE *f = fl_fopen(name(), "rb");
leave_project_dir(); leave_project_dir();
@ -111,7 +112,7 @@ void Fluid_Image::write_static() {
write_file_error("JPEG"); write_file_error("JPEG");
} else { } else {
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
size_t nData = ftell(f); nData = ftell(f);
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
if (nData) { if (nData) {
char *data = (char*)calloc(nData, 1); char *data = (char*)calloc(nData, 1);
@ -123,7 +124,7 @@ void Fluid_Image::write_static() {
} }
write_c(";\n"); 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) { } 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); bool gzipped = (strcmp(fl_filename_ext(name()), ".svgz") == 0);
// Write svg image data... // Write svg image data...
@ -160,9 +161,9 @@ void Fluid_Image::write_static() {
write_c(";\n"); write_c(";\n");
if (gzipped) 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 else
write_initializer("Fl_SVG_Image", "NULL, %s", idata_name); write_initializer("Fl_SVG_Image", "\"%s\", %s", fl_filename_name(name()), idata_name);
} else { } else {
// Write image data... // Write image data...
write_c("\n"); 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, ...) { 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: /* Outputs code that returns (and initializes if needed) an Fl_Image as follows:
static Fl_Image *'function_name_'() { 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; return image;
} */ } */
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
write_c("static Fl_Image *%s() {\n%sstatic Fl_Image *image = new %s(", write_c("static Fl_Image *%s() {\n", function_name_);
function_name_, indent(1), type_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); 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); va_end(ap);
} }
@ -287,9 +294,15 @@ void Fluid_Image::decrement() {
Fluid_Image::~Fluid_Image() { Fluid_Image::~Fluid_Image() {
int a; int a;
if (images) { if (images) {
for (a = 0;; a++) if (images[a] == this) break; for (a = 0; a<numimages; a++) {
numimages--; if (images[a] == this) {
for (; a < numimages; a++) images[a] = images[a+1]; numimages--;
for (; a < numimages; a++) {
images[a] = images[a+1];
}
break;
}
}
} }
if (img) img->release(); if (img) img->release();
free((void*)name_); free((void*)name_);

View File

@ -26,11 +26,75 @@
#include <FL/Fl_Menu_Item.H> #include <FL/Fl_Menu_Item.H>
#include <FL/Fl_Bitmap.H> #include <FL/Fl_Bitmap.H>
#include <stdlib.h>
void Fl_Bitmap::draw(int XP, int YP, int WP, int HP, int cx, int cy) { 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); 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 destructor frees all memory and server resources that are used by
the bitmap. the bitmap.

View File

@ -22,6 +22,8 @@
#include <FL/Fl_Image.H> #include <FL/Fl_Image.H>
#include "flstring.h" #include "flstring.h"
#include <stdlib.h>
void fl_restore_clip(); // from fl_rect.cxx void fl_restore_clip(); // from fl_rect.cxx
// //
@ -187,11 +189,12 @@ void Fl_Image::label(Fl_Menu_Item* m) {
box.image(jpg); box.image(jpg);
\endcode \endcode
\returns Image load failure if non-zero \returns Image load failure if non-zero
\retval 0 the image was loaded successfully \retval 0 the image was loaded successfully
\retval ERR_NO_IMAGE no image was found \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_FILE_ACCESS there was a file access related error (errno should be set)
\retval ERR_FORMAT image decoding failed \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 { int Fl_Image::fail() const {
// if no image exists, ld_ may contain a simple error code // 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. The constructor creates a new RGBA image from the specified Fl_Pixmap.

View File

@ -123,14 +123,16 @@ Fl_JPEG_Image::Fl_JPEG_Image(const char *filename)
\param name A unique name or NULL \param name A unique name or NULL
\param data A pointer to the memory location of the JPEG image \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_JPEG_Image::Fl_JPEG_Image(const char *filename)
\see Fl_Shared_Image \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) : 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" } // 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)); my_src_ptr src = (my_source_mgr*)malloc(sizeof(my_source_mgr));
cinfo->src = &(src->pub); 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. 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 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 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 #ifdef HAVE_LIBJPEG
jpeg_decompress_struct dinfo; // Decompressor info 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) { if (*fp) {
jpeg_stdio_src(&dinfo, *fp); jpeg_stdio_src(&dinfo, *fp);
} else { } 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); jpeg_read_header(&dinfo, TRUE);

View File

@ -19,6 +19,7 @@
#if defined(FLTK_USE_SVG) || defined(FL_DOXYGEN) #if defined(FLTK_USE_SVG) || defined(FL_DOXYGEN)
#include <FL/Fl_SVG_Image.H> #include <FL/Fl_SVG_Image.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/fl_utf8.h> #include <FL/fl_utf8.h>
#include <FL/fl_draw.H> #include <FL/fl_draw.H>
#include <FL/fl_string_functions.h> #include <FL/fl_string_functions.h>
@ -53,23 +54,76 @@ static double strtoll(const char *str, char **endptr, int base) {
#include <zlib.h> #include <zlib.h>
#endif #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. /** Load an SVG image from a file.
\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. This constructor loads the SVG image from a .svg or .svgz file. The reader
\param length When 0, indicates that \p svg_data contains SVG text, otherwise \p svg_data is recognizes if the data is compressed, and decompresses it if zlib is available
a buffer of \p length bytes containing GZ-compressed SVG data. (HAVE_LIBZ).
\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 <em>const unsigned char *</em>. \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) { Fl_SVG_Image::Fl_SVG_Image(const char *filename) :
init_(filename, (const unsigned char *)svg_data, NULL, length); 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 // private constructor
Fl_SVG_Image::Fl_SVG_Image(const Fl_SVG_Image *source) : Fl_RGB_Image(NULL, 0, 0, 4) { Fl_SVG_Image::Fl_SVG_Image(const Fl_SVG_Image *source) :
init_(NULL, NULL, source, 0); 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) #if defined(HAVE_LIBZ)
/* Implementation note about decompression of svgz file or in-memory data. // Decompress gzip data in memory
It seems necessary to use the gzdopen()/gzread() API to inflate a gzip'ed #define CHUNK_SIZE (2048)
file or byte buffer. Writing the in-memory gzip'ed data to an anonymous pipe static int svg_inflate(uchar *src, size_t src_length, uchar *&dst, size_t &dst_length) {
and calling gzread() on the read end of this pipe is a solution for the in-memory case. // allocate space for decompressed data in chunks
But non-blocking write to the pipe is needed to do that in the main thread, typedef struct Chunk {
and that seems impossible with Windows anonymous pipes. Chunk() { next = NULL; }
Therefore, the anonymous pipe is handled in 2 ways: struct Chunk *next;
1) Under Windows, a child thread writes to the write end of the pipe and uchar data[CHUNK_SIZE];
the main thread reads from the read end with gzread(). } Chunk;
2) Under Posix systems, the write end of the pipe is made non-blocking Chunk *first = NULL;
with a fcntl() call, and the main thread successively writes to the write Chunk *chunk = NULL, *next_chunk;
end and reads from the read end with gzread(). This allows to not have
libfltk_images requiring a threading library.
*/
static char *svg_inflate(gzFile gzf, // can be a file or the read end of a pipe z_stream stream = { };
size_t size, // size of compressed data or of file int err = Z_OK;
bool is_compressed, // true when file or byte buffer is gzip'ed const uInt max = (uInt)-1;
int fdwrite, // write end of pipe if >= 0
const unsigned char *bytes // byte buffer to write to pipe dst = 0;
) { dst_length = 0;
size_t rest_bytes = size;
int l; stream.next_in = (z_const Bytef *)src;
size_t out_size = is_compressed ? 3 * size + 1 : size + 1; stream.avail_in = 0;
char *out = (char*)malloc(out_size); stream.zalloc = (alloc_func)0;
char *p = out; 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 { do {
if (is_compressed && p + size > out + out_size) { if (stream.avail_out == 0) {
out_size += size; next_chunk = new Chunk;
size_t delta = (p - out); if (!first) first = next_chunk; else chunk->next = next_chunk;
out = (char*)realloc(out, out_size + 1); chunk = next_chunk;
p = out + delta; stream.avail_out = CHUNK_SIZE;
} stream.next_out = chunk->data;
if ( fdwrite >= 0 && Fl::system_driver()->write_nonblocking_fd(fdwrite, bytes, rest_bytes) ) {
free(out);
out = NULL;
is_compressed = false;
break;
} }
err = inflate(&stream, Z_NO_FLUSH);
} while (err == Z_OK);
l = gzread(gzf, p, (unsigned int)size); inflateEnd(&stream);
if (l > 0) {
p += l; *p = 0; // 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); // delete all the chunks that we allocated
return out; 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) #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) { void Fl_SVG_Image::init_(const char *name, const unsigned char *in_data, size_t length) {
if (copy_source) { counted_svg_image_ = new counted_NSVGimage;
filename = NULL; counted_svg_image_->svg_image = NULL;
in_filedata = NULL; counted_svg_image_->ref_count = 1;
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;
to_desaturate_ = false; to_desaturate_ = false;
average_weight_ = 1; average_weight_ = 1;
proportional = true; proportional = true;
bool is_compressed = true;
if (filename || length) { // process file or byte buffer // yes, this is a const cast to avoid duplicating user supplied data
#if defined(HAVE_LIBZ) uchar *data = const_cast<uchar*>(in_data); // 🤨 careful with this, don't overwrite user supplied data in nsvgParse()
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);
}
#else // ! HAVE_LIBZ // this is to make it clear what we are doing
// without libz, read .svg file const char *sharedname = data ? name : NULL;
FILE *fp = fl_fopen(filename, "rb"); const char *filename = data ? NULL : name;
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;
}
// filedata is NULL or contains SVG data as a char string // prepare with error data, so we can just return if an error occurs
if (filedata) { d(-1);
counted_svg_image_->svg_image = nsvgParse(filedata, "px", 96); ld(ERR_FORMAT);
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());
}
rasterized_ = false; rasterized_ = false;
raster_w_ = raster_h_ = 0; 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();
}
} }