"""A shared GIF (or any shared picture): served inline as itself on the public page, and its link preview card. Teams moves a GIF `og:image` up to 15 MB; Messenger never showed one and showed a JPEG (measured 2026-11-09/11), so Facebook's crawler and a GIF too big to move get the still. DB-lane.""" import pytest from app import main from app.config import settings from app.downloads import og from app.downloads import probe as probemod from app.downloads.models import DownloadJob, MediaAsset _PAGE = """ """ _GIF_PROBE = { "streams": [{"codec_type": "video", "codec_name": "gif", "width": 300, "height": 170}], "format": {"format_name": "gif"}, } _GIF = b"GIF89a" + (300).to_bytes(2, "little") + (170).to_bytes(2, "little") + b"\x00" * 16 _TEAMS = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) SkypeUriPreview Preview/0.5 skype-url-preview@microsoft.com" _FACEBOOK = "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)" @pytest.fixture def pages(tmp_path, monkeypatch): watch = tmp_path / "watch.html" watch.write_text(_PAGE, encoding="utf-8") monkeypatch.setattr(main, "WATCH_HTML", watch) @pytest.fixture def shared(as_user, make_user, tmp_path, monkeypatch, upload_headers): """Uploads `content` as `name` (ffprobe reading it as `data`) and shares it.""" monkeypatch.setattr(settings, "files_root", str(tmp_path / "media")) owner = as_user(make_user()) def share(name: str, content: bytes, data: dict | None, **link: object) -> dict: monkeypatch.setattr(probemod, "probe_json", lambda _p: data) job = owner.post( "/api/files/upload", content=content, headers=upload_headers(name, display_name=name) ) assert job.status_code == 200, job.text made = owner.post(f"/api/downloads/{job.json()['id']}/links", json=link) assert made.status_code == 200, made.text return {**made.json(), "job_id": job.json()["id"]} return share def _asset(db, made) -> MediaAsset: return db.get(MediaAsset, db.get(DownloadJob, made["job_id"]).asset_id) def test_a_shared_gif_is_served_inline_as_a_gif_and_can_run_nothing(shared, client): made = shared("wave.gif", _GIF, _GIF_PROBE, allow_download=False) r = client.get(f"/api/public/watch/{made['token']}/file") assert r.status_code == 200 assert r.headers["content-type"] == "image/gif" assert r.headers["content-disposition"].startswith("inline") # A "gif" whose bytes are a page must not run on this login-free origin. assert r.headers["x-content-type-options"] == "nosniff" assert "sandbox" in r.headers["content-security-policy"] assert r.content == _GIF def test_a_gif_link_that_allows_download_hands_it_over_as_a_file(shared, client): made = shared("wave.gif", _GIF, _GIF_PROBE, allow_download=True) r = client.get(f"/api/public/watch/{made['token']}/file") assert r.headers["content-type"] == "image/gif" assert r.headers["content-disposition"].startswith("attachment") def test_any_other_file_is_still_only_a_download(shared, client): made = shared("notes.html", b"", None) r = client.get(f"/api/public/watch/{made['token']}/file") assert r.headers["content-type"] == "application/octet-stream" assert r.headers["content-disposition"].startswith("attachment") def test_the_page_is_told_a_picture_is_a_picture(shared, client): made = shared("wave.gif", _GIF, _GIF_PROBE) meta = client.get(f"/api/public/watch/{made['token']}").json() assert meta["preview"] == "image" other = shared("backup.tar.gz", b"x", None) assert client.get(f"/api/public/watch/{other['token']}").json()["preview"] is None def _og(text: str, prop: str) -> str | None: marker = f'property="{prop}" content="' if marker not in text: return None return text.split(marker, 1)[1].split('"', 1)[0] def test_a_gif_link_previews_as_the_moving_gif_itself(pages, shared, client): made = shared("wave.gif", _GIF, _GIF_PROBE) page = client.get(f"/file/{made['token']}", headers={"User-Agent": _TEAMS}).text assert _og(page, "og:image") == (f"{settings.app_base}/api/public/watch/{made['token']}/file") assert _og(page, "og:image:type") == "image/gif" # Its size from its own header: a card wants it up front. assert (_og(page, "og:image:width"), _og(page, "og:image:height")) == ("300", "170") def test_facebooks_crawler_gets_the_still_of_a_gif(pages, shared, client, db): made = shared("wave.gif", _GIF, _GIF_PROBE) asset = _asset(db, made) asset.poster_path = "wave.jpg" db.commit() page = client.get(f"/file/{made['token']}", headers={"User-Agent": _FACEBOOK}).text assert _og(page, "og:image") == ( f"{settings.app_base}/api/public/watch/{made['token']}/poster.jpg" ) assert _og(page, "og:image:type") == "image/jpeg" def test_a_gif_too_big_to_move_previews_as_its_still(pages, shared, client, db): made = shared("wave.gif", _GIF, _GIF_PROBE) asset = _asset(db, made) asset.poster_path = "wave.jpg" asset.size_bytes = 15 * 1024 * 1024 + 1 db.commit() page = client.get(f"/file/{made['token']}", headers={"User-Agent": _TEAMS}).text assert _og(page, "og:image").endswith("/poster.jpg") def test_a_password_gif_link_shows_nothing_of_it(pages, shared, client): made = shared("wave.gif", _GIF, _GIF_PROBE, password="pw") page = client.get(f"/file/{made['token']}", headers={"User-Agent": _TEAMS}).text assert _og(page, "og:image") is None def test_the_card_builder_takes_no_agent_as_a_browser(shared, db): made = shared("wave.gif", _GIF, _GIF_PROBE) assert 'content="image/gif"' in og._watch_tags(made["token"], db)