diff options
Diffstat (limited to 'lisp/gnus')
-rw-r--r-- | lisp/gnus/ChangeLog | 322 | ||||
-rw-r--r-- | lisp/gnus/ChangeLog.2 | 2 | ||||
-rw-r--r-- | lisp/gnus/auth-source.el | 1408 | ||||
-rw-r--r-- | lisp/gnus/gnus-art.el | 23 | ||||
-rw-r--r-- | lisp/gnus/gnus-delay.el | 4 | ||||
-rw-r--r-- | lisp/gnus/gnus-msg.el | 8 | ||||
-rw-r--r-- | lisp/gnus/gnus-start.el | 24 | ||||
-rw-r--r-- | lisp/gnus/gnus-sum.el | 38 | ||||
-rw-r--r-- | lisp/gnus/gnus-util.el | 9 | ||||
-rw-r--r-- | lisp/gnus/gnus.el | 2 | ||||
-rw-r--r-- | lisp/gnus/mail-source.el | 88 | ||||
-rw-r--r-- | lisp/gnus/message.el | 5 | ||||
-rw-r--r-- | lisp/gnus/mml2015.el | 2 | ||||
-rw-r--r-- | lisp/gnus/nnfolder.el | 6 | ||||
-rw-r--r-- | lisp/gnus/nnimap.el | 178 | ||||
-rw-r--r-- | lisp/gnus/nntp.el | 40 | ||||
-rw-r--r-- | lisp/gnus/shr-color.el | 14 | ||||
-rw-r--r-- | lisp/gnus/sieve-manage.el | 18 |
18 files changed, 1650 insertions, 541 deletions
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog index 8781ab3c0ec..ff48920e69c 100644 --- a/lisp/gnus/ChangeLog +++ b/lisp/gnus/ChangeLog @@ -1,3 +1,224 @@ +2011-02-20 Chong Yidong <cyd@stupidchicken.com> + + * shr-color.el (shr-color->hexadecimal): Use renamed function names + color-rgb-to-hex, color-name-to-rgb, color-srgb-to-lab, and + color-lab-to-srgb. + +2011-02-21 Lars Ingebrigtsen <larsi@gnus.org> + + * nntp.el (nntp-finish-retrieve-group-infos): Add a kludge to use the + given method as in the group name if we're using an extended method. + (nntp-finish-retrieve-group-infos): Wait for the end of the LIST ACTIVE + command, if we're using that, instead of waiting for the beginning. + + * gnus-start.el (gnus-get-unread-articles): Extend the methods so that + we're sure to get unique server names, and we don't output two async + commands in the same buffer. This fixes an NNTP hang for some users. + +2011-02-21 Lars Ingebrigtsen <larsi@gnus.org> + + * gnus-sum.el (gnus-summary-next-article): Add a kludge to reselect the + summary buffer before reading going to the next buffer. This avoids + putting the point in the group buffer if you `C-g' the command. + + * auth-source.el (auth-source-netrc-parse): Add an in-memory netrc + cache (for now) to make ~/.authinfo.gpg files usable. + + * nnfolder.el (copyright-update): Define for the compiler. + + * auth-source.el (auth-source-search): Fix unbound variable. + +2011-02-19 Glenn Morris <rgm@gnu.org> + + * gnus.el (gnus-meta): Doc fix. + +2011-02-19 Chong Yidong <cyd@stupidchicken.com> + + * nnfolder.el (nnfolder-save-buffer): Don't let-bind copyright-update, + in case it's not yet loaded. + +2011-02-20 Lars Ingebrigtsen <larsi@gnus.org> + + * nnimap.el (nnimap-wait-for-response): Ensure that we get the entire + line we're waiting for. + +2011-02-19 Darren Hoo <darren.hoo@gmail.com> (tiny change) + + * gnus-art.el (gnus-article-next-page-1): Because customized mode-line + face with line-width greater than zero will cause RET in gnus summary + buffer to scroll down article page-wise because auto vscroll happens, + it should be temporalily disabled when doing a scroll-up. + +2011-02-19 Lars Ingebrigtsen <larsi@gnus.org> + + * nnimap.el (nnimap-parse-copied-articles): Allow for "<foo> OK" + outputs from the server. + +2011-02-18 Antoine Levitt <antoine.levitt@gmail.com> (tiny change) + + * gnus-art.el (gnus-article-prepare): Run gnus-article-prepare-hook + later so that bbdb can hook in easier. + +2011-02-18 Lars Ingebrigtsen <larsi@gnus.org> + + * auth-source.el (auth-source-search): Don't try to create credentials + if the caller doesn't want that. + (auth-source-search): If we don't find a match, don't bug out on + non-bound variables. + (auth-source-search): Only ask a single backend to create the + credentials. + + * nnimap.el (nnimap-log-command): Add a newline to the inhibited + logging. + (nnimap-credentials): Protect against auth-source-search returning nil. + (nnimap-request-list): Protect against not being able to open the + server. + +2011-02-17 Lars Ingebrigtsen <larsi@gnus.org> + + * auth-source.el (auth-source-search): Do a two-phase search, one with + no :create to get the responses from all backends. + + * nnimap.el (nnimap-open-connection-1): Delete duplicate server names + when getting credentials. + + * gnus-util.el (gnus-delete-duplicates): New function. + +2011-02-17 Teodor Zlatanov <tzz@lifelogs.com> + + * nnimap.el (nnimap-credentials): Instead of picking the first port as + a creation default, pass the whole port list down. It will be + completed. + + * auth-source.el (auth-source-search): Updated docs to talk about + multiple creation choices. + (auth-source-netrc-create): Accept a list as a value (from the search + parameters) and do completion on that list. Keep a separate netrc line + with the password obscured for showing the user. + + * nnimap.el (nnimap-open-connection-1): Make the `nnimap-address' the + first choice to `auth-source-search' so it will be used for entry + creation instead of the server's Gnus-specific name. + (nnimap-credentials): Rely on the auth-source library to select which + port is actually wanted in the new netrc entry, so don't override + `auth-source-creation-defaults'. + + * auth-source.el (auth-source-netrc-parse): Use :port instead of + :protocol and accept a missing user, host, or port as a wildcard match. + (auth-source-debug): Default to off. + + (auth-source-netrc-search, auth-source-netrc-create) + (auth-source-secrets-search, auth-source-secrets-create) + (auth-source-user-or-password, auth-source-backend, auth-sources) + (auth-source-backend-parse-parameters, auth-source-search): Use :port + instead of :protocol. + + * nnimap.el (nnimap-credentials): Pass a port default to + `auth-source-search' in case an entry needs to be created. + (nnimap-open-connection-1): Use :port instead of :protocol. + +2011-02-17 Katsumi Yamaoka <yamaoka@jpl.org> + + * auth-source.el (auth-source-secrets-search): Use mm-delete-duplicates + instead of delete-dups that is not available in XEmacs 21.4. + +2011-02-16 Lars Ingebrigtsen <larsi@gnus.org> + + * gnus-sum.el (gnus-propagate-marks): Change default to t again, since + nil means that nnimap doesn't get updated. + +2011-02-16 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el (auth-source-netrc-create): Return a synthetic search + result when the user doesn't want to write to the file. + (auth-source-netrc-search): Expect a synthetic result and proceed + accordingly. + (auth-source-cache-expiry): New variable to override + `password-cache-expiry'. + (auth-source-remember): Use it. + + * nnimap.el (nnimap-credentials): Remove the `inhibit-create' + parameter. Create entry if necessary by using :create t. + (nnimap-open-connection-1): Don't pass `inhibit-create'. + +2011-02-15 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el (auth-source-debug): Enable by default and don't + mention the obsolete `auth-source-hide-passwords'. + (auth-source-do-warn): New function to debug unconditionally. + (auth-source-do-debug): Use it. + (auth-source-backend-parse): Use it for invalid `auth-sources' entries + and for Secrets API entries when the secrets.el library is not + available. + +2011-02-14 Lars Ingebrigtsen <larsi@gnus.org> + + * gnus-sum.el (gnus-propagate-marks): Default to nil. + (gnus-summary-exit): Kill the correct article buffer on exit from a + `C-d' group. + + * gnus-start.el (gnus-use-backend-marks): Removed, since it duplicates + gnus-propagate-marks. + + * gnus-sum.el (gnus-summary-exit-no-update): Restore the group conf + before killing the buffers so that a non-full window conf gets handled + correctly. + (gnus-summary-exit): Ditto. + (gnus-summary-read-group-1): Ditto. + + * nntp.el (nntp-retrieve-group-data-early): Reinstate the two-part + async code again so that we can debug it properly. + + * message.el (message-reply): Take an optional switch-buffer parameter + so that Gnus window confs are respected better. + +2011-02-14 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el (auth-source-backend-parse-parameters): Don't rely on + `plist-get' to accept non-list parameters (XEmacs issue). Fix + docstring. + (auth-source-secrets-search): Use `delete-dups', `append mapcar', and + `butlast' instead of `remove-duplicates', `mapcan', and `subseq'. + (auth-sources, auth-source-backend-parse, auth-source-secrets-search): + Login collection is "Login" and not "login". + +2011-02-14 Lars Ingebrigtsen <larsi@gnus.org> + + * gnus-art.el (article-update-date-lapsed): Don't bug out when updating + multiple headers. + + * nnimap.el (nnimap-inhibit-logging): New variable. + (nnimap-log-command): Don't log login commands. + + * auth-source.el (auth-source-netrc-search): The asserts seem to want + to have more parameters. + + * nnimap.el (nnimap-send-command): Mark the command time for each + command, so that we don't get NOOPs stepping on our toes. + + * gnus-art.el (article-date-ut): Get the date from the Date header on + `t'. + +2011-02-14 Katsumi Yamaoka <yamaoka@jpl.org> + + * auth-source.el (auth-source-search): Use copy-sequence instead of + the cl.el copy-list. + +2011-02-13 Adam Sjøgren <asjo@koldfront.dk> + + * gnus-delay.el (gnus-delay-article) Fix number of seconds per day. + Improve prompt. + +2011-02-13 Lars Ingebrigtsen <larsi@gnus.org> + + * gnus-art.el (gnus-article-mode-line-format): Remove the article + washing status from the default format. It isn't very informative. + +2011-02-13 Tassilo Horn <tassilo@member.fsf.org> (tiny change) + + * nnimap.el (nnimap-request-accept-article, nnimap-process-quirk): Fix + Gcc processing on imap. + 2011-02-10 Stefan Monnier <monnier@iro.umontreal.ca> * message.el (message-bury): Don't pop up a new window when selected @@ -7,6 +228,30 @@ * gnus-sum.el (gnus-summary-save-parts): Use read-directory-name. +2011-02-10 Teodor Zlatanov <tzz@lifelogs.com> + + * sieve-manage.el: Autoload `auth-source-search'. + (sieve-sasl-auth): Use it. + +2011-02-09 Teodor Zlatanov <tzz@lifelogs.com> + + * nnimap.el: Autoload `auth-source-forget+'. + (nnimap-open-connection-1): Use it if the connection fails. + + * auth-source.el: Require `password-cache'. + (auth-source-hide-passwords, auth-source-cache): Remove and mark + obsolete. + (auth-source-magic): Marker for `password-cache' keys. + (auth-source-do-cache): Update docstring. + (auth-source-search): Use and check cache. + (auth-source-forget-all-cached, auth-source-remember) + (auth-source-recall, auth-source-forget, auth-source-forget+) + (auth-source-specmatchp): Caching support functions. + (auth-source-forget-user-or-password, auth-source-forget-all-cached): + Remove and obsolete. + (auth-source-user-or-password): Remove caching to further discourage + using it. Always hide passwords. + 2011-02-09 Lars Ingebrigtsen <larsi@gnus.org> * nntp.el (nntp-retrieve-group-data-early-disabled): Disable the async @@ -17,6 +262,22 @@ * message.el (message-options): Make message-options really buffer local. +2011-02-08 Teodor Zlatanov <tzz@lifelogs.com> + + * mail-source.el: Autoload `auth-source-search'. + (mail-source-keyword-map): Note order matters. + (mail-source-set-1): Get all the mail-source source values and + defaults and search auth-source on those if needed. This can all + probably be simplified. + + * nnimap.el: Autoload `auth-source-search'. + (nnimap-credentials): Use it. + (nnimap-open-connection-1): Ask for the virtual server and physical + address in one shot. + + * nntp.el: Autoload `auth-source-search'. + (nntp-send-authinfo): Use it. Note TODO. + 2011-02-08 Julien Danjou <julien@danjou.info> * shr.el (shr-tag-body): Add support for text attribute in body @@ -24,6 +285,13 @@ * message.el (message-options): Make message-options a local variable. +2011-02-07 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el (auth-source-secrets-search) + (auth-source-user-or-password): Use `append' instead of `nconc'. + (auth-source-user-or-password): Build return list better and protect + against nil :secret. + 2011-02-07 Lars Ingebrigtsen <larsi@gnus.org> * nnimap.el (nnimap-update-info): Refactor slightly. @@ -35,6 +303,13 @@ (nnimap-update-info): Fix macrology bug-out. (nnimap-update-info): Simplify split history test. +2011-02-06 Michael Albinus <michael.albinus@gmx.de> + + * auth-source.el (top): Require 'eieio unconditionally. Autoload + `secrets-get-attributes' instead of `secrets-get-attribute'. + (auth-source-secrets-search): Limit search when `max' is greater than + number of results. + 2011-02-06 Lars Ingebrigtsen <larsi@gnus.org> * nntp.el (nntp-finish-retrieve-group-infos): Protect against the first @@ -42,11 +317,58 @@ * proto-stream.el (open-protocol-stream): Document the return value. +2011-02-06 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el (auth-source-secrets-search): Add examples. + 2011-02-06 Julien Danjou <julien@danjou.info> * message.el (message-setup-1): Handle message-generate-headers-first set to t. +2011-02-06 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el (auth-sources): Allow for simpler defaults for Secrets + API with a string "secrets:collection-name" and with 'default. + (auth-source-backend-parse): Parse "secrets:collection-name" and + 'default. Recurse on parses instead of repeating code. Use the + Secrets API is the source is not nil and 'ignore otherwise. Emit a + message when ignoring a source. + (auth-source-search): List ignored search keys at the top level. + (auth-source-netrc-create): Use `case' instead of `cond'. + (auth-source-secrets-search): Created with TODOs. + (auth-source-secrets-create): Created with TODOs. + (auth-source-retrieve, auth-source-create, auth-source-delete) + (auth-source-protocol-defaults, auth-source-user-or-password-imap) + (auth-source-user-or-password-pop3, auth-source-user-or-password-ssh) + (auth-source-user-or-password-sftp) + (auth-source-user-or-password-smtp): Removed. + (auth-source-user-or-password): Deprecated and modified to be a wrapper + around `auth-source-search'. Not tested thoroughly. + +2011-02-04 Teodor Zlatanov <tzz@lifelogs.com> + + * auth-source.el: Bring in assoc and eioeio libraries. + (secrets-enabled): New variable to track the status of the Secrets API. + (auth-source-backend): New EIOEIO class to represent a backend. + (auth-source-creation-defaults): New variable to set prompt defaults + during token creation (see the `auth-source-search' docstring for + details). + (auth-sources): Simplify to allow a simple string as a netrc backend + spec. + (auth-source-backend-parse): Parse a backend from an `auth-sources' spec. + (auth-source-backend-parse-parameters): Fill in the backend parameters. + (auth-source-search): Main auth-source API entry point. + (auth-source-delete): Wrapper around `auth-source-search' for deletion. + (auth-source-search-collection): Helper function for searching. + (auth-source-netrc-parse, auth-source-netrc-normalize) + (auth-source-netrc-search, auth-source-netrc-create): Netrc backend. + Supports search, create, and delete. + (auth-source-secrets-search, auth-source-secrets-create): Secrets API + backend stubs. + (auth-source-user-or-password): Call `auth-source-search' but it's not + ready yet. + 2011-02-04 Lars Ingebrigtsen <larsi@gnus.org> * message.el (message-setup-1): Remove the read-only stuff, since it diff --git a/lisp/gnus/ChangeLog.2 b/lisp/gnus/ChangeLog.2 index 381ae544b24..4882032f284 100644 --- a/lisp/gnus/ChangeLog.2 +++ b/lisp/gnus/ChangeLog.2 @@ -6088,7 +6088,7 @@ (nntp-retrieve-groups): Ditto for groups. (nntp-retrieve-articles): Ditto for articles. (*): Replaced nntp-possibly-change-group calls to - nntp-with-open-group forms in all, but one, occurrance. + nntp-with-open-group forms in all, but one, occurrence. (nntp-accept-process-output): Bug fix. Detect when called with null process. diff --git a/lisp/gnus/auth-source.el b/lisp/gnus/auth-source.el index e94cfb137b0..e033b01ae97 100644 --- a/lisp/gnus/auth-source.el +++ b/lisp/gnus/auth-source.el @@ -39,23 +39,77 @@ ;;; Code: +(require 'password-cache) +(require 'mm-util) (require 'gnus-util) (require 'netrc) - +(require 'assoc) (eval-when-compile (require 'cl)) +(require 'eieio) + (autoload 'secrets-create-item "secrets") (autoload 'secrets-delete-item "secrets") (autoload 'secrets-get-alias "secrets") -(autoload 'secrets-get-attribute "secrets") +(autoload 'secrets-get-attributes "secrets") (autoload 'secrets-get-secret "secrets") (autoload 'secrets-list-collections "secrets") (autoload 'secrets-search-items "secrets") +(defvar secrets-enabled) + (defgroup auth-source nil "Authentication sources." :version "23.1" ;; No Gnus :group 'gnus) +;;;###autoload +(defcustom auth-source-cache-expiry 7200 + "How many seconds passwords are cached, or nil to disable +expiring. Overrides `password-cache-expiry' through a +let-binding." + :group 'auth-source + :type '(choice (const :tag "Never" nil) + (const :tag "All Day" 86400) + (const :tag "2 Hours" 7200) + (const :tag "30 Minutes" 1800) + (integer :tag "Seconds"))) + +(defclass auth-source-backend () + ((type :initarg :type + :initform 'netrc + :type symbol + :custom symbol + :documentation "The backend type.") + (source :initarg :source + :type string + :custom string + :documentation "The backend source.") + (host :initarg :host + :initform t + :type t + :custom string + :documentation "The backend host.") + (user :initarg :user + :initform t + :type t + :custom string + :documentation "The backend user.") + (port :initarg :port + :initform t + :type t + :custom string + :documentation "The backend protocol.") + (create-function :initarg :create-function + :initform ignore + :type function + :custom function + :documentation "The create function.") + (search-function :initarg :search-function + :initform ignore + :type function + :custom function + :documentation "The search function."))) + (defcustom auth-source-protocols '((imap "imap" "imaps" "143" "993") (pop3 "pop3" "pop" "pop3s" "110" "995") (ssh "ssh" "22") @@ -81,23 +135,28 @@ p))) auth-source-protocols)) -(defvar auth-source-cache (make-hash-table :test 'equal) - "Cache for auth-source data") +(defvar auth-source-creation-defaults nil + "Defaults for creating token values. Usually let-bound.") + +(make-obsolete 'auth-source-hide-passwords nil "Emacs 24.1") + +(defvar auth-source-magic "auth-source-magic ") (defcustom auth-source-do-cache t - "Whether auth-source should cache information." + "Whether auth-source should cache information with `password-cache'." :group 'auth-source :version "23.2" ;; No Gnus :type `boolean) (defcustom auth-source-debug nil "Whether auth-source should log debug messages. -Also see `auth-source-hide-passwords'. If the value is nil, debug messages are not logged. -If the value is t, debug messages are logged with `message'. - In that case, your authentication data will be in the - clear (except for passwords, which are always stripped out). + +If the value is t, debug messages are logged with `message'. In +that case, your authentication data will be in the clear (except +for passwords). + If the value is a function, debug messages are logged by calling that function using the same arguments as `message'." :group 'auth-source @@ -108,65 +167,71 @@ If the value is a function, debug messages are logged by calling (function :tag "Function that takes arguments like `message'") (const :tag "Don't log anything" nil))) -(defcustom auth-source-hide-passwords t - "Whether auth-source should hide passwords in log messages. -Only relevant if `auth-source-debug' is not nil." - :group 'auth-source - :version "23.2" ;; No Gnus - :type `boolean) - -(defcustom auth-sources '((:source "~/.authinfo.gpg") - (:source "~/.authinfo")) +(defcustom auth-sources '("~/.authinfo.gpg" "~/.authinfo") "List of authentication sources. -The default will get login and password information from a .gpg -file, which you should set up with the EPA/EPG packages to be -encrypted. See the auth.info manual for details. +The default will get login and password information from +\"~/.authinfo.gpg\", which you should set up with the EPA/EPG +packages to be encrypted. If that file doesn't exist, it will +try the unencrypted version \"~/.authinfo\". + +See the auth.info manual for details. Each entry is the authentication type with optional properties. It's best to customize this with `M-x customize-variable' because the choices can get pretty complex." :group 'auth-source - :version "23.2" ;; No Gnus + :version "24.1" ;; No Gnus :type `(repeat :tag "Authentication Sources" - (list :tag "Source definition" - (const :format "" :value :source) - (choice :tag "Authentication backend choice" - (string :tag "Authentication Source (file)") - (list :tag "secrets.el (Secret Service API/KWallet/GNOME Keyring)" - (const :format "" :value :secrets) - (choice :tag "Collection to use" - (string :tag "Collection name") - (const :tag "Default" 'default) - (const :tag "Login" "login") - (const :tag "Temporary" "session")))) - (repeat :tag "Extra Parameters" :inline t - (choice :tag "Extra parameter" - (list :tag "Host (omit to match as a fallback)" - (const :format "" :value :host) - (choice :tag "Host (machine) choice" - (const :tag "Any" t) - (regexp :tag "Host (machine) regular expression"))) - (list :tag "Protocol (omit to match as a fallback)" - (const :format "" :value :protocol) - (choice :tag "Protocol" - (const :tag "Any" t) - ,@auth-source-protocols-customize)) - (list :tag "User (omit to match as a fallback)" :inline t - (const :format "" :value :user) - (choice :tag "Personality or username" - (const :tag "Any" t) - (string :tag "Specific user name")))))))) + (choice + (string :tag "Just a file") + (const :tag "Default Secrets API Collection" 'default) + (const :tag "Login Secrets API Collection" "secrets:Login") + (const :tag "Temp Secrets API Collection" "secrets:session") + (list :tag "Source definition" + (const :format "" :value :source) + (choice :tag "Authentication backend choice" + (string :tag "Authentication Source (file)") + (list + :tag "Secret Service API/KWallet/GNOME Keyring" + (const :format "" :value :secrets) + (choice :tag "Collection to use" + (string :tag "Collection name") + (const :tag "Default" 'default) + (const :tag "Login" "Login") + (const + :tag "Temporary" "session")))) + (repeat :tag "Extra Parameters" :inline t + (choice :tag "Extra parameter" + (list + :tag "Host" + (const :format "" :value :host) + (choice :tag "Host (machine) choice" + (const :tag "Any" t) + (regexp + :tag "Regular expression"))) + (list + :tag "Protocol" + (const :format "" :value :port) + (choice + :tag "Protocol" + (const :tag "Any" t) + ,@auth-source-protocols-customize)) + (list :tag "User" :inline t + (const :format "" :value :user) + (choice :tag "Personality/Username" + (const :tag "Any" t) + (string :tag "Name"))))))))) (defcustom auth-source-gpg-encrypt-to t "List of recipient keys that `authinfo.gpg' encrypted to. If the value is not a list, symmetric encryption will be used." :group 'auth-source - :version "23.2" ;; No Gnus + :version "24.1" ;; No Gnus :type '(choice (const :tag "Symmetric encryption" t) - (repeat :tag "Recipient public keys" - (string :tag "Recipient public key")))) + (repeat :tag "Recipient public keys" + (string :tag "Recipient public key")))) ;; temp for debugging ;; (unintern 'auth-source-protocols) @@ -184,255 +249,919 @@ If the value is not a list, symmetric encryption will be used." ;; (auth-source-user-or-password-imap "password" "imap.myhost.com") ;; (auth-source-protocol-defaults 'imap) -;; (let ((auth-source-debug 'debug)) (auth-source-debug "hello")) -;; (let ((auth-source-debug t)) (auth-source-debug "hello")) -;; (let ((auth-source-debug nil)) (auth-source-debug "hello")) +;; (let ((auth-source-debug 'debug)) (auth-source-do-debug "hello")) +;; (let ((auth-source-debug t)) (auth-source-do-debug "hello")) +;; (let ((auth-source-debug nil)) (auth-source-do-debug "hello")) (defun auth-source-do-debug (&rest msg) - ;; set logger to either the function in auth-source-debug or 'message - ;; note that it will be 'message if auth-source-debug is nil, so - ;; we also check the value (when auth-source-debug - (let ((logger (if (functionp auth-source-debug) - auth-source-debug - 'message))) - (apply logger msg)))) - -;; (auth-source-pick nil :host "any" :protocol 'imap :user "joe") -;; (auth-source-pick t :host "any" :protocol 'imap :user "joe") -;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") -;; (:source (:secrets "session") :host t :protocol t :user "joe") -;; (:source (:secrets "login") :host t :protocol t) -;; (:source "~/.authinfo.gpg" :host t :protocol t))) - -;; (setq auth-sources '((:source (:secrets default) :host t :protocol t :user "joe") -;; (:source (:secrets "session") :host t :protocol t :user "joe") -;; (:source (:secrets "login") :host t :protocol t) + (apply 'auth-source-do-warn msg))) + +(defun auth-source-do-warn (&rest msg) + (apply + ;; set logger to either the function in auth-source-debug or 'message + ;; note that it will be 'message if auth-source-debug is nil + (if (functionp auth-source-debug) + auth-source-debug + 'message) + msg)) + + +;; (auth-source-pick nil :host "any" :port 'imap :user "joe") +;; (auth-source-pick t :host "any" :port 'imap :user "joe") +;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe") +;; (:source (:secrets "session") :host t :port t :user "joe") +;; (:source (:secrets "Login") :host t :port t) +;; (:source "~/.authinfo.gpg" :host t :port t))) + +;; (setq auth-sources '((:source (:secrets default) :host t :port t :user "joe") +;; (:source (:secrets "session") :host t :port t :user "joe") +;; (:source (:secrets "Login") :host t :port t) ;; )) -;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :protocol t))) - -(defun auth-get-source (entry) - "Return the source string of ENTRY, which is one entry in `auth-sources'. -If it is a Secret Service API, return the collection name, otherwise -the file name." - (let ((source (plist-get entry :source))) - (if (stringp source) - source - ;; Secret Service API. - (setq source (plist-get source :secrets)) - (when (eq source 'default) - (setq source (or (secrets-get-alias "default") "login"))) - (or source "session")))) - -(defun auth-source-pick (&rest spec) - "Parse `auth-sources' for matches of the SPEC plist. - -Common keys are :host, :protocol, and :user. A value of t in -SPEC means to always succeed in the match. A string value is -matched as a regex." - (let ((keys (loop for i below (length spec) by 2 collect (nth i spec))) - choices) - (dolist (choice (copy-tree auth-sources) choices) - (let ((source (plist-get choice :source)) - (match t)) - (when - (and - ;; Check existence of source. - (if (consp source) - ;; Secret Service API. - (member (auth-get-source choice) (secrets-list-collections)) - ;; authinfo file. - (file-exists-p source)) - - ;; Check keywords. - (dolist (k keys match) - (let* ((v (plist-get spec k)) - (choicev (if (plist-member choice k) - (plist-get choice k) t))) - (setq match - (and match - (or - ;; source always matches spec key - (eq t choicev) - ;; source key gives regex to match against spec - (and (stringp choicev) (string-match choicev v)) - ;; source key gives symbol to match against spec - (and (symbolp choicev) (eq choicev v)))))))) - - (add-to-list 'choices choice 'append)))))) - -(defun auth-source-retrieve (mode entry &rest spec) - "Retrieve MODE credentials according to SPEC from ENTRY." - (catch 'no-password - (let ((host (plist-get spec :host)) - (user (plist-get spec :user)) - (prot (plist-get spec :protocol)) - (source (plist-get entry :source)) - result) - (cond - ;; Secret Service API. - ((consp source) - (let ((coll (auth-get-source entry)) - item) - ;; Loop over candidates with a matching host attribute. - (dolist (elt (secrets-search-items coll :host host) item) - (when (and (or (not user) - (string-equal - user (secrets-get-attribute coll elt :user))) - (or (not prot) - (string-equal - prot (secrets-get-attribute coll elt :protocol)))) - (setq item elt) - (return elt))) - ;; Compose result. - (when item - (setq result - (mapcar (lambda (m) - (if (string-equal "password" m) - (or (secrets-get-secret coll item) - ;; When we do not find a password, - ;; we return nil anyway. - (throw 'no-password nil)) - (or (secrets-get-attribute coll item :user) - user))) - (if (consp mode) mode (list mode))))) - (if (consp mode) result (car result)))) - ;; Anything else is netrc. - (t - (let ((search (list source (list host) (list (format "%s" prot)) - (auth-source-protocol-defaults prot)))) - (setq result - (mapcar (lambda (m) - (if (string-equal "password" m) - (or (apply - 'netrc-machine-user-or-password m search) - ;; When we do not find a password, we - ;; return nil anyway. - (throw 'no-password nil)) - (or (apply - 'netrc-machine-user-or-password m search) - user))) - (if (consp mode) mode (list mode))))) - (if (consp mode) result (car result))))))) - -(defun auth-source-create (mode entry &rest spec) - "Create interactively credentials according to SPEC in ENTRY. -Return structure as specified by MODE." - (let* ((host (plist-get spec :host)) - (user (plist-get spec :user)) - (prot (plist-get spec :protocol)) - (source (plist-get entry :source)) - (name (concat (if user (format "%s@" user)) - host - (if prot (format ":%s" prot)))) - result) - (setq result - (mapcar - (lambda (m) - (cons - m - (cond - ((equal "password" m) - (let ((passwd (read-passwd - (format "Password for %s on %s: " prot host)))) - (cond - ;; Secret Service API. - ((consp source) - (apply - 'secrets-create-item - (auth-get-source entry) name passwd spec)) - (t)) ;; netrc not implemented yes. - passwd)) - ((equal "login" m) - (or user - (read-string - (format "User name for %s on %s (default %s): " prot host - (user-login-name)) - nil nil (user-login-name)))) - (t - "unknownuser")))) - (if (consp mode) mode (list mode)))) - ;; Allow the source to save the data. - (cond - ((consp source) - ;; Secret Service API -- not implemented. - ) - (t - ;; netrc interface. - (when (y-or-n-p (format "Do you want to save this password in %s? " - source)) - ;; the code below is almost same as `netrc-store-data' except - ;; the `epa-file-encrypt-to' hack (see bug#7487). - (with-temp-buffer - (when (file-exists-p source) - (insert-file-contents source)) - (when auth-source-gpg-encrypt-to - ;; making `epa-file-encrypt-to' local to this buffer lets - ;; epa-file skip the key selection query (see the - ;; `local-variable-p' check in `epa-file-write-region'). - (unless (local-variable-p 'epa-file-encrypt-to (current-buffer)) - (make-local-variable 'epa-file-encrypt-to)) - (if (listp auth-source-gpg-encrypt-to) - (setq epa-file-encrypt-to auth-source-gpg-encrypt-to))) - (goto-char (point-max)) - (unless (bolp) - (insert "\n")) - (insert (format "machine %s login %s password %s port %s\n" - host - (or user (cdr (assoc "login" result))) - (cdr (assoc "password" result)) - prot)) - (write-region (point-min) (point-max) source nil 'silent))))) - (if (consp mode) - (mapcar #'cdr result) - (cdar result)))) - -(defun auth-source-delete (entry &rest spec) - "Delete credentials according to SPEC in ENTRY." - (let ((host (plist-get spec :host)) - (user (plist-get spec :user)) - (prot (plist-get spec :protocol)) - (source (plist-get entry :source))) - (cond - ;; Secret Service API. - ((consp source) - (let ((coll (auth-get-source entry))) - ;; Loop over candidates with a matching host attribute. - (dolist (elt (secrets-search-items coll :host host)) - (when (and (or (not user) - (string-equal - user (secrets-get-attribute coll elt :user))) - (or (not prot) - (string-equal - prot (secrets-get-attribute coll elt :protocol)))) - (secrets-delete-item coll elt))))) - (t)))) ;; netrc not implemented yes. - -(defun auth-source-forget-user-or-password - (mode host protocol &optional username) - "Remove cached authentication token." - (interactive "slogin/password: \nsHost: \nsProtocol: \n") ;for testing - (remhash - (if username - (format "%s %s:%s %s" mode host protocol username) - (format "%s %s:%s" mode host protocol)) - auth-source-cache)) +;; (setq auth-sources '((:source "~/.authinfo.gpg" :host t :port t))) + +;; (auth-source-backend-parse "myfile.gpg") +;; (auth-source-backend-parse 'default) +;; (auth-source-backend-parse "secrets:Login") + +(defun auth-source-backend-parse (entry) + "Creates an auth-source-backend from an ENTRY in `auth-sources'." + (auth-source-backend-parse-parameters + entry + (cond + ;; take 'default and recurse to get it as a Secrets API default collection + ;; matching any user, host, and protocol + ((eq entry 'default) + (auth-source-backend-parse '(:source (:secrets default)))) + ;; take secrets:XYZ and recurse to get it as Secrets API collection "XYZ" + ;; matching any user, host, and protocol + ((and (stringp entry) (string-match "^secrets:\\(.+\\)" entry)) + (auth-source-backend-parse `(:source (:secrets ,(match-string 1 entry))))) + ;; take just a file name and recurse to get it as a netrc file + ;; matching any user, host, and protocol + ((stringp entry) + (auth-source-backend-parse `(:source ,entry))) + + ;; a file name with parameters + ((stringp (plist-get entry :source)) + (auth-source-backend + (plist-get entry :source) + :source (plist-get entry :source) + :type 'netrc + :search-function 'auth-source-netrc-search + :create-function 'auth-source-netrc-create)) + + ;; the Secrets API. We require the package, in order to have a + ;; defined value for `secrets-enabled'. + ((and + (not (null (plist-get entry :source))) ; the source must not be nil + (listp (plist-get entry :source)) ; and it must be a list + (require 'secrets nil t) ; and we must load the Secrets API + secrets-enabled) ; and that API must be enabled + + ;; the source is either the :secrets key in ENTRY or + ;; if that's missing or nil, it's "session" + (let ((source (or (plist-get (plist-get entry :source) :secrets) + "session"))) + + ;; if the source is a symbol, we look for the alias named so, + ;; and if that alias is missing, we use "Login" + (when (symbolp source) + (setq source (or (secrets-get-alias (symbol-name source)) + "Login"))) + + (if (featurep 'secrets) + (auth-source-backend + (format "Secrets API (%s)" source) + :source source + :type 'secrets + :search-function 'auth-source-secrets-search + :create-function 'auth-source-secrets-create) + (auth-source-do-warn + "auth-source-backend-parse: no Secrets API, ignoring spec: %S" entry) + (auth-source-backend + (format "Ignored Secrets API (%s)" source) + :source "" + :type 'ignore)))) + + ;; none of them + (t + (auth-source-do-warn + "auth-source-backend-parse: invalid backend spec: %S" entry) + (auth-source-backend + "Empty" + :source "" + :type 'ignore))))) + +(defun auth-source-backend-parse-parameters (entry backend) + "Fills in the extra auth-source-backend parameters of ENTRY. +Using the plist ENTRY, get the :host, :port, and :user search +parameters." + (let ((entry (if (stringp entry) + nil + entry)) + val) + (when (setq val (plist-get entry :host)) + (oset backend host val)) + (when (setq val (plist-get entry :user)) + (oset backend user val)) + (when (setq val (plist-get entry :port)) + (oset backend port val))) + backend) + +;; (mapcar 'auth-source-backend-parse auth-sources) + +(defun* auth-source-search (&rest spec + &key type max host user port secret + create delete + &allow-other-keys) + "Search or modify authentication backends according to SPEC. + +This function parses `auth-sources' for matches of the SPEC +plist. It can optionally create or update an authentication +token if requested. A token is just a standard Emacs property +list with a :secret property that can be a function; all the +other properties will always hold scalar values. + +Typically the :secret property, if present, contains a password. + +Common search keys are :max, :host, :port, and :user. In +addition, :create specifies how tokens will be or created. +Finally, :type can specify which backend types you want to check. + +A string value is always matched literally. A symbol is matched +as its string value, literally. All the SPEC values can be +single values (symbol or string) or lists thereof (in which case +any of the search terms matches). + +:create t means to create a token if possible. + +A new token will be created if no matching tokens were found. +The new token will have only the keys the backend requires. For +the netrc backend, for instance, that's the user, host, and +port keys. + +Here's an example: + +\(let ((auth-source-creation-defaults '((user . \"defaultUser\") + (A . \"default A\")))) + (auth-source-search :host \"mine\" :type 'netrc :max 1 + :P \"pppp\" :Q \"qqqq\" + :create t)) + +which says: + +\"Search for any entry matching host 'mine' in backends of type + 'netrc', maximum one result. + + Create a new entry if you found none. The netrc backend will + automatically require host, user, and port. The host will be + 'mine'. We prompt for the user with default 'defaultUser' and + for the port without a default. We will not prompt for A, Q, + or P. The resulting token will only have keys user, host, and + port.\" + +:create '(A B C) also means to create a token if possible. + +The behavior is like :create t but if the list contains any +parameter, that parameter will be required in the resulting +token. The value for that parameter will be obtained from the +search parameters or from user input. If any queries are needed, +the alist `auth-source-creation-defaults' will be checked for the +default prompt. + +Here's an example: + +\(let ((auth-source-creation-defaults '((user . \"defaultUser\") + (A . \"default A\")))) + (auth-source-search :host '(\"nonesuch\" \"twosuch\") :type 'netrc :max 1 + :P \"pppp\" :Q \"qqqq\" + :create '(A B Q))) + +which says: + +\"Search for any entry matching host 'nonesuch' + or 'twosuch' in backends of type 'netrc', maximum one result. + + Create a new entry if you found none. The netrc backend will + automatically require host, user, and port. The host will be + 'nonesuch' and Q will be 'qqqq'. We prompt for A with default + 'default A', for B and port with default nil, and for the + user with default 'defaultUser'. We will not prompt for Q. The + resulting token will have keys user, host, port, A, B, and Q. + It will not have P with any value, even though P is used in the + search to find only entries that have P set to 'pppp'.\" + +When multiple values are specified in the search parameter, the +user is prompted for which one. So :host (X Y Z) would ask the +user to choose between X, Y, and Z. + +This creation can fail if the search was not specific enough to +create a new token (it's up to the backend to decide that). You +should `catch' the backend-specific error as usual. Some +backends (netrc, at least) will prompt the user rather than throw +an error. + +:delete t means to delete any found entries. nil by default. +Use `auth-source-delete' in ELisp code instead of calling +`auth-source-search' directly with this parameter. + +:type (X Y Z) will check only those backend types. 'netrc and +'secrets are the only ones supported right now. + +:max N means to try to return at most N items (defaults to 1). +When 0 the function will return just t or nil to indicate if any +matches were found. More than N items may be returned, depending +on the search and the backend. + +:host (X Y Z) means to match only hosts X, Y, or Z according to +the match rules above. Defaults to t. + +:user (X Y Z) means to match only users X, Y, or Z according to +the match rules above. Defaults to t. + +:port (P Q R) means to match only protocols P, Q, or R. +Defaults to t. + +:K (V1 V2 V3) for any other key K will match values V1, V2, or +V3 (note the match rules above). + +The return value is a list with at most :max tokens. Each token +is a plist with keys :backend :host :port :user, plus any other +keys provided by the backend (notably :secret). But note the +exception for :max 0, which see above. + +The token's :secret key can hold a function. In that case you +must call it to obtain the actual value." + (let* ((backends (mapcar 'auth-source-backend-parse auth-sources)) + (max (or max 1)) + (ignored-keys '(:create :delete :max)) + (keys (loop for i below (length spec) by 2 + unless (memq (nth i spec) ignored-keys) + collect (nth i spec))) + (found (auth-source-recall spec)) + filtered-backends accessor-key found-here goal matches backend) + + (if (and found auth-source-do-cache) + (auth-source-do-debug + "auth-source-search: found %d CACHED results matching %S" + (length found) spec) + + (assert + (or (eq t create) (listp create)) t + "Invalid auth-source :create parameter (must be nil, t, or a list): %s %s") + + (setq filtered-backends (copy-sequence backends)) + (dolist (backend backends) + (dolist (key keys) + ;; ignore invalid slots + (condition-case signal + (unless (eval `(auth-source-search-collection + (plist-get spec key) + (oref backend ,key))) + (setq filtered-backends (delq backend filtered-backends)) + (return)) + (invalid-slot-name)))) + + (auth-source-do-debug + "auth-source-search: found %d backends matching %S" + (length filtered-backends) spec) + + ;; (debug spec "filtered" filtered-backends) + (setq goal max) + ;; First go through all the backends without :create, so we can + ;; query them all. + (let ((uspec (copy-sequence spec))) + (plist-put uspec :create nil) + (dolist (backend filtered-backends) + (let ((match (apply + (slot-value backend 'search-function) + :backend backend + uspec))) + (when match + (push (list backend match) matches))))) + ;; If we didn't find anything, then we allow the backend(s) to + ;; create the entries. + (when (and create + (not matches)) + (dolist (backend filtered-backends) + (unless matches + (let ((match (apply + (slot-value backend 'search-function) + :backend backend + :create create + :delete delete + spec))) + (when match + (push (list backend match) matches)))))) + + (setq backend (caar matches) + found-here (cadar matches)) + + (block nil + ;; if max is 0, as soon as we find something, return it + (when (and (zerop max) (> 0 (length found-here))) + (return t)) + + ;; decrement the goal by the number of new results + (decf goal (length found-here)) + ;; and append the new results to the full list + (setq found (append found found-here)) + + (auth-source-do-debug + "auth-source-search: found %d results (max %d/%d) in %S matching %S" + (length found-here) max goal backend spec) + + ;; return full list if the goal is 0 or negative + (when (zerop (max 0 goal)) + (return found)) + + ;; change the :max parameter in the spec to the goal + (setq spec (plist-put spec :max goal)) + + (when (and found auth-source-do-cache) + (auth-source-remember spec found)))) + + found)) + +;;; (auth-source-search :max 1) +;;; (funcall (plist-get (nth 0 (auth-source-search :max 1)) :secret)) +;;; (auth-source-search :host "nonesuch" :type 'netrc :K 1) +;;; (auth-source-search :host "nonesuch" :type 'secrets) + +(defun* auth-source-delete (&rest spec + &key delete + &allow-other-keys) + "Delete entries from the authentication backends according to SPEC. +Calls `auth-source-search' with the :delete property in SPEC set to t. +The backend may not actually delete the entries. + +Returns the deleted entries." + (auth-source-search (plist-put spec :delete t))) + +(defun auth-source-search-collection (collection value) + "Returns t is VALUE is t or COLLECTION is t or contains VALUE." + (when (and (atom collection) (not (eq t collection))) + (setq collection (list collection))) + + ;; (debug :collection collection :value value) + (or (eq collection t) + (eq value t) + (equal collection value) + (member value collection))) (defun auth-source-forget-all-cached () - "Forget all cached auth-source authentication tokens." + "Forget all cached auth-source data." (interactive) - (setq auth-source-cache (make-hash-table :test 'equal))) + (loop for sym being the symbols of password-data + ;; when the symbol name starts with auth-source-magic + when (string-match (concat "^" auth-source-magic) + (symbol-name sym)) + ;; remove that key + do (password-cache-remove (symbol-name sym)))) + +(defun auth-source-remember (spec found) + "Remember FOUND search results for SPEC." + (let ((password-cache-expiry auth-source-cache-expiry)) + (password-cache-add + (concat auth-source-magic (format "%S" spec)) found))) + +(defun auth-source-recall (spec) + "Recall FOUND search results for SPEC." + (password-read-from-cache + (concat auth-source-magic (format "%S" spec)))) + +(defun auth-source-forget (spec) + "Forget any cached data matching SPEC exactly. + +This is the same SPEC you passed to `auth-source-search'. +Returns t or nil for forgotten or not found." + (password-cache-remove (concat auth-source-magic (format "%S" spec)))) + +;;; (loop for sym being the symbols of password-data when (string-match (concat "^" auth-source-magic) (symbol-name sym)) collect (symbol-name sym)) + +;;; (auth-source-remember '(:host "wedd") '(4 5 6)) +;;; (auth-source-remember '(:host "xedd") '(1 2 3)) +;;; (auth-source-recall '(:host "xedd")) +;;; (auth-source-recall '(:host t)) +;;; (auth-source-forget+ :host t) + +(defun* auth-source-forget+ (&rest spec &allow-other-keys) + "Forget any cached data matching SPEC. Returns forgotten count. + +This is not a full `auth-source-search' spec but works similarly. +For instance, \(:host \"myhost\" \"yourhost\") would find all the +cached data that was found with a search for those two hosts, +while \(:host t) would find all host entries." + (let ((count 0) + sname) + (loop for sym being the symbols of 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 + do (progn + (password-cache-remove sname) + (incf count))) + count)) + +(defun auth-source-specmatchp (spec stored) + (let ((keys (loop for i below (length spec) by 2 + collect (nth i spec)))) + (not (eq + (dolist (key keys) + (unless (auth-source-search-collection (plist-get stored key) + (plist-get spec key)) + (return 'no))) + 'no)))) + +;;; Backend specific parsing: netrc/authinfo backend + +(defvar auth-source-netrc-cache nil) + +;;; (auth-source-netrc-parse "~/.authinfo.gpg") +(defun* auth-source-netrc-parse (&rest + spec + &key file max host user port delete + &allow-other-keys) + "Parse FILE and return a list of all entries in the file. +Note that the MAX parameter is used so we can exit the parse early." + (if (listp file) + ;; We got already parsed contents; just return it. + file + (when (file-exists-p file) + (with-temp-buffer + (let ((tokens '("machine" "host" "default" "login" "user" + "password" "account" "macdef" "force" + "port" "protocol")) + (max (or max 5000)) ; sanity check: default to stop at 5K + (modified 0) + alist elem result pair) + (if (and auth-source-netrc-cache + (equal (car auth-source-netrc-cache) + (nth 5 (file-attributes file)))) + (insert (base64-decode-string + (rot13-string (cdr auth-source-netrc-cache)))) + (insert-file-contents file) + (when (string-match "\\.gpg\\'" file) + ;; Store the contents of the file heavily encrypted in memory. + (setq auth-source-netrc-cache + (cons (nth 5 (file-attributes file)) + (rot13-string + (base64-encode-string + (buffer-string))))))) + (goto-char (point-min)) + ;; Go through the file, line by line. + (while (and (not (eobp)) + (> max 0)) + + (narrow-to-region (point) (point-at-eol)) + ;; For each line, get the tokens and values. + (while (not (eobp)) + (skip-chars-forward "\t ") + ;; Skip lines that begin with a "#". + (if (eq (char-after) ?#) + (goto-char (point-max)) + (unless (eobp) + (setq elem + (if (= (following-char) ?\") + (read (current-buffer)) + (buffer-substring + (point) (progn (skip-chars-forward "^\t ") + (point))))) + (cond + ((equal elem "macdef") + ;; We skip past the macro definition. + (widen) + (while (and (zerop (forward-line 1)) + (looking-at "$"))) + (narrow-to-region (point) (point))) + ((member elem tokens) + ;; Tokens that don't have a following value are ignored, + ;; except "default". + (when (and pair (or (cdr pair) + (equal (car pair) "default"))) + (push pair alist)) + (setq pair (list elem))) + (t + ;; Values that haven't got a preceding token are ignored. + (when pair + (setcdr pair elem) + (push pair alist) + (setq pair nil))))))) + + (when (and alist + (> max 0) + (auth-source-search-collection + host + (or + (aget alist "machine") + (aget alist "host") + t)) + (auth-source-search-collection + user + (or + (aget alist "login") + (aget alist "account") + (aget alist "user") + t)) + (auth-source-search-collection + port + (or + (aget alist "port") + (aget alist "protocol") + t))) + (decf max) + (push (nreverse alist) result) + ;; to delete a line, we just comment it out + (when delete + (goto-char (point-min)) + (insert "#") + (incf modified))) + (setq alist nil + pair nil) + (widen) + (forward-line 1)) + + (when (< 0 modified) + (when auth-source-gpg-encrypt-to + ;; (see bug#7487) making `epa-file-encrypt-to' local to + ;; this buffer lets epa-file skip the key selection query + ;; (see the `local-variable-p' check in + ;; `epa-file-write-region'). + (unless (local-variable-p 'epa-file-encrypt-to (current-buffer)) + (make-local-variable 'epa-file-encrypt-to)) + (if (listp auth-source-gpg-encrypt-to) + (setq epa-file-encrypt-to auth-source-gpg-encrypt-to))) + + ;; ask AFTER we've successfully opened the file + (when (y-or-n-p (format "Save file %s? (%d modifications)" + file modified)) + (write-region (point-min) (point-max) file nil 'silent) + (auth-source-do-debug + "auth-source-netrc-parse: modified %d lines in %s" + modified file))) + + (nreverse result)))))) + +(defun auth-source-netrc-normalize (alist) + (mapcar (lambda (entry) + (let (ret item) + (while (setq item (pop entry)) + (let ((k (car item)) + (v (cdr item))) + + ;; apply key aliases + (setq k (cond ((member k '("machine")) "host") + ((member k '("login" "account")) "user") + ((member k '("protocol")) "port") + ((member k '("password")) "secret") + (t k))) + + ;; send back the secret in a function (lexical binding) + (when (equal k "secret") + (setq v (lexical-let ((v v)) + (lambda () v)))) + + (setq ret (plist-put ret + (intern (concat ":" k)) + v)) + )) + ret)) + alist)) + +;;; (setq secret (plist-get (nth 0 (auth-source-search :host t :type 'netrc :K 1 :max 1)) :secret)) +;;; (funcall secret) + +(defun* auth-source-netrc-search (&rest + spec + &key backend create delete + type max host user port + &allow-other-keys) +"Given a property list SPEC, return search matches from the :backend. +See `auth-source-search' for details on SPEC." + ;; just in case, check that the type is correct (null or same as the backend) + (assert (or (null type) (eq type (oref backend type))) + t "Invalid netrc search: %s %s") + + (let ((results (auth-source-netrc-normalize + (auth-source-netrc-parse + :max max + :delete delete + :file (oref backend source) + :host (or host t) + :user (or user t) + :port (or port t))))) + + ;; if we need to create an entry AND none were found to match + (when (and create + (= 0 (length results))) + + ;; create based on the spec and record the value + (setq results (or + ;; if the user did not want to create the entry + ;; in the file, it will be returned + (apply (slot-value backend 'create-function) spec) + ;; if not, we do the search again without :create + ;; to get the updated data. + + ;; the result will be returned, even if the search fails + (apply 'auth-source-netrc-search + (plist-put spec :create nil))))) + results)) + +;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t) +;;; (auth-source-search :host "nonesuch" :type 'netrc :max 1 :create t :create-extra-keys '((A "default A") (B))) + +(defun* auth-source-netrc-create (&rest spec + &key backend + secret host user port create + &allow-other-keys) + (let* ((base-required '(host user port secret)) + ;; we know (because of an assertion in auth-source-search) that the + ;; :create parameter is either t or a list (which includes nil) + (create-extra (if (eq t create) nil create)) + (required (append base-required create-extra)) + (file (oref backend source)) + (add "") + (show "") + ;; `valist' is an alist + valist + ;; `artificial' will be returned if no creation is needed + artificial) -;; (progn -;; (auth-source-forget-all-cached) -;; (list -;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other") -;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "tzz") -;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" "other" "joe"))) + ;; only for base required elements (defined as function parameters): + ;; fill in the valist with whatever data we may have from the search + ;; we complete the first value if it's a list and use the value otherwise + (dolist (br base-required) + (when (symbol-value br) + (let ((br-choice (cond + ;; all-accepting choice (predicate is t) + ((eq t (symbol-value br)) nil) + ;; just the value otherwise + (t (symbol-value br))))) + (when br-choice + (aput 'valist br br-choice))))) + + ;; for extra required elements, see if the spec includes a value for them + (dolist (er create-extra) + (let ((name (concat ":" (symbol-name er))) + (keys (loop for i below (length spec) by 2 + collect (nth i spec)))) + (dolist (k keys) + (when (equal (symbol-name k) name) + (aput 'valist er (plist-get spec k)))))) + + ;; for each required element + (dolist (r required) + (let* ((data (aget valist r)) + (given-default (aget auth-source-creation-defaults r)) + ;; the defaults are simple + (default (cond + ((and (not given-default) (eq r 'user)) + (user-login-name)) + ;; note we need this empty string + ((and (not given-default) (eq r 'port)) + "") + (t given-default))) + ;; the prompt's default string depends on the data so far + (default-string (if (and default (< 0 (length default))) + (format " (default %s)" default) + " (no default)")) + ;; the prompt should also show what's entered so far + (user-value (aget valist 'user)) + (host-value (aget valist 'host)) + (port-value (aget valist 'port)) + ;; note this handles lists by just printing them + ;; later we allow the user to use completing-read to pick + (info-so-far (concat (if user-value + (format "%s@" user-value) + "[USER?]") + (if host-value + (format "%s" host-value) + "[HOST?]") + (if port-value + ;; this distinguishes protocol between + (if (zerop (length port-value)) + "" ; 'entered as "no default"' vs. + (format ":%s" port-value)) ; given + ;; and this is when the protocol is unknown + "[PORT?]")))) + + ;; now prompt if the search SPEC did not include a required key; + ;; take the result and put it in `data' AND store it in `valist' + (aput 'valist r + (setq data + (cond + ((and (null data) (eq r 'secret)) + ;; special case prompt for passwords + (read-passwd (format "Password for %s: " info-so-far))) + ((null data) + (read-string + (format "Enter %s for %s%s: " + r info-so-far default-string) + nil nil default)) + ((listp data) + (completing-read + (format "Enter %s for %s (TAB to see the choices): " + r info-so-far) + data + nil ; no predicate + t ; require a match + ;; note the default is nil, but if the user + ;; hits RET we'll get "", which is handled OK later + nil)) + (t data)))) + + (when data + (setq artificial (plist-put artificial + (intern (concat ":" (symbol-name r))) + (if (eq r 'secret) + (lexical-let ((data data)) + (lambda () data)) + data)))) + + ;; when r is not an empty string... + (when (and (stringp data) + (< 0 (length data))) + (let ((printer (lambda (hide) + ;; append the key (the symbol name of r) + ;; and the value in r + (format "%s%s %S" + ;; prepend a space + (if (zerop (length add)) "" " ") + ;; remap auth-source tokens to netrc + (case r + ('user "login") + ('host "machine") + ('secret "password") + ('port "port") ; redundant but clearer + (t (symbol-name r))) + ;; the value will be printed in %S format + (if (and hide (eq r 'secret)) + "HIDDEN_SECRET" + data))))) + (setq add (concat add (funcall printer nil))) + (setq show (concat show (funcall printer t))))))) + + (with-temp-buffer + (when (file-exists-p file) + (insert-file-contents file)) + (when auth-source-gpg-encrypt-to + ;; (see bug#7487) making `epa-file-encrypt-to' local to + ;; this buffer lets epa-file skip the key selection query + ;; (see the `local-variable-p' check in + ;; `epa-file-write-region'). + (unless (local-variable-p 'epa-file-encrypt-to (current-buffer)) + (make-local-variable 'epa-file-encrypt-to)) + (if (listp auth-source-gpg-encrypt-to) + (setq epa-file-encrypt-to auth-source-gpg-encrypt-to))) + (goto-char (point-max)) + + ;; ask AFTER we've successfully opened the file + (if (y-or-n-p (format "Add to file %s: line [%s]" file show)) + (progn + (unless (bolp) + (insert "\n")) + (insert add "\n") + (write-region (point-min) (point-max) file nil 'silent) + (auth-source-do-debug + "auth-source-netrc-create: wrote 1 new line to %s" + file) + nil) + (list artificial))))) + +;;; Backend specific parsing: Secrets API backend + +;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :create t)) +;;; (let ((auth-sources '(default))) (auth-source-search :max 1 :delete t)) +;;; (let ((auth-sources '(default))) (auth-source-search :max 1)) +;;; (let ((auth-sources '(default))) (auth-source-search)) +;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1)) +;;; (let ((auth-sources '("secrets:Login"))) (auth-source-search :max 1 :signon_realm "https://git.gnus.org/Git")) + +(defun* auth-source-secrets-search (&rest + spec + &key backend create delete label + type max host user port + &allow-other-keys) + "Search the Secrets API; spec is like `auth-source'. + +The :label key specifies the item's label. It is the only key +that can specify a substring. Any :label value besides a string +will allow any label. + +All other search keys must match exactly. If you need substring +matching, do a wider search and narrow it down yourself. + +You'll get back all the properties of the token as a plist. + +Here's an example that looks for the first item in the 'Login' +Secrets collection: + + \(let ((auth-sources '(\"secrets:Login\"))) + (auth-source-search :max 1) + +Here's another that looks for the first item in the 'Login' +Secrets collection whose label contains 'gnus': + + \(let ((auth-sources '(\"secrets:Login\"))) + (auth-source-search :max 1 :label \"gnus\") + +And this one looks for the first item in the 'Login' Secrets +collection that's a Google Chrome entry for the git.gnus.org site +authentication tokens: + + \(let ((auth-sources '(\"secrets:Login\"))) + (auth-source-search :max 1 :signon_realm \"https://git.gnus.org/Git\")) +" + + ;; TODO + (assert (not create) nil + "The Secrets API auth-source backend doesn't support creation yet") + ;; TODO + ;; (secrets-delete-item coll elt) + (assert (not delete) nil + "The Secrets API auth-source backend doesn't support deletion yet") + + (let* ((coll (oref backend source)) + (max (or max 5000)) ; sanity check: default to stop at 5K + (ignored-keys '(:create :delete :max :backend :label)) + (search-keys (loop for i below (length spec) by 2 + unless (memq (nth i spec) ignored-keys) + collect (nth i spec))) + ;; build a search spec without the ignored keys + ;; if a search key is nil or t (match anything), we skip it + (search-spec (apply 'append (mapcar + (lambda (k) + (if (or (null (plist-get spec k)) + (eq t (plist-get spec k))) + nil + (list k (plist-get spec k)))) + search-keys))) + ;; needed keys (always including host, login, port, and secret) + (returned-keys (mm-delete-duplicates (append + '(:host :login :port :secret) + search-keys))) + (items (loop for item in (apply 'secrets-search-items coll search-spec) + unless (and (stringp label) + (not (string-match label item))) + collect item)) + ;; TODO: respect max in `secrets-search-items', not after the fact + (items (butlast items (- (length items) max))) + ;; convert the item name to a full plist + (items (mapcar (lambda (item) + (append + ;; make an entry for the secret (password) element + (list + :secret + (lexical-let ((v (secrets-get-secret coll item))) + (lambda () v))) + ;; rewrite the entry from ((k1 v1) (k2 v2)) to plist + (apply 'append + (mapcar (lambda (entry) + (list (car entry) (cdr entry))) + (secrets-get-attributes coll item))))) + items)) + ;; ensure each item has each key in `returned-keys' + (items (mapcar (lambda (plist) + (append + (apply 'append + (mapcar (lambda (req) + (if (plist-get plist req) + nil + (list req nil))) + returned-keys)) + plist)) + items))) + items)) + +(defun* auth-source-secrets-create (&rest + spec + &key backend type max host user port + &allow-other-keys) + ;; TODO + ;; (apply 'secrets-create-item (auth-get-source entry) name passwd spec) + (debug spec)) + +;;; older API + +;;; (auth-source-user-or-password '("login" "password") "imap.myhost.com" t "tzz") + +;; deprecate the old interface +(make-obsolete 'auth-source-user-or-password + 'auth-source-search "Emacs 24.1") +(make-obsolete 'auth-source-forget-user-or-password + 'auth-source-forget "Emacs 24.1") (defun auth-source-user-or-password - (mode host protocol &optional username create-missing delete-existing) - "Find MODE (string or list of strings) matching HOST and PROTOCOL. + (mode host port &optional username create-missing delete-existing) + "Find MODE (string or list of strings) matching HOST and PORT. + +DEPRECATED in favor of `auth-source-search'! USERNAME is optional and will be used as \"login\" in a search across the Secret Service API (see secrets.el) if the resulting @@ -452,79 +1181,54 @@ stored in the password database which matches best (see MODE can be \"login\" or \"password\"." (auth-source-do-debug - "auth-source-user-or-password: get %s for %s (%s) + user=%s" - mode host protocol username) + "auth-source-user-or-password: DEPRECATED get %s for %s (%s) + user=%s" + mode host port username) + (let* ((listy (listp mode)) (mode (if listy mode (list mode))) (cname (if username - (format "%s %s:%s %s" mode host protocol username) - (format "%s %s:%s" mode host protocol))) - (search (list :host host :protocol protocol)) + (format "%s %s:%s %s" mode host port username) + (format "%s %s:%s" mode host port))) + (search (list :host host :port port)) (search (if username (append search (list :user username)) search)) - (found (if (not delete-existing) - (gethash cname auth-source-cache) - (remhash cname auth-source-cache) - nil))) + (search (if create-missing + (append search (list :create t)) + search)) + (search (if delete-existing + (append search (list :delete t)) + search)) + ;; (found (if (not delete-existing) + ;; (gethash cname auth-source-cache) + ;; (remhash cname auth-source-cache) + ;; nil))) + (found nil)) (if found (progn (auth-source-do-debug - "auth-source-user-or-password: cached %s=%s for %s (%s) + %s" + "auth-source-user-or-password: DEPRECATED cached %s=%s for %s (%s) + %s" mode ;; don't show the password - (if (and (member "password" mode) auth-source-hide-passwords) + (if (and (member "password" mode) t) "SECRET" found) - host protocol username) + host port username) found) ; return the found data - ;; else, if not found - (let ((choices (apply 'auth-source-pick search))) - (dolist (choice choices) - (if delete-existing - (apply 'auth-source-delete choice search) - (setq found (apply 'auth-source-retrieve mode choice search))) - (and found (return found))) - - ;; We haven't found something, so we will create it interactively. - (when (and (not found) create-missing) - (setq found (apply 'auth-source-create - mode (if choices - (car choices) - (car auth-sources)) - search))) - - ;; Cache the result. - (when found - (auth-source-do-debug - "auth-source-user-or-password: found %s=%s for %s (%s) + %s" - mode - ;; don't show the password - (if (and (member "password" mode) auth-source-hide-passwords) - "SECRET" found) - host protocol username) - (setq found (if listy found (car-safe found))) - (when auth-source-do-cache - (puthash cname found auth-source-cache))) - - found)))) - -(defun auth-source-protocol-defaults (protocol) - "Return a list of default ports and names for PROTOCOL." - (cdr-safe (assoc protocol auth-source-protocols))) - -(defun auth-source-user-or-password-imap (mode host) - (auth-source-user-or-password mode host 'imap)) - -(defun auth-source-user-or-password-pop3 (mode host) - (auth-source-user-or-password mode host 'pop3)) - -(defun auth-source-user-or-password-ssh (mode host) - (auth-source-user-or-password mode host 'ssh)) - -(defun auth-source-user-or-password-sftp (mode host) - (auth-source-user-or-password mode host 'sftp)) + ;; else, if not found, search with a max of 1 + (let ((choice (nth 0 (apply 'auth-source-search + (append '(:max 1) search))))) + (when choice + (dolist (m mode) + (cond + ((equal "password" m) + (push (if (plist-get choice :secret) + (funcall (plist-get choice :secret)) + nil) found)) + ((equal "login" m) + (push (plist-get choice :user) found))))) + (setq found (nreverse found)) + (setq found (if listy found (car-safe found))))) -(defun auth-source-user-or-password-smtp (mode host) - (auth-source-user-or-password mode host 'smtp)) + found)) (provide 'auth-source) diff --git a/lisp/gnus/gnus-art.el b/lisp/gnus/gnus-art.el index 54797b2a518..19eee78ab17 100644 --- a/lisp/gnus/gnus-art.el +++ b/lisp/gnus/gnus-art.el @@ -683,7 +683,7 @@ beginning of a line." :type 'regexp :group 'gnus-article-various) -(defcustom gnus-article-mode-line-format "Gnus: %g [%w] %S%m" +(defcustom gnus-article-mode-line-format "Gnus: %g %S%m" "*The format specification for the article mode line. See `gnus-summary-mode-line-format' for a closer description. @@ -691,6 +691,7 @@ The following additional specs are available: %w The article washing status. %m The number of MIME parts in the article." + :version "24.1" :type 'string :group 'gnus-article-various) @@ -3403,6 +3404,7 @@ possible values." (inhibit-read-only t) (inhibit-point-motion-hooks t) (first t) + (visible-date (mail-fetch-field "Date")) pos date bface eface) (save-excursion (save-restriction @@ -3426,6 +3428,9 @@ possible values." (delete-region (point-at-bol) (progn (gnus-article-forward-header) (point)))) + (when (and (not date) + visible-date) + (setq date visible-date)) (when date (article-transform-date date type bface eface))))))) @@ -3636,10 +3641,11 @@ function and want to see what the date was before converting." (let ((type (get-text-property (match-beginning 0) 'gnus-date-type))) (when (memq type '(lapsed combined-lapsed user-format)) - (unless (= window-start - (save-excursion - (forward-line 1) - (point))) + (when (and window-start + (not (= window-start + (save-excursion + (forward-line 1) + (point))))) (setq window-start nil)) (save-excursion (article-date-ut type t (match-beginning 0))) @@ -4631,6 +4637,7 @@ If ALL-HEADERS is non-nil, no headers are hidden." (forward-line -1)) (set-window-point (get-buffer-window (current-buffer)) (point)) (gnus-configure-windows 'article) + (gnus-run-hooks 'gnus-article-prepare-hook) t)))))) ;;;###autoload @@ -4648,8 +4655,7 @@ If ALL-HEADERS is non-nil, no headers are hidden." gnus-article-image-alist nil) (gnus-run-hooks 'gnus-tmp-internal-hook) (when gnus-display-mime-function - (funcall gnus-display-mime-function)) - (gnus-run-hooks 'gnus-article-prepare-hook))) + (funcall gnus-display-mime-function)))) ;;; ;;; Gnus Sticky Article Mode @@ -6316,7 +6322,8 @@ specifies." (defun gnus-article-next-page-1 (lines) (condition-case () - (let ((scroll-in-place nil)) + (let ((scroll-in-place nil) + (auto-window-vscroll nil)) (scroll-up lines)) (end-of-buffer ;; Long lines may cause an end-of-buffer error. diff --git a/lisp/gnus/gnus-delay.el b/lisp/gnus/gnus-delay.el index a06a510ecdd..bfd17055ea5 100644 --- a/lisp/gnus/gnus-delay.el +++ b/lisp/gnus/gnus-delay.el @@ -78,7 +78,7 @@ DELAY is a string, giving the length of the time. Possible values are: time, then the deadline is tomorrow, else today." (interactive (list (read-string - "Target date (YYYY-MM-DD) or length of delay (units in [mhdwMY]): " + "Target date (YYYY-MM-DD), time (hh:mm), or length of delay (units in [mhdwMY]): " gnus-delay-default-delay))) (let (num unit days year month day hour minute deadline) (cond ((string-match @@ -105,7 +105,7 @@ DELAY is a string, giving the length of the time. Possible values are: (append deadline nil)))) ;; If this time has passed already, add a day. (when (< deadline (gnus-float-time)) - (setq deadline (+ 3600 deadline))) ;3600 secs/day + (setq deadline (+ 86400 deadline))) ; 86400 secs/day ;; Convert seconds to date header. (setq deadline (message-make-date (seconds-to-time deadline)))) diff --git a/lisp/gnus/gnus-msg.el b/lisp/gnus/gnus-msg.el index 08fef2327ad..b199dcc572c 100644 --- a/lisp/gnus/gnus-msg.el +++ b/lisp/gnus/gnus-msg.el @@ -1081,14 +1081,14 @@ If VERY-WIDE, make a very wide reply." (gnus-summary-work-articles 1)))) ;; Allow user to require confirmation before replying by mail to the ;; author of a news article (or mail message). - (when (or - (not (or (gnus-news-group-p gnus-newsgroup-name) + (when (or (not (or (gnus-news-group-p gnus-newsgroup-name) gnus-confirm-treat-mail-like-news)) (not (cond ((stringp gnus-confirm-mail-reply-to-news) (string-match gnus-confirm-mail-reply-to-news gnus-newsgroup-name)) ((functionp gnus-confirm-mail-reply-to-news) - (funcall gnus-confirm-mail-reply-to-news gnus-newsgroup-name)) + (funcall gnus-confirm-mail-reply-to-news + gnus-newsgroup-name)) (t gnus-confirm-mail-reply-to-news))) (if (or wide very-wide) t ;; Ignore gnus-confirm-mail-reply-to-news for wide and very @@ -1123,7 +1123,7 @@ If VERY-WIDE, make a very wide reply." (insert headers)) (goto-char (point-max))) (mml-quote-region (point) (point-max)) - (message-reply nil wide) + (message-reply nil wide 'switch-to-buffer) (when yank (gnus-inews-yank-articles yank)) (gnus-summary-handle-replysign))))) diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el index b8a6be8702e..e5e2468058c 100644 --- a/lisp/gnus/gnus-start.el +++ b/lisp/gnus/gnus-start.el @@ -380,13 +380,6 @@ disc." :group 'gnus-newsrc :type 'boolean) -(defcustom gnus-use-backend-marks nil - "If non-nil, Gnus will store and retrieve marks from the backends. -This means that marks will be stored both in .newsrc.eld and in -the backend, and will slow operation down somewhat." - :group 'gnus-newsrc - :type 'boolean) - (defcustom gnus-check-bogus-groups-hook nil "A hook run after removing bogus groups." :group 'gnus-start-server @@ -1509,7 +1502,7 @@ If SCAN, request a scan of that group as well." (gnus-activate-group (gnus-info-group info) nil t)) ;; Allow backends to update marks, - (when gnus-use-backend-marks + (when gnus-propagate-marks (let ((method (inline (gnus-find-method-for-group (gnus-info-group info))))) (when (gnus-check-backend-function 'request-marks (car method)) @@ -1682,7 +1675,20 @@ If SCAN, request a scan of that group as well." (lambda (c1 c2) (< (gnus-method-rank (cadr c1) (car c1)) (gnus-method-rank (cadr c2) (car c2)))))) - + ;; Go through the list of servers and possibly extend methods that + ;; aren't equal (and that need extension; i.e., they are async). + (let ((methods nil)) + (dolist (elem type-cache) + (destructuring-bind (method method-type infos dummy) elem + (let ((gnus-opened-servers methods)) + (when (and (gnus-similar-server-opened method) + (gnus-check-backend-function + 'retrieve-group-data-early (car method))) + (setq method (gnus-server-extend-method + (gnus-info-group (car infos)) + method)) + (setcar elem method)) + (push (list method 'ok) methods))))) ;; Start early async retrieval of data. (dolist (elem type-cache) (destructuring-bind (method method-type infos dummy) elem diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el index 8fac5021df3..789308c4ab9 100644 --- a/lisp/gnus/gnus-sum.el +++ b/lisp/gnus/gnus-sum.el @@ -1235,8 +1235,9 @@ For example: ((1 . cn-gb-2312) (2 . big5))." :group 'gnus-summary-marks) (defcustom gnus-propagate-marks t - "If non-nil, do not propagate marks to the backends." - :version "23.1" ;; No Gnus + "If non-nil, Gnus will store and retrieve marks from the backends. +This means that marks will be stored both in .newsrc.eld and in +the backend, and will slow operation down somewhat." :type 'boolean :group 'gnus-summary-marks) @@ -4067,6 +4068,7 @@ If NO-DISPLAY, don't generate a summary buffer." ;; gnus-summary-prepare-hook since kill processing may not ;; work with hidden articles. (gnus-summary-maybe-hide-threads) + (gnus-configure-windows 'summary) (when kill-buffer (gnus-kill-or-deaden-summary kill-buffer)) (gnus-summary-auto-select-subject) @@ -4076,7 +4078,6 @@ If NO-DISPLAY, don't generate a summary buffer." gnus-newsgroup-unreads gnus-auto-select-first) (progn - (gnus-configure-windows 'summary) (let ((art (gnus-summary-article-number))) (unless (and (not gnus-plugged) (or (memq art gnus-newsgroup-undownloaded) @@ -7168,6 +7169,7 @@ If FORCE (the prefix), also save the .newsrc file(s)." (let* ((group gnus-newsgroup-name) (quit-config (gnus-group-quit-config gnus-newsgroup-name)) (gnus-group-is-exiting-p t) + (article-buffer gnus-article-buffer) (mode major-mode) (group-point nil) (buf (current-buffer))) @@ -7220,16 +7222,6 @@ If FORCE (the prefix), also save the .newsrc file(s)." (when (eq mode 'gnus-summary-mode) (gnus-kill-buffer buf))) - ;; If we have several article buffers, we kill them at exit. - (unless gnus-single-article-buffer - (when (gnus-buffer-live-p gnus-article-buffer) - (with-current-buffer gnus-article-buffer - ;; Don't kill sticky article buffers - (unless (eq major-mode 'gnus-sticky-article-mode) - (gnus-kill-buffer gnus-article-buffer) - (setq gnus-article-current nil)))) - (gnus-kill-buffer gnus-original-article-buffer)) - (setq gnus-current-select-method gnus-select-method) (set-buffer gnus-group-buffer) (if quit-config @@ -7241,6 +7233,17 @@ If FORCE (the prefix), also save the .newsrc file(s)." (if win (set-window-point win (point)))) (unless leave-hidden (gnus-configure-windows 'group 'force))) + + ;; If we have several article buffers, we kill them at exit. + (unless gnus-single-article-buffer + (when (gnus-buffer-live-p article-buffer) + (with-current-buffer article-buffer + ;; Don't kill sticky article buffers + (unless (eq major-mode 'gnus-sticky-article-mode) + (gnus-kill-buffer article-buffer) + (setq gnus-article-current nil)))) + (gnus-kill-buffer gnus-original-article-buffer)) + ;; Clear the current group name. (unless quit-config (setq gnus-newsgroup-name nil))))) @@ -7269,6 +7272,8 @@ If FORCE (the prefix), also save the .newsrc file(s)." (gnus-kill-buffer gnus-article-buffer) (gnus-kill-buffer gnus-original-article-buffer) (setq gnus-article-current nil)) + ;; Return to the group buffer. + (gnus-configure-windows 'group 'force) (if (not gnus-kill-summary-on-exit) (gnus-deaden-summary) (gnus-close-group group) @@ -7280,8 +7285,6 @@ If FORCE (the prefix), also save the .newsrc file(s)." (gnus-async-prefetch-remove-group group) (when (get-buffer gnus-article-buffer) (bury-buffer gnus-article-buffer)) - ;; Return to the group buffer. - (gnus-configure-windows 'group 'force) ;; Clear the current group name. (setq gnus-newsgroup-name nil) (unless (gnus-ephemeral-group-p group) @@ -7731,6 +7734,7 @@ If BACKWARD, the previous article is selected instead of the next." (point (with-current-buffer gnus-group-buffer (point))) + (current-summary (current-buffer)) (group (if (eq gnus-keep-same-level 'best) (gnus-summary-best-group gnus-newsgroup-name) @@ -7755,6 +7759,10 @@ If BACKWARD, the previous article is selected instead of the next." (gnus-summary-next-group nil group backward))) (t (when (gnus-key-press-event-p last-input-event) + ;; Somehow or other, we may now have selected a different + ;; window. Make point go back to the summary buffer. + (when (eq current-summary (current-buffer)) + (select-window (get-buffer-window current-summary))) (gnus-summary-walk-group-buffer gnus-newsgroup-name cmd unread backward point)))))))) diff --git a/lisp/gnus/gnus-util.el b/lisp/gnus/gnus-util.el index 67c49096b92..42dbd5948cf 100644 --- a/lisp/gnus/gnus-util.el +++ b/lisp/gnus/gnus-util.el @@ -871,6 +871,15 @@ Bind `print-quoted' and `print-readably' to t, and `print-length' and (when (file-exists-p file) (delete-file file))) +(defun gnus-delete-duplicates (list) + "Remove duplicate entries from LIST." + (let ((result nil)) + (while list + (unless (member (car list) result) + (push (car list) result)) + (pop list)) + (nreverse result))) + (defun gnus-delete-directory (directory) "Delete files in DIRECTORY. Subdirectories remain. If there's no subdirectory, delete DIRECTORY as well." diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el index 4cbdee53ab4..42acb65ff9f 100644 --- a/lisp/gnus/gnus.el +++ b/lisp/gnus/gnus.el @@ -275,7 +275,7 @@ (defgroup gnus-meta nil "Meta variables controlling major portions of Gnus. -In general, modifying these variables does not take affect until Gnus +In general, modifying these variables does not take effect until Gnus is restarted, and sometimes reloaded." :group 'gnus) diff --git a/lisp/gnus/mail-source.el b/lisp/gnus/mail-source.el index f98c195eada..6e6ef76c0c1 100644 --- a/lisp/gnus/mail-source.el +++ b/lisp/gnus/mail-source.el @@ -32,7 +32,7 @@ (eval-when-compile (require 'cl) (require 'imap)) -(autoload 'auth-source-user-or-password "auth-source") +(autoload 'auth-source-search "auth-source") (autoload 'pop3-movemail "pop3") (autoload 'pop3-get-message-count "pop3") (autoload 'nnheader-cancel-timer "nnheader") @@ -332,6 +332,7 @@ Common keywords should be listed here.") (:prescript) (:prescript-delay) (:postscript) + ;; note server and port need to come before user and password (:server (getenv "MAILHOST")) (:port 110) (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER"))) @@ -345,6 +346,7 @@ Common keywords should be listed here.") (:subdirs ("cur" "new")) (:function)) (imap + ;; note server and port need to come before user and password (:server (getenv "MAILHOST")) (:port) (:stream) @@ -417,42 +419,66 @@ the `mail-source-keyword-map' variable." (put 'mail-source-bind 'lisp-indent-function 1) (put 'mail-source-bind 'edebug-form-spec '(sexp body)) -;; TODO: use the list format for auth-source-user-or-password modes (defun mail-source-set-1 (source) (let* ((type (pop source)) - (defaults (cdr (assq type mail-source-keyword-map))) - default value keyword auth-info user-auth pass-auth) + (defaults (cdr (assq type mail-source-keyword-map))) + (search '(:max 1)) + found default value keyword auth-info user-auth pass-auth) + + ;; append to the search the useful info from the source and the defaults: + ;; user, host, and port + + ;; the msname is the mail-source parameter + (dolist (msname '(:server :user :port)) + ;; the asname is the auth-source parameter + (let* ((asname (case msname + (:server :host) ; auth-source uses :host + (t msname))) + ;; this is the mail-source default + (msdef1 (or (plist-get source msname) + (nth 1 (assoc msname defaults)))) + ;; ...evaluated + (msdef (mail-source-value msdef1))) + (setq search (append (list asname + (if msdef msdef t)) + search)))) + ;; if the port is unknown yet, get it from the mail-source type + (unless (plist-get search :port) + (setq search (append (list :port (symbol-name type))))) + (while (setq default (pop defaults)) ;; for each default :SYMBOL, set SYMBOL to the plist value for :SYMBOL ;; using `mail-source-value' to evaluate the plist value (set (mail-source-strip-keyword (setq keyword (car default))) - ;; note the following reasons for this structure: - ;; 1) the auth-sources user and password override everything - ;; 2) it avoids macros, so it's cleaner - ;; 3) it falls through to the mail-sources and then default values - (cond - ((and - (eq keyword :user) - (setq user-auth - (nth 0 (auth-source-user-or-password - '("login" "password") - ;; this is "host" in auth-sources - (if (boundp 'server) (symbol-value 'server) "") - type)))) - user-auth) - ((and - (eq keyword :password) - (setq pass-auth - (nth 1 - (auth-source-user-or-password - '("login" "password") - ;; this is "host" in auth-sources - (if (boundp 'server) (symbol-value 'server) "") - type)))) - pass-auth) - (t (if (setq value (plist-get source keyword)) - (mail-source-value value) - (mail-source-value (cadr default))))))))) + ;; note the following reasons for this structure: + ;; 1) the auth-sources user and password override everything + ;; 2) it avoids macros, so it's cleaner + ;; 3) it falls through to the mail-sources and then default values + (cond + ((and + (eq keyword :user) + (setq user-auth (plist-get + ;; cache the search result in `found' + (or found + (setq found (nth 0 (apply 'auth-source-search + search)))) + :user))) + user-auth) + ((and + (eq keyword :password) + (setq pass-auth (plist-get + ;; cache the search result in `found' + (or found + (setq found (nth 0 (apply 'auth-source-search + search)))) + :secret))) + ;; maybe set the password to the return of the :secret function + (if (functionp pass-auth) + (setq pass-auth (funcall pass-auth)) + pass-auth)) + (t (if (setq value (plist-get source keyword)) + (mail-source-value value) + (mail-source-value (cadr default))))))))) (eval-and-compile (defun mail-source-bind-common-1 () diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 42b61950986..58daf1baf94 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -6779,7 +6779,7 @@ Useful functions to put in this list include: subject) ;;;###autoload -(defun message-reply (&optional to-address wide) +(defun message-reply (&optional to-address wide switch-function) "Start editing a reply to the article in the current buffer." (interactive) (require 'gnus-sum) ; for gnus-list-identifiers @@ -6822,7 +6822,8 @@ Useful functions to put in this list include: (message-pop-to-buffer (message-buffer-name (if wide "wide reply" "reply") from - (if wide to-address nil)))) + (if wide to-address nil)) + switch-function)) (setq message-reply-headers (vector 0 subject from date message-id references 0 0 "")) diff --git a/lisp/gnus/mml2015.el b/lisp/gnus/mml2015.el index 1271168fffc..df106bb6de8 100644 --- a/lisp/gnus/mml2015.el +++ b/lisp/gnus/mml2015.el @@ -116,7 +116,7 @@ Whether the passphrase is cached at all is controlled by :type 'integer) (defcustom mml2015-signers nil - "A list of your own key ID which will be used to sign a message. + "A list of your own key ID(s) which will be used to sign a message. If set, it overrides the setting of `mml2015-sign-with-sender'." :group 'mime-security :type '(repeat (string :tag "Key ID"))) diff --git a/lisp/gnus/nnfolder.el b/lisp/gnus/nnfolder.el index dd11ff71dd3..3ec30410473 100644 --- a/lisp/gnus/nnfolder.el +++ b/lisp/gnus/nnfolder.el @@ -1083,6 +1083,8 @@ This command does not work if you use short group names." (or nnfolder-nov-directory nnfolder-directory))) (concat (nnfolder-group-pathname group) nnfolder-nov-file-suffix))) +(defvar copyright-update) + (defun nnfolder-save-buffer () "Save the buffer." (when (buffer-modified-p) @@ -1090,8 +1092,8 @@ This command does not work if you use short group names." (gnus-make-directory (file-name-directory (buffer-file-name))) (let ((coding-system-for-write (or nnfolder-file-coding-system-for-write - nnfolder-file-coding-system)) - (copyright-update nil)) + nnfolder-file-coding-system))) + (set (make-local-variable 'copyright-update) nil) (save-buffer))) (unless (or gnus-nov-is-evil nnfolder-nov-is-evil) (nnfolder-save-nov))) diff --git a/lisp/gnus/nnimap.el b/lisp/gnus/nnimap.el index a6fe6b1489b..83b8c416283 100644 --- a/lisp/gnus/nnimap.el +++ b/lisp/gnus/nnimap.el @@ -47,8 +47,8 @@ (require 'nnmail) (require 'proto-stream) -(autoload 'auth-source-forget-user-or-password "auth-source") -(autoload 'auth-source-user-or-password "auth-source") +(autoload 'auth-source-forget+ "auth-source") +(autoload 'auth-source-search "auth-source") (nnoo-declare nnimap) @@ -142,6 +142,8 @@ textual parts.") (defvar nnimap-quirks '(("QRESYNC" "Zimbra" "QRESYNC "))) +(defvar nnimap-inhibit-logging nil) + (defun nnimap-buffer () (nnimap-find-process-buffer nntp-server-buffer)) @@ -274,19 +276,18 @@ textual parts.") (push (current-buffer) nnimap-process-buffers) (current-buffer))) -(defun nnimap-credentials (address ports &optional inhibit-create) - (let (port credentials) - ;; Request the credentials from all ports, but only query on the - ;; last port if all the previous ones have failed. - (while (and (null credentials) - (setq port (pop ports))) - (setq credentials - (auth-source-user-or-password - '("login" "password") address port nil - (if inhibit-create - nil - (null ports))))) - credentials)) +(defun nnimap-credentials (address ports) + (let ((found (nth 0 (auth-source-search :max 1 + :host address + :port ports + :create t)))) + (if found + (list (plist-get found :user) + (let ((secret (plist-get found :secret))) + (if (functionp secret) + (funcall secret) + secret))) + nil))) (defun nnimap-keepalive () (let ((now (current-time))) @@ -381,26 +382,25 @@ textual parts.") (if (eq nnimap-authenticator 'anonymous) (list "anonymous" (message-make-address)) - (or - ;; First look for the credentials based - ;; on the virtual server name. - (nnimap-credentials - (nnoo-current-server 'nnimap) ports t) - ;; Then look them up based on the - ;; physical address. - (nnimap-credentials nnimap-address ports))))) + ;; Look for the credentials based on + ;; the virtual server name and the address + (nnimap-credentials + (gnus-delete-duplicates + (list + nnimap-address + (nnoo-current-server 'nnimap))) + ports)))) (setq nnimap-object nil) - (setq login-result - (nnimap-login (car credentials) (cadr credentials))) + (let ((nnimap-inhibit-logging t)) + (setq login-result + (nnimap-login (car credentials) (cadr credentials)))) (unless (car login-result) ;; If the login failed, then forget the credentials ;; that are now possibly cached. (dolist (host (list (nnoo-current-server 'nnimap) nnimap-address)) (dolist (port ports) - (dolist (element '("login" "password")) - (auth-source-forget-user-or-password - element host port)))) + (auth-source-forget+ :host host :port port))) (delete-process (nnimap-process nnimap-object)) (setq nnimap-object nil)))) (when nnimap-object @@ -969,7 +969,8 @@ textual parts.") (nnimap-add-cr) (setq message (buffer-substring-no-properties (point-min) (point-max))) (with-current-buffer (nnimap-buffer) - (when (setq message (nnimap-process-quirk "OK Gimap " 'append message)) + (when (setq message (or (nnimap-process-quirk "OK Gimap " 'append message) + message)) ;; If we have this group open read-only, then unselect it ;; before appending to it. (when (equal (nnimap-examined nnimap-object) group) @@ -997,7 +998,7 @@ textual parts.") (defun nnimap-process-quirk (greeting-match type data) (when (and (nnimap-greeting nnimap-object) - (string-match "OK Gimap " (nnimap-greeting nnimap-object)) + (string-match greeting-match (nnimap-greeting nnimap-object)) (eq type 'append) (string-match "\000" data)) (let ((choice (gnus-multiple-choice @@ -1074,60 +1075,62 @@ textual parts.") (nreverse groups))) (deffoo nnimap-request-list (&optional server) - (nnimap-possibly-change-group nil server) - (with-current-buffer nntp-server-buffer - (erase-buffer) - (let ((groups - (with-current-buffer (nnimap-buffer) - (nnimap-get-groups))) - sequences responses) - (when groups - (with-current-buffer (nnimap-buffer) - (setf (nnimap-group nnimap-object) nil) - (dolist (group groups) - (setf (nnimap-examined nnimap-object) group) - (push (list (nnimap-send-command "EXAMINE %S" (utf7-encode group t)) - group) - sequences)) - (nnimap-wait-for-response (caar sequences)) - (setq responses - (nnimap-get-responses (mapcar #'car sequences)))) - (dolist (response responses) - (let* ((sequence (car response)) - (response (cadr response)) - (group (cadr (assoc sequence sequences)))) - (when (and group - (equal (caar response) "OK")) - (let ((uidnext (nnimap-find-parameter "UIDNEXT" response)) - highest exists) - (dolist (elem response) - (when (equal (cadr elem) "EXISTS") - (setq exists (string-to-number (car elem))))) - (when uidnext - (setq highest (1- (string-to-number (car uidnext))))) - (cond - ((null highest) - (insert (format "%S 0 1 y\n" (utf7-decode group t)))) - ((zerop exists) - ;; Empty group. - (insert (format "%S %d %d y\n" - (utf7-decode group t) highest (1+ highest)))) - (t - ;; Return the widest possible range. - (insert (format "%S %d 1 y\n" (utf7-decode group t) - (or highest exists))))))))) - t)))) + (when (nnimap-possibly-change-group nil server) + (with-current-buffer nntp-server-buffer + (erase-buffer) + (let ((groups + (with-current-buffer (nnimap-buffer) + (nnimap-get-groups))) + sequences responses) + (when groups + (with-current-buffer (nnimap-buffer) + (setf (nnimap-group nnimap-object) nil) + (dolist (group groups) + (setf (nnimap-examined nnimap-object) group) + (push (list (nnimap-send-command "EXAMINE %S" + (utf7-encode group t)) + group) + sequences)) + (nnimap-wait-for-response (caar sequences)) + (setq responses + (nnimap-get-responses (mapcar #'car sequences)))) + (dolist (response responses) + (let* ((sequence (car response)) + (response (cadr response)) + (group (cadr (assoc sequence sequences)))) + (when (and group + (equal (caar response) "OK")) + (let ((uidnext (nnimap-find-parameter "UIDNEXT" response)) + highest exists) + (dolist (elem response) + (when (equal (cadr elem) "EXISTS") + (setq exists (string-to-number (car elem))))) + (when uidnext + (setq highest (1- (string-to-number (car uidnext))))) + (cond + ((null highest) + (insert (format "%S 0 1 y\n" (utf7-decode group t)))) + ((zerop exists) + ;; Empty group. + (insert (format "%S %d %d y\n" + (utf7-decode group t) + highest (1+ highest)))) + (t + ;; Return the widest possible range. + (insert (format "%S %d 1 y\n" (utf7-decode group t) + (or highest exists))))))))) + t))))) (deffoo nnimap-request-newgroups (date &optional server) - (nnimap-possibly-change-group nil server) - (with-current-buffer nntp-server-buffer - (erase-buffer) - (dolist (group (with-current-buffer (nnimap-buffer) - (nnimap-get-groups))) - (unless (assoc group nnimap-current-infos) - ;; Insert dummy numbers here -- they don't matter. - (insert (format "%S 0 1 y\n" group)))) - t)) + (when (nnimap-possibly-change-group nil server) + (with-current-buffer nntp-server-buffer + (erase-buffer) + (dolist (group (with-current-buffer (nnimap-buffer) + (nnimap-get-groups))) + (unless (assoc group nnimap-current-infos) + ;; Insert dummy numbers here -- they don't matter. + (insert (format "%S 0 1 y\n" group)))) + t))) (deffoo nnimap-retrieve-group-data-early (server infos) (when (nnimap-possibly-change-group nil server) @@ -1567,6 +1570,7 @@ textual parts.") (defvar nnimap-sequence 0) (defun nnimap-send-command (&rest args) + (setf (nnimap-last-command-time nnimap-object) (current-time)) (process-send-string (get-buffer-process (current-buffer)) (nnimap-log-command @@ -1585,12 +1589,14 @@ textual parts.") (defun nnimap-log-command (command) (with-current-buffer (get-buffer-create "*imap log*") (goto-char (point-max)) - (insert (format-time-string "%H:%M:%S") " " command)) + (insert (format-time-string "%H:%M:%S") " " + (if nnimap-inhibit-logging + "(inhibited)\n" + command))) command) (defun nnimap-command (&rest args) (erase-buffer) - (setf (nnimap-last-command-time nnimap-object) (current-time)) (let* ((sequence (apply #'nnimap-send-command args)) (response (nnimap-get-response sequence))) (if (equal (caar response) "OK") @@ -1635,7 +1641,7 @@ textual parts.") (progn (forward-line -1) (looking-at "\\*")))) - (not (looking-at (format "%d " sequence))))) + (not (looking-at (format "%d .*\n" sequence))))) (when messagep (nnheader-message 7 "nnimap read %dk" (/ (buffer-size) 1000))) (nnheader-accept-process-output process) @@ -1817,7 +1823,7 @@ textual parts.") (defun nnimap-parse-copied-articles (sequences) (let (sequence copied range) (goto-char (point-min)) - (while (re-search-forward "^\\([0-9]+\\) OK " nil t) + (while (re-search-forward "^\\([0-9]+\\) OK\\b" nil t) (setq sequence (string-to-number (match-string 1))) (when (setq range (cadr (assq sequence sequences))) (push (gnus-uncompress-range range) copied))) diff --git a/lisp/gnus/nntp.el b/lisp/gnus/nntp.el index eb2dd004638..09ecfb8f6b7 100644 --- a/lisp/gnus/nntp.el +++ b/lisp/gnus/nntp.el @@ -40,7 +40,7 @@ (eval-when-compile (require 'cl)) -(autoload 'auth-source-user-or-password "auth-source") +(autoload 'auth-source-search "auth-source") (defgroup nntp nil "NNTP access for Gnus." @@ -774,7 +774,7 @@ command whose response triggered the error." (nntp-copy-to-buffer nntp-server-buffer (point-min) (point-max)) 'headers))))) -(deffoo nntp-retrieve-group-data-early-disabled (server infos) +(deffoo nntp-retrieve-group-data-early (server infos) "Retrieve group info on INFOS." (nntp-with-open-group nil server (when (nntp-find-connection-buffer nntp-server-buffer) @@ -793,7 +793,7 @@ command whose response triggered the error." nil command (gnus-group-real-name (gnus-info-group info))))) (length infos))))) -(deffoo nntp-finish-retrieve-group-infos-disabled (server infos count) +(deffoo nntp-finish-retrieve-group-infos (server infos count) (nntp-with-open-group nil server (let ((buf (nntp-find-connection-buffer nntp-server-buffer)) (method (gnus-find-method-for-group @@ -808,16 +808,17 @@ command whose response triggered the error." (progn (goto-char last-point) ;; Count replies. - (while (re-search-forward "^[0-9]" nil t) + (while (re-search-forward + (if nntp-server-list-active-group + "^[.]" + "^[0-9]") + nil t) (incf received)) (setq last-point (point)) (< received count))) (nntp-accept-response)) ;; We now have all the entries. Remove CRs. - (goto-char (point-min)) - (while (search-forward "\r" nil t) - (replace-match "" t t)) - + (nnheader-strip-cr) (if (not nntp-server-list-active-group) (progn (nntp-copy-to-buffer nntp-server-buffer @@ -830,7 +831,14 @@ command whose response triggered the error." (delete-region (match-beginning 0) (progn (forward-line 1) (point)))) (nntp-copy-to-buffer nntp-server-buffer (point-min) (point-max)) - (gnus-active-to-gnus-format method gnus-active-hashtb nil t))))))) + (with-current-buffer nntp-server-buffer + (gnus-active-to-gnus-format + ;; Kludge to use the extended method name if you have + ;; an extended one. + (if (consp (gnus-info-method (car infos))) + (gnus-info-method (car infos)) + method) + gnus-active-hashtb nil t)))))))) (deffoo nntp-retrieve-groups (groups &optional server) "Retrieve group info on GROUPS." @@ -1231,10 +1239,16 @@ If SEND-IF-FORCE, only send authinfo to the server if the (let* ((list (netrc-parse nntp-authinfo-file)) (alist (netrc-machine list nntp-address "nntp")) (force (or (netrc-get alist "force") nntp-authinfo-force)) - (auth-info - (auth-source-user-or-password '("login" "password") nntp-address "nntp")) - (auth-user (nth 0 auth-info)) - (auth-passwd (nth 1 auth-info)) + (auth-info + (nth 0 (auth-source-search :max 1 + ;; TODO: allow the virtual server name too + :host nntp-address + :port '("119" "nntp")))) + (auth-user (plist-get auth-info :user)) + (auth-passwd (plist-get auth-info :secret)) + (auth-passwd (if (functionp auth-passwd) + (funcall auth-passwd) + auth-passwd)) (user (or ;; this is preferred to netrc-* auth-user diff --git a/lisp/gnus/shr-color.el b/lisp/gnus/shr-color.el index 4b885d9dbf8..36dd65f4a2d 100644 --- a/lisp/gnus/shr-color.el +++ b/lisp/gnus/shr-color.el @@ -259,7 +259,7 @@ Like rgb() or hsl()." (l (/ (string-to-number (match-string-no-properties 3 color)) 100.0))) (destructuring-bind (r g b) (shr-color-hsl-to-rgb-fractions h s l) - (color-rgb->hex r g b)))) + (color-rgb-to-hex r g b)))) ;; Color names ((cdr (assoc-string color shr-color-html-colors-alist t))) ;; Unrecognized color :( @@ -325,13 +325,13 @@ If FIXED-BACKGROUND is set, and if the color are not visible, a new background color will not be computed. Only the foreground color will be adapted to be visible on BG." ;; Convert fg and bg to CIE Lab - (let ((fg-norm (color-rgb->normalize fg)) - (bg-norm (color-rgb->normalize bg))) + (let ((fg-norm (color-name-to-rgb fg)) + (bg-norm (color-name-to-rgb bg))) (if (or (null fg-norm) (null bg-norm)) (list bg fg) - (let* ((fg-lab (apply 'color-srgb->lab fg-norm)) - (bg-lab (apply 'color-srgb->lab bg-norm)) + (let* ((fg-lab (apply 'color-srgb-to-lab fg-norm)) + (bg-lab (apply 'color-srgb-to-lab bg-norm)) ;; Compute color distance using CIE DE 2000 (fg-bg-distance (color-cie-de2000 fg-lab bg-lab)) ;; Compute luminance distance (substract L component) @@ -351,10 +351,10 @@ color will be adapted to be visible on BG." bg (apply 'format "#%02x%02x%02x" (mapcar (lambda (x) (* (max (min 1 x) 0) 255)) - (apply 'color-lab->srgb bg-lab)))) + (apply 'color-lab-to-srgb bg-lab)))) (apply 'format "#%02x%02x%02x" (mapcar (lambda (x) (* (max (min 1 x) 0) 255)) - (apply 'color-lab->srgb fg-lab)))))))))) + (apply 'color-lab-to-srgb fg-lab)))))))))) (provide 'shr-color) diff --git a/lisp/gnus/sieve-manage.el b/lisp/gnus/sieve-manage.el index d115f40528b..c9a0df20590 100644 --- a/lisp/gnus/sieve-manage.el +++ b/lisp/gnus/sieve-manage.el @@ -83,7 +83,7 @@ (require 'starttls)) (autoload 'sasl-find-mechanism "sasl") (autoload 'starttls-open-stream "starttls") -(autoload 'auth-source-user-or-password "auth-source") +(autoload 'auth-source-search "auth-source") ;; User customizable variables: @@ -273,16 +273,20 @@ Valid states are `closed', `initial', `nonauth', and `auth'.") "Login to server using the SASL MECH method." (message "sieve: Authenticating using %s..." mech) (with-current-buffer buffer - (let* ((user-password (auth-source-user-or-password - '("login" "password") - sieve-manage-server - "sieve" nil t)) + (let* ((auth-info (auth-source-search :host sieve-manage-server + :port "sieve" + :max 1)) + (user-name (plist-get (nth 0 auth-info) :user)) + (user-password (plist-get (nth 0 auth-info) :secret)) + (user-password (if (functionp user-password) + (funcall user-password) + user-password)) (client (sasl-make-client (sasl-find-mechanism (list mech)) - (car user-password) "sieve" sieve-manage-server)) + user-name "sieve" sieve-manage-server)) (sasl-read-passphrase ;; We *need* to copy the password, because sasl will modify it ;; somehow. - `(lambda (prompt) ,(copy-sequence (cadr user-password)))) + `(lambda (prompt) ,(copy-sequence user-password))) (step (sasl-next-step client nil)) (tag (sieve-manage-send (concat |