trusted-files.el 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. ;;; trusted-files.el --- Simplistic security for Eglot and auto-complete. -*- lexical-binding: t -*-
  2. ;;; Commentary:
  3. ;;; Code:
  4. (require 'cl-lib)
  5. (require 'cus-edit)
  6. (require 'keymap)
  7. (eval-and-compile
  8. (defconst trusted-files-generated-function-name-prefix "trusted-files--"
  9. "Prefix to append to generated functions.
  10. This is used by `trusted-files-add-hook-if-safe' and
  11. `trusted-files-mark-function-unsafe'. Note that these two functions are
  12. actually macros, so if you change this (which you probably shouldn't do), code
  13. that uses these will need to be recompiled.")
  14. (defconst trusted-files-hook-function-name-suffix
  15. "@trusted-files-hook-if-safe"
  16. "Suffix to append to function names in `trusted-files-add-hook-if-safe'.
  17. Note that `trusted-files-add-hook-if-safe' is a macro, so if this value is
  18. changed (which you probably shouldn't do), code that calls
  19. `trusted-files-add-hook-if-safe' will need to be recompiled.")
  20. (defconst trusted-files-advice-function-name-suffix
  21. "@trusted-files-advice-if-safe"
  22. "Suffix to append to function names in `trusted-files-mark-function-unsafe'.
  23. Note that `trusted-files-mark-function-unsafe' is a macro, so if this value is
  24. changed (which you probably shouldn't do), code that calls
  25. `trusted-files-mark-function-unsafe' will need to be recompiled. "))
  26. (defgroup trusted-files nil
  27. "Simplistic security for Eglot, auto-complete, etc."
  28. :group 'files
  29. :prefix "trusted-files-")
  30. (defcustom trusted-files-truename-trusted-directories t
  31. "If non-nil, use the `file-truename' of for entries in `trusted-files-list'.
  32. Note that this does not affect the current file, see
  33. `trusted-files-truename-current-directory' for that."
  34. :group 'trusted-files
  35. :tag "Resolve Symbolic Links for Trusted Directories"
  36. :type 'boolean
  37. :risky t)
  38. (defcustom trusted-files-truename-current-directory t
  39. "If non-nil, use the `file-truename' of the current file when checking safety.
  40. If this is nil, each link to a directory must individually be in
  41. `trusted-files-list' to be considered safe. Note that this does _NOT_ effect
  42. the entries in `trusted-files-list', only the current buffer's path."
  43. :group 'trusted-files
  44. :tag "Resolve Symbolic Links for the Current Directory"
  45. :type 'boolean
  46. :risky t)
  47. (defun trusted-files--remove-extra-path-parts (path)
  48. "Remove extra path parts from PATH.
  49. This removes \".\" and \"..\" components. The difference between this and
  50. `expand-file-name' is that this will not return things like \"/..\"."
  51. (let ((expanded (expand-file-name path)))
  52. (while (string-prefix-p "/.." expanded)
  53. (setq expanded (substring expanded 3))
  54. (unless (string-prefix-p "/" expanded)
  55. (setq expanded (concat "/" expanded))))
  56. expanded))
  57. (defun trusted-files--resolve-trusted-directory (path &optional leave-slash)
  58. "Resolve PATH, which is resolved according to user settings.
  59. If `trusted-files-truename-trusted-directories' is set, return the
  60. `file-truename' of PATH. In any case, remove \".\" and \"..\" components from
  61. PATH and make it absolute.
  62. With LEAVE-SLASH, only return a path with a trialing slash if PATH has a
  63. trailing slash."
  64. (unless leave-slash
  65. (cond
  66. ((file-directory-p path) (setq path (file-name-as-directory path)))
  67. ((file-exists-p path) (setq path (directory-file-name path)))))
  68. (if trusted-files-truename-trusted-directories
  69. (file-truename path)
  70. (trusted-files--remove-extra-path-parts path)))
  71. (defsubst trusted-files--resolve-current-directory (path &optional leave-slash)
  72. "Resolve PATH, which is resolved according to user settings.
  73. If `trusted-files-truename-current-directory' is set, return the `file-truename'
  74. of PATH. In any case, remove \".\" and \"..\" components from PATH and make it
  75. absolute.
  76. With LEAVE-SLASH, only return a path with a trialing slash if PATH has a
  77. trailing slash."
  78. (unless leave-slash
  79. (cond
  80. ((file-directory-p path) (setq path (file-name-as-directory path)))
  81. ((file-exists-p path) (setq path (directory-file-name path)))))
  82. (if trusted-files-truename-current-directory
  83. (file-truename path)
  84. (trusted-files--remove-extra-path-parts path)))
  85. (defun trusted-files--validate-only-allow-absolute-paths (widget)
  86. "Custom validation function to only allow WIDGET to contain absolute paths."
  87. (let ((path (widget-value widget)))
  88. (unless (and (stringp path) (file-name-absolute-p path))
  89. (widget-put widget :error "Path must be absolute")
  90. widget)))
  91. (defun trusted-files--custom-set-value (sym val)
  92. "Set SYM (probably `trusted-files-list') to the alist VAL.
  93. This parses the alist VAL and converts it to a hash table, resolving entries as
  94. necessary."
  95. (let ((table (make-hash-table :test 'equal)))
  96. (dolist (entry val)
  97. (cl-destructuring-bind (dir . type) entry
  98. (when (file-name-absolute-p dir)
  99. (let* ((resolved (trusted-files--resolve-trusted-directory dir))
  100. (current (gethash resolved table)))
  101. ;; only add a new entry if another entry with a more specific rule
  102. ;; does not exist
  103. (unless (eq current t)
  104. (puthash resolved type table))))))
  105. (set-default-toplevel-value sym table)))
  106. (defun trusted-files--custom-get-value (sym)
  107. "Convert SYM (probably `trusted-files-list') to an alist."
  108. (let ((table (default-toplevel-value sym))
  109. out)
  110. (maphash (lambda (dir type)
  111. (push (cons dir type) out))
  112. table)
  113. out))
  114. (defcustom trusted-files-list ()
  115. "List of directories that should be considered safe.
  116. This is actually a hash table. The keys are trusted paths and the values are
  117. how they are trusted. If the value is \\='subdir, that directory and all of its
  118. subdirectories are trusted. Any other non-nil value mean only trust that
  119. directory and its direct children. If the path is a file, either value means to
  120. trust only that file.
  121. The format of the paths is fairly specific. Thus, you probably should not
  122. modify this directly. Use `trusted-files-add' and `trusted-files-remove' to add
  123. a specific path. If you want to set this to some value, use `setopt' or
  124. `customize-save-variable' to set it. In this case, you will need to pass an
  125. alist with the cars being the directory and the cdrs being either \\='dir or
  126. \\='subdir. Note that in this case, relative paths will be IGNORED. That is,
  127. they will be removed before this is set. Resolve any relative paths before
  128. passing them to `setopt'."
  129. :group 'trusted-files
  130. :tag "Trusted Directories"
  131. :type '(repeat
  132. (cons :tag "Entry"
  133. (directory
  134. :tag "Directory"
  135. :validate trusted-files--validate-only-allow-absolute-paths)
  136. (choice :tag "Also Trust Subdirectories"
  137. (const :tag "Yes" subdir)
  138. (const :tag "No" dir))))
  139. :set #'trusted-files--custom-set-value
  140. :get #'trusted-files--custom-get-value
  141. :risky t)
  142. (defcustom trusted-files-show-in-modeline 'dynamic-temporary-untrusted
  143. "How to show the current buffer's trusted status in the mode line.
  144. There are three possible values:
  145. - t: always show the status
  146. - \\='untrusted: show the status if the current buffer is untrusted
  147. - \\='dynamic: as above, but only if a protected function tried to run
  148. - \\='dynamic-untrusted: as above, but only if the function failed
  149. - \\='dynamic-temporary: save as \\='dynamic, but also show when the buffer is
  150. temporarily trusted
  151. - \\='dynamic-temporary-untrusted: as above, but only if a function failed
  152. To completely disabled display of the trusted status, disable
  153. `trusted-files-modeline-mode'."
  154. :group 'tusted-dirs
  155. :tag "Show Trusted State in Modeline"
  156. :type '(choice (const :tag "Always" t)
  157. (const :tag "If Untrusted" untrusted)
  158. (const :tag "Dynamic" dynamic)
  159. (const :tag "Dynamic if Untrusted" dynamic-untrusted)
  160. (const :tag "Dynamic (or Temporary)" dynamic-temporary)
  161. (const :tag "Dynamic if Untrusted (or Temporary)"
  162. dynamic-temporary-untrusted))
  163. :set (lambda (sym val)
  164. (set-default-toplevel-value sym val)
  165. (force-mode-line-update t))
  166. :risky t)
  167. (defcustom trusted-files-modeline-ignored-buffer-rules
  168. '((trusted-files-normal-buffer-p . t))
  169. "List of rules matching buffers in which to skip drawing the mode line.
  170. Each element in this list is:
  171. - a cons with the cdr being t and the car being a regexp
  172. - a cons with the cdr being nil and the car being a literal buffer name
  173. - a cons with a cdr of nil and the car being a function of one argument that
  174. takes a buffer (not its name) and returns non-nil if that buffer should be
  175. ignored.
  176. - as above, but with a cdr of t. In this case, the function should return
  177. non-nil if the buffer should have SHOWN. That is, the inverse of above."
  178. :group 'trusted-files
  179. :tag "Mode Line Ignored Buffer Rules"
  180. :type '(repeat (choice (cons :tag "String Pattern"
  181. (string :tag "Pattern")
  182. (boolean :tag "Use Regexp"))
  183. (cons :tag "Predicate Function"
  184. (function :tag "Function")
  185. (boolean :tag "Negated"))))
  186. :risky t)
  187. (defun trusted-files--eshell-buffer-p (buffer)
  188. "Return non-nil if BUFFER is an `eshell' buffer."
  189. (with-current-buffer buffer
  190. (derived-mode-p 'eshell-mode)))
  191. (defun trusted-files--scratch-buffer-p (buffer)
  192. "Return non-nil if BUFFER is a `scratch-buffer' buffer."
  193. (and (equal (buffer-name buffer) "*scratch*")
  194. (not (buffer-file-name buffer))))
  195. (defcustom trusted-files-always-trusted-buffer-functions
  196. '(minibufferp trusted-files--eshell-buffer-p trusted-files--scratch-buffer-p)
  197. "A list of functions that are called to test if the current buffer is safe.
  198. When a buffer is tested for safety (via `trusted-files-safe-p'), this hook is
  199. run. If any function returns non-nil, the current buffer is considered safe
  200. without any additional checks."
  201. :group 'trusted-files
  202. :tag "Always Trusted Buffer Predicates"
  203. :type '(repeat (function :tag "Predicate"))
  204. :risky t)
  205. (defface trusted-files-trusted-modeline-face
  206. '((t))
  207. "Face for the trusted notification string in the mode line."
  208. :group 'trusted-files
  209. :tag "Mode Line Trusted Notification Face")
  210. (defface trusted-files-temporary-modeline-face
  211. '((t . (:inherit warning)))
  212. "Face for the temporarily trusted notification string in the mode line."
  213. :group 'trusted-files
  214. :tag "Mode Line Temporarily Trusted Notification Face")
  215. (defface trusted-files-untrusted-modeline-face
  216. '((t . (:inherit error)))
  217. "Face for the untrusted notification string in the mode line."
  218. :group 'trusted-files
  219. :tag "Mode Line Untrusted Notification Face")
  220. (defvar-local trusted-files--did-protected-function-run nil
  221. "Non-nil if a protected function tried to run in the current buffer.")
  222. ;;;###autoload (put 'trusted-files--did-protected-function-run 'risky-local-variable t)
  223. (defvar-local trusted-files--did-protected-function-fail nil
  224. "Non-nil if a protected function failed to run in the current buffer.")
  225. ;;;###autoload (put 'trusted-files--did-protected-function-fail 'risky-local-variable t)
  226. (defvar-local trusted-files--saved-buffer-name nil
  227. "Internal variable used by `trusted-files-safe-p'.
  228. This might not be accurate to the buffers current name.")
  229. ;;;###autoload (put 'trusted-files--saved-buffer-name 'risky-local-variable t)
  230. (defvar trusted-files--temporarily-trusted-cache (make-hash-table :test 'equal)
  231. "Hash table of temporarily trusted directories and buffers.
  232. Each key is a directory or buffer. In the case of a buffer, any non-nil values
  233. means that the buffer is trusted. In the case of a directory, the key is one of
  234. the following:
  235. - t: this directory is trusted
  236. - \\='subdir: this directory and its subdirectories are trusted
  237. Entries are removed from this list by
  238. `trusted-files--cleanup-temporary-trusted-cache', which is called from
  239. `kill-buffer-hook'.")
  240. ;;;###autoload (put 'trusted-files--temporarily-trusted-cache 'risky-local-variable t)
  241. (defun trusted-files--hide-modeline-in-buffer-p (&optional buffer)
  242. "Return non-nil if the mode line component should be hidden in BUFFER.
  243. BUFFER defaults to the current buffer. For an explanation of how this is
  244. decided, see `trusted-files-modeline-ignored-buffer-rules'."
  245. (unless buffer (setq buffer (current-buffer)))
  246. (cl-loop for entry in trusted-files-modeline-ignored-buffer-rules
  247. when (pcase entry
  248. (`(,(cl-type string) . nil)
  249. (equal (car entry) (buffer-name buffer)))
  250. (`(,(cl-type string) . t)
  251. (string-match-p (car entry)
  252. (buffer-name buffer)))
  253. (`(,(cl-type function) . ,negate)
  254. (xor (funcall (car entry) buffer) negate)))
  255. return t))
  256. (defun trusted-files--modeline-string ()
  257. "Return the trusted-files mode line string for the current buffer.
  258. To change when this is shown, customize `trusted-files-show-in-modeline'."
  259. (let* ((safe (car (trusted-files-safe-p nil t)))
  260. (temporary (car (memq safe
  261. '(temp-buffer temp-dir temp-subdir)))))
  262. (and (not (trusted-files--hide-modeline-in-buffer-p))
  263. (or (eq trusted-files-show-in-modeline t)
  264. (and temporary
  265. (memq trusted-files-show-in-modeline
  266. '(dynamic-temporary dynamic-temporary-untrusted)))
  267. (and (not safe) (eq trusted-files-show-in-modeline 'untrusted))
  268. (and trusted-files--did-protected-function-run
  269. (memq trusted-files-show-in-modeline
  270. '(dynamic dynamic-temporary)))
  271. (and trusted-files--did-protected-function-fail
  272. (memq trusted-files-show-in-modeline
  273. '(dynamic-untrusted dynamic-temporary-untrusted))))
  274. (list
  275. (cond
  276. (temporary
  277. `(:propertize ,(format "Temp. Trusted %s"
  278. (cl-case temporary
  279. (temp-buffer "(B)")
  280. (temp-dir "(D)")
  281. (temp-subdir "(S)")))
  282. face trusted-files-temporary-modeline-face
  283. mouse-face mode-line-highlight
  284. help-echo
  285. ,(cl-case temporary
  286. (temp-buffer
  287. "This buffer is temp. trusted. Click to untrust.")
  288. (temp-dir
  289. "This directory is temp. trusted. Click to untrust it.")
  290. (temp-subdir
  291. "A parent directory is temp. trusted. Click to untrust it."))
  292. keymap
  293. (mode-line keymap
  294. (mouse-1 . trusted-files-remove-temporary-current-buffer))))
  295. (safe '(:propertize "Trusted"
  296. face trusted-files-trusted-modeline-face
  297. help-echo
  298. (cl-case safe
  299. (dir "This buffer's directory (not a parent) is trusted.")
  300. (subdir "A parent directory of this buffer is trusted."))))
  301. (t '(:propertize "Untrusted"
  302. face trusted-files-untrusted-modeline-face
  303. help-echo "This buffer is untrusted.")))
  304. " "))))
  305. ;;;###autoload
  306. (define-minor-mode trusted-files-modeline-mode
  307. "Minor mode for showing current buffer's trusted status in the mode line."
  308. :group 'trusted-files
  309. :global t
  310. :lighter nil
  311. (let ((item '(:eval (trusted-files--modeline-string))))
  312. (if trusted-files-modeline-mode
  313. (add-to-list 'global-mode-string item)
  314. (setq global-mode-string (remove item global-mode-string))))
  315. (force-mode-line-update))
  316. ;;;###autoload
  317. (defun trusted-files-normal-buffer-p (&optional buffer)
  318. "Return non-nil if BUFFER (or the current buffer) is a normal buffer.
  319. A buffer is normal if is not hidden and it's name does not start and end with
  320. asterisks."
  321. (unless buffer (setq buffer (current-buffer)))
  322. (and (not (string-prefix-p " " (buffer-name buffer)))
  323. (not (string-match-p "\\`\\*.*\\*\\'" (buffer-name buffer)))))
  324. (defun trusted-files--subdirectory-p (parent child &optional no-resolve)
  325. "Return non-nil if CHILD is a subdirectory of PARENT.
  326. This will resolve both PARENT and CHILD with
  327. `trusted-files--resolve-current-directory', unless NO-RESOLVED is non-nil."
  328. (unless no-resolve
  329. (setq parent (trusted-files--resolve-current-directory parent)
  330. child (trusted-files--resolve-current-directory child)))
  331. (or (equal parent "/")
  332. (equal (directory-file-name parent)
  333. (directory-file-name child))
  334. (and (equal parent (file-name-as-directory parent))
  335. (string-prefix-p (file-name-as-directory parent) child))))
  336. (defun trusted-files--buffer-path (&optional buffer)
  337. "Return the path of BUFFER.
  338. BUFFER defaults to the current buffer."
  339. (unless buffer (setq buffer (current-buffer)))
  340. (if-let ((file (buffer-file-name buffer)))
  341. (trusted-files--resolve-current-directory file)
  342. (file-name-as-directory (trusted-files--resolve-current-directory
  343. (buffer-local-value 'default-directory buffer)))))
  344. (defun trusted-files--path-and-parents (path &optional no-resolve)
  345. "Return a list of PATH and each of its parent directories.
  346. Unless NO-RESOLVE, resolve PATH with `trusted-files--resolve-current-directory'."
  347. (cl-loop with start = (if no-resolve
  348. path
  349. (trusted-files--resolve-current-directory path))
  350. for prev = nil then cur
  351. for cur = start then (file-name-directory
  352. (directory-file-name cur))
  353. while (not (equal prev cur))
  354. collect cur))
  355. (defun trusted-files--buffer-path-and-parents (&optional buffer)
  356. "Return a list of the path of BUFFER and each of its parent directories.
  357. BUFFER defaults to the current buffer."
  358. (trusted-files--path-and-parents (trusted-files--buffer-path buffer)))
  359. (defsubst trusted-files--file-names-directory-p (path)
  360. "Return non-nil if PATH names a directory.
  361. On U*IX-like systems, this probably just checks if PATH ends with a slash."
  362. (equal path (file-name-as-directory path)))
  363. (defun trusted-files--same-file-or-direct-descendant-p
  364. (parent child &optional no-resolve)
  365. "Return non-nil if CHILD is a direct descendant of PARENT.
  366. That is, return non-nil if PARENT and CHILD are the same path or if PARENT is a
  367. directory and CHILD is a direct descendant.
  368. Unless NO-RESOLVE is set, resolve both PARENT and CHILD with
  369. `trusted-files--resolve-current-directory'."
  370. (unless no-resolve
  371. (setq parent (trusted-files--resolve-current-directory parent)
  372. child (trusted-files--resolve-current-directory child)))
  373. (or (equal parent child)
  374. (and (trusted-files--file-names-directory-p parent)
  375. (string-prefix-p parent child)
  376. (not (cl-position ?/ (substring (directory-file-name child)
  377. (length parent)))))))
  378. (defun trusted-files--find-buffers
  379. (path &optional subdir-too special-too resolved)
  380. "Return a list of buffers that visit PATH or a direct descendant of PATH.
  381. If SUBDIR-TOO is set, also search for subdirectories of PATH. If SPECIAL-TOO is
  382. set, also consider buffers that are special. Otherwise, only consider regular,
  383. visible, file-visiting buffers.
  384. Unless RESOLVED is set, resolve PATH with
  385. `trusted-files--resolve-current-directory'."
  386. (unless resolved (setq path (trusted-files--resolve-current-directory path)))
  387. (let (out)
  388. (dolist (buffer (buffer-list) out)
  389. (when (or special-too (trusted-files-normal-buffer-p buffer))
  390. (let ((target-dir (trusted-files--buffer-path buffer)))
  391. (when (or (and subdir-too (trusted-files--subdirectory-p
  392. path target-dir))
  393. (trusted-files--same-file-or-direct-descendant-p
  394. path target-dir t))
  395. (push buffer out)))))))
  396. (defun trusted-files--cleanup-temporary-trusted-cache ()
  397. "Cleanup `trusted-files--temporarily-trusted-cache'."
  398. (remhash (current-buffer) trusted-files--temporarily-trusted-cache)
  399. (cl-loop for cur in (trusted-files--buffer-path-and-parents)
  400. for rule = (gethash cur trusted-files--temporarily-trusted-cache)
  401. when (and rule (null (delq (current-buffer)
  402. (trusted-files--find-buffers
  403. cur (eq rule 'subdir)))))
  404. collect cur into steps
  405. and do (remhash cur trusted-files--temporarily-trusted-cache)
  406. finally do
  407. (when steps
  408. (message "Untrusted %s" (trusted-files--pprint-list steps)))))
  409. (add-hook 'kill-buffer-hook #'trusted-files--cleanup-temporary-trusted-cache)
  410. (defun trusted-files--buffer-temporarily-trusted-p (buffer)
  411. "Return non-nil if BUFFER is temprarily trusted.
  412. This checks both BUFFER and BUFFER's parent directory.
  413. Return a cons. For the car if the BUFFER is trusted, return \\='temp-buffer. If
  414. BUFFER's parent directory is exactly trusted, return \\='temp-dir. If a higher
  415. up parent directory of it is trusted, return \\='temp-subdir. For the cdr,
  416. return the directory that matched, or the BUFFER it itself matched."
  417. (or
  418. (and (gethash buffer trusted-files--temporarily-trusted-cache)
  419. (cons 'temp-buffer buffer))
  420. (cl-loop for cur in (trusted-files--buffer-path-and-parents buffer)
  421. for i upfrom 0
  422. for result = (gethash cur trusted-files--temporarily-trusted-cache)
  423. ;; direct parent (or exact match)
  424. when (and result (< i 2)) return (cons 'temp-dir cur)
  425. ;; other parent
  426. when (eq result 'subdir) return (cons 'temp-subdir cur))))
  427. (defun trusted-files--permanently-trusted-p (path &optional resolved)
  428. "Return non-nil if PATH is in `trusted-files-list'.
  429. This will resolve PATH with `trusted-files--resolve-current-directory' unless
  430. RESOLVED is non-nil.
  431. Return a cons. For the car if PATH is trusted, return \\='file. If PATH's
  432. direct parent is trusted, return \\='dir, If another parent directory of PATH is
  433. trusted, return \\='subdir. For the cdr, return the directory that matched."
  434. (cl-loop for cur in (trusted-files--path-and-parents path resolved)
  435. for i upfrom 0
  436. for result = (gethash cur trusted-files-list)
  437. ;; exact match
  438. when (and result (zerop i)) return (cons 'file cur)
  439. ;; direct parent
  440. when (and result (= i 1)) return (cons 'dir cur)
  441. ;; otherwise, other parent
  442. when (eq result 'subdir) return (cons 'subdir cur)))
  443. (defun trusted-files--always-trusted-buffer-p (buffer)
  444. "Return non-nil if BUFFER is an always trusted buffer.
  445. This calls each function in `trusted-files-always-trusted-buffer-functions'
  446. until one of them return non-nil. If none of them does, this return nil.
  447. Otherwise, it returns a cons of the symbol \\='buffer and BUFFER."
  448. (when (run-hook-with-args-until-success
  449. 'trusted-files-always-trusted-buffer-functions buffer)
  450. (cons 'buffer buffer)))
  451. (defun trusted-files-safe-p (&optional buffer no-modify)
  452. "Return non-nil if BUFFER is considered safe.
  453. BUFFER defaults to the current buffer. Also, if BUFFER is unsafe, set
  454. `trusted-files--did-protected-function-fail' to t unless NO-MODIFY is non-nil.
  455. In any case, set `trusted-files--did-protected-function-run' to t unless
  456. NO-MODIFY is non-nil.
  457. This can return a few different things depending on how BUFFER is trusted. See
  458. `trusted-files--permanently-trusted-p',
  459. `trusted-files--always-trusted-buffer-p', and
  460. `trusted-files--buffer-temporarily-trusted-p' for a list of possible return
  461. values."
  462. (unless buffer (setq buffer (current-buffer)))
  463. (let ((path (trusted-files--buffer-path buffer)))
  464. (unless no-modify
  465. (setq trusted-files--did-protected-function-run t))
  466. (let ((result (or (trusted-files--always-trusted-buffer-p buffer)
  467. (trusted-files--permanently-trusted-p path)
  468. (trusted-files--buffer-temporarily-trusted-p buffer))))
  469. (unless (or no-modify result)
  470. (setq trusted-files--did-protected-function-fail t))
  471. (unless no-modify
  472. (when (and trusted-files--saved-buffer-name
  473. (not (equal path trusted-files--saved-buffer-name)))
  474. (trusted-files--maybe-prompt-revert-newly-trusted-buffers
  475. (list buffer)))
  476. (setq trusted-files--saved-buffer-name path)
  477. (force-mode-line-update))
  478. result)))
  479. (defun trusted-files--visible-buffer-list ()
  480. "Return a list of all visible buffers.
  481. A buffer is coincided visible if it's name does not start with a space."
  482. (cl-delete-if (lambda (buf)
  483. (string-prefix-p " " (buffer-name buf)))
  484. (buffer-list)))
  485. (defun trusted-files--pprint-buffer-name (buffer)
  486. "Return a string which can represent BUFFER when prompting the user."
  487. (if-let ((path (buffer-file-name buffer))
  488. (file (file-name-nondirectory path)))
  489. (if (equal file (buffer-name buffer))
  490. file
  491. (format "%s (buffer %s)" file (buffer-name buffer)))
  492. (buffer-name buffer)))
  493. (defun trusted-files-outdated-trust-information-p (&optional buffer)
  494. "Return non-nil if BUFFER has outdated trust information.
  495. See `trusted-files-reload-newly-trusted-buffers' for an explanation of when a
  496. buffer might have outdated trust information.
  497. If BUFFER is nil, default to the current buffer;"
  498. (with-current-buffer (or buffer (current-buffer))
  499. (let ((safe (trusted-files-safe-p nil t)))
  500. (or (and safe trusted-files--did-protected-function-fail)
  501. (and (not safe) trusted-files--did-protected-function-run
  502. (not trusted-files--did-protected-function-fail))))))
  503. (cl-defun trusted-files--outdated-buffer-list (&optional (buffers (buffer-list)))
  504. "Return a list of buffers that have outdated trust information.
  505. See `trusted-files-reload-newly-trusted-buffers' for an explanation of when a
  506. buffer might have outdated trust information.
  507. If BUFFERS is passed, only consider buffers in that list. Otherwise, consider
  508. all live buffers (even special and hidden ones)."
  509. (cl-remove-if-not #'trusted-files-outdated-trust-information-p buffers))
  510. (defun trusted-files--princ-to-string (object)
  511. "Return the output resulting from calling `princ' on OBJECT."
  512. (with-output-to-string
  513. (princ object standard-output)))
  514. (defun trusted-files--pprint-list (items &optional formatter no-oxford-comma)
  515. "Pretty print ITEMS, a list of things.
  516. Each item will be converted to a string, using FORMATTER, before being printed.
  517. If FORMATTER is nil, use `trusted-files--princ-to-string'. The FORMATTER must
  518. take a single argument, the item to format, and return a string.
  519. With NO-OXFORD-COMMA, don't insert an Oxford comma."
  520. (unless formatter (setq formatter #'trusted-files--princ-to-string))
  521. (let ((len (length items)))
  522. (cl-case len
  523. (0 "")
  524. (1 (funcall formatter (car items)))
  525. (2 (concat (funcall formatter (cl-first items))
  526. " and "
  527. (funcall formatter (cl-second items))))
  528. (t (cl-loop for i upfrom 1
  529. for item in items
  530. when (/= len i)
  531. concat (funcall formatter item)
  532. and concat (if (and no-oxford-comma
  533. (= i (1- len)))
  534. " "
  535. ", ")
  536. else
  537. concat "and "
  538. and concat (funcall formatter item))))))
  539. ;;;###autoload
  540. (cl-defun trusted-files-revert-newly-trusted-buffers
  541. (&optional force silent (buffers (trusted-files--visible-buffer-list)))
  542. "Revert all buffers that have outdated trust information.
  543. A buffer is considered to have outdated trust information if:
  544. - it is marked as having a had a function fail, even though it is trusted
  545. - it is marked as having had no function fail, even though it is untrusted
  546. By default this prompts the user to save any buffers before reverting them. If
  547. the user says no to saving a buffer, skip it. With FORCE, don't ask the user
  548. anything and (possibly destructively) revert all buffers.
  549. Unless SILENT is non-nil, `message' the user with a list of each revered buffer.
  550. By default, revert all live buffers. To only check some buffers, pass a list of
  551. buffers in BUFFERS."
  552. (interactive)
  553. (let (reverted)
  554. (dolist (buffer (trusted-files--outdated-buffer-list buffers))
  555. (with-current-buffer buffer
  556. (if (not (buffer-file-name))
  557. (when (or force
  558. (and (buffer-modified-p)
  559. (yes-or-no-p
  560. (format "DISCARD CHANGES and revert %s?"
  561. (trusted-files--pprint-buffer-name
  562. buffer)))))
  563. (revert-buffer nil t)
  564. (push buffer reverted)))
  565. (when (and (not force)
  566. (buffer-modified-p)
  567. (y-or-n-p (format "Save and revert %s?"
  568. (trusted-files--pprint-buffer-name buffer))))
  569. (save-buffer))
  570. (when (or force (not (buffer-modified-p)))
  571. (revert-buffer nil t)
  572. (push buffer reverted))))
  573. (when (and (not silent) reverted)
  574. (message
  575. "Reverted buffer%s %s"
  576. (if (length= reverted 1) "" "s")
  577. (trusted-files--pprint-list reverted
  578. #'trusted-files--pprint-buffer-name)))))
  579. (cl-defun trusted-files--maybe-prompt-revert-newly-trusted-buffers
  580. (&optional (buffers (trusted-files--outdated-buffer-list
  581. (trusted-files--visible-buffer-list))))
  582. "If there are buffers with outdated trust, prompt the user to revert them.
  583. For a definition of what qualifies as a buffer with outdated trust, see
  584. `trusted-files-revert-newly-trusted-buffers'.
  585. With BUFFERS, only consider those buffers."
  586. (and buffers (y-or-n-p "Buffers with outdated trust detected! Revert?")
  587. (trusted-files-revert-newly-trusted-buffers nil nil buffers)))
  588. ;;;###autoload
  589. (defun trusted-files-add (path &optional no-recursive no-revert)
  590. "Mark PATH as a trusted file.
  591. If NO-RECURSIVE is non-nil, don't trust any subdirectories of PATH.
  592. Interactively, prompt for PATH. With a prefix argument, set NO-RECURSIVE.
  593. By default, this calls asks the user if they want to run
  594. `trusted-files-revert-newly-trusted-buffers'. If NO-REVERT is set, don't ask or
  595. call it.
  596. PATH is processed according to `trusted-files-truename-trusted-directories'."
  597. (interactive "fTrust File: \nP")
  598. (let ((resolved (trusted-files--resolve-trusted-directory path)))
  599. (puthash resolved (if no-recursive
  600. t
  601. 'subdir)
  602. trusted-files-list)
  603. (customize-save-variable
  604. 'trusted-files-list (trusted-files--custom-get-value 'trusted-files-list))
  605. ;; Now that resolved is permanently trusted, we can remove it from
  606. ;; the temporary cache
  607. (remhash resolved trusted-files--temporarily-trusted-cache)
  608. (unless no-revert
  609. (message "Added %s to the list of trusted directories"
  610. resolved)
  611. (trusted-files--maybe-prompt-revert-newly-trusted-buffers))))
  612. ;;;###autoload
  613. (defun trusted-files-add-current (&optional no-recursive no-revert)
  614. "Mark the current buffer as a trusted file.
  615. NO-RECURSIVE and NO-REVERT are the same as for `trusted-files-add' (which see)."
  616. (interactive "P")
  617. (trusted-files-add (trusted-files--buffer-path) no-recursive no-revert))
  618. (defun trusted-files--read-trusted-file (&optional prompt)
  619. "Read a trusted directory from the minibuffer with completion.
  620. PROMPT is the prompt to use, defaulting to \"Trusted File: \"."
  621. (completing-read (or prompt "Trusted File: ")
  622. (hash-table-keys trusted-files-list) nil t))
  623. ;;;###autoload
  624. (defun trusted-files-remove (path &optional no-revert)
  625. "Remove PATH from the list of trusted files.
  626. Interactively, prompt for PATH.
  627. By default, this asks the user if they want to run
  628. `trusted-files-revert-newly-trusted-buffers'. If NO-REVERT is set, don't ask or
  629. call it.
  630. PATH is processed according to `trusted-files-truename-trusted-directories'."
  631. (interactive (list (trusted-files--read-trusted-file "Untrust: ")))
  632. (let* ((resolved (trusted-files--resolve-trusted-directory path))
  633. (old-val (gethash resolved trusted-files-list)))
  634. (if (not old-val)
  635. (unless no-revert (message "%s is not trusted" resolved))
  636. (remhash resolved trusted-files-list)
  637. (customize-save-variable
  638. 'trusted-files-list (trusted-files--custom-get-value 'trusted-files-list))
  639. (unless no-revert
  640. (message "Removed %s from the list of trusted directories"
  641. resolved)
  642. (trusted-files--maybe-prompt-revert-newly-trusted-buffers)))))
  643. (defun trusted-files-remove-current (&optional no-revert)
  644. "Remove the current buffer from the list of trusted files.
  645. NO-REVERT is the same as for `trusted-files-remove' (which see)."
  646. (interactive)
  647. (trusted-files-remove (trusted-files--buffer-path) no-revert))
  648. ;;;###autoload
  649. (defun trusted-files-add-temporary-directory
  650. (path &optional no-recursive no-revert)
  651. "Temporarily trust PATH.
  652. PATH will be trusted until _ALL_ buffers that visit files located in PATH are
  653. closed. Unless NO-RECURSIVE is set, also trust
  654. subdirectories of PATH. In this case buffers visiting files in all
  655. subdirectories of PATH will also be trusted, and PATH will not be untrusted
  656. until _ALL_ of these buffers are closed as well.
  657. Unless NO-REVERT is set, prompt the user to call
  658. `trusted-files-revert-newly-trusted-buffers'.
  659. Note that only non-special, visible buffers are considered."
  660. (interactive "DTemporarily Trust: \nP")
  661. (let ((resolved (trusted-files--resolve-trusted-directory path)))
  662. (when (trusted-files--permanently-trusted-p resolved t)
  663. (user-error "%s is already permanently trusted" resolved))
  664. (unless (trusted-files--find-buffers resolved (not no-recursive) nil t)
  665. (user-error "There are no buffers in %s" resolved))
  666. (puthash resolved (if no-recursive t 'subdir)
  667. trusted-files--temporarily-trusted-cache)
  668. (unless no-revert
  669. (message "Temporarily trusted %s" resolved)
  670. (trusted-files--maybe-prompt-revert-newly-trusted-buffers))))
  671. ;;;###autoload
  672. (defun trusted-files-add-temporary-buffer (&optional buffer-or-name no-revert)
  673. "Temporarily trust BUFFER-OR-NAME, defaulting to the current buffer.
  674. The buffer will be trusted until it is closed. If a new buffer visiting the
  675. same file were to be created at a later time, that buffer would not be trusted.
  676. Interactively, prompt for the buffer.
  677. Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
  678. have outdated trust information. For an explanation of what this means, see
  679. `trusted-files-revert-newly-trusted-buffers'."
  680. (interactive "bTemporarily Trust:")
  681. (unless buffer-or-name (setq buffer-or-name (current-buffer)))
  682. (unless (bufferp buffer-or-name)
  683. (setq buffer-or-name (get-buffer buffer-or-name)))
  684. (puthash buffer-or-name t trusted-files--temporarily-trusted-cache)
  685. (unless no-revert
  686. (message "Temporarily trusted %s"
  687. (trusted-files--pprint-buffer-name buffer-or-name))
  688. (when (trusted-files-outdated-trust-information-p buffer-or-name)
  689. (trusted-files--maybe-prompt-revert-newly-trusted-buffers
  690. (list buffer-or-name)))))
  691. (defun trusted-files--filter-temporary-cache (predicate)
  692. "Return anything in the temporary trust cache that matches PREDICATE.
  693. PREDICATE should be a function of one argument. If will be passed each key in
  694. `trusted-files--temporarily-trusted-cache'. It should return non-nil if that
  695. item should be included in the returned set."
  696. (cl-delete-if-not predicate
  697. (hash-table-keys trusted-files--temporarily-trusted-cache)))
  698. (defun trusted-files--read-temporary-directory (&optional prompt)
  699. "Prompt for and return the path of a temporarily trusted directory.
  700. PROMPT defaults to \"Temporarily Trusted Directory: \"."
  701. (completing-read (or prompt "Temporarily Trusted Directory: ")
  702. (trusted-files--filter-temporary-cache 'stringp)
  703. nil t))
  704. ;;;###autoload
  705. (defun trusted-files-remove-temporary-directory (path &optional no-revert)
  706. "Untrust the temporarily trusted directory PATH.
  707. Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
  708. have outdated trust information. For an explanation of what this means, see
  709. `trusted-files-revert-newly-trusted-buffers'."
  710. (interactive (list (trusted-files--read-temporary-directory
  711. "Untrust Directory: ")))
  712. (let ((resolved (trusted-files--resolve-trusted-directory path)))
  713. (remhash resolved trusted-files--temporarily-trusted-cache)
  714. (unless no-revert
  715. (message "Untrusted %s" resolved)
  716. (trusted-files--maybe-prompt-revert-newly-trusted-buffers))))
  717. (defun trusted-files--read-temporary-buffer (&optional prompt)
  718. "Prompt the user for a temporarily trusted buffer and it (not its name).
  719. PROMPT defaults to \"Temporarily Trusted Buffer: \"."
  720. (let ((names (mapcar 'buffer-name
  721. (trusted-files--filter-temporary-cache 'bufferp))))
  722. (get-buffer (read-buffer (or prompt "Temporarily Trusted Buffer: ")
  723. nil t (lambda (buf-name)
  724. (unless (stringp buf-name)
  725. (setq buf-name (car buf-name)))
  726. (member buf-name names))))))
  727. ;;;###autoload
  728. (defun trusted-files-remove-temporary-buffer (&optional buffer-or-name no-revert)
  729. "Untust BUFFER-OR-NAME if it is a temporarily trusted buffer.
  730. If it was trusted, return non-nil, otherwise, return nil. Note that this only
  731. untrusts BUFFER-OR-NAME, and not its directory. For that, see
  732. `trusted-files-remove-temporary-directory'.
  733. Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
  734. have outdated trust information. For an explanation of what this means, see
  735. `trusted-files-revert-newly-trusted-buffers'."
  736. (interactive (list (trusted-files--read-temporary-buffer "Untrust Buffer: ")))
  737. (unless buffer-or-name (setq buffer-or-name (current-buffer)))
  738. (unless (bufferp buffer-or-name)
  739. (setq buffer-or-name (get-buffer buffer-or-name)))
  740. (remhash buffer-or-name trusted-files--temporarily-trusted-cache)
  741. (unless no-revert
  742. (message "Untrusted %s"
  743. (trusted-files--pprint-buffer-name buffer-or-name))
  744. (when (trusted-files-outdated-trust-information-p buffer-or-name)
  745. (trusted-files--maybe-prompt-revert-newly-trusted-buffers
  746. (list buffer-or-name)))))
  747. ;;;###autoload
  748. (defun trusted-files-remove-temporary-current-buffer (&optional no-revert)
  749. "Untrust the current buffer, however it's temporarily trusted. This will
  750. either untrust the current buffer directly, untrust its visited file, or untrust
  751. a parent directory of its such. If need be, it may untrust multiple things.
  752. Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
  753. have outdated trust information. For an explanation of what this means, see
  754. `trusted-files-revert-newly-trusted-buffers'."
  755. (interactive)
  756. (let (steps)
  757. (while-let ((how (cdr (trusted-files--buffer-temporarily-trusted-p
  758. (current-buffer)))))
  759. (push how steps)
  760. (if (stringp how)
  761. (trusted-files-remove-temporary-directory how t)
  762. (trusted-files-remove-temporary-buffer how t)))
  763. (unless no-revert
  764. (message "Untrusted %s"
  765. (trusted-files--pprint-list
  766. (nreverse steps)
  767. (lambda (elt)
  768. (if (stringp elt)
  769. elt
  770. (concat "buffer " (buffer-name elt))))))
  771. (trusted-files--maybe-prompt-revert-newly-trusted-buffers))))
  772. (eval-and-compile
  773. (defun trusted-files--quoted-symbol-p (form)
  774. "Return non-nil if FORM is a quoted symbol.
  775. This returns non-nil if FORM is a proper list of two elements, the first being
  776. the symbol \\='quote or \\='function and the second being a symbol."
  777. (and (memq (car-safe form) '(function quote))
  778. (consp (cdr form))
  779. (symbolp (cadr form))
  780. (null (cddr form)))))
  781. (defmacro trusted-files-only-if-safe (function &optional replacement prefix suffix)
  782. "Return a function that will call FUNCTION if the current buffer is safe. If
  783. REPLACEMENT is non-nil, call it instead of FUNCTION if the current buffer is
  784. unsafe. REPLACEMENT is called with the same arguments that FUNCTION would have
  785. been called with.
  786. If either PREFIX or SUFFIX is a string and FUNCTION is a symbol (this is a
  787. macro, so these must be true at compile time), define a new function named by
  788. concatenating PREFIX, the name of FUNCTION, and SUFFIX."
  789. (let* ((args (make-symbol "args"))
  790. (evaled-prefix (eval prefix t))
  791. (evaled-suffix (eval suffix t))
  792. (do-defun (and (or (stringp evaled-prefix) (stringp evaled-suffix))
  793. (trusted-files--quoted-symbol-p function))))
  794. `(,@(if do-defun
  795. (list 'defun (intern (concat (when (stringp evaled-prefix)
  796. evaled-prefix)
  797. (symbol-name (cl-second function))
  798. (when (stringp evaled-suffix)
  799. evaled-suffix))))
  800. '(lambda))
  801. (&rest ,args)
  802. ,@(when do-defun
  803. (list (format "Execute `%s' when the current buffer is safe.
  804. The safety check is done with `trusted-files-safe-p'."
  805. (cl-second function))))
  806. (require 'trusted-files)
  807. (if (trusted-files-safe-p)
  808. (apply ,function ,args)
  809. ,@(when replacement
  810. (list `(apply ,replacement ,args)))))))
  811. (cl-defmacro trusted-files-add-hook-if-safe
  812. (hook function &optional (depth nil depthp) (local nil localp))
  813. "Like `add-hook', but only when the current buffer is trusted.
  814. This will add FUNCTION to HOOK, initializing it if necessary. DEPTH and LOCAL
  815. are the same as `add-hook' (which see). If FUNCTION is a symbol, it is wrapped
  816. in a new function who's name is formed by concatenating the name of FUNCTION and
  817. `trusted-files-hook-function-name-suffix'."
  818. `(add-hook ,hook (trusted-files-only-if-safe
  819. ,function nil ,trusted-files-generated-function-name-prefix
  820. ,trusted-files-hook-function-name-suffix)
  821. ,@(when depthp
  822. (list depth))
  823. ,@(when localp
  824. (list local))))
  825. (defun trusted-files-remove-hook (hook function &optional local)
  826. "Remove FUNCTION from HOOK if it was added by trusted-files.
  827. This undoes `trusted-files-add-hook-if-safe'. LOCAL is the same for this as for
  828. `add-hook'. This only works if FUNCTION is a symbol."
  829. (cl-check-type function symbol)
  830. (when-let ((wrapped (intern-soft
  831. (format "%s%s%s"
  832. trusted-files-generated-function-name-prefix
  833. (symbol-name function)
  834. trusted-files-hook-function-name-suffix))))
  835. (remove-hook hook wrapped local)))
  836. (eval-and-compile
  837. (defun trusted-files--format-doc-string (format &rest args)
  838. "Call `format' fill the output as a documentation string.
  839. This will call `format' using FORMAT and ARGS. Every paragraph in the output
  840. except the first line will then be filled to a `fill-column' of 80 using
  841. `fill-region'."
  842. (let ((raw-string (apply 'format format args)))
  843. (with-temp-buffer
  844. (insert raw-string)
  845. (goto-char (point-min))
  846. (forward-line)
  847. (fill-individual-paragraphs (point) (point-max))
  848. (buffer-string))))
  849. (defun trusted-files--make-advice-function (target replacement)
  850. "Make `:around' advice for TARGET to only call it in safe directories.
  851. If REPLACEMENT is non-nil, it will be called instead in unsafe directories."
  852. (let ((oldfun (make-symbol "oldfun"))
  853. (args (make-symbol "args"))
  854. (do-defun (trusted-files--quoted-symbol-p target)))
  855. `(,@(if do-defun
  856. `(defun ,(intern
  857. (concat trusted-files-generated-function-name-prefix
  858. (symbol-name (cl-second target))
  859. trusted-files-advice-function-name-suffix)))
  860. '(lambda))
  861. (,oldfun &rest ,args)
  862. ,@(when do-defun
  863. (list
  864. (trusted-files--format-doc-string
  865. "Only call `%s' in safe directories.
  866. This is meant to be used as `:around' advice. The safety check is done with
  867. `trusted-files-safe-p'. If this check fails, %s."
  868. (symbol-name (cl-second target))
  869. (cond
  870. ((trusted-files--quoted-symbol-p replacement)
  871. (concat (symbol-name (cl-second replacement))
  872. " is called instead"))
  873. (replacement
  874. "an anonymous function is called instead.")
  875. (t "nil is returned instead.")))))
  876. (require 'trusted-files)
  877. (if (trusted-files-safe-p)
  878. (apply ,oldfun ,args)
  879. ,@(when replacement
  880. (list `(apply ,replacement ,args))))))))
  881. (defmacro trusted-files-mark-function-unsafe (function &optional replacement)
  882. "Mark FUNCTION as only being runnable in safe directories.
  883. This will add advice to FUNCTION such that it will simply return nil unless the
  884. current directory is safe. If REPLACEMENT is non-nil, it will be run instead of
  885. FUNCTION in unsafe directories. If FUNCTION is a symbol, it is wrapped
  886. in a new function who's name is formed by concatenating the name of FUNCTION and
  887. `trusted-files-advice-function-name-suffix'.
  888. This will attempt to make the advice run before any other advice by giving it a
  889. depth of -100 (see `add-function' for what this means), however, there is
  890. nothing stopping other functions from doing this as well, so care must be taken
  891. that these other pieces of advice do not call potentially unsafe functions."
  892. (let ((advice (trusted-files--make-advice-function function replacement)))
  893. (if (trusted-files--quoted-symbol-p function)
  894. `(advice-add ,function :around ,advice '(:depth -100))
  895. `(add-function :around ,function ,advice '(:depth -100)))))
  896. (defun trusted-files-unmark-function (function)
  897. "Mark FUNCTION as safe for execution in unsafe directories.
  898. This undoes the effects of `trusted-files-mark-function-unsafe'. This only
  899. works if FUNCTION is a symbol.
  900. Note that this is a function and that is a macro. Thus, this will only work if
  901. the values of `trusted-files-generated-function-name-prefix' and
  902. `trusted-files-advice-function-name-suffix' are the same as when
  903. `trusted-files-mark-function-unsafe' was compiled."
  904. (cl-check-type function symbol)
  905. (when-let ((advice (intern-soft
  906. (format "%s%s%s"
  907. trusted-files-generated-function-name-prefix
  908. (symbol-name function)
  909. trusted-files-advice-function-name-suffix))))
  910. (advice-remove function advice)))
  911. ;;; Wrapper functions
  912. (defmacro trusted-files--define-safe-wrapper (function &optional require)
  913. "Define a safe wrapper around FUNCTION.
  914. FUNCTION must be an unquoted symbol (checked at compile time). A new function
  915. will be defined by prefixing FUNCTION's name with \"trusted-files-\" and
  916. suffixing it with \"-if-safe\". If FUNCTION is a command, it will be executed
  917. with `command-execlute'. Otherwilse, will be called with `funcall' and passed no
  918. arguments.
  919. If REQUIRE is non-nil, it should be a symbol that will be passed to `require' if
  920. it is deemed safe to run FUNCTION."
  921. (cl-check-type function symbol)
  922. (let ((args (make-symbol "args"))
  923. (interactive (make-symbol "interactive")))
  924. `(defun ,(intern (concat "trusted-files-" (symbol-name function) "-if-safe"))
  925. (&rest ,args)
  926. ,(format "Call `%s' only if it is safe to do so.
  927. The check if performed with `trusted-files-safe-p'.%s"
  928. function (if (stringp (help-function-arglist nil))
  929. ""
  930. (format "\n\n%s" (cons 'fn (help-function-arglist
  931. function t)))))
  932. (declare (interactive-only ,(format "use `%s' directly instead"
  933. function)))
  934. ,@(when (commandp function)
  935. (list `(interactive nil ,@(command-modes function))))
  936. ;; this comes first to make sure that it is never showed by a macro
  937. ;; wrapping it in `lambda'.
  938. (let ((,interactive (called-interactively-p 'any)))
  939. (require 'trusted-files)
  940. (when (trusted-files-safe-p)
  941. ,@(when require
  942. (list `(require ',require)))
  943. (if ,interactive
  944. (call-interactively #',function)
  945. (apply #',function ,args)))))))
  946. (trusted-files--define-safe-wrapper eglot eglot)
  947. (trusted-files--define-safe-wrapper eglot-ensure eglot)
  948. (trusted-files--define-safe-wrapper flymake-mode flymake)
  949. (trusted-files--define-safe-wrapper flycheck-mode flycheck)
  950. (trusted-files--define-safe-wrapper sly sly)
  951. (trusted-files-mark-function-unsafe #'elisp-completion-at-point)
  952. ;;;###autoload
  953. (defvar-keymap trusted-files-map
  954. :doc "Prefix keymap for working with trusted files."
  955. :prefix 'trusted-files-map
  956. "a" #'trusted-files-add
  957. "A" #'trusted-files-add-current
  958. "r" #'trusted-files-remove
  959. "R" #'trusted-files-remove-current
  960. "b" #'trusted-files-add-temporary-buffer
  961. "B" #'trusted-files-remove-temporary-buffer
  962. "d" #'trusted-files-add-temporary-directory
  963. "D" #'trusted-files-remove-temporary-directory)
  964. (provide 'trusted-files)
  965. ;;; trusted-files.el ends here
  966. ;; Local Variables:
  967. ;; jinx-local-words: "untrust untrusts"
  968. ;; End: