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))
111 (process (funcall (cdr (assq (or type 'default)
112 acap-network-stream-alist))
113 "ACAP" buffer server port)))
115 (with-current-buffer buffer
116 (while (and (memq (process-status process) '(open run))
117 (goto-char (point-min))
118 (not (setq acap-capability (acap-parse-greeting))))
119 (message "Waiting for response from %s..." server)
120 (accept-process-output process 1))
121 (message "Waiting for response from %s...done" server)
122 (when (memq (process-status process) '(open run))
125 (defvar acap-passphrase nil)
126 (defvar acap-rp-user nil)
127 (defvar acap-rp-server nil)
128 (defvar acap-rp-auth nil)
130 (defvar acap-passphrase-alist nil)
132 (defun acap-read-passphrase (prompt)
133 "Prompt is not used."
136 (setq prompt (format "%s passphrase for %s@%s: "
137 acap-rp-auth acap-rp-user acap-rp-server))
138 (if (functionp 'read-passwd)
140 (if (load "passwd" t)
141 (read-passwd prompt))))))
144 (defvar acap-debug t)
145 (defvar acap-debug-buffer nil)
146 (defun acap-debug (string)
147 "Insert STRING to the debug buffer."
149 (if (or (null acap-debug-buffer)
150 (not (bufferp acap-debug-buffer))
151 (not (buffer-live-p acap-debug-buffer)))
152 (setq acap-debug-buffer (get-buffer-create "*Debug acap*")))
153 (with-current-buffer acap-debug-buffer
154 (goto-char (point-max))
157 ;;; Stock passphrase (Not implemented yet)
158 (defun acap-stock-passphrase (user server auth passphrase)
159 (let ((key (format "%s/%s/%s" user server auth))
161 (when (setq pair (assoc key acap-passphrase-alist))
162 (setq acap-passphrase-alist (delete pair acap-passphrase-alist)))
163 (setq acap-passphrase-alist (cons
164 (cons key passphrase)
165 acap-passphrase-alist))))
167 (defun acap-stocked-passphrase (user server auth)
168 (when acap-stock-passphrase
169 (let ((key (format "%s/%s/%s" user server auth)))
170 (cdr (assoc key acap-passphrase-alist)))))
172 (defun acap-remove-stocked-passphrase (user server auth)
173 (let ((key (format "%s/%s/%s" user server auth)))
174 (setq acap-passphrase-alist
175 (delq (assoc key acap-passphrase-alist)
176 acap-passphrase-alist))))
179 (defun acap-open (user server &optional auth port type)
180 (let* ((buffer (get-buffer-create (concat " *acap on " user " at " server)))
181 process passphrase mechanism tag)
182 (with-current-buffer buffer
184 (delete-process acap-process))
185 (setq process (acap-network-stream-open buffer server port type)
186 acap-process process)
188 (set-buffer-multibyte nil)
189 (buffer-disable-undo)
190 (setq acap-state 'initial)
191 (set-process-filter process 'acap-arrival-filter)
192 (set-process-sentinel process 'acap-sentinel)
193 (while (and (memq (process-status process) '(open run))
194 (not (eq acap-state 'auth)))
201 (cdr (or (assq 'Sasl acap-capability)
202 (assq 'SASL acap-capability))))))
204 (sasl-make-client mechanism user "acap" server))
205 (sasl-read-passphrase 'acap-read-passphrase)
207 (acap-rp-server server)
208 (acap-rp-auth (sasl-mechanism-name mechanism))
209 acap-passphrase step response cont-string)
210 (unless (string= (sasl-mechanism-name mechanism)
212 (setq acap-passphrase (acap-read-passphrase nil)))
213 (setq tag (acap-send-command
216 (format "AUTHENTICATE \"%s\""
217 (sasl-mechanism-name mechanism))
219 (sasl-next-step sclient nil))
220 (sasl-step-data step))
221 (concat " " (prin1-to-string
222 (sasl-step-data step)))))))
223 (when (setq response (acap-wait-for-response process tag))
224 (while (acap-response-cont-p response)
226 step (acap-response-cont-string response))
227 (acap-response-clear process)
228 (if (setq step (sasl-next-step sclient step))
230 (insert (or (sasl-step-data step) ""))
231 (setq response (acap-send-data-wait
232 process (current-buffer) tag)))
233 (setq response nil)))
234 (if (acap-response-ok-p response)
236 (setq acap-state 'auth)
238 (message "Authentication failed.")
242 (message "acap: Connecting to %s...failed" server))
243 (setq acap-server server
247 (defun acap-close (process)
248 (with-current-buffer (process-buffer process)
249 (unless (acap-response-ok-p (acap-send-command-wait process "LOGOUT"))
250 (message "Server %s didn't let me log out" acap-server))
251 (when (memq (process-status process) '(open run))
252 (delete-process process))
258 (defun acap-noop (process)
259 "Execute NOOP command on PROCESS."
260 (acap-send-command-wait process "NOOP"))
262 (defun acap-lang (process lang-list)
263 "Execute LANG command on PROCESS."
264 (acap-send-command-wait process
268 (mapcar 'prin1-to-string lang-list))
271 (defun acap-search (process target &optional modifier criteria)
272 "Execute SEARCH command on PROCESS.
273 TARGET is a string which specifies what is to be searched
274 \(dataset or context name\).
275 MODIFIER is an alist of modifiers. Each element should be a list like
276 \(MODIFIER-NAME DATA1 DATA2...\).
277 CRITERIA is a search criteria string.
278 If CRITERIA is not specified, \"ALL\" is assumed,
279 Modifiers and search criteria are described in section 6.4.1 of RFC2244.
282 \(acap-search process
285 \(RETURN \(\"addressbook.Alias\"
286 \"addressbook.Email\"
287 \"addressbook.List\"\)\)\)
288 \"OR NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\\
289 NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\"\)
291 \(acap-search process
292 \"/addressbook/user/fred/\"
293 '\(\(RETURN \(\"*\"\)\)
294 \"EQUAL \\\"entry\\\" \\\"i\;octed\\\" \\\"A0345\\\"\"\)"
295 (acap-send-command-wait process
296 (concat "SEARCH " (prin1-to-string target)
300 (acap-flatten modifier)
303 (or criteria "ALL"))))
305 (defun acap-freecontext (process name)
306 "Execute FREECONTEXT command on PROCESS."
307 (acap-send-command-wait process
308 (concat "FREECONTEXT " name)))
310 (defun acap-updatecontext (process names)
311 "Execute UPDATECONTEXT command on PROCESS."
312 (acap-send-command-wait process
315 (nconc (list "FREECONTEXT") names)
318 (defun acap-store (process entries)
319 "Execute STORE command on PROCESS.
320 ENTRIES is a store-entry list."
321 (acap-send-command-wait process (concat "STORE " (prin1-to-string entries))))
323 (defun acap-deletedsince (process name time)
324 "Execute DELETEDSINCE command on PROCESS."
325 (acap-send-command-wait process
326 (concat "DELETEDSINCE "
327 (prin1-to-string name)
329 (prin1-to-string (acap-encode-time time)))))
331 (defun acap-setacl (process object identifier rights)
332 "Execute SETACL command on PROCESS."
333 (acap-send-command-wait process
335 (prin1-to-string object)
337 (prin1-to-string identifier)
339 (prin1-to-string rights))))
341 (defun acap-deleteacl (process object &optional identifier)
342 "Execute DELETEACL command on PROCESS."
343 (acap-send-command-wait process
346 (prin1-to-string object)
348 (concat " " (prin1-to-string identifier))))))
350 (defun acap-myrights (process object)
351 "Execute MYRIGHTS command on PROCESS."
352 (acap-send-command-wait process
355 (prin1-to-string object))))
357 (defun acap-listrights (process object identifier)
358 "Execute LISTRIGHTS command on PROCESS."
359 (acap-send-command-wait process
362 (prin1-to-string object)
364 (prin1-to-string identifier))))
366 (defun acap-getquota (process dataset)
367 "Execute GETQUOTA command on PROCESS."
368 (acap-send-command-wait process
371 (prin1-to-string dataset))))
373 ;;; response accessor.
374 (defun acap-response-ok-p (response)
375 (assq 'done-ok response))
377 (defun acap-response-cont-p (response)
378 (assq 'cont response))
380 (defun acap-response-cont-string (response)
381 (cdr (assq 'cont response)))
383 (defun acap-response-body (response)
384 (cdr (or (assq 'done-ok response)
385 (assq 'done-no response)
386 (assq 'done-bad response))))
388 (defun acap-response-entries (response)
390 (dolist (ent response)
391 (if (eq (car ent) 'entry)
392 (setq entries (cons ent entries))))
395 (defun acap-response-entry-entry (entry)
398 (defun acap-response-entry-return-data-list (entry)
401 (defun acap-response-return-data-list-get-value (name return-data-list)
402 (nth 1 (assoc name return-data-list)))
404 (defun acap-response-listrights (response)
405 (cdr (assq 'listrights response)))
407 ;;; Send command, data.
408 (defun acap-response-clear (process)
409 (with-current-buffer (process-buffer process)
410 (setq acap-response nil)))
412 (defun acap-send-command-wait (process command)
413 (acap-wait-for-response process (acap-send-command process command)))
415 (defun acap-send-data-wait (process string tag)
416 (cond ((stringp string)
417 (acap-send-command-1 process string))
419 (with-current-buffer string
420 (acap-response-clear process)
421 (acap-send-command-1 process (format "{%d}" (buffer-size)))
422 (if (acap-response-cont-p (acap-wait-for-response process tag))
423 (with-current-buffer string
424 (acap-response-clear process)
425 (process-send-region process (point-min)
427 (process-send-string process acap-client-eol)))
428 (acap-debug (concat (buffer-string) acap-client-eol)))))
429 (acap-wait-for-response process tag))
431 (defun acap-send-command-1 (process cmdstr)
432 (acap-debug (concat "<-" cmdstr acap-client-eol))
433 (process-send-string process (concat cmdstr acap-client-eol)))
435 (defun acap-send-command (process command)
436 (with-current-buffer (process-buffer process)
437 (setq acap-response nil)
438 (if (not (listp command)) (setq command (list command)))
439 (let ((tag (setq acap-tag (1+ acap-tag)))
441 (setq cmdstr (concat (number-to-string acap-tag) " "))
442 (while (setq cmd (pop command))
444 (setq cmdstr (concat cmdstr cmd)))
446 (with-current-buffer cmd
447 (setq cmdstr (concat cmdstr (format "{%d}" (buffer-size)))))
450 (acap-send-command-1 process cmdstr)
452 response (acap-wait-for-response process tag))
453 (if (not (acap-response-cont-p response))
454 (setq command nil) ;; abort command if no cont-req
455 (with-current-buffer cmd
456 (process-send-region process (point-min)
458 (process-send-string process acap-client-eol))))))
459 (t (error "Unknown command type"))))
461 (acap-send-command-1 process cmdstr))
464 (defun acap-wait-for-response (process tag)
465 (with-current-buffer (process-buffer process)
466 (while (and (not (acap-response-cont-p acap-response))
467 (< acap-reached-tag tag))
468 (or (and (not (memq (process-status process) '(open run)))
470 (let ((len (/ (point-max) 1024))
473 (message "acap read: %dk" len))
474 (accept-process-output process 1))))
478 ;;; Sentinel, Filter.
479 (defun acap-sentinel (process string)
480 (delete-process process))
482 (defun acap-find-next-line ()
483 (when (re-search-forward (concat acap-server-eol "\\|{\\([0-9+]+\\)}"
487 (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
489 (goto-char (+ (point) (string-to-number (match-string 1))))
490 (acap-find-next-line))
493 (defun acap-arrival-filter (proc string)
494 "ACAP process filter."
496 (with-current-buffer (process-buffer proc)
497 (goto-char (point-max))
500 (goto-char (point-min))
501 (while (setq end (acap-find-next-line))
503 (narrow-to-region (point-min) end)
504 (delete-backward-char (length acap-server-eol))
505 (goto-char (point-min))
507 (cond ((or (eq acap-state 'auth)
508 (eq acap-state 'initial)
509 (eq acap-state 'nonauth))
510 (acap-parse-response))
512 (message "Unknown state %s in arrival filter"
514 (delete-region (point-min) (point-max))))))))
517 (defsubst acap-forward ()
518 (or (eobp) (forward-char)))
520 (defsubst acap-parse-number ()
521 (when (looking-at "[0-9]+")
523 (string-to-number (match-string 0))
524 (goto-char (match-end 0)))))
526 (defsubst acap-parse-literal ()
527 (when (looking-at "{\\([0-9]+\\)}\r\n")
528 (let ((pos (match-end 0))
529 (len (string-to-number (match-string 1))))
530 (if (< (point-max) (+ pos len))
532 (goto-char (+ pos len))
533 (buffer-substring pos (+ pos len))))))
535 (defun acap-parse-greeting ()
536 (when (looking-at "* ACAP")
537 (goto-char (match-end 0))
540 (while (eq (char-after (point)) ?\()
541 (push (read (current-buffer)) capabilities)
543 (nreverse capabilities))))
545 ;; resp-body = ["(" resp-code ")" SP] quoted
546 (defun acap-parse-resp-body ()
547 (let ((body (read (current-buffer))))
548 (if (listp body) ; resp-code
549 (list body (read (current-buffer)))
550 (list nil body) ; no resp-code.
553 ;; string = quoted / literal
555 ;; quoted = DQUOTE *QUOTED-CHAR DQUOTE
557 ;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
558 ;; "\" quoted-specials
560 ;; quoted-specials = DQUOTE / "\"
562 ;; TEXT-CHAR = <any CHAR except CR and LF>
564 (defsubst acap-parse-string ()
565 (cond ((eq (char-after) ?\")
567 (let ((p (point)) (name ""))
568 (skip-chars-forward "^\"\\\\")
569 (setq name (buffer-substring p (point)))
570 (while (eq (char-after) ?\\)
571 (setq p (1+ (point)))
573 (skip-chars-forward "^\"\\\\")
574 (setq name (concat name (buffer-substring p (point)))))
577 ((eq (char-after) ?{)
578 (acap-parse-literal))))
582 (defsubst acap-parse-nil ()
583 (if (looking-at "NIL")
584 (goto-char (match-end 0))))
586 ;; entry = entry-name / entry-path
587 ;; entry-name = string-utf8
588 ;; ;; entry name MUST NOT contain slash
589 ;; ;; MUST NOT begin with "."
590 ;; entry-path = string-utf8
591 ;; ;; slash-separated path to entry
592 ;; ;; begins with slash
594 (defsubst acap-parse-quoted ()
595 (if (eq (char-after) ?\")
596 (read (current-buffer))))
598 (defun acap-parse-entry ()
602 (defun acap-parse-value ()
605 ;; value-list = "(" [value *(SP value)] ")"
606 (defun acap-parse-value-list ()
608 (when (eq (char-after (point)) ?\()
610 (while (not (eq (char-after (point)) ?\)))
612 (push (acap-parse-value) values))
617 ;; return-data-list = return-data *(SP return-data)
619 ;; return-data = return-metadata / return-metalist /
622 (defun acap-parse-return-data-list ()
624 (setq rlist (list (acap-parse-return-metadata-or-return-metalist)))
626 (while (setq r (acap-parse-return-metadata-or-return-metalist))
627 (setq rlist (nconc rlist (list r)))
631 (defun acap-parse-return-metadata-or-return-metalist ()
634 (acap-parse-value-or-return-metalist)))
636 (defun acap-parse-value-or-return-metalist ()
637 (when (eq (char-after (point)) ?\()
639 (while (not (eq (char-after (point)) ?\)))
641 (push (or (acap-parse-value)
642 (acap-parse-return-metalist))
647 ;; return-metalist = "(" return-metadata *(SP return-metadata) ")"
648 ;; ;; occurs when multiple metadata items requested
650 (defun acap-parse-return-metalist ()
651 (when (eq (char-after (point)) ?\()
653 (while (not (eq (char-after (point)) ?\)))
655 (push (acap-parse-return-metadata) metadatas))
657 (nreverse metadatas))))
659 ;; return-metadata = nil / string / value-list / acl
660 (defun acap-parse-return-metadata ()
663 (acap-parse-value-list)
664 ;; (acap-parse-acl) acl is same as value-list.
667 ;; return-attr-list = "(" return-metalist *(SP return-metalist) ")"
668 ;; ;; occurs when "*" in RETURN pattern on SEARCH
669 (defun acap-parse-return-attr-list ()
670 (when (eq (char-after (point)) ?\()
672 (while (not (eq (char-after (point)) ?\)))
674 (push (acap-parse-return-metalist) metalists))
676 (nreverse metalists))))
678 (defun acap-parse-time ()
681 ;; quoted *(SP quoted)
682 (defun acap-parse-quoted-list ()
684 (setq qlist (list (acap-parse-quoted)))
686 (while (setq q (acap-parse-quoted))
687 (setq qlist (nconc qlist (list q)))
691 (defun acap-parse-any ()
692 (read (current-buffer)))
694 (defun acap-parse-extension-data ()
696 (setq elist (list (acap-parse-any)))
698 (while (setq e (acap-parse-any))
699 (setq elist (nconc elist (list e)))
703 (defun acap-parse-response ()
704 "Parse a ACAP command response."
705 (let ((token (read (current-buffer)))
713 (cons 'cont (acap-parse-string)))
715 ;; untagged response.
716 (case (prog1 (setq token (read (current-buffer)))
719 (list (acap-parse-quoted)
728 (acap-parse-return-data-list)))))
729 (ALERT ;(cons 'alert (acap-parse-resp-body))
730 (message (nth 1 (acap-parse-resp-body))))
731 (BYE ;(cons 'bye (acap-parse-resp-body)))
732 (message (acap-parse-resp-body))
734 (delete-process acap-process))
735 (CHANGE (cons 'change
736 (list (acap-parse-quoted)
748 (acap-parse-return-data-list)))))
749 (LANG (cons 'lang (list (acap-parse-quoted-list))))
751 (OK (cons 'stat-ok (acap-parse-resp-body)))
752 (NO (cons 'stat-no (acap-parse-resp-body)))
753 (BAD ;(cons 'stat-bad (acap-parse-resp-body))
754 ;; XXX cyrus-sml-acap does not return tagged bad response?
755 (error (nth 1 (acap-parse-resp-body))))))
759 (case (prog1 (setq token (read (current-buffer)))
761 (DELETED (cons 'deleted (acap-parse-quoted)))
763 ((OK Ok ok) (prog1 (cons 'done-ok (acap-parse-resp-body))
764 (setq acap-reached-tag tag)))
765 ((NO No no) (prog1 (cons 'done-no (acap-parse-resp-body))
766 (setq acap-reached-tag tag)))
767 ((BAD Bad bad) (prog1 (cons 'done-bad (acap-parse-resp-body))
768 (setq acap-reached-tag tag)))
772 (progn (acap-forward)
773 (acap-parse-return-data-list)))))
774 (LISTRIGHTS (cons 'listrights
775 (acap-parse-quoted-list)))
776 (MODTIME (cons 'modtime (acap-parse-time)))
777 (MYRIGHTS (cons 'myrights (acap-parse-quoted)))
779 (list (acap-parse-quoted)
786 (acap-parse-extension-data))))
787 (REFER (cons 'refer (list (acap-parse-quoted)
788 (acap-parse-quoted))))
789 (REMOVEFROM (cons 'removefrom
790 (list (acap-parse-quoted)
796 (acap-parse-number)))))
799 (cons 'extend (list token (acap-parse-extension-data))))))
801 (list 'garbage token)))
805 (defun acap-flatten (l)
806 "Flatten list-of-list."
813 (acap-flatten (cdr l)))))
815 (defun acap-flatten-r (l)
816 "Flatten list-of-list recursively."
820 (append (acap-flatten (car l)) (acap-flatten (cdr l))))
823 (defun acap-encode-time (time)
824 (format-time-string "%Y%m%d%H%M%S" (current-time) t)) ; Universal time.
826 (defun acap-decode-time (acap-time)
827 (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)
828 (encode-time (string-to-number (match-string 6 acap-time))
829 (string-to-number (match-string 5 acap-time))
830 (string-to-number (match-string 4 acap-time))
831 (string-to-number (match-string 3 acap-time))
832 (string-to-number (match-string 2 acap-time))
833 (string-to-number (match-string 1 acap-time))
838 ;;; acap.el ends here