new changes

This commit is contained in:
Niranjan
2026-04-07 05:26:12 +05:30
parent 60e8d457c4
commit b679cc3bb5
2 changed files with 53 additions and 26 deletions

View File

@@ -13,28 +13,46 @@ router = APIRouter(prefix="/files", tags=["files"])
def _resolve_path(path: str) -> str: def _resolve_path(path: str) -> str:
"""Resolve and validate path within allowed roots (cross-platform)""" """
cfg = get_runtime_config() Resolve API path to an OS path.
www_root = os.path.abspath(cfg["www_root"])
setup_path = os.path.abspath(cfg["setup_path"]) On Linux/macOS: path is an absolute POSIX path from filesystem root (/) so admins
allowed = [www_root, setup_path] can browse the whole server (same expectation as BT/aaPanel-style panels).
if os.name != "nt":
allowed.append(os.path.abspath("/www")) On Windows (dev): paths stay sandboxed under www_root / setup_path.
"""
if ".." in path: if ".." in path:
raise HTTPException(status_code=400, detail="Path traversal not allowed") raise HTTPException(status_code=400, detail="Path traversal not allowed")
norm_path = path.strip().replace("\\", "/").strip("/")
# Root or www_root-style path raw = path.strip().replace("\\", "/")
if not norm_path or norm_path in ("www", "www/wwwroot", "wwwroot"):
full = www_root if os.name == "nt":
elif norm_path.startswith("www/wwwroot/"): cfg = get_runtime_config()
full = os.path.abspath(os.path.join(www_root, norm_path[12:])) www_root = os.path.abspath(cfg["www_root"])
else: setup_path = os.path.abspath(cfg["setup_path"])
full = os.path.abspath(os.path.join(www_root, norm_path)) allowed = [www_root, setup_path]
if not any( norm_path = raw.strip("/")
full == r or (full + os.sep).startswith(r + os.sep) if not norm_path or norm_path in ("www", "www/wwwroot", "wwwroot"):
for r in allowed full = www_root
): elif norm_path.startswith("www/wwwroot/"):
raise HTTPException(status_code=403, detail="Path not allowed") full = os.path.abspath(os.path.join(www_root, norm_path[12:]))
else:
full = os.path.abspath(os.path.join(www_root, norm_path))
if not any(
full == r or (full + os.sep).startswith(r + os.sep)
for r in allowed
):
raise HTTPException(status_code=403, detail="Path not allowed")
return full
# POSIX: absolute paths from /
if not raw or raw == "/":
return "/"
if not raw.startswith("/"):
raw = "/" + raw
full = os.path.normpath(raw)
if not full.startswith("/"):
raise HTTPException(status_code=400, detail="Invalid path")
return full return full
@@ -51,7 +69,15 @@ async def files_list(
if not os.path.isdir(full): if not os.path.isdir(full):
raise HTTPException(status_code=404, detail="Not a directory") raise HTTPException(status_code=404, detail="Not a directory")
items = [] items = []
for name in os.listdir(full): try:
names = os.listdir(full)
except PermissionError:
raise HTTPException(status_code=403, detail="Permission denied")
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Not found")
except OSError as e:
raise HTTPException(status_code=500, detail=str(e))
for name in names:
item_path = os.path.join(full, name) item_path = os.path.join(full, name)
try: try:
stat = os.stat(item_path) stat = os.stat(item_path)
@@ -62,7 +88,8 @@ async def files_list(
}) })
except OSError: except OSError:
pass pass
return {"path": path, "items": items} display_path = full if full == "/" else full.rstrip("/")
return {"path": display_path, "items": items}
@router.get("/read") @router.get("/read")

View File

@@ -58,7 +58,7 @@ export function FilesPage() {
const handleBack = () => { const handleBack = () => {
const parts = path.replace(/\/$/, '').split('/').filter(Boolean) const parts = path.replace(/\/$/, '').split('/').filter(Boolean)
if (parts.length <= 1) return if (parts.length === 0) return
parts.pop() parts.pop()
const newPath = parts.length === 0 ? '/' : '/' + parts.join('/') const newPath = parts.length === 0 ? '/' : '/' + parts.join('/')
loadDir(newPath) loadDir(newPath)
@@ -147,8 +147,8 @@ export function FilesPage() {
.catch((err) => setError(err.message)) .catch((err) => setError(err.message))
} }
const breadcrumbs = path.split('/').filter(Boolean) const pathSegments = path.replace(/\/$/, '').split('/').filter(Boolean)
const canGoBack = breadcrumbs.length > 0 const canGoBack = pathSegments.length > 0
return ( return (
<> <>
@@ -172,7 +172,7 @@ export function FilesPage() {
)} )}
Upload Upload
</AdminButton> </AdminButton>
<code className="small bg-body-secondary px-2 py-1 rounded ms-auto text-break">Path: {path}</code> <code className="small bg-body-secondary px-2 py-1 rounded ms-auto text-break">Path: {path || '/'}</code>
</div> </div>
<Modal show={showMkdir} onHide={() => { setShowMkdir(false); setMkdirName('') }} centered> <Modal show={showMkdir} onHide={() => { setShowMkdir(false); setMkdirName('') }} centered>