From 4affafe91579799efd83f4c8e05c291eeb684c9c Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Wed, 5 Jan 2022 14:35:15 +0100 Subject: use libmagic to detect valid file formats Writing our own magic bytes detection is prone to errors and an everlasting catch-up-game. Let's use libmagic to get things right, this is less code and makes things more reliable. Building without libmagic is still possible. That will make the code act like specifying FEH_SKIP_MAGIC=1, effectively passing everything to imlib2. --- README.md | 2 + config.mk | 6 +++ src/imlib.c | 134 +++++++++++++++++++++++------------------------------------- 3 files changed, 59 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 4401af2..c4cb7ef 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Dependencies * Imlib2 * libcurl (disable with make curl=0) + * libmagic (disable with make magic=0) * libpng * libX11 * libXinerama (disable with make xinerama=0) @@ -91,6 +92,7 @@ indicates that the corresponding feature is enabled by default. | help | 0 | include help text (refers to the manpage otherwise) | | inotify | 0 | enable inotify, needed for `--auto-reload` | | stat64 | 0 | Support CIFS shares from 64bit hosts on 32bit machines | +| magic | 1 | Build against libmagic to filter unsupported file formats | | mkstemps | 1 | Whether your libc provides `mkstemps()`. If set to 0, feh will be unable to load gif images via libcurl | | verscmp | 1 | Whether your libc provides `strvercmp()`. If set to 0, feh will use an internal implementation. | | xinerama | 1 | Support Xinerama/XRandR multiscreen setups | diff --git a/config.mk b/config.mk index 2d63f72..910eac7 100644 --- a/config.mk +++ b/config.mk @@ -6,6 +6,7 @@ curl ?= 1 debug ?= 0 exif ?= 0 help ?= 0 +magic ?= 1 mkstemps ?= 1 verscmp ?= 1 xinerama ?= 1 @@ -68,6 +69,11 @@ ifeq (${mkstemps},1) CFLAGS += -DHAVE_MKSTEMPS endif +ifeq (${magic},1) + CFLAGS += -DHAVE_LIBMAGIC + LDLIBS += -lmagic +endif + ifeq (${verscmp},1) CFLAGS += -DHAVE_STRVERSCMP endif diff --git a/src/imlib.c b/src/imlib.c index 6d709a2..70d459f 100644 --- a/src/imlib.c +++ b/src/imlib.c @@ -44,6 +44,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "exif.h" #endif +#ifdef HAVE_LIBMAGIC +#include +#endif + Display *disp = NULL; Visual *vis = NULL; Screen *scr = NULL; @@ -242,98 +246,62 @@ void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum fe * avoid calling Imlib2 for files it probably cannot handle. See * and * . - * - * Note that this drops support for bz2-compressed files, unless - * FEH_SKIP_MAGIC is set */ int feh_is_image(feh_file * file) { - unsigned char buf[16]; - FILE *fh = fopen(file->filename, "r"); - if (!fh) { - return 0; - } - // Files smaller than buf will be padded with zeroes - memset(buf, 0, sizeof(buf)); - if (fread(buf, 1, 16, fh) <= 0) { - fclose(fh); - return 0; - } - fclose(fh); +#ifdef HAVE_LIBMAGIC + magic_t magic; + const char * mime_type; + int is_image = 0; - if (buf[0] == 0xff && buf[1] == 0xd8) { - // JPEG - return 1; - } - if (!memcmp(buf, "\x89PNG\x0d\x0a\x1a\x0a", 8)) { - // PNG - return 1; - } - if (buf[0] == 'A' && buf[1] == 'R' && buf[2] == 'G' && buf[3] == 'B') { - // ARGB - return 1; - } - if (buf[0] == 'B' && buf[1] == 'M') { - // BMP - return 1; - } - if (!memcmp(buf, "farbfeld", 8)) { - // farbfeld - return 1; - } - if (buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F') { - // GIF - return 1; - } - if (buf[0] == 0x00 && buf[1] == 0x00 && buf[2] <= 0x02 && buf[3] == 0x00) { - // ICO - return 1; - } - if (!memcmp(buf, "FORM", 4)) { - // Amiga IFF ILBM - return 1; - } - if (buf[0] == 'P' && buf[1] >= '1' && buf[1] <= '7') { - // PNM et al. - return 1; - } - if (strstr(file->filename, ".tga")) { - // TGA - return 1; - } - if (!memcmp(buf, "II\x2a\x00", 4) || !memcmp(buf, "MM\x00\x2a", 4)) { - // TIFF - return 1; - } - if (!memcmp(buf, "RIFF", 4)) { - // might be webp - return 1; - } - if (!memcmp(buf + 4, "ftyphei", 7) || !memcmp(buf + 4, "ftypmif1", 8)) { - // HEIC/HEIF - note that this is only supported in imlib2-heic. Ordinary - // imlib2 releases do not support heic/heif images as of 2021-01. - return 1; - } - if ((buf[0] == 0xff && buf[1] == 0x0a) || !memcmp(buf, "\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a", 12)) { - // JXL - note that this is only supported in imlib2-jxl. Ordinary - // imlib2 releases do not support JXL images as of 2021-06. + if (getenv("FEH_SKIP_MAGIC")) { return 1; } - buf[15] = 0; - if (strstr((char *)buf, "XPM")) { - // XPM - return 1; + + if (!(magic = magic_open(MAGIC_MIME_TYPE | MAGIC_SYMLINK))) { + weprintf("unable to initialize magic library\n"); + return 0; } - if (strstr(file->filename, ".bz2") || strstr(file->filename, ".gz")) { - // Imlib2 supports compressed images. It relies on the filename to - // determine the appropriate loader and does not use magic bytes here. - return 1; + + if (magic_load(magic, NULL) != 0) { + weprintf("cannot load magic database: %s\n", magic_error(magic)); + magic_close(magic); + return 0; } - // moved to the end as this variable won't be set in most cases - if (getenv("FEH_SKIP_MAGIC")) { - return 1; + + mime_type = magic_file(magic, file->filename); + + if (mime_type) { + D(("file %s has mime type: %s\n", file->filename, mime_type)); + + if (strncmp(mime_type, "image/", 6) == 0) { + is_image = 1; + } + + /* imlib2 supports loading compressed images, let's have a look inside */ + if (strcmp(mime_type, "application/gzip") == 0 || + strcmp(mime_type, "application/x-bzip2") == 0 || + strcmp(mime_type, "application/x-xz") == 0) { + magic_setflags(magic, magic_getflags(magic) | MAGIC_COMPRESS); + mime_type = magic_file(magic, file->filename); + + if (mime_type) { + D(("uncompressed file %s has mime type: %s\n", file->filename, mime_type)); + + if (strncmp(mime_type, "image/", 6) == 0) { + is_image = 1; + } + } + } } - return 0; + + magic_close(magic); + + return is_image; +#else + (void)file; + return 1; +#endif } int feh_load_image(Imlib_Image * im, feh_file * file) -- cgit v1.2.3 From facb67f8438aa8ef18ffacdccfd0a2d1c2730e5c Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Wed, 12 Jan 2022 09:53:15 +0100 Subject: global initialization for libmagic Add a global `magic_t magic` and initialize it just once. Also `feh_is_image()` now calls itself to check compressed files, saving some duplicate code. --- src/feh.h | 4 +++ src/imlib.c | 94 +++++++++++++++++++++++++++++++++++-------------------------- src/main.c | 8 ++++++ 3 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/feh.h b/src/feh.h index 007c7c5..9afe238 100644 --- a/src/feh.h +++ b/src/feh.h @@ -145,6 +145,10 @@ void init_slideshow_mode(void); void init_list_mode(void); void init_loadables_mode(void); void init_unloadables_mode(void); +#ifdef HAVE_LIBMAGIC +void uninit_magic(void); +void init_magic(void); +#endif void feh_clean_exit(void); int feh_should_ignore_image(Imlib_Image * im); int feh_load_image(Imlib_Image * im, feh_file * file); diff --git a/src/imlib.c b/src/imlib.c index 70d459f..0accbdc 100644 --- a/src/imlib.c +++ b/src/imlib.c @@ -46,6 +46,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifdef HAVE_LIBMAGIC #include + +magic_t magic = NULL; #endif Display *disp = NULL; @@ -239,6 +241,33 @@ void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum fe } } +#ifdef HAVE_LIBMAGIC +void uninit_magic(void) +{ + if (!magic) { + return; + } + + magic_close(magic); + magic = NULL; +} +void init_magic(void) +{ + if (getenv("FEH_SKIP_MAGIC")) { + return; + } + + if (!(magic = magic_open(MAGIC_NONE))) { + weprintf("unable to initialize magic library\n"); + return; + } + + if (magic_load(magic, NULL) != 0) { + weprintf("cannot load magic database: %s\n", magic_error(magic)); + uninit_magic(); + } +} + /* * This is a workaround for an Imlib2 regression, causing unloadable image * detection to be excessively slow (and, thus, causing feh to hang for a while @@ -247,62 +276,47 @@ void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum fe * and * . */ -int feh_is_image(feh_file * file) +int feh_is_image(feh_file * file, int magic_flags) { -#ifdef HAVE_LIBMAGIC - magic_t magic; - const char * mime_type; - int is_image = 0; + const char * mime_type = NULL; - if (getenv("FEH_SKIP_MAGIC")) { + if (!magic) { return 1; } - if (!(magic = magic_open(MAGIC_MIME_TYPE | MAGIC_SYMLINK))) { - weprintf("unable to initialize magic library\n"); - return 0; - } + magic_setflags(magic, MAGIC_MIME_TYPE | MAGIC_SYMLINK | magic_flags); + mime_type = magic_file(magic, file->filename); - if (magic_load(magic, NULL) != 0) { - weprintf("cannot load magic database: %s\n", magic_error(magic)); - magic_close(magic); + if (!mime_type) { return 0; } - mime_type = magic_file(magic, file->filename); - - if (mime_type) { - D(("file %s has mime type: %s\n", file->filename, mime_type)); - - if (strncmp(mime_type, "image/", 6) == 0) { - is_image = 1; - } - - /* imlib2 supports loading compressed images, let's have a look inside */ - if (strcmp(mime_type, "application/gzip") == 0 || - strcmp(mime_type, "application/x-bzip2") == 0 || - strcmp(mime_type, "application/x-xz") == 0) { - magic_setflags(magic, magic_getflags(magic) | MAGIC_COMPRESS); - mime_type = magic_file(magic, file->filename); + D(("file %s has mime type: %s\n", file->filename, mime_type)); - if (mime_type) { - D(("uncompressed file %s has mime type: %s\n", file->filename, mime_type)); + if (strncmp(mime_type, "image/", 6) == 0) { + return 1; + } - if (strncmp(mime_type, "image/", 6) == 0) { - is_image = 1; - } - } - } + /* no infinite loop on compressed content, please */ + if (magic_flags) { + return 0; } - magic_close(magic); + /* imlib2 supports loading compressed images, let's have a look inside */ + if (strcmp(mime_type, "application/gzip") == 0 || + strcmp(mime_type, "application/x-bzip2") == 0 || + strcmp(mime_type, "application/x-xz") == 0) { + return feh_is_image(file, MAGIC_COMPRESS); + } - return is_image; + return 0; +} #else - (void)file; +int feh_is_image(__attribute__((unused)) feh_file * file, __attribute__((unused)) int magic_flags) +{ return 1; -#endif } +#endif int feh_load_image(Imlib_Image * im, feh_file * file) { @@ -326,7 +340,7 @@ int feh_load_image(Imlib_Image * im, feh_file * file) } } else { - if (feh_is_image(file)) { + if (feh_is_image(file, 0)) { *im = imlib_load_image_with_error_return(file->filename, &err); } else { feh_err = LOAD_ERROR_MAGICBYTES; diff --git a/src/main.c b/src/main.c index 3d124fd..34b667a 100644 --- a/src/main.c +++ b/src/main.c @@ -69,6 +69,10 @@ int main(int argc, char **argv) #endif } +#ifdef HAVE_LIBMAGIC + init_magic(); +#endif + feh_event_init(); if (opt.index) @@ -262,6 +266,10 @@ void feh_clean_exit(void) if(disp) XCloseDisplay(disp); +#ifdef HAVE_LIBMAGIC + uninit_magic(); +#endif + /* * Only restore the old terminal settings if * - we changed them in the first place -- cgit v1.2.3 From 26cd770c8732a4467e57cf3e7a5d4c2518836275 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Thu, 10 Feb 2022 21:41:35 +0100 Subject: Run init_magic before init_parse_options init_parse_options calls feh_prepare_filelist, which in turn calls feh_file_info_preload if opt.preload is set. This function will load all images in the filelist to determine their attributes, so we need to initialize libmagic before calling init_parse_options. --- src/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 34b667a..85e0504 100644 --- a/src/main.c +++ b/src/main.c @@ -49,6 +49,11 @@ int main(int argc, char **argv) srandom(getpid() * time(NULL) % ((unsigned int) -1)); setup_signal_handlers(); + +#ifdef HAVE_LIBMAGIC + init_magic(); +#endif + init_parse_options(argc, argv); init_imlib_fonts(); @@ -69,10 +74,6 @@ int main(int argc, char **argv) #endif } -#ifdef HAVE_LIBMAGIC - init_magic(); -#endif - feh_event_init(); if (opt.index) -- cgit v1.2.3