Subject: 26.0.50; Allow non-string keys in password-cache

From: Stefan Monnier <[email-masked]>
Date: Fri, 28 Apr 2017 12:42:40 -0400

Package: Emacs
Version: 26.0.50

I suggest the patch below to make password-cache use a hash-table rather
than an obarray and at the same occasion allow non-string keys, which
simplifies auth-source's code.

Any objection?

Should I add a `password-map` function while I'm at it (alias to
`maphash`) so as to avoid auth-source depending on those internals of
password-cache?

        Stefan

diff --git a/lisp/auth-source.el b/lisp/auth-source.el
index 01d12c2614..798011e6ff 100644
--- a/lisp/auth-source.el
+++ b/lisp/auth-source.el
@@ -200,8 +200,6 @@ auth-source-netrc-use-gpg-tokens
                            (const :tag "Save GPG-encrypted password tokens" gpg)
                            (const :tag "Don't encrypt tokens" never))))))
 
-(defvar auth-source-magic "auth-source-magic ")
-
 (defcustom auth-source-do-cache t
   "Whether auth-source should cache information with `password-cache'."
   :group 'auth-source
@@ -782,16 +780,16 @@ auth-source-netrc-cache
 (defun auth-source-forget-all-cached ()
   "Forget all cached auth-source data."
   (interactive)
-  (cl-do-symbols (sym password-data)
-    ;; when the symbol name starts with auth-source-magic
-    (when (string-match (concat "^" auth-source-magic) (symbol-name sym))
-      ;; remove that key
-      (password-cache-remove (symbol-name sym))))
+  (maphash (lambda (key password)
+             (when (eq 'auth-source (car-safe key))
+               ;; remove that key
+               (password-cache-remove key)))
+           password-data)
   (setq auth-source-netrc-cache nil))
 
 (defun auth-source-format-cache-entry (spec)
   "Format SPEC entry to put it in the password cache."
-  (concat auth-source-magic (format "%S" spec)))
+  `(auth-source . ,spec))
 
 (defun auth-source-remember (spec found)
   "Remember FOUND search results for SPEC."
@@ -824,16 +822,15 @@ auth-source-forget+
 while \(:host t) would find all host entries."
   (let ((count 0)
         sname)
-    (cl-do-symbols (sym password-data)
-      ;; when the symbol name matches with auth-source-magic
-      (when (and (setq sname (symbol-name sym))
-                 (string-match (concat "^" auth-source-magic "\\(.+\\)")
-                               sname)
-                 ;; and the spec matches what was stored in the cache
-                 (auth-source-specmatchp spec (read (match-string 1 sname))))
-        ;; remove that key
-        (password-cache-remove sname)
-        (cl-incf count)))
+    (maphash
+     (lambda (key password)
+       (when (and (eq 'auth-source (car-safe key))
+                  ;; and the spec matches what was stored in the cache
+                  (auth-source-specmatchp spec (cdr key)))
+         ;; remove that key
+         (password-cache-remove key)
+         (cl-incf count)))
+     password-data)
     count))
 
 (defun auth-source-specmatchp (spec stored)
diff --git a/lisp/password-cache.el b/lisp/password-cache.el
index 7be3c6fdb6..cbc248b9ec 100644
--- a/lisp/password-cache.el
+++ b/lisp/password-cache.el
@@ -66,7 +66,7 @@ password-cache-expiry
   :type '(choice (const :tag "Never" nil)
 		 (integer :tag "Seconds")))
 
-(defvar password-data (make-vector 7 0))
+(defvar password-data (make-hash-table :test #'equal))
 
 (defun password-read-from-cache (key)
   "Obtain passphrase for KEY from time-limited passphrase cache.
@@ -74,20 +74,20 @@ password-read-from-cache
 regulate cache behavior."
   (and password-cache
        key
-       (symbol-value (intern-soft key password-data))))
+       (gethash key password-data)))
 
 ;;;###autoload
 (defun password-in-cache-p (key)
   "Check if KEY is in the cache."
   (and password-cache
        key
-       (intern-soft key password-data)))
+       (gethash key password-data)))
 
 (defun password-read (prompt &optional key)
   "Read password, for use with KEY, from user, or from cache if wanted.
 KEY indicate the purpose of the password, so the cache can
-separate passwords.  The cache is not used if KEY is nil.  It is
-typically a string.
+separate passwords.  The cache is not used if KEY is nil.
+KEY is typically a string but can be anything (compared via `equal').
 The variable `password-cache' control whether the cache is used."
   (or (password-read-from-cache key)
       (read-passwd prompt)))
@@ -115,29 +115,27 @@ password-cache-remove
 from the cache.  This may be useful when it has been detected
 that a password is invalid, so that `password-read' query the
 user again."
-  (let ((sym (intern-soft key password-data)))
-    (when sym
-      (let ((password (symbol-value sym)))
-        (when (stringp password)
-          (if (fboundp 'clear-string)
-              (clear-string password)
-            (fillarray password ?_)))
-        (unintern key password-data)))))
+  (let ((password (gethash key password-data)))
+    (when (stringp password)
+      (if (fboundp 'clear-string)
+          (clear-string password)
+        (fillarray password ?_)))
+    (remhash key password-data)))
 
 (defun password-cache-add (key password)
   "Add password to cache.
 The password is removed by a timer after `password-cache-expiry' seconds."
-  (when (and password-cache-expiry (null (intern-soft key password-data)))
+  (when (and password-cache-expiry (null (gethash key password-data)))
     (run-at-time password-cache-expiry nil
 		 #'password-cache-remove
 		 key))
-  (set (intern key password-data) password)
+  (puthash key password password-data)
   nil)
 
 (defun password-reset ()
   "Clear the password cache."
   (interactive)
-  (fillarray password-data 0))
+  (clrhash password-data))
 
 (provide 'password-cache)