Commit 2d37cd91 authored by Birte Kristina Friesel's avatar Birte Kristina Friesel
Browse files

Check magic bytes before passing a file to Imlib2's loader

This works around a regression in Imlib2, which makes (un)loadable file
detection quite slow when handling e.g. large video files. See
<https://phab.enlightenment.org/T8739> and
<https://github.com/derf/feh/issues/505> for details.

Closes #505
parent 2e4fc901
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -162,6 +162,23 @@ Use
.Cm --conversion-timeout Ar timeout
with a non-negative value to enable support for these formats.
.
.Pp
.
As Imlib2 may take several seconds to determine whether it can load a file or
not
.Pq e.g. when attempting to open a large video ,
.Nm
checks each file's header before loading it.
If it looks like an image, it is passed on to Imlib2, otherwise, it is
assumed to be unloadable.
This greatly improves performance when working in directories with mixed files
.Pq i.e., directories which do not exclusively contain image files .
If you think that Imlib2 can load a file which
.Nm
has determined to be likely not an image, set the environment variable
.Qq FEH_SKIP_MAGIC
to pass all files directly to Imlib2, bypassing the header check.
The environment variable's value does not matter, it just needs to be set.
.
.Sh OPTIONS
.
+100 −3
Original line number Diff line number Diff line
@@ -181,7 +181,11 @@ void feh_imlib_print_load_error(char *file, winwidget w, Imlib_Load_Error err)
			break;
		case IMLIB_LOAD_ERROR_UNKNOWN:
		case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
			if (getenv("FEH_SKIP_MAGIC")) {
				im_weprintf(w, "%s - No Imlib2 loader for that file format", file);
			} else {
				im_weprintf(w, "%s - Does not look like an image (magic bytes missing)", file);
			}
			break;
		case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
			im_weprintf(w, "%s - Path specified is too long", file);
@@ -215,6 +219,94 @@ void feh_imlib_print_load_error(char *file, winwidget w, Imlib_Load_Error err)
	}
}

/*
 * 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
 * when encountering an unloadable image). We use magic byte detection to
 * avoid calling Imlib2 for files it probably cannot handle. See
 * <https://phab.enlightenment.org/T8739> and
 * <https://github.com/derf/feh/issues/505>.
 *
 * 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;
	}
	if (fread(buf, 1, 16, fh) != 16) {
		return 0;
	}
	fclose(fh);

	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;
	}
	buf[15] = 0;
	if (strstr((char *)buf, "XPM")) {
		// XPM
		return 1;
	}
	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;
	}
	// moved to the end as this variable won't be set in most cases
	if (getenv("FEH_SKIP_MAGIC")) {
		return 1;
	}
	return 0;
}

int feh_load_image(Imlib_Image * im, feh_file * file)
{
	Imlib_Load_Error err = IMLIB_LOAD_ERROR_NONE;
@@ -239,8 +331,13 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
		if (!tmpname)
			err = IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT;
	}
	else
	else {
		if (feh_is_image(file)) {
			*im = imlib_load_image_with_error_return(file->filename, &err);
		} else {
			err = IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT;
		}
	}

	if (opt.conversion_timeout >= 0 && (
			(err == IMLIB_LOAD_ERROR_UNKNOWN) ||
+1 −1
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ if ( $version =~ m{ Compile-time \s switches : \s .* help }ox ) {
}

my $re_warning
  = qr{${feh_name} WARNING: test/fail/... \- No Imlib2 loader for that file format\n};
  = qr{${feh_name} WARNING: test/fail/... \- Does not look like an image \(magic bytes missing\)\n};
my $re_loadable    = qr{test/ok/...};
my $re_unloadable  = qr{test/fail/...};
my $re_list_action = qr{test/ok/... 16x16};