113 lines
6.3 KiB
Python
113 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Large image uploads: a web version (WebP, longest side capped) replaces the upload, the
|
|
original is kept and travels with rename/trash/restore; small files stay as they are; the
|
|
limits reach the library; a file above the PHP limit gets a message naming that limit."""
|
|
import json
|
|
import subprocess
|
|
import tempfile
|
|
import uuid
|
|
|
|
from support import Client, Site, write_json
|
|
|
|
|
|
def multipart(fields, files):
|
|
boundary = uuid.uuid4().hex
|
|
body = b""
|
|
for key, value in fields.items():
|
|
body += (f"--{boundary}\r\nContent-Disposition: form-data; name=\"{key}\"\r\n\r\n{value}\r\n").encode()
|
|
for key, (name, data, ctype) in files.items():
|
|
body += (f"--{boundary}\r\nContent-Disposition: form-data; name=\"{key}\"; filename=\"{name}\"\r\n"
|
|
f"Content-Type: {ctype}\r\n\r\n").encode() + data + b"\r\n"
|
|
body += f"--{boundary}--\r\n".encode()
|
|
return body, f"multipart/form-data; boundary={boundary}"
|
|
|
|
|
|
def upload(client, name, data, extra=None):
|
|
fields = {"action": "upload-media", "csrf_token": client.csrf, "type": "image"}
|
|
fields.update(extra or {})
|
|
body, ctype = multipart(fields, {"file": (name, data, "application/octet-stream")})
|
|
status, _, raw = client.request("/admin/api.php", body, "POST", {"Content-Type": ctype})
|
|
return json.loads(raw)
|
|
|
|
|
|
def post(client, action, **fields):
|
|
data = {"action": action, "csrf_token": client.csrf, **fields}
|
|
_, _, raw = client.request("/admin/api.php", data, "POST")
|
|
return json.loads(raw)
|
|
|
|
|
|
def make_png(width, height, noise=False):
|
|
"""A PNG from GD: a gradient compresses small, noise makes it big."""
|
|
script = (f"$i=imagecreatetruecolor({width},{height});"
|
|
+ ("mt_srand(1);for($y=0;$y<{h};$y++)for($x=0;$x<{w};$x++)imagesetpixel($i,$x,$y,mt_rand(0,0xffffff));".format(w=width, h=height) if noise
|
|
else f"for($y=0;$y<{height};$y+=8)imagefilledrectangle($i,0,$y,{width},$y+7,imagecolorallocate($i,$y%256,($y*3)%256,128));")
|
|
+ "imagepng($i,'php://stdout');")
|
|
return subprocess.check_output(["php", "-r", script])
|
|
|
|
|
|
with tempfile.TemporaryDirectory(prefix="raiju-web-version-") as folder:
|
|
site = Site(folder)
|
|
write_json(site.root / "content/settings.json", {"modules": {"ai": False}, "privacy": {"analyticsEnabled": False},
|
|
"media": {"maxUploadMb": 20, "webMaxSide": 1200, "webQuality": 80}})
|
|
site.start(ini={"upload_max_filesize": "3M", "post_max_size": "4M"})
|
|
try:
|
|
admin = Client(site).login()
|
|
|
|
# 1. The library reports both limits.
|
|
_, _, raw = admin.request("/admin/api.php?action=list-media&type=image")
|
|
limits = json.loads(raw)["data"]["limits"]
|
|
assert limits["imageMaxBytes"] == 20 * 1024 * 1024 and limits["serverMaxBytes"] == 3 * 1024 * 1024, limits
|
|
assert limits["imageEffectiveBytes"] == 3 * 1024 * 1024 and limits["webMaxSide"] == 1200, limits
|
|
|
|
# 2. A big photo (3000 px wide) becomes a 1200 px WebP; the original is kept.
|
|
big = make_png(3000, 2000)
|
|
result = upload(admin, "Sunset Photo.png", big)
|
|
assert result["success"], result
|
|
name = result["data"]["name"]
|
|
assert name == "Sunset-Photo.webp", name
|
|
wv = result["data"]["webVersion"]
|
|
assert wv and wv["width"] == 1200 and wv["height"] == 800 and wv["originalKept"], wv
|
|
assert (site.root / "assets/images" / name).is_file()
|
|
original = site.root / "assets/images-originals/Sunset-Photo.png"
|
|
assert original.is_file() and original.stat().st_size == len(big), "original not stored unchanged"
|
|
_, _, raw = admin.request("/admin/api.php?action=list-media&type=image")
|
|
item = next(i for i in json.loads(raw)["data"]["items"] if i["name"] == name)
|
|
assert item["original"]["name"] == "Sunset-Photo.png" and item["original"]["width"] == 3000, item["original"]
|
|
assert item["original"]["path"] == "../assets/images-originals/Sunset-Photo.png"
|
|
status, _, _ = Client(site).request("/assets/images-originals/Sunset-Photo.png")
|
|
assert status == 200, "the original must be downloadable"
|
|
|
|
# 3. A small image stays as uploaded, with no original.
|
|
small = make_png(640, 400)
|
|
result = upload(admin, "icon.png", small)
|
|
assert result["success"] and result["data"]["name"] == "icon.png" and result["data"]["webVersion"] is None, result
|
|
assert not (site.root / "assets/images-originals/icon.png").exists()
|
|
|
|
# 4. Rename, trash and restore carry the original along.
|
|
result = post(admin, "rename-media", type="image", filename=name, newName="evening.webp")
|
|
assert result["success"], result
|
|
assert (site.root / "assets/images-originals/evening.png").is_file()
|
|
assert post(admin, "delete-media", type="image", filename="evening.webp")["success"]
|
|
assert (site.root / "assets/images-originals-trash/evening.png").is_file()
|
|
assert not (site.root / "assets/images-originals/evening.png").exists()
|
|
assert post(admin, "restore-media", type="image", filename="evening.webp")["success"]
|
|
assert (site.root / "assets/images-originals/evening.png").is_file()
|
|
assert post(admin, "delete-media", type="image", filename="evening.webp")["success"]
|
|
assert post(admin, "delete-media-trash", type="image", filename="evening.webp")["success"]
|
|
assert not (site.root / "assets/images-originals-trash/evening.png").exists(), "original must go with the file"
|
|
|
|
# 5. Above the PHP limit: the message names the server limit.
|
|
huge = make_png(1400, 1400, noise=True)
|
|
assert len(huge) > 3 * 1024 * 1024, len(huge)
|
|
result = upload(admin, "noise.png", huge)
|
|
assert not result["success"] and "3 MB" in result["message"] and "upload_max_filesize" in result["message"], result
|
|
|
|
# 6. Web versions off: the big photo is stored as uploaded.
|
|
write_json(site.root / "content/settings.json", {"modules": {"ai": False}, "media": {"webVersion": False}})
|
|
result = upload(admin, "plain.png", big)
|
|
assert result["success"] and result["data"]["name"] == "plain.png" and result["data"]["webVersion"] is None, result
|
|
finally:
|
|
site.close()
|
|
|
|
print("PASS media web version: limits reported, big upload -> WebP + original, small kept, original follows rename/trash/restore, server limit named, switch off respected")
|