diff options
| author | Lars Ingebrigtsen | 2016-02-24 13:04:03 +1100 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2016-02-24 13:04:03 +1100 |
| commit | 21fe2ebec8b63d5fd0a570ed0c907802ab83f991 (patch) | |
| tree | f7fe7b6b4b2a21667cb66a1fdf7d470c7ec292a0 /lisp/net | |
| parent | e1d749bd7e0d68ab063eae3927caede6039a33cf (diff) | |
| download | emacs-21fe2ebec8b63d5fd0a570ed0c907802ab83f991.tar.gz emacs-21fe2ebec8b63d5fd0a570ed0c907802ab83f991.zip | |
Move low-level library files from the lisp/gnus directory
The files moved from lisp/gnus are:
auth-source.el -> /
compface.el -> /image
ecomplete.el -> /
flow-fill.el -> /mail
gravatar.el -> /image
gssapi.el -> /net
html2text.el -> /net
ietf-drums.el -> /mail
mail-parse.el -> /mail
mail-prsvr.el -> /mail
mailcap.el -> /net
plstore.el -> /
pop3.el -> /net
qp.el -> /mail
registry.el -> /
rfc1843.el -> /international
rfc2045.el -> /mail
rfc2047.el -> /mail
rfc2231.el -> /mail
rtree.el -> /
sieve-manage.el -> /net
sieve-mode.el -> /net
sieve.el -> /net
starttls.el -> /net
utf7.el -> /international
yenc.el -> /mail
Diffstat (limited to 'lisp/net')
| -rw-r--r-- | lisp/net/html2text.el | 461 | ||||
| -rw-r--r-- | lisp/net/mailcap.el | 1054 | ||||
| -rw-r--r-- | lisp/net/pop3.el | 914 | ||||
| -rw-r--r-- | lisp/net/sieve-manage.el | 575 | ||||
| -rw-r--r-- | lisp/net/sieve-mode.el | 221 | ||||
| -rw-r--r-- | lisp/net/sieve.el | 372 | ||||
| -rw-r--r-- | lisp/net/starttls.el | 304 |
7 files changed, 3901 insertions, 0 deletions
diff --git a/lisp/net/html2text.el b/lisp/net/html2text.el new file mode 100644 index 00000000000..2b1c2057bb4 --- /dev/null +++ b/lisp/net/html2text.el | |||
| @@ -0,0 +1,461 @@ | |||
| 1 | ;;; html2text.el --- a simple html to plain text converter -*- coding: utf-8 -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2002-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Joakim Hove <hove@phys.ntnu.no> | ||
| 6 | |||
| 7 | ;; This file is part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 10 | ;; it under the terms of the GNU General Public License as published by | ||
| 11 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 12 | ;; (at your option) any later version. | ||
| 13 | |||
| 14 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | ;; GNU General Public License for more details. | ||
| 18 | |||
| 19 | ;; You should have received a copy of the GNU General Public License | ||
| 20 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 21 | |||
| 22 | ;;; Commentary: | ||
| 23 | |||
| 24 | ;; These functions provide a simple way to wash/clean html infected | ||
| 25 | ;; mails. Definitely do not work in all cases, but some improvement | ||
| 26 | ;; in readability is generally obtained. Formatting is only done in | ||
| 27 | ;; the buffer, so the next time you enter the article it will be | ||
| 28 | ;; "re-htmlized". | ||
| 29 | ;; | ||
| 30 | ;; The main function is `html2text'. | ||
| 31 | |||
| 32 | ;;; Code: | ||
| 33 | |||
| 34 | ;; | ||
| 35 | ;; <Global variables> | ||
| 36 | ;; | ||
| 37 | |||
| 38 | (eval-when-compile | ||
| 39 | (require 'cl)) | ||
| 40 | |||
| 41 | (defvar html2text-format-single-element-list '(("hr" . html2text-clean-hr))) | ||
| 42 | |||
| 43 | (defvar html2text-replace-list | ||
| 44 | '(("´" . "`") | ||
| 45 | ("&" . "&") | ||
| 46 | ("'" . "'") | ||
| 47 | ("¦" . "|") | ||
| 48 | ("¢" . "c") | ||
| 49 | ("ˆ" . "^") | ||
| 50 | ("©" . "(C)") | ||
| 51 | ("¤" . "(#)") | ||
| 52 | ("°" . "degree") | ||
| 53 | ("÷" . "/") | ||
| 54 | ("€" . "e") | ||
| 55 | ("½" . "1/2") | ||
| 56 | (">" . ">") | ||
| 57 | ("¿" . "?") | ||
| 58 | ("«" . "<<") | ||
| 59 | ("&ldquo" . "\"") | ||
| 60 | ("‹" . "(") | ||
| 61 | ("‘" . "`") | ||
| 62 | ("<" . "<") | ||
| 63 | ("—" . "--") | ||
| 64 | (" " . " ") | ||
| 65 | ("–" . "-") | ||
| 66 | ("‰" . "%%") | ||
| 67 | ("±" . "+-") | ||
| 68 | ("£" . "£") | ||
| 69 | (""" . "\"") | ||
| 70 | ("»" . ">>") | ||
| 71 | ("&rdquo" . "\"") | ||
| 72 | ("®" . "(R)") | ||
| 73 | ("›" . ")") | ||
| 74 | ("’" . "'") | ||
| 75 | ("§" . "§") | ||
| 76 | ("¹" . "^1") | ||
| 77 | ("²" . "^2") | ||
| 78 | ("³" . "^3") | ||
| 79 | ("˜" . "~")) | ||
| 80 | "The map of entity to text. | ||
| 81 | |||
| 82 | This is an alist were each element is a dotted pair consisting of an | ||
| 83 | old string, and a replacement string. This replacement is done by the | ||
| 84 | function `html2text-substitute' which basically performs a | ||
| 85 | `replace-string' operation for every element in the list. This is | ||
| 86 | completely verbatim - without any use of REGEXP.") | ||
| 87 | |||
| 88 | (defvar html2text-remove-tag-list | ||
| 89 | '("html" "body" "p" "img" "dir" "head" "div" "br" "font" "title" "meta") | ||
| 90 | "A list of removable tags. | ||
| 91 | |||
| 92 | This is a list of tags which should be removed, without any | ||
| 93 | formatting. Note that tags in the list are presented *without* | ||
| 94 | any \"<\" or \">\". All occurrences of a tag appearing in this | ||
| 95 | list are removed, irrespective of whether it is a closing or | ||
| 96 | opening tag, or if the tag has additional attributes. The | ||
| 97 | deletion is done by the function `html2text-remove-tags'. | ||
| 98 | |||
| 99 | For instance the text: | ||
| 100 | |||
| 101 | \"Here comes something <font size\"+3\" face=\"Helvetica\"> big </font>.\" | ||
| 102 | |||
| 103 | will be reduced to: | ||
| 104 | |||
| 105 | \"Here comes something big.\" | ||
| 106 | |||
| 107 | If this list contains the element \"font\".") | ||
| 108 | |||
| 109 | (defvar html2text-format-tag-list | ||
| 110 | '(("b" . html2text-clean-bold) | ||
| 111 | ("strong" . html2text-clean-bold) | ||
| 112 | ("u" . html2text-clean-underline) | ||
| 113 | ("i" . html2text-clean-italic) | ||
| 114 | ("em" . html2text-clean-italic) | ||
| 115 | ("blockquote" . html2text-clean-blockquote) | ||
| 116 | ("a" . html2text-clean-anchor) | ||
| 117 | ("ul" . html2text-clean-ul) | ||
| 118 | ("ol" . html2text-clean-ol) | ||
| 119 | ("dl" . html2text-clean-dl) | ||
| 120 | ("center" . html2text-clean-center)) | ||
| 121 | "An alist of tags and processing functions. | ||
| 122 | |||
| 123 | This is an alist where each dotted pair consists of a tag, and then | ||
| 124 | the name of a function to be called when this tag is found. The | ||
| 125 | function is called with the arguments p1, p2, p3 and p4. These are | ||
| 126 | demonstrated below: | ||
| 127 | |||
| 128 | \"<b> This is bold text </b>\" | ||
| 129 | ^ ^ ^ ^ | ||
| 130 | | | | | | ||
| 131 | p1 p2 p3 p4 | ||
| 132 | |||
| 133 | Then the called function will typically format the text somewhat and | ||
| 134 | remove the tags.") | ||
| 135 | |||
| 136 | (defvar html2text-remove-tag-list2 '("li" "dt" "dd" "meta") | ||
| 137 | "Another list of removable tags. | ||
| 138 | |||
| 139 | This is a list of tags which are removed similarly to the list | ||
| 140 | `html2text-remove-tag-list' - but these tags are retained for the | ||
| 141 | formatting, and then moved afterward.") | ||
| 142 | |||
| 143 | ;; | ||
| 144 | ;; </Global variables> | ||
| 145 | ;; | ||
| 146 | |||
| 147 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 148 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 149 | |||
| 150 | ;; | ||
| 151 | ;; <Utility functions> | ||
| 152 | ;; | ||
| 153 | |||
| 154 | |||
| 155 | (defun html2text-replace-string (from-string to-string min max) | ||
| 156 | "Replace FROM-STRING with TO-STRING in region from MIN to MAX." | ||
| 157 | (goto-char min) | ||
| 158 | (let ((delta (- (string-width to-string) (string-width from-string))) | ||
| 159 | (change 0)) | ||
| 160 | (while (search-forward from-string max t) | ||
| 161 | (replace-match to-string) | ||
| 162 | (setq change (+ change delta))) | ||
| 163 | change)) | ||
| 164 | |||
| 165 | ;; | ||
| 166 | ;; </Utility functions> | ||
| 167 | ;; | ||
| 168 | |||
| 169 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 170 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 171 | |||
| 172 | ;; | ||
| 173 | ;; <Functions related to attributes> i.e. <font size=+3> | ||
| 174 | ;; | ||
| 175 | |||
| 176 | (defun html2text-attr-value (list attribute) | ||
| 177 | "Get value of ATTRIBUTE from LIST." | ||
| 178 | (nth 1 (assoc attribute list))) | ||
| 179 | |||
| 180 | (defun html2text-get-attr (p1 p2) | ||
| 181 | (goto-char p1) | ||
| 182 | (re-search-forward "\\s-+" p2 t) | ||
| 183 | (let (attr-list) | ||
| 184 | (while (re-search-forward "[-a-z0-9._]+" p2 t) | ||
| 185 | (setq attr-list | ||
| 186 | (cons | ||
| 187 | (list (match-string 0) | ||
| 188 | (when (looking-at "\\s-*=") | ||
| 189 | (goto-char (match-end 0)) | ||
| 190 | (skip-chars-forward "[:space:]") | ||
| 191 | (when (or (looking-at "\"[^\"]*\"\\|'[^']*'") | ||
| 192 | (looking-at "[-a-z0-9._:]+")) | ||
| 193 | (goto-char (match-end 0)) | ||
| 194 | (match-string 0)))) | ||
| 195 | attr-list))) | ||
| 196 | attr-list)) | ||
| 197 | |||
| 198 | ;; | ||
| 199 | ;; </Functions related to attributes> | ||
| 200 | ;; | ||
| 201 | |||
| 202 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 203 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 204 | |||
| 205 | ;; | ||
| 206 | ;; <Functions to be called to format a tag-pair> | ||
| 207 | ;; | ||
| 208 | (defun html2text-clean-list-items (p1 p2 list-type) | ||
| 209 | (goto-char p1) | ||
| 210 | (let ((item-nr 0) | ||
| 211 | (items 0)) | ||
| 212 | (while (search-forward "<li>" p2 t) | ||
| 213 | (setq items (1+ items))) | ||
| 214 | (goto-char p1) | ||
| 215 | (while (< item-nr items) | ||
| 216 | (setq item-nr (1+ item-nr)) | ||
| 217 | (search-forward "<li>" (point-max) t) | ||
| 218 | (cond | ||
| 219 | ((string= list-type "ul") (insert " o ")) | ||
| 220 | ((string= list-type "ol") (insert (format " %s: " item-nr))) | ||
| 221 | (t (insert " x ")))))) | ||
| 222 | |||
| 223 | (defun html2text-clean-dtdd (p1 p2) | ||
| 224 | (goto-char p1) | ||
| 225 | (let ((items 0) | ||
| 226 | (item-nr 0)) | ||
| 227 | (while (search-forward "<dt>" p2 t) | ||
| 228 | (setq items (1+ items))) | ||
| 229 | (goto-char p1) | ||
| 230 | (while (< item-nr items) | ||
| 231 | (setq item-nr (1+ item-nr)) | ||
| 232 | (re-search-forward "<dt>\\([ ]*\\)" (point-max) t) | ||
| 233 | (when (match-string 1) | ||
| 234 | (delete-region (point) (- (point) (string-width (match-string 1))))) | ||
| 235 | (let ((def-p1 (point)) | ||
| 236 | (def-p2 0)) | ||
| 237 | (re-search-forward "\\([ ]*\\)\\(</dt>\\|<dd>\\)" (point-max) t) | ||
| 238 | (if (match-string 1) | ||
| 239 | (progn | ||
| 240 | (let* ((mw1 (string-width (match-string 1))) | ||
| 241 | (mw2 (string-width (match-string 2))) | ||
| 242 | (mw (+ mw1 mw2))) | ||
| 243 | (goto-char (- (point) mw)) | ||
| 244 | (delete-region (point) (+ (point) mw1)) | ||
| 245 | (setq def-p2 (point)))) | ||
| 246 | (setq def-p2 (- (point) (string-width (match-string 2))))) | ||
| 247 | (put-text-property def-p1 def-p2 'face 'bold))))) | ||
| 248 | |||
| 249 | (defun html2text-delete-tags (p1 p2 p3 p4) | ||
| 250 | (delete-region p1 p2) | ||
| 251 | (delete-region (- p3 (- p2 p1)) (- p4 (- p2 p1)))) | ||
| 252 | |||
| 253 | (defun html2text-delete-single-tag (p1 p2) | ||
| 254 | (delete-region p1 p2)) | ||
| 255 | |||
| 256 | (defun html2text-clean-hr (p1 p2) | ||
| 257 | (html2text-delete-single-tag p1 p2) | ||
| 258 | (goto-char p1) | ||
| 259 | (newline 1) | ||
| 260 | (insert (make-string fill-column ?-))) | ||
| 261 | |||
| 262 | (defun html2text-clean-ul (p1 p2 p3 p4) | ||
| 263 | (html2text-delete-tags p1 p2 p3 p4) | ||
| 264 | (html2text-clean-list-items p1 (- p3 (- p1 p2)) "ul")) | ||
| 265 | |||
| 266 | (defun html2text-clean-ol (p1 p2 p3 p4) | ||
| 267 | (html2text-delete-tags p1 p2 p3 p4) | ||
| 268 | (html2text-clean-list-items p1 (- p3 (- p1 p2)) "ol")) | ||
| 269 | |||
| 270 | (defun html2text-clean-dl (p1 p2 p3 p4) | ||
| 271 | (html2text-delete-tags p1 p2 p3 p4) | ||
| 272 | (html2text-clean-dtdd p1 (- p3 (- p1 p2)))) | ||
| 273 | |||
| 274 | (defun html2text-clean-center (p1 p2 p3 p4) | ||
| 275 | (html2text-delete-tags p1 p2 p3 p4) | ||
| 276 | (center-region p1 (- p3 (- p2 p1)))) | ||
| 277 | |||
| 278 | (defun html2text-clean-bold (p1 p2 p3 p4) | ||
| 279 | (put-text-property p2 p3 'face 'bold) | ||
| 280 | (html2text-delete-tags p1 p2 p3 p4)) | ||
| 281 | |||
| 282 | (defun html2text-clean-title (p1 p2 p3 p4) | ||
| 283 | (put-text-property p2 p3 'face 'bold) | ||
| 284 | (html2text-delete-tags p1 p2 p3 p4)) | ||
| 285 | |||
| 286 | (defun html2text-clean-underline (p1 p2 p3 p4) | ||
| 287 | (put-text-property p2 p3 'face 'underline) | ||
| 288 | (html2text-delete-tags p1 p2 p3 p4)) | ||
| 289 | |||
| 290 | (defun html2text-clean-italic (p1 p2 p3 p4) | ||
| 291 | (put-text-property p2 p3 'face 'italic) | ||
| 292 | (html2text-delete-tags p1 p2 p3 p4)) | ||
| 293 | |||
| 294 | (defun html2text-clean-font (p1 p2 p3 p4) | ||
| 295 | (html2text-delete-tags p1 p2 p3 p4)) | ||
| 296 | |||
| 297 | (defun html2text-clean-blockquote (p1 p2 p3 p4) | ||
| 298 | (html2text-delete-tags p1 p2 p3 p4)) | ||
| 299 | |||
| 300 | (defun html2text-clean-anchor (p1 p2 p3 p4) | ||
| 301 | ;; If someone can explain how to make the URL clickable I will surely | ||
| 302 | ;; improve upon this. | ||
| 303 | ;; Maybe `goto-addr.el' can be used here. | ||
| 304 | (let* ((attr-list (html2text-get-attr p1 p2)) | ||
| 305 | (href (html2text-attr-value attr-list "href"))) | ||
| 306 | (delete-region p1 p4) | ||
| 307 | (when href | ||
| 308 | (goto-char p1) | ||
| 309 | (insert (if (string-match "\\`['\"].*['\"]\\'" href) | ||
| 310 | (substring href 1 -1) href)) | ||
| 311 | (put-text-property p1 (point) 'face 'bold)))) | ||
| 312 | |||
| 313 | ;; | ||
| 314 | ;; </Functions to be called to format a tag-pair> | ||
| 315 | ;; | ||
| 316 | |||
| 317 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 318 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 319 | |||
| 320 | ;; | ||
| 321 | ;; <Functions to be called to fix up paragraphs> | ||
| 322 | ;; | ||
| 323 | |||
| 324 | (defun html2text-fix-paragraph (p1 p2) | ||
| 325 | (goto-char p1) | ||
| 326 | (let ((refill-start) | ||
| 327 | (refill-stop)) | ||
| 328 | (when (re-search-forward "<br>$" p2 t) | ||
| 329 | (goto-char p1) | ||
| 330 | (when (re-search-forward ".+[^<][^b][^r][^>]$" p2 t) | ||
| 331 | (beginning-of-line) | ||
| 332 | (setq refill-start (point)) | ||
| 333 | (goto-char p2) | ||
| 334 | (re-search-backward ".+[^<][^b][^r][^>]$" refill-start t) | ||
| 335 | (forward-line 1) | ||
| 336 | (end-of-line) | ||
| 337 | ;; refill-stop should ideally be adjusted to | ||
| 338 | ;; accommodate the "<br>" strings which are removed | ||
| 339 | ;; between refill-start and refill-stop. Can simply | ||
| 340 | ;; be returned from my-replace-string | ||
| 341 | (setq refill-stop (+ (point) | ||
| 342 | (html2text-replace-string | ||
| 343 | "<br>" "" | ||
| 344 | refill-start (point)))) | ||
| 345 | ;; (message "Point = %s refill-stop = %s" (point) refill-stop) | ||
| 346 | ;; (sleep-for 4) | ||
| 347 | (fill-region refill-start refill-stop)))) | ||
| 348 | (html2text-replace-string "<br>" "" p1 p2)) | ||
| 349 | |||
| 350 | ;; | ||
| 351 | ;; This one is interactive ... | ||
| 352 | ;; | ||
| 353 | (defun html2text-fix-paragraphs () | ||
| 354 | "This _tries_ to fix up the paragraphs - this is done in quite a ad-hook | ||
| 355 | fashion, quite close to pure guess-work. It does work in some cases though." | ||
| 356 | (interactive) | ||
| 357 | (goto-char (point-min)) | ||
| 358 | (while (re-search-forward "^<br>$" nil t) | ||
| 359 | (delete-region (match-beginning 0) (match-end 0))) | ||
| 360 | ;; Removing lonely <br> on a single line, if they are left intact we | ||
| 361 | ;; don't have any paragraphs at all. | ||
| 362 | (goto-char (point-min)) | ||
| 363 | (while (not (eobp)) | ||
| 364 | (let ((p1 (point))) | ||
| 365 | (forward-paragraph 1) | ||
| 366 | ;;(message "Kaller fix med p1=%s p2=%s " p1 (1- (point))) (sleep-for 5) | ||
| 367 | (html2text-fix-paragraph p1 (1- (point))) | ||
| 368 | (goto-char p1) | ||
| 369 | (when (not (eobp)) | ||
| 370 | (forward-paragraph 1))))) | ||
| 371 | |||
| 372 | ;; | ||
| 373 | ;; </Functions to be called to fix up paragraphs> | ||
| 374 | ;; | ||
| 375 | |||
| 376 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 377 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 378 | |||
| 379 | ;; | ||
| 380 | ;; <Interactive functions> | ||
| 381 | ;; | ||
| 382 | |||
| 383 | (defun html2text-remove-tags (tag-list) | ||
| 384 | "Removes the tags listed in the list `html2text-remove-tag-list'. | ||
| 385 | See the documentation for that variable." | ||
| 386 | (interactive) | ||
| 387 | (dolist (tag tag-list) | ||
| 388 | (goto-char (point-min)) | ||
| 389 | (while (re-search-forward (format "\\(</?%s[^>]*>\\)" tag) (point-max) t) | ||
| 390 | (delete-region (match-beginning 0) (match-end 0))))) | ||
| 391 | |||
| 392 | (defun html2text-format-tags () | ||
| 393 | "See the variable `html2text-format-tag-list' for documentation." | ||
| 394 | (interactive) | ||
| 395 | (dolist (tag-and-function html2text-format-tag-list) | ||
| 396 | (let ((tag (car tag-and-function)) | ||
| 397 | (function (cdr tag-and-function))) | ||
| 398 | (goto-char (point-min)) | ||
| 399 | (while (re-search-forward (format "\\(<%s\\( [^>]*\\)?>\\)" tag) | ||
| 400 | (point-max) t) | ||
| 401 | (let ((p1) | ||
| 402 | (p2 (point)) | ||
| 403 | (p3) (p4)) | ||
| 404 | (search-backward "<" (point-min) t) | ||
| 405 | (setq p1 (point)) | ||
| 406 | (unless (search-forward (format "</%s>" tag) (point-max) t) | ||
| 407 | (goto-char p2) | ||
| 408 | (insert (format "</%s>" tag))) | ||
| 409 | (setq p4 (point)) | ||
| 410 | (search-backward "</" (point-min) t) | ||
| 411 | (setq p3 (point)) | ||
| 412 | (funcall function p1 p2 p3 p4) | ||
| 413 | (goto-char p1)))))) | ||
| 414 | |||
| 415 | (defun html2text-substitute () | ||
| 416 | "See the variable `html2text-replace-list' for documentation." | ||
| 417 | (interactive) | ||
| 418 | (dolist (e html2text-replace-list) | ||
| 419 | (goto-char (point-min)) | ||
| 420 | (let ((old-string (car e)) | ||
| 421 | (new-string (cdr e))) | ||
| 422 | (html2text-replace-string old-string new-string (point-min) (point-max))))) | ||
| 423 | |||
| 424 | (defun html2text-format-single-elements () | ||
| 425 | (interactive) | ||
| 426 | (dolist (tag-and-function html2text-format-single-element-list) | ||
| 427 | (let ((tag (car tag-and-function)) | ||
| 428 | (function (cdr tag-and-function))) | ||
| 429 | (goto-char (point-min)) | ||
| 430 | (while (re-search-forward (format "\\(<%s\\( [^>]*\\)?>\\)" tag) | ||
| 431 | (point-max) t) | ||
| 432 | (let ((p1) | ||
| 433 | (p2 (point))) | ||
| 434 | (search-backward "<" (point-min) t) | ||
| 435 | (setq p1 (point)) | ||
| 436 | (funcall function p1 p2)))))) | ||
| 437 | |||
| 438 | ;; | ||
| 439 | ;; Main function | ||
| 440 | ;; | ||
| 441 | |||
| 442 | ;;;###autoload | ||
| 443 | (defun html2text () | ||
| 444 | "Convert HTML to plain text in the current buffer." | ||
| 445 | (interactive) | ||
| 446 | (save-excursion | ||
| 447 | (let ((case-fold-search t) | ||
| 448 | (buffer-read-only)) | ||
| 449 | (html2text-remove-tags html2text-remove-tag-list) | ||
| 450 | (html2text-format-tags) | ||
| 451 | (html2text-remove-tags html2text-remove-tag-list2) | ||
| 452 | (html2text-substitute) | ||
| 453 | (html2text-format-single-elements) | ||
| 454 | (html2text-fix-paragraphs)))) | ||
| 455 | |||
| 456 | ;; | ||
| 457 | ;; </Interactive functions> | ||
| 458 | ;; | ||
| 459 | (provide 'html2text) | ||
| 460 | |||
| 461 | ;;; html2text.el ends here | ||
diff --git a/lisp/net/mailcap.el b/lisp/net/mailcap.el new file mode 100644 index 00000000000..609a8f4d64b --- /dev/null +++ b/lisp/net/mailcap.el | |||
| @@ -0,0 +1,1054 @@ | |||
| 1 | ;;; mailcap.el --- MIME media types configuration | ||
| 2 | |||
| 3 | ;; Copyright (C) 1998-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: William M. Perry <wmperry@aventail.com> | ||
| 6 | ;; Lars Magne Ingebrigtsen <larsi@gnus.org> | ||
| 7 | ;; Keywords: news, mail, multimedia | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; Provides configuration of MIME media types from directly from Lisp | ||
| 27 | ;; and via the usual mailcap mechanism (RFC 1524). Deals with | ||
| 28 | ;; mime.types similarly. | ||
| 29 | |||
| 30 | ;;; Code: | ||
| 31 | |||
| 32 | (eval-when-compile (require 'cl)) | ||
| 33 | (autoload 'mail-header-parse-content-type "mail-parse") | ||
| 34 | |||
| 35 | (defgroup mailcap nil | ||
| 36 | "Definition of viewers for MIME types." | ||
| 37 | :version "21.1" | ||
| 38 | :group 'mime) | ||
| 39 | |||
| 40 | (defvar mailcap-parse-args-syntax-table | ||
| 41 | (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table))) | ||
| 42 | (modify-syntax-entry ?' "\"" table) | ||
| 43 | (modify-syntax-entry ?` "\"" table) | ||
| 44 | (modify-syntax-entry ?{ "(" table) | ||
| 45 | (modify-syntax-entry ?} ")" table) | ||
| 46 | table) | ||
| 47 | "A syntax table for parsing SGML attributes.") | ||
| 48 | |||
| 49 | (defvar mailcap-print-command | ||
| 50 | (mapconcat 'identity | ||
| 51 | (cons (if (boundp 'lpr-command) | ||
| 52 | lpr-command | ||
| 53 | "lpr") | ||
| 54 | (when (boundp 'lpr-switches) | ||
| 55 | (if (stringp lpr-switches) | ||
| 56 | (list lpr-switches) | ||
| 57 | lpr-switches))) | ||
| 58 | " ") | ||
| 59 | "Shell command (including switches) used to print PostScript files.") | ||
| 60 | |||
| 61 | ;; Postpone using defcustom for this as it's so big and we essentially | ||
| 62 | ;; have to have two copies of the data around then. Perhaps just | ||
| 63 | ;; customize the Lisp viewers and rely on the normal configuration | ||
| 64 | ;; files for the rest? -- fx | ||
| 65 | (defvar mailcap-mime-data | ||
| 66 | `(("application" | ||
| 67 | ("vnd\\.ms-excel" | ||
| 68 | (viewer . "gnumeric %s") | ||
| 69 | (test . (getenv "DISPLAY")) | ||
| 70 | (type . "application/vnd.ms-excel")) | ||
| 71 | ("x-x509-ca-cert" | ||
| 72 | (viewer . ssl-view-site-cert) | ||
| 73 | (type . "application/x-x509-ca-cert")) | ||
| 74 | ("x-x509-user-cert" | ||
| 75 | (viewer . ssl-view-user-cert) | ||
| 76 | (type . "application/x-x509-user-cert")) | ||
| 77 | ("octet-stream" | ||
| 78 | (viewer . mailcap-save-binary-file) | ||
| 79 | (non-viewer . t) | ||
| 80 | (type . "application/octet-stream")) | ||
| 81 | ("dvi" | ||
| 82 | (viewer . "xdvi -safer %s") | ||
| 83 | (test . (eq window-system 'x)) | ||
| 84 | ("needsx11") | ||
| 85 | (type . "application/dvi") | ||
| 86 | ("print" . "dvips -qRP %s")) | ||
| 87 | ("dvi" | ||
| 88 | (viewer . "dvitty %s") | ||
| 89 | (test . (not (getenv "DISPLAY"))) | ||
| 90 | (type . "application/dvi") | ||
| 91 | ("print" . "dvips -qRP %s")) | ||
| 92 | ("emacs-lisp" | ||
| 93 | (viewer . mailcap-maybe-eval) | ||
| 94 | (type . "application/emacs-lisp")) | ||
| 95 | ("x-emacs-lisp" | ||
| 96 | (viewer . mailcap-maybe-eval) | ||
| 97 | (type . "application/x-emacs-lisp")) | ||
| 98 | ("x-tar" | ||
| 99 | (viewer . mailcap-save-binary-file) | ||
| 100 | (non-viewer . t) | ||
| 101 | (type . "application/x-tar")) | ||
| 102 | ("x-latex" | ||
| 103 | (viewer . tex-mode) | ||
| 104 | (type . "application/x-latex")) | ||
| 105 | ("x-tex" | ||
| 106 | (viewer . tex-mode) | ||
| 107 | (type . "application/x-tex")) | ||
| 108 | ("latex" | ||
| 109 | (viewer . tex-mode) | ||
| 110 | (type . "application/latex")) | ||
| 111 | ("tex" | ||
| 112 | (viewer . tex-mode) | ||
| 113 | (type . "application/tex")) | ||
| 114 | ("texinfo" | ||
| 115 | (viewer . texinfo-mode) | ||
| 116 | (type . "application/tex")) | ||
| 117 | ("zip" | ||
| 118 | (viewer . mailcap-save-binary-file) | ||
| 119 | (non-viewer . t) | ||
| 120 | (type . "application/zip") | ||
| 121 | ("copiousoutput")) | ||
| 122 | ("pdf" | ||
| 123 | (viewer . pdf-view-mode) | ||
| 124 | (type . "application/pdf") | ||
| 125 | (test . (eq window-system 'x))) | ||
| 126 | ("pdf" | ||
| 127 | (viewer . doc-view-mode) | ||
| 128 | (type . "application/pdf") | ||
| 129 | (test . (eq window-system 'x))) | ||
| 130 | ("pdf" | ||
| 131 | (viewer . "gv -safer %s") | ||
| 132 | (type . "application/pdf") | ||
| 133 | (test . window-system) | ||
| 134 | ("print" . ,(concat "pdf2ps %s - | " mailcap-print-command))) | ||
| 135 | ("pdf" | ||
| 136 | (viewer . "gpdf %s") | ||
| 137 | (type . "application/pdf") | ||
| 138 | ("print" . ,(concat "pdftops %s - | " mailcap-print-command)) | ||
| 139 | (test . (eq window-system 'x))) | ||
| 140 | ("pdf" | ||
| 141 | (viewer . "xpdf %s") | ||
| 142 | (type . "application/pdf") | ||
| 143 | ("print" . ,(concat "pdftops %s - | " mailcap-print-command)) | ||
| 144 | (test . (eq window-system 'x))) | ||
| 145 | ("pdf" | ||
| 146 | (viewer . ,(concat "pdftotext %s -")) | ||
| 147 | (type . "application/pdf") | ||
| 148 | ("print" . ,(concat "pdftops %s - | " mailcap-print-command)) | ||
| 149 | ("copiousoutput")) | ||
| 150 | ("postscript" | ||
| 151 | (viewer . "gv -safer %s") | ||
| 152 | (type . "application/postscript") | ||
| 153 | (test . window-system) | ||
| 154 | ("print" . ,(concat mailcap-print-command " %s")) | ||
| 155 | ("needsx11")) | ||
| 156 | ("postscript" | ||
| 157 | (viewer . "ghostview -dSAFER %s") | ||
| 158 | (type . "application/postscript") | ||
| 159 | (test . (eq window-system 'x)) | ||
| 160 | ("print" . ,(concat mailcap-print-command " %s")) | ||
| 161 | ("needsx11")) | ||
| 162 | ("postscript" | ||
| 163 | (viewer . "ps2ascii %s") | ||
| 164 | (type . "application/postscript") | ||
| 165 | (test . (not (getenv "DISPLAY"))) | ||
| 166 | ("print" . ,(concat mailcap-print-command " %s")) | ||
| 167 | ("copiousoutput")) | ||
| 168 | ("sieve" | ||
| 169 | (viewer . sieve-mode) | ||
| 170 | (type . "application/sieve")) | ||
| 171 | ("pgp-keys" | ||
| 172 | (viewer . "gpg --import --interactive --verbose") | ||
| 173 | (type . "application/pgp-keys") | ||
| 174 | ("needsterminal"))) | ||
| 175 | ("audio" | ||
| 176 | ("x-mpeg" | ||
| 177 | (viewer . "maplay %s") | ||
| 178 | (type . "audio/x-mpeg")) | ||
| 179 | (".*" | ||
| 180 | (viewer . "showaudio") | ||
| 181 | (type . "audio/*"))) | ||
| 182 | ("message" | ||
| 183 | ("rfc-*822" | ||
| 184 | (viewer . mm-view-message) | ||
| 185 | (test . (and (featurep 'gnus) | ||
| 186 | (gnus-alive-p))) | ||
| 187 | (type . "message/rfc822")) | ||
| 188 | ("rfc-*822" | ||
| 189 | (viewer . vm-mode) | ||
| 190 | (type . "message/rfc822")) | ||
| 191 | ("rfc-*822" | ||
| 192 | (viewer . view-mode) | ||
| 193 | (type . "message/rfc822"))) | ||
| 194 | ("image" | ||
| 195 | ("x-xwd" | ||
| 196 | (viewer . "xwud -in %s") | ||
| 197 | (type . "image/x-xwd") | ||
| 198 | ("compose" . "xwd -frame > %s") | ||
| 199 | (test . (eq window-system 'x)) | ||
| 200 | ("needsx11")) | ||
| 201 | ("x11-dump" | ||
| 202 | (viewer . "xwud -in %s") | ||
| 203 | (type . "image/x-xwd") | ||
| 204 | ("compose" . "xwd -frame > %s") | ||
| 205 | (test . (eq window-system 'x)) | ||
| 206 | ("needsx11")) | ||
| 207 | ("windowdump" | ||
| 208 | (viewer . "xwud -in %s") | ||
| 209 | (type . "image/x-xwd") | ||
| 210 | ("compose" . "xwd -frame > %s") | ||
| 211 | (test . (eq window-system 'x)) | ||
| 212 | ("needsx11")) | ||
| 213 | (".*" | ||
| 214 | (viewer . "display %s") | ||
| 215 | (type . "image/*") | ||
| 216 | (test . (eq window-system 'x)) | ||
| 217 | ("needsx11")) | ||
| 218 | (".*" | ||
| 219 | (viewer . "ee %s") | ||
| 220 | (type . "image/*") | ||
| 221 | (test . (eq window-system 'x)) | ||
| 222 | ("needsx11"))) | ||
| 223 | ("text" | ||
| 224 | ("plain" | ||
| 225 | (viewer . view-mode) | ||
| 226 | (type . "text/plain")) | ||
| 227 | ("plain" | ||
| 228 | (viewer . fundamental-mode) | ||
| 229 | (type . "text/plain")) | ||
| 230 | ("enriched" | ||
| 231 | (viewer . enriched-decode) | ||
| 232 | (type . "text/enriched")) | ||
| 233 | ("dns" | ||
| 234 | (viewer . dns-mode) | ||
| 235 | (type . "text/dns"))) | ||
| 236 | ("video" | ||
| 237 | ("mpeg" | ||
| 238 | (viewer . "mpeg_play %s") | ||
| 239 | (type . "video/mpeg") | ||
| 240 | (test . (eq window-system 'x)) | ||
| 241 | ("needsx11"))) | ||
| 242 | ("x-world" | ||
| 243 | ("x-vrml" | ||
| 244 | (viewer . "webspace -remote %s -URL %u") | ||
| 245 | (type . "x-world/x-vrml") | ||
| 246 | ("description" | ||
| 247 | "VRML document"))) | ||
| 248 | ("archive" | ||
| 249 | ("tar" | ||
| 250 | (viewer . tar-mode) | ||
| 251 | (type . "archive/tar")))) | ||
| 252 | "The mailcap structure is an assoc list of assoc lists. | ||
| 253 | 1st assoc list is keyed on the major content-type | ||
| 254 | 2nd assoc list is keyed on the minor content-type (which can be a regexp) | ||
| 255 | |||
| 256 | Which looks like: | ||
| 257 | ----------------- | ||
| 258 | ((\"application\" | ||
| 259 | (\"postscript\" . <info>)) | ||
| 260 | (\"text\" | ||
| 261 | (\"plain\" . <info>))) | ||
| 262 | |||
| 263 | Where <info> is another assoc list of the various information | ||
| 264 | related to the mailcap RFC 1524. This is keyed on the lowercase | ||
| 265 | attribute name (viewer, test, etc). This looks like: | ||
| 266 | ((viewer . VIEWERINFO) | ||
| 267 | (test . TESTINFO) | ||
| 268 | (xxxx . \"STRING\") | ||
| 269 | FLAG) | ||
| 270 | |||
| 271 | Where VIEWERINFO specifies how the content-type is viewed. Can be | ||
| 272 | a string, in which case it is run through a shell, with appropriate | ||
| 273 | parameters, or a symbol, in which case the symbol is `funcall'ed if | ||
| 274 | and only if it exists as a function, with the buffer as an argument. | ||
| 275 | |||
| 276 | TESTINFO is a test for the viewer's applicability, or nil. If nil, it | ||
| 277 | means the viewer is always valid. If it is a Lisp function, it is | ||
| 278 | called with a list of items from any extra fields from the | ||
| 279 | Content-Type header as argument to return a boolean value for the | ||
| 280 | validity. Otherwise, if it is a non-function Lisp symbol or list | ||
| 281 | whose car is a symbol, it is `eval'led to yield the validity. If it | ||
| 282 | is a string or list of strings, it represents a shell command to run | ||
| 283 | to return a true or false shell value for the validity.") | ||
| 284 | (put 'mailcap-mime-data 'risky-local-variable t) | ||
| 285 | |||
| 286 | (defcustom mailcap-download-directory nil | ||
| 287 | "*Directory to which `mailcap-save-binary-file' downloads files by default. | ||
| 288 | nil means your home directory." | ||
| 289 | :type '(choice (const :tag "Home directory" nil) | ||
| 290 | directory) | ||
| 291 | :group 'mailcap) | ||
| 292 | |||
| 293 | (defvar mailcap-poor-system-types | ||
| 294 | '(ms-dos windows-nt) | ||
| 295 | "Systems that don't have a Unix-like directory hierarchy.") | ||
| 296 | |||
| 297 | ;;; | ||
| 298 | ;;; Utility functions | ||
| 299 | ;;; | ||
| 300 | |||
| 301 | (defun mailcap-save-binary-file () | ||
| 302 | (goto-char (point-min)) | ||
| 303 | (unwind-protect | ||
| 304 | (let ((file (read-file-name | ||
| 305 | "Filename to save as: " | ||
| 306 | (or mailcap-download-directory "~/"))) | ||
| 307 | (require-final-newline nil)) | ||
| 308 | (write-region (point-min) (point-max) file)) | ||
| 309 | (kill-buffer (current-buffer)))) | ||
| 310 | |||
| 311 | (defvar mailcap-maybe-eval-warning | ||
| 312 | "*** WARNING *** | ||
| 313 | |||
| 314 | This MIME part contains untrusted and possibly harmful content. | ||
| 315 | If you evaluate the Emacs Lisp code contained in it, a lot of nasty | ||
| 316 | things can happen. Please examine the code very carefully before you | ||
| 317 | instruct Emacs to evaluate it. You can browse the buffer containing | ||
| 318 | the code using \\[scroll-other-window]. | ||
| 319 | |||
| 320 | If you are unsure what to do, please answer \"no\"." | ||
| 321 | "Text of warning message displayed by `mailcap-maybe-eval'. | ||
| 322 | Make sure that this text consists only of few text lines. Otherwise, | ||
| 323 | Gnus might fail to display all of it.") | ||
| 324 | |||
| 325 | (defun mailcap-maybe-eval () | ||
| 326 | "Maybe evaluate a buffer of Emacs Lisp code." | ||
| 327 | (let ((lisp-buffer (current-buffer))) | ||
| 328 | (goto-char (point-min)) | ||
| 329 | (when | ||
| 330 | (save-window-excursion | ||
| 331 | (delete-other-windows) | ||
| 332 | (let ((buffer (get-buffer-create (generate-new-buffer-name | ||
| 333 | "*Warning*")))) | ||
| 334 | (unwind-protect | ||
| 335 | (with-current-buffer buffer | ||
| 336 | (insert (substitute-command-keys | ||
| 337 | mailcap-maybe-eval-warning)) | ||
| 338 | (goto-char (point-min)) | ||
| 339 | (display-buffer buffer) | ||
| 340 | (yes-or-no-p "This is potentially dangerous emacs-lisp code, evaluate it? ")) | ||
| 341 | (kill-buffer buffer)))) | ||
| 342 | (eval-buffer (current-buffer))) | ||
| 343 | (when (buffer-live-p lisp-buffer) | ||
| 344 | (with-current-buffer lisp-buffer | ||
| 345 | (emacs-lisp-mode))))) | ||
| 346 | |||
| 347 | |||
| 348 | ;;; | ||
| 349 | ;;; The mailcap parser | ||
| 350 | ;;; | ||
| 351 | |||
| 352 | (defun mailcap-replace-regexp (regexp to-string) | ||
| 353 | ;; Quiet replace-regexp. | ||
| 354 | (goto-char (point-min)) | ||
| 355 | (while (re-search-forward regexp nil t) | ||
| 356 | (replace-match to-string t nil))) | ||
| 357 | |||
| 358 | (defvar mailcap-parsed-p nil) | ||
| 359 | |||
| 360 | (defun mailcap-parse-mailcaps (&optional path force) | ||
| 361 | "Parse out all the mailcaps specified in a path string PATH. | ||
| 362 | Components of PATH are separated by the `path-separator' character | ||
| 363 | appropriate for this system. If FORCE, re-parse even if already | ||
| 364 | parsed. If PATH is omitted, use the value of environment variable | ||
| 365 | MAILCAPS if set; otherwise (on Unix) use the path from RFC 1524, plus | ||
| 366 | /usr/local/etc/mailcap." | ||
| 367 | (interactive (list nil t)) | ||
| 368 | (when (or (not mailcap-parsed-p) | ||
| 369 | force) | ||
| 370 | (cond | ||
| 371 | (path nil) | ||
| 372 | ((getenv "MAILCAPS") (setq path (getenv "MAILCAPS"))) | ||
| 373 | ((memq system-type mailcap-poor-system-types) | ||
| 374 | (setq path '("~/.mailcap" "~/mail.cap" "~/etc/mail.cap"))) | ||
| 375 | (t (setq path | ||
| 376 | ;; This is per RFC 1524, specifically | ||
| 377 | ;; with /usr before /usr/local. | ||
| 378 | '("~/.mailcap" "/etc/mailcap" "/usr/etc/mailcap" | ||
| 379 | "/usr/local/etc/mailcap")))) | ||
| 380 | (let ((fnames (reverse | ||
| 381 | (if (stringp path) | ||
| 382 | (split-string path path-separator t) | ||
| 383 | path))) | ||
| 384 | fname) | ||
| 385 | (while fnames | ||
| 386 | (setq fname (car fnames)) | ||
| 387 | (if (and (file-readable-p fname) | ||
| 388 | (file-regular-p fname)) | ||
| 389 | (mailcap-parse-mailcap fname)) | ||
| 390 | (setq fnames (cdr fnames)))) | ||
| 391 | (setq mailcap-parsed-p t))) | ||
| 392 | |||
| 393 | (defun mailcap-parse-mailcap (fname) | ||
| 394 | "Parse out the mailcap file specified by FNAME." | ||
| 395 | (let (major ; The major mime type (image/audio/etc) | ||
| 396 | minor ; The minor mime type (gif, basic, etc) | ||
| 397 | save-pos ; Misc saved positions used in parsing | ||
| 398 | viewer ; How to view this mime type | ||
| 399 | info ; Misc info about this mime type | ||
| 400 | ) | ||
| 401 | (with-temp-buffer | ||
| 402 | (insert-file-contents fname) | ||
| 403 | (set-syntax-table mailcap-parse-args-syntax-table) | ||
| 404 | (mailcap-replace-regexp "#.*" "") ; Remove all comments | ||
| 405 | (mailcap-replace-regexp "\\\\[ \t]*\n" " ") ; And collapse spaces | ||
| 406 | (mailcap-replace-regexp "\n+" "\n") ; And blank lines | ||
| 407 | (goto-char (point-max)) | ||
| 408 | (skip-chars-backward " \t\n") | ||
| 409 | (delete-region (point) (point-max)) | ||
| 410 | (while (not (bobp)) | ||
| 411 | (skip-chars-backward " \t\n") | ||
| 412 | (beginning-of-line) | ||
| 413 | (setq save-pos (point) | ||
| 414 | info nil) | ||
| 415 | (skip-chars-forward "^/; \t\n") | ||
| 416 | (downcase-region save-pos (point)) | ||
| 417 | (setq major (buffer-substring save-pos (point))) | ||
| 418 | (skip-chars-forward " \t") | ||
| 419 | (setq minor "") | ||
| 420 | (when (eq (char-after) ?/) | ||
| 421 | (forward-char) | ||
| 422 | (skip-chars-forward " \t") | ||
| 423 | (setq save-pos (point)) | ||
| 424 | (skip-chars-forward "^; \t\n") | ||
| 425 | (downcase-region save-pos (point)) | ||
| 426 | (setq minor | ||
| 427 | (cond | ||
| 428 | ((eq ?* (or (char-after save-pos) 0)) ".*") | ||
| 429 | ((= (point) save-pos) ".*") | ||
| 430 | (t (regexp-quote (buffer-substring save-pos (point))))))) | ||
| 431 | (skip-chars-forward " \t") | ||
| 432 | ;;; Got the major/minor chunks, now for the viewers/etc | ||
| 433 | ;;; The first item _must_ be a viewer, according to the | ||
| 434 | ;;; RFC for mailcap files (#1524) | ||
| 435 | (setq viewer "") | ||
| 436 | (when (eq (char-after) ?\;) | ||
| 437 | (forward-char) | ||
| 438 | (skip-chars-forward " \t") | ||
| 439 | (setq save-pos (point)) | ||
| 440 | (skip-chars-forward "^;\n") | ||
| 441 | ;; skip \; | ||
| 442 | (while (eq (char-before) ?\\) | ||
| 443 | (backward-delete-char 1) | ||
| 444 | (forward-char) | ||
| 445 | (skip-chars-forward "^;\n")) | ||
| 446 | (if (eq (or (char-after save-pos) 0) ?') | ||
| 447 | (setq viewer (progn | ||
| 448 | (narrow-to-region (1+ save-pos) (point)) | ||
| 449 | (goto-char (point-min)) | ||
| 450 | (prog1 | ||
| 451 | (read (current-buffer)) | ||
| 452 | (goto-char (point-max)) | ||
| 453 | (widen)))) | ||
| 454 | (setq viewer (buffer-substring save-pos (point))))) | ||
| 455 | (setq save-pos (point)) | ||
| 456 | (end-of-line) | ||
| 457 | (unless (equal viewer "") | ||
| 458 | (setq info (nconc (list (cons 'viewer viewer) | ||
| 459 | (cons 'type (concat major "/" | ||
| 460 | (if (string= minor ".*") | ||
| 461 | "*" minor)))) | ||
| 462 | (mailcap-parse-mailcap-extras save-pos (point)))) | ||
| 463 | (mailcap-mailcap-entry-passes-test info) | ||
| 464 | (mailcap-add-mailcap-entry major minor info)) | ||
| 465 | (beginning-of-line))))) | ||
| 466 | |||
| 467 | (defun mailcap-parse-mailcap-extras (st nd) | ||
| 468 | "Grab all the extra stuff from a mailcap entry." | ||
| 469 | (let ( | ||
| 470 | name ; From name= | ||
| 471 | value ; its value | ||
| 472 | results ; Assoc list of results | ||
| 473 | name-pos ; Start of XXXX= position | ||
| 474 | val-pos ; Start of value position | ||
| 475 | done ; Found end of \'d ;s? | ||
| 476 | ) | ||
| 477 | (save-restriction | ||
| 478 | (narrow-to-region st nd) | ||
| 479 | (goto-char (point-min)) | ||
| 480 | (skip-chars-forward " \n\t;") | ||
| 481 | (while (not (eobp)) | ||
| 482 | (setq done nil) | ||
| 483 | (setq name-pos (point)) | ||
| 484 | (skip-chars-forward "^ \n\t=;") | ||
| 485 | (downcase-region name-pos (point)) | ||
| 486 | (setq name (buffer-substring name-pos (point))) | ||
| 487 | (skip-chars-forward " \t\n") | ||
| 488 | (if (not (eq (char-after (point)) ?=)) ; There is no value | ||
| 489 | (setq value t) | ||
| 490 | (skip-chars-forward " \t\n=") | ||
| 491 | (setq val-pos (point)) | ||
| 492 | (if (memq (char-after val-pos) '(?\" ?')) | ||
| 493 | (progn | ||
| 494 | (setq val-pos (1+ val-pos)) | ||
| 495 | (condition-case nil | ||
| 496 | (progn | ||
| 497 | (forward-sexp 1) | ||
| 498 | (backward-char 1)) | ||
| 499 | (error (goto-char (point-max))))) | ||
| 500 | (while (not done) | ||
| 501 | (skip-chars-forward "^;") | ||
| 502 | (if (eq (char-after (1- (point))) ?\\ ) | ||
| 503 | (progn | ||
| 504 | (subst-char-in-region (1- (point)) (point) ?\\ ? ) | ||
| 505 | (skip-chars-forward ";")) | ||
| 506 | (setq done t)))) | ||
| 507 | (setq value (buffer-substring val-pos (point)))) | ||
| 508 | ;; `test' as symbol, others like "copiousoutput" and "needsx11" as | ||
| 509 | ;; strings | ||
| 510 | (setq results (cons (cons (if (string-equal name "test") | ||
| 511 | 'test | ||
| 512 | name) | ||
| 513 | value) results)) | ||
| 514 | (skip-chars-forward " \";\n\t")) | ||
| 515 | results))) | ||
| 516 | |||
| 517 | (defun mailcap-mailcap-entry-passes-test (info) | ||
| 518 | "Replace the test clause of INFO itself with a boolean for some cases. | ||
| 519 | This function supports only `test -n $DISPLAY' and `test -z $DISPLAY', | ||
| 520 | replaces them with t or nil. As for others or if INFO has a interactive | ||
| 521 | spec (needsterm, needsterminal, or needsx11) but DISPLAY is not set, | ||
| 522 | the test clause will be unchanged." | ||
| 523 | (let ((test (assq 'test info)) ; The test clause | ||
| 524 | status) | ||
| 525 | (setq status (and test (split-string (cdr test) " "))) | ||
| 526 | (if (and (or (assoc "needsterm" info) | ||
| 527 | (assoc "needsterminal" info) | ||
| 528 | (assoc "needsx11" info)) | ||
| 529 | (not (getenv "DISPLAY"))) | ||
| 530 | (setq status nil) | ||
| 531 | (cond | ||
| 532 | ((and (equal (nth 0 status) "test") | ||
| 533 | (equal (nth 1 status) "-n") | ||
| 534 | (or (equal (nth 2 status) "$DISPLAY") | ||
| 535 | (equal (nth 2 status) "\"$DISPLAY\""))) | ||
| 536 | (setq status (if (getenv "DISPLAY") t nil))) | ||
| 537 | ((and (equal (nth 0 status) "test") | ||
| 538 | (equal (nth 1 status) "-z") | ||
| 539 | (or (equal (nth 2 status) "$DISPLAY") | ||
| 540 | (equal (nth 2 status) "\"$DISPLAY\""))) | ||
| 541 | (setq status (if (getenv "DISPLAY") nil t))) | ||
| 542 | (test nil) | ||
| 543 | (t nil))) | ||
| 544 | (and test (listp test) (setcdr test status)))) | ||
| 545 | |||
| 546 | ;;; | ||
| 547 | ;;; The action routines. | ||
| 548 | ;;; | ||
| 549 | |||
| 550 | (defun mailcap-possible-viewers (major minor) | ||
| 551 | "Return a list of possible viewers from MAJOR for minor type MINOR." | ||
| 552 | (let ((exact '()) | ||
| 553 | (wildcard '())) | ||
| 554 | (while major | ||
| 555 | (cond | ||
| 556 | ((equal (car (car major)) minor) | ||
| 557 | (setq exact (cons (cdr (car major)) exact))) | ||
| 558 | ((and minor (string-match (concat "^" (car (car major)) "$") minor)) | ||
| 559 | (setq wildcard (cons (cdr (car major)) wildcard)))) | ||
| 560 | (setq major (cdr major))) | ||
| 561 | (nconc exact wildcard))) | ||
| 562 | |||
| 563 | (defun mailcap-unescape-mime-test (test type-info) | ||
| 564 | (let (save-pos save-chr subst) | ||
| 565 | (cond | ||
| 566 | ((symbolp test) test) | ||
| 567 | ((and (listp test) (symbolp (car test))) test) | ||
| 568 | ((or (stringp test) | ||
| 569 | (and (listp test) (stringp (car test)) | ||
| 570 | (setq test (mapconcat 'identity test " ")))) | ||
| 571 | (with-temp-buffer | ||
| 572 | (insert test) | ||
| 573 | (goto-char (point-min)) | ||
| 574 | (while (not (eobp)) | ||
| 575 | (skip-chars-forward "^%") | ||
| 576 | (if (/= (- (point) | ||
| 577 | (progn (skip-chars-backward "\\\\") | ||
| 578 | (point))) | ||
| 579 | 0) ; It is an escaped % | ||
| 580 | (progn | ||
| 581 | (delete-char 1) | ||
| 582 | (skip-chars-forward "%.")) | ||
| 583 | (setq save-pos (point)) | ||
| 584 | (skip-chars-forward "%") | ||
| 585 | (setq save-chr (char-after (point))) | ||
| 586 | ;; Escapes: | ||
| 587 | ;; %s: name of a file for the body data | ||
| 588 | ;; %t: content-type | ||
| 589 | ;; %{<parameter name}: value of parameter in mailcap entry | ||
| 590 | ;; %n: number of sub-parts for multipart content-type | ||
| 591 | ;; %F: a set of content-type/filename pairs for multiparts | ||
| 592 | (cond | ||
| 593 | ((null save-chr) nil) | ||
| 594 | ((= save-chr ?t) | ||
| 595 | (delete-region save-pos (progn (forward-char 1) (point))) | ||
| 596 | (insert (or (cdr (assq 'type type-info)) "\"\""))) | ||
| 597 | ((memq save-chr '(?M ?n ?F)) | ||
| 598 | (delete-region save-pos (progn (forward-char 1) (point))) | ||
| 599 | (insert "\"\"")) | ||
| 600 | ((= save-chr ?{) | ||
| 601 | (forward-char 1) | ||
| 602 | (skip-chars-forward "^}") | ||
| 603 | (downcase-region (+ 2 save-pos) (point)) | ||
| 604 | (setq subst (buffer-substring (+ 2 save-pos) (point))) | ||
| 605 | (delete-region save-pos (1+ (point))) | ||
| 606 | (insert (or (cdr (assoc subst type-info)) "\"\""))) | ||
| 607 | (t nil)))) | ||
| 608 | (buffer-string))) | ||
| 609 | (t (error "Bad value to mailcap-unescape-mime-test: %s" test))))) | ||
| 610 | |||
| 611 | (defvar mailcap-viewer-test-cache nil) | ||
| 612 | |||
| 613 | (defun mailcap-viewer-passes-test (viewer-info type-info) | ||
| 614 | "Return non-nil if viewer specified by VIEWER-INFO passes its test clause. | ||
| 615 | Also return non-nil if it has no test clause. TYPE-INFO is an argument | ||
| 616 | to supply to the test." | ||
| 617 | (let* ((test-info (assq 'test viewer-info)) | ||
| 618 | (test (cdr test-info)) | ||
| 619 | (otest test) | ||
| 620 | (viewer (cdr (assq 'viewer viewer-info))) | ||
| 621 | (default-directory (expand-file-name "~/")) | ||
| 622 | status parsed-test cache result) | ||
| 623 | (cond ((not (or (stringp viewer) (fboundp viewer))) | ||
| 624 | nil) ; Non-existent Lisp function | ||
| 625 | ((setq cache (assoc test mailcap-viewer-test-cache)) | ||
| 626 | (cadr cache)) | ||
| 627 | ((not test-info) t) ; No test clause | ||
| 628 | (t | ||
| 629 | (setq | ||
| 630 | result | ||
| 631 | (cond | ||
| 632 | ((not test) nil) ; Already failed test | ||
| 633 | ((eq test t) t) ; Already passed test | ||
| 634 | ((functionp test) ; Lisp function as test | ||
| 635 | (funcall test type-info)) | ||
| 636 | ((and (symbolp test) ; Lisp variable as test | ||
| 637 | (boundp test)) | ||
| 638 | (symbol-value test)) | ||
| 639 | ((and (listp test) ; List to be eval'd | ||
| 640 | (symbolp (car test))) | ||
| 641 | (eval test)) | ||
| 642 | (t | ||
| 643 | (setq test (mailcap-unescape-mime-test test type-info) | ||
| 644 | test (list shell-file-name nil nil nil | ||
| 645 | shell-command-switch test) | ||
| 646 | status (apply 'call-process test)) | ||
| 647 | (eq 0 status)))) | ||
| 648 | (push (list otest result) mailcap-viewer-test-cache) | ||
| 649 | result)))) | ||
| 650 | |||
| 651 | (defun mailcap-add-mailcap-entry (major minor info) | ||
| 652 | (let ((old-major (assoc major mailcap-mime-data))) | ||
| 653 | (if (null old-major) ; New major area | ||
| 654 | (setq mailcap-mime-data | ||
| 655 | (cons (cons major (list (cons minor info))) | ||
| 656 | mailcap-mime-data)) | ||
| 657 | (let ((cur-minor (assoc minor old-major))) | ||
| 658 | (cond | ||
| 659 | ((or (null cur-minor) ; New minor area, or | ||
| 660 | (assq 'test info)) ; Has a test, insert at beginning | ||
| 661 | (setcdr old-major (cons (cons minor info) (cdr old-major)))) | ||
| 662 | ((and (not (assq 'test info)) ; No test info, replace completely | ||
| 663 | (not (assq 'test cur-minor)) | ||
| 664 | (equal (assq 'viewer info) ; Keep alternative viewer | ||
| 665 | (assq 'viewer cur-minor))) | ||
| 666 | (setcdr cur-minor info)) | ||
| 667 | (t | ||
| 668 | (setcdr old-major (cons (cons minor info) (cdr old-major)))))) | ||
| 669 | ))) | ||
| 670 | |||
| 671 | (defun mailcap-add (type viewer &optional test) | ||
| 672 | "Add VIEWER as a handler for TYPE. | ||
| 673 | If TEST is not given, it defaults to t." | ||
| 674 | (let ((tl (split-string type "/"))) | ||
| 675 | (when (or (not (car tl)) | ||
| 676 | (not (cadr tl))) | ||
| 677 | (error "%s is not a valid MIME type" type)) | ||
| 678 | (mailcap-add-mailcap-entry | ||
| 679 | (car tl) (cadr tl) | ||
| 680 | `((viewer . ,viewer) | ||
| 681 | (test . ,(if test test t)) | ||
| 682 | (type . ,type))))) | ||
| 683 | |||
| 684 | ;;; | ||
| 685 | ;;; The main whabbo | ||
| 686 | ;;; | ||
| 687 | |||
| 688 | (defun mailcap-viewer-lessp (x y) | ||
| 689 | "Return t if viewer X is more desirable than viewer Y." | ||
| 690 | (let ((x-wild (string-match "[*?]" (or (cdr-safe (assq 'type x)) ""))) | ||
| 691 | (y-wild (string-match "[*?]" (or (cdr-safe (assq 'type y)) ""))) | ||
| 692 | (x-lisp (not (stringp (or (cdr-safe (assq 'viewer x)) "")))) | ||
| 693 | (y-lisp (not (stringp (or (cdr-safe (assq 'viewer y)) ""))))) | ||
| 694 | (cond | ||
| 695 | ((and x-wild (not y-wild)) | ||
| 696 | nil) | ||
| 697 | ((and (not x-wild) y-wild) | ||
| 698 | t) | ||
| 699 | ((and (not y-lisp) x-lisp) | ||
| 700 | t) | ||
| 701 | (t nil)))) | ||
| 702 | |||
| 703 | (defun mailcap-mime-info (string &optional request no-decode) | ||
| 704 | "Get the MIME viewer command for STRING, return nil if none found. | ||
| 705 | Expects a complete content-type header line as its argument. | ||
| 706 | |||
| 707 | Second argument REQUEST specifies what information to return. If it is | ||
| 708 | nil or the empty string, the viewer (second field of the mailcap | ||
| 709 | entry) will be returned. If it is a string, then the mailcap field | ||
| 710 | corresponding to that string will be returned (print, description, | ||
| 711 | whatever). If a number, then all the information for this specific | ||
| 712 | viewer is returned. If `all', then all possible viewers for | ||
| 713 | this type is returned. | ||
| 714 | |||
| 715 | If NO-DECODE is non-nil, don't decode STRING." | ||
| 716 | ;; NO-DECODE avoids calling `mail-header-parse-content-type' from | ||
| 717 | ;; `mail-parse.el' | ||
| 718 | (let ( | ||
| 719 | major ; Major encoding (text, etc) | ||
| 720 | minor ; Minor encoding (html, etc) | ||
| 721 | info ; Other info | ||
| 722 | save-pos ; Misc. position during parse | ||
| 723 | major-info ; (assoc major mailcap-mime-data) | ||
| 724 | minor-info ; (assoc minor major-info) | ||
| 725 | test ; current test proc. | ||
| 726 | viewers ; Possible viewers | ||
| 727 | passed ; Viewers that passed the test | ||
| 728 | viewer ; The one and only viewer | ||
| 729 | ctl) | ||
| 730 | (save-excursion | ||
| 731 | (setq ctl | ||
| 732 | (if no-decode | ||
| 733 | (list (or string "text/plain")) | ||
| 734 | (mail-header-parse-content-type (or string "text/plain")))) | ||
| 735 | (setq major (split-string (car ctl) "/")) | ||
| 736 | (setq minor (cadr major) | ||
| 737 | major (car major)) | ||
| 738 | (when (setq major-info (cdr (assoc major mailcap-mime-data))) | ||
| 739 | (when (setq viewers (mailcap-possible-viewers major-info minor)) | ||
| 740 | (setq info (mapcar (lambda (a) (cons (symbol-name (car a)) | ||
| 741 | (cdr a))) | ||
| 742 | (cdr ctl))) | ||
| 743 | (while viewers | ||
| 744 | (if (mailcap-viewer-passes-test (car viewers) info) | ||
| 745 | (setq passed (cons (car viewers) passed))) | ||
| 746 | (setq viewers (cdr viewers))) | ||
| 747 | (setq passed (sort passed 'mailcap-viewer-lessp)) | ||
| 748 | (setq viewer (car passed)))) | ||
| 749 | (when (and (stringp (cdr (assq 'viewer viewer))) | ||
| 750 | passed) | ||
| 751 | (setq viewer (car passed))) | ||
| 752 | (cond | ||
| 753 | ((and (null viewer) (not (equal major "default")) request) | ||
| 754 | (mailcap-mime-info "default" request no-decode)) | ||
| 755 | ((or (null request) (equal request "")) | ||
| 756 | (mailcap-unescape-mime-test (cdr (assq 'viewer viewer)) info)) | ||
| 757 | ((stringp request) | ||
| 758 | (mailcap-unescape-mime-test | ||
| 759 | (cdr-safe (assoc request viewer)) info)) | ||
| 760 | ((eq request 'all) | ||
| 761 | passed) | ||
| 762 | (t | ||
| 763 | ;; MUST make a copy *sigh*, else we modify mailcap-mime-data | ||
| 764 | (setq viewer (copy-sequence viewer)) | ||
| 765 | (let ((view (assq 'viewer viewer)) | ||
| 766 | (test (assq 'test viewer))) | ||
| 767 | (if view (setcdr view (mailcap-unescape-mime-test (cdr view) info))) | ||
| 768 | (if test (setcdr test (mailcap-unescape-mime-test (cdr test) info)))) | ||
| 769 | viewer))))) | ||
| 770 | |||
| 771 | ;;; | ||
| 772 | ;;; Experimental MIME-types parsing | ||
| 773 | ;;; | ||
| 774 | |||
| 775 | (defvar mailcap-mime-extensions | ||
| 776 | '(("" . "text/plain") | ||
| 777 | (".1" . "text/plain") ;; Manual pages | ||
| 778 | (".3" . "text/plain") | ||
| 779 | (".8" . "text/plain") | ||
| 780 | (".abs" . "audio/x-mpeg") | ||
| 781 | (".aif" . "audio/aiff") | ||
| 782 | (".aifc" . "audio/aiff") | ||
| 783 | (".aiff" . "audio/aiff") | ||
| 784 | (".ano" . "application/x-annotator") | ||
| 785 | (".au" . "audio/ulaw") | ||
| 786 | (".avi" . "video/x-msvideo") | ||
| 787 | (".bcpio" . "application/x-bcpio") | ||
| 788 | (".bin" . "application/octet-stream") | ||
| 789 | (".cdf" . "application/x-netcdr") | ||
| 790 | (".cpio" . "application/x-cpio") | ||
| 791 | (".csh" . "application/x-csh") | ||
| 792 | (".css" . "text/css") | ||
| 793 | (".dvi" . "application/x-dvi") | ||
| 794 | (".diff" . "text/x-patch") | ||
| 795 | (".dpatch". "test/x-patch") | ||
| 796 | (".el" . "application/emacs-lisp") | ||
| 797 | (".eps" . "application/postscript") | ||
| 798 | (".etx" . "text/x-setext") | ||
| 799 | (".exe" . "application/octet-stream") | ||
| 800 | (".fax" . "image/x-fax") | ||
| 801 | (".gif" . "image/gif") | ||
| 802 | (".hdf" . "application/x-hdf") | ||
| 803 | (".hqx" . "application/mac-binhex40") | ||
| 804 | (".htm" . "text/html") | ||
| 805 | (".html" . "text/html") | ||
| 806 | (".icon" . "image/x-icon") | ||
| 807 | (".ief" . "image/ief") | ||
| 808 | (".jpg" . "image/jpeg") | ||
| 809 | (".macp" . "image/x-macpaint") | ||
| 810 | (".man" . "application/x-troff-man") | ||
| 811 | (".me" . "application/x-troff-me") | ||
| 812 | (".mif" . "application/mif") | ||
| 813 | (".mov" . "video/quicktime") | ||
| 814 | (".movie" . "video/x-sgi-movie") | ||
| 815 | (".mp2" . "audio/x-mpeg") | ||
| 816 | (".mp3" . "audio/x-mpeg") | ||
| 817 | (".mp2a" . "audio/x-mpeg2") | ||
| 818 | (".mpa" . "audio/x-mpeg") | ||
| 819 | (".mpa2" . "audio/x-mpeg2") | ||
| 820 | (".mpe" . "video/mpeg") | ||
| 821 | (".mpeg" . "video/mpeg") | ||
| 822 | (".mpega" . "audio/x-mpeg") | ||
| 823 | (".mpegv" . "video/mpeg") | ||
| 824 | (".mpg" . "video/mpeg") | ||
| 825 | (".mpv" . "video/mpeg") | ||
| 826 | (".ms" . "application/x-troff-ms") | ||
| 827 | (".nc" . "application/x-netcdf") | ||
| 828 | (".nc" . "application/x-netcdf") | ||
| 829 | (".oda" . "application/oda") | ||
| 830 | (".patch" . "text/x-patch") | ||
| 831 | (".pbm" . "image/x-portable-bitmap") | ||
| 832 | (".pdf" . "application/pdf") | ||
| 833 | (".pgm" . "image/portable-graymap") | ||
| 834 | (".pict" . "image/pict") | ||
| 835 | (".png" . "image/png") | ||
| 836 | (".pnm" . "image/x-portable-anymap") | ||
| 837 | (".pod" . "text/plain") | ||
| 838 | (".ppm" . "image/portable-pixmap") | ||
| 839 | (".ps" . "application/postscript") | ||
| 840 | (".qt" . "video/quicktime") | ||
| 841 | (".ras" . "image/x-raster") | ||
| 842 | (".rgb" . "image/x-rgb") | ||
| 843 | (".rtf" . "application/rtf") | ||
| 844 | (".rtx" . "text/richtext") | ||
| 845 | (".sh" . "application/x-sh") | ||
| 846 | (".sit" . "application/x-stuffit") | ||
| 847 | (".siv" . "application/sieve") | ||
| 848 | (".snd" . "audio/basic") | ||
| 849 | (".soa" . "text/dns") | ||
| 850 | (".src" . "application/x-wais-source") | ||
| 851 | (".tar" . "archive/tar") | ||
| 852 | (".tcl" . "application/x-tcl") | ||
| 853 | (".tex" . "application/x-tex") | ||
| 854 | (".texi" . "application/texinfo") | ||
| 855 | (".tga" . "image/x-targa") | ||
| 856 | (".tif" . "image/tiff") | ||
| 857 | (".tiff" . "image/tiff") | ||
| 858 | (".tr" . "application/x-troff") | ||
| 859 | (".troff" . "application/x-troff") | ||
| 860 | (".tsv" . "text/tab-separated-values") | ||
| 861 | (".txt" . "text/plain") | ||
| 862 | (".vbs" . "video/mpeg") | ||
| 863 | (".vox" . "audio/basic") | ||
| 864 | (".vrml" . "x-world/x-vrml") | ||
| 865 | (".wav" . "audio/x-wav") | ||
| 866 | (".xls" . "application/vnd.ms-excel") | ||
| 867 | (".wrl" . "x-world/x-vrml") | ||
| 868 | (".xbm" . "image/xbm") | ||
| 869 | (".xpm" . "image/xpm") | ||
| 870 | (".xwd" . "image/windowdump") | ||
| 871 | (".zip" . "application/zip") | ||
| 872 | (".ai" . "application/postscript") | ||
| 873 | (".jpe" . "image/jpeg") | ||
| 874 | (".jpeg" . "image/jpeg") | ||
| 875 | (".org" . "text/x-org")) | ||
| 876 | "An alist of file extensions and corresponding MIME content-types. | ||
| 877 | This exists for you to customize the information in Lisp. It is | ||
| 878 | merged with values from mailcap files by `mailcap-parse-mimetypes'.") | ||
| 879 | |||
| 880 | (defvar mailcap-mimetypes-parsed-p nil) | ||
| 881 | |||
| 882 | (defun mailcap-parse-mimetypes (&optional path force) | ||
| 883 | "Parse out all the mimetypes specified in a Unix-style path string PATH. | ||
| 884 | Components of PATH are separated by the `path-separator' character | ||
| 885 | appropriate for this system. If PATH is omitted, use the value of | ||
| 886 | environment variable MIMETYPES if set; otherwise use a default path. | ||
| 887 | If FORCE, re-parse even if already parsed." | ||
| 888 | (interactive (list nil t)) | ||
| 889 | (when (or (not mailcap-mimetypes-parsed-p) | ||
| 890 | force) | ||
| 891 | (cond | ||
| 892 | (path nil) | ||
| 893 | ((getenv "MIMETYPES") (setq path (getenv "MIMETYPES"))) | ||
| 894 | ((memq system-type mailcap-poor-system-types) | ||
| 895 | (setq path '("~/mime.typ" "~/etc/mime.typ"))) | ||
| 896 | (t (setq path | ||
| 897 | ;; mime.types seems to be the normal name, definitely so | ||
| 898 | ;; on current GNUish systems. The search order follows | ||
| 899 | ;; that for mailcap. | ||
| 900 | '("~/.mime.types" | ||
| 901 | "/etc/mime.types" | ||
| 902 | "/usr/etc/mime.types" | ||
| 903 | "/usr/local/etc/mime.types" | ||
| 904 | "/usr/local/www/conf/mime.types" | ||
| 905 | "~/.mime-types" | ||
| 906 | "/etc/mime-types" | ||
| 907 | "/usr/etc/mime-types" | ||
| 908 | "/usr/local/etc/mime-types" | ||
| 909 | "/usr/local/www/conf/mime-types")))) | ||
| 910 | (let ((fnames (reverse (if (stringp path) | ||
| 911 | (split-string path path-separator t) | ||
| 912 | path))) | ||
| 913 | fname) | ||
| 914 | (while fnames | ||
| 915 | (setq fname (car fnames)) | ||
| 916 | (if (and (file-readable-p fname)) | ||
| 917 | (mailcap-parse-mimetype-file fname)) | ||
| 918 | (setq fnames (cdr fnames)))) | ||
| 919 | (setq mailcap-mimetypes-parsed-p t))) | ||
| 920 | |||
| 921 | (defun mailcap-parse-mimetype-file (fname) | ||
| 922 | "Parse out a mime-types file FNAME." | ||
| 923 | (let (type ; The MIME type for this line | ||
| 924 | extns ; The extensions for this line | ||
| 925 | save-pos ; Misc. saved buffer positions | ||
| 926 | ) | ||
| 927 | (with-temp-buffer | ||
| 928 | (insert-file-contents fname) | ||
| 929 | (mailcap-replace-regexp "#.*" "") | ||
| 930 | (mailcap-replace-regexp "\n+" "\n") | ||
| 931 | (mailcap-replace-regexp "[ \t]+$" "") | ||
| 932 | (goto-char (point-max)) | ||
| 933 | (skip-chars-backward " \t\n") | ||
| 934 | (delete-region (point) (point-max)) | ||
| 935 | (goto-char (point-min)) | ||
| 936 | (while (not (eobp)) | ||
| 937 | (skip-chars-forward " \t\n") | ||
| 938 | (setq save-pos (point)) | ||
| 939 | (skip-chars-forward "^ \t\n") | ||
| 940 | (downcase-region save-pos (point)) | ||
| 941 | (setq type (buffer-substring save-pos (point))) | ||
| 942 | (while (not (eolp)) | ||
| 943 | (skip-chars-forward " \t") | ||
| 944 | (setq save-pos (point)) | ||
| 945 | (skip-chars-forward "^ \t\n") | ||
| 946 | (setq extns (cons (buffer-substring save-pos (point)) extns))) | ||
| 947 | (while extns | ||
| 948 | (setq mailcap-mime-extensions | ||
| 949 | (cons | ||
| 950 | (cons (if (= (string-to-char (car extns)) ?.) | ||
| 951 | (car extns) | ||
| 952 | (concat "." (car extns))) type) | ||
| 953 | mailcap-mime-extensions) | ||
| 954 | extns (cdr extns))))))) | ||
| 955 | |||
| 956 | (defun mailcap-extension-to-mime (extn) | ||
| 957 | "Return the MIME content type of the file extensions EXTN." | ||
| 958 | (mailcap-parse-mimetypes) | ||
| 959 | (if (and (stringp extn) | ||
| 960 | (not (eq (string-to-char extn) ?.))) | ||
| 961 | (setq extn (concat "." extn))) | ||
| 962 | (cdr (assoc (downcase extn) mailcap-mime-extensions))) | ||
| 963 | |||
| 964 | ;; Unused? | ||
| 965 | (defalias 'mailcap-command-p 'executable-find) | ||
| 966 | |||
| 967 | (defun mailcap-mime-types () | ||
| 968 | "Return a list of MIME media types." | ||
| 969 | (mailcap-parse-mimetypes) | ||
| 970 | (delete-dups | ||
| 971 | (nconc | ||
| 972 | (mapcar 'cdr mailcap-mime-extensions) | ||
| 973 | (apply | ||
| 974 | 'nconc | ||
| 975 | (mapcar | ||
| 976 | (lambda (l) | ||
| 977 | (delq nil | ||
| 978 | (mapcar | ||
| 979 | (lambda (m) | ||
| 980 | (let ((type (cdr (assq 'type (cdr m))))) | ||
| 981 | (if (equal (cadr (split-string type "/")) | ||
| 982 | "*") | ||
| 983 | nil | ||
| 984 | type))) | ||
| 985 | (cdr l)))) | ||
| 986 | mailcap-mime-data))))) | ||
| 987 | |||
| 988 | ;;; | ||
| 989 | ;;; Useful supplementary functions | ||
| 990 | ;;; | ||
| 991 | |||
| 992 | (defun mailcap-file-default-commands (files) | ||
| 993 | "Return a list of default commands for FILES." | ||
| 994 | (mailcap-parse-mailcaps) | ||
| 995 | (mailcap-parse-mimetypes) | ||
| 996 | (let* ((all-mime-type | ||
| 997 | ;; All unique MIME types from file extensions | ||
| 998 | (delete-dups | ||
| 999 | (mapcar (lambda (file) | ||
| 1000 | (mailcap-extension-to-mime | ||
| 1001 | (file-name-extension file t))) | ||
| 1002 | files))) | ||
| 1003 | (all-mime-info | ||
| 1004 | ;; All MIME info lists | ||
| 1005 | (delete-dups | ||
| 1006 | (mapcar (lambda (mime-type) | ||
| 1007 | (mailcap-mime-info mime-type 'all)) | ||
| 1008 | all-mime-type))) | ||
| 1009 | (common-mime-info | ||
| 1010 | ;; Intersection of mime-infos from different mime-types; | ||
| 1011 | ;; or just the first MIME info for a single MIME type | ||
| 1012 | (if (cdr all-mime-info) | ||
| 1013 | (delq nil (mapcar (lambda (mi1) | ||
| 1014 | (unless (memq nil (mapcar | ||
| 1015 | (lambda (mi2) | ||
| 1016 | (member mi1 mi2)) | ||
| 1017 | (cdr all-mime-info))) | ||
| 1018 | mi1)) | ||
| 1019 | (car all-mime-info))) | ||
| 1020 | (car all-mime-info))) | ||
| 1021 | (commands | ||
| 1022 | ;; Command strings from `viewer' field of the MIME info | ||
| 1023 | (delete-dups | ||
| 1024 | (delq nil (mapcar | ||
| 1025 | (lambda (mime-info) | ||
| 1026 | (let ((command (cdr (assoc 'viewer mime-info)))) | ||
| 1027 | (if (stringp command) | ||
| 1028 | (replace-regexp-in-string | ||
| 1029 | ;; Replace mailcap's `%s' placeholder | ||
| 1030 | ;; with dired's `?' placeholder | ||
| 1031 | "%s" "?" | ||
| 1032 | (replace-regexp-in-string | ||
| 1033 | ;; Remove the final filename placeholder | ||
| 1034 | "[ \t\n]*\\('\\)?%s\\1?[ \t\n]*\\'" "" | ||
| 1035 | command nil t) | ||
| 1036 | nil t)))) | ||
| 1037 | common-mime-info))))) | ||
| 1038 | commands)) | ||
| 1039 | |||
| 1040 | (defun mailcap-view-mime (type) | ||
| 1041 | "View the data in the current buffer that has MIME type TYPE. | ||
| 1042 | `mailcap-mime-data' determines the method to use." | ||
| 1043 | (let ((method (mailcap-mime-info type))) | ||
| 1044 | (if (stringp method) | ||
| 1045 | (shell-command-on-region (point-min) (point-max) | ||
| 1046 | ;; Use stdin as the "%s". | ||
| 1047 | (format method "-") | ||
| 1048 | (current-buffer) | ||
| 1049 | t) | ||
| 1050 | (funcall method)))) | ||
| 1051 | |||
| 1052 | (provide 'mailcap) | ||
| 1053 | |||
| 1054 | ;;; mailcap.el ends here | ||
diff --git a/lisp/net/pop3.el b/lisp/net/pop3.el new file mode 100644 index 00000000000..1695bbd3a40 --- /dev/null +++ b/lisp/net/pop3.el | |||
| @@ -0,0 +1,914 @@ | |||
| 1 | ;;; pop3.el --- Post Office Protocol (RFC 1460) interface | ||
| 2 | |||
| 3 | ;; Copyright (C) 1996-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Richard L. Pieri <ratinox@peorth.gweep.net> | ||
| 6 | ;; Maintainer: emacs-devel@gnu.org | ||
| 7 | ;; Keywords: mail | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; Most of the standard Post Office Protocol version 3 (RFC 1460) commands | ||
| 27 | ;; are implemented. The LIST command has not been implemented due to lack | ||
| 28 | ;; of actual usefulness. | ||
| 29 | ;; The optional POP3 command TOP has not been implemented. | ||
| 30 | |||
| 31 | ;; This program was inspired by Kyle E. Jones's vm-pop program. | ||
| 32 | |||
| 33 | ;;; Code: | ||
| 34 | |||
| 35 | (eval-when-compile (require 'cl)) | ||
| 36 | |||
| 37 | (require 'mail-utils) | ||
| 38 | (defvar parse-time-months) | ||
| 39 | |||
| 40 | (defgroup pop3 nil | ||
| 41 | "Post Office Protocol." | ||
| 42 | :group 'mail | ||
| 43 | :group 'mail-source) | ||
| 44 | |||
| 45 | (defcustom pop3-maildrop (or (user-login-name) | ||
| 46 | (getenv "LOGNAME") | ||
| 47 | (getenv "USER")) | ||
| 48 | "*POP3 maildrop." | ||
| 49 | :version "22.1" ;; Oort Gnus | ||
| 50 | :type 'string | ||
| 51 | :group 'pop3) | ||
| 52 | |||
| 53 | (defcustom pop3-mailhost (or (getenv "MAILHOST") ;; nil -> mismatch | ||
| 54 | "pop3") | ||
| 55 | "*POP3 mailhost." | ||
| 56 | :version "22.1" ;; Oort Gnus | ||
| 57 | :type 'string | ||
| 58 | :group 'pop3) | ||
| 59 | |||
| 60 | (defcustom pop3-port 110 | ||
| 61 | "*POP3 port." | ||
| 62 | :version "22.1" ;; Oort Gnus | ||
| 63 | :type 'number | ||
| 64 | :group 'pop3) | ||
| 65 | |||
| 66 | (defcustom pop3-password-required t | ||
| 67 | "*Non-nil if a password is required when connecting to POP server." | ||
| 68 | :version "22.1" ;; Oort Gnus | ||
| 69 | :type 'boolean | ||
| 70 | :group 'pop3) | ||
| 71 | |||
| 72 | ;; Should this be customizable? | ||
| 73 | (defvar pop3-password nil | ||
| 74 | "*Password to use when connecting to POP server.") | ||
| 75 | |||
| 76 | (defcustom pop3-authentication-scheme 'pass | ||
| 77 | "*POP3 authentication scheme. | ||
| 78 | Defaults to `pass', for the standard USER/PASS authentication. The other | ||
| 79 | valid value is `apop'." | ||
| 80 | :type '(choice (const :tag "Normal user/password" pass) | ||
| 81 | (const :tag "APOP" apop)) | ||
| 82 | :version "22.1" ;; Oort Gnus | ||
| 83 | :group 'pop3) | ||
| 84 | |||
| 85 | (defcustom pop3-stream-length 100 | ||
| 86 | "How many messages should be requested at one time. | ||
| 87 | The lower the number, the more latency-sensitive the fetching | ||
| 88 | will be. If your pop3 server doesn't support streaming at all, | ||
| 89 | set this to 1." | ||
| 90 | :type 'number | ||
| 91 | :version "24.1" | ||
| 92 | :group 'pop3) | ||
| 93 | |||
| 94 | (defcustom pop3-leave-mail-on-server nil | ||
| 95 | "Non-nil if the mail is to be left on the POP server after fetching. | ||
| 96 | Mails once fetched will never be fetched again by the UIDL control. | ||
| 97 | |||
| 98 | If this is neither nil nor a number, all mails will be left on the | ||
| 99 | server. If this is a number, leave mails on the server for this many | ||
| 100 | days since you first checked new mails. If this is nil, mails will be | ||
| 101 | deleted on the server right after fetching. | ||
| 102 | |||
| 103 | Gnus users should use the `:leave' keyword in a mail source to direct | ||
| 104 | the behavior per server, rather than directly modifying this value. | ||
| 105 | |||
| 106 | Note that POP servers maintain no state information between sessions, | ||
| 107 | so what the client believes is there and what is actually there may | ||
| 108 | not match up. If they do not, then you may get duplicate mails or | ||
| 109 | the whole thing can fall apart and leave you with a corrupt mailbox." | ||
| 110 | :version "24.4" | ||
| 111 | :type '(choice (const :tag "Don't leave mails" nil) | ||
| 112 | (const :tag "Leave all mails" t) | ||
| 113 | (number :tag "Leave mails for this many days" :value 14)) | ||
| 114 | :group 'pop3) | ||
| 115 | |||
| 116 | (defcustom pop3-uidl-file "~/.pop3-uidl" | ||
| 117 | "File used to save UIDL." | ||
| 118 | :version "24.4" | ||
| 119 | :type 'file | ||
| 120 | :group 'pop3) | ||
| 121 | |||
| 122 | (defcustom pop3-uidl-file-backup '(0 9) | ||
| 123 | "How to backup the UIDL file `pop3-uidl-file' when updating. | ||
| 124 | If it is a list of numbers, the first one binds `kept-old-versions' and | ||
| 125 | the other binds `kept-new-versions' to keep number of oldest and newest | ||
| 126 | versions. Otherwise, the value binds `version-control' (which see). | ||
| 127 | |||
| 128 | Note: Backup will take place whenever you check new mails on a server. | ||
| 129 | So, you may lose the backup files having been saved before a trouble | ||
| 130 | if you set it so as to make too few backups whereas you have access to | ||
| 131 | many servers." | ||
| 132 | :version "24.4" | ||
| 133 | :type '(choice (group :tag "Keep versions" :format "\n%v" :indent 3 | ||
| 134 | (number :tag "oldest") | ||
| 135 | (number :tag "newest")) | ||
| 136 | (sexp :format "%v" | ||
| 137 | :match (lambda (widget value) | ||
| 138 | (condition-case nil | ||
| 139 | (not (and (numberp (car value)) | ||
| 140 | (numberp (car (cdr value))))) | ||
| 141 | (error t))))) | ||
| 142 | :group 'pop3) | ||
| 143 | |||
| 144 | (defvar pop3-timestamp nil | ||
| 145 | "Timestamp returned when initially connected to the POP server. | ||
| 146 | Used for APOP authentication.") | ||
| 147 | |||
| 148 | (defvar pop3-read-point nil) | ||
| 149 | (defvar pop3-debug nil) | ||
| 150 | |||
| 151 | ;; Borrowed from nnheader-accept-process-output in nnheader.el. See the | ||
| 152 | ;; comments there for explanations about the values. | ||
| 153 | |||
| 154 | (eval-and-compile | ||
| 155 | (if (and (fboundp 'nnheader-accept-process-output) | ||
| 156 | (boundp 'nnheader-read-timeout)) | ||
| 157 | (defalias 'pop3-accept-process-output 'nnheader-accept-process-output) | ||
| 158 | ;; Borrowed from `nnheader.el': | ||
| 159 | (defvar pop3-read-timeout | ||
| 160 | (if (string-match "windows-nt\\|os/2\\|cygwin" | ||
| 161 | (symbol-name system-type)) | ||
| 162 | 1.0 | ||
| 163 | 0.01) | ||
| 164 | "How long pop3 should wait between checking for the end of output. | ||
| 165 | Shorter values mean quicker response, but are more CPU intensive.") | ||
| 166 | (defun pop3-accept-process-output (process) | ||
| 167 | (accept-process-output | ||
| 168 | process | ||
| 169 | (truncate pop3-read-timeout) | ||
| 170 | (truncate (* (- pop3-read-timeout | ||
| 171 | (truncate pop3-read-timeout)) | ||
| 172 | 1000)))))) | ||
| 173 | |||
| 174 | (defvar pop3-uidl) | ||
| 175 | ;; List of UIDLs of existing messages at present in the server: | ||
| 176 | ;; ("UIDL1" "UIDL2" "UIDL3"...) | ||
| 177 | |||
| 178 | (defvar pop3-uidl-saved) | ||
| 179 | ;; Locally saved UIDL data; an alist of the server, the user, and the UIDL | ||
| 180 | ;; and timestamp pairs: | ||
| 181 | ;; (("SERVER_A" ("USER_A1" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...) | ||
| 182 | ;; ("USER_A2" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...) | ||
| 183 | ;; ...) | ||
| 184 | ;; ("SERVER_B" ("USER_B1" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...) | ||
| 185 | ;; ("USER_B2" "UIDL1" TIMESTAMP1 "UIDL2" TIMESTAMP2...) | ||
| 186 | ;; ...)) | ||
| 187 | ;; Where TIMESTAMP is the most significant two digits of an Emacs time, | ||
| 188 | ;; i.e. the return value of `current-time'. | ||
| 189 | |||
| 190 | ;;;###autoload | ||
| 191 | (defun pop3-movemail (file) | ||
| 192 | "Transfer contents of a maildrop to the specified FILE. | ||
| 193 | Use streaming commands." | ||
| 194 | (let ((process (pop3-open-server pop3-mailhost pop3-port)) | ||
| 195 | messages total-size | ||
| 196 | pop3-uidl | ||
| 197 | pop3-uidl-saved) | ||
| 198 | (pop3-logon process) | ||
| 199 | (if pop3-leave-mail-on-server | ||
| 200 | (setq messages (pop3-uidl-stat process) | ||
| 201 | total-size (cadr messages) | ||
| 202 | messages (car messages)) | ||
| 203 | (let ((size (pop3-stat process))) | ||
| 204 | (dotimes (i (car size)) (push (1+ i) messages)) | ||
| 205 | (setq messages (nreverse messages) | ||
| 206 | total-size (cadr size)))) | ||
| 207 | (when messages | ||
| 208 | (with-current-buffer (process-buffer process) | ||
| 209 | (pop3-send-streaming-command process "RETR" messages total-size) | ||
| 210 | (pop3-write-to-file file messages) | ||
| 211 | (unless pop3-leave-mail-on-server | ||
| 212 | (pop3-send-streaming-command process "DELE" messages nil)))) | ||
| 213 | (if pop3-leave-mail-on-server | ||
| 214 | (when (prog1 (pop3-uidl-dele process) (pop3-quit process)) | ||
| 215 | (pop3-uidl-save)) | ||
| 216 | (pop3-quit process) | ||
| 217 | ;; Remove UIDL data for the account that got not to leave mails. | ||
| 218 | (setq pop3-uidl-saved (pop3-uidl-load)) | ||
| 219 | (let ((elt (assoc pop3-maildrop | ||
| 220 | (cdr (assoc pop3-mailhost pop3-uidl-saved))))) | ||
| 221 | (when elt | ||
| 222 | (setcdr elt nil) | ||
| 223 | (pop3-uidl-save)))) | ||
| 224 | t)) | ||
| 225 | |||
| 226 | (defun pop3-send-streaming-command (process command messages total-size) | ||
| 227 | (erase-buffer) | ||
| 228 | (let ((count (length messages)) | ||
| 229 | (i 1) | ||
| 230 | (start-point (point-min)) | ||
| 231 | (waited-for 0)) | ||
| 232 | (while messages | ||
| 233 | (process-send-string process (format "%s %d\r\n" command (pop messages))) | ||
| 234 | ;; Only do 100 messages at a time to avoid pipe stalls. | ||
| 235 | (when (zerop (% i pop3-stream-length)) | ||
| 236 | (setq start-point | ||
| 237 | (pop3-wait-for-messages process pop3-stream-length | ||
| 238 | total-size start-point)) | ||
| 239 | (incf waited-for pop3-stream-length)) | ||
| 240 | (incf i)) | ||
| 241 | (pop3-wait-for-messages process (- count waited-for) | ||
| 242 | total-size start-point))) | ||
| 243 | |||
| 244 | (defun pop3-wait-for-messages (process count total-size start-point) | ||
| 245 | (while (> count 0) | ||
| 246 | (goto-char start-point) | ||
| 247 | (while (or (and (re-search-forward "^\\+OK" nil t) | ||
| 248 | (or (not total-size) | ||
| 249 | (re-search-forward "^\\.\r?\n" nil t))) | ||
| 250 | (re-search-forward "^-ERR " nil t)) | ||
| 251 | (decf count) | ||
| 252 | (setq start-point (point))) | ||
| 253 | (unless (memq (process-status process) '(open run)) | ||
| 254 | (error "pop3 process died")) | ||
| 255 | (when total-size | ||
| 256 | (let ((size 0)) | ||
| 257 | (goto-char (point-min)) | ||
| 258 | (while (re-search-forward "^\\+OK.*\n" nil t) | ||
| 259 | (setq size (+ size (- (point)) | ||
| 260 | (if (re-search-forward "^\\.\r?\n" nil 'move) | ||
| 261 | (match-beginning 0) | ||
| 262 | (point))))) | ||
| 263 | (message "pop3 retrieved %dKB (%d%%)" | ||
| 264 | (truncate (/ size 1000)) | ||
| 265 | (truncate (* (/ (* size 1.0) total-size) 100))))) | ||
| 266 | (pop3-accept-process-output process)) | ||
| 267 | start-point) | ||
| 268 | |||
| 269 | (defun pop3-write-to-file (file messages) | ||
| 270 | (let ((pop-buffer (current-buffer)) | ||
| 271 | (start (point-min)) | ||
| 272 | beg end | ||
| 273 | temp-buffer) | ||
| 274 | (with-temp-buffer | ||
| 275 | (setq temp-buffer (current-buffer)) | ||
| 276 | (with-current-buffer pop-buffer | ||
| 277 | (goto-char (point-min)) | ||
| 278 | (while (re-search-forward "^\\+OK" nil t) | ||
| 279 | (forward-line 1) | ||
| 280 | (setq beg (point)) | ||
| 281 | (when (re-search-forward "^\\.\r?\n" nil t) | ||
| 282 | (setq start (point)) | ||
| 283 | (forward-line -1) | ||
| 284 | (setq end (point))) | ||
| 285 | (with-current-buffer temp-buffer | ||
| 286 | (goto-char (point-max)) | ||
| 287 | (let ((hstart (point))) | ||
| 288 | (insert-buffer-substring pop-buffer beg end) | ||
| 289 | (pop3-clean-region hstart (point)) | ||
| 290 | (goto-char (point-max)) | ||
| 291 | (pop3-munge-message-separator hstart (point)) | ||
| 292 | (when pop3-leave-mail-on-server | ||
| 293 | (pop3-uidl-add-xheader hstart (pop messages))) | ||
| 294 | (goto-char (point-max)))))) | ||
| 295 | (let ((coding-system-for-write 'binary)) | ||
| 296 | (goto-char (point-min)) | ||
| 297 | ;; Check whether something inserted a newline at the start and | ||
| 298 | ;; delete it. | ||
| 299 | (when (eolp) | ||
| 300 | (delete-char 1)) | ||
| 301 | (write-region (point-min) (point-max) file nil 'nomesg))))) | ||
| 302 | |||
| 303 | (defun pop3-logon (process) | ||
| 304 | (let ((pop3-password pop3-password)) | ||
| 305 | ;; for debugging only | ||
| 306 | (if pop3-debug (switch-to-buffer (process-buffer process))) | ||
| 307 | ;; query for password | ||
| 308 | (if (and pop3-password-required (not pop3-password)) | ||
| 309 | (setq pop3-password | ||
| 310 | (read-passwd (format "Password for %s: " pop3-maildrop)))) | ||
| 311 | (cond ((equal 'apop pop3-authentication-scheme) | ||
| 312 | (pop3-apop process pop3-maildrop)) | ||
| 313 | ((equal 'pass pop3-authentication-scheme) | ||
| 314 | (pop3-user process pop3-maildrop) | ||
| 315 | (pop3-pass process)) | ||
| 316 | (t (error "Invalid POP3 authentication scheme"))))) | ||
| 317 | |||
| 318 | (defun pop3-get-message-count () | ||
| 319 | "Return the number of messages in the maildrop." | ||
| 320 | (let* ((process (pop3-open-server pop3-mailhost pop3-port)) | ||
| 321 | message-count | ||
| 322 | (pop3-password pop3-password)) | ||
| 323 | ;; for debugging only | ||
| 324 | (if pop3-debug (switch-to-buffer (process-buffer process))) | ||
| 325 | ;; query for password | ||
| 326 | (if (and pop3-password-required (not pop3-password)) | ||
| 327 | (setq pop3-password | ||
| 328 | (read-passwd (format "Password for %s: " pop3-maildrop)))) | ||
| 329 | (cond ((equal 'apop pop3-authentication-scheme) | ||
| 330 | (pop3-apop process pop3-maildrop)) | ||
| 331 | ((equal 'pass pop3-authentication-scheme) | ||
| 332 | (pop3-user process pop3-maildrop) | ||
| 333 | (pop3-pass process)) | ||
| 334 | (t (error "Invalid POP3 authentication scheme"))) | ||
| 335 | (setq message-count (car (pop3-stat process))) | ||
| 336 | (pop3-quit process) | ||
| 337 | message-count)) | ||
| 338 | |||
| 339 | (defun pop3-uidl-stat (process) | ||
| 340 | "Return a list of unread message numbers and total size." | ||
| 341 | (pop3-send-command process "UIDL") | ||
| 342 | (let (err messages size) | ||
| 343 | (if (condition-case code | ||
| 344 | (progn | ||
| 345 | (pop3-read-response process) | ||
| 346 | t) | ||
| 347 | (error (setq err (error-message-string code)) | ||
| 348 | nil)) | ||
| 349 | (let ((start pop3-read-point) | ||
| 350 | saved list) | ||
| 351 | (with-current-buffer (process-buffer process) | ||
| 352 | (while (not (re-search-forward "^\\.\r\n" nil t)) | ||
| 353 | (unless (memq (process-status process) '(open run)) | ||
| 354 | (error "pop3 server closed the connection")) | ||
| 355 | (pop3-accept-process-output process) | ||
| 356 | (goto-char start)) | ||
| 357 | (setq pop3-read-point (point-marker) | ||
| 358 | pop3-uidl nil) | ||
| 359 | (while (progn (forward-line -1) (>= (point) start)) | ||
| 360 | (when (looking-at "[0-9]+ \\([^\n\r ]+\\)") | ||
| 361 | (push (match-string 1) pop3-uidl))) | ||
| 362 | (when pop3-uidl | ||
| 363 | (setq pop3-uidl-saved (pop3-uidl-load) | ||
| 364 | saved (cdr (assoc pop3-maildrop | ||
| 365 | (cdr (assoc pop3-mailhost | ||
| 366 | pop3-uidl-saved))))) | ||
| 367 | (let ((i (length pop3-uidl))) | ||
| 368 | (while (> i 0) | ||
| 369 | (unless (member (nth (1- i) pop3-uidl) saved) | ||
| 370 | (push i messages)) | ||
| 371 | (decf i))) | ||
| 372 | (when messages | ||
| 373 | (setq list (pop3-list process) | ||
| 374 | size 0) | ||
| 375 | (dolist (msg messages) | ||
| 376 | (setq size (+ size (cdr (assq msg list))))) | ||
| 377 | (list messages size))))) | ||
| 378 | (message "%s doesn't support UIDL (%s), so we try a regressive way..." | ||
| 379 | pop3-mailhost err) | ||
| 380 | (sit-for 1) | ||
| 381 | (setq size (pop3-stat process)) | ||
| 382 | (dotimes (i (car size)) (push (1+ i) messages)) | ||
| 383 | (setcar size (nreverse messages)) | ||
| 384 | size))) | ||
| 385 | |||
| 386 | (defun pop3-uidl-dele (process) | ||
| 387 | "Delete messages according to `pop3-leave-mail-on-server'. | ||
| 388 | Return non-nil if it is necessary to update the local UIDL file." | ||
| 389 | (let* ((ctime (current-time)) | ||
| 390 | (srvr (assoc pop3-mailhost pop3-uidl-saved)) | ||
| 391 | (saved (assoc pop3-maildrop (cdr srvr))) | ||
| 392 | i uidl mod new tstamp dele) | ||
| 393 | (setcdr (cdr ctime) nil) | ||
| 394 | ;; Add new messages to the data to be saved. | ||
| 395 | (cond ((and pop3-uidl saved) | ||
| 396 | (setq i (1- (length pop3-uidl))) | ||
| 397 | (while (>= i 0) | ||
| 398 | (unless (member (setq uidl (nth i pop3-uidl)) (cdr saved)) | ||
| 399 | (push ctime new) | ||
| 400 | (push uidl new)) | ||
| 401 | (decf i))) | ||
| 402 | (pop3-uidl | ||
| 403 | (setq new (apply 'nconc (mapcar (lambda (elt) (list elt ctime)) | ||
| 404 | pop3-uidl))))) | ||
| 405 | (when new (setq mod t)) | ||
| 406 | ;; List expirable messages and delete them from the data to be saved. | ||
| 407 | (setq ctime (when (numberp pop3-leave-mail-on-server) | ||
| 408 | (/ (+ (* (car ctime) 65536.0) (cadr ctime)) 86400)) | ||
| 409 | i (1- (length saved))) | ||
| 410 | (while (> i 0) | ||
| 411 | (if (member (setq uidl (nth (1- i) saved)) pop3-uidl) | ||
| 412 | (progn | ||
| 413 | (setq tstamp (nth i saved)) | ||
| 414 | (if (and ctime | ||
| 415 | (> (- ctime (/ (+ (* (car tstamp) 65536.0) (cadr tstamp)) | ||
| 416 | 86400)) | ||
| 417 | pop3-leave-mail-on-server)) | ||
| 418 | ;; Mails to delete. | ||
| 419 | (progn | ||
| 420 | (setq mod t) | ||
| 421 | (push uidl dele)) | ||
| 422 | ;; Mails to keep. | ||
| 423 | (push tstamp new) | ||
| 424 | (push uidl new))) | ||
| 425 | ;; Mails having been deleted in the server. | ||
| 426 | (setq mod t)) | ||
| 427 | (decf i 2)) | ||
| 428 | (cond (saved | ||
| 429 | (setcdr saved new)) | ||
| 430 | (srvr | ||
| 431 | (setcdr (last srvr) (list (cons pop3-maildrop new)))) | ||
| 432 | (t | ||
| 433 | (add-to-list 'pop3-uidl-saved | ||
| 434 | (list pop3-mailhost (cons pop3-maildrop new)) | ||
| 435 | t))) | ||
| 436 | ;; Actually delete the messages in the server. | ||
| 437 | (when dele | ||
| 438 | (setq uidl nil | ||
| 439 | i (length pop3-uidl)) | ||
| 440 | (while (> i 0) | ||
| 441 | (when (member (nth (1- i) pop3-uidl) dele) | ||
| 442 | (push i uidl)) | ||
| 443 | (decf i)) | ||
| 444 | (when uidl | ||
| 445 | (pop3-send-streaming-command process "DELE" uidl nil))) | ||
| 446 | mod)) | ||
| 447 | |||
| 448 | (defun pop3-uidl-load () | ||
| 449 | "Load saved UIDL." | ||
| 450 | (when (file-exists-p pop3-uidl-file) | ||
| 451 | (with-temp-buffer | ||
| 452 | (condition-case code | ||
| 453 | (progn | ||
| 454 | (insert-file-contents pop3-uidl-file) | ||
| 455 | (goto-char (point-min)) | ||
| 456 | (read (current-buffer))) | ||
| 457 | (error | ||
| 458 | (message "Error while loading %s (%s)" | ||
| 459 | pop3-uidl-file (error-message-string code)) | ||
| 460 | (sit-for 1) | ||
| 461 | nil))))) | ||
| 462 | |||
| 463 | (defun pop3-uidl-save () | ||
| 464 | "Save UIDL." | ||
| 465 | (with-temp-buffer | ||
| 466 | (if pop3-uidl-saved | ||
| 467 | (progn | ||
| 468 | (insert "(") | ||
| 469 | (dolist (srvr pop3-uidl-saved) | ||
| 470 | (when (cdr srvr) | ||
| 471 | (insert "(\"" (pop srvr) "\"\n ") | ||
| 472 | (dolist (elt srvr) | ||
| 473 | (when (cdr elt) | ||
| 474 | (insert "(\"" (pop elt) "\"\n ") | ||
| 475 | (while elt | ||
| 476 | (insert (format "\"%s\" %s\n " (pop elt) (pop elt)))) | ||
| 477 | (delete-char -4) | ||
| 478 | (insert ")\n "))) | ||
| 479 | (delete-char -3) | ||
| 480 | (if (eq (char-before) ?\)) | ||
| 481 | (insert ")\n ") | ||
| 482 | (goto-char (1+ (point-at-bol))) | ||
| 483 | (delete-region (point) (point-max))))) | ||
| 484 | (when (eq (char-before) ? ) | ||
| 485 | (delete-char -2)) | ||
| 486 | (insert ")\n")) | ||
| 487 | (insert "()\n")) | ||
| 488 | (let ((buffer-file-name pop3-uidl-file) | ||
| 489 | (delete-old-versions t) | ||
| 490 | (kept-new-versions kept-new-versions) | ||
| 491 | (kept-old-versions kept-old-versions) | ||
| 492 | (version-control version-control)) | ||
| 493 | (if (consp pop3-uidl-file-backup) | ||
| 494 | (setq kept-new-versions (cadr pop3-uidl-file-backup) | ||
| 495 | kept-old-versions (car pop3-uidl-file-backup) | ||
| 496 | version-control t) | ||
| 497 | (setq version-control pop3-uidl-file-backup)) | ||
| 498 | (save-buffer)))) | ||
| 499 | |||
| 500 | (defun pop3-uidl-add-xheader (start msgno) | ||
| 501 | "Add X-UIDL header." | ||
| 502 | (let ((case-fold-search t)) | ||
| 503 | (save-restriction | ||
| 504 | (narrow-to-region start (progn | ||
| 505 | (goto-char start) | ||
| 506 | (search-forward "\n\n" nil 'move) | ||
| 507 | (1- (point)))) | ||
| 508 | (goto-char start) | ||
| 509 | (while (re-search-forward "^x-uidl:" nil t) | ||
| 510 | (while (progn | ||
| 511 | (forward-line 1) | ||
| 512 | (memq (char-after) '(?\t ? )))) | ||
| 513 | (delete-region (match-beginning 0) (point))) | ||
| 514 | (goto-char (point-max)) | ||
| 515 | (insert "X-UIDL: " (nth (1- msgno) pop3-uidl) "\n")))) | ||
| 516 | |||
| 517 | (defcustom pop3-stream-type nil | ||
| 518 | "*Transport security type for POP3 connections. | ||
| 519 | This may be either nil (plain connection), `ssl' (use an | ||
| 520 | SSL/TSL-secured stream) or `starttls' (use the starttls mechanism | ||
| 521 | to turn on TLS security after opening the stream). However, if | ||
| 522 | this is nil, `ssl' is assumed for connections to port | ||
| 523 | 995 (pop3s)." | ||
| 524 | :version "23.1" ;; No Gnus | ||
| 525 | :group 'pop3 | ||
| 526 | :type '(choice (const :tag "Plain" nil) | ||
| 527 | (const :tag "SSL/TLS" ssl) | ||
| 528 | (const starttls))) | ||
| 529 | |||
| 530 | (defun pop3-open-server (mailhost port) | ||
| 531 | "Open TCP connection to MAILHOST on PORT. | ||
| 532 | Returns the process associated with the connection." | ||
| 533 | (let ((coding-system-for-read 'binary) | ||
| 534 | (coding-system-for-write 'binary) | ||
| 535 | result) | ||
| 536 | (with-current-buffer | ||
| 537 | (get-buffer-create (concat " trace of POP session to " | ||
| 538 | mailhost)) | ||
| 539 | (erase-buffer) | ||
| 540 | (setq pop3-read-point (point-min)) | ||
| 541 | (setq result | ||
| 542 | (open-network-stream | ||
| 543 | "POP" (current-buffer) mailhost port | ||
| 544 | :type (cond | ||
| 545 | ((or (eq pop3-stream-type 'ssl) | ||
| 546 | (and (not pop3-stream-type) | ||
| 547 | (member port '(995 "pop3s")))) | ||
| 548 | 'tls) | ||
| 549 | (t | ||
| 550 | (or pop3-stream-type 'network))) | ||
| 551 | :warn-unless-encrypted t | ||
| 552 | :capability-command "CAPA\r\n" | ||
| 553 | :end-of-command "^\\(-ERR\\|+OK\\).*\n" | ||
| 554 | :end-of-capability "^\\.\r?\n\\|^-ERR" | ||
| 555 | :success "^\\+OK.*\n" | ||
| 556 | :return-list t | ||
| 557 | :starttls-function | ||
| 558 | (lambda (capabilities) | ||
| 559 | (and (string-match "\\bSTLS\\b" capabilities) | ||
| 560 | "STLS\r\n")))) | ||
| 561 | (when result | ||
| 562 | (let ((response (plist-get (cdr result) :greeting))) | ||
| 563 | (setq pop3-timestamp | ||
| 564 | (substring response (or (string-match "<" response) 0) | ||
| 565 | (+ 1 (or (string-match ">" response) -1))))) | ||
| 566 | (set-process-query-on-exit-flag (car result) nil) | ||
| 567 | (erase-buffer) | ||
| 568 | (car result))))) | ||
| 569 | |||
| 570 | ;; Support functions | ||
| 571 | |||
| 572 | (defun pop3-send-command (process command) | ||
| 573 | (set-buffer (process-buffer process)) | ||
| 574 | (goto-char (point-max)) | ||
| 575 | ;; (if (= (aref command 0) ?P) | ||
| 576 | ;; (insert "PASS <omitted>\r\n") | ||
| 577 | ;; (insert command "\r\n")) | ||
| 578 | (setq pop3-read-point (point)) | ||
| 579 | (goto-char (point-max)) | ||
| 580 | (process-send-string process (concat command "\r\n"))) | ||
| 581 | |||
| 582 | (defun pop3-read-response (process &optional return) | ||
| 583 | "Read the response from the server. | ||
| 584 | Return the response string if optional second argument is non-nil." | ||
| 585 | (let ((case-fold-search nil) | ||
| 586 | match-end) | ||
| 587 | (with-current-buffer (process-buffer process) | ||
| 588 | (goto-char pop3-read-point) | ||
| 589 | (while (and (memq (process-status process) '(open run)) | ||
| 590 | (not (search-forward "\r\n" nil t))) | ||
| 591 | (pop3-accept-process-output process) | ||
| 592 | (goto-char pop3-read-point)) | ||
| 593 | (setq match-end (point)) | ||
| 594 | (goto-char pop3-read-point) | ||
| 595 | (if (looking-at "-ERR") | ||
| 596 | (error "%s" (buffer-substring (point) (- match-end 2))) | ||
| 597 | (if (not (looking-at "+OK")) | ||
| 598 | (progn (setq pop3-read-point match-end) nil) | ||
| 599 | (setq pop3-read-point match-end) | ||
| 600 | (if return | ||
| 601 | (buffer-substring (point) match-end) | ||
| 602 | t) | ||
| 603 | ))))) | ||
| 604 | |||
| 605 | (defun pop3-clean-region (start end) | ||
| 606 | (setq end (set-marker (make-marker) end)) | ||
| 607 | (save-excursion | ||
| 608 | (goto-char start) | ||
| 609 | (while (and (< (point) end) (search-forward "\r\n" end t)) | ||
| 610 | (replace-match "\n" t t)) | ||
| 611 | (goto-char start) | ||
| 612 | (while (and (< (point) end) (re-search-forward "^\\." end t)) | ||
| 613 | (replace-match "" t t) | ||
| 614 | (forward-char))) | ||
| 615 | (set-marker end nil)) | ||
| 616 | |||
| 617 | ;; Copied from message-make-date. | ||
| 618 | (defun pop3-make-date (&optional now) | ||
| 619 | "Make a valid date header. | ||
| 620 | If NOW, use that time instead." | ||
| 621 | (require 'parse-time) | ||
| 622 | (let* ((now (or now (current-time))) | ||
| 623 | (zone (nth 8 (decode-time now))) | ||
| 624 | (sign "+")) | ||
| 625 | (when (< zone 0) | ||
| 626 | (setq sign "-") | ||
| 627 | (setq zone (- zone))) | ||
| 628 | (concat | ||
| 629 | (format-time-string "%d" now) | ||
| 630 | ;; The month name of the %b spec is locale-specific. Pfff. | ||
| 631 | (format " %s " | ||
| 632 | (capitalize (car (rassoc (nth 4 (decode-time now)) | ||
| 633 | parse-time-months)))) | ||
| 634 | (format-time-string "%Y %H:%M:%S %z" now)))) | ||
| 635 | |||
| 636 | (defun pop3-munge-message-separator (start end) | ||
| 637 | "Check to see if a message separator exists. If not, generate one." | ||
| 638 | (save-excursion | ||
| 639 | (save-restriction | ||
| 640 | (narrow-to-region start end) | ||
| 641 | (goto-char (point-min)) | ||
| 642 | (if (not (or (looking-at "From .?") ; Unix mail | ||
| 643 | (looking-at "\001\001\001\001\n") ; MMDF | ||
| 644 | (looking-at "BABYL OPTIONS:") ; Babyl | ||
| 645 | )) | ||
| 646 | (let* ((from (mail-strip-quoted-names (mail-fetch-field "From"))) | ||
| 647 | (tdate (mail-fetch-field "Date")) | ||
| 648 | (date (split-string (or (and tdate | ||
| 649 | (not (string= "" tdate)) | ||
| 650 | tdate) | ||
| 651 | (pop3-make-date)) | ||
| 652 | " ")) | ||
| 653 | (From_)) | ||
| 654 | ;; sample date formats I have seen | ||
| 655 | ;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT) | ||
| 656 | ;; Date: 08 Jul 1996 23:22:24 -0400 | ||
| 657 | ;; should be | ||
| 658 | ;; Tue Jul 9 09:04:21 1996 | ||
| 659 | |||
| 660 | ;; Fixme: This should use timezone on the date field contents. | ||
| 661 | (setq date | ||
| 662 | (cond ((not date) | ||
| 663 | "Tue Jan 1 00:00:0 1900") | ||
| 664 | ((string-match "[A-Z]" (nth 0 date)) | ||
| 665 | (format "%s %s %s %s %s" | ||
| 666 | (nth 0 date) (nth 2 date) (nth 1 date) | ||
| 667 | (nth 4 date) (nth 3 date))) | ||
| 668 | (t | ||
| 669 | ;; this really needs to be better but I don't feel | ||
| 670 | ;; like writing a date to day converter. | ||
| 671 | (format "Sun %s %s %s %s" | ||
| 672 | (nth 1 date) (nth 0 date) | ||
| 673 | (nth 3 date) (nth 2 date))) | ||
| 674 | )) | ||
| 675 | (setq From_ (format "\nFrom %s %s\n" from date)) | ||
| 676 | (while (string-match "," From_) | ||
| 677 | (setq From_ (concat (substring From_ 0 (match-beginning 0)) | ||
| 678 | (substring From_ (match-end 0))))) | ||
| 679 | (goto-char (point-min)) | ||
| 680 | (insert From_) | ||
| 681 | (if (search-forward "\n\n" nil t) | ||
| 682 | nil | ||
| 683 | (goto-char (point-max)) | ||
| 684 | (insert "\n")) | ||
| 685 | (let ((size (- (point-max) (point)))) | ||
| 686 | (forward-line -1) | ||
| 687 | (insert (format "Content-Length: %s\n" size))) | ||
| 688 | ))))) | ||
| 689 | |||
| 690 | ;; The Command Set | ||
| 691 | |||
| 692 | ;; AUTHORIZATION STATE | ||
| 693 | |||
| 694 | (defun pop3-user (process user) | ||
| 695 | "Send USER information to POP3 server." | ||
| 696 | (pop3-send-command process (format "USER %s" user)) | ||
| 697 | (let ((response (pop3-read-response process t))) | ||
| 698 | (if (not (and response (string-match "+OK" response))) | ||
| 699 | (error "USER %s not valid" user)))) | ||
| 700 | |||
| 701 | (defun pop3-pass (process) | ||
| 702 | "Send authentication information to the server." | ||
| 703 | (pop3-send-command process (format "PASS %s" pop3-password)) | ||
| 704 | (let ((response (pop3-read-response process t))) | ||
| 705 | (if (not (and response (string-match "+OK" response))) | ||
| 706 | (pop3-quit process)))) | ||
| 707 | |||
| 708 | (defun pop3-apop (process user) | ||
| 709 | "Send alternate authentication information to the server." | ||
| 710 | (let ((pass pop3-password)) | ||
| 711 | (if (and pop3-password-required (not pass)) | ||
| 712 | (setq pass | ||
| 713 | (read-passwd (format "Password for %s: " pop3-maildrop)))) | ||
| 714 | (if pass | ||
| 715 | (let ((hash (md5 (concat pop3-timestamp pass) nil nil 'binary))) | ||
| 716 | (pop3-send-command process (format "APOP %s %s" user hash)) | ||
| 717 | (let ((response (pop3-read-response process t))) | ||
| 718 | (if (not (and response (string-match "+OK" response))) | ||
| 719 | (pop3-quit process))))) | ||
| 720 | )) | ||
| 721 | |||
| 722 | ;; TRANSACTION STATE | ||
| 723 | |||
| 724 | (defun pop3-stat (process) | ||
| 725 | "Return the number of messages in the maildrop and the maildrop's size." | ||
| 726 | (pop3-send-command process "STAT") | ||
| 727 | (let ((response (pop3-read-response process t))) | ||
| 728 | (list (string-to-number (nth 1 (split-string response " "))) | ||
| 729 | (string-to-number (nth 2 (split-string response " ")))) | ||
| 730 | )) | ||
| 731 | |||
| 732 | (defun pop3-list (process &optional msg) | ||
| 733 | "If MSG is nil, return an alist of (MESSAGE-ID . SIZE) pairs. | ||
| 734 | Otherwise, return the size of the message-id MSG" | ||
| 735 | (pop3-send-command process (if msg | ||
| 736 | (format "LIST %d" msg) | ||
| 737 | "LIST")) | ||
| 738 | (let ((response (pop3-read-response process t))) | ||
| 739 | (if msg | ||
| 740 | (string-to-number (nth 2 (split-string response " "))) | ||
| 741 | (let ((start pop3-read-point) end) | ||
| 742 | (with-current-buffer (process-buffer process) | ||
| 743 | (while (not (re-search-forward "^\\.\r\n" nil t)) | ||
| 744 | (pop3-accept-process-output process) | ||
| 745 | (goto-char start)) | ||
| 746 | (setq pop3-read-point (point-marker)) | ||
| 747 | (goto-char (match-beginning 0)) | ||
| 748 | (setq end (point-marker)) | ||
| 749 | (mapcar #'(lambda (s) (let ((split (split-string s " "))) | ||
| 750 | (cons (string-to-number (nth 0 split)) | ||
| 751 | (string-to-number (nth 1 split))))) | ||
| 752 | (split-string (buffer-substring start end) "\r\n" t))))))) | ||
| 753 | |||
| 754 | (defun pop3-retr (process msg crashbuf) | ||
| 755 | "Retrieve message-id MSG to buffer CRASHBUF." | ||
| 756 | (pop3-send-command process (format "RETR %s" msg)) | ||
| 757 | (pop3-read-response process) | ||
| 758 | (let ((start pop3-read-point) end) | ||
| 759 | (with-current-buffer (process-buffer process) | ||
| 760 | (while (not (re-search-forward "^\\.\r\n" nil t)) | ||
| 761 | (unless (memq (process-status process) '(open run)) | ||
| 762 | (error "pop3 server closed the connection")) | ||
| 763 | (pop3-accept-process-output process) | ||
| 764 | (goto-char start)) | ||
| 765 | (setq pop3-read-point (point-marker)) | ||
| 766 | ;; this code does not seem to work for some POP servers... | ||
| 767 | ;; and I cannot figure out why not. | ||
| 768 | ;; (goto-char (match-beginning 0)) | ||
| 769 | ;; (backward-char 2) | ||
| 770 | ;; (if (not (looking-at "\r\n")) | ||
| 771 | ;; (insert "\r\n")) | ||
| 772 | ;; (re-search-forward "\\.\r\n") | ||
| 773 | (goto-char (match-beginning 0)) | ||
| 774 | (setq end (point-marker)) | ||
| 775 | (pop3-clean-region start end) | ||
| 776 | (pop3-munge-message-separator start end) | ||
| 777 | (with-current-buffer crashbuf | ||
| 778 | (erase-buffer)) | ||
| 779 | (copy-to-buffer crashbuf start end) | ||
| 780 | (delete-region start end) | ||
| 781 | ))) | ||
| 782 | |||
| 783 | (defun pop3-dele (process msg) | ||
| 784 | "Mark message-id MSG as deleted." | ||
| 785 | (pop3-send-command process (format "DELE %s" msg)) | ||
| 786 | (pop3-read-response process)) | ||
| 787 | |||
| 788 | (defun pop3-noop (process msg) | ||
| 789 | "No-operation." | ||
| 790 | (pop3-send-command process "NOOP") | ||
| 791 | (pop3-read-response process)) | ||
| 792 | |||
| 793 | (defun pop3-last (process) | ||
| 794 | "Return highest accessed message-id number for the session." | ||
| 795 | (pop3-send-command process "LAST") | ||
| 796 | (let ((response (pop3-read-response process t))) | ||
| 797 | (string-to-number (nth 1 (split-string response " "))) | ||
| 798 | )) | ||
| 799 | |||
| 800 | (defun pop3-rset (process) | ||
| 801 | "Remove all delete marks from current maildrop." | ||
| 802 | (pop3-send-command process "RSET") | ||
| 803 | (pop3-read-response process)) | ||
| 804 | |||
| 805 | ;; UPDATE | ||
| 806 | |||
| 807 | (defun pop3-quit (process) | ||
| 808 | "Close connection to POP3 server. | ||
| 809 | Tell server to remove all messages marked as deleted, unlock the maildrop, | ||
| 810 | and close the connection." | ||
| 811 | (pop3-send-command process "QUIT") | ||
| 812 | (pop3-read-response process t) | ||
| 813 | (if process | ||
| 814 | (with-current-buffer (process-buffer process) | ||
| 815 | (goto-char (point-max)) | ||
| 816 | (delete-process process)))) | ||
| 817 | |||
| 818 | ;; Summary of POP3 (Post Office Protocol version 3) commands and responses | ||
| 819 | |||
| 820 | ;;; AUTHORIZATION STATE | ||
| 821 | |||
| 822 | ;; Initial TCP connection | ||
| 823 | ;; Arguments: none | ||
| 824 | ;; Restrictions: none | ||
| 825 | ;; Possible responses: | ||
| 826 | ;; +OK [POP3 server ready] | ||
| 827 | |||
| 828 | ;; USER name | ||
| 829 | ;; Arguments: a server specific user-id (required) | ||
| 830 | ;; Restrictions: authorization state [after unsuccessful USER or PASS | ||
| 831 | ;; Possible responses: | ||
| 832 | ;; +OK [valid user-id] | ||
| 833 | ;; -ERR [invalid user-id] | ||
| 834 | |||
| 835 | ;; PASS string | ||
| 836 | ;; Arguments: a server/user-id specific password (required) | ||
| 837 | ;; Restrictions: authorization state, after successful USER | ||
| 838 | ;; Possible responses: | ||
| 839 | ;; +OK [maildrop locked and ready] | ||
| 840 | ;; -ERR [invalid password] | ||
| 841 | ;; -ERR [unable to lock maildrop] | ||
| 842 | |||
| 843 | ;; STLS (RFC 2595) | ||
| 844 | ;; Arguments: none | ||
| 845 | ;; Restrictions: Only permitted in AUTHORIZATION state. | ||
| 846 | ;; Possible responses: | ||
| 847 | ;; +OK | ||
| 848 | ;; -ERR | ||
| 849 | |||
| 850 | ;;; TRANSACTION STATE | ||
| 851 | |||
| 852 | ;; STAT | ||
| 853 | ;; Arguments: none | ||
| 854 | ;; Restrictions: transaction state | ||
| 855 | ;; Possible responses: | ||
| 856 | ;; +OK nn mm [# of messages, size of maildrop] | ||
| 857 | |||
| 858 | ;; LIST [msg] | ||
| 859 | ;; Arguments: a message-id (optional) | ||
| 860 | ;; Restrictions: transaction state; msg must not be deleted | ||
| 861 | ;; Possible responses: | ||
| 862 | ;; +OK [scan listing follows] | ||
| 863 | ;; -ERR [no such message] | ||
| 864 | |||
| 865 | ;; RETR msg | ||
| 866 | ;; Arguments: a message-id (required) | ||
| 867 | ;; Restrictions: transaction state; msg must not be deleted | ||
| 868 | ;; Possible responses: | ||
| 869 | ;; +OK [message contents follow] | ||
| 870 | ;; -ERR [no such message] | ||
| 871 | |||
| 872 | ;; DELE msg | ||
| 873 | ;; Arguments: a message-id (required) | ||
| 874 | ;; Restrictions: transaction state; msg must not be deleted | ||
| 875 | ;; Possible responses: | ||
| 876 | ;; +OK [message deleted] | ||
| 877 | ;; -ERR [no such message] | ||
| 878 | |||
| 879 | ;; NOOP | ||
| 880 | ;; Arguments: none | ||
| 881 | ;; Restrictions: transaction state | ||
| 882 | ;; Possible responses: | ||
| 883 | ;; +OK | ||
| 884 | |||
| 885 | ;; LAST | ||
| 886 | ;; Arguments: none | ||
| 887 | ;; Restrictions: transaction state | ||
| 888 | ;; Possible responses: | ||
| 889 | ;; +OK nn [highest numbered message accessed] | ||
| 890 | |||
| 891 | ;; RSET | ||
| 892 | ;; Arguments: none | ||
| 893 | ;; Restrictions: transaction state | ||
| 894 | ;; Possible responses: | ||
| 895 | ;; +OK [all delete marks removed] | ||
| 896 | |||
| 897 | ;; UIDL [msg] | ||
| 898 | ;; Arguments: a message-id (optional) | ||
| 899 | ;; Restrictions: transaction state; msg must not be deleted | ||
| 900 | ;; Possible responses: | ||
| 901 | ;; +OK [uidl listing follows] | ||
| 902 | ;; -ERR [no such message] | ||
| 903 | |||
| 904 | ;;; UPDATE STATE | ||
| 905 | |||
| 906 | ;; QUIT | ||
| 907 | ;; Arguments: none | ||
| 908 | ;; Restrictions: none | ||
| 909 | ;; Possible responses: | ||
| 910 | ;; +OK [TCP connection closed] | ||
| 911 | |||
| 912 | (provide 'pop3) | ||
| 913 | |||
| 914 | ;;; pop3.el ends here | ||
diff --git a/lisp/net/sieve-manage.el b/lisp/net/sieve-manage.el new file mode 100644 index 00000000000..695bbd860de --- /dev/null +++ b/lisp/net/sieve-manage.el | |||
| @@ -0,0 +1,575 @@ | |||
| 1 | ;;; sieve-manage.el --- Implementation of the managesieve protocol in elisp | ||
| 2 | |||
| 3 | ;; Copyright (C) 2001-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Simon Josefsson <simon@josefsson.org> | ||
| 6 | ;; Albert Krewinkel <tarleb@moltkeplatz.de> | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;; This library provides an elisp API for the managesieve network | ||
| 26 | ;; protocol. | ||
| 27 | ;; | ||
| 28 | ;; It uses the SASL library for authentication, which means it | ||
| 29 | ;; supports DIGEST-MD5, CRAM-MD5, SCRAM-MD5, NTLM, PLAIN and LOGIN | ||
| 30 | ;; methods. STARTTLS is not well tested, but should be easy to get to | ||
| 31 | ;; work if someone wants. | ||
| 32 | ;; | ||
| 33 | ;; The API should be fairly obvious for anyone familiar with the | ||
| 34 | ;; managesieve protocol, interface functions include: | ||
| 35 | ;; | ||
| 36 | ;; `sieve-manage-open' | ||
| 37 | ;; open connection to managesieve server, returning a buffer to be | ||
| 38 | ;; used by all other API functions. | ||
| 39 | ;; | ||
| 40 | ;; `sieve-manage-opened' | ||
| 41 | ;; check if a server is open or not | ||
| 42 | ;; | ||
| 43 | ;; `sieve-manage-close' | ||
| 44 | ;; close a server connection. | ||
| 45 | ;; | ||
| 46 | ;; `sieve-manage-listscripts' | ||
| 47 | ;; `sieve-manage-deletescript' | ||
| 48 | ;; `sieve-manage-getscript' | ||
| 49 | ;; performs managesieve protocol actions | ||
| 50 | ;; | ||
| 51 | ;; and that's it. Example of a managesieve session in *scratch*: | ||
| 52 | ;; | ||
| 53 | ;; (with-current-buffer (sieve-manage-open "mail.example.com") | ||
| 54 | ;; (sieve-manage-authenticate) | ||
| 55 | ;; (sieve-manage-listscripts)) | ||
| 56 | ;; | ||
| 57 | ;; => ((active . "main") "vacation") | ||
| 58 | ;; | ||
| 59 | ;; References: | ||
| 60 | ;; | ||
| 61 | ;; draft-martin-managesieve-02.txt, | ||
| 62 | ;; "A Protocol for Remotely Managing Sieve Scripts", | ||
| 63 | ;; by Tim Martin. | ||
| 64 | ;; | ||
| 65 | ;; Release history: | ||
| 66 | ;; | ||
| 67 | ;; 2001-10-31 Committed to Oort Gnus. | ||
| 68 | ;; 2002-07-27 Added DELETESCRIPT. Suggested by Ned Ludd. | ||
| 69 | ;; 2002-08-03 Use SASL library. | ||
| 70 | ;; 2013-06-05 Enabled STARTTLS support, fixed bit rot. | ||
| 71 | |||
| 72 | ;;; Code: | ||
| 73 | |||
| 74 | (if (locate-library "password-cache") | ||
| 75 | (require 'password-cache) | ||
| 76 | (require 'password)) | ||
| 77 | |||
| 78 | (eval-when-compile (require 'cl)) | ||
| 79 | (require 'sasl) | ||
| 80 | (require 'starttls) | ||
| 81 | (autoload 'sasl-find-mechanism "sasl") | ||
| 82 | (autoload 'auth-source-search "auth-source") | ||
| 83 | |||
| 84 | ;; User customizable variables: | ||
| 85 | |||
| 86 | (defgroup sieve-manage nil | ||
| 87 | "Low-level Managesieve protocol issues." | ||
| 88 | :group 'mail | ||
| 89 | :prefix "sieve-") | ||
| 90 | |||
| 91 | (defcustom sieve-manage-log "*sieve-manage-log*" | ||
| 92 | "Name of buffer for managesieve session trace." | ||
| 93 | :type 'string | ||
| 94 | :group 'sieve-manage) | ||
| 95 | |||
| 96 | (defcustom sieve-manage-server-eol "\r\n" | ||
| 97 | "The EOL string sent from the server." | ||
| 98 | :type 'string | ||
| 99 | :group 'sieve-manage) | ||
| 100 | |||
| 101 | (defcustom sieve-manage-client-eol "\r\n" | ||
| 102 | "The EOL string we send to the server." | ||
| 103 | :type 'string | ||
| 104 | :group 'sieve-manage) | ||
| 105 | |||
| 106 | (defcustom sieve-manage-authenticators '(digest-md5 | ||
| 107 | cram-md5 | ||
| 108 | scram-md5 | ||
| 109 | ntlm | ||
| 110 | plain | ||
| 111 | login) | ||
| 112 | "Priority of authenticators to consider when authenticating to server." | ||
| 113 | ;; FIXME Improve this. It's not `set'. | ||
| 114 | ;; It's like (repeat (choice (const ...))), where each choice can | ||
| 115 | ;; only appear once. | ||
| 116 | :type '(repeat symbol) | ||
| 117 | :group 'sieve-manage) | ||
| 118 | |||
| 119 | (defcustom sieve-manage-authenticator-alist | ||
| 120 | '((cram-md5 sieve-manage-cram-md5-p sieve-manage-cram-md5-auth) | ||
| 121 | (digest-md5 sieve-manage-digest-md5-p sieve-manage-digest-md5-auth) | ||
| 122 | (scram-md5 sieve-manage-scram-md5-p sieve-manage-scram-md5-auth) | ||
| 123 | (ntlm sieve-manage-ntlm-p sieve-manage-ntlm-auth) | ||
| 124 | (plain sieve-manage-plain-p sieve-manage-plain-auth) | ||
| 125 | (login sieve-manage-login-p sieve-manage-login-auth)) | ||
| 126 | "Definition of authenticators. | ||
| 127 | |||
| 128 | \(NAME CHECK AUTHENTICATE) | ||
| 129 | |||
| 130 | NAME names the authenticator. CHECK is a function returning non-nil if | ||
| 131 | the server support the authenticator and AUTHENTICATE is a function | ||
| 132 | for doing the actual authentication." | ||
| 133 | :type '(repeat (list (symbol :tag "Name") (function :tag "Check function") | ||
| 134 | (function :tag "Authentication function"))) | ||
| 135 | :group 'sieve-manage) | ||
| 136 | |||
| 137 | (defcustom sieve-manage-default-port "sieve" | ||
| 138 | "Default port number or service name for managesieve protocol." | ||
| 139 | :type '(choice integer string) | ||
| 140 | :version "24.4" | ||
| 141 | :group 'sieve-manage) | ||
| 142 | |||
| 143 | (defcustom sieve-manage-default-stream 'network | ||
| 144 | "Default stream type to use for `sieve-manage'." | ||
| 145 | :version "24.1" | ||
| 146 | :type 'symbol | ||
| 147 | :group 'sieve-manage) | ||
| 148 | |||
| 149 | ;; Internal variables: | ||
| 150 | |||
| 151 | (defconst sieve-manage-local-variables '(sieve-manage-server | ||
| 152 | sieve-manage-port | ||
| 153 | sieve-manage-auth | ||
| 154 | sieve-manage-stream | ||
| 155 | sieve-manage-process | ||
| 156 | sieve-manage-client-eol | ||
| 157 | sieve-manage-server-eol | ||
| 158 | sieve-manage-capability)) | ||
| 159 | (defconst sieve-manage-coding-system-for-read 'binary) | ||
| 160 | (defconst sieve-manage-coding-system-for-write 'binary) | ||
| 161 | (defvar sieve-manage-stream nil) | ||
| 162 | (defvar sieve-manage-auth nil) | ||
| 163 | (defvar sieve-manage-server nil) | ||
| 164 | (defvar sieve-manage-port nil) | ||
| 165 | (defvar sieve-manage-state 'closed | ||
| 166 | "Managesieve state. | ||
| 167 | Valid states are `closed', `initial', `nonauth', and `auth'.") | ||
| 168 | (defvar sieve-manage-process nil) | ||
| 169 | (defvar sieve-manage-capability nil) | ||
| 170 | |||
| 171 | ;; Internal utility functions | ||
| 172 | (autoload 'mm-enable-multibyte "mm-util") | ||
| 173 | |||
| 174 | (defun sieve-manage-make-process-buffer () | ||
| 175 | (with-current-buffer | ||
| 176 | (generate-new-buffer (format " *sieve %s:%s*" | ||
| 177 | sieve-manage-server | ||
| 178 | sieve-manage-port)) | ||
| 179 | (mapc 'make-local-variable sieve-manage-local-variables) | ||
| 180 | (mm-enable-multibyte) | ||
| 181 | (buffer-disable-undo) | ||
| 182 | (current-buffer))) | ||
| 183 | |||
| 184 | (defun sieve-manage-erase (&optional p buffer) | ||
| 185 | (let ((buffer (or buffer (current-buffer)))) | ||
| 186 | (and sieve-manage-log | ||
| 187 | (with-current-buffer (get-buffer-create sieve-manage-log) | ||
| 188 | (mm-enable-multibyte) | ||
| 189 | (buffer-disable-undo) | ||
| 190 | (goto-char (point-max)) | ||
| 191 | (insert-buffer-substring buffer (with-current-buffer buffer | ||
| 192 | (point-min)) | ||
| 193 | (or p (with-current-buffer buffer | ||
| 194 | (point-max))))))) | ||
| 195 | (delete-region (point-min) (or p (point-max)))) | ||
| 196 | |||
| 197 | (defun sieve-manage-open-server (server port &optional stream buffer) | ||
| 198 | "Open network connection to SERVER on PORT. | ||
| 199 | Return the buffer associated with the connection." | ||
| 200 | (with-current-buffer buffer | ||
| 201 | (sieve-manage-erase) | ||
| 202 | (setq sieve-manage-state 'initial) | ||
| 203 | (destructuring-bind (proc . props) | ||
| 204 | (open-network-stream | ||
| 205 | "SIEVE" buffer server port | ||
| 206 | :type stream | ||
| 207 | :capability-command "CAPABILITY\r\n" | ||
| 208 | :end-of-command "^\\(OK\\|NO\\).*\n" | ||
| 209 | :success "^OK.*\n" | ||
| 210 | :return-list t | ||
| 211 | :starttls-function | ||
| 212 | (lambda (capabilities) | ||
| 213 | (when (string-match "\\bSTARTTLS\\b" capabilities) | ||
| 214 | "STARTTLS\r\n"))) | ||
| 215 | (setq sieve-manage-process proc) | ||
| 216 | (setq sieve-manage-capability | ||
| 217 | (sieve-manage-parse-capability (plist-get props :capabilities))) | ||
| 218 | ;; Ignore new capabilities issues after successful STARTTLS | ||
| 219 | (when (and (memq stream '(nil network starttls)) | ||
| 220 | (eq (plist-get props :type) 'tls)) | ||
| 221 | (sieve-manage-drop-next-answer)) | ||
| 222 | (current-buffer)))) | ||
| 223 | |||
| 224 | ;; Authenticators | ||
| 225 | (defun sieve-sasl-auth (buffer mech) | ||
| 226 | "Login to server using the SASL MECH method." | ||
| 227 | (message "sieve: Authenticating using %s..." mech) | ||
| 228 | (with-current-buffer buffer | ||
| 229 | (let* ((auth-info (auth-source-search :host sieve-manage-server | ||
| 230 | :port "sieve" | ||
| 231 | :max 1 | ||
| 232 | :create t)) | ||
| 233 | (user-name (or (plist-get (nth 0 auth-info) :user) "")) | ||
| 234 | (user-password (or (plist-get (nth 0 auth-info) :secret) "")) | ||
| 235 | (user-password (if (functionp user-password) | ||
| 236 | (funcall user-password) | ||
| 237 | user-password)) | ||
| 238 | (client (sasl-make-client (sasl-find-mechanism (list mech)) | ||
| 239 | user-name "sieve" sieve-manage-server)) | ||
| 240 | (sasl-read-passphrase | ||
| 241 | ;; We *need* to copy the password, because sasl will modify it | ||
| 242 | ;; somehow. | ||
| 243 | `(lambda (prompt) ,(copy-sequence user-password))) | ||
| 244 | (step (sasl-next-step client nil)) | ||
| 245 | (tag (sieve-manage-send | ||
| 246 | (concat | ||
| 247 | "AUTHENTICATE \"" | ||
| 248 | mech | ||
| 249 | "\"" | ||
| 250 | (and (sasl-step-data step) | ||
| 251 | (concat | ||
| 252 | " \"" | ||
| 253 | (base64-encode-string | ||
| 254 | (sasl-step-data step) | ||
| 255 | 'no-line-break) | ||
| 256 | "\""))))) | ||
| 257 | data rsp) | ||
| 258 | (catch 'done | ||
| 259 | (while t | ||
| 260 | (setq rsp nil) | ||
| 261 | (goto-char (point-min)) | ||
| 262 | (while (null (or (progn | ||
| 263 | (setq rsp (sieve-manage-is-string)) | ||
| 264 | (if (not (and rsp (looking-at | ||
| 265 | sieve-manage-server-eol))) | ||
| 266 | (setq rsp nil) | ||
| 267 | (goto-char (match-end 0)) | ||
| 268 | rsp)) | ||
| 269 | (setq rsp (sieve-manage-is-okno)))) | ||
| 270 | (accept-process-output sieve-manage-process 1) | ||
| 271 | (goto-char (point-min))) | ||
| 272 | (sieve-manage-erase) | ||
| 273 | (when (sieve-manage-ok-p rsp) | ||
| 274 | (when (and (cadr rsp) | ||
| 275 | (string-match "^SASL \"\\([^\"]+\\)\"" (cadr rsp))) | ||
| 276 | (sasl-step-set-data | ||
| 277 | step (base64-decode-string (match-string 1 (cadr rsp))))) | ||
| 278 | (if (and (setq step (sasl-next-step client step)) | ||
| 279 | (setq data (sasl-step-data step))) | ||
| 280 | ;; We got data for server but it's finished | ||
| 281 | (error "Server not ready for SASL data: %s" data) | ||
| 282 | ;; The authentication process is finished. | ||
| 283 | (throw 'done t))) | ||
| 284 | (unless (stringp rsp) | ||
| 285 | (error "Server aborted SASL authentication: %s" (caddr rsp))) | ||
| 286 | (sasl-step-set-data step (base64-decode-string rsp)) | ||
| 287 | (setq step (sasl-next-step client step)) | ||
| 288 | (sieve-manage-send | ||
| 289 | (if (sasl-step-data step) | ||
| 290 | (concat "\"" | ||
| 291 | (base64-encode-string (sasl-step-data step) | ||
| 292 | 'no-line-break) | ||
| 293 | "\"") | ||
| 294 | "")))) | ||
| 295 | (message "sieve: Login using %s...done" mech)))) | ||
| 296 | |||
| 297 | (defun sieve-manage-cram-md5-p (buffer) | ||
| 298 | (sieve-manage-capability "SASL" "CRAM-MD5" buffer)) | ||
| 299 | |||
| 300 | (defun sieve-manage-cram-md5-auth (buffer) | ||
| 301 | "Login to managesieve server using the CRAM-MD5 SASL method." | ||
| 302 | (sieve-sasl-auth buffer "CRAM-MD5")) | ||
| 303 | |||
| 304 | (defun sieve-manage-digest-md5-p (buffer) | ||
| 305 | (sieve-manage-capability "SASL" "DIGEST-MD5" buffer)) | ||
| 306 | |||
| 307 | (defun sieve-manage-digest-md5-auth (buffer) | ||
| 308 | "Login to managesieve server using the DIGEST-MD5 SASL method." | ||
| 309 | (sieve-sasl-auth buffer "DIGEST-MD5")) | ||
| 310 | |||
| 311 | (defun sieve-manage-scram-md5-p (buffer) | ||
| 312 | (sieve-manage-capability "SASL" "SCRAM-MD5" buffer)) | ||
| 313 | |||
| 314 | (defun sieve-manage-scram-md5-auth (buffer) | ||
| 315 | "Login to managesieve server using the SCRAM-MD5 SASL method." | ||
| 316 | (sieve-sasl-auth buffer "SCRAM-MD5")) | ||
| 317 | |||
| 318 | (defun sieve-manage-ntlm-p (buffer) | ||
| 319 | (sieve-manage-capability "SASL" "NTLM" buffer)) | ||
| 320 | |||
| 321 | (defun sieve-manage-ntlm-auth (buffer) | ||
| 322 | "Login to managesieve server using the NTLM SASL method." | ||
| 323 | (sieve-sasl-auth buffer "NTLM")) | ||
| 324 | |||
| 325 | (defun sieve-manage-plain-p (buffer) | ||
| 326 | (sieve-manage-capability "SASL" "PLAIN" buffer)) | ||
| 327 | |||
| 328 | (defun sieve-manage-plain-auth (buffer) | ||
| 329 | "Login to managesieve server using the PLAIN SASL method." | ||
| 330 | (sieve-sasl-auth buffer "PLAIN")) | ||
| 331 | |||
| 332 | (defun sieve-manage-login-p (buffer) | ||
| 333 | (sieve-manage-capability "SASL" "LOGIN" buffer)) | ||
| 334 | |||
| 335 | (defun sieve-manage-login-auth (buffer) | ||
| 336 | "Login to managesieve server using the LOGIN SASL method." | ||
| 337 | (sieve-sasl-auth buffer "LOGIN")) | ||
| 338 | |||
| 339 | ;; Managesieve API | ||
| 340 | |||
| 341 | (defun sieve-manage-open (server &optional port stream auth buffer) | ||
| 342 | "Open a network connection to a managesieve SERVER (string). | ||
| 343 | Optional argument PORT is port number (integer) on remote server. | ||
| 344 | Optional argument STREAM is any of `sieve-manage-streams' (a symbol). | ||
| 345 | Optional argument AUTH indicates authenticator to use, see | ||
| 346 | `sieve-manage-authenticators' for available authenticators. | ||
| 347 | If nil, chooses the best stream the server is capable of. | ||
| 348 | Optional argument BUFFER is buffer (buffer, or string naming buffer) | ||
| 349 | to work in." | ||
| 350 | (setq sieve-manage-port (or port sieve-manage-default-port)) | ||
| 351 | (with-current-buffer (or buffer (sieve-manage-make-process-buffer)) | ||
| 352 | (setq sieve-manage-server (or server | ||
| 353 | sieve-manage-server) | ||
| 354 | sieve-manage-stream (or stream | ||
| 355 | sieve-manage-stream | ||
| 356 | sieve-manage-default-stream) | ||
| 357 | sieve-manage-auth (or auth | ||
| 358 | sieve-manage-auth)) | ||
| 359 | (message "sieve: Connecting to %s..." sieve-manage-server) | ||
| 360 | (sieve-manage-open-server sieve-manage-server | ||
| 361 | sieve-manage-port | ||
| 362 | sieve-manage-stream | ||
| 363 | (current-buffer)) | ||
| 364 | (when (sieve-manage-opened (current-buffer)) | ||
| 365 | ;; Choose authenticator | ||
| 366 | (when (and (null sieve-manage-auth) | ||
| 367 | (not (eq sieve-manage-state 'auth))) | ||
| 368 | (dolist (auth sieve-manage-authenticators) | ||
| 369 | (when (funcall (nth 1 (assq auth sieve-manage-authenticator-alist)) | ||
| 370 | buffer) | ||
| 371 | (setq sieve-manage-auth auth) | ||
| 372 | (return))) | ||
| 373 | (unless sieve-manage-auth | ||
| 374 | (error "Couldn't figure out authenticator for server"))) | ||
| 375 | (sieve-manage-erase) | ||
| 376 | (current-buffer)))) | ||
| 377 | |||
| 378 | (defun sieve-manage-authenticate (&optional buffer) | ||
| 379 | "Authenticate on server in BUFFER. | ||
| 380 | Return `sieve-manage-state' value." | ||
| 381 | (with-current-buffer (or buffer (current-buffer)) | ||
| 382 | (if (eq sieve-manage-state 'nonauth) | ||
| 383 | (when (funcall (nth 2 (assq sieve-manage-auth | ||
| 384 | sieve-manage-authenticator-alist)) | ||
| 385 | (current-buffer)) | ||
| 386 | (setq sieve-manage-state 'auth)) | ||
| 387 | sieve-manage-state))) | ||
| 388 | |||
| 389 | (defun sieve-manage-opened (&optional buffer) | ||
| 390 | "Return non-nil if connection to managesieve server in BUFFER is open. | ||
| 391 | If BUFFER is nil then the current buffer is used." | ||
| 392 | (and (setq buffer (get-buffer (or buffer (current-buffer)))) | ||
| 393 | (buffer-live-p buffer) | ||
| 394 | (with-current-buffer buffer | ||
| 395 | (and sieve-manage-process | ||
| 396 | (memq (process-status sieve-manage-process) '(open run)))))) | ||
| 397 | |||
| 398 | (defun sieve-manage-close (&optional buffer) | ||
| 399 | "Close connection to managesieve server in BUFFER. | ||
| 400 | If BUFFER is nil, the current buffer is used." | ||
| 401 | (with-current-buffer (or buffer (current-buffer)) | ||
| 402 | (when (sieve-manage-opened) | ||
| 403 | (sieve-manage-send "LOGOUT") | ||
| 404 | (sit-for 1)) | ||
| 405 | (when (and sieve-manage-process | ||
| 406 | (memq (process-status sieve-manage-process) '(open run))) | ||
| 407 | (delete-process sieve-manage-process)) | ||
| 408 | (setq sieve-manage-process nil) | ||
| 409 | (sieve-manage-erase) | ||
| 410 | t)) | ||
| 411 | |||
| 412 | (defun sieve-manage-capability (&optional name value buffer) | ||
| 413 | "Check if capability NAME of server BUFFER match VALUE. | ||
| 414 | If it does, return the server value of NAME. If not returns nil. | ||
| 415 | If VALUE is nil, do not check VALUE and return server value. | ||
| 416 | If NAME is nil, return the full server list of capabilities." | ||
| 417 | (with-current-buffer (or buffer (current-buffer)) | ||
| 418 | (if (null name) | ||
| 419 | sieve-manage-capability | ||
| 420 | (let ((server-value (cadr (assoc name sieve-manage-capability)))) | ||
| 421 | (when (or (null value) | ||
| 422 | (and server-value | ||
| 423 | (string-match value server-value))) | ||
| 424 | server-value))))) | ||
| 425 | |||
| 426 | (defun sieve-manage-listscripts (&optional buffer) | ||
| 427 | (with-current-buffer (or buffer (current-buffer)) | ||
| 428 | (sieve-manage-send "LISTSCRIPTS") | ||
| 429 | (sieve-manage-parse-listscripts))) | ||
| 430 | |||
| 431 | (defun sieve-manage-havespace (name size &optional buffer) | ||
| 432 | (with-current-buffer (or buffer (current-buffer)) | ||
| 433 | (sieve-manage-send (format "HAVESPACE \"%s\" %s" name size)) | ||
| 434 | (sieve-manage-parse-okno))) | ||
| 435 | |||
| 436 | (defun sieve-manage-putscript (name content &optional buffer) | ||
| 437 | (with-current-buffer (or buffer (current-buffer)) | ||
| 438 | (sieve-manage-send (format "PUTSCRIPT \"%s\" {%d+}%s%s" name | ||
| 439 | ;; Here we assume that the coding-system will | ||
| 440 | ;; replace each char with a single byte. | ||
| 441 | ;; This is always the case if `content' is | ||
| 442 | ;; a unibyte string. | ||
| 443 | (length content) | ||
| 444 | sieve-manage-client-eol content)) | ||
| 445 | (sieve-manage-parse-okno))) | ||
| 446 | |||
| 447 | (defun sieve-manage-deletescript (name &optional buffer) | ||
| 448 | (with-current-buffer (or buffer (current-buffer)) | ||
| 449 | (sieve-manage-send (format "DELETESCRIPT \"%s\"" name)) | ||
| 450 | (sieve-manage-parse-okno))) | ||
| 451 | |||
| 452 | (defun sieve-manage-getscript (name output-buffer &optional buffer) | ||
| 453 | (with-current-buffer (or buffer (current-buffer)) | ||
| 454 | (sieve-manage-send (format "GETSCRIPT \"%s\"" name)) | ||
| 455 | (let ((script (sieve-manage-parse-string))) | ||
| 456 | (sieve-manage-parse-crlf) | ||
| 457 | (with-current-buffer output-buffer | ||
| 458 | (insert script)) | ||
| 459 | (sieve-manage-parse-okno)))) | ||
| 460 | |||
| 461 | (defun sieve-manage-setactive (name &optional buffer) | ||
| 462 | (with-current-buffer (or buffer (current-buffer)) | ||
| 463 | (sieve-manage-send (format "SETACTIVE \"%s\"" name)) | ||
| 464 | (sieve-manage-parse-okno))) | ||
| 465 | |||
| 466 | ;; Protocol parsing routines | ||
| 467 | |||
| 468 | (defun sieve-manage-wait-for-answer () | ||
| 469 | (let ((pattern "^\\(OK\\|NO\\).*\n") | ||
| 470 | pos) | ||
| 471 | (while (not pos) | ||
| 472 | (setq pos (search-forward-regexp pattern nil t)) | ||
| 473 | (goto-char (point-min)) | ||
| 474 | (sleep-for 0 50)) | ||
| 475 | pos)) | ||
| 476 | |||
| 477 | (defun sieve-manage-drop-next-answer () | ||
| 478 | (sieve-manage-wait-for-answer) | ||
| 479 | (sieve-manage-erase)) | ||
| 480 | |||
| 481 | (defun sieve-manage-ok-p (rsp) | ||
| 482 | (string= (downcase (or (car-safe rsp) "")) "ok")) | ||
| 483 | |||
| 484 | (defun sieve-manage-is-okno () | ||
| 485 | (when (looking-at (concat | ||
| 486 | "^\\(OK\\|NO\\)\\( (\\([^)]+\\))\\)?\\( \\(.*\\)\\)?" | ||
| 487 | sieve-manage-server-eol)) | ||
| 488 | (let ((status (match-string 1)) | ||
| 489 | (resp-code (match-string 3)) | ||
| 490 | (response (match-string 5))) | ||
| 491 | (when response | ||
| 492 | (goto-char (match-beginning 5)) | ||
| 493 | (setq response (sieve-manage-is-string))) | ||
| 494 | (list status resp-code response)))) | ||
| 495 | |||
| 496 | (defun sieve-manage-parse-okno () | ||
| 497 | (let (rsp) | ||
| 498 | (while (null rsp) | ||
| 499 | (accept-process-output (get-buffer-process (current-buffer)) 1) | ||
| 500 | (goto-char (point-min)) | ||
| 501 | (setq rsp (sieve-manage-is-okno))) | ||
| 502 | (sieve-manage-erase) | ||
| 503 | rsp)) | ||
| 504 | |||
| 505 | (defun sieve-manage-parse-capability (str) | ||
| 506 | "Parse managesieve capability string `STR'. | ||
| 507 | Set variable `sieve-manage-capability' to " | ||
| 508 | (let ((capas (delq nil | ||
| 509 | (mapcar #'split-string-and-unquote | ||
| 510 | (split-string str "\n"))))) | ||
| 511 | (when (string= "OK" (caar (last capas))) | ||
| 512 | (setq sieve-manage-state 'nonauth)) | ||
| 513 | capas)) | ||
| 514 | |||
| 515 | (defun sieve-manage-is-string () | ||
| 516 | (cond ((looking-at "\"\\([^\"]+\\)\"") | ||
| 517 | (prog1 | ||
| 518 | (match-string 1) | ||
| 519 | (goto-char (match-end 0)))) | ||
| 520 | ((looking-at (concat "{\\([0-9]+\\+?\\)}" sieve-manage-server-eol)) | ||
| 521 | (let ((pos (match-end 0)) | ||
| 522 | (len (string-to-number (match-string 1)))) | ||
| 523 | (if (< (point-max) (+ pos len)) | ||
| 524 | nil | ||
| 525 | (goto-char (+ pos len)) | ||
| 526 | (buffer-substring pos (+ pos len))))))) | ||
| 527 | |||
| 528 | (defun sieve-manage-parse-string () | ||
| 529 | (let (rsp) | ||
| 530 | (while (null rsp) | ||
| 531 | (accept-process-output (get-buffer-process (current-buffer)) 1) | ||
| 532 | (goto-char (point-min)) | ||
| 533 | (setq rsp (sieve-manage-is-string))) | ||
| 534 | (sieve-manage-erase (point)) | ||
| 535 | rsp)) | ||
| 536 | |||
| 537 | (defun sieve-manage-parse-crlf () | ||
| 538 | (when (looking-at sieve-manage-server-eol) | ||
| 539 | (sieve-manage-erase (match-end 0)))) | ||
| 540 | |||
| 541 | (defun sieve-manage-parse-listscripts () | ||
| 542 | (let (tmp rsp data) | ||
| 543 | (while (null rsp) | ||
| 544 | (while (null (or (setq rsp (sieve-manage-is-okno)) | ||
| 545 | (setq tmp (sieve-manage-is-string)))) | ||
| 546 | (accept-process-output (get-buffer-process (current-buffer)) 1) | ||
| 547 | (goto-char (point-min))) | ||
| 548 | (when tmp | ||
| 549 | (while (not (looking-at (concat "\\( ACTIVE\\)?" | ||
| 550 | sieve-manage-server-eol))) | ||
| 551 | (accept-process-output (get-buffer-process (current-buffer)) 1) | ||
| 552 | (goto-char (point-min))) | ||
| 553 | (if (match-string 1) | ||
| 554 | (push (cons 'active tmp) data) | ||
| 555 | (push tmp data)) | ||
| 556 | (goto-char (match-end 0)) | ||
| 557 | (setq tmp nil))) | ||
| 558 | (sieve-manage-erase) | ||
| 559 | (if (sieve-manage-ok-p rsp) | ||
| 560 | data | ||
| 561 | rsp))) | ||
| 562 | |||
| 563 | (defun sieve-manage-send (cmdstr) | ||
| 564 | (setq cmdstr (concat cmdstr sieve-manage-client-eol)) | ||
| 565 | (and sieve-manage-log | ||
| 566 | (with-current-buffer (get-buffer-create sieve-manage-log) | ||
| 567 | (mm-enable-multibyte) | ||
| 568 | (buffer-disable-undo) | ||
| 569 | (goto-char (point-max)) | ||
| 570 | (insert cmdstr))) | ||
| 571 | (process-send-string sieve-manage-process cmdstr)) | ||
| 572 | |||
| 573 | (provide 'sieve-manage) | ||
| 574 | |||
| 575 | ;; sieve-manage.el ends here | ||
diff --git a/lisp/net/sieve-mode.el b/lisp/net/sieve-mode.el new file mode 100644 index 00000000000..7575ba67c5e --- /dev/null +++ b/lisp/net/sieve-mode.el | |||
| @@ -0,0 +1,221 @@ | |||
| 1 | ;;; sieve-mode.el --- Sieve code editing commands for Emacs | ||
| 2 | |||
| 3 | ;; Copyright (C) 2001-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Simon Josefsson <simon@josefsson.org> | ||
| 6 | |||
| 7 | ;; This file is part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 10 | ;; it under the terms of the GNU General Public License as published by | ||
| 11 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 12 | ;; (at your option) any later version. | ||
| 13 | |||
| 14 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | ;; GNU General Public License for more details. | ||
| 18 | |||
| 19 | ;; You should have received a copy of the GNU General Public License | ||
| 20 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 21 | |||
| 22 | ;;; Commentary: | ||
| 23 | |||
| 24 | ;; This file contain editing mode functions and font-lock support for | ||
| 25 | ;; editing Sieve scripts. It sets up C-mode with support for | ||
| 26 | ;; sieve-style #-comments and a lightly hacked syntax table. It was | ||
| 27 | ;; strongly influenced by awk-mode.el. | ||
| 28 | ;; | ||
| 29 | ;; Put something similar to the following in your .emacs to use this file: | ||
| 30 | ;; | ||
| 31 | ;; (load "~/lisp/sieve") | ||
| 32 | ;; (setq auto-mode-alist (cons '("\\.siv\\'" . sieve-mode) auto-mode-alist)) | ||
| 33 | ;; | ||
| 34 | ;; References: | ||
| 35 | ;; | ||
| 36 | ;; RFC 3028, | ||
| 37 | ;; "Sieve: A Mail Filtering Language", | ||
| 38 | ;; by Tim Showalter. | ||
| 39 | ;; | ||
| 40 | ;; Release history: | ||
| 41 | ;; | ||
| 42 | ;; 2001-03-02 version 1.0 posted to gnu.emacs.sources | ||
| 43 | ;; version 1.1 change file extension into ".siv" (official one) | ||
| 44 | ;; added keymap and menubar to hook into sieve-manage | ||
| 45 | ;; 2001-10-31 version 1.2 committed to Oort Gnus | ||
| 46 | |||
| 47 | ;;; Code: | ||
| 48 | |||
| 49 | (autoload 'sieve-manage "sieve") | ||
| 50 | (autoload 'sieve-upload "sieve") | ||
| 51 | (eval-when-compile | ||
| 52 | (require 'font-lock)) | ||
| 53 | |||
| 54 | (defgroup sieve nil | ||
| 55 | "Sieve." | ||
| 56 | :group 'languages) | ||
| 57 | |||
| 58 | (defcustom sieve-mode-hook nil | ||
| 59 | "Hook run in sieve mode buffers." | ||
| 60 | :group 'sieve | ||
| 61 | :type 'hook) | ||
| 62 | |||
| 63 | ;; Font-lock | ||
| 64 | |||
| 65 | (defvar sieve-control-commands-face 'sieve-control-commands | ||
| 66 | "Face name used for Sieve Control Commands.") | ||
| 67 | |||
| 68 | (defface sieve-control-commands | ||
| 69 | '((((type tty) (class color)) (:foreground "blue" :weight light)) | ||
| 70 | (((class grayscale) (background light)) (:foreground "LightGray" :bold t)) | ||
| 71 | (((class grayscale) (background dark)) (:foreground "DimGray" :bold t)) | ||
| 72 | (((class color) (background light)) (:foreground "Orchid")) | ||
| 73 | (((class color) (background dark)) (:foreground "LightSteelBlue")) | ||
| 74 | (t (:bold t))) | ||
| 75 | "Face used for Sieve Control Commands." | ||
| 76 | :group 'sieve) | ||
| 77 | ;; backward-compatibility alias | ||
| 78 | (put 'sieve-control-commands-face 'face-alias 'sieve-control-commands) | ||
| 79 | (put 'sieve-control-commands-face 'obsolete-face "22.1") | ||
| 80 | |||
| 81 | (defvar sieve-action-commands-face 'sieve-action-commands | ||
| 82 | "Face name used for Sieve Action Commands.") | ||
| 83 | |||
| 84 | (defface sieve-action-commands | ||
| 85 | '((((type tty) (class color)) (:foreground "blue" :weight bold)) | ||
| 86 | (((class color) (background light)) (:foreground "Blue")) | ||
| 87 | (((class color) (background dark)) (:foreground "LightSkyBlue")) | ||
| 88 | (t (:inverse-video t :bold t))) | ||
| 89 | "Face used for Sieve Action Commands." | ||
| 90 | :group 'sieve) | ||
| 91 | ;; backward-compatibility alias | ||
| 92 | (put 'sieve-action-commands-face 'face-alias 'sieve-action-commands) | ||
| 93 | (put 'sieve-action-commands-face 'obsolete-face "22.1") | ||
| 94 | |||
| 95 | (defvar sieve-test-commands-face 'sieve-test-commands | ||
| 96 | "Face name used for Sieve Test Commands.") | ||
| 97 | |||
| 98 | (defface sieve-test-commands | ||
| 99 | '((((type tty) (class color)) (:foreground "magenta")) | ||
| 100 | (((class grayscale) (background light)) | ||
| 101 | (:foreground "LightGray" :bold t :underline t)) | ||
| 102 | (((class grayscale) (background dark)) | ||
| 103 | (:foreground "Gray50" :bold t :underline t)) | ||
| 104 | (((class color) (background light)) (:foreground "CadetBlue")) | ||
| 105 | (((class color) (background dark)) (:foreground "Aquamarine")) | ||
| 106 | (t (:bold t :underline t))) | ||
| 107 | "Face used for Sieve Test Commands." | ||
| 108 | :group 'sieve) | ||
| 109 | ;; backward-compatibility alias | ||
| 110 | (put 'sieve-test-commands-face 'face-alias 'sieve-test-commands) | ||
| 111 | (put 'sieve-test-commands-face 'obsolete-face "22.1") | ||
| 112 | |||
| 113 | (defvar sieve-tagged-arguments-face 'sieve-tagged-arguments | ||
| 114 | "Face name used for Sieve Tagged Arguments.") | ||
| 115 | |||
| 116 | (defface sieve-tagged-arguments | ||
| 117 | '((((type tty) (class color)) (:foreground "cyan" :weight bold)) | ||
| 118 | (((class grayscale) (background light)) (:foreground "LightGray" :bold t)) | ||
| 119 | (((class grayscale) (background dark)) (:foreground "DimGray" :bold t)) | ||
| 120 | (((class color) (background light)) (:foreground "Purple")) | ||
| 121 | (((class color) (background dark)) (:foreground "Cyan")) | ||
| 122 | (t (:bold t))) | ||
| 123 | "Face used for Sieve Tagged Arguments." | ||
| 124 | :group 'sieve) | ||
| 125 | ;; backward-compatibility alias | ||
| 126 | (put 'sieve-tagged-arguments-face 'face-alias 'sieve-tagged-arguments) | ||
| 127 | (put 'sieve-tagged-arguments-face 'obsolete-face "22.1") | ||
| 128 | |||
| 129 | |||
| 130 | (defconst sieve-font-lock-keywords | ||
| 131 | (eval-when-compile | ||
| 132 | (list | ||
| 133 | ;; control commands | ||
| 134 | (cons (regexp-opt '("require" "if" "else" "elsif" "stop") | ||
| 135 | 'words) | ||
| 136 | 'sieve-control-commands-face) | ||
| 137 | ;; action commands | ||
| 138 | (cons (regexp-opt '("fileinto" "redirect" "reject" "keep" "discard") | ||
| 139 | 'words) | ||
| 140 | 'sieve-action-commands-face) | ||
| 141 | ;; test commands | ||
| 142 | (cons (regexp-opt '("address" "allof" "anyof" "exists" "false" | ||
| 143 | "true" "header" "not" "size" "envelope" | ||
| 144 | "body") | ||
| 145 | 'words) | ||
| 146 | 'sieve-test-commands-face) | ||
| 147 | (cons "\\Sw+:\\sw+" | ||
| 148 | 'sieve-tagged-arguments-face)))) | ||
| 149 | |||
| 150 | ;; Syntax table | ||
| 151 | |||
| 152 | (defvar sieve-mode-syntax-table nil | ||
| 153 | "Syntax table in use in sieve-mode buffers.") | ||
| 154 | |||
| 155 | (if sieve-mode-syntax-table | ||
| 156 | () | ||
| 157 | (setq sieve-mode-syntax-table (make-syntax-table)) | ||
| 158 | (modify-syntax-entry ?\\ "\\" sieve-mode-syntax-table) | ||
| 159 | (modify-syntax-entry ?\n "> " sieve-mode-syntax-table) | ||
| 160 | (modify-syntax-entry ?\f "> " sieve-mode-syntax-table) | ||
| 161 | (modify-syntax-entry ?\# "< " sieve-mode-syntax-table) | ||
| 162 | (modify-syntax-entry ?/ "." sieve-mode-syntax-table) | ||
| 163 | (modify-syntax-entry ?* "." sieve-mode-syntax-table) | ||
| 164 | (modify-syntax-entry ?+ "." sieve-mode-syntax-table) | ||
| 165 | (modify-syntax-entry ?- "." sieve-mode-syntax-table) | ||
| 166 | (modify-syntax-entry ?= "." sieve-mode-syntax-table) | ||
| 167 | (modify-syntax-entry ?% "." sieve-mode-syntax-table) | ||
| 168 | (modify-syntax-entry ?< "." sieve-mode-syntax-table) | ||
| 169 | (modify-syntax-entry ?> "." sieve-mode-syntax-table) | ||
| 170 | (modify-syntax-entry ?& "." sieve-mode-syntax-table) | ||
| 171 | (modify-syntax-entry ?| "." sieve-mode-syntax-table) | ||
| 172 | (modify-syntax-entry ?_ "_" sieve-mode-syntax-table) | ||
| 173 | (modify-syntax-entry ?\' "\"" sieve-mode-syntax-table)) | ||
| 174 | |||
| 175 | ;; Key map definition | ||
| 176 | |||
| 177 | (defvar sieve-mode-map | ||
| 178 | (let ((map (make-sparse-keymap))) | ||
| 179 | (define-key map "\C-c\C-l" 'sieve-upload) | ||
| 180 | (define-key map "\C-c\C-c" 'sieve-upload-and-kill) | ||
| 181 | (define-key map "\C-c\C-m" 'sieve-manage) | ||
| 182 | map) | ||
| 183 | "Key map used in sieve mode.") | ||
| 184 | |||
| 185 | ;; Menu definition | ||
| 186 | |||
| 187 | (defvar sieve-mode-menu nil | ||
| 188 | "Menubar used in sieve mode.") | ||
| 189 | |||
| 190 | ;; Code for Sieve editing mode. | ||
| 191 | (autoload 'easy-menu-add-item "easymenu") | ||
| 192 | |||
| 193 | ;;;###autoload | ||
| 194 | (define-derived-mode sieve-mode c-mode "Sieve" | ||
| 195 | "Major mode for editing Sieve code. | ||
| 196 | This is much like C mode except for the syntax of comments. Its keymap | ||
| 197 | inherits from C mode's and it has the same variables for customizing | ||
| 198 | indentation. It has its own abbrev table and its own syntax table. | ||
| 199 | |||
| 200 | Turning on Sieve mode runs `sieve-mode-hook'." | ||
| 201 | (set (make-local-variable 'paragraph-start) (concat "$\\|" page-delimiter)) | ||
| 202 | (set (make-local-variable 'paragraph-separate) paragraph-start) | ||
| 203 | (set (make-local-variable 'comment-start) "#") | ||
| 204 | (set (make-local-variable 'comment-end) "") | ||
| 205 | ;;(set (make-local-variable 'comment-start-skip) "\\(^\\|\\s-\\);?#+ *") | ||
| 206 | (set (make-local-variable 'comment-start-skip) "#+ *") | ||
| 207 | (set (make-local-variable 'font-lock-defaults) | ||
| 208 | '(sieve-font-lock-keywords nil nil ((?_ . "w")))) | ||
| 209 | (easy-menu-add-item nil nil sieve-mode-menu)) | ||
| 210 | |||
| 211 | ;; Menu | ||
| 212 | |||
| 213 | (easy-menu-define sieve-mode-menu sieve-mode-map | ||
| 214 | "Sieve Menu." | ||
| 215 | '("Sieve" | ||
| 216 | ["Upload script" sieve-upload t] | ||
| 217 | ["Manage scripts on server" sieve-manage t])) | ||
| 218 | |||
| 219 | (provide 'sieve-mode) | ||
| 220 | |||
| 221 | ;; sieve-mode.el ends here | ||
diff --git a/lisp/net/sieve.el b/lisp/net/sieve.el new file mode 100644 index 00000000000..2046e53697d --- /dev/null +++ b/lisp/net/sieve.el | |||
| @@ -0,0 +1,372 @@ | |||
| 1 | ;;; sieve.el --- Utilities to manage sieve scripts | ||
| 2 | |||
| 3 | ;; Copyright (C) 2001-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Simon Josefsson <simon@josefsson.org> | ||
| 6 | |||
| 7 | ;; This file is part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 10 | ;; it under the terms of the GNU General Public License as published by | ||
| 11 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 12 | ;; (at your option) any later version. | ||
| 13 | |||
| 14 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | ;; GNU General Public License for more details. | ||
| 18 | |||
| 19 | ;; You should have received a copy of the GNU General Public License | ||
| 20 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 21 | |||
| 22 | ;;; Commentary: | ||
| 23 | |||
| 24 | ;; This file contain utilities to facilitate upload, download and | ||
| 25 | ;; general management of sieve scripts. Currently only the | ||
| 26 | ;; Managesieve protocol is supported (using sieve-manage.el), but when | ||
| 27 | ;; (useful) alternatives become available, they might be supported as | ||
| 28 | ;; well. | ||
| 29 | ;; | ||
| 30 | ;; The cursor navigation was inspired by biff-mode by Franklin Lee. | ||
| 31 | ;; | ||
| 32 | ;; Release history: | ||
| 33 | ;; | ||
| 34 | ;; 2001-10-31 Committed to Oort Gnus. | ||
| 35 | ;; 2002-07-27 Fix down-mouse-2 and down-mouse-3 in manage-mode. Fix menubar | ||
| 36 | ;; in manage-mode. Change some messages. Added sieve-deactivate*, | ||
| 37 | ;; sieve-remove. Fixed help text in manage-mode. Suggested by | ||
| 38 | ;; Ned Ludd. | ||
| 39 | ;; | ||
| 40 | ;; Todo: | ||
| 41 | ;; | ||
| 42 | ;; * Namespace? This file contains `sieve-manage' and | ||
| 43 | ;; `sieve-manage-mode', but there is a sieve-manage.el file as well. | ||
| 44 | ;; Can't think of a good solution though, this file need a *-mode, | ||
| 45 | ;; and naming it `sieve-mode' would collide with sieve-mode.el. One | ||
| 46 | ;; solution would be to come up with some better name that this file | ||
| 47 | ;; can use that doesn't have the managesieve specific "manage" in | ||
| 48 | ;; it. sieve-dired? i dunno. we could copy all off sieve.el into | ||
| 49 | ;; sieve-manage.el too, but I'd like to separate the interface from | ||
| 50 | ;; the protocol implementation since the backends are likely to | ||
| 51 | ;; change (well). | ||
| 52 | ;; | ||
| 53 | ;; * Define servers? We could have a customize buffer to create a server, | ||
| 54 | ;; with authentication/stream/etc parameters, much like Gnus, and then | ||
| 55 | ;; only use names of defined servers when interacting with M-x sieve-*. | ||
| 56 | ;; Right now you can't use STARTTLS, which sieve-manage.el provides | ||
| 57 | |||
| 58 | ;;; Code: | ||
| 59 | |||
| 60 | (require 'sieve-manage) | ||
| 61 | (require 'sieve-mode) | ||
| 62 | |||
| 63 | ;; User customizable variables: | ||
| 64 | |||
| 65 | (defgroup sieve nil | ||
| 66 | "Manage sieve scripts." | ||
| 67 | :version "22.1" | ||
| 68 | :group 'tools) | ||
| 69 | |||
| 70 | (defcustom sieve-new-script "<new script>" | ||
| 71 | "Name of name script indicator." | ||
| 72 | :type 'string | ||
| 73 | :group 'sieve) | ||
| 74 | |||
| 75 | (defcustom sieve-buffer "*sieve*" | ||
| 76 | "Name of sieve management buffer." | ||
| 77 | :type 'string | ||
| 78 | :group 'sieve) | ||
| 79 | |||
| 80 | (defcustom sieve-template "\ | ||
| 81 | require \"fileinto\"; | ||
| 82 | |||
| 83 | # Example script (remove comment character '#' to make it effective!): | ||
| 84 | # | ||
| 85 | # if header :contains \"from\" \"coyote\" { | ||
| 86 | # discard; | ||
| 87 | # } elsif header :contains [\"subject\"] [\"$$$\"] { | ||
| 88 | # discard; | ||
| 89 | # } else { | ||
| 90 | # fileinto \"INBOX\"; | ||
| 91 | # } | ||
| 92 | " | ||
| 93 | "Template sieve script." | ||
| 94 | :type 'string | ||
| 95 | :group 'sieve) | ||
| 96 | |||
| 97 | ;; Internal variables: | ||
| 98 | |||
| 99 | (defvar sieve-manage-buffer nil) | ||
| 100 | (defvar sieve-buffer-header-end nil) | ||
| 101 | (defvar sieve-buffer-script-name nil | ||
| 102 | "The real script name of the buffer.") | ||
| 103 | (make-local-variable 'sieve-buffer-script-name) | ||
| 104 | |||
| 105 | ;; Sieve-manage mode: | ||
| 106 | |||
| 107 | (defvar sieve-manage-mode-map | ||
| 108 | (let ((map (make-sparse-keymap))) | ||
| 109 | ;; various | ||
| 110 | (define-key map "?" 'sieve-help) | ||
| 111 | (define-key map "h" 'sieve-help) | ||
| 112 | ;; activating | ||
| 113 | (define-key map "m" 'sieve-activate) | ||
| 114 | (define-key map "u" 'sieve-deactivate) | ||
| 115 | (define-key map "\M-\C-?" 'sieve-deactivate-all) | ||
| 116 | ;; navigation keys | ||
| 117 | (define-key map "\C-p" 'sieve-prev-line) | ||
| 118 | (define-key map [up] 'sieve-prev-line) | ||
| 119 | (define-key map "\C-n" 'sieve-next-line) | ||
| 120 | (define-key map [down] 'sieve-next-line) | ||
| 121 | (define-key map " " 'sieve-next-line) | ||
| 122 | (define-key map "n" 'sieve-next-line) | ||
| 123 | (define-key map "p" 'sieve-prev-line) | ||
| 124 | (define-key map "\C-m" 'sieve-edit-script) | ||
| 125 | (define-key map "f" 'sieve-edit-script) | ||
| 126 | (define-key map "o" 'sieve-edit-script-other-window) | ||
| 127 | (define-key map "r" 'sieve-remove) | ||
| 128 | (define-key map "q" 'sieve-bury-buffer) | ||
| 129 | (define-key map "Q" 'sieve-manage-quit) | ||
| 130 | (define-key map [(down-mouse-2)] 'sieve-edit-script) | ||
| 131 | (define-key map [(down-mouse-3)] 'sieve-manage-mode-menu) | ||
| 132 | map) | ||
| 133 | "Keymap for `sieve-manage-mode'.") | ||
| 134 | |||
| 135 | (easy-menu-define sieve-manage-mode-menu sieve-manage-mode-map | ||
| 136 | "Sieve Menu." | ||
| 137 | '("Manage Sieve" | ||
| 138 | ["Edit script" sieve-edit-script t] | ||
| 139 | ["Activate script" sieve-activate t] | ||
| 140 | ["Deactivate script" sieve-deactivate t])) | ||
| 141 | |||
| 142 | (define-derived-mode sieve-manage-mode fundamental-mode "Sieve-manage" | ||
| 143 | "Mode used for sieve script management." | ||
| 144 | (buffer-disable-undo (current-buffer)) | ||
| 145 | (setq truncate-lines t) | ||
| 146 | (easy-menu-add sieve-manage-mode-menu sieve-manage-mode-map)) | ||
| 147 | |||
| 148 | (put 'sieve-manage-mode 'mode-class 'special) | ||
| 149 | |||
| 150 | ;; Commands used in sieve-manage mode: | ||
| 151 | |||
| 152 | (defun sieve-manage-quit () | ||
| 153 | "Quit Manage Sieve and close the connection." | ||
| 154 | (interactive) | ||
| 155 | (sieve-manage-close sieve-manage-buffer) | ||
| 156 | (kill-buffer sieve-manage-buffer) | ||
| 157 | (kill-buffer (current-buffer))) | ||
| 158 | |||
| 159 | (defun sieve-bury-buffer () | ||
| 160 | "Bury the Manage Sieve buffer without closing the connection." | ||
| 161 | (interactive) | ||
| 162 | (bury-buffer)) | ||
| 163 | |||
| 164 | (defun sieve-activate (&optional pos) | ||
| 165 | (interactive "d") | ||
| 166 | (let ((name (sieve-script-at-point)) err) | ||
| 167 | (when (or (null name) (string-equal name sieve-new-script)) | ||
| 168 | (error "No sieve script at point")) | ||
| 169 | (message "Activating script %s..." name) | ||
| 170 | (setq err (sieve-manage-setactive name sieve-manage-buffer)) | ||
| 171 | (sieve-refresh-scriptlist) | ||
| 172 | (if (sieve-manage-ok-p err) | ||
| 173 | (message "Activating script %s...done" name) | ||
| 174 | (message "Activating script %s...failed: %s" name (nth 2 err))))) | ||
| 175 | |||
| 176 | (defun sieve-deactivate-all (&optional pos) | ||
| 177 | (interactive "d") | ||
| 178 | (let ((name (sieve-script-at-point)) err) | ||
| 179 | (message "Deactivating scripts...") | ||
| 180 | (setq err (sieve-manage-setactive "" sieve-manage-buffer)) | ||
| 181 | (sieve-refresh-scriptlist) | ||
| 182 | (if (sieve-manage-ok-p err) | ||
| 183 | (message "Deactivating scripts...done") | ||
| 184 | (message "Deactivating scripts...failed: %s" (nth 2 err))))) | ||
| 185 | |||
| 186 | (defalias 'sieve-deactivate 'sieve-deactivate-all) | ||
| 187 | |||
| 188 | (defun sieve-remove (&optional pos) | ||
| 189 | (interactive "d") | ||
| 190 | (let ((name (sieve-script-at-point)) err) | ||
| 191 | (when (or (null name) (string-equal name sieve-new-script)) | ||
| 192 | (error "No sieve script at point")) | ||
| 193 | (message "Removing sieve script %s..." name) | ||
| 194 | (setq err (sieve-manage-deletescript name sieve-manage-buffer)) | ||
| 195 | (unless (sieve-manage-ok-p err) | ||
| 196 | (error "Removing sieve script %s...failed: " err)) | ||
| 197 | (sieve-refresh-scriptlist) | ||
| 198 | (message "Removing sieve script %s...done" name))) | ||
| 199 | |||
| 200 | (defun sieve-edit-script (&optional pos) | ||
| 201 | (interactive "d") | ||
| 202 | (let ((name (sieve-script-at-point))) | ||
| 203 | (unless name | ||
| 204 | (error "No sieve script at point")) | ||
| 205 | (if (not (string-equal name sieve-new-script)) | ||
| 206 | (let ((newbuf (generate-new-buffer name)) | ||
| 207 | err) | ||
| 208 | (setq err (sieve-manage-getscript name newbuf sieve-manage-buffer)) | ||
| 209 | (switch-to-buffer newbuf) | ||
| 210 | (unless (sieve-manage-ok-p err) | ||
| 211 | (error "Sieve download failed: %s" err))) | ||
| 212 | (switch-to-buffer (get-buffer-create "template.siv")) | ||
| 213 | (insert sieve-template)) | ||
| 214 | (sieve-mode) | ||
| 215 | (setq sieve-buffer-script-name name) | ||
| 216 | (goto-char (point-min)) | ||
| 217 | (message | ||
| 218 | (substitute-command-keys | ||
| 219 | "Press \\[sieve-upload] to upload script to server.")))) | ||
| 220 | |||
| 221 | (defmacro sieve-change-region (&rest body) | ||
| 222 | "Turns off sieve-region before executing BODY, then re-enables it after. | ||
| 223 | Used to bracket operations which move point in the sieve-buffer." | ||
| 224 | `(progn | ||
| 225 | (sieve-highlight nil) | ||
| 226 | ,@body | ||
| 227 | (sieve-highlight t))) | ||
| 228 | (put 'sieve-change-region 'lisp-indent-function 0) | ||
| 229 | |||
| 230 | (defun sieve-next-line (&optional arg) | ||
| 231 | (interactive) | ||
| 232 | (unless arg | ||
| 233 | (setq arg 1)) | ||
| 234 | (if (save-excursion | ||
| 235 | (forward-line arg) | ||
| 236 | (sieve-script-at-point)) | ||
| 237 | (sieve-change-region | ||
| 238 | (forward-line arg)) | ||
| 239 | (message "End of list"))) | ||
| 240 | |||
| 241 | (defun sieve-prev-line (&optional arg) | ||
| 242 | (interactive) | ||
| 243 | (unless arg | ||
| 244 | (setq arg -1)) | ||
| 245 | (if (save-excursion | ||
| 246 | (forward-line arg) | ||
| 247 | (sieve-script-at-point)) | ||
| 248 | (sieve-change-region | ||
| 249 | (forward-line arg)) | ||
| 250 | (message "Beginning of list"))) | ||
| 251 | |||
| 252 | (defun sieve-help () | ||
| 253 | "Display help for various sieve commands." | ||
| 254 | (interactive) | ||
| 255 | (if (eq last-command 'sieve-help) | ||
| 256 | ;; would need minor-mode for log-edit-mode | ||
| 257 | (describe-function 'sieve-mode) | ||
| 258 | (message "%s" (substitute-command-keys | ||
| 259 | "`\\[sieve-edit-script]':edit `\\[sieve-activate]':activate `\\[sieve-deactivate]':deactivate `\\[sieve-remove]':remove")))) | ||
| 260 | |||
| 261 | ;; Create buffer: | ||
| 262 | |||
| 263 | (defun sieve-setup-buffer (server port) | ||
| 264 | (setq buffer-read-only nil) | ||
| 265 | (erase-buffer) | ||
| 266 | (buffer-disable-undo) | ||
| 267 | (let* ((port (or port sieve-manage-default-port)) | ||
| 268 | (header (format "Server : %s:%s\n\n" server port))) | ||
| 269 | (insert header)) | ||
| 270 | (set (make-local-variable 'sieve-buffer-header-end) | ||
| 271 | (point-max))) | ||
| 272 | |||
| 273 | (defun sieve-script-at-point (&optional pos) | ||
| 274 | "Return name of sieve script at point POS, or nil." | ||
| 275 | (interactive "d") | ||
| 276 | (get-char-property (or pos (point)) 'script-name)) | ||
| 277 | |||
| 278 | (defun sieve-highlight (on) | ||
| 279 | "Turn ON or off highlighting on the current language overlay." | ||
| 280 | (overlay-put (car (overlays-at (point))) 'face (if on 'highlight 'default))) | ||
| 281 | |||
| 282 | (defun sieve-insert-scripts (scripts) | ||
| 283 | "Format and insert LANGUAGE-LIST strings into current buffer at point." | ||
| 284 | (while scripts | ||
| 285 | (let ((p (point)) | ||
| 286 | (ext nil) | ||
| 287 | (script (pop scripts))) | ||
| 288 | (if (consp script) | ||
| 289 | (insert (format " ACTIVE %s" (cdr script))) | ||
| 290 | (insert (format " %s" script))) | ||
| 291 | (setq ext (make-overlay p (point))) | ||
| 292 | (overlay-put ext 'mouse-face 'highlight) | ||
| 293 | (overlay-put ext 'script-name (if (consp script) | ||
| 294 | (cdr script) | ||
| 295 | script)) | ||
| 296 | (insert "\n")))) | ||
| 297 | |||
| 298 | (defun sieve-open-server (server &optional port) | ||
| 299 | "Open SERVER (on PORT) and authenticate." | ||
| 300 | (with-current-buffer | ||
| 301 | (or ;; open server | ||
| 302 | (set (make-local-variable 'sieve-manage-buffer) | ||
| 303 | (sieve-manage-open server port)) | ||
| 304 | (error "Error opening server %s" server)) | ||
| 305 | (sieve-manage-authenticate))) | ||
| 306 | |||
| 307 | (defun sieve-refresh-scriptlist () | ||
| 308 | (interactive) | ||
| 309 | (with-current-buffer sieve-buffer | ||
| 310 | (setq buffer-read-only nil) | ||
| 311 | (delete-region (or sieve-buffer-header-end (point-max)) (point-max)) | ||
| 312 | (goto-char (point-max)) | ||
| 313 | ;; get list of script names and print them | ||
| 314 | (let ((scripts (sieve-manage-listscripts sieve-manage-buffer))) | ||
| 315 | (if (null scripts) | ||
| 316 | (insert | ||
| 317 | (substitute-command-keys | ||
| 318 | (format | ||
| 319 | "No scripts on server, press \\[sieve-edit-script] on %s to create a new script.\n" | ||
| 320 | sieve-new-script))) | ||
| 321 | (insert | ||
| 322 | (substitute-command-keys | ||
| 323 | (format (concat "%d script%s on server, press \\[sieve-edit-script] on a script " | ||
| 324 | "name edits it, or\npress \\[sieve-edit-script] on %s to create " | ||
| 325 | "a new script.\n") (length scripts) | ||
| 326 | (if (eq (length scripts) 1) "" "s") | ||
| 327 | sieve-new-script)))) | ||
| 328 | (save-excursion | ||
| 329 | (sieve-insert-scripts (list sieve-new-script)) | ||
| 330 | (sieve-insert-scripts scripts))) | ||
| 331 | (sieve-highlight t) | ||
| 332 | (setq buffer-read-only t))) | ||
| 333 | |||
| 334 | ;;;###autoload | ||
| 335 | (defun sieve-manage (server &optional port) | ||
| 336 | (interactive "sServer: ") | ||
| 337 | (switch-to-buffer (get-buffer-create sieve-buffer)) | ||
| 338 | (sieve-manage-mode) | ||
| 339 | (sieve-setup-buffer server port) | ||
| 340 | (if (sieve-open-server server port) | ||
| 341 | (sieve-refresh-scriptlist) | ||
| 342 | (message "Could not open server %s" server))) | ||
| 343 | |||
| 344 | ;;;###autoload | ||
| 345 | (defun sieve-upload (&optional name) | ||
| 346 | (interactive) | ||
| 347 | (when (or (get-buffer sieve-buffer) (call-interactively 'sieve-manage)) | ||
| 348 | (let ((script (buffer-string)) err) | ||
| 349 | (with-current-buffer (get-buffer sieve-buffer) | ||
| 350 | (setq err (sieve-manage-putscript | ||
| 351 | (or name sieve-buffer-script-name (buffer-name)) | ||
| 352 | script sieve-manage-buffer)) | ||
| 353 | (if (sieve-manage-ok-p err) | ||
| 354 | (message (substitute-command-keys | ||
| 355 | "Sieve upload done. Use \\[sieve-manage] to manage scripts.")) | ||
| 356 | (message "Sieve upload failed: %s" (nth 2 err))))))) | ||
| 357 | |||
| 358 | ;;;###autoload | ||
| 359 | (defun sieve-upload-and-bury (&optional name) | ||
| 360 | (interactive) | ||
| 361 | (sieve-upload name) | ||
| 362 | (bury-buffer)) | ||
| 363 | |||
| 364 | ;;;###autoload | ||
| 365 | (defun sieve-upload-and-kill (&optional name) | ||
| 366 | (interactive) | ||
| 367 | (sieve-upload name) | ||
| 368 | (kill-buffer)) | ||
| 369 | |||
| 370 | (provide 'sieve) | ||
| 371 | |||
| 372 | ;; sieve.el ends here | ||
diff --git a/lisp/net/starttls.el b/lisp/net/starttls.el new file mode 100644 index 00000000000..096ed2adc0d --- /dev/null +++ b/lisp/net/starttls.el | |||
| @@ -0,0 +1,304 @@ | |||
| 1 | ;;; starttls.el --- STARTTLS functions | ||
| 2 | |||
| 3 | ;; Copyright (C) 1999-2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Daiki Ueno <ueno@unixuser.org> | ||
| 6 | ;; Author: Simon Josefsson <simon@josefsson.org> | ||
| 7 | ;; Created: 1999/11/20 | ||
| 8 | ;; Keywords: TLS, SSL, OpenSSL, GnuTLS, mail, news | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 13 | ;; it under the terms of the GNU General Public License as published by | ||
| 14 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 15 | ;; (at your option) any later version. | ||
| 16 | |||
| 17 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 20 | ;; GNU General Public License for more details. | ||
| 21 | |||
| 22 | ;; You should have received a copy of the GNU General Public License | ||
| 23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 24 | |||
| 25 | ;;; Commentary: | ||
| 26 | |||
| 27 | ;; This module defines some utility functions for STARTTLS profiles. | ||
| 28 | |||
| 29 | ;; [RFC 2595] "Using TLS with IMAP, POP3 and ACAP" | ||
| 30 | ;; by Chris Newman <chris.newman@innosoft.com> (1999/06) | ||
| 31 | |||
| 32 | ;; This file now contains a combination of the two previous | ||
| 33 | ;; implementations both called "starttls.el". The first one is Daiki | ||
| 34 | ;; Ueno's starttls.el which uses his own "starttls" command line tool, | ||
| 35 | ;; and the second one is Simon Josefsson's starttls.el which uses | ||
| 36 | ;; "gnutls-cli" from GnuTLS. | ||
| 37 | ;; | ||
| 38 | ;; If "starttls" is available, it is preferred by the code over | ||
| 39 | ;; "gnutls-cli", for backwards compatibility. Use | ||
| 40 | ;; `starttls-use-gnutls' to toggle between implementations if you have | ||
| 41 | ;; both tools installed. It is recommended to use GnuTLS, though, as | ||
| 42 | ;; it performs more verification of the certificates. | ||
| 43 | |||
| 44 | ;; The GnuTLS support requires GnuTLS 0.9.90 (released 2003-10-08) or | ||
| 45 | ;; later, from <http://www.gnu.org/software/gnutls/>, or "starttls" | ||
| 46 | ;; from <ftp://ftp.opaopa.org/pub/elisp/>. | ||
| 47 | |||
| 48 | ;; Usage is similar to `open-network-stream'. For example: | ||
| 49 | ;; | ||
| 50 | ;; (when (setq tmp (starttls-open-stream | ||
| 51 | ;; "test" (current-buffer) "yxa.extundo.com" 25)) | ||
| 52 | ;; (accept-process-output tmp 15) | ||
| 53 | ;; (process-send-string tmp "STARTTLS\n") | ||
| 54 | ;; (accept-process-output tmp 15) | ||
| 55 | ;; (message "STARTTLS output:\n%s" (starttls-negotiate tmp)) | ||
| 56 | ;; (process-send-string tmp "EHLO foo\n")) | ||
| 57 | |||
| 58 | ;; An example run yields the following output: | ||
| 59 | ;; | ||
| 60 | ;; 220 yxa.extundo.com ESMTP Sendmail 8.12.11/8.12.11/Debian-3; Wed, 26 May 2004 19:12:29 +0200; (No UCE/UBE) logging access from: c494102a.s-bi.bostream.se(OK)-c494102a.s-bi.bostream.se [217.215.27.65] | ||
| 61 | ;; 220 2.0.0 Ready to start TLS | ||
| 62 | ;; 250-yxa.extundo.com Hello c494102a.s-bi.bostream.se [217.215.27.65], pleased to meet you | ||
| 63 | ;; 250-ENHANCEDSTATUSCODES | ||
| 64 | ;; 250-PIPELINING | ||
| 65 | ;; 250-EXPN | ||
| 66 | ;; 250-VERB | ||
| 67 | ;; 250-8BITMIME | ||
| 68 | ;; 250-SIZE | ||
| 69 | ;; 250-DSN | ||
| 70 | ;; 250-ETRN | ||
| 71 | ;; 250-AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN | ||
| 72 | ;; 250-DELIVERBY | ||
| 73 | ;; 250 HELP | ||
| 74 | ;; nil | ||
| 75 | ;; | ||
| 76 | ;; With the message buffer containing: | ||
| 77 | ;; | ||
| 78 | ;; STARTTLS output: | ||
| 79 | ;; *** Starting TLS handshake | ||
| 80 | ;; - Server's trusted authorities: | ||
| 81 | ;; [0]: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com | ||
| 82 | ;; - Certificate type: X.509 | ||
| 83 | ;; - Got a certificate list of 2 certificates. | ||
| 84 | ;; | ||
| 85 | ;; - Certificate[0] info: | ||
| 86 | ;; # The hostname in the certificate matches 'yxa.extundo.com'. | ||
| 87 | ;; # valid since: Wed May 26 12:16:00 CEST 2004 | ||
| 88 | ;; # expires at: Wed Jul 26 12:16:00 CEST 2023 | ||
| 89 | ;; # serial number: 04 | ||
| 90 | ;; # fingerprint: 7c 04 4b c1 fa 26 9b 5d 90 22 52 3c 65 3d 85 3a | ||
| 91 | ;; # version: #1 | ||
| 92 | ;; # public key algorithm: RSA | ||
| 93 | ;; # Modulus: 1024 bits | ||
| 94 | ;; # Subject's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=Mail server,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com | ||
| 95 | ;; # Issuer's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com | ||
| 96 | ;; | ||
| 97 | ;; - Certificate[1] info: | ||
| 98 | ;; # valid since: Sun May 23 11:35:00 CEST 2004 | ||
| 99 | ;; # expires at: Sun Jul 23 11:35:00 CEST 2023 | ||
| 100 | ;; # serial number: 00 | ||
| 101 | ;; # fingerprint: fc 76 d8 63 1a c9 0b 3b fa 40 fe ed 47 7a 58 ae | ||
| 102 | ;; # version: #3 | ||
| 103 | ;; # public key algorithm: RSA | ||
| 104 | ;; # Modulus: 1024 bits | ||
| 105 | ;; # Subject's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com | ||
| 106 | ;; # Issuer's DN: C=SE,ST=Stockholm,L=Stockholm,O=YXA,OU=CA,CN=yxa.extundo.com,EMAIL=staff@yxa.extundo.com | ||
| 107 | ;; | ||
| 108 | ;; - Peer's certificate issuer is unknown | ||
| 109 | ;; - Peer's certificate is NOT trusted | ||
| 110 | ;; - Version: TLS 1.0 | ||
| 111 | ;; - Key Exchange: RSA | ||
| 112 | ;; - Cipher: ARCFOUR 128 | ||
| 113 | ;; - MAC: SHA | ||
| 114 | ;; - Compression: NULL | ||
| 115 | |||
| 116 | ;;; Code: | ||
| 117 | |||
| 118 | (defgroup starttls nil | ||
| 119 | "Support for `Transport Layer Security' protocol." | ||
| 120 | :version "21.1" | ||
| 121 | :group 'mail) | ||
| 122 | |||
| 123 | (defcustom starttls-gnutls-program "gnutls-cli" | ||
| 124 | "Name of GnuTLS command line tool. | ||
| 125 | This program is used when GnuTLS is used, i.e. when | ||
| 126 | `starttls-use-gnutls' is non-nil." | ||
| 127 | :version "22.1" | ||
| 128 | :type 'string | ||
| 129 | :group 'starttls) | ||
| 130 | |||
| 131 | (defcustom starttls-program "starttls" | ||
| 132 | "The program to run in a subprocess to open an TLSv1 connection. | ||
| 133 | This program is used when the `starttls' command is used, | ||
| 134 | i.e. when `starttls-use-gnutls' is nil." | ||
| 135 | :type 'string | ||
| 136 | :group 'starttls) | ||
| 137 | |||
| 138 | (defcustom starttls-use-gnutls (not (executable-find starttls-program)) | ||
| 139 | "*Whether to use GnuTLS instead of the `starttls' command." | ||
| 140 | :version "22.1" | ||
| 141 | :type 'boolean | ||
| 142 | :group 'starttls) | ||
| 143 | |||
| 144 | (defcustom starttls-extra-args nil | ||
| 145 | "Extra arguments to `starttls-program'. | ||
| 146 | These apply when the `starttls' command is used, i.e. when | ||
| 147 | `starttls-use-gnutls' is nil." | ||
| 148 | :type '(repeat string) | ||
| 149 | :group 'starttls) | ||
| 150 | |||
| 151 | (defcustom starttls-extra-arguments nil | ||
| 152 | "Extra arguments to `starttls-gnutls-program'. | ||
| 153 | These apply when GnuTLS is used, i.e. when `starttls-use-gnutls' is non-nil. | ||
| 154 | |||
| 155 | For example, non-TLS compliant servers may require | ||
| 156 | '(\"--protocols\" \"ssl3\"). Invoke \"gnutls-cli --help\" to | ||
| 157 | find out which parameters are available." | ||
| 158 | :version "22.1" | ||
| 159 | :type '(repeat string) | ||
| 160 | :group 'starttls) | ||
| 161 | |||
| 162 | (defcustom starttls-process-connection-type nil | ||
| 163 | "*Value for `process-connection-type' to use when starting STARTTLS process." | ||
| 164 | :version "22.1" | ||
| 165 | :type 'boolean | ||
| 166 | :group 'starttls) | ||
| 167 | |||
| 168 | (defcustom starttls-connect "- Simple Client Mode:\n\n" | ||
| 169 | "*Regular expression indicating successful connection. | ||
| 170 | The default is what GnuTLS's \"gnutls-cli\" outputs." | ||
| 171 | ;; GnuTLS cli.c:main() prints this string when it is starting to run | ||
| 172 | ;; in the application read/write phase. If the logic, or the string | ||
| 173 | ;; itself, is modified, this must be updated. | ||
| 174 | :version "22.1" | ||
| 175 | :type 'regexp | ||
| 176 | :group 'starttls) | ||
| 177 | |||
| 178 | (defcustom starttls-failure "\\*\\*\\* Handshake has failed" | ||
| 179 | "*Regular expression indicating failed TLS handshake. | ||
| 180 | The default is what GnuTLS's \"gnutls-cli\" outputs." | ||
| 181 | ;; GnuTLS cli.c:do_handshake() prints this string on failure. If the | ||
| 182 | ;; logic, or the string itself, is modified, this must be updated. | ||
| 183 | :version "22.1" | ||
| 184 | :type 'regexp | ||
| 185 | :group 'starttls) | ||
| 186 | |||
| 187 | (defcustom starttls-success "- Compression: " | ||
| 188 | "*Regular expression indicating completed TLS handshakes. | ||
| 189 | The default is what GnuTLS's \"gnutls-cli\" outputs." | ||
| 190 | ;; GnuTLS cli.c:do_handshake() calls, on success, | ||
| 191 | ;; common.c:print_info(), that unconditionally print this string | ||
| 192 | ;; last. If that logic, or the string itself, is modified, this | ||
| 193 | ;; must be updated. | ||
| 194 | :version "22.1" | ||
| 195 | :type 'regexp | ||
| 196 | :group 'starttls) | ||
| 197 | |||
| 198 | (defun starttls-negotiate-gnutls (process) | ||
| 199 | "Negotiate TLS on PROCESS opened by `open-starttls-stream'. | ||
| 200 | This should typically only be done once. It typically returns a | ||
| 201 | multi-line informational message with information about the | ||
| 202 | handshake, or nil on failure." | ||
| 203 | (let (buffer info old-max done-ok done-bad) | ||
| 204 | (if (null (setq buffer (process-buffer process))) | ||
| 205 | ;; XXX How to remove/extract the TLS negotiation junk? | ||
| 206 | (signal-process (process-id process) 'SIGALRM) | ||
| 207 | (with-current-buffer buffer | ||
| 208 | (save-excursion | ||
| 209 | (setq old-max (goto-char (point-max))) | ||
| 210 | (signal-process (process-id process) 'SIGALRM) | ||
| 211 | (while (and (processp process) | ||
| 212 | (eq (process-status process) 'run) | ||
| 213 | (save-excursion | ||
| 214 | (goto-char old-max) | ||
| 215 | (not (or (setq done-ok (re-search-forward | ||
| 216 | starttls-success nil t)) | ||
| 217 | (setq done-bad (re-search-forward | ||
| 218 | starttls-failure nil t)))))) | ||
| 219 | (accept-process-output process 1 100) | ||
| 220 | (sit-for 0.1)) | ||
| 221 | (setq info (buffer-substring-no-properties old-max (point-max))) | ||
| 222 | (delete-region old-max (point-max)) | ||
| 223 | (if (or (and done-ok (not done-bad)) | ||
| 224 | ;; Prevent mitm that fake success msg after failure msg. | ||
| 225 | (and done-ok done-bad (< done-ok done-bad))) | ||
| 226 | info | ||
| 227 | (message "STARTTLS negotiation failed: %s" info) | ||
| 228 | nil)))))) | ||
| 229 | |||
| 230 | (defun starttls-negotiate (process) | ||
| 231 | (if starttls-use-gnutls | ||
| 232 | (starttls-negotiate-gnutls process) | ||
| 233 | (signal-process (process-id process) 'SIGALRM))) | ||
| 234 | |||
| 235 | (defun starttls-open-stream-gnutls (name buffer host port) | ||
| 236 | (message "Opening STARTTLS connection to `%s:%s'..." host port) | ||
| 237 | (let* (done | ||
| 238 | (old-max (with-current-buffer buffer (point-max))) | ||
| 239 | (process-connection-type starttls-process-connection-type) | ||
| 240 | (process (apply #'start-process name buffer | ||
| 241 | starttls-gnutls-program "-s" host | ||
| 242 | "-p" (if (integerp port) | ||
| 243 | (int-to-string port) | ||
| 244 | port) | ||
| 245 | starttls-extra-arguments))) | ||
| 246 | (set-process-query-on-exit-flag process nil) | ||
| 247 | (while (and (processp process) | ||
| 248 | (eq (process-status process) 'run) | ||
| 249 | (with-current-buffer buffer | ||
| 250 | (goto-char old-max) | ||
| 251 | (not (setq done (re-search-forward | ||
| 252 | starttls-connect nil t))))) | ||
| 253 | (accept-process-output process 0 100) | ||
| 254 | (sit-for 0.1)) | ||
| 255 | (if done | ||
| 256 | (with-current-buffer buffer | ||
| 257 | (delete-region old-max done)) | ||
| 258 | (delete-process process) | ||
| 259 | (setq process nil)) | ||
| 260 | (message "Opening STARTTLS connection to `%s:%s'...%s" | ||
| 261 | host port (if done "done" "failed")) | ||
| 262 | process)) | ||
| 263 | |||
| 264 | ;;;###autoload | ||
| 265 | (defun starttls-open-stream (name buffer host port) | ||
| 266 | "Open a TLS connection for a port to a host. | ||
| 267 | Returns a subprocess object to represent the connection. | ||
| 268 | Input and output work as for subprocesses; `delete-process' closes it. | ||
| 269 | Args are NAME BUFFER HOST PORT. | ||
| 270 | NAME is name for process. It is modified if necessary to make it unique. | ||
| 271 | BUFFER is the buffer (or `buffer-name') to associate with the process. | ||
| 272 | Process output goes at end of that buffer, unless you specify | ||
| 273 | an output stream or filter function to handle the output. | ||
| 274 | BUFFER may be also nil, meaning that this process is not associated | ||
| 275 | with any buffer | ||
| 276 | Third arg is name of the host to connect to, or its IP address. | ||
| 277 | Fourth arg PORT is an integer specifying a port to connect to. | ||
| 278 | If `starttls-use-gnutls' is nil, this may also be a service name, but | ||
| 279 | GnuTLS requires a port number." | ||
| 280 | (if starttls-use-gnutls | ||
| 281 | (starttls-open-stream-gnutls name buffer host port) | ||
| 282 | (message "Opening STARTTLS connection to `%s:%s'" host (format "%s" port)) | ||
| 283 | (let* ((process-connection-type starttls-process-connection-type) | ||
| 284 | (process (apply #'start-process | ||
| 285 | name buffer starttls-program | ||
| 286 | host (format "%s" port) | ||
| 287 | starttls-extra-args))) | ||
| 288 | (set-process-query-on-exit-flag process nil) | ||
| 289 | process))) | ||
| 290 | |||
| 291 | (defun starttls-available-p () | ||
| 292 | "Say whether the STARTTLS programs are available." | ||
| 293 | (and (not (memq system-type '(windows-nt ms-dos))) | ||
| 294 | (executable-find (if starttls-use-gnutls | ||
| 295 | starttls-gnutls-program | ||
| 296 | starttls-program)))) | ||
| 297 | |||
| 298 | (defalias 'starttls-any-program-available 'starttls-available-p) | ||
| 299 | (make-obsolete 'starttls-any-program-available 'starttls-available-p | ||
| 300 | "2011-08-02") | ||
| 301 | |||
| 302 | (provide 'starttls) | ||
| 303 | |||
| 304 | ;;; starttls.el ends here | ||