summaryrefslogtreecommitdiff
path: root/lisp/gnus
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/gnus')
-rw-r--r--lisp/gnus/ChangeLog322
-rw-r--r--lisp/gnus/ChangeLog.22
-rw-r--r--lisp/gnus/auth-source.el1408
-rw-r--r--lisp/gnus/gnus-art.el23
-rw-r--r--lisp/gnus/gnus-delay.el4
-rw-r--r--lisp/gnus/gnus-msg.el8
-rw-r--r--lisp/gnus/gnus-start.el24
-rw-r--r--lisp/gnus/gnus-sum.el38
-rw-r--r--lisp/gnus/gnus-util.el9
-rw-r--r--lisp/gnus/gnus.el2
-rw-r--r--lisp/gnus/mail-source.el88
-rw-r--r--lisp/gnus/message.el5
-rw-r--r--lisp/gnus/mml2015.el2
-rw-r--r--lisp/gnus/nnfolder.el6
-rw-r--r--lisp/gnus/nnimap.el178
-rw-r--r--lisp/gnus/nntp.el40
-rw-r--r--lisp/gnus/shr-color.el14
-rw-r--r--lisp/gnus/sieve-manage.el18
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