a52406dd9a39801bf1c1c876453bb14b6d19f8d2
[elisp/wanderlust.git] / elmo / acap.el
1 ;;; acap.el --- An ACAP interface.
2
3 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Keywords: ACAP
5
6 ;; Copyright (C) 2001 Yuuichi Teranishi <teranisi@gohome.org>
7
8 ;; This file is not part of GNU Emacs
9
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)
13 ;; any later version.
14 ;;
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.
19 ;;
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.
24 ;;
25
26 ;;; Commentary:
27 ;;  Some codes are based on imap.el.
28
29 ;;; History:
30 ;; 
31
32 ;;; Code:
33
34 (eval-when-compile
35   (require 'cl)
36   (require 'static))
37 (require 'pces)
38 (require 'sasl)
39
40 ;; User variables.
41 (defgroup acap nil
42   "Low level ACAP issues."
43   :group 'applications)
44
45 (defcustom acap-default-user (user-login-name)
46   "Default username to use."
47   :type 'string
48   :group 'acap)
49
50 (defcustom acap-default-port 674
51   "Default port for ACAP."
52   :type 'integer
53   :group 'acap)
54
55 (defcustom acap-stock-passphrase nil
56   "Stock passphrase on memory if t."
57   :type 'boolean
58   :group 'acap)
59
60 ;; Constants.
61 (defconst acap-server-eol "\r\n"
62   "The EOL string sent from the server.")
63
64 (defconst acap-client-eol "\r\n"
65   "The EOL string sent from the server.")
66
67 ;; Internal variables.
68 (defvar acap-state 'closed
69   "ACAP state.
70 Valid states are `closed', `initial', `auth'.")
71
72 (defvar acap-capability nil
73   "Capability for server.")
74
75 (defvar acap-reached-tag 0
76   "Lower limit on command tags that have been parsed.")
77
78 (defvar acap-tag 0
79   "Command tag number.")
80
81 (defvar acap-auth nil
82   "Authenticated mechanism name.")
83
84 (defvar acap-process nil
85   "Process for the buffer.")
86
87 (defvar acap-server nil
88   "Server name.")
89
90 (defvar acap-port nil
91   "Port number.")
92
93 (defvar acap-response nil
94   "ACAP Response.")
95
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)
105
106 (defvar acap-network-stream-alist
107   '((default . open-network-stream-as-binary)))
108
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)))
114     (when process
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))
123           process)))))
124
125 (defvar acap-passphrase nil)
126 (defvar acap-rp-user nil)
127 (defvar acap-rp-server nil)
128 (defvar acap-rp-auth nil)
129
130 (defvar acap-passphrase-alist nil)
131
132 (defun acap-read-passphrase (prompt)
133   "Prompt is not used."
134   (or acap-passphrase
135       (progn
136         (setq prompt (format "%s passphrase for %s@%s: "
137                              acap-rp-auth acap-rp-user acap-rp-server))
138         (if (functionp 'read-passwd)
139             (read-passwd prompt)
140           (if (load "passwd" t)
141               (read-passwd prompt))))))
142
143 ;;; Debug.
144 (defvar acap-debug t)
145 (defvar acap-debug-buffer nil)
146 (defun acap-debug (string)
147   "Insert STRING to the debug buffer."
148   (when acap-debug
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))
155       (insert string))))
156
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))
160         pair)
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))))
166
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)))))
171
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))))
177
178 ;;; Open, Close 
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
183       (if acap-process
184           (delete-process acap-process))
185       (setq process (acap-network-stream-open buffer server port type)
186             acap-process process)
187       (erase-buffer)
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)))
195         (setq acap-auth
196               (unwind-protect
197                   (let* ((mechanism
198                           (sasl-find-mechanism
199                            (if auth
200                                (list auth)
201                              (cdr (or (assq 'Sasl acap-capability)
202                                       (assq 'SASL acap-capability))))))
203                          (sclient
204                           (sasl-make-client mechanism user "acap" server))
205                          (sasl-read-passphrase 'acap-read-passphrase)
206                          (acap-rp-user user)
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)
211                                      "ANONYMOUS")
212                       (setq acap-passphrase (acap-read-passphrase nil)))
213                     (setq tag (acap-send-command
214                                process
215                                (concat
216                                 (format "AUTHENTICATE \"%s\""
217                                         (sasl-mechanism-name mechanism))
218                                 (if (and (setq step
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)
225                         (sasl-step-set-data
226                          step (acap-response-cont-string response))
227                         (acap-response-clear process)
228                         (if (setq step (sasl-next-step sclient step))
229                             (with-temp-buffer
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)
235                           (progn
236                             (setq acap-state 'auth)
237                             mechanism)
238                         (message "Authentication failed.")
239                         (sit-for 1))))
240                 nil)))
241       (unless acap-auth
242         (message "acap: Connecting to %s...failed" server))
243       (setq acap-server server
244             acap-port port)
245       process)))
246
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))
253     (erase-buffer)
254     t))
255
256 ;;; Commands
257
258 (defun acap-noop (process)
259   "Execute NOOP command on PROCESS."
260   (acap-send-command-wait process "NOOP"))
261
262 (defun acap-lang (process lang-list)
263   "Execute LANG command on PROCESS."
264   (acap-send-command-wait process
265                           (mapconcat
266                            'identity
267                            (nconc (list "LANG")
268                                   (mapcar 'prin1-to-string lang-list))
269                            " ")))
270
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.
280
281 Examples:
282 \(acap-search process
283              \"/addressbook/\"
284              '\((DEPTH 3\)
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\"\)
290
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)
297                                   (if modifier " ")
298                                   (mapconcat
299                                    'prin1-to-string
300                                    (acap-flatten modifier)
301                                    " ")
302                                   " "
303                                   (or criteria "ALL"))))
304
305 (defun acap-freecontext (process name)
306   "Execute FREECONTEXT command on PROCESS."
307   (acap-send-command-wait process
308                           (concat "FREECONTEXT " name)))
309
310 (defun acap-updatecontext (process names)
311   "Execute UPDATECONTEXT command on PROCESS."
312   (acap-send-command-wait process
313                           (mapconcat
314                            'identity
315                            (nconc (list "FREECONTEXT") names)
316                            " ")))
317
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))))
322
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)
328                                   " "
329                                   (prin1-to-string (acap-encode-time time)))))
330
331 (defun acap-setacl (process object identifier rights)
332   "Execute SETACL command on PROCESS."
333   (acap-send-command-wait process
334                           (concat "SETACL "
335                                   (prin1-to-string object)
336                                   " "
337                                   (prin1-to-string identifier)
338                                   " "
339                                   (prin1-to-string rights))))
340
341 (defun acap-deleteacl (process object &optional identifier)
342   "Execute DELETEACL command on PROCESS."
343   (acap-send-command-wait process
344                           (concat
345                            "DELETEACL "
346                            (prin1-to-string object)
347                            (if identifier
348                                (concat " " (prin1-to-string identifier))))))
349
350 (defun acap-myrights (process object)
351   "Execute MYRIGHTS command on PROCESS."
352   (acap-send-command-wait process
353                           (concat
354                            "MYRIGHTS "
355                            (prin1-to-string object))))
356
357 (defun acap-listrights (process object identifier)
358   "Execute LISTRIGHTS command on PROCESS."
359   (acap-send-command-wait process
360                           (concat
361                            "LISTRIGHTS "
362                            (prin1-to-string object)
363                            " "
364                            (prin1-to-string identifier))))
365
366 (defun acap-getquota (process dataset)
367   "Execute GETQUOTA command on PROCESS."
368   (acap-send-command-wait process
369                           (concat
370                            "GETQUOTA "
371                            (prin1-to-string dataset))))
372
373 ;;; response accessor.
374 (defun acap-response-ok-p (response)
375   (assq 'done-ok response))
376
377 (defun acap-response-cont-p (response)
378   (assq 'cont response))
379
380 (defun acap-response-cont-string (response)
381   (cdr (assq 'cont response)))
382
383 (defun acap-response-body (response)
384   (cdr (or (assq 'done-ok response)
385            (assq 'done-no response)
386            (assq 'done-bad response))))
387
388 (defun acap-response-entries (response)
389   (let (entries)
390     (dolist (ent response)
391       (if (eq (car ent) 'entry)
392           (setq entries (cons ent entries))))
393     entries))
394
395 (defun acap-response-entry-entry (entry)
396   (car (cdr entry)))
397
398 (defun acap-response-entry-return-data-list (entry)
399   (nth 1 (cdr entry)))
400
401 (defun acap-response-return-data-list-get-value (name return-data-list)
402   (nth 1 (assoc name return-data-list)))
403
404 (defun acap-response-listrights (response)
405   (cdr (assq 'listrights response)))
406
407 ;;; Send command, data.
408 (defun acap-response-clear (process)
409   (with-current-buffer (process-buffer process)
410     (setq acap-response nil)))
411
412 (defun acap-send-command-wait (process command)
413   (acap-wait-for-response process (acap-send-command process command)))
414
415 (defun acap-send-data-wait (process string tag)
416   (cond ((stringp string)
417          (acap-send-command-1 process string))
418         ((bufferp 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)
426                                       (point-max))
427                  (process-send-string process acap-client-eol)))
428            (acap-debug (concat (buffer-string) acap-client-eol)))))
429   (acap-wait-for-response process tag))
430
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)))
434
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)))
440           cmd cmdstr response)
441       (setq cmdstr (concat (number-to-string acap-tag) " "))
442       (while (setq cmd (pop command))
443         (cond ((stringp cmd)
444                (setq cmdstr (concat cmdstr cmd)))
445               ((bufferp cmd)
446                (with-current-buffer cmd
447                  (setq cmdstr (concat cmdstr (format "{%d}" (buffer-size)))))
448                (unwind-protect
449                    (progn
450                      (acap-send-command-1 process cmdstr)
451                      (setq cmdstr nil
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)
457                                               (point-max))
458                          (process-send-string process acap-client-eol))))))
459               (t (error "Unknown command type"))))
460       (when cmdstr
461         (acap-send-command-1 process cmdstr))
462       tag)))
463
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)))
469                (sit-for 1))
470           (let ((len (/ (point-max) 1024))
471                 message-log-max)
472             (unless (< len 10)
473               (message "acap read: %dk" len))
474             (accept-process-output process 1))))
475     (message "")
476     acap-response))
477
478 ;;; Sentinel, Filter.
479 (defun acap-sentinel (process string)
480   (delete-process process))
481
482 (defun acap-find-next-line ()
483   (when (re-search-forward (concat acap-server-eol "\\|{\\([0-9+]+\\)}"
484                                    acap-server-eol)
485                            nil t)
486     (if (match-string 1)
487         (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
488             nil
489           (goto-char (+ (point) (string-to-number (match-string 1))))
490           (acap-find-next-line))
491       (point))))
492
493 (defun acap-arrival-filter (proc string)
494   "ACAP process filter."
495   (acap-debug string)
496   (with-current-buffer (process-buffer proc)
497     (goto-char (point-max))
498     (insert string)
499     (let (end)
500       (goto-char (point-min))
501       (while (setq end (acap-find-next-line))
502         (save-restriction
503           (narrow-to-region (point-min) end)
504           (delete-backward-char (length acap-server-eol))
505           (goto-char (point-min))
506           (unwind-protect
507               (cond ((or (eq acap-state 'auth)
508                          (eq acap-state 'initial)
509                          (eq acap-state 'nonauth))
510                      (acap-parse-response))
511                     (t
512                      (message "Unknown state %s in arrival filter"
513                               acap-state)))
514             (delete-region (point-min) (point-max))))))))
515
516 ;;; acap parser.
517 (defsubst acap-forward ()
518   (or (eobp) (forward-char)))
519
520 (defsubst acap-parse-number ()
521   (when (looking-at "[0-9]+")
522     (prog1
523         (string-to-number (match-string 0))
524       (goto-char (match-end 0)))))
525
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))
531           nil
532         (goto-char (+ pos len))
533         (buffer-substring pos (+ pos len))))))
534
535 (defun acap-parse-greeting ()
536   (when (looking-at "* ACAP")
537     (goto-char (match-end 0))
538     (acap-forward)
539     (let (capabilities)
540       (while (eq (char-after (point)) ?\()
541         (push (read (current-buffer)) capabilities)
542         (acap-forward))
543       (nreverse capabilities))))
544
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.
551       )))
552
553 ;;   string          = quoted / literal
554 ;;
555 ;;   quoted          = DQUOTE *QUOTED-CHAR DQUOTE
556 ;;
557 ;;   QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> /
558 ;;                     "\" quoted-specials
559 ;;
560 ;;   quoted-specials = DQUOTE / "\"
561 ;;
562 ;;   TEXT-CHAR       = <any CHAR except CR and LF>
563
564 (defsubst acap-parse-string ()
565   (cond ((eq (char-after) ?\")
566          (forward-char 1)
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)))
572              (forward-char 2)
573              (skip-chars-forward "^\"\\\\")
574              (setq name (concat name (buffer-substring p (point)))))
575            (forward-char 1)
576            name))
577         ((eq (char-after) ?{)
578          (acap-parse-literal))))
579
580 ;;   nil             = "NIL"
581
582 (defsubst acap-parse-nil ()
583   (if (looking-at "NIL")
584       (goto-char (match-end 0))))
585
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
593
594 (defsubst acap-parse-quoted ()
595   (if (eq (char-after) ?\")
596       (read (current-buffer))))
597
598 (defun acap-parse-entry ()
599   (acap-parse-quoted))
600
601 ;; value              = string
602 (defun acap-parse-value ()
603   (acap-parse-string))
604
605 ;; value-list         = "(" [value *(SP value)] ")"
606 (defun acap-parse-value-list ()
607   ;; same as acl.
608   (when (eq (char-after (point)) ?\()
609     (let (values)
610       (while (not (eq (char-after (point)) ?\)))
611         (acap-forward)
612         (push (acap-parse-value) values))
613       (acap-forward)
614       (nreverse values))))
615
616 ;;
617 ;;   return-data-list   = return-data *(SP return-data)
618 ;;
619 ;;   return-data        = return-metadata / return-metalist /
620 ;;                        return-attr-list
621
622 (defun acap-parse-return-data-list ()
623   (let (rlist r)
624     (setq rlist (list (acap-parse-return-metadata-or-return-metalist)))
625     (acap-forward)
626     (while (setq r (acap-parse-return-metadata-or-return-metalist))
627       (setq rlist (nconc rlist (list r)))
628       (acap-forward))
629     rlist))
630
631 (defun acap-parse-return-metadata-or-return-metalist ()
632   (or (acap-parse-nil)
633       (acap-parse-string)
634       (acap-parse-value-or-return-metalist)))
635
636 (defun acap-parse-value-or-return-metalist ()
637   (when (eq (char-after (point)) ?\()
638     (let (elems)
639       (while (not (eq (char-after (point)) ?\)))
640         (acap-forward)
641         (push (or (acap-parse-value)
642                   (acap-parse-return-metalist))
643               elems))
644       (acap-forward)
645       (nreverse elems))))
646
647 ;;   return-metalist    = "(" return-metadata *(SP return-metadata) ")"
648 ;;                        ;; occurs when multiple metadata items requested
649 ;;
650 (defun acap-parse-return-metalist ()
651   (when (eq (char-after (point)) ?\()
652     (let (metadatas)
653       (while (not (eq (char-after (point)) ?\)))
654         (acap-forward)
655         (push (acap-parse-return-metadata) metadatas))
656       (acap-forward)
657       (nreverse metadatas))))
658
659 ;;   return-metadata    = nil / string / value-list / acl
660 (defun acap-parse-return-metadata ()
661   (or (acap-parse-nil)
662       (acap-parse-string)
663       (acap-parse-value-list)
664       ;; (acap-parse-acl) acl is same as value-list.
665       ))
666
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)) ?\()
671     (let (metalists)
672       (while (not (eq (char-after (point)) ?\)))
673         (acap-forward)
674         (push (acap-parse-return-metalist) metalists))
675       (acap-forward)
676       (nreverse metalists))))
677
678 (defun acap-parse-time ()
679   (acap-parse-quoted))
680
681 ;; quoted *(SP quoted)
682 (defun acap-parse-quoted-list ()
683   (let (qlist q)
684     (setq qlist (list (acap-parse-quoted)))
685     (acap-forward)
686     (while (setq q (acap-parse-quoted))
687       (setq qlist (nconc qlist (list q)))
688       (acap-forward))
689     qlist))
690
691 (defun acap-parse-any ()
692   (read (current-buffer)))
693
694 (defun acap-parse-extension-data ()
695   (let (elist e)
696     (setq elist (list (acap-parse-any)))
697     (acap-forward)
698     (while (setq e (acap-parse-any))
699       (setq elist (nconc elist (list e)))
700       (acap-forward))
701     elist))
702
703 (defun acap-parse-response ()
704   "Parse a ACAP command response."
705   (let ((token (read (current-buffer)))
706         tag)
707     (setq
708      acap-response
709      (cons
710       (cond
711        ((eq token '+)
712         (acap-forward)
713         (cons 'cont (acap-parse-string)))
714        ((eq token '*)
715         ;; untagged response.
716         (case (prog1 (setq token (read (current-buffer)))
717                 (acap-forward))
718           (ADDTO (cons 'addto
719                        (list (acap-parse-quoted)
720                              (progn
721                                (acap-forward)
722                                (acap-parse-quoted))
723                              (progn
724                                (acap-forward)
725                                (acap-parse-number))
726                              (progn
727                                (acap-forward)
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))
733            (ding)
734            (delete-process acap-process))
735           (CHANGE (cons 'change
736                         (list (acap-parse-quoted)
737                               (progn
738                                 (acap-forward)
739                                 (acap-parse-quoted))
740                               (progn
741                                 (acap-forward)
742                                 (acap-parse-number))
743                               (progn
744                                 (acap-forward)
745                                 (acap-parse-number))
746                               (progn
747                                 (acap-forward)
748                                 (acap-parse-return-data-list)))))
749           (LANG (cons 'lang (list (acap-parse-quoted-list))))
750           ;; response-stat
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))))))
756        ((integerp token)
757         ;; tagged response.
758         (setq tag token)
759         (case (prog1 (setq token (read (current-buffer)))
760                 (acap-forward))
761           (DELETED   (cons 'deleted (acap-parse-quoted)))
762           ;; response-done
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)))
769           (ENTRY (cons 'entry
770                        (list
771                         (acap-parse-entry)
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)))
778           (QUOTA      (cons 'quota
779                             (list (acap-parse-quoted)
780                                   (progn
781                                     (acap-forward)
782                                     (acap-parse-number))
783                                   (progn
784                                     (acap-forward)
785                                     (acap-parse-number))
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)
791                                   (progn
792                                     (acap-forward)
793                                     (acap-parse-quoted))
794                                   (progn
795                                     (acap-forward)
796                                     (acap-parse-number)))))
797           ;; response-extend
798           (t ; extend-token
799            (cons 'extend (list token (acap-parse-extension-data))))))
800        (t ; garbage
801         (list 'garbage token)))
802       acap-response))))
803
804 ;;; Utilities.
805 (defun acap-flatten (l)
806   "Flatten list-of-list."
807   (unless (null l)
808     (append
809      (if (and (car l)
810               (listp (car l)))
811          (car l)
812        (list (car l)))
813      (acap-flatten (cdr l)))))
814
815 (defun acap-flatten-r (l)
816   "Flatten list-of-list recursively."
817   (cond
818    ((null l) '())
819    ((listp l)
820     (append (acap-flatten (car l)) (acap-flatten (cdr l))))
821    (t (list l))))
822
823 (defun acap-encode-time (time)
824   (format-time-string "%Y%m%d%H%M%S" (current-time) t)) ; Universal time.
825
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))
834                  t)))
835
836 (provide 'acap)
837
838 ;;; acap.el ends here