Repository: python-pillow/Pillow

Issue:
ZeroDivisionError is raised when the `maxval` of the PPM metadata is 0
<!--
Thank you for reporting an issue.

Follow these guidelines to ensure your issue is handled properly.

If you have a ...

1.  General question: consider asking the question on Stack Overflow
    with the python-imaging-library tag:

    * https://stackoverflow.com/questions/tagged/python-imaging-library

    Do not ask a question in both places.

    If you think you have found a bug or have an unexplained exception
    then file a bug report here.

2.  Bug report: include a self-contained, copy-pastable example that
    generates the issue if possible. Be concise with code posted.
    Guidelines on how to provide a good bug report:

    * https://stackoverflow.com/help/mcve

    Bug reports which follow these guidelines are easier to diagnose,
    and are often handled much more quickly.

3.  Feature request: do a quick search of existing issues
    to make sure this has not been asked before.

We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->

Hi,

Thank you for developing and maintaining this project.

Recently I am fuzzing this library for fun, and I found the test case which raises ZeroDivisionError.

### What did you do?

```python3
from PIL import Image
import io

# Load hopper.ppm. Assumes the current directory is the root of this repository.
with open('Tests/images/hopper.ppm', 'rb') as f:
    data = bytearray(f.read())

# Sets the maximum value 0
data[49] = 0x30
data[50] = 0x30
data[51] = 0x30

# Raises ZeroDivisionError
with Image.open(io.BytesIO(data)) as img:
    img.load()
```

### What did you expect to happen?

The file is broken, so some error should be raised. But `ZeroDivisionError` may be a little confusing.

### What actually happened?

```
Traceback (most recent call last):
  File "issue1.py", line 15, in <module>
    img.load()
  File "/usr/local/lib/python3.8/site-packages/PIL/ImageFile.py", line 234, in load
    err_code = decoder.decode(b"")[1]
  File "/usr/local/lib/python3.8/site-packages/PIL/PpmImagePlugin.py", line 152, in decode
    value = min(out_max, round(value / maxval * out_max))
ZeroDivisionError: division by zero
```

### What are your OS, Python and Pillow versions?

* OS: Debian GNU/Linux 11 (bullseye)
* Python: 3.8.13
* Pillow: 9.1.0

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->

I am happy to create the pull request to fix this issue, but I am not very sure which exception should be raised here.



Discussion:
http://netpbm.sourceforge.net/doc/ppm.html states that maxval "Must be less than 65536 and more than zero", so yes, I agree it is broken.

I've created #6242 to raise a "Invalid maxval" ValueError.
Thank you. LGTM!

Resolving patch:
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -116,6 +116,10 @@ def _open(self):
                     break
             elif ix == 2:  # token is maxval
                 maxval = token
+                if not 0 < maxval < 65536:
+                    raise ValueError(
+                        "maxval must be greater than 0 and less than 65536"
+                    )
                 if maxval > 255 and mode == "L":
                     self.mode = "I"
 


Test patch:
diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py
--- a/Tests/test_file_ppm.py
+++ b/Tests/test_file_ppm.py
@@ -145,6 +145,19 @@ def test_truncated_file(tmp_path):
             im.load()
 
 
+@pytest.mark.parametrize("maxval", (0, 65536))
+def test_invalid_maxval(maxval, tmp_path):
+    path = str(tmp_path / "temp.ppm")
+    with open(path, "w") as f:
+        f.write("P6\n3 1 " + str(maxval))
+
+    with pytest.raises(ValueError) as e:
+        with Image.open(path):
+            pass
+
+    assert str(e.value) == "maxval must be greater than 0 and less than 65536"
+
+
 def test_neg_ppm():
     # Storage.c accepted negative values for xsize, ysize.  the
     # internal open_ppm function didn't check for sanity but it