Merge branch 'main' into open
This commit is contained in:
commit
42902eb5d7
@ -1 +1 @@
|
||||
cibuildwheel==3.4.0
|
||||
cibuildwheel==3.4.1
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
mypy==1.19.1
|
||||
mypy==1.20.2
|
||||
arro3-compute
|
||||
arro3-core
|
||||
IceSpringPySideStubs-PyQt6
|
||||
|
||||
1
.ci/requirements-sbom.txt
Normal file
1
.ci/requirements-sbom.txt
Normal file
@ -0,0 +1 @@
|
||||
check-jsonschema==0.37.1
|
||||
424
.github/INCIDENT_RESPONSE.md
vendored
Normal file
424
.github/INCIDENT_RESPONSE.md
vendored
Normal file
@ -0,0 +1,424 @@
|
||||
# Incident Response Plan — Pillow
|
||||
|
||||
This document describes how the Pillow maintainers detect, triage, fix, communicate, and
|
||||
learn from security incidents. It supplements the existing [Security Policy](SECURITY.md)
|
||||
and [Release Checklist](../RELEASING.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Preparation
|
||||
|
||||
Maintaining readiness before an incident occurs reduces response time and errors under pressure.
|
||||
|
||||
### 1.1 Version Support Matrix
|
||||
|
||||
Security fixes are applied to the **latest stable release only**. Users on older versions
|
||||
are expected to upgrade. Reporters should assume only the latest release will receive a patch.
|
||||
|
||||
| Branch | Status |
|
||||
|---|---|
|
||||
| `main` / latest stable | ✅ Security fixes applied |
|
||||
| All older releases | ❌ No security support — please upgrade |
|
||||
|
||||
### 1.2 Team Readiness
|
||||
|
||||
The four members of the Pillow core team are in regular contact and share collective
|
||||
responsibility for incident response. Any core team member may act as Incident Lead.
|
||||
Contact details are known to all team members.
|
||||
|
||||
### 1.3 Readiness Review
|
||||
|
||||
At each quarterly release, maintainers should re-read this document and update any stale content.
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This plan covers:
|
||||
|
||||
| Incident type | Examples |
|
||||
|---|---|
|
||||
| Vulnerability in Pillow's own Python or C code | Buffer overflow in an image decoder, integer overflow in `ImagingNew` |
|
||||
| Vulnerability in a bundled or wheel-shipped C library | libjpeg, libwebp, libtiff, libpng, openjpeg, libavif |
|
||||
| Supply-chain compromise | Malicious commit, stolen maintainer credentials, tampered PyPI wheel |
|
||||
| CI/CD or infrastructure compromise | GitHub Actions secret leak, Codecov breach, PyPI token exposure |
|
||||
| Critical non-security regression | Data-loss bug shipped in a release, crash on all supported platforms |
|
||||
|
||||
---
|
||||
|
||||
## 3. Definitions
|
||||
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| **Incident** | Any event that compromises or threatens the confidentiality, integrity, or availability of Pillow's code, release artifacts, or infrastructure. |
|
||||
| **Vulnerability** | A security flaw in Pillow or a bundled library that can be exploited by a crafted image or API call. |
|
||||
| **Incident Lead** | The maintainer who owns coordination of the response from triage to closure. |
|
||||
| **Embargo** | A period during which fix details are kept private to allow coordinated patching before public disclosure. |
|
||||
| **Yank** | A PyPI action that keeps a release downloadable by pinned users but removes it from default `pip install` resolution. |
|
||||
| **CVE** | Common Vulnerabilities and Exposures — a public identifier assigned to a specific vulnerability. |
|
||||
| **CNA** | CVE Numbering Authority — GitHub is a CNA and can assign CVEs directly through the advisory workflow. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Roles
|
||||
|
||||
| Role | Responsibility |
|
||||
|---|---|
|
||||
| **Incident Lead** | First maintainer to triage the report. Owns the incident until resolution. |
|
||||
| **Patch Owner** | Writes and tests the fix (may be the same person as Incident Lead). |
|
||||
| **Release Manager** | Cuts the point release following [RELEASING.md](../RELEASING.md). |
|
||||
| **Communications Owner** | Drafts the GitHub Security Advisory, announces on Mastodon, notifies distros. |
|
||||
| **Tidelift Contact** | For reports that arrive via Tidelift, coordinate through the Tidelift security portal. |
|
||||
|
||||
One person may fill multiple roles.
|
||||
|
||||
---
|
||||
|
||||
## 5. Severity Classification
|
||||
|
||||
Use the [CVSS 4.0](https://www.first.org/cvss/v4.0/specification-document) base score as
|
||||
a guide, mapped to the following levels:
|
||||
|
||||
| Severity | CVSS | Definition | Target Response SLA |
|
||||
|---|---|---|---|
|
||||
| **Critical** | 9.0 – 10.0 | Remote code execution, arbitrary write, or complete integrity/confidentiality loss achievable by opening a crafted image | Best effort; embargoed release where possible |
|
||||
| **High** | 7.0 – 8.9 | Heap/stack buffer overflow, use-after-free, or significant information disclosure | Best effort |
|
||||
| **Medium** | 4.0 – 6.9 | Denial of service via crafted image, out-of-bounds read, limited info disclosure | Next scheduled quarterly release, or earlier point release if needed |
|
||||
| **Low** | 0.1 – 3.9 | Minor information disclosure, unlikely to be exploitable in practice | Next quarterly release |
|
||||
|
||||
Supply-chain and CI/CD incidents are always treated as **Critical** regardless of CVSS.
|
||||
|
||||
> **Note:** These are good-faith targets for a small volunteer maintainer team, not contractual SLAs. Public safety and transparency will always be prioritised, even when timing varies.
|
||||
|
||||
---
|
||||
|
||||
## 6. Detection Sources
|
||||
|
||||
Vulnerabilities and incidents may be reported or discovered through:
|
||||
|
||||
1. **GitHub private security advisory** — preferred channel; see [SECURITY.md](SECURITY.md)
|
||||
2. **Tidelift security contact** — <https://tidelift.com/docs/security>
|
||||
3. **External researcher / coordinated disclosure** — e.g. Google Project Zero, vendor PSIRT
|
||||
4. **Automated scanning** — Dependabot, GitHub code-scanning (CodeQL), CI fuzzing
|
||||
5. **Distro security teams** — Debian, Red Hat, Ubuntu, Alpine may report upstream
|
||||
6. **User bug report** — public issue (reassess if it has security implications and convert to a private advisory if needed)
|
||||
|
||||
---
|
||||
|
||||
## 7. Response Process
|
||||
|
||||
### 7.1 Triage (all severities)
|
||||
|
||||
1. **Acknowledge receipt** to the reporter within **72 hours** using the template in
|
||||
[Appendix A](#appendix-a-communication-templates). Ask the reporter:
|
||||
- How they would like to be credited (name, handle, or anonymous)
|
||||
- Whether they intend to publish their own advisory, and if so, their preferred timeline
|
||||
- Thank them explicitly — reporters do the project a favour by disclosing privately.
|
||||
2. Reproduce the issue. If the report is invalid, close it and notify the reporter.
|
||||
3. Assign a severity level ([Section 5: Severity Classification](#5-severity-classification)).
|
||||
4. If the GitHub Security Advisory was not created by the reporter, create one now and keep
|
||||
it **private** until the fix is released. Add the reporter as a collaborator if they wish
|
||||
to be involved.
|
||||
5. **Request a CVE** through the GitHub Security Advisory workflow (GitHub is a CVE
|
||||
Numbering Authority — no separate MITRE form required). The CVE is reserved privately
|
||||
and published automatically when the advisory goes public.
|
||||
6. **Escalation** — Escalate beyond the core maintainer team if any of the following apply:
|
||||
- The fix requires changes to CPython or a dependency outside Pillow's control → contact the relevant upstream immediately
|
||||
- A legal concern arises (e.g. GDPR-reportable data exposure) → contact the project's legal/fiscal sponsor
|
||||
- The Incident Lead is unreachable for > 24 hours on a Critical issue → any other maintainer may assume the role
|
||||
|
||||
### 7.2 Fix Development
|
||||
|
||||
1. Develop the fix in a **private fork** or directly in the private security advisory
|
||||
workspace on GitHub. Do **not** push to a public branch before the embargo lifts.
|
||||
2. Write a regression test that fails before the fix and passes after.
|
||||
3. Review the patch with at least one other maintainer.
|
||||
|
||||
### 7.3 Standard (Non-Embargoed) Release
|
||||
|
||||
For Medium and Low severity, or when no distro pre-notification is needed:
|
||||
|
||||
1. Merge the fix to `main`, then cherry-pick to all affected release branches
|
||||
(see [RELEASING.md — Point release](../RELEASING.md)).
|
||||
2. Amend commit messages to include the CVE identifier.
|
||||
3. Follow the [Point release](../RELEASING.md#point-release) process in RELEASING.md to
|
||||
tag, push, and confirm wheels are live on PyPI.
|
||||
4. Publish the GitHub Security Advisory (this simultaneously publishes the CVE).
|
||||
|
||||
### 7.4 Embargoed Release
|
||||
|
||||
For Critical and High severity where distro pre-notification improves user safety:
|
||||
|
||||
1. Prepare patches against all affected release branches and test locally.
|
||||
2. Agree on an **embargo date** with the reporter (typically 7–14 days out, up to 90 days for
|
||||
complex issues).
|
||||
3. Privately send the patch to distros via the
|
||||
[linux-distros](https://oss-security.openwall.org/wiki/mailing-lists/distros) mailing list
|
||||
or directly to individual distro security teams.
|
||||
4. On the embargo date:
|
||||
- Amend commit messages with the CVE identifier.
|
||||
- Follow the [Embargoed release](../RELEASING.md#embargoed-release) process in
|
||||
RELEASING.md to tag, push, and confirm wheels are live on PyPI.
|
||||
- Publish the GitHub Security Advisory.
|
||||
|
||||
### 7.5 Supply-Chain / Infrastructure Compromise
|
||||
|
||||
1. **Immediately** revoke any potentially compromised credentials:
|
||||
- PyPI API tokens
|
||||
- GitHub personal access tokens and OAuth apps
|
||||
- Codecov or other CI service tokens
|
||||
2. Audit recent commits and releases for tampering:
|
||||
- Verify release tags against known-good SHAs
|
||||
- Re-inspect any wheel published since the potential compromise window
|
||||
3. If a PyPI release is suspected to be tampered: yank it immediately via the
|
||||
[PyPI release management page](https://pypi.org/manage/project/Pillow/releases/)
|
||||
(login required); see [https://pypi.org/security/](https://pypi.org/security/) for
|
||||
reporting to the PyPI security team.
|
||||
4. Issue a public advisory describing the scope and any user action required.
|
||||
|
||||
### 7.6 Recovery
|
||||
|
||||
After the fix is released and the advisory is public:
|
||||
|
||||
1. Verify that the patched wheels are live on PyPI and passing CI across all supported platforms.
|
||||
2. Confirm any yanked releases are handled correctly .
|
||||
3. Resume normal development operations on `main`.
|
||||
4. Monitor the GitHub issue tracker and Mastodon for user reports of residual problems for at least **72 hours** post-release.
|
||||
5. Close the private GitHub Security Advisory once recovery is confirmed.
|
||||
|
||||
---
|
||||
|
||||
## 8. Communication
|
||||
|
||||
### Internal (during embargo)
|
||||
- Use the **private GitHub Security Advisory** thread for coordination with the reporter.
|
||||
- Use private communication channels for all other coordination.
|
||||
- Do not discuss details in public issues, PRs, or Gitter/IRC channels.
|
||||
|
||||
### External (at or after disclosure)
|
||||
|
||||
| Audience | Channel | Timing |
|
||||
|---|---|---|
|
||||
| General users | [GitHub Security Advisory](https://github.com/python-pillow/Pillow/security/advisories) | At release |
|
||||
| PyPI ecosystem | CVE published via advisory | At release |
|
||||
| Downstream distros | Direct email or linux-distros list | Before embargo date (embargoed) |
|
||||
| Tidelift subscribers | Tidelift security portal | At release (or coordinated) |
|
||||
| Community | [Mastodon @pillow](https://fosstodon.org/@pillow) | At release |
|
||||
|
||||
**Advisory content should include:**
|
||||
- CVE identifier and CVSS score
|
||||
- Affected Pillow versions
|
||||
- Fixed version(s)
|
||||
- Nature of the vulnerability (without full exploit details if still fresh)
|
||||
- Credit to the reporter (with their consent)
|
||||
- Upgrade instructions (`python3 -m pip install --upgrade Pillow`)
|
||||
|
||||
---
|
||||
|
||||
## 9. Dependency Map
|
||||
|
||||
Understanding what Pillow depends on (upstream) and what depends on Pillow (downstream)
|
||||
is essential for scoping impact and coordinating notifications during an incident.
|
||||
|
||||
### 9.1 Upstream Dependencies
|
||||
|
||||
#### Bundled C libraries (shipped in official wheels)
|
||||
|
||||
These libraries are compiled into Pillow's binary wheels. A CVE in any of them may
|
||||
require a Pillow point release even if Pillow's own code is unchanged.
|
||||
|
||||
| Library | Purpose | Security advisory tracker |
|
||||
|---|---|---|
|
||||
| [libjpeg-turbo](https://libjpeg-turbo.org/) | JPEG encode/decode | [GitHub](https://github.com/libjpeg-turbo/libjpeg-turbo/security) |
|
||||
| [libpng](http://www.libpng.org/pub/png/libpng.html) | PNG encode/decode within FreeType 2, OpenJPEG and WebP | [SourceForge](https://sourceforge.net/p/libpng/bugs/) |
|
||||
| [libtiff](https://libtiff.gitlab.io/libtiff/) | TIFF encode/decode | [GitLab](https://gitlab.com/libtiff/libtiff/-/work_items) |
|
||||
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | WebP encode/decode | [Chromium tracker](https://issues.webmproject.org/issues) |
|
||||
| [libavif](https://github.com/AOMediaCodec/libavif) | AVIF encode/decode | [GitHub](https://github.com/AOMediaCodec/libavif/security) |
|
||||
| [aom](https://aomedia.googlesource.com/aom/) | AV1 codec (AVIF) | [Chromium tracker](https://aomedia.issues.chromium.org/issues) |
|
||||
| [dav1d](https://code.videolan.org/videolan/dav1d) | AV1 decode (AVIF) | [VideoLAN Security](https://www.videolan.org/security/) |
|
||||
| [openjpeg](https://www.openjpeg.org/) | JPEG 2000 encode/decode | [GitHub](https://github.com/uclouvain/openjpeg/security) |
|
||||
| [freetype2](https://freetype.org/) | Font rendering | [GitLab](https://gitlab.freedesktop.org/freetype/freetype/-/work_items) |
|
||||
| [lcms2](https://www.littlecms.com/) | ICC color management | [GitHub](https://github.com/mm2/Little-CMS/security) |
|
||||
| [harfbuzz](https://harfbuzz.github.io/) | Text shaping (via raqm) | [GitHub](https://github.com/harfbuzz/harfbuzz/security) |
|
||||
| [raqm](https://github.com/HOST-Oman/libraqm) | Complex text layout | [GitHub](https://github.com/HOST-Oman/libraqm) |
|
||||
| [fribidi](https://github.com/fribidi/fribidi) | Unicode bidi (via raqm) | [GitHub](https://github.com/fribidi/fribidi) |
|
||||
| [zlib](https://zlib.net/) | Deflate compression | [zlib.net](https://zlib.net/) |
|
||||
| [liblzma / xz-utils](https://tukaani.org/xz/) | XZ/LZMA compression | [GitHub](https://github.com/tukaani-project/xz/security) |
|
||||
| [bzip2](https://gitlab.com/bzip2/bzip2) | BZ2 compression | [GitLab](https://gitlab.com/bzip2/bzip2/-/work_items) |
|
||||
| [zstd](https://github.com/facebook/zstd) | Zstandard compression | [GitHub](https://github.com/facebook/zstd/security) |
|
||||
| [brotli](https://github.com/google/brotli) | Brotli compression | [GitHub](https://github.com/google/brotli/security) |
|
||||
| [libyuv](https://chromium.googlesource.com/libyuv/libyuv/) | YUV conversion | [Chromium tracker](https://libyuv.issues.chromium.org/issues) |
|
||||
|
||||
#### Python-level dependencies
|
||||
|
||||
| Package | Required? | Purpose |
|
||||
|---|---|---|
|
||||
| `setuptools` | Build-time only | Package build backend |
|
||||
| `pybind11` | Build-time only | Compile C files in parallel |
|
||||
| `olefile` | Optional (`fpx`, `mic` extras) | OLE2 container parsing (FPX, MIC formats) |
|
||||
| `defusedxml` | Optional (`xmp` extra) | Safe XML parsing for XMP metadata |
|
||||
|
||||
See [`pyproject.toml`](../pyproject.toml) for the complete and authoritative list of
|
||||
optional dependencies.
|
||||
|
||||
### 9.2 Responding to an Upstream Vulnerability
|
||||
|
||||
When a CVE is published for a bundled C library:
|
||||
|
||||
1. Assess whether the vulnerable code path is reachable through Pillow's API.
|
||||
2. If reachable, treat as a Pillow vulnerability and follow [Section 5: Severity Classification](#5-severity-classification).
|
||||
3. Update the bundled library version in the wheel build scripts and rebuild wheels.
|
||||
4. Reference the upstream CVE in Pillow's release notes and GitHub Security Advisory.
|
||||
5. If not reachable, document the rationale in a public issue so downstream distributors
|
||||
can make informed decisions about patching their system packages.
|
||||
|
||||
### 9.3 Downstream Dependencies
|
||||
|
||||
A vulnerability in Pillow can have wide impact. Notify or consider the blast radius of
|
||||
these downstream consumers when assessing severity and planning communications.
|
||||
|
||||
#### Linux distribution packages
|
||||
|
||||
| Distribution | Package name | Security contact |
|
||||
|---|---|---|
|
||||
| Debian / Ubuntu | `python3-pil` | [Debian Security](https://www.debian.org/security/) / [Ubuntu Security](https://ubuntu.com/security) |
|
||||
| Fedora / RHEL / CentOS | `python3-pillow` | [Red Hat Security](https://access.redhat.com/security/) |
|
||||
| Alpine Linux | `py3-pillow` | [Alpine security](https://security.alpinelinux.org/) |
|
||||
| Arch Linux | `python-pillow` | [Arch security tracker](https://security.archlinux.org/) |
|
||||
| Homebrew | `pillow` | [Homebrew maintainers](https://github.com/Homebrew/homebrew-core/security) |
|
||||
| conda-forge | `pillow` | [conda-forge](https://github.com/conda-forge/pillow-feedstock) |
|
||||
|
||||
#### Major Python ecosystem consumers
|
||||
|
||||
These are high-profile projects known to depend on Pillow; a critical vulnerability may
|
||||
warrant proactive notification.
|
||||
|
||||
| Project | Usage |
|
||||
|---|---|
|
||||
| [matplotlib](https://matplotlib.org/) | Image I/O for plots |
|
||||
| [scikit-image](https://scikit-image.org/) | Image processing |
|
||||
| [torchvision](https://github.com/pytorch/vision) (PyTorch) | Dataset loading, transforms |
|
||||
| [Keras / TensorFlow](https://keras.io/) | Image preprocessing utilities |
|
||||
| [Django](https://www.djangoproject.com/) | `ImageField` validation and thumbnail generation |
|
||||
| [Wagtail](https://wagtail.org/) | CMS image renditions |
|
||||
| [Plone](https://plone.org/) | CMS image handling |
|
||||
| [Jupyter / IPython](https://jupyter.org/) | Inline image display |
|
||||
| [ReportLab](https://www.reportlab.com/) | PDF image embedding |
|
||||
| [Tidelift subscribers](https://tidelift.com/) | Enterprise consumers (coordinated via Tidelift) |
|
||||
|
||||
#### Pillow ecosystem plugins
|
||||
|
||||
Third-party plugins extend Pillow and are distributed separately on PyPI. Their
|
||||
maintainers should be notified for Critical/High issues that affect the plugin API
|
||||
or the formats they decode. See the
|
||||
[full plugin list](https://pillow.readthedocs.io/en/stable/handbook/third-party-plugins.html#plugin-list).
|
||||
|
||||
---
|
||||
|
||||
## 11. Plan Maintenance
|
||||
|
||||
This document is a living record. It should be kept current so it is useful when an incident actually occurs. Revisit it during the [Section 1.3 readiness review](#13-readiness-review) at each quarterly release.
|
||||
|
||||
---
|
||||
|
||||
## 12. References
|
||||
|
||||
- [Security Policy](SECURITY.md)
|
||||
- [Release Checklist](../RELEASING.md)
|
||||
- [Contributing Guide](CONTRIBUTING.md)
|
||||
- [Tidelift Security Contact](https://tidelift.com/docs/security)
|
||||
- [GitHub: Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)
|
||||
- [GitHub as a CVE Numbering Authority (CNA)](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/about-repository-security-advisories)
|
||||
- [FIRST CVSS 4.0 Calculator](https://www.first.org/cvss/calculator/4.0)
|
||||
- [linux-distros mailing list](https://oss-security.openwall.org/wiki/mailing-lists/distros)
|
||||
- [OpenSSF CVD Guide](https://github.com/ossf/oss-vulnerability-guide) *(basis for this plan)*
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Communication Templates
|
||||
|
||||
### A.1 Reporter Acknowledgment
|
||||
|
||||
> Subject: Re: [Security] \<brief issue description\>
|
||||
>
|
||||
> Hi \<name\>,
|
||||
>
|
||||
> Thank you for taking the time to report this issue. We appreciate it.
|
||||
>
|
||||
> We have received your report and will review it as soon as possible. We will
|
||||
> keep you updated on our progress.
|
||||
>
|
||||
> Questions:
|
||||
>
|
||||
> - How would you like to be credited in the advisory? (name, handle,
|
||||
> organisation, or anonymous)
|
||||
> - Do you plan to publish your own write-up or advisory? If so, do you have a
|
||||
> disclosure date in mind?
|
||||
>
|
||||
> We apply coordinated disclosure principles to all vulnerability reports. If
|
||||
> you have any questions or concerns at any point, please reply to this thread.
|
||||
>
|
||||
> Thank you again,
|
||||
> The Pillow team
|
||||
|
||||
### A.2 Embargoed Distro Notification
|
||||
|
||||
> Subject: [EMBARGOED] Pillow security issue — \<CVE-XXXX-XXXXX\> — disclosure \<DATE\>
|
||||
>
|
||||
> This is an embargoed notification of a vulnerability in Pillow. Please keep this
|
||||
> information confidential until the disclosure date listed below.
|
||||
>
|
||||
> **CVE:** \<CVE-XXXX-XXXXX\>
|
||||
>
|
||||
> **Affected versions:** \<e.g. Pillow < 11.x.x\>
|
||||
>
|
||||
> **Fixed version:** \<version\>
|
||||
>
|
||||
> **Severity:** \<Critical / High / Medium / Low\> (CVSS \<score\>: \<vector\>)
|
||||
>
|
||||
> **Reporter:** \<name / affiliation, or "reported privately"\>
|
||||
>
|
||||
> **Public disclosure date:** \<DATE TIME UTC\>
|
||||
>
|
||||
> **Summary:**
|
||||
> \<One paragraph describing the vulnerability class and impact without a full exploit.\>
|
||||
>
|
||||
> **Proof of concept:**
|
||||
> \<Minimal reproducer or attached patch.\>
|
||||
>
|
||||
> **Remediation:**
|
||||
> Upgrade to Pillow \<fixed version\>. No known workaround.
|
||||
>
|
||||
> Please do not share this information, issue public patches, or make user communications
|
||||
> before the disclosure date. We will notify this list immediately if the date changes.
|
||||
>
|
||||
> — The Pillow maintainers
|
||||
|
||||
### A.3 Public Disclosure Advisory
|
||||
|
||||
*(Published as a GitHub Security Advisory; the CVE and date are included automatically.)*
|
||||
|
||||
> **Summary:** \<One-paragraph technical summary.\>
|
||||
>
|
||||
> **CVE:** \<CVE-XXXX-XXXXX\>
|
||||
>
|
||||
> **Affected versions:** Pillow \< \<fixed version\>
|
||||
>
|
||||
> **Fixed version:** \<version\>
|
||||
>
|
||||
> **Severity:** \<rating\> (CVSS \<score\>)
|
||||
>
|
||||
> **Reporter:** \<credited name / "reported privately"\>
|
||||
>
|
||||
> **Details:**
|
||||
> \<Fuller technical description. Include attack scenario where helpful.\>
|
||||
>
|
||||
> **Remediation:**
|
||||
> ```
|
||||
> python3 -m pip install --upgrade Pillow
|
||||
> ```
|
||||
>
|
||||
> **Timeline:**
|
||||
> - Reported: \<date\>
|
||||
> - Fixed: \<date\>
|
||||
> - Disclosed: \<date\>
|
||||
18
.github/SECURITY.md
vendored
18
.github/SECURITY.md
vendored
@ -1,7 +1,21 @@
|
||||
# Security policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).
|
||||
|
||||
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
||||
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/docs/security). Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
DO NOT report sensitive vulnerability information in public.
|
||||
**DO NOT report sensitive vulnerability information in public.**
|
||||
|
||||
## Threat model
|
||||
|
||||
Pillow's primary attack surface is parsing untrusted image data. A full STRIDE threat model covering spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege is maintained in the [Security handbook page](https://pillow.readthedocs.io/en/latest/handbook/security.html).
|
||||
|
||||
Key risks to be aware of when using Pillow to process untrusted images:
|
||||
|
||||
- **Decompression bombs** — do not set `Image.MAX_IMAGE_PIXELS = None` in production.
|
||||
- **EPS files invoke Ghostscript** — block EPS input at the application layer unless strictly required.
|
||||
- **`ImageMath.unsafe_eval()`** — never pass user-controlled strings to this function; use `lambda_eval` instead.
|
||||
- **C extension memory safety** — keep Pillow and its bundled C libraries (libjpeg, libpng, libtiff, libwebp, etc.) up to date.
|
||||
- **Sandboxing** — for high-risk deployments, run image processing in a sandboxed subprocess.
|
||||
|
||||
19
.github/dependencies.json
vendored
Normal file
19
.github/dependencies.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"brotli": "1.2.0",
|
||||
"bzip2": "1.0.8",
|
||||
"freetype": "2.14.3",
|
||||
"fribidi": "1.0.16",
|
||||
"harfbuzz": "14.2.0",
|
||||
"jpegturbo": "3.1.4.1",
|
||||
"lcms2": "2.19",
|
||||
"libavif": "1.4.1",
|
||||
"libimagequant": "4.4.1",
|
||||
"libpng": "1.6.58",
|
||||
"libwebp": "1.6.0",
|
||||
"libxcb": "1.17.0",
|
||||
"openjpeg": "2.5.4",
|
||||
"tiff": "4.7.1",
|
||||
"xz": "5.8.3",
|
||||
"zlib-ng": "2.3.3",
|
||||
"zstd": "1.5.7"
|
||||
}
|
||||
560
.github/generate-sbom.py
vendored
Executable file
560
.github/generate-sbom.py
vendored
Executable file
@ -0,0 +1,560 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a CycloneDX 1.7 SBOM for Pillow's C extensions and their
|
||||
vendored/optional native library dependencies.
|
||||
|
||||
Usage:
|
||||
python3 .github/generate-sbom.py [output-file]
|
||||
|
||||
Output defaults to pillow-{version}.cdx.json in the current directory.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import datetime as dt
|
||||
import difflib
|
||||
import hashlib
|
||||
import json
|
||||
import urllib.request
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
version_file = Path(__file__).parent.parent / "src" / "PIL" / "_version.py"
|
||||
return version_file.read_text(encoding="utf-8").split('"')[1]
|
||||
|
||||
|
||||
def load_dep_versions() -> dict[str, str]:
|
||||
deps_file = Path(__file__).parent / "dependencies.json"
|
||||
return json.loads(deps_file.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def upstream_diff_b64(
|
||||
upstream_url: str,
|
||||
upstream_display: bytes,
|
||||
local_path: Path,
|
||||
local_display: bytes,
|
||||
) -> str:
|
||||
"""
|
||||
Fetch an upstream file and return a base64-encoded unified diff vs the local copy.
|
||||
"""
|
||||
with urllib.request.urlopen(upstream_url) as resp:
|
||||
upstream_text = resp.read()
|
||||
local_text = local_path.read_bytes()
|
||||
diff_lines = difflib.diff_bytes(
|
||||
difflib.unified_diff,
|
||||
upstream_text.splitlines(keepends=True),
|
||||
local_text.splitlines(keepends=True),
|
||||
fromfile=b"a/" + upstream_display,
|
||||
tofile=b"b/" + local_display,
|
||||
)
|
||||
return base64.b64encode(b"".join(diff_lines)).decode()
|
||||
|
||||
|
||||
def generate(version: str) -> dict:
|
||||
serial = str(uuid.uuid4())
|
||||
now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
purl = f"pkg:pypi/pillow@{version}"
|
||||
root = Path(__file__).parent.parent
|
||||
thirdparty = root / "src" / "thirdparty"
|
||||
versions = load_dep_versions()
|
||||
|
||||
metadata_component = {
|
||||
"bom-ref": purl,
|
||||
"type": "library",
|
||||
"name": "Pillow",
|
||||
"version": version,
|
||||
"description": "Python Imaging Library (fork)",
|
||||
"licenses": [{"license": {"id": "MIT-CMU"}}],
|
||||
"purl": purl,
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://python-pillow.github.io"},
|
||||
{"type": "vcs", "url": "https://github.com/python-pillow/Pillow"},
|
||||
{"type": "documentation", "url": "https://pillow.readthedocs.io"},
|
||||
{
|
||||
"type": "security-contact",
|
||||
"url": "https://github.com/python-pillow/Pillow/security/policy",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
c_extensions = [
|
||||
("PIL._avif", "AVIF image format extension"),
|
||||
(
|
||||
"PIL._imaging",
|
||||
"Core image processing extension "
|
||||
"(decode, encode, map, display, outline, path, libImaging)",
|
||||
),
|
||||
("PIL._imagingcms", "LittleCMS2 colour management extension"),
|
||||
("PIL._imagingft", "FreeType font rendering extension"),
|
||||
("PIL._imagingmath", "Image math operations extension"),
|
||||
("PIL._imagingmorph", "Image morphology extension"),
|
||||
("PIL._imagingtk", "Tk/Tcl display extension"),
|
||||
("PIL._webp", "WebP image format extension"),
|
||||
]
|
||||
|
||||
ext_components = [
|
||||
{
|
||||
"bom-ref": f"{purl}#c-ext/{name}",
|
||||
"type": "library",
|
||||
"name": name,
|
||||
"version": version,
|
||||
"description": desc,
|
||||
"licenses": [{"license": {"id": "MIT-CMU"}}],
|
||||
"purl": f"{purl}#c-ext/{name}",
|
||||
}
|
||||
for name, desc in c_extensions
|
||||
]
|
||||
|
||||
vendored_components = [
|
||||
{
|
||||
"bom-ref": f"{purl}#thirdparty/fribidi-shim",
|
||||
"type": "library",
|
||||
"name": "fribidi-shim",
|
||||
"version": "1.x",
|
||||
"description": "FriBiDi runtime-loading shim "
|
||||
"(vendored in src/thirdparty/fribidi-shim/); "
|
||||
"loads libfribidi dynamically",
|
||||
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
|
||||
"hashes": [
|
||||
{
|
||||
"alg": "SHA-256",
|
||||
"content": sha256_file(thirdparty / "fribidi-shim" / "fribidi.c"),
|
||||
}
|
||||
],
|
||||
"pedigree": {
|
||||
"notes": "Pillow-authored shim; not taken from an upstream project."
|
||||
},
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:github/python/pythoncapi-compat",
|
||||
"type": "library",
|
||||
"name": "pythoncapi_compat",
|
||||
"description": "Backport header for new CPython C-API functions "
|
||||
"(vendored in src/thirdparty/pythoncapi_compat.h)",
|
||||
"licenses": [{"license": {"id": "0BSD"}}],
|
||||
"hashes": [
|
||||
{
|
||||
"alg": "SHA-256",
|
||||
"content": sha256_file(thirdparty / "pythoncapi_compat.h"),
|
||||
}
|
||||
],
|
||||
"pedigree": {
|
||||
"notes": "Vendored unmodified from upstream python/pythoncapi-compat."
|
||||
},
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/python/pythoncapi-compat",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": f"{purl}#thirdparty/raqm",
|
||||
"type": "library",
|
||||
"name": "raqm",
|
||||
"version": "0.10.5",
|
||||
"description": "Complex text layout library "
|
||||
"(vendored in src/thirdparty/raqm/)",
|
||||
"licenses": [{"license": {"id": "MIT"}}],
|
||||
"hashes": [
|
||||
{
|
||||
"alg": "SHA-256",
|
||||
"content": sha256_file(thirdparty / "raqm" / "raqm.c"),
|
||||
}
|
||||
],
|
||||
"pedigree": {
|
||||
"ancestors": [
|
||||
{
|
||||
"bom-ref": "pkg:github/HOST-Oman/libraqm@0.10.5#upstream",
|
||||
"type": "library",
|
||||
"name": "raqm",
|
||||
"version": "0.10.5",
|
||||
"purl": "pkg:github/HOST-Oman/libraqm@0.10.5",
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/HOST-Oman/libraqm/releases/tag/v0.10.5",
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"patches": [
|
||||
{
|
||||
"type": "unofficial",
|
||||
"diff": {
|
||||
"text": {
|
||||
# raqm-version.h.in → raqm-version.h:
|
||||
# template @RAQM_VERSION_*@ placeholders replaced
|
||||
# with literal 0.10.5 values; filename changed to
|
||||
# drop the .in suffix; minor indentation fix.
|
||||
"content": upstream_diff_b64(
|
||||
"https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.10.5/src/raqm-version.h.in",
|
||||
b"src/raqm-version.h.in",
|
||||
thirdparty / "raqm" / "raqm-version.h",
|
||||
b"src/raqm-version.h",
|
||||
),
|
||||
"encoding": "base64",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "unofficial",
|
||||
"diff": {
|
||||
"text": {
|
||||
# raqm.c: wrap the <fribidi.h> include in an
|
||||
# #ifdef HAVE_FRIBIDI_SYSTEM guard so that when
|
||||
# building without a system FriBiDi Pillow's own
|
||||
# fribidi-shim is used instead.
|
||||
"content": upstream_diff_b64(
|
||||
"https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.10.5/src/raqm.c",
|
||||
b"src/raqm.c",
|
||||
thirdparty / "raqm" / "raqm.c",
|
||||
b"src/raqm.c",
|
||||
),
|
||||
"encoding": "base64",
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"notes": (
|
||||
"Vendored from upstream HOST-Oman/libraqm v0.10.5 with two "
|
||||
"Pillow-specific modifications: (1) raqm-version.h.in was "
|
||||
"pre-processed into raqm-version.h with version placeholders "
|
||||
"replaced by literal values; (2) raqm.c wraps the <fribidi.h> "
|
||||
"include in an #ifdef HAVE_FRIBIDI_SYSTEM guard so Pillow's "
|
||||
"bundled fribidi-shim is used when a system FriBiDi is absent."
|
||||
),
|
||||
},
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/python-pillow/Pillow/tree/main/src/thirdparty/raqm",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
native_deps = [
|
||||
{
|
||||
"bom-ref": "pkg:generic/freetype2",
|
||||
"type": "library",
|
||||
"name": "FreeType",
|
||||
"version": versions["freetype"],
|
||||
"scope": "optional",
|
||||
"description": "Font rendering (optional, used by PIL._imagingft). "
|
||||
"Required for text/font support.",
|
||||
"licenses": [{"license": {"id": "FTL"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://freetype.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://download.savannah.gnu.org/releases/freetype/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/fribidi",
|
||||
"type": "library",
|
||||
"name": "FriBiDi",
|
||||
"version": versions["fribidi"],
|
||||
"scope": "optional",
|
||||
"description": "Unicode bidi algorithm library (optional, "
|
||||
"loaded at runtime by fribidi-shim).",
|
||||
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/fribidi/fribidi/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/harfbuzz",
|
||||
"type": "library",
|
||||
"name": "HarfBuzz",
|
||||
"version": versions["harfbuzz"],
|
||||
"scope": "optional",
|
||||
"description": "Text shaping (optional, required by libraqm "
|
||||
"for complex text layout).",
|
||||
"licenses": [{"license": {"id": "MIT"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://harfbuzz.github.io"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libavif",
|
||||
"type": "library",
|
||||
"name": "libavif",
|
||||
"version": versions["libavif"],
|
||||
"scope": "optional",
|
||||
"description": "AVIF codec (optional, used by PIL._avif).",
|
||||
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://github.com/AOMediaCodec/libavif"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/AOMediaCodec/libavif/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libimagequant",
|
||||
"type": "library",
|
||||
"name": "libimagequant",
|
||||
"version": versions["libimagequant"],
|
||||
"scope": "optional",
|
||||
"description": "Improved colour quantization (optional).",
|
||||
"licenses": [{"license": {"id": "GPL-3.0-or-later"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://pngquant.org/lib/"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/ImageOptim/libimagequant/tags",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libjpeg",
|
||||
"type": "library",
|
||||
"name": "libjpeg / libjpeg-turbo",
|
||||
"version": versions["jpegturbo"],
|
||||
"description": "JPEG codec (required by default; disable with "
|
||||
"-C jpeg=disable).",
|
||||
"licenses": [
|
||||
{"license": {"id": "IJG"}},
|
||||
{"license": {"id": "BSD-3-Clause"}},
|
||||
],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://ijg.org"},
|
||||
{"type": "website", "url": "https://libjpeg-turbo.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libtiff",
|
||||
"type": "library",
|
||||
"name": "libtiff",
|
||||
"version": versions["tiff"],
|
||||
"scope": "optional",
|
||||
"description": "TIFF codec (optional).",
|
||||
"licenses": [{"license": {"id": "libtiff"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://libtiff.gitlab.io/libtiff/"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://download.osgeo.org/libtiff/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libwebp",
|
||||
"type": "library",
|
||||
"name": "libwebp",
|
||||
"version": versions["libwebp"],
|
||||
"scope": "optional",
|
||||
"description": "WebP codec (optional, used by PIL._webp).",
|
||||
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
|
||||
"externalReferences": [
|
||||
{
|
||||
"type": "website",
|
||||
"url": "https://chromium.googlesource.com/webm/libwebp",
|
||||
},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://chromium.googlesource.com/webm/libwebp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/libxcb",
|
||||
"type": "library",
|
||||
"name": "libxcb",
|
||||
"version": versions["libxcb"],
|
||||
"scope": "optional",
|
||||
"description": "X11 screen-grab support (optional, "
|
||||
"used by PIL._imaging on macOS and Linux).",
|
||||
"licenses": [{"license": {"id": "X11"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://xcb.freedesktop.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://xcb.freedesktop.org/dist/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/littlecms2",
|
||||
"type": "library",
|
||||
"name": "Little CMS 2",
|
||||
"version": versions["lcms2"],
|
||||
"scope": "optional",
|
||||
"description": "Colour management (optional, used by PIL._imagingcms).",
|
||||
"licenses": [{"license": {"id": "MIT"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://www.littlecms.com"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/mm2/Little-CMS/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/openjpeg",
|
||||
"type": "library",
|
||||
"name": "OpenJPEG",
|
||||
"version": versions["openjpeg"],
|
||||
"scope": "optional",
|
||||
"description": "JPEG 2000 codec (optional).",
|
||||
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://www.openjpeg.org"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/uclouvain/openjpeg/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:pypi/pybind11",
|
||||
"type": "library",
|
||||
"name": "pybind11",
|
||||
"scope": "excluded",
|
||||
"description": "Parallel C compilation library (build-time dependency).",
|
||||
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://pybind11.readthedocs.io"},
|
||||
{
|
||||
"type": "distribution",
|
||||
"url": "https://github.com/pybind/pybind11/releases",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:generic/zlib",
|
||||
"type": "library",
|
||||
"name": "zlib",
|
||||
"version": versions["zlib-ng"],
|
||||
"description": "Deflate/PNG compression (required by default; "
|
||||
"disable with -C zlib=disable).",
|
||||
"licenses": [{"license": {"id": "Zlib"}}],
|
||||
"externalReferences": [
|
||||
{"type": "website", "url": "https://zlib.net"},
|
||||
{"type": "distribution", "url": "https://zlib.net"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
{
|
||||
"ref": purl,
|
||||
"dependsOn": [e["bom-ref"] for e in ext_components],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._avif",
|
||||
"dependsOn": ["pkg:generic/libavif"],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._imaging",
|
||||
"dependsOn": [
|
||||
"pkg:generic/libimagequant",
|
||||
"pkg:generic/libjpeg",
|
||||
"pkg:generic/libtiff",
|
||||
"pkg:generic/libxcb",
|
||||
"pkg:generic/openjpeg",
|
||||
"pkg:generic/zlib",
|
||||
],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._imagingcms",
|
||||
"dependsOn": ["pkg:generic/littlecms2"],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._imagingft",
|
||||
"dependsOn": [
|
||||
"pkg:generic/freetype2",
|
||||
"pkg:generic/fribidi",
|
||||
"pkg:generic/harfbuzz",
|
||||
f"{purl}#thirdparty/fribidi-shim",
|
||||
f"{purl}#thirdparty/raqm",
|
||||
],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#c-ext/PIL._webp",
|
||||
"dependsOn": ["pkg:generic/libwebp"],
|
||||
},
|
||||
{
|
||||
"ref": f"{purl}#thirdparty/raqm",
|
||||
"dependsOn": [
|
||||
"pkg:generic/harfbuzz",
|
||||
f"{purl}#thirdparty/fribidi-shim",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": f"urn:uuid:{serial}",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": now,
|
||||
"lifecycles": [{"phase": "build"}],
|
||||
"tools": {
|
||||
"components": [
|
||||
{
|
||||
"type": "application",
|
||||
"name": "generate-sbom.py",
|
||||
"group": "pillow",
|
||||
}
|
||||
]
|
||||
},
|
||||
"component": metadata_component,
|
||||
},
|
||||
"components": ext_components + vendored_components + native_deps,
|
||||
"dependencies": dependencies,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
version = get_version()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
"output",
|
||||
nargs="?",
|
||||
type=Path,
|
||||
default=Path(f"pillow-{version}.cdx.json"),
|
||||
help="output file",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
sbom = generate(version)
|
||||
args.output.write_text(json.dumps(sbom, indent=2) + "\n", encoding="utf-8")
|
||||
print(
|
||||
f"Wrote {args.output} (Pillow {version}, {len(sbom['components'])} components)"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
165
.github/renovate.json
vendored
165
.github/renovate.json
vendored
@ -7,16 +7,169 @@
|
||||
"Dependency"
|
||||
],
|
||||
"minimumReleaseAge": "7 days",
|
||||
"prCreation": "not-pending",
|
||||
"schedule": [
|
||||
"* * 3 * *"
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"brotli\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "brotli",
|
||||
"packageNameTemplate": "google/brotli",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"bzip2\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "bzip2",
|
||||
"packageNameTemplate": "bzip2/bzip2",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"extractVersionTemplate": "^bzip2-(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"freetype\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "freetype",
|
||||
"packageNameTemplate": "freetype/freetype",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"registryUrlTemplate": "https://gitlab.freedesktop.org",
|
||||
"extractVersionTemplate": "^VER-(?<version>[\\d-]+)$",
|
||||
"versioningTemplate": "regex:^(?<major>\\d+)[.-](?<minor>\\d+)[.-](?<patch>\\d+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"fribidi\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "fribidi",
|
||||
"packageNameTemplate": "fribidi/fribidi",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"harfbuzz\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "harfbuzz",
|
||||
"packageNameTemplate": "harfbuzz/harfbuzz",
|
||||
"datasourceTemplate": "github-releases"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"jpegturbo\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "jpegturbo",
|
||||
"packageNameTemplate": "libjpeg-turbo/libjpeg-turbo",
|
||||
"datasourceTemplate": "github-releases"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"lcms2\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "lcms2",
|
||||
"packageNameTemplate": "mm2/Little-CMS",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^lcms(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libavif\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libavif",
|
||||
"packageNameTemplate": "AOMediaCodec/libavif",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libimagequant\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libimagequant",
|
||||
"packageNameTemplate": "ImageOptim/libimagequant",
|
||||
"datasourceTemplate": "github-tags"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libpng\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libpng",
|
||||
"packageNameTemplate": "pnggroup/libpng",
|
||||
"datasourceTemplate": "github-tags",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libwebp\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libwebp",
|
||||
"packageNameTemplate": "webmproject/libwebp",
|
||||
"datasourceTemplate": "github-tags",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"libxcb\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "libxcb",
|
||||
"packageNameTemplate": "xorg/lib/libxcb",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"registryUrlTemplate": "https://gitlab.freedesktop.org",
|
||||
"extractVersionTemplate": "^libxcb-(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"openjpeg\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "openjpeg",
|
||||
"packageNameTemplate": "uclouvain/openjpeg",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"tiff\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "tiff",
|
||||
"packageNameTemplate": "libtiff/libtiff",
|
||||
"datasourceTemplate": "gitlab-tags",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"xz\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "xz",
|
||||
"packageNameTemplate": "tukaani-project/xz",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"zlib-ng\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "zlib-ng",
|
||||
"packageNameTemplate": "zlib-ng/zlib-ng",
|
||||
"datasourceTemplate": "github-releases"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
|
||||
"matchStrings": ["\"zstd\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
|
||||
"depNameTemplate": "zstd",
|
||||
"packageNameTemplate": "facebook/zstd",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.+)$"
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "github-actions",
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"matchManagers": ["github-actions"],
|
||||
"separateMajorMinor": false
|
||||
}
|
||||
],
|
||||
"schedule": [
|
||||
"* * 3 * *"
|
||||
]
|
||||
}
|
||||
|
||||
17
.github/workflows/cifuzz.yml
vendored
17
.github/workflows/cifuzz.yml
vendored
@ -4,17 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths:
|
||||
paths: &paths
|
||||
- ".github/dependencies.json"
|
||||
- ".github/workflows/cifuzz.yml"
|
||||
- ".github/workflows/wheels-dependencies.sh"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/cifuzz.yml"
|
||||
- ".github/workflows/wheels-dependencies.sh"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
paths: *paths
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -33,27 +30,27 @@ jobs:
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@c11174f47deee98f260dede5d661614bda78ae39 # 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
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@c11174f47deee98f260dede5d661614bda78ae39 # master
|
||||
with:
|
||||
oss-fuzz-project-name: 'pillow'
|
||||
fuzz-seconds: 600
|
||||
language: python
|
||||
dry-run: false
|
||||
- name: Upload New Crash
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
- name: Upload Legacy Crash
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: steps.run.outcome == 'success'
|
||||
with:
|
||||
name: crash
|
||||
|
||||
17
.github/workflows/docs.yml
vendored
17
.github/workflows/docs.yml
vendored
@ -4,15 +4,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths:
|
||||
paths: &paths
|
||||
- ".github/workflows/docs.yml"
|
||||
- "docs/**"
|
||||
- "src/PIL/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/docs.yml"
|
||||
- "docs/**"
|
||||
- "src/PIL/**"
|
||||
paths: *paths
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -32,12 +29,12 @@ jobs:
|
||||
name: Docs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
@ -49,21 +46,21 @@ jobs:
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Cache libavif
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libavif
|
||||
with:
|
||||
path: ~/cache-libavif
|
||||
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
|
||||
|
||||
- name: Cache libimagequant
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libimagequant
|
||||
with:
|
||||
path: ~/cache-libimagequant
|
||||
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||
|
||||
- name: Cache libwebp
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libwebp
|
||||
with:
|
||||
path: ~/cache-libwebp
|
||||
|
||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@ -18,14 +18,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Lint
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
- name: Lint
|
||||
run: uvx --with tox-uv tox -e lint
|
||||
- name: Mypy
|
||||
|
||||
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
@ -26,6 +26,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Drafts your next release notes as pull requests are merged into "main"
|
||||
- uses: release-drafter/release-drafter@v7
|
||||
- uses: release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Check issues"
|
||||
uses: actions/stale@v10
|
||||
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
only-labels: "Awaiting OP Action"
|
||||
|
||||
27
.github/workflows/test-docker.yml
vendored
27
.github/workflows/test-docker.yml
vendored
@ -4,19 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
paths-ignore: &paths-ignore
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
paths-ignore: *paths-ignore
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -39,8 +34,8 @@ jobs:
|
||||
os: ["ubuntu-latest"]
|
||||
docker: [
|
||||
# Run slower jobs first to give them a headstart and reduce waiting time
|
||||
ubuntu-24.04-noble-ppc64le,
|
||||
ubuntu-24.04-noble-s390x,
|
||||
ubuntu-26.04-resolute-ppc64le,
|
||||
ubuntu-26.04-resolute-s390x,
|
||||
# Then run the remainder
|
||||
alpine,
|
||||
amazon-2023-amd64,
|
||||
@ -50,24 +45,26 @@ jobs:
|
||||
debian-13-trixie-x86,
|
||||
debian-13-trixie-amd64,
|
||||
fedora-43-amd64,
|
||||
fedora-44-amd64,
|
||||
gentoo,
|
||||
ubuntu-22.04-jammy-amd64,
|
||||
ubuntu-24.04-noble-amd64,
|
||||
ubuntu-26.04-resolute-amd64,
|
||||
]
|
||||
dockerTag: [main]
|
||||
include:
|
||||
- docker: "ubuntu-24.04-noble-ppc64le"
|
||||
- docker: "ubuntu-26.04-resolute-ppc64le"
|
||||
qemu-arch: "ppc64le"
|
||||
- docker: "ubuntu-24.04-noble-s390x"
|
||||
- docker: "ubuntu-26.04-resolute-s390x"
|
||||
qemu-arch: "s390x"
|
||||
- docker: "ubuntu-24.04-noble-arm64v8"
|
||||
- docker: "ubuntu-26.04-resolute-arm64v8"
|
||||
os: "ubuntu-24.04-arm"
|
||||
dockerTag: main
|
||||
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@ -76,7 +73,7 @@ jobs:
|
||||
|
||||
- name: Set up QEMU
|
||||
if: "matrix.qemu-arch"
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
with:
|
||||
platforms: ${{ matrix.qemu-arch }}
|
||||
|
||||
@ -104,7 +101,7 @@ jobs:
|
||||
.ci/after_success.sh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
flags: GHA_Docker
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
13
.github/workflows/test-mingw.yml
vendored
13
.github/workflows/test-mingw.yml
vendored
@ -4,19 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
paths-ignore: &paths-ignore
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
paths-ignore: *paths-ignore
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -46,7 +41,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@ -87,7 +82,7 @@ jobs:
|
||||
.ci/test.sh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
flags: GHA_Windows
|
||||
|
||||
7
.github/workflows/test-valgrind-memory.yml
vendored
7
.github/workflows/test-valgrind-memory.yml
vendored
@ -8,12 +8,13 @@ on:
|
||||
# branches:
|
||||
# - "**"
|
||||
# paths:
|
||||
# - ".github/workflows/test-valgrind.yml"
|
||||
# - ".github/workflows/test-valgrind-memory.yml"
|
||||
# - "**.c"
|
||||
# - "**.h"
|
||||
# - "depends/docker-test-valgrind-memory.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- ".github/workflows/test-valgrind-memory.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
- "depends/docker-test-valgrind-memory.sh"
|
||||
@ -44,7 +45,7 @@ jobs:
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
9
.github/workflows/test-valgrind.yml
vendored
9
.github/workflows/test-valgrind.yml
vendored
@ -6,15 +6,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths:
|
||||
paths: &paths
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
paths: *paths
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -42,7 +39,7 @@ jobs:
|
||||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
23
.github/workflows/test-windows.yml
vendored
23
.github/workflows/test-windows.yml
vendored
@ -4,19 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
paths-ignore: &paths-ignore
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
paths-ignore: *paths-ignore
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -49,19 +44,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout cached dependencies
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/pillow-depends
|
||||
path: winbuild\depends
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
@ -69,7 +64,7 @@ jobs:
|
||||
|
||||
# sets env: pythonLocation
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
@ -113,7 +108,7 @@ jobs:
|
||||
|
||||
- name: Cache build
|
||||
id: build-cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: winbuild\build
|
||||
key:
|
||||
@ -217,7 +212,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
@ -229,7 +224,7 @@ jobs:
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
flags: GHA_Windows
|
||||
|
||||
23
.github/workflows/test.yml
vendored
23
.github/workflows/test.yml
vendored
@ -4,19 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
paths-ignore: &paths-ignore
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
paths-ignore: *paths-ignore
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -69,12 +64,12 @@ jobs:
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
@ -93,7 +88,7 @@ jobs:
|
||||
|
||||
- name: Cache libavif
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libavif
|
||||
with:
|
||||
path: ~/cache-libavif
|
||||
@ -101,7 +96,7 @@ jobs:
|
||||
|
||||
- name: Cache libimagequant
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libimagequant
|
||||
with:
|
||||
path: ~/cache-libimagequant
|
||||
@ -109,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Cache libwebp
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: cache-libwebp
|
||||
with:
|
||||
path: ~/cache-libwebp
|
||||
@ -162,7 +157,7 @@ jobs:
|
||||
mkdir -p Tests/errors
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
@ -173,7 +168,7 @@ jobs:
|
||||
.ci/after_success.sh
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v6
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
||||
33
.github/workflows/wheels-dependencies.sh
vendored
33
.github/workflows/wheels-dependencies.sh
vendored
@ -89,22 +89,23 @@ fi
|
||||
|
||||
ARCHIVE_SDIR=pillow-depends-main
|
||||
|
||||
# Package versions for fresh source builds.
|
||||
FREETYPE_VERSION=2.14.3
|
||||
HARFBUZZ_VERSION=13.2.1
|
||||
LIBPNG_VERSION=1.6.56
|
||||
JPEGTURBO_VERSION=3.1.4.1
|
||||
OPENJPEG_VERSION=2.5.4
|
||||
XZ_VERSION=5.8.3
|
||||
ZSTD_VERSION=1.5.7
|
||||
TIFF_VERSION=4.7.1
|
||||
LCMS2_VERSION=2.18
|
||||
ZLIB_NG_VERSION=2.3.3
|
||||
LIBWEBP_VERSION=1.6.0
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.2.0
|
||||
LIBAVIF_VERSION=1.4.1
|
||||
VERSIONS_FILE="$PROJECTDIR/.github/dependencies.json"
|
||||
_get_ver() { python3 -c "import json; print(json.load(open('$VERSIONS_FILE'))['$1'])"; }
|
||||
FREETYPE_VERSION=$(_get_ver freetype)
|
||||
HARFBUZZ_VERSION=$(_get_ver harfbuzz)
|
||||
LIBPNG_VERSION=$(_get_ver libpng)
|
||||
JPEGTURBO_VERSION=$(_get_ver jpegturbo)
|
||||
OPENJPEG_VERSION=$(_get_ver openjpeg)
|
||||
XZ_VERSION=$(_get_ver xz)
|
||||
ZSTD_VERSION=$(_get_ver zstd)
|
||||
TIFF_VERSION=$(_get_ver tiff)
|
||||
LCMS2_VERSION=$(_get_ver lcms2)
|
||||
ZLIB_NG_VERSION=$(_get_ver zlib-ng)
|
||||
LIBWEBP_VERSION=$(_get_ver libwebp)
|
||||
BZIP2_VERSION=$(_get_ver bzip2)
|
||||
LIBXCB_VERSION=$(_get_ver libxcb)
|
||||
BROTLI_VERSION=$(_get_ver brotli)
|
||||
LIBAVIF_VERSION=$(_get_ver libavif)
|
||||
|
||||
function build_pkg_config {
|
||||
if [ -e pkg-config-stamp ]; then return; fi
|
||||
|
||||
95
.github/workflows/wheels.yml
vendored
95
.github/workflows/wheels.yml
vendored
@ -10,9 +10,12 @@ on:
|
||||
# │ │ │ │ │
|
||||
- cron: "42 1 * * 0,3"
|
||||
push:
|
||||
paths:
|
||||
paths: &paths
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- ".ci/requirements-sbom.txt"
|
||||
- ".github/dependencies.json"
|
||||
- ".github/generate-sbom.py"
|
||||
- ".github/workflows/wheels*"
|
||||
- "pyproject.toml"
|
||||
- "setup.py"
|
||||
- "wheels/*"
|
||||
@ -21,14 +24,7 @@ on:
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- "pyproject.toml"
|
||||
- "setup.py"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
- "winbuild/fribidi.cmake"
|
||||
paths: *paths
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -107,12 +103,12 @@ jobs:
|
||||
os: macos-15-intel
|
||||
cibw_arch: x86_64_iphonesimulator
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@ -130,7 +126,7 @@ jobs:
|
||||
CIBW_ENABLE: cpython-prerelease pypy
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: dist-${{ matrix.name }}
|
||||
path: ./wheelhouse/*.whl
|
||||
@ -150,18 +146,18 @@ jobs:
|
||||
- cibw_arch: ARM64
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
path: Tests\test-images
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@ -210,13 +206,13 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: dist-windows-${{ matrix.cibw_arch }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
- name: Upload fribidi.dll
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: fribidi-windows-${{ matrix.cibw_arch }}
|
||||
path: winbuild\build\bin\fribidi*
|
||||
@ -225,18 +221,18 @@ jobs:
|
||||
if: github.event_name != 'schedule' || github.event.repository.fork == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- run: make sdist
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: dist-sdist
|
||||
path: dist/*.tar.gz
|
||||
@ -246,7 +242,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Count dists
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
@ -268,17 +264,64 @@ jobs:
|
||||
name: release-anaconda
|
||||
url: https://anaconda.org/channels/scientific-python-nightly-wheels/packages/pillow/overview
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: dist-!(sdist)*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Upload wheels to scientific-python-nightly-wheels
|
||||
uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3
|
||||
uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # 0.6.4
|
||||
with:
|
||||
artifacts_path: dist
|
||||
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
|
||||
|
||||
sbom:
|
||||
runs-on: ubuntu-latest
|
||||
name: Generate SBOM
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Generate CycloneDX SBOM
|
||||
run: python3 .github/generate-sbom.py
|
||||
|
||||
- name: Upload SBOM as workflow artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: sbom
|
||||
path: "pillow-*.cdx.json"
|
||||
|
||||
- name: Validate SBOM
|
||||
run: |
|
||||
python3 -m pip install -r .ci/requirements-sbom.txt
|
||||
check-jsonschema --schemafile "https://raw.githubusercontent.com/CycloneDX/specification/1.7/schema/bom-1.7.schema.json" pillow-*.cdx.json
|
||||
|
||||
sbom-publish:
|
||||
if: |
|
||||
github.event.repository.fork == false
|
||||
&& github.event_name == 'push'
|
||||
&& startsWith(github.ref, 'refs/tags')
|
||||
needs: [count-dists, sbom]
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish SBOM to GitHub release
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: sbom
|
||||
path: .
|
||||
|
||||
- name: Attach SBOM to GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release upload "$GITHUB_REF_NAME" pillow-*.cdx.json
|
||||
|
||||
pypi-publish:
|
||||
if: github.event.repository.fork == false && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
needs: count-dists
|
||||
@ -290,12 +333,12 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
attestations: true
|
||||
|
||||
6
.github/zizmor.yml
vendored
6
.github/zizmor.yml
vendored
@ -1,6 +0,0 @@
|
||||
# https://docs.zizmor.sh/configuration/
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
"*": ref-pin
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -97,3 +97,6 @@ pillow-test-images.zip
|
||||
|
||||
# pyinstaller
|
||||
*.spec
|
||||
|
||||
# Generated SBOM
|
||||
pillow-*.cdx.json
|
||||
|
||||
@ -9,8 +9,10 @@
|
||||
Pillow is the friendly PIL fork by [Jeffrey 'Alex' Clark and
|
||||
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
|
||||
PIL is the Python Imaging Library by Fredrik Lundh and contributors.
|
||||
As of 2019, Pillow development is
|
||||
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
|
||||
Development is supported by:
|
||||
- [Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise) (since 2018)
|
||||
- [Thanks.dev](https://thanks.dev) (since 2023)
|
||||
- [GitHub Sponsors](https://github.com/sponsors/python-pillow) (since 2026)
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
|
||||
@ -19,6 +19,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||
git checkout -t remotes/origin/5.2.x
|
||||
```
|
||||
* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
|
||||
* [ ] If this is a security fix: amend commits to include the CVE identifier in the commit message.
|
||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in release branch e.g. `5.2.x`.
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||
* [ ] Run pre-release check via `make release-test`.
|
||||
@ -38,6 +39,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
* [ ] If this is a security fix: publish the [GitHub Security Advisory or Advisories](https://github.com/python-pillow/Pillow/security/advisories).
|
||||
|
||||
## Embargoed release
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@ -281,6 +282,11 @@ def test_bytesio_object() -> None:
|
||||
),
|
||||
)
|
||||
def test_1(filename: str) -> None:
|
||||
gs_binary = EpsImagePlugin.gs_binary
|
||||
assert isinstance(gs_binary, str)
|
||||
if subprocess.check_output([gs_binary, "--version"]) == b"10.06.0\n":
|
||||
pytest.skip("Fails with Ghostscript 10.06.0")
|
||||
|
||||
with Image.open(filename) as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/eps/1.bmp")
|
||||
|
||||
|
||||
@ -502,8 +502,9 @@ class TestFilePng:
|
||||
im = roundtrip(im)
|
||||
assert im.info["transparency"] == (248, 248, 248)
|
||||
|
||||
im = roundtrip(im, transparency=(0, 1, 2))
|
||||
assert im.info["transparency"] == (0, 1, 2)
|
||||
for transparency in ((0, 1, 2), [0, 1, 2]):
|
||||
im = roundtrip(im, transparency=transparency)
|
||||
assert im.info["transparency"] == (0, 1, 2)
|
||||
|
||||
def test_trns_p(self, tmp_path: Path) -> None:
|
||||
# Check writing a transparency of 0, issue #528
|
||||
@ -518,6 +519,36 @@ class TestFilePng:
|
||||
|
||||
assert_image_equal(im2.convert("RGBA"), im.convert("RGBA"))
|
||||
|
||||
def test_trns_invalid(self, tmp_path: Path) -> None:
|
||||
out = tmp_path / "temp.png"
|
||||
|
||||
for mode in ("1", "L", "I;16"):
|
||||
im = Image.new(mode, (1, 1))
|
||||
with pytest.raises(
|
||||
ValueError, match=f"transparency for {mode} must be an integer"
|
||||
):
|
||||
im.save(out, transparency="invalid")
|
||||
|
||||
im = Image.new("I", (1, 1))
|
||||
with pytest.warns(DeprecationWarning, match="Saving I mode images as PNG"):
|
||||
with pytest.raises(ValueError):
|
||||
im.save(out, transparency="invalid")
|
||||
|
||||
im = Image.new("P", (1, 1))
|
||||
with pytest.raises(
|
||||
ValueError, match="transparency for P must be an integer or bytes"
|
||||
):
|
||||
im.save(out, transparency="invalid")
|
||||
|
||||
im = Image.new("RGB", (1, 1))
|
||||
with pytest.raises(
|
||||
ValueError, match="transparency for RGB must be list or tuple"
|
||||
):
|
||||
im.save(out, transparency="invalid")
|
||||
|
||||
with pytest.raises(ValueError, match="transparency for RGB must have length 3"):
|
||||
im.save(out, transparency=(1, 2))
|
||||
|
||||
def test_trns_null(self) -> None:
|
||||
# Check reading images with null tRNS value, issue #1239
|
||||
test_file = "Tests/images/tRNS_null_1x1.png"
|
||||
|
||||
@ -871,7 +871,7 @@ class TestImage:
|
||||
def test_exif_webp(self, tmp_path: Path) -> None:
|
||||
with Image.open("Tests/images/hopper.webp") as im:
|
||||
exif = im.getexif()
|
||||
assert exif == {}
|
||||
assert dict(exif) == {}
|
||||
|
||||
out = tmp_path / "temp.webp"
|
||||
exif[258] = 8
|
||||
@ -893,7 +893,7 @@ class TestImage:
|
||||
def test_exif_png(self, tmp_path: Path) -> None:
|
||||
with Image.open("Tests/images/exif.png") as im:
|
||||
exif = im.getexif()
|
||||
assert exif == {274: 1}
|
||||
assert dict(exif) == {274: 1}
|
||||
|
||||
out = tmp_path / "temp.png"
|
||||
exif[258] = 8
|
||||
|
||||
@ -627,3 +627,37 @@ class TestCoreResampleBox:
|
||||
0.4,
|
||||
f">>> {size} {box} {flt}",
|
||||
)
|
||||
|
||||
|
||||
class TestCoreResample16bpc:
|
||||
# Lanczos weighting during downsampling can push accumulated float sums
|
||||
@pytest.mark.parametrize(
|
||||
"offset",
|
||||
(
|
||||
# below 0. These must be clamped to 0, not corrupted byte-by-byte.
|
||||
0, # Left half = 65535, right half = 0
|
||||
# above 65535. These must be clamped to 65535, not corrupted byte-by-byte.
|
||||
50, # # Left half = 0, right half = 65535
|
||||
),
|
||||
)
|
||||
def test_resampling_clamp_overflow(self, offset: int) -> None:
|
||||
ims = {}
|
||||
width, height = 100, 10
|
||||
for mode in ("I;16", "F"):
|
||||
im = Image.new(mode, (width, height))
|
||||
im.paste(65535, (offset, 0, offset + width // 2, height))
|
||||
|
||||
# 5x downsampling with Lanczos
|
||||
# creates ~8.7% overshoot or undershoot at the step edge
|
||||
ims[mode] = im.resize((20, height), Image.Resampling.LANCZOS)
|
||||
|
||||
for y in range(height):
|
||||
for x in range(20):
|
||||
v = ims["F"].getpixel((x, y))
|
||||
assert isinstance(v, float)
|
||||
expected = max(0, min(65535, round(v)))
|
||||
|
||||
value = ims["I;16"].getpixel((x, y))
|
||||
assert (
|
||||
value == expected
|
||||
), f"Pixel ({x}, {y}): expected {expected}, got {value}"
|
||||
|
||||
@ -35,7 +35,7 @@ class TestImageGrab:
|
||||
ImageGrab.grab()
|
||||
|
||||
ImageGrab.grab(xdisplay="")
|
||||
except OSError as e:
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
pytest.skip(str(e))
|
||||
|
||||
@pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB")
|
||||
|
||||
@ -256,6 +256,13 @@ def test_expand_palette(border: int | tuple[int, int, int, int]) -> None:
|
||||
assert_image_equal(im_cropped, im)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("border", ((1,), (1, 2, 3), (1, 2, 3, 4, 5)))
|
||||
def test_expand_invalid_border(border: tuple[int, ...]) -> None:
|
||||
im = Image.new("1", (1, 1))
|
||||
with pytest.raises(ValueError):
|
||||
ImageOps.expand(im, border)
|
||||
|
||||
|
||||
def test_colorize_2color() -> None:
|
||||
# Test the colorizing function with 2-color functionality
|
||||
|
||||
|
||||
@ -8,3 +8,4 @@ Handbook
|
||||
tutorial
|
||||
concepts
|
||||
appendices
|
||||
security
|
||||
|
||||
259
docs/handbook/security.rst
Normal file
259
docs/handbook/security.rst
Normal file
@ -0,0 +1,259 @@
|
||||
Security
|
||||
========
|
||||
|
||||
Pillow's primary attack surface is **parsing untrusted image data**. This page
|
||||
documents the threat model for developers integrating Pillow into applications
|
||||
that handle images from untrusted sources, along with recommended mitigations.
|
||||
|
||||
To report a vulnerability see :ref:`security-reporting`.
|
||||
|
||||
.. _security-threat-model:
|
||||
|
||||
Threat model (STRIDE)
|
||||
---------------------
|
||||
|
||||
The analysis below follows the `STRIDE
|
||||
<https://en.wikipedia.org/wiki/STRIDE_model>`_ framework and covers the
|
||||
boundary between untrusted image input and the Pillow API.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
┌──────────────────────────────────────────┐
|
||||
Untrusted zone │ Pillow API │
|
||||
───────────── │ │
|
||||
Image files ────►│ Image.open() ──► Format plugins │
|
||||
Byte streams │ (40+ parsers) (Python + C FFI) │
|
||||
User metadata │ │
|
||||
│ ImageMath.unsafe_eval(expr) ───────────┼──► Python eval()
|
||||
│ ImageShow.show(image) ─────────────────┼──► os.system / subprocess
|
||||
│ EpsImagePlugin.open(eps) ──────────────┼──► Ghostscript (gs)
|
||||
└──────────────┬───────────────────────────┘
|
||||
│ C extensions:
|
||||
│ _imaging · _imagingft · _imagingcms
|
||||
│ _webp · _avif · _imagingtk
|
||||
│ _imagingmath · _imagingmorph
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ C libraries (bundled or system) │
|
||||
│ libjpeg · libpng · libtiff · libwebp │
|
||||
│ openjpeg · freetype · littlecms2 │
|
||||
└──────────────────────────────────────────┘
|
||||
|
||||
Spoofing
|
||||
^^^^^^^^
|
||||
|
||||
**S-1 — Format sniffing bypass**
|
||||
|
||||
``Image.open()`` detects format by magic bytes, not file extension or MIME
|
||||
type. An attacker can name a file ``safe.png`` while its content is TIFF, JPEG
|
||||
2000, or EPS, causing a different — potentially more dangerous — parser to run.
|
||||
|
||||
*Mitigations:* validate MIME type and magic bytes independently before calling
|
||||
``Image.open()``; pass the ``formats`` argument with an allowlist of accepted
|
||||
formats.
|
||||
|
||||
**S-2 — Plugin registry spoofing**
|
||||
|
||||
Pillow's format registry is a global mutable dictionary. A malicious package
|
||||
installed in the same environment could register a replacement parser for a
|
||||
well-known format.
|
||||
|
||||
*Mitigations:* use isolated virtual environments with pinned, hash-verified
|
||||
dependencies; audit ``Image.registered_extensions()`` at startup.
|
||||
|
||||
Tampering
|
||||
^^^^^^^^^
|
||||
|
||||
**T-1 — Malicious metadata propagation**
|
||||
|
||||
Pillow preserves EXIF, XMP, IPTC, ICC profiles, and comments when
|
||||
round-tripping images. Applications that store or render metadata without
|
||||
sanitisation are vulnerable to second-order injection (SQLi, XSS, command
|
||||
injection).
|
||||
|
||||
*Mitigations:* treat all values from ``image.info``, ``image._getexif()``,
|
||||
``image.getexif()``, and ``image.text`` as untrusted; sanitise before storing
|
||||
or rendering; strip metadata when it is not required.
|
||||
|
||||
**T-2 — Covert data channel (steganography)**
|
||||
|
||||
Pillow does not remove hidden data (JPEG comments, PNG text chunks) when
|
||||
re-saving. An attacker can embed data that survives the
|
||||
encode-decode cycle invisibly.
|
||||
|
||||
*Mitigations:* to guarantee a clean output when saving, create a new image instance via
|
||||
``image.copy()`` and delete the ``image.info`` contents.
|
||||
|
||||
**T-3 — Supply chain tampering**
|
||||
|
||||
Pre-compiled wheels bundle libjpeg-turbo, libpng, libtiff, libwebp, openjpeg,
|
||||
freetype, littlecms2, and other libraries. A compromised PyPI release or build pipeline
|
||||
could ship malicious binaries.
|
||||
|
||||
*Mitigations:* pin with hash verification
|
||||
(``python3 -m pip install --require-hashes``); monitor `Pillow security advisories
|
||||
<https://github.com/python-pillow/Pillow/security/advisories>`_; use
|
||||
Dependabot or OSV-Scanner for bundled C library CVEs.
|
||||
|
||||
Repudiation
|
||||
^^^^^^^^^^^
|
||||
|
||||
**R-1 — No structured audit trail**
|
||||
|
||||
Without application-level logging there is no record of which images were
|
||||
opened, what formats were detected, or what operations were performed, making
|
||||
forensic investigation harder after an incident.
|
||||
|
||||
*Mitigations:* log the filename/hash, detected format, and dimensions of every
|
||||
image processed; log and alert on ``Image.DecompressionBombWarning``,
|
||||
``Image.DecompressionBombError``, and ``PIL.UnidentifiedImageError``.
|
||||
|
||||
Information disclosure
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**I-1 — Metadata in saved images**
|
||||
|
||||
GPS coordinates, author names, software version strings, and ICC profiles can
|
||||
be inadvertently included in output images served publicly.
|
||||
|
||||
*Mitigations:* explicitly strip EXIF and XMP on save (set ``exif=b""``,
|
||||
``icc_profile=None``, omit ``pnginfo``); verify output with ``exiftool`` in CI.
|
||||
|
||||
**I-2 — Temporary file exposure**
|
||||
|
||||
Several code paths write pixel data to temporary files via
|
||||
``tempfile.mkstemp()``. Exception paths can leave these files behind on shared
|
||||
filesystems.
|
||||
|
||||
*Mitigations:* files are created with mode ``0o600``; mount ``/tmp`` as a
|
||||
per-container ``tmpfs``; ensure ``try/finally`` cleanup is in place.
|
||||
|
||||
Denial of service
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
**D-1 — Decompression bomb**
|
||||
|
||||
A small compressed image can expand to gigabytes in memory.
|
||||
:py:data:`PIL.Image.MAX_IMAGE_PIXELS` raises
|
||||
``Image.DecompressionBombError`` at 2× the limit and
|
||||
``Image.DecompressionBombWarning`` at 1×. PNG text chunks are
|
||||
separately capped by ``PngImagePlugin.MAX_TEXT_CHUNK`` and
|
||||
``MAX_TEXT_MEMORY``. Check the values in your installed Pillow version at
|
||||
runtime or in the reference/source for the current defaults.
|
||||
|
||||
*Mitigations:* **never** set ``Image.MAX_IMAGE_PIXELS = None`` in production;
|
||||
treat ``Image.DecompressionBombWarning`` as an error; set OS/container memory limits
|
||||
per worker.
|
||||
|
||||
**D-2 — CPU exhaustion**
|
||||
|
||||
Large-but-legal images (within ``MAX_IMAGE_PIXELS``) can still saturate CPU
|
||||
through high-quality resampling, convolution filters, or complex draw
|
||||
operations.
|
||||
|
||||
*Mitigations:* apply per-request CPU time limits; set a practical dimension
|
||||
ceiling below ``MAX_IMAGE_PIXELS``; rate-limit processing requests.
|
||||
|
||||
**D-3 — Algorithmic complexity in parsers**
|
||||
|
||||
Formats such as TIFF (nested IFD chains), animated GIF/WebP (many frames), and
|
||||
PNG (many text chunks) can exhaust CPU or memory before pixel data is decoded.
|
||||
|
||||
*Mitigations:* restrict accepted formats to the minimum required; enforce a
|
||||
file-size limit before passing data to Pillow; use per-request timeouts.
|
||||
|
||||
Elevation of privilege
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
**E-1 — C extension memory corruption (RCE)**
|
||||
|
||||
Pillow's ~87 C source files and its bundled C libraries process
|
||||
attacker-controlled bytes. Historical CVEs include buffer overflows, integer
|
||||
overflows, and use-after-free vulnerabilities that allow arbitrary code
|
||||
execution.
|
||||
|
||||
*Mitigations:* keep Pillow and all C libraries up to date; compile with
|
||||
hardening flags (ASLR, stack canaries, PIE, ``_FORTIFY_SOURCE=2``); run image
|
||||
processing in a sandboxed subprocess (seccomp-bpf, AppArmor, or a restricted
|
||||
container).
|
||||
|
||||
**E-2 — Ghostscript exploitation via EPS (RCE)**
|
||||
|
||||
Opening an EPS file invokes the system Ghostscript binary (``gs``) via
|
||||
``subprocess``. Ghostscript has a long history of sandbox-escape CVEs
|
||||
permitting arbitrary code execution from malicious PostScript.
|
||||
|
||||
*Mitigations:* **block EPS files** at the application input layer before
|
||||
passing files to Pillow; if EPS must be supported, run Ghostscript in a fully
|
||||
isolated sandbox with no network and no sensitive mounts. Pillow does not
|
||||
provide a stable public API for unregistering individual format plugins, so do
|
||||
not rely on mutating internal registries such as ``Image.OPEN`` as a security
|
||||
control.
|
||||
|
||||
|
||||
**E-3 — ImageMath.unsafe_eval() code injection**
|
||||
|
||||
:py:meth:`~PIL.ImageMath.unsafe_eval` calls Python's built-in ``eval()`` with
|
||||
only a minimal ``__builtins__`` restriction, which can be bypassed via
|
||||
introspection. Any user-controlled string passed to this function results in
|
||||
arbitrary code execution.
|
||||
|
||||
*Mitigations:* **never** pass user-controlled strings to
|
||||
``ImageMath.unsafe_eval()``; use :py:meth:`~PIL.ImageMath.lambda_eval` instead,
|
||||
which accepts a Python callable and never calls ``eval``.
|
||||
|
||||
**E-4 — Font path traversal via ImageFont**
|
||||
|
||||
``ImageFont.truetype(font, size)`` passes the filename to the FreeType C
|
||||
library. If font paths are constructed from user input without
|
||||
canonicalisation, an attacker may supply a path like
|
||||
``../../../../etc/passwd``.
|
||||
|
||||
*Mitigations:* never construct font paths from user input; if font selection
|
||||
must be user-driven, resolve names against an explicit allowlist of
|
||||
pre-validated absolute paths.
|
||||
|
||||
.. _security-recommendations:
|
||||
|
||||
Recommendations
|
||||
---------------
|
||||
|
||||
The following mitigations are listed in priority order.
|
||||
|
||||
1. **Sandbox image processing** — run Pillow workers in a seccomp/AppArmor
|
||||
restricted subprocess, isolated from the main application process.
|
||||
2. **Block or sandbox EPS** — reject EPS at the application boundary, or run
|
||||
Ghostscript in an isolated container.
|
||||
3. **Never use** ``ImageMath.unsafe_eval()`` **with user input** — migrate all
|
||||
callers to :py:meth:`~PIL.ImageMath.lambda_eval`.
|
||||
4. **Keep all dependencies current** — Pillow and its C library dependencies
|
||||
(including libjpeg, libpng, libtiff, libwebp, openjpeg, freetype,
|
||||
littlecms2, Ghostscript, and others). Subscribe to `Pillow security
|
||||
advisories <https://github.com/python-pillow/Pillow/security/advisories>`_.
|
||||
5. **Enforce** ``MAX_IMAGE_PIXELS`` — never set it to ``None``; treat
|
||||
``Image.DecompressionBombWarning`` as an error.
|
||||
6. **Allowlist image formats** — restrict accepted formats when opening
|
||||
images, for example with ``Image.open(..., formats=...)``, and isolate
|
||||
installs/environments if you need to minimise supported formats.
|
||||
7. **Strip metadata on output** — never pass through EXIF/XMP/ICC from user
|
||||
uploads to publicly served images.
|
||||
8. **Sanitise all metadata** returned by Pillow before using it downstream.
|
||||
9. **Pin dependencies with hash verification** — use
|
||||
``pip install --require-hashes`` and lockfiles.
|
||||
10. **Log and alert** on ``Image.DecompressionBombWarning``,
|
||||
``Image.DecompressionBombError``, ``PIL.UnidentifiedImageError``,
|
||||
and all exceptions from ``Image.open()``.
|
||||
|
||||
.. _security-reporting:
|
||||
|
||||
Reporting a vulnerability
|
||||
-------------------------
|
||||
|
||||
To report sensitive vulnerability information, report it `privately on GitHub
|
||||
<https://github.com/python-pillow/Pillow/security/advisories/new>`_.
|
||||
|
||||
If you cannot use GitHub, use the `Tidelift security contact
|
||||
<https://tidelift.com/docs/security>`_. Tidelift will coordinate the fix and
|
||||
disclosure.
|
||||
|
||||
**Do not report sensitive vulnerability information in public.**
|
||||
@ -51,7 +51,7 @@ Many of Pillow's features require external libraries:
|
||||
* **littlecms** provides color management
|
||||
|
||||
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
|
||||
above uses liblcms2. Tested with **1.19** and **2.7-2.18**.
|
||||
above uses liblcms2. Tested with **1.19** and **2.7-2.19**.
|
||||
|
||||
* **libwebp** provides the WebP format.
|
||||
|
||||
@ -116,7 +116,7 @@ Many of Pillow's features require external libraries:
|
||||
|
||||
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
||||
|
||||
Prerequisites for **Ubuntu 16.04 LTS - 24.04 LTS** are installed with::
|
||||
Prerequisites for **Ubuntu 16.04 LTS - 26.04 LTS** are installed with::
|
||||
|
||||
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
||||
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
|
||||
|
||||
@ -31,6 +31,8 @@ These platforms are built and tested for every change.
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 43 | 3.14 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 44 | 3.14 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.13 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14, | arm64 |
|
||||
@ -42,9 +44,9 @@ These platforms are built and tested for every change.
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||
| | 3.14, 3.15, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | arm64v8, ppc64le, |
|
||||
| | | s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 26.04 LTS (Resolute)| 3.14 | x86-64, arm64v8, |
|
||||
| | | ppc64le, s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.10 | x86 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
||||
@ -27,6 +27,10 @@ The ImageColor module supports the following string formats:
|
||||
as three percentages (0% to 100%). For example, ``rgb(255,0,0)`` and
|
||||
``rgb(100%,0%,0%)`` both specify pure red.
|
||||
|
||||
* RGBA functions, given as ``rgba(red, green, blue, alpha)`` where the color
|
||||
values and the alpha value are integers in the range 0 to 255. For example,
|
||||
``rgba(255,0,0,128)`` specifies pure red with 50% opacity.
|
||||
|
||||
* Hue-Saturation-Lightness (HSL) functions, given as ``hsl(hue, saturation%,
|
||||
lightness%)`` where hue is the color given as an angle between 0 and 360
|
||||
(red=0, green=120, blue=240), saturation is a value between 0% and 100%
|
||||
|
||||
@ -13,28 +13,28 @@ introduced in Pillow 10.3.0.
|
||||
|
||||
The data being read is now limited to only the necessary amount.
|
||||
|
||||
Fix OOB write with invalid tile extents
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
:cve:`2026-42311`: Fix OOB write with invalid tile extents
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Pillow 12.1.1 addressed :cve:`2026-25990` by improving checks for tile extents to
|
||||
prevent an OOB write from specially crafted PSD images in Pillow >= 10.3.0. However,
|
||||
these checks did not consider integer overflow. This has been corrected.
|
||||
|
||||
Prevent PDF parsing trailer infinite loop
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
:cve:`2026-42310`: Prevent PDF parsing trailer infinite loop
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When parsing a PDF, if a trailer refers to itself, or a more complex cyclic loop
|
||||
exists, then an infinite loop occurs. Pillow now keeps a record of which trailers it
|
||||
has already processed. PdfParser was added in Pillow 4.2.0.
|
||||
|
||||
Integer overflow when processing fonts
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
:cve:`2026-42308`: Integer overflow when processing fonts
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If a font advances for each glyph by an exceeding large amount, when Pillow keeps track
|
||||
of the current position, it may lead to an integer overflow. This has been fixed.
|
||||
|
||||
Heap buffer overflow with nested list coordinates
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
:cve:`2026-42309`: Heap buffer overflow with nested list coordinates
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Passing nested lists as coordinates to APIs that accept coordinates such as
|
||||
``ImagePath.Path``, :py:meth:`~PIL.ImageDraw.ImageDraw.polygon`
|
||||
|
||||
@ -2639,11 +2639,8 @@ class Image:
|
||||
if is_path(fp):
|
||||
filename = os.fspath(fp)
|
||||
open_fp = True
|
||||
elif fp == sys.stdout:
|
||||
try:
|
||||
fp = sys.stdout.buffer
|
||||
except AttributeError:
|
||||
pass
|
||||
elif fp == sys.stdout and isinstance(sys.stdout, io.TextIOWrapper):
|
||||
fp = sys.stdout.buffer
|
||||
if not filename and hasattr(fp, "name") and is_path(fp.name):
|
||||
# only set the name for metadata purposes
|
||||
filename = os.fspath(fp.name)
|
||||
|
||||
@ -36,6 +36,9 @@ def _border(border: int | tuple[int, ...]) -> tuple[int, int, int, int]:
|
||||
left, top = right, bottom = border
|
||||
elif len(border) == 4:
|
||||
left, top, right, bottom = border
|
||||
else:
|
||||
msg = "border must be an integer, or a tuple of two or four elements"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
left = top = right = bottom = border
|
||||
return left, top, right, bottom
|
||||
|
||||
@ -383,7 +383,7 @@ class PdfParser:
|
||||
msg = "specify buf or f or filename, but not both buf and f"
|
||||
raise RuntimeError(msg)
|
||||
self.filename = filename
|
||||
self.buf: bytes | bytearray | mmap.mmap | None = buf
|
||||
self.buf: bytes | bytearray | memoryview | mmap.mmap | None = buf
|
||||
self.f = f
|
||||
self.start_offset = start_offset
|
||||
self.should_close_buf = False
|
||||
@ -435,7 +435,9 @@ class PdfParser:
|
||||
self.seek_end()
|
||||
|
||||
def close_buf(self) -> None:
|
||||
if isinstance(self.buf, mmap.mmap):
|
||||
if isinstance(self.buf, memoryview):
|
||||
self.buf.release()
|
||||
elif isinstance(self.buf, mmap.mmap):
|
||||
self.buf.close()
|
||||
self.buf = None
|
||||
|
||||
|
||||
@ -1443,35 +1443,47 @@ def _save(
|
||||
palette_bytes += b"\0"
|
||||
chunk(fp, b"PLTE", palette_bytes)
|
||||
|
||||
transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None))
|
||||
transparency = im.encoderinfo.get("transparency", im.info.get("transparency"))
|
||||
|
||||
if transparency or transparency == 0:
|
||||
if transparency is not None:
|
||||
if im.mode == "P":
|
||||
# limit to actual palette size
|
||||
alpha_bytes = colors
|
||||
if isinstance(transparency, bytes):
|
||||
chunk(fp, b"tRNS", transparency[:alpha_bytes])
|
||||
else:
|
||||
elif isinstance(transparency, int):
|
||||
transparency = max(0, min(255, transparency))
|
||||
alpha = b"\xff" * transparency + b"\0"
|
||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||
else:
|
||||
msg = "transparency for P must be an integer or bytes"
|
||||
raise ValueError(msg)
|
||||
elif im.mode in ("1", "L", "I", "I;16"):
|
||||
transparency = max(0, min(65535, transparency))
|
||||
chunk(fp, b"tRNS", o16(transparency))
|
||||
if isinstance(transparency, int):
|
||||
transparency = max(0, min(65535, transparency))
|
||||
chunk(fp, b"tRNS", o16(transparency))
|
||||
else:
|
||||
msg = f"transparency for {im.mode} must be an integer"
|
||||
raise ValueError(msg)
|
||||
elif im.mode == "RGB":
|
||||
red, green, blue = transparency
|
||||
chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue))
|
||||
else:
|
||||
if "transparency" in im.encoderinfo:
|
||||
# don't bother with transparency if it's an RGBA
|
||||
# and it's in the info dict. It's probably just stale.
|
||||
msg = "cannot use transparency for this mode"
|
||||
raise OSError(msg)
|
||||
else:
|
||||
if im.mode == "P" and im.im.getpalettemode() == "RGBA":
|
||||
alpha = im.im.getpalette("RGBA", "A")
|
||||
alpha_bytes = colors
|
||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||
if not isinstance(transparency, (list, tuple)):
|
||||
msg = "transparency for RGB must be list or tuple"
|
||||
raise ValueError(msg)
|
||||
elif len(transparency) != 3:
|
||||
msg = "transparency for RGB must have length 3"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
red, green, blue = transparency
|
||||
chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue))
|
||||
elif im.encoderinfo.get("transparency") is not None:
|
||||
# don't bother with transparency if it's an RGBA
|
||||
# and it's in the info dict. It's probably just stale.
|
||||
msg = "cannot use transparency for this mode"
|
||||
raise OSError(msg)
|
||||
elif im.mode == "P" and im.im.getpalettemode() == "RGBA":
|
||||
alpha = im.im.getpalette("RGBA", "A")
|
||||
alpha_bytes = colors
|
||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||
|
||||
if dpi := im.encoderinfo.get("dpi"):
|
||||
chunk(
|
||||
|
||||
@ -33,8 +33,10 @@ _tkinit(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg);
|
||||
if (interp == NULL && PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This will bomb if interp is invalid... */
|
||||
TkImaging_Init(interp);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
|
||||
@ -37,8 +37,6 @@
|
||||
#define MAX(a, b) (a) > (b) ? (a) : (b)
|
||||
#define MIN(a, b) (a) < (b) ? (a) : (b)
|
||||
|
||||
#define CLIP16(v) ((v) <= 0 ? 0 : (v) >= 65535 ? 65535 : (v))
|
||||
|
||||
/* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
|
||||
#define L(rgb) ((INT32)(rgb)[0] * 299 + (INT32)(rgb)[1] * 587 + (INT32)(rgb)[2] * 114)
|
||||
#define L24(rgb) ((rgb)[0] * 19595 + (rgb)[1] * 38470 + (rgb)[2] * 7471 + 0x8000)
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
|
||||
#define CLIP8(v) ((v) <= 0 ? 0 : (v) < 256 ? (v) : 255)
|
||||
|
||||
#define CLIP16(v) ((v) <= 0 ? 0 : (v) < 65536 ? (v) : 65535)
|
||||
|
||||
/* This is to work around a bug in GCC prior 4.9 in 64 bit mode.
|
||||
GCC generates code with partial dependency which is 3 times slower.
|
||||
See: https://stackoverflow.com/a/26588074/253146 */
|
||||
|
||||
@ -492,9 +492,9 @@ ImagingResampleHorizontal_16bpc(
|
||||
<< 8)) *
|
||||
k[x];
|
||||
}
|
||||
ss_int = ROUND_UP(ss);
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = CLIP8(ss_int % 256);
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = CLIP8(ss_int >> 8);
|
||||
ss_int = CLIP16(ROUND_UP(ss));
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = ss_int & 0xFF;
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = ss_int >> 8;
|
||||
}
|
||||
}
|
||||
ImagingSectionLeave(&cookie);
|
||||
@ -531,9 +531,9 @@ ImagingResampleVertical_16bpc(
|
||||
(imIn->image8[y + ymin][xx * 2 + (bigendian ? 0 : 1)] << 8)) *
|
||||
k[y];
|
||||
}
|
||||
ss_int = ROUND_UP(ss);
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = CLIP8(ss_int % 256);
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = CLIP8(ss_int >> 8);
|
||||
ss_int = CLIP16(ROUND_UP(ss));
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 1 : 0)] = ss_int & 0xFF;
|
||||
imOut->image8[yy][xx * 2 + (bigendian ? 0 : 1)] = ss_int >> 8;
|
||||
}
|
||||
}
|
||||
ImagingSectionLeave(&cookie);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
@ -8,6 +9,7 @@ import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
@ -112,30 +114,17 @@ ARCHITECTURES = {
|
||||
"ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"},
|
||||
}
|
||||
|
||||
V = {
|
||||
"BROTLI": "1.2.0",
|
||||
"FREETYPE": "2.14.3",
|
||||
"FRIBIDI": "1.0.16",
|
||||
"HARFBUZZ": "13.2.1",
|
||||
"JPEGTURBO": "3.1.4.1",
|
||||
"LCMS2": "2.18",
|
||||
"LIBAVIF": "1.4.1",
|
||||
"LIBIMAGEQUANT": "4.4.1",
|
||||
"LIBPNG": "1.6.56",
|
||||
"LIBWEBP": "1.6.0",
|
||||
"OPENJPEG": "2.5.4",
|
||||
"TIFF": "4.7.1",
|
||||
"XZ": "5.8.3",
|
||||
"ZLIBNG": "2.3.3",
|
||||
}
|
||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||
V = json.loads(
|
||||
(Path(__file__).parents[1] / ".github" / "dependencies.json").read_text()
|
||||
)
|
||||
V["libpng-xy"] = "".join(V["libpng"].split(".")[:2])
|
||||
|
||||
|
||||
# dependencies, listed in order of compilation
|
||||
DEPS: dict[str, dict[str, Any]] = {
|
||||
"libjpeg": {
|
||||
"url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['JPEGTURBO']}/libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
|
||||
"filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
|
||||
"url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['jpegturbo']}/libjpeg-turbo-{V['jpegturbo']}.tar.gz",
|
||||
"filename": f"libjpeg-turbo-{V['jpegturbo']}.tar.gz",
|
||||
"license": ["README.ijg", "LICENSE.md"],
|
||||
"license_pattern": (
|
||||
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
|
||||
@ -162,8 +151,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"bins": ["djpeg.exe"],
|
||||
},
|
||||
"zlib": {
|
||||
"url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",
|
||||
"filename": f"zlib-ng-{V['ZLIBNG']}.tar.gz",
|
||||
"url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['zlib-ng']}.tar.gz",
|
||||
"filename": f"zlib-ng-{V['zlib-ng']}.tar.gz",
|
||||
"license": "LICENSE.md",
|
||||
"patch": {
|
||||
r"CMakeLists.txt": {
|
||||
@ -179,8 +168,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"libs": [r"zlib.lib"],
|
||||
},
|
||||
"xz": {
|
||||
"url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME",
|
||||
"filename": f"xz-{V['XZ']}.tar.gz",
|
||||
"url": f"https://github.com/tukaani-project/xz/releases/download/v{V['xz']}/FILENAME",
|
||||
"filename": f"xz-{V['xz']}.tar.gz",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||
@ -192,7 +181,7 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
"libwebp": {
|
||||
"url": "http://downloads.webmproject.org/releases/webp/FILENAME",
|
||||
"filename": f"libwebp-{V['LIBWEBP']}.tar.gz",
|
||||
"filename": f"libwebp-{V['libwebp']}.tar.gz",
|
||||
"license": "COPYING",
|
||||
"patch": {
|
||||
r"src\enc\picture_csp_enc.c": {
|
||||
@ -213,7 +202,7 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
"libtiff": {
|
||||
"url": "https://download.osgeo.org/libtiff/FILENAME",
|
||||
"filename": f"tiff-{V['TIFF']}.tar.gz",
|
||||
"filename": f"tiff-{V['tiff']}.tar.gz",
|
||||
"license": "LICENSE.md",
|
||||
"patch": {
|
||||
r"libtiff\tif_lzma.c": {
|
||||
@ -237,22 +226,22 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"libs": [r"libtiff\*.lib"],
|
||||
},
|
||||
"libpng": {
|
||||
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
|
||||
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['libpng-xy']}/{V['libpng']}/"
|
||||
f"FILENAME/download",
|
||||
"filename": f"libpng-{V['LIBPNG']}.tar.gz",
|
||||
"filename": f"libpng-{V['libpng']}.tar.gz",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
|
||||
cmd_copy(
|
||||
f"libpng{V['LIBPNG_XY']}_static.lib", f"libpng{V['LIBPNG_XY']}.lib"
|
||||
f"libpng{V['libpng-xy']}_static.lib", f"libpng{V['libpng-xy']}.lib"
|
||||
),
|
||||
],
|
||||
"headers": [r"png*.h"],
|
||||
"libs": [f"libpng{V['LIBPNG_XY']}.lib"],
|
||||
"libs": [f"libpng{V['libpng-xy']}.lib"],
|
||||
},
|
||||
"brotli": {
|
||||
"url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz",
|
||||
"filename": f"brotli-{V['BROTLI']}.tar.gz",
|
||||
"url": f"https://github.com/google/brotli/archive/refs/tags/v{V['brotli']}.tar.gz",
|
||||
"filename": f"brotli-{V['brotli']}.tar.gz",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
*cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||
@ -262,7 +251,7 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
"freetype": {
|
||||
"url": "https://download.savannah.gnu.org/releases/freetype/FILENAME",
|
||||
"filename": f"freetype-{V['FREETYPE']}.tar.gz",
|
||||
"filename": f"freetype-{V['freetype']}.tar.gz",
|
||||
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
|
||||
"patch": {
|
||||
r"builds\windows\vc2010\freetype.vcxproj": {
|
||||
@ -275,7 +264,7 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"<UserDefines></UserDefines>": "<UserDefines>FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI</UserDefines>", # noqa: E501
|
||||
"<UserIncludeDirectories></UserIncludeDirectories>": r"<UserIncludeDirectories>{dir_harfbuzz}\src;{inc_dir}</UserIncludeDirectories>", # noqa: E501
|
||||
"<UserLibraryDirectories></UserLibraryDirectories>": "<UserLibraryDirectories>{lib_dir}</UserLibraryDirectories>", # noqa: E501
|
||||
"<UserDependencies></UserDependencies>": f"<UserDependencies>zlib.lib;libpng{V['LIBPNG_XY']}.lib;brotlicommon.lib;brotlidec.lib</UserDependencies>", # noqa: E501
|
||||
"<UserDependencies></UserDependencies>": f"<UserDependencies>zlib.lib;libpng{V['libpng-xy']}.lib;brotlicommon.lib;brotlidec.lib</UserDependencies>", # noqa: E501
|
||||
},
|
||||
r"src/autofit/afshaper.c": {
|
||||
# link against harfbuzz.lib
|
||||
@ -295,8 +284,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
|
||||
},
|
||||
"lcms2": {
|
||||
"url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download",
|
||||
"filename": f"lcms2-{V['LCMS2']}.tar.gz",
|
||||
"url": f"{SF_PROJECTS}/lcms/files/lcms/{V['lcms2']}/FILENAME/download",
|
||||
"filename": f"lcms2-{V['lcms2']}.tar.gz",
|
||||
"license": "LICENSE",
|
||||
"patch": {
|
||||
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
||||
@ -320,21 +309,21 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"libs": [r"Lib\MS\*.lib"],
|
||||
},
|
||||
"openjpeg": {
|
||||
"url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz",
|
||||
"filename": f"openjpeg-{V['OPENJPEG']}.tar.gz",
|
||||
"url": f"https://github.com/uclouvain/openjpeg/archive/v{V['openjpeg']}.tar.gz",
|
||||
"filename": f"openjpeg-{V['openjpeg']}.tar.gz",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
"openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF"
|
||||
),
|
||||
cmd_mkdir(rf"{{inc_dir}}\openjpeg-{V['OPENJPEG']}"),
|
||||
cmd_copy(r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{V['OPENJPEG']}"),
|
||||
cmd_mkdir(rf"{{inc_dir}}\openjpeg-{V['openjpeg']}"),
|
||||
cmd_copy(r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{V['openjpeg']}"),
|
||||
],
|
||||
"libs": [r"bin\*.lib"],
|
||||
},
|
||||
"libimagequant": {
|
||||
"url": "https://github.com/ImageOptim/libimagequant/archive/{V['LIBIMAGEQUANT']}.tar.gz",
|
||||
"filename": f"libimagequant-{V['LIBIMAGEQUANT']}.tar.gz",
|
||||
"url": "https://github.com/ImageOptim/libimagequant/archive/{V['libimagequant']}.tar.gz",
|
||||
"filename": f"libimagequant-{V['libimagequant']}.tar.gz",
|
||||
"license": "COPYRIGHT",
|
||||
"build": [
|
||||
cmd_cd("imagequant-sys"),
|
||||
@ -344,8 +333,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"libs": [r"..\target\release\imagequant_sys.lib"],
|
||||
},
|
||||
"harfbuzz": {
|
||||
"url": f"https://github.com/harfbuzz/harfbuzz/releases/download/{V['HARFBUZZ']}/FILENAME",
|
||||
"filename": f"harfbuzz-{V['HARFBUZZ']}.tar.xz",
|
||||
"url": f"https://github.com/harfbuzz/harfbuzz/releases/download/{V['harfbuzz']}/FILENAME",
|
||||
"filename": f"harfbuzz-{V['harfbuzz']}.tar.xz",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
@ -358,11 +347,11 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"libs": [r"*.lib"],
|
||||
},
|
||||
"fribidi": {
|
||||
"url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip",
|
||||
"filename": f"fribidi-{V['FRIBIDI']}.zip",
|
||||
"url": f"https://github.com/fribidi/fribidi/archive/v{V['fribidi']}.zip",
|
||||
"filename": f"fribidi-{V['fribidi']}.zip",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"),
|
||||
cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['fribidi']}-COPYING"),
|
||||
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
||||
# generated tab.i files cannot be cross-compiled
|
||||
" ^&^& ".join(
|
||||
@ -376,8 +365,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||
"bins": [r"*.dll"],
|
||||
},
|
||||
"libavif": {
|
||||
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.tar.gz",
|
||||
"filename": f"libavif-{V['LIBAVIF']}.tar.gz",
|
||||
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['libavif']}.tar.gz",
|
||||
"filename": f"libavif-{V['libavif']}.tar.gz",
|
||||
"license": "LICENSE",
|
||||
"build": [
|
||||
"rustup update",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user