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.
28 ;; acap.el is an elisp library providing an interface for talking to
29 ;; ACAP (RFC2244) servers.
31 ;; This is a transcript of short interactive session for demonstration
34 ;; (setq proc (acap-open "my.acap.server" "username" "CRAM-MD5"))
37 ;; (acap-search proc "/addressbook/" '((RETURN ("*")))))
38 ;; => ((done-ok nil "search completed")
39 ;; (modtime . "20010828091433000010")
43 ;; ("modtime" "20010824004532000003")
44 ;; ("entry" "user"))))
46 ;; ((("modtime" "20010824004532000002")
48 ;; ("dataset.owner" "anonymous")
49 ;; ("dataset.acl" ("$anyone xrwia")))))
56 ;; 27 Aug 2001 Created (Some codes are based on imap.el.).
68 "Low level ACAP issues."
71 (defcustom acap-default-user (user-login-name)
72 "Default username to use."
76 (defcustom acap-default-port 674
77 "Default port for ACAP."
81 (defcustom acap-stock-passphrase nil
82 "Stock passphrase on memory if t."
87 (defconst acap-server-eol "\r\n"
88 "The EOL string sent from the server.")
90 (defconst acap-client-eol "\r\n"
91 "The EOL string sent from the server.")
93 ;; Internal variables.
94 (defvar acap-state 'closed
96 Valid states are `closed', `initial', `auth'.")
98 (defvar acap-capability nil
99 "Capability for server.")
101 (defvar acap-reached-tag 0
102 "Lower limit on command tags that have been parsed.")
105 "Command tag number.")
107 (defvar acap-auth nil
108 "Authenticated mechanism name.")
110 (defvar acap-process nil
111 "Process for the buffer.")
113 (defvar acap-server nil
116 (defvar acap-port nil
119 (defvar acap-response nil
122 (make-variable-buffer-local 'acap-state)
123 (make-variable-buffer-local 'acap-auth)
124 (make-variable-buffer-local 'acap-capability)
125 (make-variable-buffer-local 'acap-reached-tag)
126 (make-variable-buffer-local 'acap-failed-tag)
127 (make-variable-buffer-local 'acap-tag)
128 (make-variable-buffer-local 'acap-server)
129 (make-variable-buffer-local 'acap-port)
130 (make-variable-buffer-local 'acap-response)
132 (defvar acap-network-stream-alist
133 '((default . open-network-stream-as-binary)))
135 (defun acap-network-stream-open (buffer server port &optional type)
136 (let* ((port (or port acap-default-port))
138 (message "Connecting to %s..." server)
139 (funcall (cdr (assq (or type 'default)
140 acap-network-stream-alist))
141 "ACAP" buffer server port))))
143 (with-current-buffer buffer
144 (while (and (memq (process-status process) '(open run))
145 (goto-char (point-min))
146 (not (setq acap-capability (acap-parse-greeting))))
147 (message "Waiting for response from %s..." server)
148 (accept-process-output process 1))
149 (message "Waiting for response from %s...done" server)
150 (when (memq (process-status process) '(open run))
153 (defvar acap-passphrase nil)
154 (defvar acap-rp-user nil)
155 (defvar acap-rp-server nil)
156 (defvar acap-rp-auth nil)
158 (defvar acap-passphrase-alist nil)
160 (defun acap-read-passphrase (prompt)
161 "Prompt is not used."
164 (setq prompt (format "%s passphrase for %s@%s: "
165 acap-rp-auth acap-rp-user acap-rp-server))
166 (if (functionp 'read-passwd)
168 (if (load "passwd" t)
169 (read-passwd prompt))))))
172 (defvar acap-debug t)
173 (defvar acap-debug-buffer nil)
174 (defun acap-debug (string)
175 "Insert STRING to the debug buffer."
177 (if (or (null acap-debug-buffer)
178 (not (bufferp acap-debug-buffer))
179 (not (buffer-live-p acap-debug-buffer)))
180 (setq acap-debug-buffer (get-buffer-create "*Debug acap*")))
181 (with-current-buffer acap-debug-buffer
182 (goto-char (point-max))
185 ;;; Stock passphrase (Not implemented yet)
186 (defun acap-stock-passphrase (user server auth passphrase)
187 (let ((key (format "%s/%s/%s" user server auth))
189 (when (setq pair (assoc key acap-passphrase-alist))
190 (setq acap-passphrase-alist (delete pair acap-passphrase-alist)))
191 (setq acap-passphrase-alist (cons
192 (cons key passphrase)
193 acap-passphrase-alist))))
195 (defun acap-stocked-passphrase (user server auth)
196 (when acap-stock-passphrase
197 (let ((key (format "%s/%s/%s" user server auth)))
198 (cdr (assoc key acap-passphrase-alist)))))
200 (defun acap-remove-stocked-passphrase (user server auth)
201 (let ((key (format "%s/%s/%s" user server auth)))
202 (setq acap-passphrase-alist
203 (delq (assoc key acap-passphrase-alist)
204 acap-passphrase-alist))))
207 (defun acap-open (server &optional user auth port type)
208 (let* ((user (or user acap-default-user))
209 (buffer (get-buffer-create (concat " *acap on " user " at " server)))
210 process passphrase mechanism tag)
211 (with-current-buffer buffer
213 (delete-process acap-process))
214 (setq process (acap-network-stream-open buffer server port type)
215 acap-process process)
217 (set-buffer-multibyte nil)
218 (buffer-disable-undo)
219 (setq acap-state 'initial)
220 (set-process-filter process 'acap-arrival-filter)
221 (set-process-sentinel process 'acap-sentinel)
222 (while (and (memq (process-status process) '(open run))
223 (not (eq acap-state 'auth)))
230 (cdr (or (assq 'Sasl acap-capability)
231 (assq 'SASL acap-capability))))))
233 (sasl-make-client mechanism user "acap" server))
234 (sasl-read-passphrase 'acap-read-passphrase)
236 (acap-rp-server server)
237 (acap-rp-auth (sasl-mechanism-name mechanism))
238 acap-passphrase step response cont-string)
239 (unless (string= (sasl-mechanism-name mechanism)
241 (setq acap-passphrase (acap-read-passphrase nil)))
242 (setq tag (acap-send-command
245 (format "AUTHENTICATE \"%s\""
246 (sasl-mechanism-name mechanism))
248 (sasl-next-step sclient nil))
249 (sasl-step-data step))
250 (concat " " (prin1-to-string
251 (sasl-step-data step)))))))
252 (when (setq response (acap-wait-for-response process tag))
253 (while (acap-response-cont-p response)
255 step (acap-response-cont-string response))
256 (acap-response-clear process)
257 (if (setq step (sasl-next-step sclient step))
259 (insert (or (sasl-step-data step) ""))
260 (setq response (acap-send-data-wait
261 process (current-buffer) tag)))
262 (setq response nil)))
263 (if (acap-response-ok-p response)
265 (setq acap-state 'auth)
267 (message "Authentication failed.")
271 (message "acap: Connecting to %s...failed" server))
272 (setq acap-server server
276 (defun acap-close (process)
277 (with-current-buffer (process-buffer process)
278 (unless (acap-response-ok-p (acap-send-command-wait process "LOGOUT"))
279 (message "Server %s didn't let me log out" acap-server))
280 (when (memq (process-status process) '(open run))
281 (delete-process process))
287 (defun acap-noop (process)
288 "Execute NOOP command on PROCESS."
289 (acap-send-command-wait process "NOOP"))
291 (defun acap-lang (process lang-list)
292 "Execute LANG command on PROCESS."
293 (acap-send-command-wait process
297 (mapcar 'prin1-to-string lang-list))
300 (defun acap-search (process target &optional modifier criteria)
301 "Execute SEARCH command on PROCESS.
302 TARGET is a string which specifies what is to be searched
303 \(dataset or context name\).
304 MODIFIER is an alist of modifiers. Each element should be a list like
305 \(MODIFIER-NAME DATA1 DATA2...\).
306 CRITERIA is a search criteria string.
307 If CRITERIA is not specified, \"ALL\" is assumed,
308 Modifiers and search criteria are described in section 6.4.1 of RFC2244.
311 \(acap-search process
314 \(RETURN \(\"addressbook.Alias\"
315 \"addressbook.Email\"
316 \"addressbook.List\"\)\)\)
317 \"OR NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\\
318 NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\"\)
320 \(acap-search process
321 \"/addressbook/user/fred/\"
322 '\(\(RETURN \(\"*\"\)\)
323 \"EQUAL \\\"entry\\\" \\\"i\;octed\\\" \\\"A0345\\\"\"\)"
324 (acap-send-command-wait process
325 (concat "SEARCH " (prin1-to-string target)
329 (acap-flatten modifier)
332 (or criteria "ALL"))))
334 (defun acap-freecontext (process name)
335 "Execute FREECONTEXT command on PROCESS."
336 (acap-send-command-wait process
337 (concat "FREECONTEXT " name)))
339 (defun acap-updatecontext (process names)
340 "Execute UPDATECONTEXT command on PROCESS."
341 (acap-send-command-wait process
344 (nconc (list "FREECONTEXT") names)
347 (defun acap-store (process entries)
348 "Execute STORE command on PROCESS.
349 ENTRIES is a store-entry list."
350 (acap-send-command-wait process (concat "STORE " (prin1-to-string entries))))
352 (defun acap-deletedsince (process name time)
353 "Execute DELETEDSINCE command on PROCESS."
354 (acap-send-command-wait process
355 (concat "DELETEDSINCE "
356 (prin1-to-string name)
358 (prin1-to-string (acap-encode-time time)))))
360 (defun acap-setacl (process object identifier rights)
361 "Execute SETACL command on PROCESS."
362 (acap-send-command-wait process
364 (prin1-to-string object)
366 (prin1-to-string identifier)
368 (prin1-to-string rights))))
370 (defun acap-deleteacl (process object &optional identifier)
371 "Execute DELETEACL command on PROCESS."
372 (acap-send-command-wait process
375 (prin1-to-string object)
377 (concat " " (prin1-to-string identifier))))))
379 (defun acap-myrights (process object)
380 "Execute MYRIGHTS command on PROCESS."
381 (acap-send-command-wait process
384 (prin1-to-string object))))
386 (defun acap-listrights (process object identifier)
387 "Execute LISTRIGHTS command on PROCESS."
388 (acap-send-command-wait process
391 (prin1-to-string object)
393 (prin1-to-string identifier))))
395 (defun acap-getquota (process dataset)
396 "Execute GETQUOTA command on PROCESS."
397 (acap-send-command-wait process
400 (prin1-to-string dataset))))
402 ;;; response accessor.
403 (defun acap-response-ok-p (response)
404 (assq 'done-ok response))
406 (defun acap-response-cont-p (response)
407 (assq 'cont response))
409 (defun acap-response-cont-string (response)
410 (cdr (assq 'cont response)))
412 (defun acap-response-body (response)
413 (cdr (or (assq 'done-ok response)
414 (assq 'done-no response)
415 (assq 'done-bad response))))
417 (defun acap-response-entries (response)
419 (dolist (ent response)
420 (if (eq (car ent) 'entry)
421 (setq entries (cons ent entries))))
424 (defun acap-response-entry-entry (entry)
427 (defun acap-response-entry-return-data-list (entry)
430 (defun acap-response-return-data-list-get-value (name return-data-list)
431 (nth 1 (assoc name return-data-list)))
433 (defun acap-response-listrights (response)
434 (cdr (assq 'listrights response)))
436 ;;; Send command, data.
437 (defun acap-response-clear (process)
438 (with-current-buffer (process-buffer process)
439 (setq acap-response nil)))
441 (defun acap-send-command-wait (process command)
442 (acap-wait-for-response process (acap-send-command process command)))
444 (defun acap-send-data-wait (process string tag)
445 (cond ((stringp string)
446 (acap-send-command-1 process string))
448 (with-current-buffer string
449 (acap-response-clear process)
450 (acap-send-command-1 process (format "{%d}" (buffer-size)))
451 (if (acap-response-cont-p (acap-wait-for-response process tag))
452 (with-current-buffer string
453 (acap-response-clear process)
454 (process-send-region process (point-min)
456 (process-send-string process acap-client-eol)))
457 (acap-debug (concat (buffer-string) acap-client-eol)))))
458 (acap-wait-for-response process tag))
460 (defun acap-send-command-1 (process cmdstr)
461 (acap-debug (concat "<-" cmdstr acap-client-eol))
462 (process-send-string process (concat cmdstr acap-client-eol)))
464 (defun acap-send-command (process command)
465 (with-current-buffer (process-buffer process)
466 (setq acap-response nil)
467 (if (not (listp command)) (setq command (list command)))
468 (let ((tag (setq acap-tag (1+ acap-tag)))
470 (setq cmdstr (concat (number-to-string acap-tag) " "))
471 (while (setq cmd (pop command))
473 (setq cmdstr (concat cmdstr cmd)))
475 (with-current-buffer cmd
476 (setq cmdstr (concat cmdstr (format "{%d}" (buffer-size)))))
479 (acap-send-command-1 process cmdstr)
481 response (acap-wait-for-response process tag))
482 (if (not (acap-response-cont-p response))
483 (setq command nil) ;; abort command if no cont-req
484 (with-current-buffer cmd
485 (process-send-region process (point-min)
487 (process-send-string process acap-client-eol))))))
488 (t (error "Unknown command type"))))
490 (acap-send-command-1 process cmdstr))
493 (defun acap-wait-for-response (process tag)
494 (with-current-buffer (process-buffer process)
495 (while (and (not (acap-response-cont-p acap-response))
496 (< acap-reached-tag tag))
497 (or (and (not (memq (process-status process) '(open run)))
499 (let ((len (/ (point-max) 1024))
502 (message "acap read: %dk" len))
503 (accept-process-output process 1))))
507 ;;; Sentinel, Filter.
508 (defun acap-sentinel (process string)
509 (delete-process process))
511 (defun acap-find-next-line ()
512 (when (re-search-forward (concat acap-server-eol "\\|{\\([0-9+]+\\)}"
516 (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
518 (goto-char (+ (point) (string-to-number (match-string 1))))
519 (acap-find-next-line))
522 (defun acap-arrival-filter (proc string)
523 "ACAP process filter."
525 (with-current-buffer (process-buffer proc)
526 (goto-char (point-max))
529 (goto-char (point-min))
530 (while (setq end (acap-find-next-line))
532 (narrow-to-region (point-min) end)
533 (delete-backward-char (length acap-server-eol))
534 (goto-char (point-min))
536 (cond ((or (eq acap-state 'auth)
537 (eq acap-state 'initial)
538 (eq acap-state 'nonauth))
539 (acap-parse-response))
541 (message "Unknown state %s in arrival filter"
543 (delete-region (point-min) (point-max))))))))
546 (defsubst acap-forward ()
547 (or (eobp) (forward-char)))
549 (defsubst acap-parse-number ()
550 (when (looking-at "[0-9]+")
552 (string-to-number (match-string 0))
553 (goto-char (match-end 0)))))
555 (defsubst acap-parse-literal ()
556 (when (looking-at "{\\([0-9]+\\)}\r\n")
557 (let ((pos (match-end 0))
558 (len (string-to-number (match-string 1))))
559 (if (< (point-max) (+ pos len))
561 (goto-char (+ pos len))
562 (buffer-substring pos (+ pos len))))))
564 (defun acap-parse-greeting ()
565 (when (looking-at "* ACAP")
566 (goto-char (match-end 0))
569 (while (eq (char-after (point)) ?\()
570 (push (read (current-buffer)) capabilities)
572 (nreverse capabilities))))
574 ;; resp-body = ["(" resp-code ")" SP] quoted
575 (defun acap-parse-resp-body ()
576 (let ((body (read (current-buffer))))
577 (if (listp body) ; resp-code
578 (list body (read (current-buffer)))
579 (list nil body) ; no resp-code.
582 ;; string = quoted / literal
584 ;; quoted = DQUOTE *QUOTED-CHAR DQUOTE
586 ;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
587 ;; "\" quoted-specials
589 ;; quoted-specials = DQUOTE / "\"
591 ;; TEXT-CHAR = <any CHAR except CR and LF>
593 (defsubst acap-parse-string ()
594 (cond ((eq (char-after) ?\")
596 (let ((p (point)) (name ""))
597 (skip-chars-forward "^\"\\\\")
598 (setq name (buffer-substring p (point)))
599 (while (eq (char-after) ?\\)
600 (setq p (1+ (point)))
602 (skip-chars-forward "^\"\\\\")
603 (setq name (concat name (buffer-substring p (point)))))
606 ((eq (char-after) ?{)
607 (acap-parse-literal))))
611 (defsubst acap-parse-nil ()
612 (if (looking-at "NIL")
613 (goto-char (match-end 0))))
615 ;; entry = entry-name / entry-path
616 ;; entry-name = string-utf8
617 ;; ;; entry name MUST NOT contain slash
618 ;; ;; MUST NOT begin with "."
619 ;; entry-path = string-utf8
620 ;; ;; slash-separated path to entry
621 ;; ;; begins with slash
623 (defsubst acap-parse-quoted ()
624 (if (eq (char-after) ?\")
625 (read (current-buffer))))
627 (defun acap-parse-entry ()
631 (defun acap-parse-value ()
634 ;; value-list = "(" [value *(SP value)] ")"
635 (defun acap-parse-value-list ()
637 (when (eq (char-after (point)) ?\()
639 (while (not (eq (char-after (point)) ?\)))
641 (push (acap-parse-value) values))
646 ;; return-data-list = return-data *(SP return-data)
648 ;; return-data = return-metadata / return-metalist /
651 (defun acap-parse-return-data-list ()
653 (setq rlist (list (acap-parse-return-metadata-or-return-metalist)))
655 (while (setq r (acap-parse-return-metadata-or-return-metalist))
656 (setq rlist (nconc rlist (list r)))
660 (defun acap-parse-return-metadata-or-return-metalist ()
661 (or (acap-parse-string)
662 (acap-parse-value-or-return-metalist)
663 (and (acap-parse-nil) nil)))
665 (defun acap-parse-value-or-return-metalist ()
666 (when (eq (char-after (point)) ?\()
668 (while (not (eq (char-after (point)) ?\)))
670 (push (or (acap-parse-value)
671 (acap-parse-return-metalist))
676 ;; return-metalist = "(" return-metadata *(SP return-metadata) ")"
677 ;; ;; occurs when multiple metadata items requested
679 (defun acap-parse-return-metalist ()
680 (when (eq (char-after (point)) ?\()
682 (while (not (eq (char-after (point)) ?\)))
684 (push (acap-parse-return-metadata) metadatas))
686 (nreverse metadatas))))
688 ;; return-metadata = nil / string / value-list / acl
689 (defun acap-parse-return-metadata ()
690 (or (acap-parse-string)
691 (acap-parse-value-list)
692 (and (acap-parse-nil) nil)
693 ;; (acap-parse-acl) acl is same as value-list.
696 ;; return-attr-list = "(" return-metalist *(SP return-metalist) ")"
697 ;; ;; occurs when "*" in RETURN pattern on SEARCH
698 (defun acap-parse-return-attr-list ()
699 (when (eq (char-after (point)) ?\()
701 (while (not (eq (char-after (point)) ?\)))
703 (push (acap-parse-return-metalist) metalists))
705 (nreverse metalists))))
707 (defun acap-parse-time ()
710 ;; quoted *(SP quoted)
711 (defun acap-parse-quoted-list ()
713 (setq qlist (list (acap-parse-quoted)))
715 (while (setq q (acap-parse-quoted))
716 (setq qlist (nconc qlist (list q)))
720 (defun acap-parse-any ()
721 (read (current-buffer)))
723 (defun acap-parse-extension-data ()
725 (setq elist (list (acap-parse-any)))
727 (while (setq e (acap-parse-any))
728 (setq elist (nconc elist (list e)))
732 (defun acap-parse-response ()
733 "Parse a ACAP command response."
734 (let ((token (read (current-buffer)))
742 (cons 'cont (acap-parse-string)))
744 ;; untagged response.
745 (case (prog1 (setq token (read (current-buffer)))
748 (list (acap-parse-quoted)
757 (acap-parse-return-data-list)))))
758 (ALERT ;(cons 'alert (acap-parse-resp-body))
759 (message (nth 1 (acap-parse-resp-body))))
760 (BYE ;(cons 'bye (acap-parse-resp-body)))
761 ;;(message (nth 1 (acap-parse-resp-body)))
763 (delete-process acap-process))
764 (CHANGE (cons 'change
765 (list (acap-parse-quoted)
777 (acap-parse-return-data-list)))))
778 (LANG (cons 'lang (list (acap-parse-quoted-list))))
780 (OK (cons 'stat-ok (acap-parse-resp-body)))
781 (NO (cons 'stat-no (acap-parse-resp-body)))
782 (BAD ;(cons 'stat-bad (acap-parse-resp-body))
783 ;; XXX cyrus-sml-acap does not return tagged bad response?
784 (error (nth 1 (acap-parse-resp-body))))))
788 (case (prog1 (setq token (read (current-buffer)))
790 (DELETED (cons 'deleted (acap-parse-quoted)))
792 ((OK Ok ok) (prog1 (cons 'done-ok (acap-parse-resp-body))
793 (setq acap-reached-tag tag)))
794 ((NO No no) (prog1 (cons 'done-no (acap-parse-resp-body))
795 (setq acap-reached-tag tag)))
796 ((BAD Bad bad) (prog1 (cons 'done-bad (acap-parse-resp-body))
797 (setq acap-reached-tag tag)))
801 (progn (acap-forward)
802 (acap-parse-return-data-list)))))
803 (LISTRIGHTS (cons 'listrights
804 (acap-parse-quoted-list)))
805 (MODTIME (cons 'modtime (acap-parse-time)))
806 (MYRIGHTS (cons 'myrights (acap-parse-quoted)))
808 (list (acap-parse-quoted)
815 (acap-parse-extension-data))))
816 (REFER (cons 'refer (list (acap-parse-quoted)
817 (acap-parse-quoted))))
818 (REMOVEFROM (cons 'removefrom
819 (list (acap-parse-quoted)
825 (acap-parse-number)))))
828 (cons 'extend (list token (acap-parse-extension-data))))))
830 (list 'garbage token)))
834 (defun acap-flatten (l)
835 "Flatten list-of-list."
842 (acap-flatten (cdr l)))))
844 (defun acap-flatten-r (l)
845 "Flatten list-of-list recursively."
849 (append (acap-flatten (car l)) (acap-flatten (cdr l))))
852 (defun acap-encode-time (time)
853 (format-time-string "%Y%m%d%H%M%S" (current-time) t)) ; Universal time.
855 (defun acap-decode-time (acap-time)
856 (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)
857 (encode-time (string-to-number (match-string 6 acap-time))
858 (string-to-number (match-string 5 acap-time))
859 (string-to-number (match-string 4 acap-time))
860 (string-to-number (match-string 3 acap-time))
861 (string-to-number (match-string 2 acap-time))
862 (string-to-number (match-string 1 acap-time))
867 ;;; acap.el ends here