From 552f36f036d55e8e707d7cafed93724ae7c5e08d Mon Sep 17 00:00:00 2001 From: askmeaboutloom Date: Sat, 4 Nov 2023 06:45:50 +0100 Subject: [PATCH] Don't rely on file extensions when opening images Instead check the file magic. Especially on Android the reported extension may be nonsense. This also fiddles with PSD loading a bit to use the DP_Input that was used to read the magic directly instead of opening a fresh file handle from a path. --- src/drawdance/bundled/psd_sdk/PsdFile.cpp | 6 +- src/drawdance/bundled/psd_sdk/PsdFile.h | 4 +- src/drawdance/libengine/dpengine/image.c | 50 ++++++++++------- src/drawdance/libengine/dpengine/image.h | 6 ++ src/drawdance/libengine/dpengine/load.c | 55 ++++++++++++++----- src/drawdance/libengine/dpengine/load.h | 3 +- src/drawdance/libengine/dpengine/load_psd.cpp | 10 ++-- 7 files changed, 88 insertions(+), 46 deletions(-) diff --git a/src/drawdance/bundled/psd_sdk/PsdFile.cpp b/src/drawdance/bundled/psd_sdk/PsdFile.cpp index 4c56a5db48..8b1aed2595 100644 --- a/src/drawdance/bundled/psd_sdk/PsdFile.cpp +++ b/src/drawdance/bundled/psd_sdk/PsdFile.cpp @@ -26,11 +26,9 @@ File::~File(void) // --------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------- -bool File::OpenRead(const char* filename) +bool File::OpenRead(void* user) { - PSD_ASSERT_NOT_NULL(filename); - - return DoOpenRead(filename); + return DoOpenRead(user); } diff --git a/src/drawdance/bundled/psd_sdk/PsdFile.h b/src/drawdance/bundled/psd_sdk/PsdFile.h index 4112e54bf9..7d614eaaf2 100644 --- a/src/drawdance/bundled/psd_sdk/PsdFile.h +++ b/src/drawdance/bundled/psd_sdk/PsdFile.h @@ -33,7 +33,7 @@ class File virtual ~File(void); /// Tries to open a file for reading, and returns whether the operation was successful. - bool OpenRead(const char* filename); + bool OpenRead(void* user); /// Tries to close a file, and returns whether the operation was successful. bool Close(void); @@ -53,7 +53,7 @@ class File Allocator* m_allocator; private: - virtual bool DoOpenRead(const char* filename) PSD_ABSTRACT; + virtual bool DoOpenRead(void* filename) PSD_ABSTRACT; virtual bool DoClose(void) PSD_ABSTRACT; virtual ReadOperation DoRead(void* buffer, uint32_t count, uint64_t position) PSD_ABSTRACT; diff --git a/src/drawdance/libengine/dpengine/image.c b/src/drawdance/libengine/dpengine/image.c index a2598837db..2308d344b5 100644 --- a/src/drawdance/libengine/dpengine/image.c +++ b/src/drawdance/libengine/dpengine/image.c @@ -40,6 +40,17 @@ struct DP_Image { }; +DP_Image *DP_image_new(int width, int height) +{ + DP_ASSERT(width > 0); + DP_ASSERT(height > 0); + size_t count = DP_int_to_size(width) * DP_int_to_size(height); + DP_Image *img = DP_malloc_zeroed(DP_FLEX_SIZEOF(DP_Image, pixels, count)); + img->width = width; + img->height = height; + return img; +} + static void assign_type(DP_ImageFileType *out_type, DP_ImageFileType type) { if (out_type) { @@ -47,33 +58,28 @@ static void assign_type(DP_ImageFileType *out_type, DP_ImageFileType type) } } -static bool guess_png(unsigned char *buf, size_t size) +static bool guess_png(const unsigned char *buf, size_t size) { unsigned char sig[] = {0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa}; return size >= sizeof(sig) && memcmp(buf, sig, sizeof(sig)) == 0; } -static bool guess_jpeg(unsigned char *buf, size_t size) +static bool guess_jpeg(const unsigned char *buf, size_t size) { return size >= 4 && buf[0] == 0xff && buf[1] == 0xd8 && buf[2] == 0xff && ((buf[3] >= 0xe0 && buf[3] <= 0xef) || buf[3] == 0xdb); } -static DP_Image *read_image_guess(DP_Input *input, DP_ImageFileType *out_type) +DP_Image *DP_image_new_from_file_guess(DP_Input *input, + const unsigned char *buf, size_t size, + DP_ImageFileType *out_type) { - unsigned char buf[8]; - bool error; - size_t read = DP_input_read(input, buf, sizeof(buf), &error); - if (error) { - return NULL; - } - DP_Image *(*read_fn)(DP_Input *); - if (guess_png(buf, read)) { + if (guess_png(buf, size)) { assign_type(out_type, DP_IMAGE_FILE_TYPE_PNG); read_fn = DP_image_png_read; } - else if (guess_jpeg(buf, read)) { + else if (guess_jpeg(buf, size)) { assign_type(out_type, DP_IMAGE_FILE_TYPE_JPEG); read_fn = DP_image_jpeg_read; } @@ -83,7 +89,7 @@ static DP_Image *read_image_guess(DP_Input *input, DP_ImageFileType *out_type) return NULL; } - if (DP_input_rewind_by(input, read)) { + if (DP_input_rewind_by(input, size)) { return read_fn(input); } else { @@ -91,15 +97,17 @@ static DP_Image *read_image_guess(DP_Input *input, DP_ImageFileType *out_type) } } -DP_Image *DP_image_new(int width, int height) +static DP_Image *read_image_guess(DP_Input *input, DP_ImageFileType *out_type) { - DP_ASSERT(width > 0); - DP_ASSERT(height > 0); - size_t count = DP_int_to_size(width) * DP_int_to_size(height); - DP_Image *img = DP_malloc_zeroed(DP_FLEX_SIZEOF(DP_Image, pixels, count)); - img->width = width; - img->height = height; - return img; + unsigned char buf[8]; + bool error; + size_t read = DP_input_read(input, buf, sizeof(buf), &error); + if (error) { + return NULL; + } + else { + return DP_image_new_from_file_guess(input, buf, read, out_type); + } } DP_Image *DP_image_new_from_file(DP_Input *input, DP_ImageFileType type, diff --git a/src/drawdance/libengine/dpengine/image.h b/src/drawdance/libengine/dpengine/image.h index fa3b02ef00..11e2d8931f 100644 --- a/src/drawdance/libengine/dpengine/image.h +++ b/src/drawdance/libengine/dpengine/image.h @@ -43,6 +43,12 @@ typedef DP_Pixel8 (*DP_ImageGetPixelFn)(void *user, int x, int y); DP_Image *DP_image_new(int width, int height); +// Expects `size` bytes to have been read into `buf` to check file magic. 8 +// bytes are enough. This function takes care of rewinding `input` if needed. +DP_Image *DP_image_new_from_file_guess(DP_Input *input, + const unsigned char *buf, size_t size, + DP_ImageFileType *out_type); + DP_Image *DP_image_new_from_file(DP_Input *input, DP_ImageFileType type, DP_ImageFileType *out_type); diff --git a/src/drawdance/libengine/dpengine/load.c b/src/drawdance/libengine/dpengine/load.c index b6690ef096..79e996b406 100644 --- a/src/drawdance/libengine/dpengine/load.c +++ b/src/drawdance/libengine/dpengine/load.c @@ -1210,11 +1210,11 @@ static DP_CanvasState *load_ora(DP_DrawContext *dc, const char *path, DP_CanvasState *load_flat_image(DP_DrawContext *dc, DP_Input *input, const char *flat_image_layer_title, + const unsigned char *buf, size_t size, DP_LoadResult *out_result) { DP_ImageFileType type; - DP_Image *img = - DP_image_new_from_file(input, DP_IMAGE_FILE_TYPE_GUESS, &type); + DP_Image *img = DP_image_new_from_file_guess(input, buf, size, &type); if (!img) { assign_load_result(out_result, type == DP_IMAGE_FILE_TYPE_UNKNOWN ? DP_LOAD_RESULT_UNKNOWN_FORMAT @@ -1258,28 +1258,57 @@ DP_CanvasState *load_flat_image(DP_DrawContext *dc, DP_Input *input, } +static bool guess_zip(const unsigned char *buf, size_t size) +{ + return size >= 4 && buf[0] == 0x50 && buf[1] == 0x4B && buf[2] == 0x03 + && (buf[3] == 0x04 || buf[3] == 0x06 || buf[3] == 0x08); +} + +static bool guess_psd(const unsigned char *buf, size_t size) +{ + return size >= 4 && buf[0] == 0x38 && buf[1] == 0x42 && buf[2] == 0x50 + && buf[3] == 0x53; +} + static DP_CanvasState *load(DP_DrawContext *dc, const char *path, const char *flat_image_layer_title, DP_LoadResult *out_result) { - const char *dot = strrchr(path, '.'); - if (DP_str_equal_lowercase(dot, ".ora")) { - return load_ora(dc, path, NULL, NULL, out_result); - } - - if (DP_str_equal_lowercase(dot, ".psd")) { - return DP_load_psd(dc, path, out_result); - } - DP_Input *input = DP_file_input_new_from_path(path); if (!input) { assign_load_result(out_result, DP_LOAD_RESULT_OPEN_ERROR); return NULL; } - DP_CanvasState *cs = - load_flat_image(dc, input, flat_image_layer_title, out_result); + unsigned char buf[8]; + bool error; + size_t read = DP_input_read(input, buf, sizeof(buf), &error); + if (error) { + assign_load_result(out_result, DP_LOAD_RESULT_READ_ERROR); + return NULL; + } + + // We could also check if there's an uncompressed mimetype file at the + // beginning of the ZIP archive like the spec says, but since we only + // support one ZIP-based format, that's not necessary and would preclude ORA + // files with the pretty unimportant defect of compressing a file wrong. + if (guess_zip(buf, read)) { + DP_input_free(input); + return load_ora(dc, path, NULL, NULL, out_result); + } + + if (guess_psd(buf, read)) { + if (DP_input_rewind_by(input, read)) { + return DP_load_psd(dc, input, out_result); + } + else { + assign_load_result(out_result, DP_LOAD_RESULT_READ_ERROR); + return NULL; + } + } + DP_CanvasState *cs = load_flat_image(dc, input, flat_image_layer_title, buf, + read, out_result); DP_input_free(input); return cs; } diff --git a/src/drawdance/libengine/dpengine/load.h b/src/drawdance/libengine/dpengine/load.h index 4302499828..376df84ca9 100644 --- a/src/drawdance/libengine/dpengine/load.h +++ b/src/drawdance/libengine/dpengine/load.h @@ -25,6 +25,7 @@ typedef struct DP_CanvasState DP_CanvasState; typedef struct DP_DrawContext DP_DrawContext; +typedef struct DP_Input DP_Input; typedef struct DP_Player DP_Player; @@ -61,7 +62,7 @@ DP_CanvasState *DP_load_ora(DP_DrawContext *dc, const char *path, DP_LoadFixedLayerFn on_fixed_layer, void *user, DP_LoadResult *out_result); -DP_CanvasState *DP_load_psd(DP_DrawContext *dc, const char *path, +DP_CanvasState *DP_load_psd(DP_DrawContext *dc, DP_Input *input, DP_LoadResult *out_result); DP_Player *DP_load_recording(const char *path, DP_LoadResult *out_result); diff --git a/src/drawdance/libengine/dpengine/load_psd.cpp b/src/drawdance/libengine/dpengine/load_psd.cpp index a798f25c39..fd99191bde 100644 --- a/src/drawdance/libengine/dpengine/load_psd.cpp +++ b/src/drawdance/libengine/dpengine/load_psd.cpp @@ -52,9 +52,9 @@ class DP_PsdFile final : public psd::File { DP_PsdFile &operator=(DP_PsdFile &&) = delete; private: - bool DoOpenRead(const char *filename) override + bool DoOpenRead(void *user) override { - m_input = DP_file_input_new_from_path(filename); + m_input = static_cast(user); return m_input != nullptr; } @@ -401,14 +401,14 @@ static DP_TransientCanvasState *extract_layers(psd::Document *document, return tcs; } -extern "C" DP_CanvasState *DP_load_psd(DP_DrawContext *dc, const char *path, +extern "C" DP_CanvasState *DP_load_psd(DP_DrawContext *dc, DP_Input *input, DP_LoadResult *out_result) { psd::MallocAllocator allocator; DP_PsdFile file(&allocator); - bool opened = file.OpenRead(path); + bool opened = file.OpenRead(input); if (!opened) { - DP_error_set("Failed to open PSD file '%s'", path); + DP_error_set("Failed to open PSD file"); assign_load_result(out_result, DP_LOAD_RESULT_OPEN_ERROR); return nullptr; }