Check error.
[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 ;;
28 ;; acap.el is an elisp library providing an interface for talking to
29 ;; ACAP (RFC2244) servers.
30 ;;
31 ;; This is a transcript of short interactive session for demonstration
32 ;; purposes.
33
34 ;; (setq proc (acap-open "my.acap.server" "username" "CRAM-MD5"))
35 ;; => #<process ACAP>
36 ;;
37 ;; (acap-search proc "/addressbook/" '((RETURN ("*")))))
38 ;; => ((done-ok nil "search completed")
39 ;;     (modtime . "20010828091433000010")
40 ;;     (entry "user"
41 ;;         ((("subdataset"
42 ;;            ("."))
43 ;;           ("modtime" "20010824004532000003")
44 ;;           ("entry" "user"))))
45 ;;     (entry ""
46 ;;         ((("modtime" "20010824004532000002")
47 ;;           ("entry" "")
48 ;;           ("dataset.owner" "anonymous")
49 ;;           ("dataset.acl" ("$anyone   xrwia")))))
50 ;;
51 ;; (acap-close proc)
52 ;; => t
53 ;;
54 ;; Todo:
55 ;;  * Send literal data for STORE.
56
57 ;;; History:
58 ;;
59 ;; 27 Aug 2001 Created (Some codes are based on imap.el.).
60
61 ;;; Code:
62
63 (eval-when-compile
64   (require 'cl)
65   (require 'static))
66 (require 'pces)
67 (require 'sasl)
68
69 ;; User variables.
70 (defgroup acap nil
71   "Low level ACAP issues."
72   :group 'applications)
73
74 (defcustom acap-default-user (user-login-name)
75   "Default username to use."
76   :type 'string
77   :group 'acap)
78
79 (defcustom acap-default-port 674
80   "Default port for ACAP."
81   :type 'integer
82   :group 'acap)
83
84 (defcustom acap-stock-passphrase nil
85   "Stock passphrase on memory if t."
86   :type 'boolean
87   :group 'acap)
88
89 ;; Constants.
90 (defconst acap-server-eol "\r\n"
91   "The EOL string sent from the server.")
92
93 (defconst acap-client-eol "\r\n"
94   "The EOL string sent from the server.")
95
96 ;; Internal variables.
97 (defvar acap-state 'closed
98   "ACAP state.
99 Valid states are `closed', `initial', `auth'.")
100
101 (defvar acap-capability nil
102   "Capability for server.")
103
104 (defvar acap-reached-tag 0
105   "Lower limit on command tags that have been parsed.")
106
107 (defvar acap-tag 0
108   "Command tag number.")
109
110 (defvar acap-auth nil
111   "Authenticated mechanism name.")
112
113 (defvar acap-process nil
114   "Process for the buffer.")
115
116 (defvar acap-server nil
117   "Server name.")
118
119 (defvar acap-port nil
120   "Port number.")
121
122 (defvar acap-response nil
123   "ACAP Response.")
124
125 (defvar acap-logging-out nil
126   "Non-nil when ACAP is logging out.")
127
128 (make-variable-buffer-local 'acap-state)
129 (make-variable-buffer-local 'acap-auth)
130 (make-variable-buffer-local 'acap-capability)
131 (make-variable-buffer-local 'acap-reached-tag)
132 (make-variable-buffer-local 'acap-failed-tag)
133 (make-variable-buffer-local 'acap-tag)
134 (make-variable-buffer-local 'acap-server)
135 (make-variable-buffer-local 'acap-port)
136 (make-variable-buffer-local 'acap-response)
137 (make-variable-buffer-local 'acap-logging-out)
138
139 (defvar acap-network-stream-alist
140   '((default . open-network-stream-as-binary)))
141
142 (defun acap-network-stream-open (buffer server port &optional type)
143   (let* ((port (or port acap-default-port))
144          (process (progn
145                     (message "Connecting to %s..." server)
146                     (funcall (cdr (assq (or type 'default)
147                                         acap-network-stream-alist))
148                              "ACAP" buffer server port))))
149     (when process
150       (with-current-buffer buffer
151         (while (and (memq (process-status process) '(open run))
152                     (goto-char (point-min))
153                     (not (setq acap-capability (acap-parse-greeting))))
154           (message "Waiting for response from %s..." server)
155           (accept-process-output process 1))
156         (message "Waiting for response from %s...done" server)
157         (when (memq (process-status process) '(open run))
158           process)))))
159
160 (defvar acap-passphrase nil)
161 (defvar acap-rp-user nil)
162 (defvar acap-rp-server nil)
163 (defvar acap-rp-auth nil)
164
165 (defvar acap-passphrase-alist nil)
166
167 (eval-and-compile
168   (autoload 'ange-ftp-read-passwd "ange-ftp"))
169
170 (defun acap-read-passphrase (prompt)
171   "Prompt is not used."
172   (or acap-passphrase
173       (progn
174         (setq prompt (format "%s passphrase for %s@%s: "
175                              acap-rp-auth acap-rp-user acap-rp-server))
176         (if (functionp 'read-passwd)
177             (read-passwd prompt)
178           (if (load "passwd" t)
179               (read-passwd prompt)
180             (ange-ftp-read-passwd prompt))))))
181
182 ;;; Debug.
183 (defvar acap-debug t)
184 (defvar acap-debug-buffer nil)
185 (defun acap-debug (string)
186   "Insert STRING to the debug buffer."
187   (when acap-debug
188     (if (or (null acap-debug-buffer)
189             (not (bufferp acap-debug-buffer))
190             (not (buffer-live-p acap-debug-buffer)))
191         (setq acap-debug-buffer (get-buffer-create "*Debug acap*")))
192     (with-current-buffer acap-debug-buffer
193       (goto-char (point-max))
194       (insert string))))
195
196 ;;; Stock passphrase (Not implemented yet)
197 (defun acap-stock-passphrase (user server auth passphrase)
198   (let ((key (format "%s/%s/%s" user server auth))
199         pair)
200     (when (setq pair (assoc key acap-passphrase-alist))
201       (setq acap-passphrase-alist (delete pair acap-passphrase-alist)))
202     (setq acap-passphrase-alist (cons
203                                  (cons key passphrase)
204                                  acap-passphrase-alist))))
205
206 (defun acap-stocked-passphrase (user server auth)
207   (when acap-stock-passphrase
208     (let ((key (format "%s/%s/%s" user server auth)))
209       (cdr (assoc key acap-passphrase-alist)))))
210
211 (defun acap-remove-stocked-passphrase (user server auth)
212   (let ((key (format "%s/%s/%s" user server auth)))
213     (setq acap-passphrase-alist
214           (delq (assoc key acap-passphrase-alist)
215                 acap-passphrase-alist))))
216
217 ;;; Open, Close
218 (defun acap-open (server &optional user auth port type)
219   (let* ((user (or user acap-default-user))
220          (buffer (get-buffer-create (concat " *acap on " user " at " server)))
221          process passphrase mechanism tag)
222     (with-current-buffer buffer
223       (erase-buffer)
224       (if acap-process
225           (delete-process acap-process))
226       (setq process (acap-network-stream-open buffer server port type)
227             acap-process process)
228       (set-buffer-multibyte nil)
229       (buffer-disable-undo)
230       (setq acap-state 'initial)
231       (set-process-filter process 'acap-arrival-filter)
232       (set-process-sentinel process 'acap-sentinel)
233       (while (and (memq (process-status process) '(open run))
234                   (not (eq acap-state 'auth)))
235         (setq acap-auth
236               (unwind-protect
237                   (let* ((mechanism
238                           (sasl-find-mechanism
239                            (if auth
240                                (list auth)
241                              (cdr (or (assq 'Sasl acap-capability)
242                                       (assq 'SASL acap-capability))))))
243                          (sclient
244                           (sasl-make-client mechanism user "acap" server))
245                          (sasl-read-passphrase 'acap-read-passphrase)
246                          (acap-rp-user user)
247                          (acap-rp-server server)
248                          (acap-rp-auth (sasl-mechanism-name mechanism))
249                          acap-passphrase step response cont-string)
250                     (unless (string= (sasl-mechanism-name mechanism)
251                                      "ANONYMOUS")
252                       (setq acap-passphrase (acap-read-passphrase nil)))
253                     (setq tag (acap-send-command
254                                process
255                                (concat
256                                 (format "AUTHENTICATE \"%s\""
257                                         (sasl-mechanism-name mechanism))
258                                 (if (and (setq step
259                                                (sasl-next-step sclient nil))
260                                          (sasl-step-data step))
261                                     (concat " " (prin1-to-string
262                                                  (sasl-step-data step)))))))
263                     (when (setq response (acap-wait-for-response process tag))
264                       (while (acap-response-cont-p response)
265                         (sasl-step-set-data
266                          step (acap-response-cont-string response))
267                         (acap-response-clear process)
268                         (if (setq step (sasl-next-step sclient step))
269                             (with-temp-buffer
270                               (insert (or (sasl-step-data step) ""))
271                               (setq response (acap-send-data-wait
272                                               process (current-buffer) tag)))
273                           (setq response nil)))
274                       (if (acap-response-ok-p response)
275                           (progn
276                             (setq acap-state 'auth)
277                             mechanism)
278                         (message "Authentication failed.")
279                         (sit-for 1))))
280                 nil)))
281       (unless acap-auth
282         (message "acap: Connecting to %s...failed" server))
283       (setq acap-server server
284             acap-port port)
285       process)))
286
287 (defun acap-close (process)
288   (with-current-buffer (process-buffer process)
289     (setq acap-logging-out t)
290     (unless (acap-response-ok-p (acap-send-command-wait process "LOGOUT"))
291       (message "Server %s didn't let me log out" acap-server))
292     (when (memq (process-status process) '(open run))
293       (delete-process process))
294     (erase-buffer)
295     t))
296
297 ;;; Commands
298
299 (defun acap-noop (process)
300   "Execute NOOP command on PROCESS."
301   (acap-send-command-wait process "NOOP"))
302
303 (defun acap-lang (process lang-list)
304   "Execute LANG command on PROCESS."
305   (acap-send-command-wait process
306                           (mapconcat
307                            'identity
308                            (nconc (list "LANG")
309                                   (mapcar 'prin1-to-string lang-list))
310                            " ")))
311
312 (defun acap-search (process target &optional modifier criteria)
313   "Execute SEARCH command on PROCESS.
314 TARGET is a string which specifies what is to be searched
315 \(dataset or context name\).
316 MODIFIER is an alist of modifiers. Each element should be a list like
317 \(MODIFIER-NAME DATA1 DATA2...\).
318 CRITERIA is a search criteria string.
319 If CRITERIA is not specified, \"ALL\" is assumed,
320 Modifiers and search criteria are described in section 6.4.1 of RFC2244.
321
322 Examples:
323 \(acap-search process
324              \"/addressbook/\"
325              '\((DEPTH 3\)
326                \(RETURN \(\"addressbook.Alias\"
327                         \"addressbook.Email\"
328                         \"addressbook.List\"\)\)\)
329              \"OR NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\\
330                  NOT EQUAL \\\"addressbook.Email\\\" \\\"i\;octed\\\" NIL\"\)
331
332 \(acap-search process
333              \"/addressbook/user/fred/\"
334              '\(\(RETURN \(\"*\"\)\)
335              \"EQUAL \\\"entry\\\" \\\"i\;octed\\\" \\\"A0345\\\"\"\)"
336   (acap-send-command-wait process
337                           (concat "SEARCH " (prin1-to-string target)
338                                   (if modifier " ")
339                                   (mapconcat
340                                    'prin1-to-string
341                                    (acap-flatten modifier)
342                                    " ")
343                                   " "
344                                   (or criteria "ALL"))))
345
346 (defun acap-freecontext (process name)
347   "Execute FREECONTEXT command on PROCESS."
348   (acap-send-command-wait process
349                           (concat "FREECONTEXT " name)))
350
351 (defun acap-updatecontext (process names)
352   "Execute UPDATECONTEXT command on PROCESS."
353   (acap-send-command-wait process
354                           (mapconcat
355                            'identity
356                            (nconc (list "FREECONTEXT") names)
357                            " ")))
358
359 (defun acap-store (process entries)
360   "Execute STORE command on PROCESS.
361 ENTRIES is a store-entry list."
362   (with-temp-buffer
363     ;; As far as I know, current implementation of ACAP server
364     ;; (cyrus-smlacapd 0.5) does not accept literal argument for STORE.
365     ;; If literal argument is available, command arguments can be sent using
366     ;; function `acap-send-command-wait'.
367     (set-buffer-multibyte nil)
368     (insert "STORE (")
369     (let (beg tag)
370       (while entries
371         (cond
372          ((stringp (car entries))
373           (setq beg (point))
374           (insert (car entries))
375           (goto-char beg)
376           (while (re-search-forward "\\\\" nil t)
377             (replace-match "\\\\\\\\"))
378           (goto-char beg)
379           (while (re-search-forward "\"" nil t)
380             (replace-match "\\\\\""))
381           (goto-char beg)
382           (insert "\"")
383           (goto-char (point-max))
384           (insert "\""))
385          ((symbolp (car entries))
386           (insert (prin1-to-string (car entries)))))
387         (if (cdr entries)(insert " "))
388         (setq entries (cdr entries)))
389       (insert ")")
390       (goto-char (point-min))
391       (insert (with-current-buffer (process-buffer process)
392                 (number-to-string (setq tag (setq acap-tag (1+ acap-tag)))))
393               " ")
394       (process-send-region process (point-min) (point-max))
395       (acap-debug (concat (buffer-string) acap-client-eol))
396       (process-send-string process acap-client-eol)
397       (acap-wait-for-response process tag))))
398
399 (defun acap-deletedsince (process name time)
400   "Execute DELETEDSINCE command on PROCESS."
401   (acap-send-command-wait process
402                           (concat "DELETEDSINCE "
403                                   (prin1-to-string name)
404                                   " "
405                                   (prin1-to-string (acap-encode-time time)))))
406
407 (defun acap-setacl (process object identifier rights)
408   "Execute SETACL command on PROCESS."
409   (acap-send-command-wait process
410                           (concat "SETACL "
411                                   (prin1-to-string object)
412                                   " "
413                                   (prin1-to-string identifier)
414                                   " "
415                                   (prin1-to-string rights))))
416
417 (defun acap-deleteacl (process object &optional identifier)
418   "Execute DELETEACL command on PROCESS."
419   (acap-send-command-wait process
420                           (concat
421                            "DELETEACL "
422                            (prin1-to-string object)
423                            (if identifier
424                                (concat " " (prin1-to-string identifier))))))
425
426 (defun acap-myrights (process object)
427   "Execute MYRIGHTS command on PROCESS."
428   (acap-send-command-wait process
429                           (concat
430                            "MYRIGHTS "
431                            (prin1-to-string object))))
432
433 (defun acap-listrights (process object identifier)
434   "Execute LISTRIGHTS command on PROCESS."
435   (acap-send-command-wait process
436                           (concat
437                            "LISTRIGHTS "
438                            (prin1-to-string object)
439                            " "
440                            (prin1-to-string identifier))))
441
442 (defun acap-getquota (process dataset)
443   "Execute GETQUOTA command on PROCESS."
444   (acap-send-command-wait process
445                           (concat
446                            "GETQUOTA "
447                            (prin1-to-string dataset))))
448
449 ;;; response accessor.
450 (defun acap-response-ok-p (response)
451   (assq 'done-ok response))
452
453 (defun acap-response-bye-p (response)
454   (assq 'bye response))
455
456 (defun acap-response-bye-message (response)
457   (nth 1 (cdr (assq 'bye response))))
458
459 (defun acap-response-cont-p (response)
460   (assq 'cont response))
461
462 (defun acap-response-cont-string (response)
463   (cdr (assq 'cont response)))
464
465 (defun acap-response-body (response)
466   (cdr (or (assq 'done-ok response)
467            (assq 'done-no response)
468            (assq 'done-bad response))))
469
470 (defun acap-response-entries (response)
471   (let (entries)
472     (dolist (ent response)
473       (if (eq (car ent) 'entry)
474           (setq entries (cons ent entries))))
475     entries))
476
477 (defun acap-response-entry-entry (entry)
478   (car (cdr entry)))
479
480 (defun acap-response-entry-return-data-list (entry)
481   (nth 1 (cdr entry)))
482
483 (defun acap-response-return-data-list-get-value (name return-data-list)
484   (nth 1 (assoc name return-data-list)))
485
486 (defun acap-response-listrights (response)
487   (cdr (assq 'listrights response)))
488
489 ;;; Send command, data.
490 (defun acap-response-clear (process)
491   (with-current-buffer (process-buffer process)
492     (setq acap-response nil)))
493
494 (defun acap-send-command-wait (process command)
495   (acap-wait-for-response process (acap-send-command process command)))
496
497 (defun acap-send-data-wait (process string tag)
498   (cond ((stringp string)
499          (acap-send-command-1 process string))
500         ((bufferp string)
501          (with-current-buffer string
502            (acap-response-clear process)
503            (acap-send-command-1 process (format "{%d}" (buffer-size)))
504            (if (acap-response-cont-p (acap-wait-for-response process tag))
505                (with-current-buffer string
506                  (acap-response-clear process)
507                  (process-send-region process (point-min)
508                                       (point-max))
509                  (process-send-string process acap-client-eol)))
510            (acap-debug (concat (buffer-string) acap-client-eol)))))
511   (acap-wait-for-response process tag))
512
513 (defun acap-send-command-1 (process cmdstr)
514   (acap-debug (concat "<-" cmdstr acap-client-eol))
515   (process-send-string process (concat cmdstr acap-client-eol)))
516
517 (defun acap-send-command (process command)
518   (with-current-buffer (process-buffer process)
519     (setq acap-response nil)
520     (if (not (listp command)) (setq command (list command)))
521     (let ((tag (setq acap-tag (1+ acap-tag)))
522           cmd cmdstr response)
523       (setq cmdstr (concat (number-to-string acap-tag) " "))
524       (while (setq cmd (pop command))
525         (cond ((stringp cmd)
526                (setq cmdstr (concat cmdstr cmd)))
527               ((bufferp cmd)
528                (with-current-buffer cmd
529                  (setq cmdstr (concat cmdstr (format "{%d}" (buffer-size)))))
530                (unwind-protect
531                    (progn
532                      (acap-send-command-1 process cmdstr)
533                      (setq cmdstr nil
534                            response (acap-wait-for-response process tag))
535                      (if (not (acap-response-cont-p response))
536                          (setq command nil) ;; abort command if no cont-req
537                        (with-current-buffer cmd
538                          (process-send-region process (point-min)
539                                               (point-max))
540                          (process-send-string process acap-client-eol))))))
541               (t (error "Unknown command type"))))
542       (when cmdstr
543         (acap-send-command-1 process cmdstr))
544       tag)))
545
546 (defun acap-wait-for-response (process tag)
547   (with-current-buffer (process-buffer process)
548     (while (and (not (acap-response-cont-p acap-response))
549                 (< acap-reached-tag tag))
550       (when (acap-response-bye-p acap-response)
551         (if acap-logging-out
552             (setq acap-response nil)
553           (error (prog1 (acap-response-bye-message acap-response)
554                    (setq acap-response nil)))))
555       (or (and (not (memq (process-status process) '(open run)))
556                (sit-for 1))
557           (let ((len (/ (point-max) 1024))
558                 message-log-max)
559             (unless (< len 10)
560               (message "acap read: %dk" len))
561             (accept-process-output process 1))))
562     (message "")
563     acap-response))
564
565 ;;; Sentinel, Filter.
566 (defun acap-sentinel (process string)
567   (delete-process process))
568
569 (defun acap-find-next-line ()
570   (when (re-search-forward (concat acap-server-eol "\\|{\\([0-9+]+\\)}"
571                                    acap-server-eol)
572                            nil t)
573     (if (match-string 1)
574         (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
575             nil
576           (goto-char (+ (point) (string-to-number (match-string 1))))
577           (acap-find-next-line))
578       (point))))
579
580 (defun acap-arrival-filter (proc string)
581   "ACAP process filter."
582   (acap-debug string)
583   (with-current-buffer (process-buffer proc)
584     (goto-char (point-max))
585     (insert string)
586     (let (end)
587       (goto-char (point-min))
588       (while (setq end (acap-find-next-line))
589         (save-restriction
590           (narrow-to-region (point-min) end)
591           (delete-backward-char (length acap-server-eol))
592           (goto-char (point-min))
593           (unwind-protect
594               (cond ((or (eq acap-state 'auth)
595                          (eq acap-state 'initial)
596                          (eq acap-state 'nonauth))
597                      (acap-parse-response))
598                     (t
599                      (message "Unknown state %s in arrival filter"
600                               acap-state)))
601             (delete-region (point-min) (point-max))))))))
602
603 ;;; acap parser.
604 (defsubst acap-forward ()
605   (or (eobp) (forward-char)))
606
607 (defsubst acap-parse-number ()
608   (when (looking-at "[0-9]+")
609     (prog1
610         (string-to-number (match-string 0))
611       (goto-char (match-end 0)))))
612
613 (defsubst acap-parse-literal ()
614   (when (looking-at "{\\([0-9]+\\)}\r\n")
615     (let ((pos (match-end 0))
616           (len (string-to-number (match-string 1))))
617       (if (< (point-max) (+ pos len))
618           nil
619         (goto-char (+ pos len))
620         (buffer-substring pos (+ pos len))))))
621
622 (defun acap-parse-greeting ()
623   (when (looking-at "* ACAP")
624     (goto-char (match-end 0))
625     (acap-forward)
626     (let (capabilities)
627       (while (eq (char-after (point)) ?\()
628         (push (read (current-buffer)) capabilities)
629         (acap-forward))
630       (nreverse capabilities))))
631
632 ;; resp-body = ["(" resp-code ")" SP] quoted
633 (defun acap-parse-resp-body ()
634   (let ((body (read (current-buffer))))
635     (if (listp body) ; resp-code
636         (list body (read (current-buffer)))
637       (list nil body) ; no resp-code.
638       )))
639
640 ;;   string          = quoted / literal
641 ;;
642 ;;   quoted          = DQUOTE *QUOTED-CHAR DQUOTE
643 ;;
644 ;;   QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> /
645 ;;                     "\" quoted-specials
646 ;;
647 ;;   quoted-specials = DQUOTE / "\"
648 ;;
649 ;;   TEXT-CHAR       = <any CHAR except CR and LF>
650
651 (defsubst acap-parse-string ()
652   (cond ((eq (char-after) ?\")
653          (forward-char 1)
654          (let ((p (point)) (name ""))
655            (skip-chars-forward "^\"\\\\")
656            (setq name (buffer-substring p (point)))
657            (while (eq (char-after) ?\\)
658              (setq p (1+ (point)))
659              (forward-char 2)
660              (skip-chars-forward "^\"\\\\")
661              (setq name (concat name (buffer-substring p (point)))))
662            (forward-char 1)
663            name))
664         ((eq (char-after) ?{)
665          (acap-parse-literal))))
666
667 ;;   nil             = "NIL"
668
669 (defsubst acap-parse-nil ()
670   (if (looking-at "NIL")
671       (goto-char (match-end 0))))
672
673 ;; entry              = entry-name / entry-path
674 ;; entry-name         = string-utf8
675 ;;                        ;; entry name MUST NOT contain slash
676 ;;                        ;; MUST NOT begin with "."
677 ;; entry-path         = string-utf8
678 ;;                        ;; slash-separated path to entry
679 ;;                        ;; begins with slash
680
681 (defsubst acap-parse-quoted ()
682   (if (eq (char-after) ?\")
683       (read (current-buffer))))
684
685 (defun acap-parse-entry ()
686   (acap-parse-quoted))
687
688 ;; value              = string
689 (defun acap-parse-value ()
690   (acap-parse-string))
691
692 ;; value-list         = "(" [value *(SP value)] ")"
693 (defun acap-parse-value-list ()
694   ;; same as acl.
695   (when (eq (char-after (point)) ?\()
696     (let (values)
697       (while (not (eq (char-after (point)) ?\)))
698         (acap-forward)
699         (push (acap-parse-value) values))
700       (acap-forward)
701       (nreverse values))))
702
703 ;;
704 ;;   return-data-list   = return-data *(SP return-data)
705 ;;
706 ;;   return-data        = return-metadata / return-metalist /
707 ;;                        return-attr-list
708
709 (defun acap-parse-return-data-list ()
710   (let (rlist r)
711     (setq rlist (list (acap-parse-return-metadata-or-return-metalist)))
712     (acap-forward)
713     (while (setq r (acap-parse-return-metadata-or-return-metalist))
714       (setq rlist (nconc rlist (list r)))
715       (acap-forward))
716     rlist))
717
718 (defun acap-parse-return-metadata-or-return-metalist ()
719   (or (acap-parse-string)
720       (acap-parse-value-or-return-metalist)
721       (and (acap-parse-nil) nil)))
722
723 (defun acap-parse-value-or-return-metalist ()
724   (when (eq (char-after (point)) ?\()
725     (let (elems)
726       (while (not (eq (char-after (point)) ?\)))
727         (acap-forward)
728         (push (or (acap-parse-value)
729                   (acap-parse-return-metalist))
730               elems))
731       (acap-forward)
732       (nreverse elems))))
733
734 ;;   return-metalist    = "(" return-metadata *(SP return-metadata) ")"
735 ;;                        ;; occurs when multiple metadata items requested
736 ;;
737 (defun acap-parse-return-metalist ()
738   (when (eq (char-after (point)) ?\()
739     (let (metadatas)
740       (while (not (eq (char-after (point)) ?\)))
741         (acap-forward)
742         (push (acap-parse-return-metadata) metadatas))
743       (acap-forward)
744       (nreverse metadatas))))
745
746 ;;   return-metadata    = nil / string / value-list / acl
747 (defun acap-parse-return-metadata ()
748   (or (acap-parse-string)
749       (acap-parse-value-list)
750       (and (acap-parse-nil) nil)
751       ;; (acap-parse-acl) acl is same as value-list.
752       ))
753
754 ;;   return-attr-list   = "(" return-metalist *(SP return-metalist) ")"
755 ;;                        ;; occurs when "*" in RETURN pattern on SEARCH
756 (defun acap-parse-return-attr-list ()
757   (when (eq (char-after (point)) ?\()
758     (let (metalists)
759       (while (not (eq (char-after (point)) ?\)))
760         (acap-forward)
761         (push (acap-parse-return-metalist) metalists))
762       (acap-forward)
763       (nreverse metalists))))
764
765 (defun acap-parse-time ()
766   (acap-parse-quoted))
767
768 ;; quoted *(SP quoted)
769 (defun acap-parse-quoted-list ()
770   (let (qlist q)
771     (setq qlist (list (acap-parse-quoted)))
772     (acap-forward)
773     (while (setq q (acap-parse-quoted))
774       (setq qlist (nconc qlist (list q)))
775       (acap-forward))
776     qlist))
777
778 (defun acap-parse-any ()
779   (read (current-buffer)))
780
781 (defun acap-parse-extension-data ()
782   (let (elist e)
783     (setq elist (list (acap-parse-any)))
784     (acap-forward)
785     (while (setq e (acap-parse-any))
786       (setq elist (nconc elist (list e)))
787       (acap-forward))
788     elist))
789
790 (defun acap-parse-response ()
791   "Parse a ACAP command response."
792   (let ((token (read (current-buffer)))
793         tag)
794     (setq
795      acap-response
796      (cons
797       (cond
798        ((eq token '+)
799         (acap-forward)
800         (cons 'cont (acap-parse-string)))
801        ((eq token '*)
802         ;; untagged response.
803         (case (prog1 (setq token (read (current-buffer)))
804                 (acap-forward))
805           (ADDTO (cons 'addto
806                        (list (acap-parse-quoted)
807                              (progn
808                                (acap-forward)
809                                (acap-parse-quoted))
810                              (progn
811                                (acap-forward)
812                                (acap-parse-number))
813                              (progn
814                                (acap-forward)
815                                (acap-parse-return-data-list)))))
816           (ALERT ;(cons 'alert (acap-parse-resp-body))
817            (message (nth 1 (acap-parse-resp-body))))
818           ((BYE Bye bye)  
819            (cons 'bye (acap-parse-resp-body)))
820           (CHANGE (cons 'change
821                         (list (acap-parse-quoted)
822                               (progn
823                                 (acap-forward)
824                                 (acap-parse-quoted))
825                               (progn
826                                 (acap-forward)
827                                 (acap-parse-number))
828                               (progn
829                                 (acap-forward)
830                                 (acap-parse-number))
831                               (progn
832                                 (acap-forward)
833                                 (acap-parse-return-data-list)))))
834           (LANG (cons 'lang (list (acap-parse-quoted-list))))
835           ;; response-stat
836           (OK   (cons 'stat-ok (acap-parse-resp-body)))
837           (NO   (cons 'stat-no (acap-parse-resp-body)))
838           (BAD  ;(cons 'stat-bad (acap-parse-resp-body))
839            ;; XXX cyrus-sml-acap does not return tagged bad response?
840            (error (nth 1 (acap-parse-resp-body))))))
841        ((integerp token)
842         ;; tagged response.
843         (setq tag token)
844         (case (prog1 (setq token (read (current-buffer)))
845                 (acap-forward))
846           (DELETED   (cons 'deleted (acap-parse-quoted)))
847           ;; response-done
848           ((OK Ok ok) (prog1 (cons 'done-ok (acap-parse-resp-body))
849                         (setq acap-reached-tag tag)))
850           ((NO No no)   (prog1 (cons 'done-no (acap-parse-resp-body))
851                           (setq acap-reached-tag tag)))
852           ((BAD Bad bad) (prog1 (cons 'done-bad (acap-parse-resp-body))
853                            (setq acap-reached-tag tag)))
854           (ENTRY (cons 'entry
855                        (list
856                         (acap-parse-entry)
857                         (progn (acap-forward)
858                                (acap-parse-return-data-list)))))
859           (LISTRIGHTS (cons 'listrights
860                             (acap-parse-quoted-list)))
861           (MODTIME    (cons 'modtime (acap-parse-time)))
862           (MYRIGHTS   (cons 'myrights (acap-parse-quoted)))
863           (QUOTA      (cons 'quota
864                             (list (acap-parse-quoted)
865                                   (progn
866                                     (acap-forward)
867                                     (acap-parse-number))
868                                   (progn
869                                     (acap-forward)
870                                     (acap-parse-number))
871                                   (acap-parse-extension-data))))
872           (REFER      (cons 'refer (list (acap-parse-quoted)
873                                          (acap-parse-quoted))))
874           (REMOVEFROM (cons 'removefrom
875                             (list (acap-parse-quoted)
876                                   (progn
877                                     (acap-forward)
878                                     (acap-parse-quoted))
879                                   (progn
880                                     (acap-forward)
881                                     (acap-parse-number)))))
882           ;; response-extend
883           (t ; extend-token
884            (cons 'extend (list token (acap-parse-extension-data))))))
885        (t ; garbage
886         (list 'garbage token)))
887       acap-response))))
888
889 ;;; Utilities.
890 (defun acap-flatten (l)
891   "Flatten list-of-list."
892   (unless (null l)
893     (append
894      (if (and (car l)
895               (listp (car l)))
896          (car l)
897        (list (car l)))
898      (acap-flatten (cdr l)))))
899
900 (defun acap-flatten-r (l)
901   "Flatten list-of-list recursively."
902   (cond
903    ((null l) '())
904    ((listp l)
905     (append (acap-flatten (car l)) (acap-flatten (cdr l))))
906    (t (list l))))
907
908 (defun acap-encode-time (time)
909   (format-time-string "%Y%m%d%H%M%S" (current-time) t)) ; Universal time.
910
911 (defun acap-decode-time (acap-time)
912   (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)
913     (encode-time (string-to-number (match-string 6 acap-time))
914                  (string-to-number (match-string 5 acap-time))
915                  (string-to-number (match-string 4 acap-time))
916                  (string-to-number (match-string 3 acap-time))
917                  (string-to-number (match-string 2 acap-time))
918                  (string-to-number (match-string 1 acap-time))
919                  t)))
920
921 (provide 'acap)
922
923 ;;; acap.el ends here