summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2022-12-17 16:01:35 +0100
committerGitHub <noreply@github.com>2022-12-17 16:01:35 +0100
commit12da87ba0c11a7b46d6cdc5716f0b30523898429 (patch)
tree3403a5954fdc7641bcb3387f5b1cf78e85bb08cc
parent08f6741d7b0115787309c9ee4800ecbe317c3c07 (diff)
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
-rw-r--r--FL/Fl_Bitmap.H8
-rw-r--r--FL/Fl_Image.H8
-rw-r--r--FL/Fl_JPEG_Image.H4
-rw-r--r--FL/Fl_SVG_Image.H7
-rw-r--r--FL/Fl_Shared_Image.H1
-rw-r--r--fluid/Fluid_Image.cxx37
-rw-r--r--src/Fl_Bitmap.cxx64
-rw-r--r--src/Fl_Image.cxx52
-rw-r--r--src/Fl_JPEG_Image.cxx17
-rw-r--r--src/Fl_SVG_Image.cxx343
10 files changed, 383 insertions, 158 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; a<numimages; a++) {
+ if (images[a] == this) {
+ numimages--;
+ for (; a < numimages; a++) {
+ images[a] = images[a+1];
+ }
+ break;
+ }
+ }
}
if (img) img->release();
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 <FL/Fl_Menu_Item.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) {
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 <FL/Fl_Image.H>
#include "flstring.h"
+#include <stdlib.h>
+
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
@@ -378,6 +381,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 RGBA image is built fully opaque except for the transparent area
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 <FL/Fl_SVG_Image.H>
+#include <FL/Fl_Shared_Image.H>
#include <FL/fl_utf8.h>
#include <FL/fl_draw.H>
#include <FL/fl_string_functions.h>
@@ -53,23 +54,76 @@ static double strtoll(const char *str, char **endptr, int base) {
#include <zlib.h>
#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 <em>const unsigned char *</em>.
+
+/** 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.
- */
-
-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;
+// 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;
+
+ 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 (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;
}
- 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);
+
+ 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;
}
+ }
- l = gzread(gzf, p, (unsigned int)size);
- if (l > 0) {
- p += l; *p = 0;
- }
- } 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<uchar*>(in_data); // 🤨 careful with this, don't overwrite user supplied data in nsvgParse()
+
+ // this is to make it clear what we are doing
+ const char *sharedname = data ? name : NULL;
+ const char *filename = data ? NULL : name;
+
+ // 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;
-#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';
+ // 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(filedata);
- filedata = NULL;
+ free((void*)data);
+ data = NULL;
}
}
- fclose(fp);
+ 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
- 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
- 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);
+ // 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 {
- w(int(counted_svg_image_->svg_image->width + 0.5));
- h(int(counted_svg_image_->svg_image->height + 0.5));
+ data = (uchar*)fl_strdup((char*)in_data);
}
- } else if (copy_source) {
- w(copy_source->w());
- h(copy_source->h());
}
- rasterized_ = false;
- raster_w_ = raster_h_ = 0;
+ 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();
+ }
}