Merge branch 'master' into jp2-decode-subsample

This commit is contained in:
Andrew Murray 2021-04-03 09:26:55 +11:00 committed by GitHub
commit 39fe85f308
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
240 changed files with 7042 additions and 2356 deletions

View File

@ -24,15 +24,15 @@ install:
- mv c:\pillow-depends-master c:\pillow-depends
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\
- ..\pillow-depends\gs9533w32.exe /S
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.53.3\bin;%PATH%
- ..\pillow-depends\gs9540w32.exe /S
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.54.0\bin;%PATH%
- cd c:\pillow\winbuild\
- ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
c:\pillow\winbuild\build\build_dep_all.cmd
$host.SetShouldExit(0)
- path C:\pillow\winbuild\build\bin;%PATH%
- '%PYTHON%\%EXECUTABLE% -m pip install -U "setuptools>=49.3.2"'
- '%PYTHON%\%EXECUTABLE% -m pip install -U setuptools'
build_script:
- ps: |
@ -45,6 +45,7 @@ test_script:
- cd c:\pillow
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov'
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?

View File

@ -27,6 +27,7 @@ python3 -m pip install coverage
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install test-image-results
# TODO Remove condition when numpy supports 3.10

View File

@ -2,4 +2,6 @@
set -e
python -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests
python3 -c "from PIL import Image"
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests

View File

@ -18,6 +18,7 @@ Please send a pull request to the master branch. Please include [documentation](
- Provide tests for any newly added code.
- Follow PEP 8.
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
- Include [release notes](https://github.com/python-pillow/Pillow/tree/master/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
## Reporting Issues

47
.github/workflows/cifuzz.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: CIFuzz
on:
push:
paths:
- "**.c"
- "**.h"
pull_request:
paths:
- "**.c"
- "**.h"
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
language: python
dry-run: false
- name: Run Fuzzers
id: run
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
fuzz-seconds: 600
language: python
dry-run: false
- name: Upload New Crash
uses: actions/upload-artifact@v2
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
uses: actions/upload-artifact@v2
if: steps.run.outcome == 'success'
with:
name: crash
path: ./out/crash*
- name: Fail on legacy crash
if: success()
run: |
[ ! -e out/crash-* ]
echo No legacy crash detected

View File

@ -34,12 +34,12 @@ jobs:
python-version: 3.8
- name: Build system information
run: python .github/workflows/system-info.py
run: python3 .github/workflows/system-info.py
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U tox
python3 -m pip install -U pip
python3 -m pip install -U tox
- name: Lint
run: tox -e lint

View File

@ -9,6 +9,7 @@ python3 -m pip install coverage
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install test-image-results

View File

@ -21,7 +21,6 @@ jobs:
centos-7-amd64,
centos-8-amd64,
debian-10-buster-x86,
fedora-32-amd64,
fedora-33-amd64,
ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64,
@ -41,7 +40,7 @@ jobs:
- uses: actions/checkout@v2
- name: Build system information
run: python .github/workflows/system-info.py
run: python3 .github/workflows/system-info.py
- name: Set up QEMU
if: "matrix.qemu-arch"

52
.github/workflows/test-valgrind.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Test Valgrind
# like the docker tests, but running valgrind only on *.c/*.h changes.
on:
push:
paths:
- "**.c"
- "**.h"
pull_request:
paths:
- "**.c"
- "**.h"
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-20.04-focal-amd64-valgrind,
]
dockerTag: [master]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v2
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE
success:
needs: build
runs-on: ubuntu-latest
name: Valgrind Test Successful
steps:
- name: Success
run: echo Valgrind Test Successful

View File

@ -57,8 +57,8 @@ jobs:
- name: Print build system information
run: python .github/workflows/system-info.py
- name: python -m pip install wheel pytest pytest-cov
run: python -m pip install wheel pytest pytest-cov
- name: python -m pip install wheel pytest pytest-cov pytest-timeout
run: python -m pip install wheel pytest pytest-cov pytest-timeout
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
- name: Upgrade setuptools
@ -68,11 +68,11 @@ jobs:
- name: Install dependencies
id: install
run: |
7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.14.02" >> $env:GITHUB_PATH
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
winbuild\depends\gs9533w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.53.3\bin" >> $env:GITHUB_PATH
winbuild\depends\gs9540w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.54.0\bin" >> $env:GITHUB_PATH
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
@ -110,7 +110,7 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libwebp.cmd"
# for FreeType CBDT font support
# for FreeType CBDT/SBIX font support
- name: Build dependencies / libpng
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libpng.cmd"
@ -137,14 +137,11 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
# Raqm dependencies
- name: Build dependencies / FriBidi
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_fribidi.cmd"
- name: Build dependencies / Raqm
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libraqm.cmd"
# trim ~150MB x 9
- name: Optimize build cache
if: steps.build-cache.outputs.cache-hit != 'true'
@ -174,7 +171,7 @@ jobs:
if: failure()
run: |
mkdir -p Tests/errors
shell: pwsh
shell: bash
- name: Upload errors
uses: actions/upload-artifact@v2
@ -249,8 +246,6 @@ jobs:
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-pyqt5 \
${{ matrix.package }}-python3-pytest \
${{ matrix.package }}-python3-pytest-cov \
${{ matrix.package }}-python3-setuptools \
${{ matrix.package }}-freetype \
${{ matrix.package }}-ghostscript \
@ -263,7 +258,7 @@ jobs:
${{ matrix.package }}-openjpeg2 \
subversion
python3 -m pip install pyroma
python3 -m pip install pyroma pytest pytest-cov
pushd depends && ./install_extra_test_images.sh && popd
@ -273,6 +268,7 @@ jobs:
- name: Test Pillow
run: |
python3 selftest.py --installed
python3 -c "from PIL import Image"
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage

View File

@ -58,7 +58,7 @@ jobs:
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Build system information
run: python .github/workflows/system-info.py
run: python3 .github/workflows/system-info.py
- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')
@ -92,7 +92,6 @@ jobs:
if: failure()
run: |
mkdir -p Tests/errors
shell: pwsh
- name: Upload errors
uses: actions/upload-artifact@v2

3
.gitignore vendored
View File

@ -88,3 +88,6 @@ Tests/images/jpeg2000
Tests/images/msp
Tests/images/picins
Tests/images/sunraster
# pyinstaller
*.spec

View File

@ -2,7 +2,136 @@
Changelog (Pillow)
==================
8.1.0 (2020-01-02)
8.2.0 (2021-04-01)
------------------
- Added getxmp() method #5144
[UrielMaD, radarhere]
- Add ImageShow support for GraphicsMagick #5349
[latosha-maltba, radarhere]
- Do not load transparent pixels from subsequent GIF frames #5333
[zewt, radarhere]
- Use LZW encoding when saving GIF images #5291
[raygard]
- Set all transparent colors to be equal in quantize() #5282
[radarhere]
- Allow PixelAccess to use Python __int__ when parsing x and y #5206
[radarhere]
- Removed Image._MODEINFO #5316
[radarhere]
- Add preserve_tone option to autocontrast #5350
[elejke, radarhere]
- Fixed linear_gradient and radial_gradient I and F modes #5274
[radarhere]
- Add support for reading TIFFs with PlanarConfiguration=2 #5364
[kkopachev, wiredfool, nulano]
- Deprecated categories #5351
[radarhere]
- Do not premultiply alpha when resizing with Image.NEAREST resampling #5304
[nulano]
- Dynamically link FriBiDi instead of Raqm #5062
[nulano]
- Allow fewer PNG palette entries than the bit depth maximum when saving #5330
[radarhere]
- Use duration from info dictionary when saving WebP #5338
[radarhere]
- Stop flattening EXIF IFD into getexif() #4947
[radarhere, kkopachev]
- Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343
[radarhere]
- Save ICC profile from TIFF encoderinfo #5321
[radarhere]
- Moved RGB fix inside ImageQt class #5268
[radarhere]
- Allow alpha_composite destination to be negative #5313
[radarhere]
- Ensure file is closed if it is opened by ImageQt.ImageQt #5260
[radarhere]
- Added ImageDraw rounded_rectangle method #5208
[radarhere]
- Added IPythonViewer #5289
[radarhere, Kipkurui-mutai]
- Only draw each rectangle outline pixel once #5183
[radarhere]
- Use mmap instead of built-in Win32 mapper #5224
[radarhere, cgohlke]
- Handle PCX images with an odd stride #5214
[radarhere]
- Only read different sizes for "Large Thumbnail" MPO frames #5168
[radarhere]
- Added PyQt6 support #5258
[radarhere]
- Changed Image.open formats parameter to be case-insensitive #5250
[Piolie, radarhere]
- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-01-02) #5216
[radarhere]
- Added tk version to pilinfo #5226
[radarhere, nulano]
- Support for ignoring tests when running valgrind #5150
[wiredfool, radarhere, hugovk]
- OSS-Fuzz support #5189
[wiredfool, radarhere]
8.1.2 (2021-03-06)
------------------
- Fix Memory DOS in BLP (CVE-2021-27921), ICNS (CVE-2021-27922) and ICO (CVE-2021-27923) Image Plugins
[wiredfool]
8.1.1 (2021-03-01)
------------------
- Use more specific regex chars to prevent ReDoS. CVE-2021-25292
[hugovk]
- Fix OOB Read in TiffDecode.c, and check the tile validity before reading. CVE-2021-25291
[wiredfool]
- Fix negative size read in TiffDecode.c. CVE-2021-25290
[wiredfool]
- Fix OOB read in SgiRleDecode.c. CVE-2021-25293
[wiredfool]
- Incorrect error code checking in TiffDecode.c. CVE-2021-25289
[wiredfool]
- PyModule_AddObject fix for Python 3.10 #5194
[radarhere]
8.1.0 (2021-01-02)
------------------
- Fix TIFF OOB Write error. CVE-2020-35654 #5175

View File

@ -5,4 +5,5 @@ from io import BytesIO
from PIL import Image
Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00"))
with Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00")):
pass

View File

@ -5,4 +5,7 @@ from io import BytesIO
from PIL import Image
Image.open(BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang"))
with Image.open(
BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang")
):
pass

View File

@ -10,3 +10,17 @@ def pytest_report_header(config):
return out.getvalue()
except Exception as e:
return f"pytest_report_header failed: {e}"
def pytest_configure(config):
# We're marking some tests to ignore valgrind errors and XFAIL them.
# Ensure that the mark is defined
# even in cases where pytest-valgrind isn't installed
try:
config.addinivalue_line(
"markers",
"valgrind_known_error: Tests that have known issues with valgrind",
)
except Exception:
# valgrind is already installed
pass

View File

@ -0,0 +1,40 @@
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
DejaVu Fonts — License
Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below)
Bitstream Vera Fonts Copyright
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
Arev Fonts Copyright
Original text
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.

View File

@ -15,8 +15,11 @@ FreeMono.ttf is licensed under GPLv3, with the GPL font exception.
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC
KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later.
FreeMono.ttf is licensed under GPLv3.
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base

Binary file not shown.

View File

@ -257,8 +257,23 @@ def netpbm_available():
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
def imagemagick_available():
return bool(IMCONVERT and shutil.which(IMCONVERT))
def magick_command():
if sys.platform == "win32":
magickhome = os.environ.get("MAGICK_HOME", "")
if magickhome:
imagemagick = [os.path.join(magickhome, "convert.exe")]
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
else:
imagemagick = None
graphicsmagick = None
else:
imagemagick = ["convert"]
graphicsmagick = ["gm", "convert"]
if imagemagick and shutil.which(imagemagick[0]):
return imagemagick
elif graphicsmagick and shutil.which(graphicsmagick[0]):
return graphicsmagick
def on_appveyor():
@ -296,14 +311,6 @@ def is_mingw():
return sysconfig.get_platform() == "mingw"
if sys.platform == "win32":
IMCONVERT = os.environ.get("MAGICK_HOME", "")
if IMCONVERT:
IMCONVERT = os.path.join(IMCONVERT, "convert.exe")
else:
IMCONVERT = "convert"
class cached_property:
def __init__(self, func):
self.func = func

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

BIN
Tests/images/odd_stride.pcx Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Tests/images/xmp_test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

48
Tests/oss-fuzz/build.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/bash -eu
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
python3 setup.py build --build-base=/tmp/build install
# Build fuzzers in $OUT.
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
fuzzer_basename=$(basename -s .py $fuzzer)
fuzzer_package=${fuzzer_basename}.pkg
pyinstaller \
--add-binary /usr/local/lib/libjpeg.so.9:. \
--add-binary /usr/local/lib/libfreetype.so.6:. \
--add-binary /usr/local/lib/liblcms2.so.2:. \
--add-binary /usr/local/lib/libopenjp2.so.7:. \
--add-binary /usr/local/lib/libpng16.so.16:. \
--add-binary /usr/local/lib/libtiff.so.5:. \
--add-binary /usr/local/lib/libwebp.so.7:. \
--add-binary /usr/local/lib/libwebpdemux.so.2:. \
--add-binary /usr/local/lib/libwebpmux.so.3:. \
--add-binary /usr/local/lib/libxcb.so.1:. \
--distpath $OUT --onefile --name $fuzzer_package $fuzzer
# Create execution wrapper.
echo "#!/bin/sh
# LLVMFuzzerTestOneInput for fuzzer detection.
this_dir=\$(dirname \"\$0\")
LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \
ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \
\$this_dir/$fuzzer_package \$@" > $OUT/$fuzzer_basename
chmod u+x $OUT/$fuzzer_basename
done
find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@
find Tests/fonts -print | zip -q $OUT/fuzz_font_seed_corpus.zip -@

View File

@ -0,0 +1,33 @@
#!/bin/bash -eu
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
# Generate image dictionaries here for each of the fuzzers and put them in the
# $OUT directory, named for the fuzzer
git clone --depth 1 https://github.com/google/fuzzing
cat fuzzing/dictionaries/bmp.dict \
fuzzing/dictionaries/dds.dict \
fuzzing/dictionaries/gif.dict \
fuzzing/dictionaries/icns.dict \
fuzzing/dictionaries/jpeg.dict \
fuzzing/dictionaries/jpeg2000.dict \
fuzzing/dictionaries/pbm.dict \
fuzzing/dictionaries/png.dict \
fuzzing/dictionaries/psd.dict \
fuzzing/dictionaries/tiff.dict \
fuzzing/dictionaries/webp.dict \
> $OUT/fuzz_pillow.dict

40
Tests/oss-fuzz/fuzz_font.py Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/python3
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import atheris_no_libfuzzer as atheris
import fuzzers
def TestOneInput(data):
try:
fuzzers.fuzz_font(data)
except Exception:
# We're catching all exceptions because Pillow's exceptions are
# directly inheriting from Exception.
return
return
def main():
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
atheris.Fuzz()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,40 @@
#!/usr/bin/python3
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import atheris_no_libfuzzer as atheris
import fuzzers
def TestOneInput(data):
try:
fuzzers.fuzz_image(data)
except Exception:
# We're catching all exceptions because Pillow's exceptions are
# directly inheriting from Exception.
return
return
def main():
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
atheris.Fuzz()
if __name__ == "__main__":
main()

36
Tests/oss-fuzz/fuzzers.py Normal file
View File

@ -0,0 +1,36 @@
import io
import warnings
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
def enable_decompressionbomb_error():
ImageFile.LOAD_TRUNCATED_IMAGES = True
warnings.filterwarnings("ignore")
warnings.simplefilter("error", Image.DecompressionBombWarning)
def fuzz_image(data):
# This will fail on some images in the corpus, as we have many
# invalid images in the test suite.
with Image.open(io.BytesIO(data)) as im:
im.rotate(45)
im.filter(ImageFilter.DETAIL)
im.save(io.BytesIO(), "BMP")
def fuzz_font(data):
wrapper = io.BytesIO(data)
try:
font = ImageFont.truetype(wrapper)
except OSError:
# Catch pcf/pilfonts/random garbage here. They return
# different font objects.
return
font.getsize_multiline("ABC\nAaaa")
font.getmask("test text")
with Image.new(mode="RGBA", size=(200, 200)) as im:
draw = ImageDraw.Draw(im)
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2)
draw.text((10, 10), "Test Text", font=font, fill="#000")

View File

@ -0,0 +1,53 @@
import subprocess
import sys
import fuzzers
import pytest
from PIL import Image
if sys.platform.startswith("win32"):
pytest.skip("Fuzzer is linux only", allow_module_level=True)
@pytest.mark.parametrize(
"path",
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
)
def test_fuzz_images(path):
fuzzers.enable_decompressionbomb_error()
try:
with open(path, "rb") as f:
fuzzers.fuzz_image(f.read())
assert True
except (
OSError,
SyntaxError,
MemoryError,
ValueError,
NotImplementedError,
OverflowError,
):
# Known exceptions that are through from Pillow
assert True
except (
Image.DecompressionBombError,
Image.DecompressionBombWarning,
Image.UnidentifiedImageError,
):
# Known Image.* exceptions
assert True
@pytest.mark.parametrize(
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
)
def test_fuzz_fonts(path):
if not path:
return
with open(path, "rb") as f:
try:
fuzzers.fuzz_font(f.read())
except (Image.DecompressionBombError, Image.DecompressionBombWarning):
pass
assert True

View File

@ -20,7 +20,7 @@ def test_bad():
either"""
for f in get_files("b"):
def open(f):
with pytest.warns(None) as record:
try:
with Image.open(f) as im:
im.load()
@ -28,7 +28,7 @@ def test_bad():
pass
# Assert that there is no unclosed file warning
pytest.warns(None, open, f)
assert not record
def test_questionable():

View File

@ -52,17 +52,21 @@ 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")
with Image.open("Tests/images/decompression_bomb.ico"):
pass
def test_exception_gif(self):
with pytest.raises(Image.DecompressionBombError):
Image.open("Tests/images/decompression_bomb.gif")
with Image.open("Tests/images/decompression_bomb.gif"):
pass
def test_exception_bmp(self):
with pytest.raises(Image.DecompressionBombError):
Image.open("Tests/images/bmp/b/reallybig.bmp")
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
pass
class TestDecompressionCrop:

View File

@ -312,7 +312,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()

View File

@ -1,21 +1,39 @@
import pytest
from PIL import Image
from .helper import assert_image_equal
from .helper import assert_image_equal_tofile
def test_load_blp2_raw():
with Image.open("Tests/images/blp/blp2_raw.blp") as im:
with Image.open("Tests/images/blp/blp2_raw.png") as target:
assert_image_equal(im, target)
assert_image_equal_tofile(im, "Tests/images/blp/blp2_raw.png")
def test_load_blp2_dxt1():
with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
with Image.open("Tests/images/blp/blp2_dxt1.png") as target:
assert_image_equal(im, target)
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1.png")
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)
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
@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()

View File

@ -4,7 +4,7 @@ import pytest
from PIL import BmpImagePlugin, Image
from .helper import assert_image_equal, hopper
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
def test_sanity(tmp_path):
@ -111,8 +111,7 @@ def test_load_dib():
assert im.format == "DIB"
assert im.get_format_mimetype() == "image/bmp"
with Image.open("Tests/images/clipboard_target.png") as target:
assert_image_equal(im, target)
assert_image_equal_tofile(im, "Tests/images/clipboard_target.png")
def test_save_dib(tmp_path):
@ -136,5 +135,4 @@ def test_rgba_bitfields():
b, g, r = im.split()[1:]
im = Image.merge("RGB", (r, g, b))
with Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") as target:
assert_image_equal(im, target)
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")

View File

@ -31,20 +31,20 @@ def test_unclosed_file():
def test_closed_file():
def open():
with pytest.warns(None) as record:
im = Image.open(TEST_FILE)
im.load()
im.close()
pytest.warns(None, open)
assert not record
def test_context_manager():
def open():
with pytest.warns(None) as record:
with Image.open(TEST_FILE) as im:
im.load()
pytest.warns(None, open)
assert not record
def test_invalid_file():

View File

@ -5,7 +5,7 @@ import pytest
from PIL import DdsImagePlugin, Image
from .helper import assert_image_equal
from .helper import assert_image_equal, assert_image_equal_tofile
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
@ -41,22 +41,20 @@ def test_sanity_dxt5():
assert im.mode == "RGBA"
assert im.size == (256, 256)
with Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) as target:
assert_image_equal(target, im)
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
def test_sanity_dxt3():
"""Check DXT3 images can be opened"""
with Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) as target:
with Image.open(TEST_FILE_DXT3) as im:
im.load()
with Image.open(TEST_FILE_DXT3) as im:
im.load()
assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.size == (256, 256)
assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.size == (256, 256)
assert_image_equal(target, im)
assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png"))
def test_dx10_bc7():
@ -69,8 +67,7 @@ def test_dx10_bc7():
assert im.mode == "RGBA"
assert im.size == (256, 256)
with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target:
assert_image_equal(target, im)
assert_image_equal_tofile(im, TEST_FILE_DX10_BC7.replace(".dds", ".png"))
def test_dx10_bc7_unorm_srgb():
@ -84,10 +81,9 @@ def test_dx10_bc7_unorm_srgb():
assert im.size == (16, 16)
assert im.info["gamma"] == 1 / 2.2
with Image.open(
TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png")
) as target:
assert_image_equal(target, im)
assert_image_equal_tofile(
im, TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png")
)
def test_dx10_r8g8b8a8():
@ -100,8 +96,7 @@ def test_dx10_r8g8b8a8():
assert im.mode == "RGBA"
assert im.size == (256, 256)
with Image.open(TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png")) as target:
assert_image_equal(target, im)
assert_image_equal_tofile(im, TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png"))
def test_dx10_r8g8b8a8_unorm_srgb():
@ -115,15 +110,15 @@ def test_dx10_r8g8b8a8_unorm_srgb():
assert im.size == (16, 16)
assert im.info["gamma"] == 1 / 2.2
with Image.open(
TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB.replace(".dds", ".png")
) as target:
assert_image_equal(target, im)
assert_image_equal_tofile(
im, TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB.replace(".dds", ".png")
)
def test_unimplemented_dxgi_format():
with pytest.raises(NotImplementedError):
Image.open("Tests/images/unimplemented_dxgi_format.dds")
with Image.open("Tests/images/unimplemented_dxgi_format.dds"):
pass
def test_uncompressed_rgb():
@ -136,8 +131,9 @@ def test_uncompressed_rgb():
assert im.mode == "RGBA"
assert im.size == (800, 600)
with Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")) as target:
assert_image_equal(target, im)
assert_image_equal_tofile(
im, TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")
)
def test__validate_true():
@ -170,7 +166,8 @@ def test_short_header():
img_file = f.read()
def short_header():
Image.open(BytesIO(img_file[:119]))
with Image.open(BytesIO(img_file[:119])):
pass # pragma: no cover
with pytest.raises(OSError):
short_header()
@ -192,4 +189,5 @@ def test_short_file():
def test_unimplemented_pixel_format():
with pytest.raises(NotImplementedError):
Image.open("Tests/images/unimplemented_pixel_format.dds")
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
pass

View File

@ -4,7 +4,12 @@ import pytest
from PIL import EpsImagePlugin, Image, features
from .helper import assert_image_similar, hopper, skip_unless_feature
from .helper import (
assert_image_similar,
assert_image_similar_tofile,
hopper,
skip_unless_feature,
)
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
@ -59,6 +64,7 @@ def test_invalid_file():
EpsImagePlugin.EpsImageFile(invalid_file)
@pytest.mark.valgrind_known_error(reason="Known Failing")
@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:
@ -71,8 +77,9 @@ def test_cmyk():
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)
assert_image_similar_tofile(
cmyk_image, "Tests/images/pil_sample_rgb.jpg", 10
)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@ -257,3 +264,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

View File

@ -2,7 +2,7 @@ import pytest
from PIL import FliImagePlugin, Image
from .helper import assert_image_equal, is_pypy
from .helper import assert_image_equal_tofile, is_pypy
# created as an export of a palette image from Gimp2.6
# save as...-> hopper.fli, default options.
@ -38,20 +38,20 @@ def test_unclosed_file():
def test_closed_file():
def open():
with pytest.warns(None) as record:
im = Image.open(static_test_file)
im.load()
im.close()
pytest.warns(None, open)
assert not record
def test_context_manager():
def open():
with pytest.warns(None) as record:
with Image.open(static_test_file) as im:
im.load()
pytest.warns(None, open)
assert not record
def test_tell():
@ -122,5 +122,19 @@ 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)
assert_image_equal_tofile(im, "Tests/images/a_fli.png")
@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()

View File

@ -21,4 +21,5 @@ def test_invalid_file():
def test_fpx_invalid_number_of_bands():
with pytest.raises(OSError, match="Invalid number of bands"):
Image.open("Tests/images/input_bw_five_bands.fpx")
with Image.open("Tests/images/input_bw_five_bands.fpx"):
pass

View File

@ -1,12 +1,11 @@
from PIL import Image
from .helper import assert_image_equal, assert_image_similar
from .helper import assert_image_equal_tofile, assert_image_similar
def test_load_raw():
with Image.open("Tests/images/ftex_uncompressed.ftu") as im:
with Image.open("Tests/images/ftex_uncompressed.png") as target:
assert_image_equal(im, target)
assert_image_equal_tofile(im, "Tests/images/ftex_uncompressed.png")
def test_load_dxt1():

Some files were not shown because too many files have changed in this diff Show More