Update.
[elisp/epg.git] / epg.el
diff --git a/epg.el b/epg.el
index 4e16aab..e445aec 100644 (file)
--- a/epg.el
+++ b/epg.el
 
 (defvar epg-prompt-alist nil)
 
+(put 'epg-error 'error-conditions '(epg-error error))
+
 (defun epg-make-data-from-file (file)
   "Make a data object from FILE."
   (cons 'epg-data (vector file nil)))
@@ -324,18 +326,25 @@ This function is for internal use only."
     (signal 'wrong-type-argument (list 'epg-context-p context)))
   (aset (cdr context) 6 compress-algorithm))
 
-(defun epg-context-set-passphrase-callback (context
-                                                passphrase-callback)
-  "Set the function used to query passphrase."
+(defun epg-context-set-passphrase-callback (context passphrase-callback
+                                                   &optional handback)
+  "Set the function used to query passphrase.
+If optional argument HANDBACK is specified, it is passed to CALLBACK."
   (unless (eq (car-safe context) 'epg-context)
     (signal 'wrong-type-argument (list 'epg-context-p context)))
-  (aset (cdr context) 7 passphrase-callback))
-
-(defun epg-context-set-progress-callback (context progress-callback)
-  "Set the function which handles progress update."
+  (aset (cdr context) 7 (if handback
+                           (cons passphrase-callback handback)
+                         passphrase-callback)))
+
+(defun epg-context-set-progress-callback (context progress-callback
+                                                 &optional handback)
+  "Set the function which handles progress update.
+If optional argument HANDBACK is specified, it is passed to CALLBACK."
   (unless (eq (car-safe context) 'epg-context)
     (signal 'wrong-type-argument (list 'epg-context-p context)))
-  (aset (cdr context) 8 progress-callback))
+  (aset (cdr context) 8 (if handback
+                           (cons progress-callback handback)
+                         progress-callback)))
 
 (defun epg-context-set-signers (context signers)
   "Set the list of key-id for singning."
@@ -780,8 +789,8 @@ This function is for internal use only."
                                       sig-notation)))
   (aset (cdr sig-notation) 1 value))
 
-(defun epg-make-import-status (fingerprint reason new user-id signature sub-key
-                                          secret)
+(defun epg-make-import-status (fingerprint &optional reason new user-id
+                                          signature sub-key secret)
   "Return a import status object."
   (cons 'epg-import-status (vector fingerprint reason new user-id signature
                                   sub-key secret)))
@@ -829,16 +838,17 @@ This function is for internal use only."
   (aref (cdr import-status) 6))
 
 (defun epg-make-import-result (considered no-user-id imported imported-rsa
-                                         unchanged new-user-id new-sub-key
-                                         new-signature new-revocation
+                                         unchanged new-user-ids new-sub-keys
+                                         new-signatures new-revocations
                                          secret-read secret-imported
-                                         secret-unchanged not-imported)
+                                         secret-unchanged not-imported
+                                         imports)
   "Return a import result object."
   (cons 'epg-import-result (vector considered no-user-id imported imported-rsa
                                   unchanged new-user-ids new-sub-keys
                                   new-signatures new-revocations secret-read
                                   secret-imported secret-unchanged
-                                  not-imported nil)))
+                                  not-imported imports)))
 
 (defun epg-import-result-considered (import-result)
   "Return the total number of considered keys."
@@ -986,6 +996,49 @@ This function is for internal use only."
    (format "%02X " (epg-new-signature-class new-signature))
    (epg-new-signature-fingerprint new-signature)))
 
+(defun epg-import-result-to-string (import-result)
+  "Convert IMPORT-RESULT to a human readable string."
+  (concat (format "Total number processed: %d\n"
+                 (epg-import-result-considered import-result))
+         (if (> (epg-import-result-not-imported import-result) 0)
+             (format "      skipped new keys: %d\n"
+                     (epg-import-result-not-imported import-result)))
+         (if (> (epg-import-result-no-user-id import-result) 0)
+             (format "          w/o user IDs: %d\n"
+                     (epg-import-result-no-user-id import-result)))
+         (if (> (epg-import-result-imported import-result) 0)
+             (concat (format "              imported: %d"
+                             (epg-import-result-imported import-result))
+                     (if (> (epg-import-result-imported-rsa import-result) 0)
+                         (format "  (RSA: %d)"
+                                 (epg-import-result-imported-rsa
+                                  import-result)))
+                     "\n"))
+         (if (> (epg-import-result-unchanged import-result) 0)
+             (format "             unchanged: %d\n"
+                     (epg-import-result-unchanged import-result)))
+         (if (> (epg-import-result-new-user-ids import-result) 0)
+             (format "          new user IDs: %d\n"
+                     (epg-import-result-new-user-ids import-result)))
+         (if (> (epg-import-result-new-sub-keys import-result) 0)
+             (format "           new subkeys: %d\n"
+                     (epg-import-result-new-sub-keys import-result)))
+         (if (> (epg-import-result-new-signatures import-result) 0)
+             (format "        new signatures: %d\n"
+                     (epg-import-result-new-signatures import-result)))
+         (if (> (epg-import-result-new-revocations import-result) 0)
+             (format "   new key revocations: %d\n"
+                     (epg-import-result-new-revocations import-result)))
+         (if (> (epg-import-result-secret-read import-result) 0)
+             (format "      secret keys read: %d\n"
+                     (epg-import-result-secret-read import-result)))
+         (if (> (epg-import-result-secret-imported import-result) 0)
+             (format "  secret keys imported: %d\n"
+                     (epg-import-result-secret-imported import-result)))
+         (if (> (epg-import-result-secret-unchanged import-result) 0)
+             (format " secret keys unchanged: %d\n"
+                     (epg-import-result-secret-unchanged import-result)))))
+
 (defun epg--start (context args)
   "Start `epg-gpg-program' in a subprocess with given ARGS."
   (if (and (epg-context-process context)
@@ -997,12 +1050,15 @@ This function is for internal use only."
   (let* ((args (append (list "--no-tty"
                             "--status-fd" "1"
                             "--yes")
-                      (if (epg-context-progress-callback context)
-                          (list "--enable-progress-filter"))
+                      (if (string-match ":" (or (getenv "GPG_AGENT_INFO") ""))
+                          '("--use-agent"))
+                      (if (and (not (eq (epg-context-protocol context) 'CMS))
+                               (epg-context-progress-callback context))
+                          '("--enable-progress-filter"))
                       (if epg-gpg-home-directory
                           (list "--homedir" epg-gpg-home-directory))
                       (unless (eq (epg-context-protocol context) 'CMS)
-                        (list "--command-fd" "0"))
+                        '("--command-fd" "0"))
                       (if (epg-context-armor context) '("--armor"))
                       (if (epg-context-textmode context) '("--textmode"))
                       (if (epg-context-output-file context)
@@ -1166,9 +1222,10 @@ This function is for internal use only."
                  (if epg-passphrase-coding-system
                      (progn
                        (setq encoded-passphrase-with-new-line
-                             (encode-coding-string
+                             (epg--encode-coding-string
                               passphrase-with-new-line
-                              epg-passphrase-coding-system))
+                              (coding-system-change-eol-conversion
+                               epg-passphrase-coding-system 'unix)))
                        (epg--clear-string passphrase-with-new-line)
                        (setq passphrase-with-new-line nil))
                    (setq encoded-passphrase-with-new-line
@@ -1381,8 +1438,9 @@ This function is for internal use only."
               (epg-sig-notations signature))))))
 
 (defun epg--status-PROGRESS (context string)
-  (if (string-match "\\`\\([^ ]+\\) \\([^ ]\\) \\([0-9]+\\) \\([0-9]+\\)"
-                   string)
+  (if (and (epg-context-progress-callback context)
+          (string-match "\\`\\([^ ]+\\) \\([^ ]\\) \\([0-9]+\\) \\([0-9]+\\)"
+                        string))
       (funcall (if (consp (epg-context-progress-callback context))
                   (car (epg-context-progress-callback context))
                 (epg-context-progress-callback context))
@@ -1395,21 +1453,21 @@ This function is for internal use only."
                   (cdr (epg-context-progress-callback context))))))
 
 (defun epg--status-DECRYPTION_FAILED (context string)
-  (epg-context-set-result-for
-   context 'error
-   (cons '(decryption-failed)
-        (epg-context-result-for context 'error))))
+  (epg-context-set-result-for context 'decryption-failed t))
+
+(defun epg--status-DECRYPTION_OKAY (context string)
+  (epg-context-set-result-for context 'decryption-okay t))
 
 (defun epg--status-NODATA (context string)
   (epg-context-set-result-for
    context 'error
-   (cons (list 'no-data (cons 'reason (string-to-number string)))
+   (cons (cons 'no-data (string-to-number string))
         (epg-context-result-for context 'error))))
 
 (defun epg--status-UNEXPECTED (context string)
   (epg-context-set-result-for
    context 'error
-   (cons (list 'unexpected (cons 'reason (string-to-number string)))
+   (cons (cons 'unexpected (string-to-number string))
         (epg-context-result-for context 'error))))
 
 (defun epg--status-KEYEXPIRED (context string)
@@ -1452,8 +1510,8 @@ This function is for internal use only."
   (if (string-match "\\`\\([0-9]+\\)" string)
       (epg-context-set-result-for
        context 'error
-       (cons (list 'delete-problem
-                  (cons 'reason (string-to-number (match-string 1 string))))
+       (cons (cons 'delete-problem
+                  (string-to-number (match-string 1 string)))
             (epg-context-result-for context 'error)))))
 
 (defun epg--status-SIG_CREATED (context string)
@@ -1526,22 +1584,20 @@ This function is for internal use only."
 \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)" string)
     (epg-context-set-result-for
      context 'import
-     (cons (epg-make-import-result
-           (string-to-number (match-string 1 string))
-           (string-to-number (match-string 2 string))
-           (string-to-number (match-string 3 string))
-           (string-to-number (match-string 4 string))
-           (string-to-number (match-string 5 string))
-           (string-to-number (match-string 6 string))
-           (string-to-number (match-string 7 string))
-           (string-to-number (match-string 8 string))
-           (string-to-number (match-string 9 string))
-           (string-to-number (match-string 10 string))
-           (string-to-number (match-string 11 string))
-           (string-to-number (match-string 12 string))
-           (string-to-number (match-string 13 string))
-           (epg-context-result-for context 'import-status))
-          (epg-context-result-for context 'import)))
+     (epg-make-import-result (string-to-number (match-string 1 string))
+                            (string-to-number (match-string 2 string))
+                            (string-to-number (match-string 3 string))
+                            (string-to-number (match-string 4 string))
+                            (string-to-number (match-string 5 string))
+                            (string-to-number (match-string 6 string))
+                            (string-to-number (match-string 7 string))
+                            (string-to-number (match-string 8 string))
+                            (string-to-number (match-string 9 string))
+                            (string-to-number (match-string 10 string))
+                            (string-to-number (match-string 11 string))
+                            (string-to-number (match-string 12 string))
+                            (string-to-number (match-string 13 string))
+                            (epg-context-result-for context 'import-status)))
     (epg-context-set-result-for context 'import-status nil)))
 
 (defun epg-passphrase-callback-function (context key-id handback)
@@ -1744,6 +1800,10 @@ You can then use `write-region' to write new data into the file."
   (defun epg--clear-string (string)
     (fillarray string 0)))
 
+(if (fboundp 'encode-coding-string)
+    (defalias 'epg--encode-coding-string 'encode-coding-string)
+  (defalias 'epg--encode-coding-string 'identity))
+
 (defun epg--args-from-sig-notations (notations)
   (apply #'nconc
         (mapcar
@@ -1775,7 +1835,7 @@ You can then use `write-region' to write new data into the file."
               (epg-context-result-for epg-context 'error)))))
   (if (eq (process-status (epg-context-process context)) 'run)
       (delete-process (epg-context-process context))))
-  
+
 ;;;###autoload
 (defun epg-start-decrypt (context cipher)
   "Initiate a decrypt operation on CIPHER.
@@ -1795,6 +1855,19 @@ If you are unsure, use synchronous version of this function
   (unless (eq (epg-context-protocol context) 'CMS)
     (epg-wait-for-status context '("BEGIN_DECRYPTION"))))
 
+(defun epg--check-error-for-decrypt (context)
+  (if (epg-context-result-for context 'decryption-failed)
+      (signal 'epg-error (list "Decryption failed")))
+  (if (epg-context-result-for context 'no-secret-key)
+      (signal 'epg-error
+             (list "No secret key"
+                   (epg-context-result-for context 'no-secret-key))))
+    (unless (epg-context-result-for context 'decryption-okay)
+      (let* ((error (epg-context-result-for context 'error)))
+       (if (assq 'no-data error)
+           (signal 'epg-error (list "No data")))
+       (signal 'epg-error (list "Can't decrypt" error)))))
+
 ;;;###autoload
 (defun epg-decrypt-file (context cipher plain)
   "Decrypt a file CIPHER and store the result to a file PLAIN.
@@ -1807,9 +1880,7 @@ If PLAIN is nil, it returns the result as a string."
                                       (epg--make-temp-file "epg-output")))
        (epg-start-decrypt context (epg-make-data-from-file cipher))
        (epg-wait-for-completion context)
-       (if (epg-context-result-for context 'error)
-           (error "Decrypt failed: %S"
-                  (epg-context-result-for context 'error)))
+       (epg--check-error-for-decrypt context)
        (unless plain
          (epg-read-output context)))
     (unless plain
@@ -1828,9 +1899,7 @@ If PLAIN is nil, it returns the result as a string."
                                       (epg--make-temp-file "epg-output"))
          (epg-start-decrypt context (epg-make-data-from-file input-file))
          (epg-wait-for-completion context)
-         (if (epg-context-result-for context 'error)
-             (error "Decrypt failed: %S"
-                    (epg-context-result-for context 'error)))
+         (epg--check-error-for-decrypt context)
          (epg-read-output context))
       (epg-delete-output-file context)
       (if (file-exists-p input-file)
@@ -2155,7 +2224,7 @@ If you are unsure, use synchronous version of this function
   "Extract public KEYS."
   (unwind-protect
       (progn
-       (if keys
+       (if file
            (epg-context-set-output-file context file)
          (epg-context-set-output-file context
                                       (epg--make-temp-file "epg-output")))
@@ -2261,11 +2330,11 @@ If you are unsure, use synchronous version of this function
   (epg--start context (cons (if allow-secret
                               "--delete-secret-key"
                             "--delete-key")
-                          (mapcar
-                           (lambda (key)
-                             (epg-sub-key-id
-                              (car (epg-key-sub-key-list key))))
-                           keys))))
+                           (mapcar
+                            (lambda (key)
+                              (epg-sub-key-id
+                               (car (epg-key-sub-key-list key))))
+                            keys))))
 
 ;;;###autoload
 (defun epg-delete-keys (context keys &optional allow-secret)
@@ -2274,9 +2343,13 @@ If you are unsure, use synchronous version of this function
       (progn
        (epg-start-delete-keys context keys allow-secret)
        (epg-wait-for-completion context)
-       (if (epg-context-result-for context 'error)
-           (error "Delete keys failed: %S"
-                  (epg-context-result-for context 'error))))
+       (let ((entry (assq 'delete-problem
+                          (epg-context-result-for context 'error))))
+         (if entry
+             (if (setq entry (assq (cdr entry)
+                                   epg-delete-problem-reason-alist))
+                 (error "Delete keys failed: %s" (cdr entry))
+               (error "Delete keys failed")))))
     (epg-reset context)))
 
 ;;;###autoload