aboutsummaryrefslogtreecommitdiff
path: root/gptel-tools
diff options
context:
space:
mode:
Diffstat (limited to 'gptel-tools')
-rw-r--r--gptel-tools/git_diff.el8
-rw-r--r--gptel-tools/git_log.el8
-rw-r--r--gptel-tools/git_status.el8
-rw-r--r--gptel-tools/move_to_trash.el23
-rw-r--r--gptel-tools/read_buffer.el4
-rw-r--r--gptel-tools/read_text_file.el20
-rw-r--r--gptel-tools/update_text_file.el30
-rw-r--r--gptel-tools/web_fetch.el2
-rw-r--r--gptel-tools/write_text_file.el15
9 files changed, 82 insertions, 36 deletions
diff --git a/gptel-tools/git_diff.el b/gptel-tools/git_diff.el
index daccdc20..47db8dae 100644
--- a/gptel-tools/git_diff.el
+++ b/gptel-tools/git_diff.el
@@ -23,11 +23,17 @@
"Validate PATH for a git diff call. Return the expanded path on success.
Same contract as the other git_* validators: under HOME, a directory,
inside a git working tree."
- (let ((full (expand-file-name (or path "~") "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full (expand-file-name (or path "~") "~")))
(unless (string-prefix-p (expand-file-name "~") full)
(error "Path must be within home directory: %s" path))
(unless (file-directory-p full)
(error "Not a directory: %s" full))
+ (let ((resolved (file-truename full)))
+ (unless (or (string= resolved (directory-file-name home))
+ (string-prefix-p home resolved))
+ (error "Resolved path must be within home directory: %s" path))
+ (setq full resolved))
(let ((default-directory full))
(unless (zerop (process-file "git" nil nil nil
"rev-parse" "--is-inside-work-tree"))
diff --git a/gptel-tools/git_log.el b/gptel-tools/git_log.el
index 9cfae263..324435dc 100644
--- a/gptel-tools/git_log.el
+++ b/gptel-tools/git_log.el
@@ -25,11 +25,17 @@
"Validate PATH for a git log call. Return the expanded path on success.
Same contract as the git_status validator: must be under HOME, must
be a directory, must be inside a git working tree."
- (let ((full (expand-file-name (or path "~") "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full (expand-file-name (or path "~") "~")))
(unless (string-prefix-p (expand-file-name "~") full)
(error "Path must be within home directory: %s" path))
(unless (file-directory-p full)
(error "Not a directory: %s" full))
+ (let ((resolved (file-truename full)))
+ (unless (or (string= resolved (directory-file-name home))
+ (string-prefix-p home resolved))
+ (error "Resolved path must be within home directory: %s" path))
+ (setq full resolved))
(let ((default-directory full))
(unless (zerop (process-file "git" nil nil nil
"rev-parse" "--is-inside-work-tree"))
diff --git a/gptel-tools/git_status.el b/gptel-tools/git_status.el
index 300d5da5..de76a985 100644
--- a/gptel-tools/git_status.el
+++ b/gptel-tools/git_status.el
@@ -23,11 +23,17 @@
PATH must resolve under the user's home directory, must be an
existing directory, and must be inside a git working tree. Returns
the expanded path string on success; signals `error' otherwise."
- (let ((full (expand-file-name (or path "~") "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full (expand-file-name (or path "~") "~")))
(unless (string-prefix-p (expand-file-name "~") full)
(error "Path must be within home directory: %s" path))
(unless (file-directory-p full)
(error "Not a directory: %s" full))
+ (let ((resolved (file-truename full)))
+ (unless (or (string= resolved (directory-file-name home))
+ (string-prefix-p home resolved))
+ (error "Resolved path must be within home directory: %s" path))
+ (setq full resolved))
(let ((default-directory full))
(unless (zerop (process-file "git" nil nil nil
"rev-parse" "--is-inside-work-tree"))
diff --git a/gptel-tools/move_to_trash.el b/gptel-tools/move_to_trash.el
index 6ea97995..923da790 100644
--- a/gptel-tools/move_to_trash.el
+++ b/gptel-tools/move_to_trash.el
@@ -41,7 +41,7 @@ YYYY-MM-DD-HH-MM-SS."
(let* ((extension (file-name-extension base-name t))
(name-sans-ext (file-name-sans-extension base-name))
(timestamp (format-time-string "%Y-%m-%d-%H-%M-%S"))
- (new-name (if extension
+ (new-name (if (and extension (not (string= extension "")))
(concat name-sans-ext "-" timestamp extension)
(concat base-name "-" timestamp))))
(expand-file-name new-name trash-dir)))))
@@ -51,15 +51,18 @@ YYYY-MM-DD-HH-MM-SS."
Returns the expanded path if valid, signals an error otherwise.
Ensures path is within home directory or /tmp, and prevents
trashing of critical system directories."
- (let ((expanded-path (expand-file-name path))
- (home-dir (expand-file-name "~"))
- (critical-dirs (list (expand-file-name "~")
- (expand-file-name "~/.emacs.d")
- (expand-file-name "~/.config")
- "/tmp")))
+ (let* ((expanded-path (expand-file-name path))
+ (resolved-path (and (file-exists-p expanded-path)
+ (file-truename expanded-path)))
+ (home-dir (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (tmp-dir (file-name-as-directory (file-truename "/tmp")))
+ (critical-dirs (list (directory-file-name home-dir)
+ (file-truename (expand-file-name "~/.emacs.d"))
+ (file-truename (expand-file-name "~/.config"))
+ (directory-file-name tmp-dir))))
;; Security check: must be within allowed directories
(unless (or (string-prefix-p home-dir expanded-path)
- (string-prefix-p "/tmp" expanded-path))
+ (string-prefix-p tmp-dir expanded-path))
(error "Path must be within home directory or /tmp: %s" path))
;; Prevent trashing critical directories
@@ -70,6 +73,10 @@ trashing of critical system directories."
(unless (file-exists-p expanded-path)
(error "File or directory does not exist: %s" path))
+ (unless (or (string-prefix-p home-dir resolved-path)
+ (string-prefix-p tmp-dir resolved-path))
+ (error "Resolved path must be within home directory or /tmp: %s" path))
+
expanded-path))
(defun gptel--move-to-trash-perform (expanded-path trash-dir)
diff --git a/gptel-tools/read_buffer.el b/gptel-tools/read_buffer.el
index 1b4fc904..c9136e3c 100644
--- a/gptel-tools/read_buffer.el
+++ b/gptel-tools/read_buffer.el
@@ -14,7 +14,9 @@ error when no live buffer matches."
(unless (buffer-live-p (get-buffer buffer))
(error "Buffer %s is not live" buffer))
(with-current-buffer buffer
- (buffer-substring-no-properties (point-min) (point-max))))
+ (save-restriction
+ (widen)
+ (buffer-substring-no-properties (point-min) (point-max)))))
(gptel-make-tool
:name "read_buffer"
diff --git a/gptel-tools/read_text_file.el b/gptel-tools/read_text_file.el
index 8e0433a9..f35c9494 100644
--- a/gptel-tools/read_text_file.el
+++ b/gptel-tools/read_text_file.el
@@ -25,19 +25,21 @@
;; Helper functions for read_text_file tool
(defun cj/validate-file-path (path)
"Validate PATH is within home directory and exists."
- (let ((full-path (expand-file-name path "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full-path (expand-file-name path "~")))
(unless (string-prefix-p (expand-file-name "~") full-path)
(error "Path must be within home directory"))
(unless (file-exists-p full-path)
(error "File not found: %s" full-path))
- (when (file-directory-p full-path)
- (error "Path is a directory, not a file: %s" full-path))
- (unless (file-readable-p full-path)
- (error "No read permission for file: %s" full-path))
- ;; Follow symlinks
- (if (file-symlink-p full-path)
- (file-truename full-path)
- full-path)))
+ (let ((resolved (file-truename full-path)))
+ (unless (or (string= resolved (directory-file-name home))
+ (string-prefix-p home resolved))
+ (error "Resolved path must be within home directory: %s" path))
+ (when (file-directory-p resolved)
+ (error "Path is a directory, not a file: %s" resolved))
+ (unless (file-readable-p resolved)
+ (error "No read permission for file: %s" resolved))
+ resolved)))
(defun cj/get-file-metadata (path)
"Return formatted metadata string for file at PATH."
diff --git a/gptel-tools/update_text_file.el b/gptel-tools/update_text_file.el
index 492ed554..f8b58025 100644
--- a/gptel-tools/update_text_file.el
+++ b/gptel-tools/update_text_file.el
@@ -40,20 +40,23 @@
PATH must resolve inside the user's home directory, must exist, must
be a regular file, and must be readable and writable."
- (let ((full (expand-file-name path "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full (expand-file-name path "~")))
(unless (string-prefix-p (expand-file-name "~") full)
(error "Path must be within home directory: %s" path))
(unless (file-exists-p full)
(error "File not found: %s" full))
- (when (file-directory-p full)
- (error "Path is a directory, not a file: %s" full))
- (unless (file-readable-p full)
- (error "No read permission for file: %s" full))
- (unless (file-writable-p full)
- (error "No write permission for file: %s" full))
- (if (file-symlink-p full)
- (file-truename full)
- full)))
+ (let ((resolved (file-truename full)))
+ (unless (or (string= resolved (directory-file-name home))
+ (string-prefix-p home resolved))
+ (error "Resolved path must be within home directory: %s" path))
+ (when (file-directory-p resolved)
+ (error "Path is a directory, not a file: %s" resolved))
+ (unless (file-readable-p resolved)
+ (error "No read permission for file: %s" resolved))
+ (unless (file-writable-p resolved)
+ (error "No write permission for file: %s" resolved))
+ resolved)))
(defun cj/update-text-file--backup-name (path)
"Return a backup filename for PATH timestamped to the current second."
@@ -113,9 +116,10 @@ on out-of-range LINE-NUM or empty TEXT."
;; extra empty element at the end. Trim it so the line count
;; matches what a human would say.
(trailing-newline (string-suffix-p "\n" content))
- (line-count (if trailing-newline
- (1- (length lines))
- (length lines))))
+ (line-count (cond
+ ((string-empty-p content) 0)
+ (trailing-newline (1- (length lines)))
+ (t (length lines)))))
(when (> line-num (1+ line-count))
(error "Line %d out of range (file has %d lines)" line-num line-count))
(let* ((to-insert (if (string-suffix-p "\n" text)
diff --git a/gptel-tools/web_fetch.el b/gptel-tools/web_fetch.el
index 1f950a31..b2f80c5f 100644
--- a/gptel-tools/web_fetch.el
+++ b/gptel-tools/web_fetch.el
@@ -62,7 +62,7 @@ from the response status line, or nil when the line is unrecognized."
(let* ((status (when (re-search-forward
"^HTTP/[0-9.]+ \\([0-9]+\\)" (point-max) t)
(string-to-number (match-string 1))))
- (body-start (when (re-search-forward "\n\n" nil t)
+ (body-start (when (re-search-forward "\r?\n\r?\n" nil t)
(point))))
(cons status
(if body-start
diff --git a/gptel-tools/write_text_file.el b/gptel-tools/write_text_file.el
index 40482c66..1bda5446 100644
--- a/gptel-tools/write_text_file.el
+++ b/gptel-tools/write_text_file.el
@@ -22,9 +22,22 @@
(defun cj/write-text-file--validate-path (path)
"Validate PATH for write. Return the expanded path on success.
PATH must resolve inside the user's home directory."
- (let ((full (expand-file-name path "~")))
+ (let* ((home (file-name-as-directory (file-truename (expand-file-name "~"))))
+ (full (expand-file-name path "~"))
+ (existing (and (file-exists-p full) (file-truename full)))
+ (parent (file-name-directory full))
+ (resolved-parent (and parent
+ (file-exists-p parent)
+ (file-truename parent))))
(unless (string-prefix-p (expand-file-name "~") full)
(error "Path must be within home directory: %s" path))
+ (when (and existing
+ (not (string-prefix-p home existing)))
+ (error "Resolved path must be within home directory: %s" path))
+ (when (and resolved-parent
+ (not (or (string= resolved-parent (directory-file-name home))
+ (string-prefix-p home resolved-parent))))
+ (error "Resolved parent must be within home directory: %s" path))
full))
(defun cj/write-text-file--backup-name (path)