fix: resolve merge conflicts and refactor join_url to use urllib.parse

- Resolve merge conflicts in .jules/sentinel.md and app/utils/network.py
- Refactor join_url() to use urllib.parse.urlsplit/urlunsplit and posixpath
  instead of sentinel-string hack, preventing corruption for any input URL
- Fix test to use pytest tmp_path fixture instead of hard-coded /tmp/workdir
This commit is contained in:
copilot-swe-agent[bot]
2026-03-23 16:17:01 +00:00
5 changed files with 96 additions and 16 deletions
+12 -1
View File
@@ -7,8 +7,19 @@ def hash_file(filepath: str | Path, chunk_size: int = 65536) -> str:
Returns the SHA-256 hash of the file at 'filepath'.
Reads the file in chunks to handle large files efficiently.
"""
from app.config import settings
filepath_obj = Path(filepath).resolve()
workdir_obj = Path(settings.workdir).resolve()
# Security check: Ensure the resolved path is strictly within the allowed workdir
try:
filepath_obj.relative_to(workdir_obj)
except ValueError:
raise FileNotFoundError(f"Access denied: path traversal attempt or file outside workdir '{filepath}'")
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
with open(filepath_obj, "rb") as f:
while True:
data = f.read(chunk_size)
if not data:
+24 -8
View File
@@ -1,6 +1,8 @@
import ipaddress
import logging
import posixpath
import socket
from urllib.parse import urlsplit, urlunsplit
logger = logging.getLogger(__name__)
@@ -36,12 +38,26 @@ def is_private_ip(hostname: str) -> bool:
def join_url(base: str, *parts: str) -> str:
"""
Safely join a base URL and multiple path parts.
Handles double slashes while preserving the protocol '://'.
Safely join a base URL with one or more path parts.
Uses urllib.parse to correctly handle scheme/netloc/query/fragment so that
only the path component is normalised (double slashes removed via
posixpath.join). The scheme separator ``://`` is therefore never at risk
of being collapsed.
Examples:
join_url("https://example.com/dav/", "/remote/", "file.pdf")
-> "https://example.com/dav/remote/file.pdf"
"""
url = "/".join([base, *parts])
url = url.replace("://", "$PLACEHOLDER$")
while "//" in url:
url = url.replace("//", "/")
url = url.replace("$PLACEHOLDER$", "://")
return url
parsed = urlsplit(base)
# Strip leading/trailing slashes from every part so posixpath.join
# produces a clean joined path without accidental double slashes.
stripped_parts = [p.strip("/") for p in parts if p.strip("/")]
base_path = parsed.path.rstrip("/")
if stripped_parts:
new_path = base_path + "/" + "/".join(stripped_parts)
else:
new_path = base_path
# Normalise any remaining double slashes in the path only.
new_path = posixpath.normpath(new_path) if new_path else "/"
return urlunsplit((parsed.scheme, parsed.netloc, new_path, parsed.query, parsed.fragment))