From b666aefb18882eb78f7464c1288df1193d0305e3 Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: May 24 2021 13:48:06 +0000 Subject: Backport fix for CVE-2021-28675 - CVE-2021-28678, CVE-2021-25287-CVE-2021-25288 --- diff --git a/.gitignore b/.gitignore index 2692447..729d1f6 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ /crash-db8bfa78b19721225425530c5946217720d7df4e.sgi /crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif /CVE_testdata.tar.gz +/CVE-2021-286xx-testdata.tar.gz diff --git a/CVE-2021-25289.patch b/CVE-2021-25289.patch index 3ac8f26..117e972 100644 --- a/CVE-2021-25289.patch +++ b/CVE-2021-25289.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2.0-new/src/libImaging/TiffDecode.c ---- Pillow-7.2.0/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.215803588 +0100 -+++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.288803582 +0100 +--- Pillow-7.2.0/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.598397282 +0200 ++++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.680397284 +0200 @@ -305,7 +305,7 @@ int _decodeStripYCbCr(Imaging im, Imagin img.row_offset = state->y; rows_to_read = min(rows_per_strip, img.height - state->y); @@ -12,7 +12,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2. TIFFRGBAImageEnd(&img); diff -rupN --no-dereference Pillow-7.2.0/Tests/test_tiff_crashes.py Pillow-7.2.0-new/Tests/test_tiff_crashes.py --- Pillow-7.2.0/Tests/test_tiff_crashes.py 1970-01-01 01:00:00.000000000 +0100 -+++ Pillow-7.2.0-new/Tests/test_tiff_crashes.py 2021-03-06 11:40:18.289803582 +0100 ++++ Pillow-7.2.0-new/Tests/test_tiff_crashes.py 2021-05-24 15:38:10.682397284 +0200 @@ -0,0 +1,40 @@ +# Reproductions/tests for crashes/read errors in TiffDecode.c + diff --git a/CVE-2021-25290.patch b/CVE-2021-25290.patch index 7f7d313..8cfc5f7 100644 --- a/CVE-2021-25290.patch +++ b/CVE-2021-25290.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2.0-new/src/libImaging/TiffDecode.c ---- Pillow-7.2.0/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.357803577 +0100 -+++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.359803577 +0100 +--- Pillow-7.2.0/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.756397285 +0200 ++++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.761397285 +0200 @@ -36,6 +36,10 @@ tsize_t _tiffReadProc(thandle_t hdata, t TRACE(("_tiffReadProc: %d \n", (int)size)); dump_state(state); @@ -13,8 +13,8 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2. TRACE(("to_read: %d\n", (int)to_read)); diff -rupN --no-dereference Pillow-7.2.0/Tests/test_tiff_crashes.py Pillow-7.2.0-new/Tests/test_tiff_crashes.py ---- Pillow-7.2.0/Tests/test_tiff_crashes.py 2021-03-06 11:40:18.358803577 +0100 -+++ Pillow-7.2.0-new/Tests/test_tiff_crashes.py 2021-03-06 11:40:18.360803577 +0100 +--- Pillow-7.2.0/Tests/test_tiff_crashes.py 2021-05-24 15:38:10.756397285 +0200 ++++ Pillow-7.2.0-new/Tests/test_tiff_crashes.py 2021-05-24 15:38:10.761397285 +0200 @@ -21,8 +21,14 @@ from .helper import on_ci @pytest.mark.parametrize( "test_file", diff --git a/CVE-2021-25291.patch b/CVE-2021-25291.patch index 52867d9..3d570cd 100644 --- a/CVE-2021-25291.patch +++ b/CVE-2021-25291.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2.0-new/src/libImaging/TiffDecode.c ---- Pillow-7.2.0/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.426803572 +0100 -+++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.429803572 +0100 +--- Pillow-7.2.0/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.833397287 +0200 ++++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.838397287 +0200 @@ -36,10 +36,6 @@ tsize_t _tiffReadProc(thandle_t hdata, t TRACE(("_tiffReadProc: %d \n", (int)size)); dump_state(state); @@ -29,8 +29,8 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2. if (!TIFFReadRGBATile(tiff, col, row, buffer)) { return -1; diff -rupN --no-dereference Pillow-7.2.0/Tests/test_tiff_crashes.py Pillow-7.2.0-new/Tests/test_tiff_crashes.py ---- Pillow-7.2.0/Tests/test_tiff_crashes.py 2021-03-06 11:40:18.427803572 +0100 -+++ Pillow-7.2.0-new/Tests/test_tiff_crashes.py 2021-03-06 11:40:18.429803572 +0100 +--- Pillow-7.2.0/Tests/test_tiff_crashes.py 2021-05-24 15:38:10.833397287 +0200 ++++ Pillow-7.2.0-new/Tests/test_tiff_crashes.py 2021-05-24 15:38:10.838397287 +0200 @@ -29,6 +29,7 @@ from .helper import on_ci "Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif", "Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif", diff --git a/CVE-2021-25292.patch b/CVE-2021-25292.patch index ddfe958..77d5b4d 100644 --- a/CVE-2021-25292.patch +++ b/CVE-2021-25292.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/PdfParser.py Pillow-7.2.0-new/src/PIL/PdfParser.py --- Pillow-7.2.0/src/PIL/PdfParser.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/PIL/PdfParser.py 2021-03-06 11:40:18.498803566 +0100 ++++ Pillow-7.2.0-new/src/PIL/PdfParser.py 2021-05-24 15:38:10.915397289 +0200 @@ -580,8 +580,9 @@ class PdfParser: whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]" whitespace_optional = whitespace + b"*" diff --git a/CVE-2021-25293.patch b/CVE-2021-25293.patch index 97c9e2f..5c365fd 100644 --- a/CVE-2021-25293.patch +++ b/CVE-2021-25293.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/SgiRleDecode.c Pillow-7.2.0-new/src/libImaging/SgiRleDecode.c ---- Pillow-7.2.0/src/libImaging/SgiRleDecode.c 2021-03-06 11:40:18.286803582 +0100 -+++ Pillow-7.2.0-new/src/libImaging/SgiRleDecode.c 2021-03-06 11:40:18.573803561 +0100 +--- Pillow-7.2.0/src/libImaging/SgiRleDecode.c 2021-05-24 15:38:10.678397283 +0200 ++++ Pillow-7.2.0-new/src/libImaging/SgiRleDecode.c 2021-05-24 15:38:10.993397291 +0200 @@ -25,13 +25,58 @@ static void read4B(UINT32* dest, UINT8* *dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]); } @@ -191,8 +191,8 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/SgiRleDecode.c Pillow-7. + return 0; } diff -rupN --no-dereference Pillow-7.2.0/Tests/test_sgi_crash.py Pillow-7.2.0-new/Tests/test_sgi_crash.py ---- Pillow-7.2.0/Tests/test_sgi_crash.py 2021-03-06 11:40:18.286803582 +0100 -+++ Pillow-7.2.0-new/Tests/test_sgi_crash.py 2021-03-06 11:40:18.574803560 +0100 +--- Pillow-7.2.0/Tests/test_sgi_crash.py 2021-05-24 15:38:10.678397283 +0200 ++++ Pillow-7.2.0-new/Tests/test_sgi_crash.py 2021-05-24 15:38:10.993397291 +0200 @@ -10,6 +10,13 @@ from PIL import Image "Tests/images/sgi_crash.bin", "Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi", diff --git a/CVE-2021-2792x.patch b/CVE-2021-2792x.patch index e70bc0c..22c2148 100644 --- a/CVE-2021-2792x.patch +++ b/CVE-2021-2792x.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/BlpImagePlugin.py Pillow-7.2.0-new/src/PIL/BlpImagePlugin.py --- Pillow-7.2.0/src/PIL/BlpImagePlugin.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/PIL/BlpImagePlugin.py 2021-03-06 12:05:41.723686937 +0100 ++++ Pillow-7.2.0-new/src/PIL/BlpImagePlugin.py 2021-05-24 15:38:11.071397292 +0200 @@ -353,6 +353,7 @@ class BLP1Decoder(_BLPBaseDecoder): data = jpeg_header + data data = BytesIO(data) @@ -11,7 +11,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/BlpImagePlugin.py Pillow-7.2.0- self.mode = image.mode diff -rupN --no-dereference Pillow-7.2.0/src/PIL/IcnsImagePlugin.py Pillow-7.2.0-new/src/PIL/IcnsImagePlugin.py --- Pillow-7.2.0/src/PIL/IcnsImagePlugin.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/PIL/IcnsImagePlugin.py 2021-03-06 12:05:41.723686937 +0100 ++++ Pillow-7.2.0-new/src/PIL/IcnsImagePlugin.py 2021-05-24 15:38:11.071397292 +0200 @@ -106,6 +106,7 @@ def read_png_or_jpeg2000(fobj, start_len if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": fobj.seek(start) @@ -30,7 +30,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/IcnsImagePlugin.py Pillow-7.2.0 return {"RGBA": im} diff -rupN --no-dereference Pillow-7.2.0/src/PIL/IcoImagePlugin.py Pillow-7.2.0-new/src/PIL/IcoImagePlugin.py --- Pillow-7.2.0/src/PIL/IcoImagePlugin.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/PIL/IcoImagePlugin.py 2021-03-06 12:05:41.723686937 +0100 ++++ Pillow-7.2.0-new/src/PIL/IcoImagePlugin.py 2021-05-24 15:38:11.071397292 +0200 @@ -174,6 +174,7 @@ class IcoFile: if data[:8] == PngImagePlugin._MAGIC: # png frame @@ -41,7 +41,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/IcoImagePlugin.py Pillow-7.2.0- im = BmpImagePlugin.DibImageFile(self.buf) diff -rupN --no-dereference Pillow-7.2.0/Tests/test_file_icns.py Pillow-7.2.0-new/Tests/test_file_icns.py --- Pillow-7.2.0/Tests/test_file_icns.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/Tests/test_file_icns.py 2021-03-06 12:05:41.723686937 +0100 ++++ Pillow-7.2.0-new/Tests/test_file_icns.py 2021-05-24 15:38:11.071397292 +0200 @@ -138,3 +138,8 @@ def test_not_an_icns_file(): with io.BytesIO(b"invalid\n") as fp: with pytest.raises(SyntaxError): diff --git a/CVE-2021-286xx.patch b/CVE-2021-286xx.patch new file mode 100644 index 0000000..1c54994 --- /dev/null +++ b/CVE-2021-286xx.patch @@ -0,0 +1,4265 @@ +diff -rupN Pillow-7.2.0/src/libImaging/FliDecode.c Pillow-7.2.0-new/src/libImaging/FliDecode.c +--- Pillow-7.2.0/src/libImaging/FliDecode.c 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/src/libImaging/FliDecode.c 2021-05-24 15:38:11.150397294 +0200 +@@ -242,6 +242,11 @@ ImagingFliDecode(Imaging im, ImagingCode + return -1; + } + advance = I32(ptr); ++ if (advance == 0 ) { ++ // If there's no advance, we're in an infinite loop ++ state->errcode = IMAGING_CODEC_BROKEN; ++ return -1; ++ } + if (advance < 0 || advance > bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; +diff -rupN Pillow-7.2.0/src/libImaging/Jpeg2KDecode.c Pillow-7.2.0-new/src/libImaging/Jpeg2KDecode.c +--- Pillow-7.2.0/src/libImaging/Jpeg2KDecode.c 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/src/libImaging/Jpeg2KDecode.c 2021-05-24 15:38:11.150397294 +0200 +@@ -589,7 +589,7 @@ j2k_decode_entry(Imaging im, ImagingCode + j2k_unpacker_t unpack = NULL; + size_t buffer_size = 0, tile_bytes = 0; + unsigned n, tile_height, tile_width; +- int components; ++ int total_component_width = 0; + + + stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE); +@@ -751,23 +751,40 @@ j2k_decode_entry(Imaging im, ImagingCode + goto quick_exit; + } + ++ if (tile_info.nb_comps != image->numcomps) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ + /* Sometimes the tile_info.datasize we get back from openjpeg +- is less than numcomps*w*h, and we overflow in the ++ is less than sum(comp_bytes)*w*h, and we overflow in the + shuffle stage */ + + tile_width = tile_info.x1 - tile_info.x0; + tile_height = tile_info.y1 - tile_info.y0; +- components = tile_info.nb_comps == 3 ? 4 : tile_info.nb_comps; +- if (( tile_width > UINT_MAX / components ) || +- ( tile_height > UINT_MAX / components ) || +- ( tile_width > UINT_MAX / (tile_height * components )) || +- ( tile_height > UINT_MAX / (tile_width * components ))) { ++ ++ /* Total component width = sum (component_width) e.g, it's ++ legal for an la file to have a 1 byte width for l, and 4 for ++ a, and then a malicious file could have a smaller tile_bytes ++ */ ++ ++ for (n=0; n < tile_info.nb_comps; n++) { ++ // see csize /acsize calcs ++ int csize = (image->comps[n].prec + 7) >> 3; ++ csize = (csize == 3) ? 4 : csize; ++ total_component_width += csize; ++ } ++ if ((tile_width > UINT_MAX / total_component_width) || ++ (tile_height > UINT_MAX / total_component_width) || ++ (tile_width > UINT_MAX / (tile_height * total_component_width)) || ++ (tile_height > UINT_MAX / (tile_width * total_component_width))) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + goto quick_exit; + } + +- tile_bytes = tile_width * tile_height * components; ++ tile_bytes = tile_width * tile_height * total_component_width; + + if (tile_bytes > tile_info.data_size) { + tile_info.data_size = tile_bytes; +diff -rupN Pillow-7.2.0/src/libImaging/Jpeg2KDecode.c.orig Pillow-7.2.0-new/src/libImaging/Jpeg2KDecode.c.orig +--- Pillow-7.2.0/src/libImaging/Jpeg2KDecode.c.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/src/libImaging/Jpeg2KDecode.c.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,889 @@ ++/* ++ * The Python Imaging Library. ++ * $Id$ ++ * ++ * decoder for JPEG2000 image data. ++ * ++ * history: ++ * 2014-03-12 ajh Created ++ * ++ * Copyright (c) 2014 Coriolis Systems Limited ++ * Copyright (c) 2014 Alastair Houghton ++ * ++ * See the README file for details on usage and redistribution. ++ */ ++ ++#include "Imaging.h" ++ ++#ifdef HAVE_OPENJPEG ++ ++#include ++#include "Jpeg2K.h" ++ ++typedef struct { ++ OPJ_UINT32 tile_index; ++ OPJ_UINT32 data_size; ++ OPJ_INT32 x0, y0, x1, y1; ++ OPJ_UINT32 nb_comps; ++} JPEG2KTILEINFO; ++ ++/* -------------------------------------------------------------------- */ ++/* Error handler */ ++/* -------------------------------------------------------------------- */ ++ ++static void ++j2k_error(const char *msg, void *client_data) ++{ ++ JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *) client_data; ++ free((void *)state->error_msg); ++ state->error_msg = strdup(msg); ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Buffer input stream */ ++/* -------------------------------------------------------------------- */ ++ ++static OPJ_SIZE_T ++j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) ++{ ++ ImagingCodecState state = (ImagingCodecState)p_user_data; ++ ++ size_t len = _imaging_read_pyFd(state->fd, p_buffer, p_nb_bytes); ++ ++ return len ? len : (OPJ_SIZE_T)-1; ++} ++ ++static OPJ_OFF_T ++j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) ++{ ++ off_t pos; ++ ImagingCodecState state = (ImagingCodecState)p_user_data; ++ ++ _imaging_seek_pyFd(state->fd, p_nb_bytes, SEEK_CUR); ++ pos = _imaging_tell_pyFd(state->fd); ++ ++ return pos ? pos : (OPJ_OFF_T)-1; ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Unpackers */ ++/* -------------------------------------------------------------------- */ ++ ++typedef void (*j2k_unpacker_t)(opj_image_t *in, ++ const JPEG2KTILEINFO *tileInfo, ++ const UINT8 *data, ++ Imaging im); ++ ++struct j2k_decode_unpacker { ++ const char *mode; ++ OPJ_COLOR_SPACE color_space; ++ unsigned components; ++ j2k_unpacker_t unpacker; ++}; ++ ++static inline ++unsigned j2ku_shift(unsigned x, int n) ++{ ++ if (n < 0) { ++ return x >> -n; ++ } else { ++ return x << n; ++ } ++} ++ ++static void ++j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shift = 8 - in->comps[0].prec; ++ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; ++ int csiz = (in->comps[0].prec + 7) >> 3; ++ ++ unsigned x, y; ++ ++ if (csiz == 3) { ++ csiz = 4; ++ } ++ ++ if (shift < 0) { ++ offset += 1 << (-shift - 1); ++ } ++ ++ /* csiz*h*w + offset = tileinfo.datasize */ ++ switch (csiz) { ++ case 1: ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data = &tiledata[y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ *row++ = j2ku_shift(offset + *data++, shift); ++ } ++ } ++ break; ++ case 2: ++ for (y = 0; y < h; ++y) { ++ const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ *row++ = j2ku_shift(offset + *data++, shift); ++ } ++ } ++ break; ++ case 4: ++ for (y = 0; y < h; ++y) { ++ const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ *row++ = j2ku_shift(offset + *data++, shift); ++ } ++ } ++ break; ++ } ++} ++ ++ ++static void ++j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shift = 16 - in->comps[0].prec; ++ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; ++ int csiz = (in->comps[0].prec + 7) >> 3; ++ ++ unsigned x, y; ++ ++ if (csiz == 3) { ++ csiz = 4; ++ } ++ ++ if (shift < 0) { ++ offset += 1 << (-shift - 1); ++ } ++ ++ switch (csiz) { ++ case 1: ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data = &tiledata[y * w]; ++ UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ *row++ = j2ku_shift(offset + *data++, shift); ++ } ++ } ++ break; ++ case 2: ++ for (y = 0; y < h; ++y) { ++ const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; ++ UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ *row++ = j2ku_shift(offset + *data++, shift); ++ } ++ } ++ break; ++ case 4: ++ for (y = 0; y < h; ++y) { ++ const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; ++ UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ *row++ = j2ku_shift(offset + *data++, shift); ++ } ++ } ++ break; ++ } ++} ++ ++ ++static void ++j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shift = 8 - in->comps[0].prec; ++ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; ++ int csiz = (in->comps[0].prec + 7) >> 3; ++ ++ unsigned x, y; ++ ++ if (shift < 0) { ++ offset += 1 << (-shift - 1); ++ } ++ ++ if (csiz == 3) { ++ csiz = 4; ++ } ++ ++ switch (csiz) { ++ case 1: ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data = &tiledata[y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ UINT8 byte = j2ku_shift(offset + *data++, shift); ++ row[0] = row[1] = row[2] = byte; ++ row[3] = 0xff; ++ row += 4; ++ } ++ } ++ break; ++ case 2: ++ for (y = 0; y < h; ++y) { ++ const UINT16 *data = (UINT16 *)&tiledata[2 * y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ UINT8 byte = j2ku_shift(offset + *data++, shift); ++ row[0] = row[1] = row[2] = byte; ++ row[3] = 0xff; ++ row += 4; ++ } ++ } ++ break; ++ case 4: ++ for (y = 0; y < h; ++y) { ++ const UINT32 *data = (UINT32 *)&tiledata[4 * y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; ++ for (x = 0; x < w; ++x) { ++ UINT8 byte = j2ku_shift(offset + *data++, shift); ++ row[0] = row[1] = row[2] = byte; ++ row[3] = 0xff; ++ row += 4; ++ } ++ } ++ break; ++ } ++} ++ ++static void ++j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shift = 8 - in->comps[0].prec; ++ int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; ++ int csiz = (in->comps[0].prec + 7) >> 3; ++ int ashift = 8 - in->comps[1].prec; ++ int aoffset = in->comps[1].sgnd ? 1 << (in->comps[1].prec - 1) : 0; ++ int acsiz = (in->comps[1].prec + 7) >> 3; ++ const UINT8 *atiledata; ++ ++ unsigned x, y; ++ ++ if (csiz == 3) { ++ csiz = 4; ++ } ++ if (acsiz == 3) { ++ acsiz = 4; ++ } ++ ++ if (shift < 0) { ++ offset += 1 << (-shift - 1); ++ } ++ if (ashift < 0) { ++ aoffset += 1 << (-ashift - 1); ++ } ++ ++ atiledata = tiledata + csiz * w * h; ++ ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data = &tiledata[csiz * y * w]; ++ const UINT8 *adata = &atiledata[acsiz * y * w]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; ++ for (x = 0; x < w; ++x) { ++ UINT32 word = 0, aword = 0, byte; ++ ++ switch (csiz) { ++ case 1: word = *data++; break; ++ case 2: word = *(const UINT16 *)data; data += 2; break; ++ case 4: word = *(const UINT32 *)data; data += 4; break; ++ } ++ ++ switch (acsiz) { ++ case 1: aword = *adata++; break; ++ case 2: aword = *(const UINT16 *)adata; adata += 2; break; ++ case 4: aword = *(const UINT32 *)adata; adata += 4; break; ++ } ++ ++ byte = j2ku_shift(offset + word, shift); ++ row[0] = row[1] = row[2] = byte; ++ row[3] = j2ku_shift(aoffset + aword, ashift); ++ row += 4; ++ } ++ } ++} ++ ++static void ++j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shifts[3], offsets[3], csiz[3]; ++ const UINT8 *cdata[3]; ++ const UINT8 *cptr = tiledata; ++ unsigned n, x, y; ++ ++ for (n = 0; n < 3; ++n) { ++ cdata[n] = cptr; ++ shifts[n] = 8 - in->comps[n].prec; ++ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; ++ csiz[n] = (in->comps[n].prec + 7) >> 3; ++ ++ if (csiz[n] == 3) { ++ csiz[n] = 4; ++ } ++ ++ if (shifts[n] < 0) { ++ offsets[n] += 1 << (-shifts[n] - 1); ++ } ++ ++ cptr += csiz[n] * w * h; ++ } ++ ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data[3]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; ++ for (n = 0; n < 3; ++n) { ++ data[n] = &cdata[n][csiz[n] * y * w]; ++ } ++ ++ for (x = 0; x < w; ++x) { ++ for (n = 0; n < 3; ++n) { ++ UINT32 word = 0; ++ ++ switch (csiz[n]) { ++ case 1: word = *data[n]++; break; ++ case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; ++ case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; ++ } ++ ++ row[n] = j2ku_shift(offsets[n] + word, shifts[n]); ++ } ++ row[3] = 0xff; ++ row += 4; ++ } ++ } ++} ++ ++static void ++j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shifts[3], offsets[3], csiz[3]; ++ const UINT8 *cdata[3]; ++ const UINT8 *cptr = tiledata; ++ unsigned n, x, y; ++ ++ for (n = 0; n < 3; ++n) { ++ cdata[n] = cptr; ++ shifts[n] = 8 - in->comps[n].prec; ++ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; ++ csiz[n] = (in->comps[n].prec + 7) >> 3; ++ ++ if (csiz[n] == 3) { ++ csiz[n] = 4; ++ } ++ ++ if (shifts[n] < 0) { ++ offsets[n] += 1 << (-shifts[n] - 1); ++ } ++ ++ cptr += csiz[n] * w * h; ++ } ++ ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data[3]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; ++ UINT8 *row_start = row; ++ for (n = 0; n < 3; ++n) { ++ data[n] = &cdata[n][csiz[n] * y * w]; ++ } ++ ++ for (x = 0; x < w; ++x) { ++ for (n = 0; n < 3; ++n) { ++ UINT32 word = 0; ++ ++ switch (csiz[n]) { ++ case 1: word = *data[n]++; break; ++ case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; ++ case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; ++ } ++ ++ row[n] = j2ku_shift(offsets[n] + word, shifts[n]); ++ } ++ row[3] = 0xff; ++ row += 4; ++ } ++ ++ ImagingConvertYCbCr2RGB(row_start, row_start, w); ++ } ++} ++ ++static void ++j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shifts[4], offsets[4], csiz[4]; ++ const UINT8 *cdata[4]; ++ const UINT8 *cptr = tiledata; ++ unsigned n, x, y; ++ ++ for (n = 0; n < 4; ++n) { ++ cdata[n] = cptr; ++ shifts[n] = 8 - in->comps[n].prec; ++ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; ++ csiz[n] = (in->comps[n].prec + 7) >> 3; ++ ++ if (csiz[n] == 3) { ++ csiz[n] = 4; ++ } ++ ++ if (shifts[n] < 0) { ++ offsets[n] += 1 << (-shifts[n] - 1); ++ } ++ ++ cptr += csiz[n] * w * h; ++ } ++ ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data[4]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; ++ for (n = 0; n < 4; ++n) { ++ data[n] = &cdata[n][csiz[n] * y * w]; ++ } ++ ++ for (x = 0; x < w; ++x) { ++ for (n = 0; n < 4; ++n) { ++ UINT32 word = 0; ++ ++ switch (csiz[n]) { ++ case 1: word = *data[n]++; break; ++ case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; ++ case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; ++ } ++ ++ row[n] = j2ku_shift(offsets[n] + word, shifts[n]); ++ } ++ row += 4; ++ } ++ } ++} ++ ++static void ++j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, ++ const UINT8 *tiledata, Imaging im) ++{ ++ unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; ++ unsigned w = tileinfo->x1 - tileinfo->x0; ++ unsigned h = tileinfo->y1 - tileinfo->y0; ++ ++ int shifts[4], offsets[4], csiz[4]; ++ const UINT8 *cdata[4]; ++ const UINT8 *cptr = tiledata; ++ unsigned n, x, y; ++ ++ for (n = 0; n < 4; ++n) { ++ cdata[n] = cptr; ++ shifts[n] = 8 - in->comps[n].prec; ++ offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; ++ csiz[n] = (in->comps[n].prec + 7) >> 3; ++ ++ if (csiz[n] == 3) { ++ csiz[n] = 4; ++ } ++ ++ if (shifts[n] < 0) { ++ offsets[n] += 1 << (-shifts[n] - 1); ++ } ++ ++ cptr += csiz[n] * w * h; ++ } ++ ++ for (y = 0; y < h; ++y) { ++ const UINT8 *data[4]; ++ UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; ++ UINT8 *row_start = row; ++ for (n = 0; n < 4; ++n) { ++ data[n] = &cdata[n][csiz[n] * y * w]; ++ } ++ ++ for (x = 0; x < w; ++x) { ++ for (n = 0; n < 4; ++n) { ++ UINT32 word = 0; ++ ++ switch (csiz[n]) { ++ case 1: word = *data[n]++; break; ++ case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; ++ case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; ++ } ++ ++ row[n] = j2ku_shift(offsets[n] + word, shifts[n]); ++ } ++ row += 4; ++ } ++ ++ ImagingConvertYCbCr2RGB(row_start, row_start, w); ++ } ++} ++ ++static const struct j2k_decode_unpacker j2k_unpackers[] = { ++ { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, ++ { "I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, ++ { "I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, ++ { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, ++ { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, ++ { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, ++ { "RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb }, ++ { "RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb }, ++ { "RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb }, ++ { "RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb }, ++ { "RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, ++ { "RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, ++ { "RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb }, ++ { "RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb }, ++ { "RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba }, ++ { "RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba }, ++}; ++ ++/* -------------------------------------------------------------------- */ ++/* Decoder */ ++/* -------------------------------------------------------------------- */ ++ ++enum { ++ J2K_STATE_START = 0, ++ J2K_STATE_DECODING = 1, ++ J2K_STATE_DONE = 2, ++ J2K_STATE_FAILED = 3, ++}; ++ ++static int ++j2k_decode_entry(Imaging im, ImagingCodecState state) ++{ ++ JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context; ++ opj_stream_t *stream = NULL; ++ opj_image_t *image = NULL; ++ opj_codec_t *codec = NULL; ++ opj_dparameters_t params; ++ OPJ_COLOR_SPACE color_space; ++ j2k_unpacker_t unpack = NULL; ++ size_t buffer_size = 0, tile_bytes = 0; ++ unsigned n, tile_height, tile_width; ++ int components; ++ ++ ++ stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE); ++ ++ if (!stream) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ opj_stream_set_read_function(stream, j2k_read); ++ opj_stream_set_skip_function(stream, j2k_skip); ++ ++ /* OpenJPEG 2.0 doesn't have OPJ_VERSION_MAJOR */ ++#ifndef OPJ_VERSION_MAJOR ++ opj_stream_set_user_data(stream, state); ++#else ++ opj_stream_set_user_data(stream, state, NULL); ++ ++ /* Hack: if we don't know the length, the largest file we can ++ possibly support is 4GB. We can't go larger than this, because ++ OpenJPEG truncates this value for the final box in the file, and ++ the box lengths in OpenJPEG are currently 32 bit. */ ++ if (context->length < 0) { ++ opj_stream_set_user_data_length(stream, 0xffffffff); ++ } else { ++ opj_stream_set_user_data_length(stream, context->length); ++ } ++#endif ++ ++ /* Setup decompression context */ ++ context->error_msg = NULL; ++ ++ opj_set_default_decoder_parameters(¶ms); ++ params.cp_reduce = context->reduce; ++ params.cp_layer = context->layers; ++ ++ codec = opj_create_decompress(context->format); ++ ++ if (!codec) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ opj_set_error_handler(codec, j2k_error, context); ++ opj_setup_decoder(codec, ¶ms); ++ ++ if (!opj_read_header(stream, codec, &image)) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ /* Check that this image is something we can handle */ ++ if (image->numcomps < 1 || image->numcomps > 4 ++ || image->color_space == OPJ_CLRSPC_UNKNOWN) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ for (n = 1; n < image->numcomps; ++n) { ++ if (image->comps[n].dx != 1 || image->comps[n].dy != 1) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ } ++ ++ /* ++ Colorspace Number of components PIL mode ++ ------------------------------------------------------ ++ sRGB 3 RGB ++ sRGB 4 RGBA ++ gray 1 L or I ++ gray 2 LA ++ YCC 3 YCbCr ++ ++ ++ If colorspace is unspecified, we assume: ++ ++ Number of components Colorspace ++ ----------------------------------------- ++ 1 gray ++ 2 gray (+ alpha) ++ 3 sRGB ++ 4 sRGB (+ alpha) ++ ++ */ ++ ++ /* Find the correct unpacker */ ++ color_space = image->color_space; ++ ++ if (color_space == OPJ_CLRSPC_UNSPECIFIED) { ++ switch (image->numcomps) { ++ case 1: case 2: color_space = OPJ_CLRSPC_GRAY; break; ++ case 3: case 4: color_space = OPJ_CLRSPC_SRGB; break; ++ } ++ } ++ ++ for (n = 0; n < sizeof(j2k_unpackers) / sizeof (j2k_unpackers[0]); ++n) { ++ if (color_space == j2k_unpackers[n].color_space ++ && image->numcomps == j2k_unpackers[n].components ++ && strcmp (im->mode, j2k_unpackers[n].mode) == 0) { ++ unpack = j2k_unpackers[n].unpacker; ++ break; ++ } ++ } ++ ++ if (!unpack) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ /* Decode the image tile-by-tile; this means we only need use as much ++ memory as is required for one tile's worth of components. */ ++ for (;;) { ++ JPEG2KTILEINFO tile_info; ++ OPJ_BOOL should_continue; ++ unsigned correction = (1 << params.cp_reduce) - 1; ++ ++ if (!opj_read_tile_header(codec, ++ stream, ++ &tile_info.tile_index, ++ &tile_info.data_size, ++ &tile_info.x0, &tile_info.y0, ++ &tile_info.x1, &tile_info.y1, ++ &tile_info.nb_comps, ++ &should_continue)) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ if (!should_continue) { ++ break; ++ } ++ ++ /* Adjust the tile co-ordinates based on the reduction (OpenJPEG ++ doesn't do this for us) */ ++ tile_info.x0 = (tile_info.x0 + correction) >> context->reduce; ++ tile_info.y0 = (tile_info.y0 + correction) >> context->reduce; ++ tile_info.x1 = (tile_info.x1 + correction) >> context->reduce; ++ tile_info.y1 = (tile_info.y1 + correction) >> context->reduce; ++ ++ /* Check the tile bounds; if the tile is outside the image area, ++ or if it has a negative width or height (i.e. the coordinates are ++ swapped), bail. */ ++ if (tile_info.x0 >= tile_info.x1 ++ || tile_info.y0 >= tile_info.y1 ++ || tile_info.x0 < image->x0 ++ || tile_info.y0 < image->y0 ++ || tile_info.x1 - image->x0 > im->xsize ++ || tile_info.y1 - image->y0 > im->ysize) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ /* Sometimes the tile_info.datasize we get back from openjpeg ++ is less than numcomps*w*h, and we overflow in the ++ shuffle stage */ ++ ++ tile_width = tile_info.x1 - tile_info.x0; ++ tile_height = tile_info.y1 - tile_info.y0; ++ components = tile_info.nb_comps == 3 ? 4 : tile_info.nb_comps; ++ if (( tile_width > UINT_MAX / components ) || ++ ( tile_height > UINT_MAX / components ) || ++ ( tile_width > UINT_MAX / (tile_height * components )) || ++ ( tile_height > UINT_MAX / (tile_width * components ))) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ tile_bytes = tile_width * tile_height * components; ++ ++ if (tile_bytes > tile_info.data_size) { ++ tile_info.data_size = tile_bytes; ++ } ++ ++ if (buffer_size < tile_info.data_size) { ++ /* malloc check ok, overflow and tile size sanity check above */ ++ UINT8 *new = realloc (state->buffer, tile_info.data_size); ++ if (!new) { ++ state->errcode = IMAGING_CODEC_MEMORY; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ state->buffer = new; ++ buffer_size = tile_info.data_size; ++ } ++ ++ ++ if (!opj_decode_tile_data(codec, ++ tile_info.tile_index, ++ (OPJ_BYTE *)state->buffer, ++ tile_info.data_size, ++ stream)) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ unpack(image, &tile_info, state->buffer, im); ++ } ++ ++ if (!opj_end_decompress(codec, stream)) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ goto quick_exit; ++ } ++ ++ state->state = J2K_STATE_DONE; ++ state->errcode = IMAGING_CODEC_END; ++ ++ if (context->pfile) { ++ if(fclose(context->pfile)){ ++ context->pfile = NULL; ++ } ++ } ++ ++ quick_exit: ++ if (codec) { ++ opj_destroy_codec(codec); ++ } ++ if (image) { ++ opj_image_destroy(image); ++ } ++ if (stream) { ++ opj_stream_destroy(stream); ++ } ++ ++ return -1; ++} ++ ++int ++ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) ++{ ++ ++ if (bytes){ ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ return -1; ++ } ++ ++ if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED) { ++ return -1; ++ } ++ ++ if (state->state == J2K_STATE_START) { ++ state->state = J2K_STATE_DECODING; ++ ++ return j2k_decode_entry(im, state); ++ } ++ ++ if (state->state == J2K_STATE_DECODING) { ++ state->errcode = IMAGING_CODEC_BROKEN; ++ state->state = J2K_STATE_FAILED; ++ return -1; ++ } ++ return -1; ++} ++ ++/* -------------------------------------------------------------------- */ ++/* Cleanup */ ++/* -------------------------------------------------------------------- */ ++ ++int ++ImagingJpeg2KDecodeCleanup(ImagingCodecState state) { ++ JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context; ++ ++ if (context->error_msg) { ++ free ((void *)context->error_msg); ++ } ++ ++ context->error_msg = NULL; ++ ++ return -1; ++} ++ ++const char * ++ImagingJpeg2KVersion(void) ++{ ++ return opj_version(); ++} ++ ++#endif /* HAVE_OPENJPEG */ ++ ++/* ++ * Local Variables: ++ * c-basic-offset: 4 ++ * End: ++ * ++ */ +diff -rupN Pillow-7.2.0/src/PIL/BlpImagePlugin.py Pillow-7.2.0-new/src/PIL/BlpImagePlugin.py +--- Pillow-7.2.0/src/PIL/BlpImagePlugin.py 2021-05-24 15:38:11.147397294 +0200 ++++ Pillow-7.2.0-new/src/PIL/BlpImagePlugin.py 2021-05-24 15:38:11.150397294 +0200 +@@ -286,33 +286,36 @@ class _BLPBaseDecoder(ImageFile.PyDecode + raise OSError("Truncated Blp file") from e + return 0, 0 + ++ def _safe_read(self, length): ++ return ImageFile._safe_read(self.fd, length) ++ + def _read_palette(self): + ret = [] + for i in range(256): + try: +- b, g, r, a = struct.unpack("<4B", self.fd.read(4)) ++ b, g, r, a = struct.unpack("<4B", self._safe_read(4)) + except struct.error: + break + ret.append((b, g, r, a)) + return ret + + def _read_blp_header(self): +- (self._blp_compression,) = struct.unpack("read method. + :param size: Number of bytes to read. +- :returns: A string containing up to size bytes of data. ++ :returns: A string containing size bytes of data. ++ ++ Raises an OSError if the file is truncated and the read cannot be completed ++ + """ + if size <= 0: + return b"" + if size <= SAFEBLOCK: +- return fp.read(size) ++ data = fp.read(size) ++ if len(data) < size: ++ raise OSError("Truncated File Read") ++ return data + data = [] + while size > 0: + block = fp.read(min(size, SAFEBLOCK)) +@@ -564,6 +570,8 @@ def _safe_read(fp, size): + break + data.append(block) + size -= len(block) ++ if sum(len(d) for d in data) < size: ++ raise OSError("Truncated File Read") + return b"".join(data) + + +diff -rupN Pillow-7.2.0/src/PIL/ImageFile.py.orig Pillow-7.2.0-new/src/PIL/ImageFile.py.orig +--- Pillow-7.2.0/src/PIL/ImageFile.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/src/PIL/ImageFile.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,693 @@ ++# ++# The Python Imaging Library. ++# $Id$ ++# ++# base class for image file handlers ++# ++# history: ++# 1995-09-09 fl Created ++# 1996-03-11 fl Fixed load mechanism. ++# 1996-04-15 fl Added pcx/xbm decoders. ++# 1996-04-30 fl Added encoders. ++# 1996-12-14 fl Added load helpers ++# 1997-01-11 fl Use encode_to_file where possible ++# 1997-08-27 fl Flush output in _save ++# 1998-03-05 fl Use memory mapping for some modes ++# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B" ++# 1999-05-31 fl Added image parser ++# 2000-10-12 fl Set readonly flag on memory-mapped images ++# 2002-03-20 fl Use better messages for common decoder errors ++# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available ++# 2003-10-30 fl Added StubImageFile class ++# 2004-02-25 fl Made incremental parser more robust ++# ++# Copyright (c) 1997-2004 by Secret Labs AB ++# Copyright (c) 1995-2004 by Fredrik Lundh ++# ++# See the README file for information on usage and redistribution. ++# ++ ++import io ++import struct ++import sys ++import warnings ++ ++from . import Image ++from ._util import isPath ++ ++MAXBLOCK = 65536 ++ ++SAFEBLOCK = 1024 * 1024 ++ ++LOAD_TRUNCATED_IMAGES = False ++ ++ERRORS = { ++ -1: "image buffer overrun error", ++ -2: "decoding error", ++ -3: "unknown error", ++ -8: "bad configuration", ++ -9: "out of memory error", ++} ++ ++ ++# ++# -------------------------------------------------------------------- ++# Helpers ++ ++ ++def raise_oserror(error): ++ try: ++ message = Image.core.getcodecstatus(error) ++ except AttributeError: ++ message = ERRORS.get(error) ++ if not message: ++ message = "decoder error %d" % error ++ raise OSError(message + " when reading image file") ++ ++ ++def raise_ioerror(error): ++ warnings.warn( ++ "raise_ioerror is deprecated and will be removed in a future release. " ++ "Use raise_oserror instead.", ++ DeprecationWarning, ++ ) ++ return raise_oserror(error) ++ ++ ++def _tilesort(t): ++ # sort on offset ++ return t[2] ++ ++ ++# ++# -------------------------------------------------------------------- ++# ImageFile base class ++ ++ ++class ImageFile(Image.Image): ++ """Base class for image file format handlers.""" ++ ++ def __init__(self, fp=None, filename=None): ++ super().__init__() ++ ++ self._min_frame = 0 ++ ++ self.custom_mimetype = None ++ ++ self.tile = None ++ self.readonly = 1 # until we know better ++ ++ self.decoderconfig = () ++ self.decodermaxblock = MAXBLOCK ++ ++ if isPath(fp): ++ # filename ++ self.fp = open(fp, "rb") ++ self.filename = fp ++ self._exclusive_fp = True ++ else: ++ # stream ++ self.fp = fp ++ self.filename = filename ++ # can be overridden ++ self._exclusive_fp = None ++ ++ try: ++ try: ++ self._open() ++ except ( ++ IndexError, # end of data ++ TypeError, # end of data (ord) ++ KeyError, # unsupported mode ++ EOFError, # got header but not the first frame ++ struct.error, ++ ) as v: ++ raise SyntaxError(v) from v ++ ++ if not self.mode or self.size[0] <= 0: ++ raise SyntaxError("not identified by this driver") ++ except BaseException: ++ # close the file only if we have opened it this constructor ++ if self._exclusive_fp: ++ self.fp.close() ++ raise ++ ++ def get_format_mimetype(self): ++ if self.custom_mimetype: ++ return self.custom_mimetype ++ if self.format is not None: ++ return Image.MIME.get(self.format.upper()) ++ ++ def verify(self): ++ """Check file integrity""" ++ ++ # raise exception if something's wrong. must be called ++ # directly after open, and closes file when finished. ++ if self._exclusive_fp: ++ self.fp.close() ++ self.fp = None ++ ++ def load(self): ++ """Load image data based on tile list""" ++ ++ if self.tile is None: ++ raise OSError("cannot load this image") ++ ++ pixel = Image.Image.load(self) ++ if not self.tile: ++ return pixel ++ ++ self.map = None ++ use_mmap = self.filename and len(self.tile) == 1 ++ # As of pypy 2.1.0, memory mapping was failing here. ++ use_mmap = use_mmap and not hasattr(sys, "pypy_version_info") ++ ++ readonly = 0 ++ ++ # look for read/seek overrides ++ try: ++ read = self.load_read ++ # don't use mmap if there are custom read/seek functions ++ use_mmap = False ++ except AttributeError: ++ read = self.fp.read ++ ++ try: ++ seek = self.load_seek ++ use_mmap = False ++ except AttributeError: ++ seek = self.fp.seek ++ ++ if use_mmap: ++ # try memory mapping ++ decoder_name, extents, offset, args = self.tile[0] ++ if ( ++ decoder_name == "raw" ++ and len(args) >= 3 ++ and args[0] == self.mode ++ and args[0] in Image._MAPMODES ++ ): ++ try: ++ if hasattr(Image.core, "map"): ++ # use built-in mapper WIN32 only ++ self.map = Image.core.map(self.filename) ++ self.map.seek(offset) ++ self.im = self.map.readimage( ++ self.mode, self.size, args[1], args[2] ++ ) ++ else: ++ # use mmap, if possible ++ import mmap ++ ++ with open(self.filename, "r") as fp: ++ self.map = mmap.mmap( ++ fp.fileno(), 0, access=mmap.ACCESS_READ ++ ) ++ self.im = Image.core.map_buffer( ++ self.map, self.size, decoder_name, offset, args ++ ) ++ readonly = 1 ++ # After trashing self.im, ++ # we might need to reload the palette data. ++ if self.palette: ++ self.palette.dirty = 1 ++ except (AttributeError, OSError, ImportError): ++ self.map = None ++ ++ self.load_prepare() ++ err_code = -3 # initialize to unknown error ++ if not self.map: ++ # sort tiles in file order ++ self.tile.sort(key=_tilesort) ++ ++ try: ++ # FIXME: This is a hack to handle TIFF's JpegTables tag. ++ prefix = self.tile_prefix ++ except AttributeError: ++ prefix = b"" ++ ++ for decoder_name, extents, offset, args in self.tile: ++ decoder = Image._getdecoder( ++ self.mode, decoder_name, args, self.decoderconfig ++ ) ++ try: ++ seek(offset) ++ decoder.setimage(self.im, extents) ++ if decoder.pulls_fd: ++ decoder.setfd(self.fp) ++ status, err_code = decoder.decode(b"") ++ else: ++ b = prefix ++ while True: ++ try: ++ s = read(self.decodermaxblock) ++ except (IndexError, struct.error) as e: ++ # truncated png/gif ++ if LOAD_TRUNCATED_IMAGES: ++ break ++ else: ++ raise OSError("image file is truncated") from e ++ ++ if not s: # truncated jpeg ++ if LOAD_TRUNCATED_IMAGES: ++ break ++ else: ++ raise OSError( ++ "image file is truncated " ++ "(%d bytes not processed)" % len(b) ++ ) ++ ++ b = b + s ++ n, err_code = decoder.decode(b) ++ if n < 0: ++ break ++ b = b[n:] ++ finally: ++ # Need to cleanup here to prevent leaks ++ decoder.cleanup() ++ ++ self.tile = [] ++ self.readonly = readonly ++ ++ self.load_end() ++ ++ if self._exclusive_fp and self._close_exclusive_fp_after_loading: ++ self.fp.close() ++ self.fp = None ++ ++ if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0: ++ # still raised if decoder fails to return anything ++ raise_oserror(err_code) ++ ++ return Image.Image.load(self) ++ ++ def load_prepare(self): ++ # create image memory if necessary ++ if not self.im or self.im.mode != self.mode or self.im.size != self.size: ++ self.im = Image.core.new(self.mode, self.size) ++ # create palette (optional) ++ if self.mode == "P": ++ Image.Image.load(self) ++ ++ def load_end(self): ++ # may be overridden ++ pass ++ ++ # may be defined for contained formats ++ # def load_seek(self, pos): ++ # pass ++ ++ # may be defined for blocked formats (e.g. PNG) ++ # def load_read(self, bytes): ++ # pass ++ ++ def _seek_check(self, frame): ++ if ( ++ frame < self._min_frame ++ # Only check upper limit on frames if additional seek operations ++ # are not required to do so ++ or ( ++ not (hasattr(self, "_n_frames") and self._n_frames is None) ++ and frame >= self.n_frames + self._min_frame ++ ) ++ ): ++ raise EOFError("attempt to seek outside sequence") ++ ++ return self.tell() != frame ++ ++ ++class StubImageFile(ImageFile): ++ """ ++ Base class for stub image loaders. ++ ++ A stub loader is an image loader that can identify files of a ++ certain format, but relies on external code to load the file. ++ """ ++ ++ def _open(self): ++ raise NotImplementedError("StubImageFile subclass must implement _open") ++ ++ def load(self): ++ loader = self._load() ++ if loader is None: ++ raise OSError("cannot find loader for this %s file" % self.format) ++ image = loader.load(self) ++ assert image is not None ++ # become the other object (!) ++ self.__class__ = image.__class__ ++ self.__dict__ = image.__dict__ ++ ++ def _load(self): ++ """(Hook) Find actual image loader.""" ++ raise NotImplementedError("StubImageFile subclass must implement _load") ++ ++ ++class Parser: ++ """ ++ Incremental image parser. This class implements the standard ++ feed/close consumer interface. ++ """ ++ ++ incremental = None ++ image = None ++ data = None ++ decoder = None ++ offset = 0 ++ finished = 0 ++ ++ def reset(self): ++ """ ++ (Consumer) Reset the parser. Note that you can only call this ++ method immediately after you've created a parser; parser ++ instances cannot be reused. ++ """ ++ assert self.data is None, "cannot reuse parsers" ++ ++ def feed(self, data): ++ """ ++ (Consumer) Feed data to the parser. ++ ++ :param data: A string buffer. ++ :exception OSError: If the parser failed to parse the image file. ++ """ ++ # collect data ++ ++ if self.finished: ++ return ++ ++ if self.data is None: ++ self.data = data ++ else: ++ self.data = self.data + data ++ ++ # parse what we have ++ if self.decoder: ++ ++ if self.offset > 0: ++ # skip header ++ skip = min(len(self.data), self.offset) ++ self.data = self.data[skip:] ++ self.offset = self.offset - skip ++ if self.offset > 0 or not self.data: ++ return ++ ++ n, e = self.decoder.decode(self.data) ++ ++ if n < 0: ++ # end of stream ++ self.data = None ++ self.finished = 1 ++ if e < 0: ++ # decoding error ++ self.image = None ++ raise_oserror(e) ++ else: ++ # end of image ++ return ++ self.data = self.data[n:] ++ ++ elif self.image: ++ ++ # if we end up here with no decoder, this file cannot ++ # be incrementally parsed. wait until we've gotten all ++ # available data ++ pass ++ ++ else: ++ ++ # attempt to open this file ++ try: ++ with io.BytesIO(self.data) as fp: ++ im = Image.open(fp) ++ except OSError: ++ # traceback.print_exc() ++ pass # not enough data ++ else: ++ flag = hasattr(im, "load_seek") or hasattr(im, "load_read") ++ if flag or len(im.tile) != 1: ++ # custom load code, or multiple tiles ++ self.decode = None ++ else: ++ # initialize decoder ++ im.load_prepare() ++ d, e, o, a = im.tile[0] ++ im.tile = [] ++ self.decoder = Image._getdecoder(im.mode, d, a, im.decoderconfig) ++ self.decoder.setimage(im.im, e) ++ ++ # calculate decoder offset ++ self.offset = o ++ if self.offset <= len(self.data): ++ self.data = self.data[self.offset :] ++ self.offset = 0 ++ ++ self.image = im ++ ++ def __enter__(self): ++ return self ++ ++ def __exit__(self, *args): ++ self.close() ++ ++ def close(self): ++ """ ++ (Consumer) Close the stream. ++ ++ :returns: An image object. ++ :exception OSError: If the parser failed to parse the image file either ++ because it cannot be identified or cannot be ++ decoded. ++ """ ++ # finish decoding ++ if self.decoder: ++ # get rid of what's left in the buffers ++ self.feed(b"") ++ self.data = self.decoder = None ++ if not self.finished: ++ raise OSError("image was incomplete") ++ if not self.image: ++ raise OSError("cannot parse this image") ++ if self.data: ++ # incremental parsing not possible; reopen the file ++ # not that we have all data ++ with io.BytesIO(self.data) as fp: ++ try: ++ self.image = Image.open(fp) ++ finally: ++ self.image.load() ++ return self.image ++ ++ ++# -------------------------------------------------------------------- ++ ++ ++def _save(im, fp, tile, bufsize=0): ++ """Helper to save image based on tile list ++ ++ :param im: Image object. ++ :param fp: File object. ++ :param tile: Tile list. ++ :param bufsize: Optional buffer size ++ """ ++ ++ im.load() ++ if not hasattr(im, "encoderconfig"): ++ im.encoderconfig = () ++ tile.sort(key=_tilesort) ++ # FIXME: make MAXBLOCK a configuration parameter ++ # It would be great if we could have the encoder specify what it needs ++ # But, it would need at least the image size in most cases. RawEncode is ++ # a tricky case. ++ bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c ++ if fp == sys.stdout: ++ fp.flush() ++ return ++ try: ++ fh = fp.fileno() ++ fp.flush() ++ except (AttributeError, io.UnsupportedOperation) as e: ++ # compress to Python file-compatible object ++ for e, b, o, a in tile: ++ e = Image._getencoder(im.mode, e, a, im.encoderconfig) ++ if o > 0: ++ fp.seek(o) ++ e.setimage(im.im, b) ++ if e.pushes_fd: ++ e.setfd(fp) ++ l, s = e.encode_to_pyfd() ++ else: ++ while True: ++ l, s, d = e.encode(bufsize) ++ fp.write(d) ++ if s: ++ break ++ if s < 0: ++ raise OSError("encoder error %d when writing image file" % s) from e ++ e.cleanup() ++ else: ++ # slight speedup: compress to real file object ++ for e, b, o, a in tile: ++ e = Image._getencoder(im.mode, e, a, im.encoderconfig) ++ if o > 0: ++ fp.seek(o) ++ e.setimage(im.im, b) ++ if e.pushes_fd: ++ e.setfd(fp) ++ l, s = e.encode_to_pyfd() ++ else: ++ s = e.encode_to_file(fh, bufsize) ++ if s < 0: ++ raise OSError("encoder error %d when writing image file" % s) ++ e.cleanup() ++ if hasattr(fp, "flush"): ++ fp.flush() ++ ++ ++def _safe_read(fp, size): ++ """ ++ Reads large blocks in a safe way. Unlike fp.read(n), this function ++ doesn't trust the user. If the requested size is larger than ++ SAFEBLOCK, the file is read block by block. ++ ++ :param fp: File handle. Must implement a read method. ++ :param size: Number of bytes to read. ++ :returns: A string containing up to size bytes of data. ++ """ ++ if size <= 0: ++ return b"" ++ if size <= SAFEBLOCK: ++ return fp.read(size) ++ data = [] ++ while size > 0: ++ block = fp.read(min(size, SAFEBLOCK)) ++ if not block: ++ break ++ data.append(block) ++ size -= len(block) ++ return b"".join(data) ++ ++ ++class PyCodecState: ++ def __init__(self): ++ self.xsize = 0 ++ self.ysize = 0 ++ self.xoff = 0 ++ self.yoff = 0 ++ ++ def extents(self): ++ return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize) ++ ++ ++class PyDecoder: ++ """ ++ Python implementation of a format decoder. Override this class and ++ add the decoding logic in the `decode` method. ++ ++ See :ref:`Writing Your Own File Decoder in Python` ++ """ ++ ++ _pulls_fd = False ++ ++ def __init__(self, mode, *args): ++ self.im = None ++ self.state = PyCodecState() ++ self.fd = None ++ self.mode = mode ++ self.init(args) ++ ++ def init(self, args): ++ """ ++ Override to perform decoder specific initialization ++ ++ :param args: Array of args items from the tile entry ++ :returns: None ++ """ ++ self.args = args ++ ++ @property ++ def pulls_fd(self): ++ return self._pulls_fd ++ ++ def decode(self, buffer): ++ """ ++ Override to perform the decoding process. ++ ++ :param buffer: A bytes object with the data to be decoded. ++ :returns: A tuple of (bytes consumed, errcode). ++ If finished with decoding return <0 for the bytes consumed. ++ Err codes are from `ERRORS` ++ """ ++ raise NotImplementedError() ++ ++ def cleanup(self): ++ """ ++ Override to perform decoder specific cleanup ++ ++ :returns: None ++ """ ++ pass ++ ++ def setfd(self, fd): ++ """ ++ Called from ImageFile to set the python file-like object ++ ++ :param fd: A python file-like object ++ :returns: None ++ """ ++ self.fd = fd ++ ++ def setimage(self, im, extents=None): ++ """ ++ Called from ImageFile to set the core output image for the decoder ++ ++ :param im: A core image object ++ :param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle ++ for this tile ++ :returns: None ++ """ ++ ++ # following c code ++ self.im = im ++ ++ if extents: ++ (x0, y0, x1, y1) = extents ++ else: ++ (x0, y0, x1, y1) = (0, 0, 0, 0) ++ ++ if x0 == 0 and x1 == 0: ++ self.state.xsize, self.state.ysize = self.im.size ++ else: ++ self.state.xoff = x0 ++ self.state.yoff = y0 ++ self.state.xsize = x1 - x0 ++ self.state.ysize = y1 - y0 ++ ++ if self.state.xsize <= 0 or self.state.ysize <= 0: ++ raise ValueError("Size cannot be negative") ++ ++ if ( ++ self.state.xsize + self.state.xoff > self.im.size[0] ++ or self.state.ysize + self.state.yoff > self.im.size[1] ++ ): ++ raise ValueError("Tile cannot extend outside image") ++ ++ def set_as_raw(self, data, rawmode=None): ++ """ ++ Convenience method to set the internal image from a stream of raw data ++ ++ :param data: Bytes to be set ++ :param rawmode: The rawmode to be used for the decoder. ++ If not specified, it will default to the mode of the image ++ :returns: None ++ """ ++ ++ if not rawmode: ++ rawmode = self.mode ++ d = Image._getdecoder(self.mode, "raw", (rawmode)) ++ d.setimage(self.im, self.state.extents()) ++ s = d.decode(data) ++ ++ if s[0] >= 0: ++ raise ValueError("not enough image data") ++ if s[1] != 0: ++ raise ValueError("cannot decode image data") +diff -rupN Pillow-7.2.0/src/PIL/ImageFont.py Pillow-7.2.0-new/src/PIL/ImageFont.py +--- Pillow-7.2.0/src/PIL/ImageFont.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/src/PIL/ImageFont.py 2021-05-24 15:39:59.010399707 +0200 +@@ -472,6 +472,7 @@ class FreeTypeFont: + text, mode == "1", direction, features, language + ) + size = size[0] + stroke_width * 2, size[1] + stroke_width * 2 ++ Image._decompression_bomb_check(size) + im = fill("L", size, 0) + self.font.render( + text, im.id, mode == "1", direction, features, language, stroke_width +diff -rupN Pillow-7.2.0/src/PIL/PsdImagePlugin.py Pillow-7.2.0-new/src/PIL/PsdImagePlugin.py +--- Pillow-7.2.0/src/PIL/PsdImagePlugin.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/src/PIL/PsdImagePlugin.py 2021-05-24 15:38:11.151397294 +0200 +@@ -117,7 +117,8 @@ class PsdImageFile(ImageFile.ImageFile): + end = self.fp.tell() + size + size = i32(read(4)) + if size: +- self.layers = _layerinfo(self.fp) ++ _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size)) ++ self.layers = _layerinfo(_layer_data, size) + self.fp.seek(end) + self.n_frames = len(self.layers) + self.is_animated = self.n_frames > 1 +@@ -169,11 +170,20 @@ class PsdImageFile(ImageFile.ImageFile): + self.__fp = None + + +-def _layerinfo(file): ++def _layerinfo(fp, ct_bytes): + # read layerinfo block + layers = [] +- read = file.read +- for i in range(abs(i16(read(2)))): ++ ++ def read(size): ++ return ImageFile._safe_read(fp, size) ++ ++ ct = i16(read(2)) ++ ++ # sanity check ++ if ct_bytes < (abs(ct) * 20): ++ raise SyntaxError("Layer block too short for number of layers requested") ++ ++ for i in range(abs(ct)): + + # bounding box + y0 = i32(read(4)) +@@ -184,7 +194,8 @@ def _layerinfo(file): + # image info + info = [] + mode = [] +- types = list(range(i16(read(2)))) ++ ct_types = i16(read(2)) ++ types = list(range(ct_types)) + if len(types) > 4: + continue + +@@ -217,16 +228,16 @@ def _layerinfo(file): + size = i32(read(4)) # length of the extra data field + combined = 0 + if size: +- data_end = file.tell() + size ++ data_end = fp.tell() + size + + length = i32(read(4)) + if length: +- file.seek(length - 16, io.SEEK_CUR) ++ fp.seek(length - 16, io.SEEK_CUR) + combined += length + 4 + + length = i32(read(4)) + if length: +- file.seek(length, io.SEEK_CUR) ++ fp.seek(length, io.SEEK_CUR) + combined += length + 4 + + length = i8(read(1)) +@@ -236,7 +247,7 @@ def _layerinfo(file): + name = read(length).decode("latin-1", "replace") + combined += length + 1 + +- file.seek(data_end) ++ fp.seek(data_end) + layers.append((name, mode, (x0, y0, x1, y1))) + + # get tiles +@@ -244,7 +255,7 @@ def _layerinfo(file): + for name, mode, bbox in layers: + tile = [] + for m in mode: +- t = _maketile(file, m, bbox, 1) ++ t = _maketile(fp, m, bbox, 1) + if t: + tile.extend(t) + layers[i] = name, mode, bbox, tile +diff -rupN Pillow-7.2.0/src/PIL/PsdImagePlugin.py.orig Pillow-7.2.0-new/src/PIL/PsdImagePlugin.py.orig +--- Pillow-7.2.0/src/PIL/PsdImagePlugin.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/src/PIL/PsdImagePlugin.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,309 @@ ++# ++# The Python Imaging Library ++# $Id$ ++# ++# Adobe PSD 2.5/3.0 file handling ++# ++# History: ++# 1995-09-01 fl Created ++# 1997-01-03 fl Read most PSD images ++# 1997-01-18 fl Fixed P and CMYK support ++# 2001-10-21 fl Added seek/tell support (for layers) ++# ++# Copyright (c) 1997-2001 by Secret Labs AB. ++# Copyright (c) 1995-2001 by Fredrik Lundh ++# ++# See the README file for information on usage and redistribution. ++# ++ ++import io ++ ++from . import Image, ImageFile, ImagePalette ++from ._binary import i8, i16be as i16, i32be as i32 ++ ++MODES = { ++ # (photoshop mode, bits) -> (pil mode, required channels) ++ (0, 1): ("1", 1), ++ (0, 8): ("L", 1), ++ (1, 8): ("L", 1), ++ (2, 8): ("P", 1), ++ (3, 8): ("RGB", 3), ++ (4, 8): ("CMYK", 4), ++ (7, 8): ("L", 1), # FIXME: multilayer ++ (8, 8): ("L", 1), # duotone ++ (9, 8): ("LAB", 3), ++} ++ ++ ++# --------------------------------------------------------------------. ++# read PSD images ++ ++ ++def _accept(prefix): ++ return prefix[:4] == b"8BPS" ++ ++ ++## ++# Image plugin for Photoshop images. ++ ++ ++class PsdImageFile(ImageFile.ImageFile): ++ ++ format = "PSD" ++ format_description = "Adobe Photoshop" ++ _close_exclusive_fp_after_loading = False ++ ++ def _open(self): ++ ++ read = self.fp.read ++ ++ # ++ # header ++ ++ s = read(26) ++ if not _accept(s) or i16(s[4:]) != 1: ++ raise SyntaxError("not a PSD file") ++ ++ psd_bits = i16(s[22:]) ++ psd_channels = i16(s[12:]) ++ psd_mode = i16(s[24:]) ++ ++ mode, channels = MODES[(psd_mode, psd_bits)] ++ ++ if channels > psd_channels: ++ raise OSError("not enough channels") ++ ++ self.mode = mode ++ self._size = i32(s[18:]), i32(s[14:]) ++ ++ # ++ # color mode data ++ ++ size = i32(read(4)) ++ if size: ++ data = read(size) ++ if mode == "P" and size == 768: ++ self.palette = ImagePalette.raw("RGB;L", data) ++ ++ # ++ # image resources ++ ++ self.resources = [] ++ ++ size = i32(read(4)) ++ if size: ++ # load resources ++ end = self.fp.tell() + size ++ while self.fp.tell() < end: ++ read(4) # signature ++ id = i16(read(2)) ++ name = read(i8(read(1))) ++ if not (len(name) & 1): ++ read(1) # padding ++ data = read(i32(read(4))) ++ if len(data) & 1: ++ read(1) # padding ++ self.resources.append((id, name, data)) ++ if id == 1039: # ICC profile ++ self.info["icc_profile"] = data ++ ++ # ++ # layer and mask information ++ ++ self.layers = [] ++ ++ size = i32(read(4)) ++ if size: ++ end = self.fp.tell() + size ++ size = i32(read(4)) ++ if size: ++ self.layers = _layerinfo(self.fp) ++ self.fp.seek(end) ++ self.n_frames = len(self.layers) ++ self.is_animated = self.n_frames > 1 ++ ++ # ++ # image descriptor ++ ++ self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) ++ ++ # keep the file open ++ self.__fp = self.fp ++ self.frame = 1 ++ self._min_frame = 1 ++ ++ def seek(self, layer): ++ if not self._seek_check(layer): ++ return ++ ++ # seek to given layer (1..max) ++ try: ++ name, mode, bbox, tile = self.layers[layer - 1] ++ self.mode = mode ++ self.tile = tile ++ self.frame = layer ++ self.fp = self.__fp ++ return name, bbox ++ except IndexError as e: ++ raise EOFError("no such layer") from e ++ ++ def tell(self): ++ # return layer number (0=image, 1..max=layers) ++ return self.frame ++ ++ def load_prepare(self): ++ # create image memory if necessary ++ if not self.im or self.im.mode != self.mode or self.im.size != self.size: ++ self.im = Image.core.fill(self.mode, self.size, 0) ++ # create palette (optional) ++ if self.mode == "P": ++ Image.Image.load(self) ++ ++ def _close__fp(self): ++ try: ++ if self.__fp != self.fp: ++ self.__fp.close() ++ except AttributeError: ++ pass ++ finally: ++ self.__fp = None ++ ++ ++def _layerinfo(file): ++ # read layerinfo block ++ layers = [] ++ read = file.read ++ for i in range(abs(i16(read(2)))): ++ ++ # bounding box ++ y0 = i32(read(4)) ++ x0 = i32(read(4)) ++ y1 = i32(read(4)) ++ x1 = i32(read(4)) ++ ++ # image info ++ info = [] ++ mode = [] ++ types = list(range(i16(read(2)))) ++ if len(types) > 4: ++ continue ++ ++ for i in types: ++ type = i16(read(2)) ++ ++ if type == 65535: ++ m = "A" ++ else: ++ m = "RGBA"[type] ++ ++ mode.append(m) ++ size = i32(read(4)) ++ info.append((m, size)) ++ ++ # figure out the image mode ++ mode.sort() ++ if mode == ["R"]: ++ mode = "L" ++ elif mode == ["B", "G", "R"]: ++ mode = "RGB" ++ elif mode == ["A", "B", "G", "R"]: ++ mode = "RGBA" ++ else: ++ mode = None # unknown ++ ++ # skip over blend flags and extra information ++ read(12) # filler ++ name = "" ++ size = i32(read(4)) # length of the extra data field ++ combined = 0 ++ if size: ++ data_end = file.tell() + size ++ ++ length = i32(read(4)) ++ if length: ++ file.seek(length - 16, io.SEEK_CUR) ++ combined += length + 4 ++ ++ length = i32(read(4)) ++ if length: ++ file.seek(length, io.SEEK_CUR) ++ combined += length + 4 ++ ++ length = i8(read(1)) ++ if length: ++ # Don't know the proper encoding, ++ # Latin-1 should be a good guess ++ name = read(length).decode("latin-1", "replace") ++ combined += length + 1 ++ ++ file.seek(data_end) ++ layers.append((name, mode, (x0, y0, x1, y1))) ++ ++ # get tiles ++ i = 0 ++ for name, mode, bbox in layers: ++ tile = [] ++ for m in mode: ++ t = _maketile(file, m, bbox, 1) ++ if t: ++ tile.extend(t) ++ layers[i] = name, mode, bbox, tile ++ i += 1 ++ ++ return layers ++ ++ ++def _maketile(file, mode, bbox, channels): ++ ++ tile = None ++ read = file.read ++ ++ compression = i16(read(2)) ++ ++ xsize = bbox[2] - bbox[0] ++ ysize = bbox[3] - bbox[1] ++ ++ offset = file.tell() ++ ++ if compression == 0: ++ # ++ # raw compression ++ tile = [] ++ for channel in range(channels): ++ layer = mode[channel] ++ if mode == "CMYK": ++ layer += ";I" ++ tile.append(("raw", bbox, offset, layer)) ++ offset = offset + xsize * ysize ++ ++ elif compression == 1: ++ # ++ # packbits compression ++ i = 0 ++ tile = [] ++ bytecount = read(channels * ysize * 2) ++ offset = file.tell() ++ for channel in range(channels): ++ layer = mode[channel] ++ if mode == "CMYK": ++ layer += ";I" ++ tile.append(("packbits", bbox, offset, layer)) ++ for y in range(ysize): ++ offset = offset + i16(bytecount[i : i + 2]) ++ i += 2 ++ ++ file.seek(offset) ++ ++ if offset & 1: ++ read(1) # padding ++ ++ return tile ++ ++ ++# -------------------------------------------------------------------- ++# registry ++ ++ ++Image.register_open(PsdImageFile.format, PsdImageFile, _accept) ++ ++Image.register_extension(PsdImageFile.format, ".psd") +diff -rupN Pillow-7.2.0/Tests/test_decompression_bomb.py Pillow-7.2.0-new/Tests/test_decompression_bomb.py +--- Pillow-7.2.0/Tests/test_decompression_bomb.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_decompression_bomb.py 2021-05-24 15:38:11.151397294 +0200 +@@ -51,6 +51,7 @@ class TestDecompressionBomb: + with Image.open(TEST_FILE): + pass + ++ @pytest.mark.xfail(reason="different exception") + def test_exception_ico(self): + with pytest.raises(Image.DecompressionBombError): + Image.open("Tests/images/decompression_bomb.ico") +diff -rupN Pillow-7.2.0/Tests/test_decompression_bomb.py.orig Pillow-7.2.0-new/Tests/test_decompression_bomb.py.orig +--- Pillow-7.2.0/Tests/test_decompression_bomb.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/Tests/test_decompression_bomb.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,98 @@ ++import pytest ++from PIL import Image ++ ++from .helper import hopper ++ ++TEST_FILE = "Tests/images/hopper.ppm" ++ ++ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS ++ ++ ++class TestDecompressionBomb: ++ @classmethod ++ def teardown_class(self): ++ Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT ++ ++ def test_no_warning_small_file(self): ++ # Implicit assert: no warning. ++ # A warning would cause a failure. ++ with Image.open(TEST_FILE): ++ pass ++ ++ def test_no_warning_no_limit(self): ++ # Arrange ++ # Turn limit off ++ Image.MAX_IMAGE_PIXELS = None ++ assert Image.MAX_IMAGE_PIXELS is None ++ ++ # Act / Assert ++ # Implicit assert: no warning. ++ # A warning would cause a failure. ++ with Image.open(TEST_FILE): ++ pass ++ ++ def test_warning(self): ++ # Set limit to trigger warning on the test file ++ Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 ++ assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 ++ ++ def open(): ++ with Image.open(TEST_FILE): ++ pass ++ ++ pytest.warns(Image.DecompressionBombWarning, open) ++ ++ def test_exception(self): ++ # Set limit to trigger exception on the test file ++ Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 ++ assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1 ++ ++ with pytest.raises(Image.DecompressionBombError): ++ with Image.open(TEST_FILE): ++ pass ++ ++ def test_exception_ico(self): ++ with pytest.raises(Image.DecompressionBombError): ++ Image.open("Tests/images/decompression_bomb.ico") ++ ++ def test_exception_gif(self): ++ with pytest.raises(Image.DecompressionBombError): ++ Image.open("Tests/images/decompression_bomb.gif") ++ ++ ++class TestDecompressionCrop: ++ @classmethod ++ def setup_class(self): ++ width, height = 128, 128 ++ Image.MAX_IMAGE_PIXELS = height * width * 4 - 1 ++ ++ @classmethod ++ def teardown_class(self): ++ Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT ++ ++ def testEnlargeCrop(self): ++ # Crops can extend the extents, therefore we should have the ++ # same decompression bomb warnings on them. ++ with hopper() as src: ++ box = (0, 0, src.width * 2, src.height * 2) ++ pytest.warns(Image.DecompressionBombWarning, src.crop, box) ++ ++ def test_crop_decompression_checks(self): ++ ++ im = Image.new("RGB", (100, 100)) ++ ++ good_values = ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)) ++ ++ warning_values = ((-160, -160, 99, 99), (160, 160, -99, -99)) ++ ++ error_values = ((-99909, -99990, 99999, 99999), (99909, 99990, -99999, -99999)) ++ ++ for value in good_values: ++ assert im.crop(value).size == (9, 9) ++ ++ for value in warning_values: ++ pytest.warns(Image.DecompressionBombWarning, im.crop, value) ++ ++ for value in error_values: ++ with pytest.raises(Image.DecompressionBombError): ++ im.crop(value) +diff -rupN Pillow-7.2.0/Tests/test_file_apng.py Pillow-7.2.0-new/Tests/test_file_apng.py +--- Pillow-7.2.0/Tests/test_file_apng.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_apng.py 2021-05-24 15:38:11.152397294 +0200 +@@ -286,7 +286,7 @@ def test_apng_syntax_errors(): + exception = e + assert exception is None + +- with pytest.raises(SyntaxError): ++ with pytest.raises(OSError): + with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im: + im.seek(im.n_frames - 1) + im.load() +diff -rupN Pillow-7.2.0/Tests/test_file_apng.py.orig Pillow-7.2.0-new/Tests/test_file_apng.py.orig +--- Pillow-7.2.0/Tests/test_file_apng.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/Tests/test_file_apng.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,582 @@ ++import pytest ++from PIL import Image, ImageSequence, PngImagePlugin ++ ++ ++# APNG browser support tests and fixtures via: ++# https://philip.html5.org/tests/apng/tests.html ++# (referenced from https://wiki.mozilla.org/APNG_Specification) ++def test_apng_basic(): ++ with Image.open("Tests/images/apng/single_frame.png") as im: ++ assert not im.is_animated ++ assert im.n_frames == 1 ++ assert im.get_format_mimetype() == "image/apng" ++ assert im.info.get("default_image") is None ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/single_frame_default.png") as im: ++ assert im.is_animated ++ assert im.n_frames == 2 ++ assert im.get_format_mimetype() == "image/apng" ++ assert im.info.get("default_image") ++ assert im.getpixel((0, 0)) == (255, 0, 0, 255) ++ assert im.getpixel((64, 32)) == (255, 0, 0, 255) ++ im.seek(1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ # test out of bounds seek ++ with pytest.raises(EOFError): ++ im.seek(2) ++ ++ # test rewind support ++ im.seek(0) ++ assert im.getpixel((0, 0)) == (255, 0, 0, 255) ++ assert im.getpixel((64, 32)) == (255, 0, 0, 255) ++ im.seek(1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_fdat(): ++ with Image.open("Tests/images/apng/split_fdat.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/split_fdat_zero_chunk.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_dispose(): ++ with Image.open("Tests/images/apng/dispose_op_none.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/dispose_op_background.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 0, 0, 0) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ with Image.open("Tests/images/apng/dispose_op_background_final.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/dispose_op_previous.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 0, 0, 0) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ ++def test_apng_dispose_region(): ++ with Image.open("Tests/images/apng/dispose_op_none_region.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 0, 0, 0) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ with Image.open("Tests/images/apng/dispose_op_background_region.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 0, 255, 255) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_dispose_op_background_p_mode(): ++ with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im: ++ im.seek(1) ++ im.load() ++ assert im.size == (128, 64) ++ ++ ++def test_apng_blend(): ++ with Image.open("Tests/images/apng/blend_op_source_solid.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 0, 0, 0) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 2) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 2) ++ ++ with Image.open("Tests/images/apng/blend_op_over.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 97) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_chunk_order(): ++ with Image.open("Tests/images/apng/fctl_actl.png") as im: ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_delay(): ++ with Image.open("Tests/images/apng/delay.png") as im: ++ im.seek(1) ++ assert im.info.get("duration") == 500.0 ++ im.seek(2) ++ assert im.info.get("duration") == 1000.0 ++ im.seek(3) ++ assert im.info.get("duration") == 500.0 ++ im.seek(4) ++ assert im.info.get("duration") == 1000.0 ++ ++ with Image.open("Tests/images/apng/delay_round.png") as im: ++ im.seek(1) ++ assert im.info.get("duration") == 500.0 ++ im.seek(2) ++ assert im.info.get("duration") == 1000.0 ++ ++ with Image.open("Tests/images/apng/delay_short_max.png") as im: ++ im.seek(1) ++ assert im.info.get("duration") == 500.0 ++ im.seek(2) ++ assert im.info.get("duration") == 1000.0 ++ ++ with Image.open("Tests/images/apng/delay_zero_denom.png") as im: ++ im.seek(1) ++ assert im.info.get("duration") == 500.0 ++ im.seek(2) ++ assert im.info.get("duration") == 1000.0 ++ ++ with Image.open("Tests/images/apng/delay_zero_numer.png") as im: ++ im.seek(1) ++ assert im.info.get("duration") == 0.0 ++ im.seek(2) ++ assert im.info.get("duration") == 0.0 ++ im.seek(3) ++ assert im.info.get("duration") == 500.0 ++ im.seek(4) ++ assert im.info.get("duration") == 1000.0 ++ ++ ++def test_apng_num_plays(): ++ with Image.open("Tests/images/apng/num_plays.png") as im: ++ assert im.info.get("loop") == 0 ++ ++ with Image.open("Tests/images/apng/num_plays_1.png") as im: ++ assert im.info.get("loop") == 1 ++ ++ ++def test_apng_mode(): ++ with Image.open("Tests/images/apng/mode_16bit.png") as im: ++ assert im.mode == "RGBA" ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (0, 0, 128, 191) ++ assert im.getpixel((64, 32)) == (0, 0, 128, 191) ++ ++ with Image.open("Tests/images/apng/mode_greyscale.png") as im: ++ assert im.mode == "L" ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == 128 ++ assert im.getpixel((64, 32)) == 255 ++ ++ with Image.open("Tests/images/apng/mode_greyscale_alpha.png") as im: ++ assert im.mode == "LA" ++ im.seek(im.n_frames - 1) ++ assert im.getpixel((0, 0)) == (128, 191) ++ assert im.getpixel((64, 32)) == (128, 191) ++ ++ with Image.open("Tests/images/apng/mode_palette.png") as im: ++ assert im.mode == "P" ++ im.seek(im.n_frames - 1) ++ im = im.convert("RGB") ++ assert im.getpixel((0, 0)) == (0, 255, 0) ++ assert im.getpixel((64, 32)) == (0, 255, 0) ++ ++ with Image.open("Tests/images/apng/mode_palette_alpha.png") as im: ++ assert im.mode == "P" ++ im.seek(im.n_frames - 1) ++ im = im.convert("RGBA") ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im: ++ assert im.mode == "P" ++ im.seek(im.n_frames - 1) ++ im = im.convert("RGBA") ++ assert im.getpixel((0, 0)) == (0, 0, 255, 128) ++ assert im.getpixel((64, 32)) == (0, 0, 255, 128) ++ ++ ++def test_apng_chunk_errors(): ++ with Image.open("Tests/images/apng/chunk_no_actl.png") as im: ++ assert not im.is_animated ++ ++ def open(): ++ with Image.open("Tests/images/apng/chunk_multi_actl.png") as im: ++ im.load() ++ assert not im.is_animated ++ ++ pytest.warns(UserWarning, open) ++ ++ with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im: ++ assert not im.is_animated ++ ++ with Image.open("Tests/images/apng/chunk_no_fctl.png") as im: ++ with pytest.raises(SyntaxError): ++ im.seek(im.n_frames - 1) ++ ++ with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im: ++ with pytest.raises(SyntaxError): ++ im.seek(im.n_frames - 1) ++ ++ with Image.open("Tests/images/apng/chunk_no_fdat.png") as im: ++ with pytest.raises(SyntaxError): ++ im.seek(im.n_frames - 1) ++ ++ ++def test_apng_syntax_errors(): ++ def open_frames_zero(): ++ with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: ++ assert not im.is_animated ++ with pytest.raises(OSError): ++ im.load() ++ ++ pytest.warns(UserWarning, open_frames_zero) ++ ++ def open_frames_zero_default(): ++ with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im: ++ assert not im.is_animated ++ im.load() ++ ++ pytest.warns(UserWarning, open_frames_zero_default) ++ ++ # we can handle this case gracefully ++ exception = None ++ with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im: ++ try: ++ im.seek(im.n_frames - 1) ++ except Exception as e: ++ exception = e ++ assert exception is None ++ ++ with pytest.raises(SyntaxError): ++ with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im: ++ im.seek(im.n_frames - 1) ++ im.load() ++ ++ def open(): ++ with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: ++ assert not im.is_animated ++ im.load() ++ ++ pytest.warns(UserWarning, open) ++ ++ ++def test_apng_sequence_errors(): ++ test_files = [ ++ "sequence_start.png", ++ "sequence_gap.png", ++ "sequence_repeat.png", ++ "sequence_repeat_chunk.png", ++ "sequence_reorder.png", ++ "sequence_reorder_chunk.png", ++ "sequence_fdat_fctl.png", ++ ] ++ for f in test_files: ++ with pytest.raises(SyntaxError): ++ with Image.open("Tests/images/apng/{0}".format(f)) as im: ++ im.seek(im.n_frames - 1) ++ im.load() ++ ++ ++def test_apng_save(tmp_path): ++ with Image.open("Tests/images/apng/single_frame.png") as im: ++ test_file = str(tmp_path / "temp.png") ++ im.save(test_file, save_all=True) ++ ++ with Image.open(test_file) as im: ++ im.load() ++ assert not im.is_animated ++ assert im.n_frames == 1 ++ assert im.get_format_mimetype() == "image/apng" ++ assert im.info.get("default_image") is None ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ with Image.open("Tests/images/apng/single_frame_default.png") as im: ++ frames = [] ++ for frame_im in ImageSequence.Iterator(im): ++ frames.append(frame_im.copy()) ++ frames[0].save( ++ test_file, save_all=True, default_image=True, append_images=frames[1:] ++ ) ++ ++ with Image.open(test_file) as im: ++ im.load() ++ assert im.is_animated ++ assert im.n_frames == 2 ++ assert im.get_format_mimetype() == "image/apng" ++ assert im.info.get("default_image") ++ im.seek(1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_save_split_fdat(tmp_path): ++ # test to make sure we do not generate sequence errors when writing ++ # frames with image data spanning multiple fdAT chunks (in this case ++ # both the default image and first animation frame will span multiple ++ # data chunks) ++ test_file = str(tmp_path / "temp.png") ++ with Image.open("Tests/images/old-style-jpeg-compression.png") as im: ++ frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))] ++ im.save( ++ test_file, save_all=True, default_image=True, append_images=frames, ++ ) ++ with Image.open(test_file) as im: ++ exception = None ++ try: ++ im.seek(im.n_frames - 1) ++ im.load() ++ except Exception as e: ++ exception = e ++ assert exception is None ++ ++ ++def test_apng_save_duration_loop(tmp_path): ++ test_file = str(tmp_path / "temp.png") ++ with Image.open("Tests/images/apng/delay.png") as im: ++ frames = [] ++ durations = [] ++ loop = im.info.get("loop") ++ default_image = im.info.get("default_image") ++ for i, frame_im in enumerate(ImageSequence.Iterator(im)): ++ frames.append(frame_im.copy()) ++ if i != 0 or not default_image: ++ durations.append(frame_im.info.get("duration", 0)) ++ frames[0].save( ++ test_file, ++ save_all=True, ++ default_image=default_image, ++ append_images=frames[1:], ++ duration=durations, ++ loop=loop, ++ ) ++ ++ with Image.open(test_file) as im: ++ im.load() ++ assert im.info.get("loop") == loop ++ im.seek(1) ++ assert im.info.get("duration") == 500.0 ++ im.seek(2) ++ assert im.info.get("duration") == 1000.0 ++ im.seek(3) ++ assert im.info.get("duration") == 500.0 ++ im.seek(4) ++ assert im.info.get("duration") == 1000.0 ++ ++ # test removal of duplicated frames ++ frame = Image.new("RGBA", (128, 64), (255, 0, 0, 255)) ++ frame.save(test_file, save_all=True, append_images=[frame], duration=[500, 250]) ++ with Image.open(test_file) as im: ++ im.load() ++ assert im.n_frames == 1 ++ assert im.info.get("duration") == 750 ++ ++ ++def test_apng_save_disposal(tmp_path): ++ test_file = str(tmp_path / "temp.png") ++ size = (128, 64) ++ red = Image.new("RGBA", size, (255, 0, 0, 255)) ++ green = Image.new("RGBA", size, (0, 255, 0, 255)) ++ transparent = Image.new("RGBA", size, (0, 0, 0, 0)) ++ ++ # test APNG_DISPOSE_OP_NONE ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[green, transparent], ++ disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ blend=PngImagePlugin.APNG_BLEND_OP_OVER, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(2) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ # test APNG_DISPOSE_OP_BACKGROUND ++ disposal = [ ++ PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND, ++ PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ ] ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[red, transparent], ++ disposal=disposal, ++ blend=PngImagePlugin.APNG_BLEND_OP_OVER, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(2) ++ assert im.getpixel((0, 0)) == (0, 0, 0, 0) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ disposal = [ ++ PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND, ++ ] ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[green], ++ disposal=disposal, ++ blend=PngImagePlugin.APNG_BLEND_OP_OVER, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ # test APNG_DISPOSE_OP_PREVIOUS ++ disposal = [ ++ PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, ++ PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ ] ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[green, red, transparent], ++ default_image=True, ++ disposal=disposal, ++ blend=PngImagePlugin.APNG_BLEND_OP_OVER, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(3) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ disposal = [ ++ PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, ++ ] ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[green], ++ disposal=disposal, ++ blend=PngImagePlugin.APNG_BLEND_OP_OVER, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_save_disposal_previous(tmp_path): ++ test_file = str(tmp_path / "temp.png") ++ size = (128, 64) ++ transparent = Image.new("RGBA", size, (0, 0, 0, 0)) ++ red = Image.new("RGBA", size, (255, 0, 0, 255)) ++ green = Image.new("RGBA", size, (0, 255, 0, 255)) ++ ++ # test APNG_DISPOSE_OP_NONE ++ transparent.save( ++ test_file, ++ save_all=True, ++ append_images=[red, green], ++ disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(2) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ ++def test_apng_save_blend(tmp_path): ++ test_file = str(tmp_path / "temp.png") ++ size = (128, 64) ++ red = Image.new("RGBA", size, (255, 0, 0, 255)) ++ green = Image.new("RGBA", size, (0, 255, 0, 255)) ++ transparent = Image.new("RGBA", size, (0, 0, 0, 0)) ++ ++ # test APNG_BLEND_OP_SOURCE on solid color ++ blend = [ ++ PngImagePlugin.APNG_BLEND_OP_OVER, ++ PngImagePlugin.APNG_BLEND_OP_SOURCE, ++ ] ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[red, green], ++ default_image=True, ++ disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ blend=blend, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(2) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ ++ # test APNG_BLEND_OP_SOURCE on transparent color ++ blend = [ ++ PngImagePlugin.APNG_BLEND_OP_OVER, ++ PngImagePlugin.APNG_BLEND_OP_SOURCE, ++ ] ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[red, transparent], ++ default_image=True, ++ disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ blend=blend, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(2) ++ assert im.getpixel((0, 0)) == (0, 0, 0, 0) ++ assert im.getpixel((64, 32)) == (0, 0, 0, 0) ++ ++ # test APNG_BLEND_OP_OVER ++ red.save( ++ test_file, ++ save_all=True, ++ append_images=[green, transparent], ++ default_image=True, ++ disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, ++ blend=PngImagePlugin.APNG_BLEND_OP_OVER, ++ ) ++ with Image.open(test_file) as im: ++ im.seek(1) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) ++ im.seek(2) ++ assert im.getpixel((0, 0)) == (0, 255, 0, 255) ++ assert im.getpixel((64, 32)) == (0, 255, 0, 255) +diff -rupN Pillow-7.2.0/Tests/test_file_blp.py Pillow-7.2.0-new/Tests/test_file_blp.py +--- Pillow-7.2.0/Tests/test_file_blp.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_blp.py 2021-05-24 15:38:11.152397294 +0200 +@@ -1,4 +1,5 @@ + from PIL import Image ++import pytest + + from .helper import assert_image_equal + +@@ -19,3 +20,21 @@ def test_load_blp2_dxt1a(): + with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: + with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: + assert_image_equal(im, target) ++ ++@pytest.mark.parametrize( ++ "test_file", ++ [ ++ "Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp", ++ "Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp", ++ "Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp", ++ "Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp", ++ "Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp", ++ "Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp", ++ "Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp", ++ ], ++) ++def test_crashes(test_file): ++ with open(test_file, "rb") as f: ++ with Image.open(f) as im: ++ with pytest.raises(OSError): ++ im.load() +diff -rupN Pillow-7.2.0/Tests/test_file_eps.py Pillow-7.2.0-new/Tests/test_file_eps.py +--- Pillow-7.2.0/Tests/test_file_eps.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_eps.py 2021-05-24 15:38:11.152397294 +0200 +@@ -256,3 +256,15 @@ def test_emptyline(): + assert image.mode == "RGB" + assert image.size == (460, 352) + assert image.format == "EPS" ++ ++ ++@pytest.mark.timeout(timeout=5) ++@pytest.mark.parametrize( ++ "test_file", ++ ["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], ++) ++def test_timeout(test_file): ++ with open(test_file, "rb") as f: ++ with pytest.raises(Image.UnidentifiedImageError): ++ with Image.open(f): ++ pass +diff -rupN Pillow-7.2.0/Tests/test_file_eps.py.orig Pillow-7.2.0-new/Tests/test_file_eps.py.orig +--- Pillow-7.2.0/Tests/test_file_eps.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/Tests/test_file_eps.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,258 @@ ++import io ++ ++import pytest ++from PIL import EpsImagePlugin, Image, features ++ ++from .helper import assert_image_similar, hopper, skip_unless_feature ++ ++HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() ++ ++# Our two EPS test files (they are identical except for their bounding boxes) ++FILE1 = "Tests/images/zero_bb.eps" ++FILE2 = "Tests/images/non_zero_bb.eps" ++ ++# Due to palletization, we'll need to convert these to RGB after load ++FILE1_COMPARE = "Tests/images/zero_bb.png" ++FILE1_COMPARE_SCALE2 = "Tests/images/zero_bb_scale2.png" ++ ++FILE2_COMPARE = "Tests/images/non_zero_bb.png" ++FILE2_COMPARE_SCALE2 = "Tests/images/non_zero_bb_scale2.png" ++ ++# EPS test files with binary preview ++FILE3 = "Tests/images/binary_preview_map.eps" ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_sanity(): ++ # Regular scale ++ with Image.open(FILE1) as image1: ++ image1.load() ++ assert image1.mode == "RGB" ++ assert image1.size == (460, 352) ++ assert image1.format == "EPS" ++ ++ with Image.open(FILE2) as image2: ++ image2.load() ++ assert image2.mode == "RGB" ++ assert image2.size == (360, 252) ++ assert image2.format == "EPS" ++ ++ # Double scale ++ with Image.open(FILE1) as image1_scale2: ++ image1_scale2.load(scale=2) ++ assert image1_scale2.mode == "RGB" ++ assert image1_scale2.size == (920, 704) ++ assert image1_scale2.format == "EPS" ++ ++ with Image.open(FILE2) as image2_scale2: ++ image2_scale2.load(scale=2) ++ assert image2_scale2.mode == "RGB" ++ assert image2_scale2.size == (720, 504) ++ assert image2_scale2.format == "EPS" ++ ++ ++def test_invalid_file(): ++ invalid_file = "Tests/images/flower.jpg" ++ ++ with pytest.raises(SyntaxError): ++ EpsImagePlugin.EpsImageFile(invalid_file) ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_cmyk(): ++ with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image: ++ ++ assert cmyk_image.mode == "CMYK" ++ assert cmyk_image.size == (100, 100) ++ assert cmyk_image.format == "EPS" ++ ++ cmyk_image.load() ++ assert cmyk_image.mode == "RGB" ++ ++ if features.check("jpg"): ++ with Image.open("Tests/images/pil_sample_rgb.jpg") as target: ++ assert_image_similar(cmyk_image, target, 10) ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_showpage(): ++ # See https://github.com/python-pillow/Pillow/issues/2615 ++ with Image.open("Tests/images/reqd_showpage.eps") as plot_image: ++ with Image.open("Tests/images/reqd_showpage.png") as target: ++ # should not crash/hang ++ plot_image.load() ++ # fonts could be slightly different ++ assert_image_similar(plot_image, target, 6) ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_file_object(tmp_path): ++ # issue 479 ++ with Image.open(FILE1) as image1: ++ with open(str(tmp_path / "temp.eps"), "wb") as fh: ++ image1.save(fh, "EPS") ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_iobase_object(tmp_path): ++ # issue 479 ++ with Image.open(FILE1) as image1: ++ with open(str(tmp_path / "temp_iobase.eps"), "wb") as fh: ++ image1.save(fh, "EPS") ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_bytesio_object(): ++ with open(FILE1, "rb") as f: ++ img_bytes = io.BytesIO(f.read()) ++ ++ with Image.open(img_bytes) as img: ++ img.load() ++ ++ with Image.open(FILE1_COMPARE) as image1_scale1_compare: ++ image1_scale1_compare = image1_scale1_compare.convert("RGB") ++ image1_scale1_compare.load() ++ assert_image_similar(img, image1_scale1_compare, 5) ++ ++ ++def test_image_mode_not_supported(tmp_path): ++ im = hopper("RGBA") ++ tmpfile = str(tmp_path / "temp.eps") ++ with pytest.raises(ValueError): ++ im.save(tmpfile) ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++@skip_unless_feature("zlib") ++def test_render_scale1(): ++ # We need png support for these render test ++ ++ # Zero bounding box ++ with Image.open(FILE1) as image1_scale1: ++ image1_scale1.load() ++ with Image.open(FILE1_COMPARE) as image1_scale1_compare: ++ image1_scale1_compare = image1_scale1_compare.convert("RGB") ++ image1_scale1_compare.load() ++ assert_image_similar(image1_scale1, image1_scale1_compare, 5) ++ ++ # Non-Zero bounding box ++ with Image.open(FILE2) as image2_scale1: ++ image2_scale1.load() ++ with Image.open(FILE2_COMPARE) as image2_scale1_compare: ++ image2_scale1_compare = image2_scale1_compare.convert("RGB") ++ image2_scale1_compare.load() ++ assert_image_similar(image2_scale1, image2_scale1_compare, 10) ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++@skip_unless_feature("zlib") ++def test_render_scale2(): ++ # We need png support for these render test ++ ++ # Zero bounding box ++ with Image.open(FILE1) as image1_scale2: ++ image1_scale2.load(scale=2) ++ with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare: ++ image1_scale2_compare = image1_scale2_compare.convert("RGB") ++ image1_scale2_compare.load() ++ assert_image_similar(image1_scale2, image1_scale2_compare, 5) ++ ++ # Non-Zero bounding box ++ with Image.open(FILE2) as image2_scale2: ++ image2_scale2.load(scale=2) ++ with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare: ++ image2_scale2_compare = image2_scale2_compare.convert("RGB") ++ image2_scale2_compare.load() ++ assert_image_similar(image2_scale2, image2_scale2_compare, 10) ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_resize(): ++ files = [FILE1, FILE2, "Tests/images/illu10_preview.eps"] ++ for fn in files: ++ with Image.open(fn) as im: ++ new_size = (100, 100) ++ im = im.resize(new_size) ++ assert im.size == new_size ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_thumbnail(): ++ # Issue #619 ++ # Arrange ++ files = [FILE1, FILE2] ++ for fn in files: ++ with Image.open(FILE1) as im: ++ new_size = (100, 100) ++ im.thumbnail(new_size) ++ assert max(im.size) == max(new_size) ++ ++ ++def test_read_binary_preview(): ++ # Issue 302 ++ # open image with binary preview ++ with Image.open(FILE3): ++ pass ++ ++ ++def test_readline(tmp_path): ++ # check all the freaking line endings possible from the spec ++ # test_string = u'something\r\nelse\n\rbaz\rbif\n' ++ line_endings = ["\r\n", "\n", "\n\r", "\r"] ++ strings = ["something", "else", "baz", "bif"] ++ ++ def _test_readline(t, ending): ++ ending = "Failure with line ending: %s" % ( ++ "".join("%s" % ord(s) for s in ending) ++ ) ++ assert t.readline().strip("\r\n") == "something", ending ++ assert t.readline().strip("\r\n") == "else", ending ++ assert t.readline().strip("\r\n") == "baz", ending ++ assert t.readline().strip("\r\n") == "bif", ending ++ ++ def _test_readline_io_psfile(test_string, ending): ++ f = io.BytesIO(test_string.encode("latin-1")) ++ t = EpsImagePlugin.PSFile(f) ++ _test_readline(t, ending) ++ ++ def _test_readline_file_psfile(test_string, ending): ++ f = str(tmp_path / "temp.txt") ++ with open(f, "wb") as w: ++ w.write(test_string.encode("latin-1")) ++ ++ with open(f, "rb") as r: ++ t = EpsImagePlugin.PSFile(r) ++ _test_readline(t, ending) ++ ++ for ending in line_endings: ++ s = ending.join(strings) ++ _test_readline_io_psfile(s, ending) ++ _test_readline_file_psfile(s, ending) ++ ++ ++def test_open_eps(): ++ # https://github.com/python-pillow/Pillow/issues/1104 ++ # Arrange ++ FILES = [ ++ "Tests/images/illu10_no_preview.eps", ++ "Tests/images/illu10_preview.eps", ++ "Tests/images/illuCS6_no_preview.eps", ++ "Tests/images/illuCS6_preview.eps", ++ ] ++ ++ # Act / Assert ++ for filename in FILES: ++ with Image.open(filename) as img: ++ assert img.mode == "RGB" ++ ++ ++@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") ++def test_emptyline(): ++ # Test file includes an empty line in the header data ++ emptyline_file = "Tests/images/zero_bb_emptyline.eps" ++ ++ with Image.open(emptyline_file) as image: ++ image.load() ++ assert image.mode == "RGB" ++ assert image.size == (460, 352) ++ assert image.format == "EPS" +diff -rupN Pillow-7.2.0/Tests/test_file_fli.py Pillow-7.2.0-new/Tests/test_file_fli.py +--- Pillow-7.2.0/Tests/test_file_fli.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_fli.py 2021-05-24 15:38:11.152397294 +0200 +@@ -123,3 +123,17 @@ def test_seek(): + + with Image.open("Tests/images/a_fli.png") as expected: + assert_image_equal(im, expected) ++ ++@pytest.mark.parametrize( ++ "test_file", ++ [ ++ "Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli", ++ "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli", ++ ], ++) ++@pytest.mark.timeout(timeout=3) ++def test_timeouts(test_file): ++ with open(test_file, "rb") as f: ++ with Image.open(f) as im: ++ with pytest.raises(OSError): ++ im.load() +diff -rupN Pillow-7.2.0/Tests/test_file_fli.py.orig Pillow-7.2.0-new/Tests/test_file_fli.py.orig +--- Pillow-7.2.0/Tests/test_file_fli.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/Tests/test_file_fli.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,125 @@ ++import pytest ++from PIL import FliImagePlugin, Image ++ ++from .helper import assert_image_equal, is_pypy ++ ++# created as an export of a palette image from Gimp2.6 ++# save as...-> hopper.fli, default options. ++static_test_file = "Tests/images/hopper.fli" ++ ++# From https://samples.libav.org/fli-flc/ ++animated_test_file = "Tests/images/a.fli" ++ ++ ++def test_sanity(): ++ with Image.open(static_test_file) as im: ++ im.load() ++ assert im.mode == "P" ++ assert im.size == (128, 128) ++ assert im.format == "FLI" ++ assert not im.is_animated ++ ++ with Image.open(animated_test_file) as im: ++ assert im.mode == "P" ++ assert im.size == (320, 200) ++ assert im.format == "FLI" ++ assert im.info["duration"] == 71 ++ assert im.is_animated ++ ++ ++@pytest.mark.skipif(is_pypy(), reason="Requires CPython") ++def test_unclosed_file(): ++ def open(): ++ im = Image.open(static_test_file) ++ im.load() ++ ++ pytest.warns(ResourceWarning, open) ++ ++ ++def test_closed_file(): ++ def open(): ++ im = Image.open(static_test_file) ++ im.load() ++ im.close() ++ ++ pytest.warns(None, open) ++ ++ ++def test_context_manager(): ++ def open(): ++ with Image.open(static_test_file) as im: ++ im.load() ++ ++ pytest.warns(None, open) ++ ++ ++def test_tell(): ++ # Arrange ++ with Image.open(static_test_file) as im: ++ ++ # Act ++ frame = im.tell() ++ ++ # Assert ++ assert frame == 0 ++ ++ ++def test_invalid_file(): ++ invalid_file = "Tests/images/flower.jpg" ++ ++ with pytest.raises(SyntaxError): ++ FliImagePlugin.FliImageFile(invalid_file) ++ ++ ++def test_n_frames(): ++ with Image.open(static_test_file) as im: ++ assert im.n_frames == 1 ++ assert not im.is_animated ++ ++ with Image.open(animated_test_file) as im: ++ assert im.n_frames == 384 ++ assert im.is_animated ++ ++ ++def test_eoferror(): ++ with Image.open(animated_test_file) as im: ++ n_frames = im.n_frames ++ ++ # Test seeking past the last frame ++ with pytest.raises(EOFError): ++ im.seek(n_frames) ++ assert im.tell() < n_frames ++ ++ # Test that seeking to the last frame does not raise an error ++ im.seek(n_frames - 1) ++ ++ ++def test_seek_tell(): ++ with Image.open(animated_test_file) as im: ++ ++ layer_number = im.tell() ++ assert layer_number == 0 ++ ++ im.seek(0) ++ layer_number = im.tell() ++ assert layer_number == 0 ++ ++ im.seek(1) ++ layer_number = im.tell() ++ assert layer_number == 1 ++ ++ im.seek(2) ++ layer_number = im.tell() ++ assert layer_number == 2 ++ ++ im.seek(1) ++ layer_number = im.tell() ++ assert layer_number == 1 ++ ++ ++def test_seek(): ++ with Image.open(animated_test_file) as im: ++ im.seek(50) ++ ++ with Image.open("Tests/images/a_fli.png") as expected: ++ assert_image_equal(im, expected) +diff -rupN Pillow-7.2.0/Tests/test_file_jpeg2k.py Pillow-7.2.0-new/Tests/test_file_jpeg2k.py +--- Pillow-7.2.0/Tests/test_file_jpeg2k.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_jpeg2k.py 2021-05-24 15:38:11.152397294 +0200 +@@ -233,3 +233,19 @@ def test_parser_feed(): + + # Assert + assert p.image.size == (640, 480) ++ ++ ++@pytest.mark.parametrize( ++ "test_file", ++ [ ++ "Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k", ++ "Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k", ++ "Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k", ++ "Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k", ++ ], ++) ++def test_crashes(test_file): ++ with open(test_file, "rb") as f: ++ with Image.open(f) as im: ++ # Valgrind should not complain here ++ im.load() +diff -rupN Pillow-7.2.0/Tests/test_file_psd.py Pillow-7.2.0-new/Tests/test_file_psd.py +--- Pillow-7.2.0/Tests/test_file_psd.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_psd.py 2021-05-24 15:38:11.153397294 +0200 +@@ -127,3 +127,24 @@ def test_combined_larger_than_size(): + # then the seek can't be negative + with pytest.raises(OSError): + Image.open("Tests/images/combined_larger_than_size.psd") ++ ++@pytest.mark.parametrize( ++ "test_file,raises", ++ [ ++ ( ++ "Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd", ++ Image.UnidentifiedImageError, ++ ), ++ ( ++ "Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd", ++ Image.UnidentifiedImageError, ++ ), ++ ("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError), ++ ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), ++ ], ++) ++def test_crashes(test_file, raises): ++ with open(test_file, "rb") as f: ++ with pytest.raises(raises): ++ with Image.open(f): ++ pass +diff -rupN Pillow-7.2.0/Tests/test_file_psd.py.orig Pillow-7.2.0-new/Tests/test_file_psd.py.orig +--- Pillow-7.2.0/Tests/test_file_psd.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/Tests/test_file_psd.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,129 @@ ++import pytest ++from PIL import Image, PsdImagePlugin ++ ++from .helper import assert_image_similar, hopper, is_pypy ++ ++test_file = "Tests/images/hopper.psd" ++ ++ ++def test_sanity(): ++ with Image.open(test_file) as im: ++ im.load() ++ assert im.mode == "RGB" ++ assert im.size == (128, 128) ++ assert im.format == "PSD" ++ ++ im2 = hopper() ++ assert_image_similar(im, im2, 4.8) ++ ++ ++@pytest.mark.skipif(is_pypy(), reason="Requires CPython") ++def test_unclosed_file(): ++ def open(): ++ im = Image.open(test_file) ++ im.load() ++ ++ pytest.warns(ResourceWarning, open) ++ ++ ++def test_closed_file(): ++ def open(): ++ im = Image.open(test_file) ++ im.load() ++ im.close() ++ ++ pytest.warns(None, open) ++ ++ ++def test_context_manager(): ++ def open(): ++ with Image.open(test_file) as im: ++ im.load() ++ ++ pytest.warns(None, open) ++ ++ ++def test_invalid_file(): ++ invalid_file = "Tests/images/flower.jpg" ++ ++ with pytest.raises(SyntaxError): ++ PsdImagePlugin.PsdImageFile(invalid_file) ++ ++ ++def test_n_frames(): ++ with Image.open("Tests/images/hopper_merged.psd") as im: ++ assert im.n_frames == 1 ++ assert not im.is_animated ++ ++ with Image.open(test_file) as im: ++ assert im.n_frames == 2 ++ assert im.is_animated ++ ++ ++def test_eoferror(): ++ with Image.open(test_file) as im: ++ # PSD seek index starts at 1 rather than 0 ++ n_frames = im.n_frames + 1 ++ ++ # Test seeking past the last frame ++ with pytest.raises(EOFError): ++ im.seek(n_frames) ++ assert im.tell() < n_frames ++ ++ # Test that seeking to the last frame does not raise an error ++ im.seek(n_frames - 1) ++ ++ ++def test_seek_tell(): ++ with Image.open(test_file) as im: ++ ++ layer_number = im.tell() ++ assert layer_number == 1 ++ ++ with pytest.raises(EOFError): ++ im.seek(0) ++ ++ im.seek(1) ++ layer_number = im.tell() ++ assert layer_number == 1 ++ ++ im.seek(2) ++ layer_number = im.tell() ++ assert layer_number == 2 ++ ++ ++def test_seek_eoferror(): ++ with Image.open(test_file) as im: ++ ++ with pytest.raises(EOFError): ++ im.seek(-1) ++ ++ ++def test_open_after_exclusive_load(): ++ with Image.open(test_file) as im: ++ im.load() ++ im.seek(im.tell() + 1) ++ im.load() ++ ++ ++def test_icc_profile(): ++ with Image.open(test_file) as im: ++ assert "icc_profile" in im.info ++ ++ icc_profile = im.info["icc_profile"] ++ assert len(icc_profile) == 3144 ++ ++ ++def test_no_icc_profile(): ++ with Image.open("Tests/images/hopper_merged.psd") as im: ++ assert "icc_profile" not in im.info ++ ++ ++def test_combined_larger_than_size(): ++ # The 'combined' sizes of the individual parts is larger than the ++ # declared 'size' of the extra data field, resulting in a backwards seek. ++ ++ # If we instead take the 'size' of the extra data field as the source of truth, ++ # then the seek can't be negative ++ with pytest.raises(OSError): ++ Image.open("Tests/images/combined_larger_than_size.psd") +diff -rupN Pillow-7.2.0/Tests/test_file_tiff.py Pillow-7.2.0-new/Tests/test_file_tiff.py +--- Pillow-7.2.0/Tests/test_file_tiff.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_file_tiff.py 2021-05-24 15:38:11.153397294 +0200 +@@ -598,8 +598,9 @@ class TestFileTiff: + @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") + def test_string_dimension(self): + # Assert that an error is raised if one of the dimensions is a string +- with pytest.raises(ValueError): +- Image.open("Tests/images/string_dimension.tiff") ++ with Image.open("Tests/images/string_dimension.tiff") as im: ++ with pytest.raises(OSError): ++ im.load() + + + @pytest.mark.skipif(not is_win32(), reason="Windows only") +diff -rupN Pillow-7.2.0/Tests/test_file_tiff.py.orig Pillow-7.2.0-new/Tests/test_file_tiff.py.orig +--- Pillow-7.2.0/Tests/test_file_tiff.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ Pillow-7.2.0-new/Tests/test_file_tiff.py.orig 2020-06-30 09:50:35.000000000 +0200 +@@ -0,0 +1,627 @@ ++import logging ++import os ++from io import BytesIO ++ ++import pytest ++from PIL import Image, TiffImagePlugin ++from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION ++ ++from .helper import ( ++ assert_image_equal, ++ assert_image_equal_tofile, ++ assert_image_similar, ++ assert_image_similar_tofile, ++ hopper, ++ is_pypy, ++ is_win32, ++) ++ ++logger = logging.getLogger(__name__) ++ ++ ++class TestFileTiff: ++ def test_sanity(self, tmp_path): ++ ++ filename = str(tmp_path / "temp.tif") ++ ++ hopper("RGB").save(filename) ++ ++ with Image.open(filename) as im: ++ im.load() ++ assert im.mode == "RGB" ++ assert im.size == (128, 128) ++ assert im.format == "TIFF" ++ ++ hopper("1").save(filename) ++ with Image.open(filename): ++ pass ++ ++ hopper("L").save(filename) ++ with Image.open(filename): ++ pass ++ ++ hopper("P").save(filename) ++ with Image.open(filename): ++ pass ++ ++ hopper("RGB").save(filename) ++ with Image.open(filename): ++ pass ++ ++ hopper("I").save(filename) ++ with Image.open(filename): ++ pass ++ ++ @pytest.mark.skipif(is_pypy(), reason="Requires CPython") ++ def test_unclosed_file(self): ++ def open(): ++ im = Image.open("Tests/images/multipage.tiff") ++ im.load() ++ ++ pytest.warns(ResourceWarning, open) ++ ++ def test_closed_file(self): ++ def open(): ++ im = Image.open("Tests/images/multipage.tiff") ++ im.load() ++ im.close() ++ ++ pytest.warns(None, open) ++ ++ def test_context_manager(self): ++ def open(): ++ with Image.open("Tests/images/multipage.tiff") as im: ++ im.load() ++ ++ pytest.warns(None, open) ++ ++ def test_mac_tiff(self): ++ # Read RGBa images from macOS [@PIL136] ++ ++ filename = "Tests/images/pil136.tiff" ++ with Image.open(filename) as im: ++ assert im.mode == "RGBA" ++ assert im.size == (55, 43) ++ assert im.tile == [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))] ++ im.load() ++ ++ assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) ++ ++ def test_wrong_bits_per_sample(self): ++ with Image.open("Tests/images/tiff_wrong_bits_per_sample.tiff") as im: ++ assert im.mode == "RGBA" ++ assert im.size == (52, 53) ++ assert im.tile == [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))] ++ im.load() ++ ++ def test_set_legacy_api(self): ++ ifd = TiffImagePlugin.ImageFileDirectory_v2() ++ with pytest.raises(Exception) as e: ++ ifd.legacy_api = None ++ assert str(e.value) == "Not allowing setting of legacy api" ++ ++ def test_xyres_tiff(self): ++ filename = "Tests/images/pil168.tif" ++ with Image.open(filename) as im: ++ ++ # legacy api ++ assert isinstance(im.tag[X_RESOLUTION][0], tuple) ++ assert isinstance(im.tag[Y_RESOLUTION][0], tuple) ++ ++ # v2 api ++ assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) ++ assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) ++ ++ assert im.info["dpi"] == (72.0, 72.0) ++ ++ def test_xyres_fallback_tiff(self): ++ filename = "Tests/images/compression.tif" ++ with Image.open(filename) as im: ++ ++ # v2 api ++ assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) ++ assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) ++ with pytest.raises(KeyError): ++ im.tag_v2[RESOLUTION_UNIT] ++ ++ # Legacy. ++ assert im.info["resolution"] == (100.0, 100.0) ++ # Fallback "inch". ++ assert im.info["dpi"] == (100.0, 100.0) ++ ++ def test_int_resolution(self): ++ filename = "Tests/images/pil168.tif" ++ with Image.open(filename) as im: ++ ++ # Try to read a file where X,Y_RESOLUTION are ints ++ im.tag_v2[X_RESOLUTION] = 71 ++ im.tag_v2[Y_RESOLUTION] = 71 ++ im._setup() ++ assert im.info["dpi"] == (71.0, 71.0) ++ ++ def test_load_dpi_rounding(self): ++ for resolutionUnit, dpi in ((None, (72, 73)), (2, (72, 73)), (3, (183, 185))): ++ with Image.open( ++ "Tests/images/hopper_roundDown_" + str(resolutionUnit) + ".tif" ++ ) as im: ++ assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit ++ assert im.info["dpi"] == (dpi[0], dpi[0]) ++ ++ with Image.open( ++ "Tests/images/hopper_roundUp_" + str(resolutionUnit) + ".tif" ++ ) as im: ++ assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit ++ assert im.info["dpi"] == (dpi[1], dpi[1]) ++ ++ def test_save_dpi_rounding(self, tmp_path): ++ outfile = str(tmp_path / "temp.tif") ++ with Image.open("Tests/images/hopper.tif") as im: ++ for dpi in (72.2, 72.8): ++ im.save(outfile, dpi=(dpi, dpi)) ++ ++ with Image.open(outfile) as reloaded: ++ reloaded.load() ++ assert (round(dpi), round(dpi)) == reloaded.info["dpi"] ++ ++ def test_save_setting_missing_resolution(self): ++ b = BytesIO() ++ Image.open("Tests/images/10ct_32bit_128.tiff").save( ++ b, format="tiff", resolution=123.45 ++ ) ++ with Image.open(b) as im: ++ assert float(im.tag_v2[X_RESOLUTION]) == 123.45 ++ assert float(im.tag_v2[Y_RESOLUTION]) == 123.45 ++ ++ def test_invalid_file(self): ++ invalid_file = "Tests/images/flower.jpg" ++ ++ with pytest.raises(SyntaxError): ++ TiffImagePlugin.TiffImageFile(invalid_file) ++ ++ TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0") ++ with pytest.raises(SyntaxError): ++ TiffImagePlugin.TiffImageFile(invalid_file) ++ TiffImagePlugin.PREFIXES.pop() ++ ++ def test_bad_exif(self): ++ with Image.open("Tests/images/hopper_bad_exif.jpg") as i: ++ # Should not raise struct.error. ++ pytest.warns(UserWarning, i._getexif) ++ ++ def test_save_rgba(self, tmp_path): ++ im = hopper("RGBA") ++ outfile = str(tmp_path / "temp.tif") ++ im.save(outfile) ++ ++ def test_save_unsupported_mode(self, tmp_path): ++ im = hopper("HSV") ++ outfile = str(tmp_path / "temp.tif") ++ with pytest.raises(OSError): ++ im.save(outfile) ++ ++ def test_little_endian(self): ++ with Image.open("Tests/images/16bit.cropped.tif") as im: ++ assert im.getpixel((0, 0)) == 480 ++ assert im.mode == "I;16" ++ ++ b = im.tobytes() ++ # Bytes are in image native order (little endian) ++ assert b[0] == ord(b"\xe0") ++ assert b[1] == ord(b"\x01") ++ ++ def test_big_endian(self): ++ with Image.open("Tests/images/16bit.MM.cropped.tif") as im: ++ assert im.getpixel((0, 0)) == 480 ++ assert im.mode == "I;16B" ++ ++ b = im.tobytes() ++ # Bytes are in image native order (big endian) ++ assert b[0] == ord(b"\x01") ++ assert b[1] == ord(b"\xe0") ++ ++ def test_16bit_s(self): ++ with Image.open("Tests/images/16bit.s.tif") as im: ++ im.load() ++ assert im.mode == "I" ++ assert im.getpixel((0, 0)) == 32767 ++ assert im.getpixel((0, 1)) == 0 ++ ++ def test_12bit_rawmode(self): ++ """ Are we generating the same interpretation ++ of the image as Imagemagick is? """ ++ ++ with Image.open("Tests/images/12bit.cropped.tif") as im: ++ # to make the target -- ++ # convert 12bit.cropped.tif -depth 16 tmp.tif ++ # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif ++ # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, ++ # so we need to unshift so that the integer values are the same. ++ ++ assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") ++ ++ def test_32bit_float(self): ++ # Issue 614, specific 32-bit float format ++ path = "Tests/images/10ct_32bit_128.tiff" ++ with Image.open(path) as im: ++ im.load() ++ ++ assert im.getpixel((0, 0)) == -0.4526388943195343 ++ assert im.getextrema() == (-3.140936851501465, 3.140684127807617) ++ ++ def test_unknown_pixel_mode(self): ++ with pytest.raises(OSError): ++ Image.open("Tests/images/hopper_unknown_pixel_mode.tif") ++ ++ def test_n_frames(self): ++ for path, n_frames in [ ++ ["Tests/images/multipage-lastframe.tif", 1], ++ ["Tests/images/multipage.tiff", 3], ++ ]: ++ with Image.open(path) as im: ++ assert im.n_frames == n_frames ++ assert im.is_animated == (n_frames != 1) ++ ++ def test_eoferror(self): ++ with Image.open("Tests/images/multipage-lastframe.tif") as im: ++ n_frames = im.n_frames ++ ++ # Test seeking past the last frame ++ with pytest.raises(EOFError): ++ im.seek(n_frames) ++ assert im.tell() < n_frames ++ ++ # Test that seeking to the last frame does not raise an error ++ im.seek(n_frames - 1) ++ ++ def test_multipage(self): ++ # issue #862 ++ with Image.open("Tests/images/multipage.tiff") as im: ++ # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue ++ ++ im.seek(0) ++ assert im.size == (10, 10) ++ assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) ++ ++ im.seek(1) ++ im.load() ++ assert im.size == (10, 10) ++ assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) ++ ++ im.seek(0) ++ im.load() ++ assert im.size == (10, 10) ++ assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) ++ ++ im.seek(2) ++ im.load() ++ assert im.size == (20, 20) ++ assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) ++ ++ def test_multipage_last_frame(self): ++ with Image.open("Tests/images/multipage-lastframe.tif") as im: ++ im.load() ++ assert im.size == (20, 20) ++ assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) ++ ++ def test___str__(self): ++ filename = "Tests/images/pil136.tiff" ++ with Image.open(filename) as im: ++ ++ # Act ++ ret = str(im.ifd) ++ ++ # Assert ++ assert isinstance(ret, str) ++ ++ def test_dict(self): ++ # Arrange ++ filename = "Tests/images/pil136.tiff" ++ with Image.open(filename) as im: ++ ++ # v2 interface ++ v2_tags = { ++ 256: 55, ++ 257: 43, ++ 258: (8, 8, 8, 8), ++ 259: 1, ++ 262: 2, ++ 296: 2, ++ 273: (8,), ++ 338: (1,), ++ 277: 4, ++ 279: (9460,), ++ 282: 72.0, ++ 283: 72.0, ++ 284: 1, ++ } ++ assert dict(im.tag_v2) == v2_tags ++ ++ # legacy interface ++ legacy_tags = { ++ 256: (55,), ++ 257: (43,), ++ 258: (8, 8, 8, 8), ++ 259: (1,), ++ 262: (2,), ++ 296: (2,), ++ 273: (8,), ++ 338: (1,), ++ 277: (4,), ++ 279: (9460,), ++ 282: ((720000, 10000),), ++ 283: ((720000, 10000),), ++ 284: (1,), ++ } ++ assert dict(im.tag) == legacy_tags ++ ++ def test__delitem__(self): ++ filename = "Tests/images/pil136.tiff" ++ with Image.open(filename) as im: ++ len_before = len(dict(im.ifd)) ++ del im.ifd[256] ++ len_after = len(dict(im.ifd)) ++ assert len_before == len_after + 1 ++ ++ def test_load_byte(self): ++ for legacy_api in [False, True]: ++ ifd = TiffImagePlugin.ImageFileDirectory_v2() ++ data = b"abc" ++ ret = ifd.load_byte(data, legacy_api) ++ assert ret == b"abc" ++ ++ def test_load_string(self): ++ ifd = TiffImagePlugin.ImageFileDirectory_v2() ++ data = b"abc\0" ++ ret = ifd.load_string(data, False) ++ assert ret == "abc" ++ ++ def test_load_float(self): ++ ifd = TiffImagePlugin.ImageFileDirectory_v2() ++ data = b"abcdabcd" ++ ret = ifd.load_float(data, False) ++ assert ret == (1.6777999408082104e22, 1.6777999408082104e22) ++ ++ def test_load_double(self): ++ ifd = TiffImagePlugin.ImageFileDirectory_v2() ++ data = b"abcdefghabcdefgh" ++ ret = ifd.load_double(data, False) ++ assert ret == (8.540883223036124e194, 8.540883223036124e194) ++ ++ def test_seek(self): ++ filename = "Tests/images/pil136.tiff" ++ with Image.open(filename) as im: ++ im.seek(0) ++ assert im.tell() == 0 ++ ++ def test_seek_eof(self): ++ filename = "Tests/images/pil136.tiff" ++ with Image.open(filename) as im: ++ assert im.tell() == 0 ++ with pytest.raises(EOFError): ++ im.seek(-1) ++ with pytest.raises(EOFError): ++ im.seek(1) ++ ++ def test__limit_rational_int(self): ++ from PIL.TiffImagePlugin import _limit_rational ++ ++ value = 34 ++ ret = _limit_rational(value, 65536) ++ assert ret == (34, 1) ++ ++ def test__limit_rational_float(self): ++ from PIL.TiffImagePlugin import _limit_rational ++ ++ value = 22.3 ++ ret = _limit_rational(value, 65536) ++ assert ret == (223, 10) ++ ++ def test_4bit(self): ++ test_file = "Tests/images/hopper_gray_4bpp.tif" ++ original = hopper("L") ++ with Image.open(test_file) as im: ++ assert im.size == (128, 128) ++ assert im.mode == "L" ++ assert_image_similar(im, original, 7.3) ++ ++ def test_gray_semibyte_per_pixel(self): ++ test_files = ( ++ ( ++ 24.8, # epsilon ++ ( # group ++ "Tests/images/tiff_gray_2_4_bpp/hopper2.tif", ++ "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", ++ "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", ++ "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif", ++ ), ++ ), ++ ( ++ 7.3, # epsilon ++ ( # group ++ "Tests/images/tiff_gray_2_4_bpp/hopper4.tif", ++ "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", ++ "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", ++ "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif", ++ ), ++ ), ++ ) ++ original = hopper("L") ++ for epsilon, group in test_files: ++ with Image.open(group[0]) as im: ++ assert im.size == (128, 128) ++ assert im.mode == "L" ++ assert_image_similar(im, original, epsilon) ++ for file in group[1:]: ++ with Image.open(file) as im2: ++ assert im2.size == (128, 128) ++ assert im2.mode == "L" ++ assert_image_equal(im, im2) ++ ++ def test_with_underscores(self, tmp_path): ++ kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} ++ filename = str(tmp_path / "temp.tif") ++ hopper("RGB").save(filename, **kwargs) ++ with Image.open(filename) as im: ++ ++ # legacy interface ++ assert im.tag[X_RESOLUTION][0][0] == 72 ++ assert im.tag[Y_RESOLUTION][0][0] == 36 ++ ++ # v2 interface ++ assert im.tag_v2[X_RESOLUTION] == 72 ++ assert im.tag_v2[Y_RESOLUTION] == 36 ++ ++ def test_roundtrip_tiff_uint16(self, tmp_path): ++ # Test an image of all '0' values ++ pixel_value = 0x1234 ++ infile = "Tests/images/uint16_1_4660.tif" ++ with Image.open(infile) as im: ++ assert im.getpixel((0, 0)) == pixel_value ++ ++ tmpfile = str(tmp_path / "temp.tif") ++ im.save(tmpfile) ++ ++ with Image.open(tmpfile) as reloaded: ++ assert_image_equal(im, reloaded) ++ ++ def test_strip_raw(self): ++ infile = "Tests/images/tiff_strip_raw.tif" ++ with Image.open(infile) as im: ++ assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") ++ ++ def test_strip_planar_raw(self): ++ # gdal_translate -of GTiff -co INTERLEAVE=BAND \ ++ # tiff_strip_raw.tif tiff_strip_planar_raw.tiff ++ infile = "Tests/images/tiff_strip_planar_raw.tif" ++ with Image.open(infile) as im: ++ assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") ++ ++ def test_strip_planar_raw_with_overviews(self): ++ # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16 ++ infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif" ++ with Image.open(infile) as im: ++ assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") ++ ++ def test_tiled_planar_raw(self): ++ # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \ ++ # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \ ++ # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff ++ infile = "Tests/images/tiff_tiled_planar_raw.tif" ++ with Image.open(infile) as im: ++ assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") ++ ++ def test_palette(self, tmp_path): ++ def roundtrip(mode): ++ outfile = str(tmp_path / "temp.tif") ++ ++ im = hopper(mode) ++ im.save(outfile) ++ ++ with Image.open(outfile) as reloaded: ++ assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) ++ ++ for mode in ["P", "PA"]: ++ roundtrip(mode) ++ ++ def test_tiff_save_all(self): ++ mp = BytesIO() ++ with Image.open("Tests/images/multipage.tiff") as im: ++ im.save(mp, format="tiff", save_all=True) ++ ++ mp.seek(0, os.SEEK_SET) ++ with Image.open(mp) as im: ++ assert im.n_frames == 3 ++ ++ # Test appending images ++ mp = BytesIO() ++ im = Image.new("RGB", (100, 100), "#f00") ++ ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] ++ im.copy().save(mp, format="TIFF", save_all=True, append_images=ims) ++ ++ mp.seek(0, os.SEEK_SET) ++ with Image.open(mp) as reread: ++ assert reread.n_frames == 3 ++ ++ # Test appending using a generator ++ def imGenerator(ims): ++ yield from ims ++ ++ mp = BytesIO() ++ im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims)) ++ ++ mp.seek(0, os.SEEK_SET) ++ with Image.open(mp) as reread: ++ assert reread.n_frames == 3 ++ ++ def test_saving_icc_profile(self, tmp_path): ++ # Tests saving TIFF with icc_profile set. ++ # At the time of writing this will only work for non-compressed tiffs ++ # as libtiff does not support embedded ICC profiles, ++ # ImageFile._save(..) however does. ++ im = Image.new("RGB", (1, 1)) ++ im.info["icc_profile"] = "Dummy value" ++ ++ # Try save-load round trip to make sure both handle icc_profile. ++ tmpfile = str(tmp_path / "temp.tif") ++ im.save(tmpfile, "TIFF", compression="raw") ++ with Image.open(tmpfile) as reloaded: ++ assert b"Dummy value" == reloaded.info["icc_profile"] ++ ++ def test_close_on_load_exclusive(self, tmp_path): ++ # similar to test_fd_leak, but runs on unixlike os ++ tmpfile = str(tmp_path / "temp.tif") ++ ++ with Image.open("Tests/images/uint16_1_4660.tif") as im: ++ im.save(tmpfile) ++ ++ im = Image.open(tmpfile) ++ fp = im.fp ++ assert not fp.closed ++ im.load() ++ assert fp.closed ++ ++ def test_close_on_load_nonexclusive(self, tmp_path): ++ tmpfile = str(tmp_path / "temp.tif") ++ ++ with Image.open("Tests/images/uint16_1_4660.tif") as im: ++ im.save(tmpfile) ++ ++ with open(tmpfile, "rb") as f: ++ im = Image.open(f) ++ fp = im.fp ++ assert not fp.closed ++ im.load() ++ assert not fp.closed ++ ++ # Ignore this UserWarning which triggers for four tags: ++ # "Possibly corrupt EXIF data. Expecting to read 50404352 bytes but..." ++ @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") ++ def test_string_dimension(self): ++ # Assert that an error is raised if one of the dimensions is a string ++ with pytest.raises(ValueError): ++ Image.open("Tests/images/string_dimension.tiff") ++ ++ ++@pytest.mark.skipif(not is_win32(), reason="Windows only") ++class TestFileTiffW32: ++ def test_fd_leak(self, tmp_path): ++ tmpfile = str(tmp_path / "temp.tif") ++ ++ # this is an mmaped file. ++ with Image.open("Tests/images/uint16_1_4660.tif") as im: ++ im.save(tmpfile) ++ ++ im = Image.open(tmpfile) ++ fp = im.fp ++ assert not fp.closed ++ with pytest.raises(OSError): ++ os.remove(tmpfile) ++ im.load() ++ assert fp.closed ++ ++ # this closes the mmap ++ im.close() ++ ++ # this should not fail, as load should have closed the file pointer, ++ # and close should have closed the mmap ++ os.remove(tmpfile) +diff -rupN Pillow-7.2.0/Tests/test_imagefont.py Pillow-7.2.0-new/Tests/test_imagefont.py +--- Pillow-7.2.0/Tests/test_imagefont.py 2020-06-30 09:50:35.000000000 +0200 ++++ Pillow-7.2.0-new/Tests/test_imagefont.py 2021-05-24 15:40:20.388400185 +0200 +@@ -753,3 +753,15 @@ def test_render_mono_size(): + + draw.text((10, 10), "r" * 10, "black", ttf) + assert_image_equal_tofile(im, "Tests/images/text_mono.gif") ++ ++@pytest.mark.parametrize( ++ "test_file", ++ [ ++ "Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf", ++ ], ++) ++def test_oom(test_file): ++ with open(test_file, "rb") as f: ++ font = ImageFont.truetype(BytesIO(f.read())) ++ with pytest.raises(Image.DecompressionBombError): ++ font.getmask("Test Text") diff --git a/pillow_CVE-2020-35653.patch b/pillow_CVE-2020-35653.patch index 5d82208..214cf35 100644 --- a/pillow_CVE-2020-35653.patch +++ b/pillow_CVE-2020-35653.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/PcxImagePlugin.py Pillow-7.2.0-new/src/PIL/PcxImagePlugin.py --- Pillow-7.2.0/src/PIL/PcxImagePlugin.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/PIL/PcxImagePlugin.py 2021-03-06 11:40:18.072803599 +0100 ++++ Pillow-7.2.0-new/src/PIL/PcxImagePlugin.py 2021-05-24 15:38:10.445397278 +0200 @@ -64,13 +64,13 @@ class PcxImageFile(ImageFile.ImageFile): version = i8(s[1]) bits = i8(s[3]) @@ -31,7 +31,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/PIL/PcxImagePlugin.py Pillow-7.2.0- diff -rupN --no-dereference Pillow-7.2.0/Tests/test_image.py Pillow-7.2.0-new/Tests/test_image.py --- Pillow-7.2.0/Tests/test_image.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/Tests/test_image.py 2021-03-06 11:40:18.073803599 +0100 ++++ Pillow-7.2.0-new/Tests/test_image.py 2021-05-24 15:38:10.445397278 +0200 @@ -653,25 +653,29 @@ class TestImage: with pytest.warns(DeprecationWarning): assert test_module.PILLOW_VERSION > "7.0.0" diff --git a/pillow_CVE-2020-35654.patch b/pillow_CVE-2020-35654.patch index 5f0dc36..116625b 100644 --- a/pillow_CVE-2020-35654.patch +++ b/pillow_CVE-2020-35654.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2.0-new/src/libImaging/TiffDecode.c --- Pillow-7.2.0/src/libImaging/TiffDecode.c 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-03-06 11:40:18.145803593 +0100 ++++ Pillow-7.2.0-new/src/libImaging/TiffDecode.c 2021-05-24 15:38:10.523397280 +0200 @@ -227,54 +227,181 @@ int ReadTile(TIFF* tiff, UINT32 col, UIN return 0; } @@ -326,7 +326,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/TiffDecode.c Pillow-7.2. TIFFClose(tiff); diff -rupN --no-dereference Pillow-7.2.0/Tests/check_tiff_crashes.py Pillow-7.2.0-new/Tests/check_tiff_crashes.py --- Pillow-7.2.0/Tests/check_tiff_crashes.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/Tests/check_tiff_crashes.py 2021-03-06 11:40:18.145803593 +0100 ++++ Pillow-7.2.0-new/Tests/check_tiff_crashes.py 2021-05-24 15:38:10.523397280 +0200 @@ -19,6 +19,7 @@ from PIL import Image repro_read_strip = ( "images/crash_1.tif", diff --git a/pillow_CVE-2020-35655.patch b/pillow_CVE-2020-35655.patch index dc463c2..732283a 100644 --- a/pillow_CVE-2020-35655.patch +++ b/pillow_CVE-2020-35655.patch @@ -1,6 +1,6 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/SgiRleDecode.c Pillow-7.2.0-new/src/libImaging/SgiRleDecode.c --- Pillow-7.2.0/src/libImaging/SgiRleDecode.c 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/src/libImaging/SgiRleDecode.c 2021-03-06 11:40:18.218803588 +0100 ++++ Pillow-7.2.0-new/src/libImaging/SgiRleDecode.c 2021-05-24 15:38:10.601397282 +0200 @@ -112,14 +112,33 @@ ImagingSgiRleDecode(Imaging im, ImagingC int err = 0; int status; @@ -85,7 +85,7 @@ diff -rupN --no-dereference Pillow-7.2.0/src/libImaging/SgiRleDecode.c Pillow-7. } diff -rupN --no-dereference Pillow-7.2.0/Tests/test_sgi_crash.py Pillow-7.2.0-new/Tests/test_sgi_crash.py --- Pillow-7.2.0/Tests/test_sgi_crash.py 2020-06-30 09:50:35.000000000 +0200 -+++ Pillow-7.2.0-new/Tests/test_sgi_crash.py 2021-03-06 11:40:18.218803588 +0100 ++++ Pillow-7.2.0-new/Tests/test_sgi_crash.py 2021-05-24 15:38:10.601397282 +0200 @@ -5,7 +5,12 @@ from PIL import Image @pytest.mark.parametrize( diff --git a/python-pillow.spec b/python-pillow.spec index ed86c01..23205ed 100644 --- a/python-pillow.spec +++ b/python-pillow.spec @@ -8,7 +8,7 @@ Name: python-%{srcname} Version: 7.2.0 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Python image processing library # License: see http://www.pythonware.com/products/pil/license.htm @@ -45,6 +45,10 @@ Patch8: CVE-2021-25293.patch # Backport patch for CVE-2021-2792{1,2,3} # https://github.com/python-pillow/Pillow/commit/480f6819b592d7f07b9a9a52a7656c10bbe07442.patch Patch9: CVE-2021-2792x.patch +# Backport fix for CVE-2021-28675 - CVE-2021-28678, CVE-2021-25287-CVE-2021-25288 +# https://github.com/python-pillow/Pillow/commit/ee635befc6497f1c6c4fdb58c232e62d922ec8b7.patch +Patch10: CVE-2021-286xx.patch +Source2: CVE-2021-286xx-testdata.tar.gz @@ -218,6 +222,9 @@ popd %changelog +* Mon May 24 2021 Sandro Mani - 7.2.0-6 +- Backport fix for CVE-2021-28675 - CVE-2021-28678, CVE-2021-25287-CVE-2021-25288 + * Sat Mar 06 2021 Sandro Mani - 7.2.0-5 - Backport fix for CVE-2021-2792{1,2,3} diff --git a/sources b/sources index 764685e..0502c9c 100644 --- a/sources +++ b/sources @@ -1,2 +1,3 @@ +SHA512 (CVE-2021-286xx-testdata.tar.gz) = 5331006799ba6736e20809b981313d4ce19210465b59ccf2b986ee6c36d5fed89cb92e46e5d1578ea019b598eb0b27c3bae5330a8ab49b3f3d7d4ae05316703c SHA512 (CVE_testdata.tar.gz) = 8b5fb5d20ff4f496e010033e9109af938e979aa3454f0457f9c280f5d6d35ec23e492932b0c5961eed3e18594fb5e2a2847aad6abf4f5ba2ecb1a4c0be056a0d SHA512 (Pillow-7.2.0.tar.gz) = ac9c7c8f445b3f67f51bea13fad118d1612c45272c26d33bec286f3c2e198912b934378c4bf986b409aaa2a83d92ff176ee4d25f28701d61746c9cb86d0f412b