From e756e0af5e00def9f032d708a701beb7b01702b4 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Thu, 18 Jul 2024 13:05:27 +0200 Subject: [PATCH] Fixing file:// file pickup - for change detection of local files (#2505) --- .../content_fetchers/requests.py | 21 ++++++++++----- changedetectionio/run_basic_tests.sh | 6 ++++- changedetectionio/tests/test_security.py | 27 ++++++++++++++++--- requirements.txt | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/changedetectionio/content_fetchers/requests.py b/changedetectionio/content_fetchers/requests.py index 2c28cda7..5e984c5e 100644 --- a/changedetectionio/content_fetchers/requests.py +++ b/changedetectionio/content_fetchers/requests.py @@ -4,6 +4,7 @@ import os import chardet import requests +from changedetectionio import strtobool from changedetectionio.content_fetchers.exceptions import BrowserStepsInUnsupportedFetcher, EmptyReply, Non200ErrorCodeReceived from changedetectionio.content_fetchers.base import Fetcher @@ -45,13 +46,19 @@ class fetcher(Fetcher): if self.system_https_proxy: proxies['https'] = self.system_https_proxy - r = requests.request(method=request_method, - data=request_body, - url=url, - headers=request_headers, - timeout=timeout, - proxies=proxies, - verify=False) + session = requests.Session() + + if strtobool(os.getenv('ALLOW_FILE_URI', 'false')) and url.startswith('file://'): + from requests_file import FileAdapter + session.mount('file://', FileAdapter()) + + r = session.request(method=request_method, + data=request_body, + url=url, + headers=request_headers, + timeout=timeout, + proxies=proxies, + verify=False) # If the response did not tell us what encoding format to expect, Then use chardet to override what `requests` thinks. # For example - some sites don't tell us it's utf-8, but return utf-8 content diff --git a/changedetectionio/run_basic_tests.sh b/changedetectionio/run_basic_tests.sh index d9fa9ff0..38bcd603 100755 --- a/changedetectionio/run_basic_tests.sh +++ b/changedetectionio/run_basic_tests.sh @@ -35,4 +35,8 @@ pytest tests/test_access_control.py pytest tests/test_notification.py pytest tests/test_backend.py pytest tests/test_rss.py -pytest tests/test_unique_lines.py \ No newline at end of file +pytest tests/test_unique_lines.py + +# Check file:// will pickup a file when enabled +echo "Hello world" > /tmp/test-file.txt +ALLOW_FILE_URI=yes pytest tests/test_security.py diff --git a/changedetectionio/tests/test_security.py b/changedetectionio/tests/test_security.py index 0b2ee7dc..af55efb2 100644 --- a/changedetectionio/tests/test_security.py +++ b/changedetectionio/tests/test_security.py @@ -1,7 +1,12 @@ +import os + from flask import url_for from .util import set_original_response, set_modified_response, live_server_setup, wait_for_all_checks import time +from .. import strtobool + + def test_setup(client, live_server, measure_memory_usage): live_server_setup(live_server) @@ -55,17 +60,33 @@ def test_bad_access(client, live_server, measure_memory_usage): assert b'Watch protocol is not permitted by SAFE_PROTOCOL_REGEX' in res.data - # file:// is permitted by default, but it will be caught by ALLOW_FILE_URI +def test_file_access(client, live_server, measure_memory_usage): + #live_server_setup(live_server) + + test_file_path = "/tmp/test-file.txt" + + # file:// is permitted by default, but it will be caught by ALLOW_FILE_URI client.post( url_for("form_quick_watch_add"), - data={"url": 'file:///tasty/disk/drive', "tags": ''}, + data={"url": f"file://{test_file_path}", "tags": ''}, follow_redirects=True ) wait_for_all_checks(client) res = client.get(url_for("index")) - assert b'file:// type access is denied for security reasons.' in res.data + # If it is enabled at test time + if strtobool(os.getenv('ALLOW_FILE_URI', 'false')): + res = client.get( + url_for("preview_page", uuid="first"), + follow_redirects=True + ) + + # Should see something (this file added by run_basic_tests.sh) + assert b"Hello world" in res.data + else: + # Default should be here + assert b'file:// type access is denied for security reasons.' in res.data def test_xss(client, live_server, measure_memory_usage): #live_server_setup(live_server) diff --git a/requirements.txt b/requirements.txt index fcfdf774..770c8601 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,6 +22,7 @@ validators~=0.21 # >= 2.26 also adds Brotli support if brotli is installed brotli~=1.0 requests[socks] +requests-file urllib3==1.26.19 chardet>2.3.0