mirror of
https://github.com/alexta69/metube.git
synced 2026-06-20 16:20:07 +00:00
fix open download of cookie files
This commit is contained in:
+25
-2
@@ -16,7 +16,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
from urllib.parse import parse_qs, unquote, urlencode, urlparse, urlunparse
|
||||||
from watchfiles import DefaultFilter, Change, awatch
|
from watchfiles import DefaultFilter, Change, awatch
|
||||||
|
|
||||||
from ytdl import DownloadQueueNotifier, DownloadQueue, Download
|
from ytdl import DownloadQueueNotifier, DownloadQueue, Download
|
||||||
@@ -286,7 +286,30 @@ class ObjectSerializer(json.JSONEncoder):
|
|||||||
return json.JSONEncoder.default(self, obj)
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
serializer = ObjectSerializer()
|
serializer = ObjectSerializer()
|
||||||
app = web.Application()
|
|
||||||
|
_STATE_DIR_REAL = os.path.realpath(config.STATE_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_within_state_dir(real_target: str) -> bool:
|
||||||
|
return real_target == _STATE_DIR_REAL or real_target.startswith(_STATE_DIR_REAL + os.sep)
|
||||||
|
|
||||||
|
|
||||||
|
@web.middleware
|
||||||
|
async def state_dir_guard(request, handler):
|
||||||
|
for prefix, base in (
|
||||||
|
(config.URL_PREFIX + 'download/', config.DOWNLOAD_DIR),
|
||||||
|
(config.URL_PREFIX + 'audio_download/', config.AUDIO_DOWNLOAD_DIR),
|
||||||
|
):
|
||||||
|
if request.path.startswith(prefix):
|
||||||
|
rel = unquote(request.path[len(prefix):])
|
||||||
|
target = os.path.realpath(os.path.join(base, rel))
|
||||||
|
if _is_within_state_dir(target):
|
||||||
|
raise web.HTTPNotFound()
|
||||||
|
break
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
|
|
||||||
|
app = web.Application(middlewares=[state_dir_guard])
|
||||||
_cors_origins = [o.strip() for o in config.CORS_ALLOWED_ORIGINS.split(',') if o.strip()] if config.CORS_ALLOWED_ORIGINS else []
|
_cors_origins = [o.strip() for o in config.CORS_ALLOWED_ORIGINS.split(',') if o.strip()] if config.CORS_ALLOWED_ORIGINS else []
|
||||||
sio = socketio.AsyncServer(cors_allowed_origins=_cors_origins if _cors_origins else [])
|
sio = socketio.AsyncServer(cors_allowed_origins=_cors_origins if _cors_origins else [])
|
||||||
sio.attach(app, socketio_path=config.URL_PREFIX + 'socket.io')
|
sio.attach(app, socketio_path=config.URL_PREFIX + 'socket.io')
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
from aiohttp.test_utils import TestClient, TestServer
|
||||||
|
|
||||||
import main
|
import main
|
||||||
|
|
||||||
@@ -306,3 +309,41 @@ async def test_subscribe_rejects_clip_options(mock_dqueue, monkeypatch):
|
|||||||
with pytest.raises(web.HTTPBadRequest):
|
with pytest.raises(web.HTTPBadRequest):
|
||||||
await main.subscribe(req)
|
await main.subscribe(req)
|
||||||
main.submgr.add_subscription.assert_not_awaited()
|
main.submgr.add_subscription.assert_not_awaited()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_within_state_dir_blocks_state_subtree():
|
||||||
|
state_dir = main._STATE_DIR_REAL
|
||||||
|
assert main._is_within_state_dir(state_dir)
|
||||||
|
assert main._is_within_state_dir(os.path.join(state_dir, "cookies.txt"))
|
||||||
|
assert main._is_within_state_dir(os.path.join(state_dir, "queue", "item.json"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_within_state_dir_allows_sibling_downloads():
|
||||||
|
download_dir = os.path.realpath(main.config.DOWNLOAD_DIR)
|
||||||
|
assert not main._is_within_state_dir(os.path.join(download_dir, "video.mp4"))
|
||||||
|
assert not main._is_within_state_dir("/tmp/unrelated/video.mp4")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_download_blocks_state_dir_files(monkeypatch):
|
||||||
|
download_dir = Path(main.config.DOWNLOAD_DIR)
|
||||||
|
state_dir = download_dir / ".metube"
|
||||||
|
state_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(state_dir / "cookies.txt").write_text("# Netscape HTTP Cookie File\n", encoding="utf-8")
|
||||||
|
(download_dir / "video.mp4").write_bytes(b"video")
|
||||||
|
|
||||||
|
monkeypatch.setattr(main.config, "STATE_DIR", str(state_dir))
|
||||||
|
monkeypatch.setattr(main, "_STATE_DIR_REAL", os.path.realpath(str(state_dir)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with TestClient(TestServer(main.app)) as client:
|
||||||
|
blocked = await client.get("/download/.metube/cookies.txt")
|
||||||
|
assert blocked.status == 404
|
||||||
|
|
||||||
|
allowed = await client.get("/download/video.mp4")
|
||||||
|
assert allowed.status == 200
|
||||||
|
assert await allowed.read() == b"video"
|
||||||
|
finally:
|
||||||
|
(state_dir / "cookies.txt").unlink(missing_ok=True)
|
||||||
|
(download_dir / "video.mp4").unlink(missing_ok=True)
|
||||||
|
state_dir.rmdir()
|
||||||
|
|||||||
Reference in New Issue
Block a user