1 ;;; acap.el --- An ACAP interface.
3 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Copyright (C) 2001 Yuuichi Teranishi <teranisi@gohome.org>
8 ;; This file is not part of GNU Emacs
10 ;; This program 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 2, or (at your option)
15 ;; This program 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.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
27 ;; Some codes are based on imap.el.
42 "Low level ACAP issues."
45 (defcustom acap-default-user (user-login-name)
46 "Default username to use."
50 (defcustom acap-default-port 674
51 "Default port for ACAP."
55 (defcustom acap-stock-passphrase nil
56 "Stock passphrase on memory if t."
61 (defconst acap-server-eol "\r\n"
62 "The EOL string sent from the server.")
64 (defconst acap-client-eol "\r\n"
65 "The EOL string sent from the server.")
67 ;; Internal variables.
68 (defvar acap-state 'closed
70 Valid states are `closed', `initial', `auth'.")
72 (defvar acap-capability nil
73 "Capability for server.")
75 (defvar acap-reached-tag 0
76 "Lower limit on command tags that have been parsed.")
79 "Command tag number.")
82 "Authenticated mechanism name.")
84 (defvar acap-process nil
85 "Process for the buffer.")
87 (defvar acap-server nil
93 (defvar acap-response nil
96 (make-variable-buffer-local 'acap-state)
97 (make-variable-buffer-local 'acap-auth)
98 (make-variable-buffer-local 'acap-capability)
99 (make-variable-buffer-local 'acap-reached-tag)
100 (make-variable-buffer-local 'acap-failed-tag)
101 (make-variable-buffer-local 'acap-tag)
102 (make-variable-buffer-local 'acap-server)
103 (make-variable-buffer-local 'acap-port)
104 (make-variable-buffer-local 'acap-response)
106 (defvar acap-network-stream-alist
107 '((default . open-network-stream-as-binary)))
109 (defun acap-network-stream-open (buffer server port &optional type)
110 (let* ((port (or port acap-default-port))
112 (message "Connecting to %s..." server)
113 (funcall (cdr (assq (or type 'default)
114 acap-network-stream-alist))
115 "ACAP" buffer server port))))
117 (with-current-buffer buffer
118 (while (and (memq (process-status process) '(open run))
119 (goto-char (point-min))
120 (not (setq acap-capability (acap-parse-greeting))))
121 (message "Waiting for response from %s..." server)
122 (accept-process-output process 1))
123 (message "Waiting for response from %s...done" server)
124 (when (memq (process-status process) '(open run))
127 (defvar acap-passphrase nil)
128 (defvar acap-rp-user nil)
129 (defvar acap-rp-server nil)
130 (defvar acap-rp-auth nil)
132 (defvar acap-passphrase-alist nil)
134 (defun acap-read-passphrase (prompt)
135 "Prompt is not used."
138 (setq prompt (format "%s passphrase for %s@%s: "
139 acap-rp-auth acap-rp-user acap-rp-server))
140 (if (functionp 'read-passwd)
142 (if (load "passwd" t)
143 (read-passwd prompt))))))
146 (defvar acap-debug t)
147 (defvar acap-debug-buffer nil)
148 (defun acap-debug (string)
149 "Insert STRING to the debug buffer."
151 (if (or (null acap-debug-buffer)
152 (not (bufferp acap-debug-buffer))
153 (not (buffer-live-p acap-debug-buffer)))
154 (setq acap-debug-buffer (get-buffer-create "*Debug acap*")))
155 (with-current-buffer acap-debug-buffer
156 (goto-char (point-max))
159 ;;; Stock passphrase (Not implemented yet)
160 (defun acap-stock-passphrase (user server auth passphrase)
161 (let ((key (format "%s/%s/%s" user server auth))
163 (when (setq pair (assoc key acap-passphrase-alist))
164 (setq acap-passphrase-alist (delete pair acap-passphrase-alist)))
165 (setq acap-passphrase-alist (cons
166 (cons key passphrase)
167 acap-passphrase-alist))))
169 (defun acap-stocked-passphrase (user server auth)
170 (when acap-stock-passphrase
171 (let ((key (format "%s/%s/%s" user server auth)))
172 (cdr (assoc key acap-passphrase-alist)))))
174 (defun acap-remove-stocked-passphrase (user server auth)
175 (let ((key (format "%s/%s/%s" user server auth)))
176 (setq acap-passphrase-alist
177 (delq (assoc key acap-passphrase-alist)
178 acap-passphrase-alist))))
181 (defun acap-open (server &optional user auth port type)
182 (let* ((user (or user acap-default-user))
183 (buffer (get-buffer-create (concat " *acap on " user " at " server)))
184 process passphrase mechanism tag)
185 (with-current-buffer buffer
187 (delete-process acap-process))
188 (setq process (acap-network-stream-open buffer server port type)
189 acap-process process)
191 (set-buffer-multibyte nil)
192 (buffer-disable-undo)
193 (setq acap-state 'initial)
194 (set-process-filter process 'acap-arrival-filter)
195 (set-process-sentinel process 'acap-sentinel)
196 (while (and (memq (process-status process) '(open run))
197 (not (eq acap-state 'auth)))
204 (cdr (or (assq 'Sasl acap-capability)
205 (assq 'SASL acap-capability))))))
207 (sasl-make-client mechanism user "acap" server))
208 (sasl-read-passphrase 'acap-read-passphrase)
210 (acap-rp-server server)
211 (acap-rp-auth (sasl-mechanism-name mechanism))
212 acap-passphrase step response cont-string)
213 (unless (string= (sasl-mechanism-name mechanism)
215 (setq acap-passphrase (acap-read-passphrase nil)))
216 (setq tag (acap-send-command
219 (format "AUTHENTICATE \"%s\""
220 (sasl-mechanism-name mechanism))
222 (sasl-next-step sclient nil))
223 (sasl-step-data step))
224 (concat " " (prin1-to-string
225 (sasl-step-data step)))))))
226 (when (setq response (acap-wait-for-response process tag))
227 (while (acap-response-cont-p response)
229 step (acap-response-cont-string response))
230 (acap-response-clear process)
231 (if (setq step (sasl-next-step sclient step))
233 (insert (or (sasl-step-data step) ""))
234 (setq response (acap-send-data-wait
235 process (current-buffer) tag)))
236 (setq response nil)))
237 (if (acap-response-ok-p response)
239 (setq acap-state 'auth)
241 (message "Authentication failed.")
245 (message "acap: Connecting to %s...failed" server))
246 (setq acap-server server
250 (defun acap-close (process)
251 (with-current-buffer (process-buffer process)
252 (unless (acap-response-ok-p (acap-send-command-wait process "LOGOUT"))
253 (message "Server %s didn't let me log out" acap-server))
254 (when (memq (process-status process) '(open run))
255 (delete-process process))
261 (defun acap-noop (process)
262 "Execute NOOP command on PROCESS."
263 (acap-send-command-wait process "NOOP"))
265 (defun acap-lang (process lang-list)
266 "Execute LANG command on PROCESS."
267 (acap-send-command-wait process
271 (mapcar 'prin1-to-string lang-list))
274 (defun acap-search (process target &optional modifier criteria)
275 "Execute SEARCH command on PROCESS.
276 TARGET is a string which specifies what is to be searched
277 \(dataset or context name\).
278 MODIFIER is an alist of modifiers. Each element should be a list like
279 \(MODIFIER-NAME DATA1 DATA2...\).
280 CRITERIA is a search criteria string.
281 If CRITERIA is not specified, \"ALL\" is assumed,
282 Modifiers and search criteria are described in section 6.4.1 of RFC2244.
285 \(acap-search process
288 \(RETURN \(\"addressbook.Alias\"
289 \"addressbook.Email\"
290 \"addressbook.List\"\)\)\)
291 \"OR NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\\
292 NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\"\)
294 \(acap-search process
295 \"/addressbook/user/fred/\"
296 '\(\(RETURN \(\"*\"\)\)
297 \"EQUAL \\\"entry\\\" \\\"i\;octed\\\" \\\"A0345\\\"\"\)"
298 (acap-send-command-wait process
299 (concat "SEARCH " (prin1-to-string target)
303 (acap-flatten modifier)
306 (or criteria "ALL"))))
308 (defun acap-freecontext (process name)
309 "Execute FREECONTEXT command on PROCESS."
310 (acap-send-command-wait process
311 (concat "FREECONTEXT " name)))
313 (defun acap-updatecontext (process names)
314 "Execute UPDATECONTEXT command on PROCESS."
315 (acap-send-command-wait process
318 (nconc (list "FREECONTEXT") names)
321 (defun acap-store (process entries)
322 "Execute STORE command on PROCESS.
323 ENTRIES is a store-entry list."
324 (acap-send-command-wait process (concat "STORE " (prin1-to-string entries))))
326 (defun acap-deletedsince (process name time)
327 "Execute DELETEDSINCE command on PROCESS."
328 (acap-send-command-wait process
329 (concat "DELETEDSINCE "
330 (prin1-to-string name)
332 (prin1-to-string (acap-encode-time time)))))
334 (defun acap-setacl (process object identifier rights)
335 "Execute SETACL command on PROCESS."
336 (acap-send-command-wait process
338 (prin1-to-string object)
340 (prin1-to-string identifier)
342 (prin1-to-string rights))))
344 (defun acap-deleteacl (process object &optional identifier)
345 "Execute DELETEACL command on PROCESS."
346 (acap-send-command-wait process
349 (prin1-to-string object)
351 (concat " " (prin1-to-string identifier))))))
353 (defun acap-myrights (process object)
354 "Execute MYRIGHTS command on PROCESS."
355 (acap-send-command-wait process
358 (prin1-to-string object))))
360 (defun acap-listrights (process object identifier)
361 "Execute LISTRIGHTS command on PROCESS."
362 (acap-send-command-wait process
365 (prin1-to-string object)
367 (prin1-to-string identifier))))
369 (defun acap-getquota (process dataset)
370 "Execute GETQUOTA command on PROCESS."
371 (acap-send-command-wait process
374 (prin1-to-string dataset))))
376 ;;; response accessor.
377 (defun acap-response-ok-p (response)
378 (assq 'done-ok response))
380 (defun acap-response-cont-p (response)
381 (assq 'cont response))
383 (defun acap-response-cont-string (response)
384 (cdr (assq 'cont response)))
386 (defun acap-response-body (response)
387 (cdr (or (assq 'done-ok response)
388 (assq 'done-no response)
389 (assq 'done-bad response))))
391 (defun acap-response-entries (response)
393 (dolist (ent response)
394 (if (eq (car ent) 'entry)
395 (setq entries (cons ent entries))))
398 (defun acap-response-entry-entry (entry)
401 (defun acap-response-entry-return-data-list (entry)
404 (defun acap-response-return-data-list-get-value (name return-data-list)
405 (nth 1 (assoc name return-data-list)))
407 (defun acap-response-listrights (response)
408 (cdr (assq 'listrights response)))
410 ;;; Send command, data.
411 (defun acap-response-clear (process)
412 (with-current-buffer (process-buffer process)
413 (setq acap-response nil)))
415 (defun acap-send-command-wait (process command)
416 (acap-wait-for-response process (acap-send-command process command)))
418 (defun acap-send-data-wait (process string tag)
419 (cond ((stringp string)
420 (acap-send-command-1 process string))
422 (with-current-buffer string
423 (acap-response-clear process)
424 (acap-send-command-1 process (format "{%d}" (buffer-size)))
425 (if (acap-response-cont-p (acap-wait-for-response process tag))
426 (with-current-buffer string
427 (acap-response-clear process)
428 (process-send-region process (point-min)
430 (process-send-string process acap-client-eol)))
431 (acap-debug (concat (buffer-string) acap-client-eol)))))
432 (acap-wait-for-response process tag))
434 (defun acap-send-command-1 (process cmdstr)
435 (acap-debug (concat "<-" cmdstr acap-client-eol))
436 (process-send-string process (concat cmdstr acap-client-eol)))
438 (defun acap-send-command (process command)
439 (with-current-buffer (process-buffer process)
440 (setq acap-response nil)
441 (if (not (listp command)) (setq command (list command)))
442 (let ((tag (setq acap-tag (1+ acap-tag)))
444 (setq cmdstr (concat (number-to-string acap-tag) " "))
445 (while (setq cmd (pop command))
447 (setq cmdstr (concat cmdstr cmd)))
449 (with-current-buffer cmd
450 (setq cmdstr (concat cmdstr (format "{%d}" (buffer-size)))))
453 (acap-send-command-1 process cmdstr)
455 response (acap-wait-for-response process tag))
456 (if (not (acap-response-cont-p response))
457 (setq command nil) ;; abort command if no cont-req
458 (with-current-buffer cmd
459 (process-send-region process (point-min)
461 (process-send-string process acap-client-eol))))))
462 (t (error "Unknown command type"))))
464 (acap-send-command-1 process cmdstr))
467 (defun acap-wait-for-response (process tag)
468 (with-current-buffer (process-buffer process)
469 (while (and (not (acap-response-cont-p acap-response))
470 (< acap-reached-tag tag))
471 (or (and (not (memq (process-status process) '(open run)))
473 (let ((len (/ (point-max) 1024))
476 (message "acap read: %dk" len))
477 (accept-process-output process 1))))
481 ;;; Sentinel, Filter.
482 (defun acap-sentinel (process string)
483 (delete-process process))
485 (defun acap-find-next-line ()
486 (when (re-search-forward (concat acap-server-eol "\\|{\\([0-9+]+\\)}"
490 (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
492 (goto-char (+ (point) (string-to-number (match-string 1))))
493 (acap-find-next-line))
496 (defun acap-arrival-filter (proc string)
497 "ACAP process filter."
499 (with-current-buffer (process-buffer proc)
500 (goto-char (point-max))
503 (goto-char (point-min))
504 (while (setq end (acap-find-next-line))
506 (narrow-to-region (point-min) end)
507 (delete-backward-char (length acap-server-eol))
508 (goto-char (point-min))
510 (cond ((or (eq acap-state 'auth)
511 (eq acap-state 'initial)
512 (eq acap-state 'nonauth))
513 (acap-parse-response))
515 (message "Unknown state %s in arrival filter"
517 (delete-region (point-min) (point-max))))))))
520 (defsubst acap-forward ()
521 (or (eobp) (forward-char)))
523 (defsubst acap-parse-number ()
524 (when (looking-at "[0-9]+")
526 (string-to-number (match-string 0))
527 (goto-char (match-end 0)))))
529 (defsubst acap-parse-literal ()
530 (when (looking-at "{\\([0-9]+\\)}\r\n")
531 (let ((pos (match-end 0))
532 (len (string-to-number (match-string 1))))
533 (if (< (point-max) (+ pos len))
535 (goto-char (+ pos len))
536 (buffer-substring pos (+ pos len))))))
538 (defun acap-parse-greeting ()
539 (when (looking-at "* ACAP")
540 (goto-char (match-end 0))
543 (while (eq (char-after (point)) ?\()
544 (push (read (current-buffer)) capabilities)
546 (nreverse capabilities))))
548 ;; resp-body = ["(" resp-code ")" SP] quoted
549 (defun acap-parse-resp-body ()
550 (let ((body (read (current-buffer))))
551 (if (listp body) ; resp-code
552 (list body (read (current-buffer)))
553 (list nil body) ; no resp-code.
556 ;; string = quoted / literal
558 ;; quoted = DQUOTE *QUOTED-CHAR DQUOTE
560 ;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
561 ;; "\" quoted-specials
563 ;; quoted-specials = DQUOTE / "\"
565 ;; TEXT-CHAR = <any CHAR except CR and LF>
567 (defsubst acap-parse-string ()
568 (cond ((eq (char-after) ?\")
570 (let ((p (point)) (name ""))
571 (skip-chars-forward "^\"\\\\")
572 (setq name (buffer-substring p (point)))
573 (while (eq (char-after) ?\\)
574 (setq p (1+ (point)))
576 (skip-chars-forward "^\"\\\\")
577 (setq name (concat name (buffer-substring p (point)))))
580 ((eq (char-after) ?{)
581 (acap-parse-literal))))
585 (defsubst acap-parse-nil ()
586 (if (looking-at "NIL")
587 (goto-char (match-end 0))))
589 ;; entry = entry-name / entry-path
590 ;; entry-name = string-utf8
591 ;; ;; entry name MUST NOT contain slash
592 ;; ;; MUST NOT begin with "."
593 ;; entry-path = string-utf8
594 ;; ;; slash-separated path to entry
595 ;; ;; begins with slash
597 (defsubst acap-parse-quoted ()
598 (if (eq (char-after) ?\")
599 (read (current-buffer))))
601 (defun acap-parse-entry ()
605 (defun acap-parse-value ()
608 ;; value-list = "(" [value *(SP value)] ")"
609 (defun acap-parse-value-list ()
611 (when (eq (char-after (point)) ?\()
613 (while (not (eq (char-after (point)) ?\)))
615 (push (acap-parse-value) values))
620 ;; return-data-list = return-data *(SP return-data)
622 ;; return-data = return-metadata / return-metalist /
625 (defun acap-parse-return-data-list ()
627 (setq rlist (list (acap-parse-return-metadata-or-return-metalist)))
629 (while (setq r (acap-parse-return-metadata-or-return-metalist))
630 (setq rlist (nconc rlist (list r)))
634 (defun acap-parse-return-metadata-or-return-metalist ()
637 (acap-parse-value-or-return-metalist)))
639 (defun acap-parse-value-or-return-metalist ()
640 (when (eq (char-after (point)) ?\()
642 (while (not (eq (char-after (point)) ?\)))
644 (push (or (acap-parse-value)
645 (acap-parse-return-metalist))
650 ;; return-metalist = "(" return-metadata *(SP return-metadata) ")"
651 ;; ;; occurs when multiple metadata items requested
653 (defun acap-parse-return-metalist ()
654 (when (eq (char-after (point)) ?\()
656 (while (not (eq (char-after (point)) ?\)))
658 (push (acap-parse-return-metadata) metadatas))
660 (nreverse metadatas))))
662 ;; return-metadata = nil / string / value-list / acl
663 (defun acap-parse-return-metadata ()
666 (acap-parse-value-list)
667 ;; (acap-parse-acl) acl is same as value-list.
670 ;; return-attr-list = "(" return-metalist *(SP return-metalist) ")"
671 ;; ;; occurs when "*" in RETURN pattern on SEARCH
672 (defun acap-parse-return-attr-list ()
673 (when (eq (char-after (point)) ?\()
675 (while (not (eq (char-after (point)) ?\)))
677 (push (acap-parse-return-metalist) metalists))
679 (nreverse metalists))))
681 (defun acap-parse-time ()
684 ;; quoted *(SP quoted)
685 (defun acap-parse-quoted-list ()
687 (setq qlist (list (acap-parse-quoted)))
689 (while (setq q (acap-parse-quoted))
690 (setq qlist (nconc qlist (list q)))
694 (defun acap-parse-any ()
695 (read (current-buffer)))
697 (defun acap-parse-extension-data ()
699 (setq elist (list (acap-parse-any)))
701 (while (setq e (acap-parse-any))
702 (setq elist (nconc elist (list e)))
706 (defun acap-parse-response ()
707 "Parse a ACAP command response."
708 (let ((token (read (current-buffer)))
716 (cons 'cont (acap-parse-string)))
718 ;; untagged response.
719 (case (prog1 (setq token (read (current-buffer)))
722 (list (acap-parse-quoted)
731 (acap-parse-return-data-list)))))
732 (ALERT ;(cons 'alert (acap-parse-resp-body))
733 (message (nth 1 (acap-parse-resp-body))))
734 (BYE ;(cons 'bye (acap-parse-resp-body)))
735 ;;(message (nth 1 (acap-parse-resp-body)))
737 (delete-process acap-process))
738 (CHANGE (cons 'change
739 (list (acap-parse-quoted)
751 (acap-parse-return-data-list)))))
752 (LANG (cons 'lang (list (acap-parse-quoted-list))))
754 (OK (cons 'stat-ok (acap-parse-resp-body)))
755 (NO (cons 'stat-no (acap-parse-resp-body)))
756 (BAD ;(cons 'stat-bad (acap-parse-resp-body))
757 ;; XXX cyrus-sml-acap does not return tagged bad response?
758 (error (nth 1 (acap-parse-resp-body))))))
762 (case (prog1 (setq token (read (current-buffer)))
764 (DELETED (cons 'deleted (acap-parse-quoted)))
766 ((OK Ok ok) (prog1 (cons 'done-ok (acap-parse-resp-body))
767 (setq acap-reached-tag tag)))
768 ((NO No no) (prog1 (cons 'done-no (acap-parse-resp-body))
769 (setq acap-reached-tag tag)))
770 ((BAD Bad bad) (prog1 (cons 'done-bad (acap-parse-resp-body))
771 (setq acap-reached-tag tag)))
775 (progn (acap-forward)
776 (acap-parse-return-data-list)))))
777 (LISTRIGHTS (cons 'listrights
778 (acap-parse-quoted-list)))
779 (MODTIME (cons 'modtime (acap-parse-time)))
780 (MYRIGHTS (cons 'myrights (acap-parse-quoted)))
782 (list (acap-parse-quoted)
789 (acap-parse-extension-data))))
790 (REFER (cons 'refer (list (acap-parse-quoted)
791 (acap-parse-quoted))))
792 (REMOVEFROM (cons 'removefrom
793 (list (acap-parse-quoted)
799 (acap-parse-number)))))
802 (cons 'extend (list token (acap-parse-extension-data))))))
804 (list 'garbage token)))
808 (defun acap-flatten (l)
809 "Flatten list-of-list."
816 (acap-flatten (cdr l)))))
818 (defun acap-flatten-r (l)
819 "Flatten list-of-list recursively."
823 (append (acap-flatten (car l)) (acap-flatten (cdr l))))
826 (defun acap-encode-time (time)
827 (format-time-string "%Y%m%d%H%M%S" (current-time) t)) ; Universal time.
829 (defun acap-decode-time (acap-time)
830 (when (string-match "^\\([0-9][0-9][0-9][0-9]\\)\\([0-1][0-9]\\)\\([0-3][0-9]\\)\\([0-2][0-9]\\)\\([0-5][0-9]\\)\\([0-5][0-9]\\)" acap-time)
831 (encode-time (string-to-number (match-string 6 acap-time))
832 (string-to-number (match-string 5 acap-time))
833 (string-to-number (match-string 4 acap-time))
834 (string-to-number (match-string 3 acap-time))
835 (string-to-number (match-string 2 acap-time))
836 (string-to-number (match-string 1 acap-time))
841 ;;; acap.el ends here