Subject: bug#22014: 24.4; RFC 5322 Disallows Multiple 'To' Fields in Mail Headers

From: Richard Stallman <[email-masked]>
Date: Thu, 26 Nov 2015 09:46:32 -0500

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  >   The ‘To’, ‘CC’, and ‘BCC’ fields can appear any number of times ...

  > This is in contravention to RFC 5322, the current standard for mail
  > headers.

What year did they do that?  I suspect they changed the spec subsequently
to our implementing it.

  >   On the other hand, it would be nice if Emacs
  > tried to be conformant in this regard, by detecting and consolidating
  > multiple To:, CC:, or BCC: fields.

Emacs must not override the user's decision!

The reason to us multiple To or CC headers
is to group the names in a meaningful way.
Consolidating them automatically would make the header less clear.

I wrote this change for sendmail.el, which asks the user what to do.
But I have not felt like fighting with git to install it.  Would
someone else like to install it?  Maybe the same code can work for msg
mode; I never use that.

diff -u /home/rms/emacs-git/build-aug-12/lisp/mail/sendmail.el.\~1\~ /home/rms/emacs-git/build-aug-12/lisp/mail/sendmail.el
--- /home/rms/emacs-git/build-aug-12/lisp/mail/sendmail.el.~1~	2015-08-12 11:15:38.785756144 -0400
+++ /home/rms/emacs-git/build-aug-12/lisp/mail/sendmail.el	2015-08-12 11:24:47.048474821 -0400
@@ -907,6 +907,8 @@
                 (concat "\\(?:[[:space:];,]\\|\\`\\)"
                         (regexp-opt mail-mailing-lists t)
                         "\\(?:[[:space:];,]\\|\\'\\)"))))
+        (mail-combine-fields "To")
+        (mail-combine-fields "CC")
 	;; If there are mailing lists defined
 	(when ml
 	  (save-excursion
@@ -1075,6 +1077,71 @@
 		 (goto-char fullname-start))))
 	   (insert ")\n")))))
 
+(defun mail-combine-fields (field)
+  "Offer to combine all FIELD fields in buffer into one FIELD field.
+If this finds multiple FIELD fields, it asks the user whether
+to combine them into one, and does so if the user says y."
+  (let ((search-pattern (format "^%s[ \t]*:" field))
+        first-to-end
+        query-asked
+        query-answer
+        (old-point (point))
+        (old-max (point-max)))
+    (save-excursion
+      (save-restriction
+        (goto-char (point-min))
+        (narrow-to-region (point-min) (mail-header-end))
+        ;; Find the first FIELD field and record where it ends.
+        (when (re-search-forward search-pattern nil t)
+          (forward-line 1)
+          (re-search-forward "^[^ \t]" nil t)
+          (beginning-of-line)
+          (setq first-to-end (point-marker))
+          (set-marker-insertion-type first-to-end t)
+          ;; Find each following FIELD field
+          ;; and combine it with the first FIELD field.
+          (while (re-search-forward search-pattern nil t)
+            ;; For the second FIELD field, ask user to
+            ;; approve combining them.
+            ;; But if the user refuse to combine them, signal error.
+            (unless query-asked
+              (save-restriction
+                ;; This is just so the screen doesn't change.
+                (narrow-to-region (point-min) old-max)
+                (goto-char old-point)
+                (setq query-asked t)
+                (if (y-or-n-p (format "Message contains multiple %s fields.  Combine? " field))
+                    (setq query-answer t))))
+            (when query-answer
+              (let ((this-to-start (line-beginning-position))
+                    this-to-end
+                    this-to)
+                (forward-line 1)
+                (re-search-forward "^[^ \t]" nil t)
+                (beginning-of-line)
+                (setq this-to-end (point))
+                ;; Get the text of this FIELD field.
+                (setq this-to (buffer-substring this-to-start this-to-end))
+                ;; Delete it.
+                (delete-region this-to-start this-to-end)
+                (save-excursion
+                  ;; Put a comma after the first FIELD field.
+                  (goto-char first-to-end)
+                  (forward-char -1)
+                  (insert ",")
+                  ;; Copy this one after it.
+                  (goto-char first-to-end)
+                  (save-excursion
+                    (insert this-to))
+                  ;; Replace the FIELD: with spaces.
+                  (looking-at search-pattern)
+                  ;; Try to preserve alignment of contents of the field
+                  (let ((prefix-length (length (match-string 0))))
+                    (replace-match " ")
+                    (dotimes (i (1- prefix-length))
+                      (insert " ")))))))
+          (set-marker first-to-end nil))))))
+
 (defun mail-encode-header (beg end)
   "Encode the mail header between BEG and END according to RFC2047.
 Return non-nil if and only if some part of the header is encoded."

Diff finished.  Wed Nov 25 21:50:29 2015

-- 
Dr Richard Stallman
President, Free Software Foundation (gnu.org, fsf.org)
Internet Hall-of-Famer (internethalloffame.org)
Skype: No way! See stallman.org/skype.html.