mirror of
https://github.com/open-webui/open-terminal.git
synced 2026-11-04 08:10:53 +00:00
feat: resolve relative paths against session cwd in all file endpoints
All LLM-exposed file endpoints (list_files, read_file, write_file, display_file, replace_file_content, grep_search, glob_search) now read the X-Session-Id header and resolve relative paths against the session's working directory instead of always using fs.home. - Added optional cwd parameter to UserFS.resolve_path() - Relative paths join against cwd if provided, else fs.home - Absolute paths are unaffected - execute endpoint also resolves relative cwd param against session cwd
This commit is contained in:
1 parent
fbaf2e8e69
commit
4db47e8cee
2 files changed
+35
-15
No files matched your search
+30
-11
@@ -450,10 +450,13 @@ async def set_cwd(
|
||||
},
|
||||
)
|
||||
async def list_files(
|
||||
http_request: Request,
|
||||
directory: str = Query(".", description="Directory path to list."),
|
||||
fs: UserFS = Depends(get_filesystem),
|
||||
):
|
||||
target = fs.resolve_path(directory)
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(directory, cwd=session_cwd)
|
||||
if not await fs.isdir(target):
|
||||
raise HTTPException(status_code=404, detail="Directory not found")
|
||||
entries = await fs.listdir(target)
|
||||
@@ -473,6 +476,7 @@ async def list_files(
|
||||
},
|
||||
)
|
||||
async def read_file(
|
||||
http_request: Request,
|
||||
path: str = Query(..., description="Path to the file to read."),
|
||||
start_line: Optional[int] = Query(
|
||||
None, description="First line to return (1-indexed, inclusive). Defaults to the beginning of the file.", ge=1
|
||||
@@ -482,8 +486,9 @@ async def read_file(
|
||||
),
|
||||
fs: UserFS = Depends(get_filesystem),
|
||||
):
|
||||
|
||||
target = fs.resolve_path(path)
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(path, cwd=session_cwd)
|
||||
if not await fs.isfile(target):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
@@ -544,6 +549,7 @@ async def read_file(
|
||||
},
|
||||
)
|
||||
async def display_file(
|
||||
http_request: Request,
|
||||
path: str = Query(..., description="Absolute path to the file to display."),
|
||||
fs: UserFS = Depends(get_filesystem),
|
||||
):
|
||||
@@ -554,7 +560,9 @@ async def display_file(
|
||||
intercepting this response and presenting the file in its own UI (e.g.
|
||||
opening a preview pane, launching a viewer, etc.).
|
||||
"""
|
||||
target = fs.resolve_path(path)
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(path, cwd=session_cwd)
|
||||
exists = await fs.isfile(target)
|
||||
return {"path": target, "exists": exists}
|
||||
|
||||
@@ -595,8 +603,10 @@ async def view_file(
|
||||
401: {"description": "Invalid or missing API key."},
|
||||
},
|
||||
)
|
||||
async def write_file(request: WriteRequest, fs: UserFS = Depends(get_filesystem)):
|
||||
target = fs.resolve_path(request.path)
|
||||
async def write_file(http_request: Request, request: WriteRequest, fs: UserFS = Depends(get_filesystem)):
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(request.path, cwd=session_cwd)
|
||||
try:
|
||||
await fs.write(target, request.content)
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
@@ -676,8 +686,10 @@ async def move_entry(request: MoveRequest, fs: UserFS = Depends(get_filesystem))
|
||||
401: {"description": "Invalid or missing API key."},
|
||||
},
|
||||
)
|
||||
async def replace_file_content(request: ReplaceRequest, fs: UserFS = Depends(get_filesystem)):
|
||||
target = fs.resolve_path(request.path)
|
||||
async def replace_file_content(http_request: Request, request: ReplaceRequest, fs: UserFS = Depends(get_filesystem)):
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(request.path, cwd=session_cwd)
|
||||
if not await fs.isfile(target):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
@@ -735,6 +747,7 @@ async def replace_file_content(request: ReplaceRequest, fs: UserFS = Depends(get
|
||||
},
|
||||
)
|
||||
async def grep_search(
|
||||
http_request: Request,
|
||||
query: str = Query(..., description="Text or regex pattern to search for."),
|
||||
path: str = Query(".", description="Directory or file to search in."),
|
||||
regex: bool = Query(False, description="Treat query as a regex pattern."),
|
||||
@@ -754,7 +767,9 @@ async def grep_search(
|
||||
),
|
||||
fs: UserFS = Depends(get_filesystem),
|
||||
):
|
||||
target = fs.resolve_path(path)
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(path, cwd=session_cwd)
|
||||
if not await aiofiles.os.path.exists(target):
|
||||
raise HTTPException(status_code=404, detail="Search path not found")
|
||||
|
||||
@@ -845,6 +860,7 @@ async def grep_search(
|
||||
},
|
||||
)
|
||||
async def glob_search(
|
||||
http_request: Request,
|
||||
pattern: str = Query(..., description="Glob pattern to search for (e.g. '*.py')."),
|
||||
path: str = Query(".", description="Directory to search within."),
|
||||
exclude: Optional[list[str]] = Query(
|
||||
@@ -860,7 +876,9 @@ async def glob_search(
|
||||
),
|
||||
fs: UserFS = Depends(get_filesystem),
|
||||
):
|
||||
target = fs.resolve_path(path)
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
target = fs.resolve_path(path, cwd=session_cwd)
|
||||
if not await aiofiles.os.path.isdir(target):
|
||||
raise HTTPException(status_code=404, detail="Search directory not found")
|
||||
|
||||
@@ -1093,7 +1111,8 @@ async def execute(
|
||||
):
|
||||
fs = get_filesystem(http_request)
|
||||
session_id = http_request.headers.get("x-session-id")
|
||||
cwd = fs.resolve_path(request.cwd) if request.cwd else _get_session_cwd(session_id, fs)
|
||||
session_cwd = _get_session_cwd(session_id, fs) if session_id else None
|
||||
cwd = fs.resolve_path(request.cwd, cwd=session_cwd) if request.cwd else (session_cwd or fs.home)
|
||||
|
||||
subprocess_env = {**os.environ, **request.env} if request.env else None
|
||||
runner = await create_runner(
|
||||
|
||||
@@ -38,12 +38,13 @@ class UserFS:
|
||||
# Path resolution
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def resolve_path(self, path: str) -> str:
|
||||
def resolve_path(self, path: str, cwd: str | None = None) -> str:
|
||||
"""Resolve *path* to an absolute path relative to the user's home.
|
||||
|
||||
Absolute paths are normalised in place. Relative paths are joined
|
||||
to ``self.home`` so that they resolve against the user's home
|
||||
directory rather than the server process's ``os.getcwd()``.
|
||||
to *cwd* (if provided) or ``self.home`` so that they resolve against
|
||||
the session's working directory rather than the server process's
|
||||
``os.getcwd()``.
|
||||
|
||||
In multi-user mode, paths under ``/home/user`` (the server process's
|
||||
default home) are automatically rewritten to the provisioned user's
|
||||
@@ -61,7 +62,7 @@ class UserFS:
|
||||
path = self.home + path[len(prefix):]
|
||||
break
|
||||
return os.path.normpath(path)
|
||||
return os.path.normpath(os.path.join(self.home, path))
|
||||
return os.path.normpath(os.path.join(cwd or self.home, path))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Path validation
|
||||
|
||||
Reference in new issue
Block a user