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")))))
55 ;; * Send literal data for STORE.
59 ;; 27 Aug 2001 Created (Some codes are based on imap.el.).
71 "Low level ACAP issues."
74 (defcustom acap-default-user (user-login-name)
75 "Default username to use."
79 (defcustom acap-default-port 674
80 "Default port for ACAP."
84 (defcustom acap-stock-passphrase nil
85 "Stock passphrase on memory if t."
90 (defconst acap-server-eol "\r\n"
91 "The EOL string sent from the server.")
93 (defconst acap-client-eol "\r\n"
94 "The EOL string sent from the server.")
96 ;; Internal variables.
97 (defvar acap-state 'closed
99 Valid states are `closed', `initial', `auth'.")
101 (defvar acap-capability nil
102 "Capability for server.")
104 (defvar acap-reached-tag 0
105 "Lower limit on command tags that have been parsed.")
108 "Command tag number.")
110 (defvar acap-auth nil
111 "Authenticated mechanism name.")
113 (defvar acap-process nil
114 "Process for the buffer.")
116 (defvar acap-server nil
119 (defvar acap-port nil
122 (defvar acap-response nil
125 (defvar acap-logging-out nil
126 "Non-nil when ACAP is logging out.")
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)
139 (defvar acap-network-stream-alist
140 '((default . open-network-stream-as-binary)))
142 (defun acap-network-stream-open (buffer server port &optional type)
143 (let* ((port (or port acap-default-port))
145 (message "Connecting to %s..." server)
146 (funcall (cdr (assq (or type 'default)
147 acap-network-stream-alist))
148 "ACAP" buffer server port))))
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))
160 (defvar acap-passphrase nil)
161 (defvar acap-rp-user nil)
162 (defvar acap-rp-server nil)
163 (defvar acap-rp-auth nil)
165 (defvar acap-passphrase-alist nil)
168 (autoload 'ange-ftp-read-passwd "ange-ftp"))
170 (defun acap-read-passphrase (prompt)
171 "Prompt is not used."
174 (setq prompt (format "%s passphrase for %s@%s: "
175 acap-rp-auth acap-rp-user acap-rp-server))
176 (if (functionp 'read-passwd)
178 (if (load "passwd" t)
180 (ange-ftp-read-passwd prompt))))))
183 (defvar acap-debug t)
184 (defvar acap-debug-buffer nil)
185 (defun acap-debug (string)
186 "Insert STRING to the debug buffer."
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))
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))
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))))
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)))))
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))))
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
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)))
241 (cdr (or (assq 'Sasl acap-capability)
242 (assq 'SASL acap-capability))))))
244 (sasl-make-client mechanism user "acap" server))
245 (sasl-read-passphrase 'acap-read-passphrase)
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)
252 (setq acap-passphrase (acap-read-passphrase nil)))
253 (setq tag (acap-send-command
256 (format "AUTHENTICATE \"%s\""
257 (sasl-mechanism-name mechanism))
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)
266 step (acap-response-cont-string response))
267 (acap-response-clear process)
268 (if (setq step (sasl-next-step sclient step))
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)
276 (setq acap-state 'auth)
278 (message "Authentication failed.")
282 (message "acap: Connecting to %s...failed" server))
283 (setq acap-server server
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))
299 (defun acap-noop (process)
300 "Execute NOOP command on PROCESS."
301 (acap-send-command-wait process "NOOP"))
303 (defun acap-lang (process lang-list)
304 "Execute LANG command on PROCESS."
305 (acap-send-command-wait process
309 (mapcar 'prin1-to-string lang-list))
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.
323 \(acap-search process
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\"\)
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)
341 (acap-flatten modifier)
344 (or criteria "ALL"))))
346 (defun acap-freecontext (process name)
347 "Execute FREECONTEXT command on PROCESS."
348 (acap-send-command-wait process
349 (concat "FREECONTEXT " name)))
351 (defun acap-updatecontext (process names)
352 "Execute UPDATECONTEXT command on PROCESS."
353 (acap-send-command-wait process
356 (nconc (list "FREECONTEXT") names)
359 (defun acap-store (process entries)
360 "Execute STORE command on PROCESS.
361 ENTRIES is a store-entry list."
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)
372 ((stringp (car entries))
374 (insert (car entries))
376 (while (re-search-forward "\\\\" nil t)
377 (replace-match "\\\\\\\\"))
379 (while (re-search-forward "\"" nil t)
380 (replace-match "\\\\\""))
383 (goto-char (point-max))
385 ((symbolp (car entries))
386 (insert (prin1-to-string (car entries)))))
387 (if (cdr entries)(insert " "))
388 (setq entries (cdr entries)))
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)))))
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))))
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)
405 (prin1-to-string (acap-encode-time time)))))
407 (defun acap-setacl (process object identifier rights)
408 "Execute SETACL command on PROCESS."
409 (acap-send-command-wait process
411 (prin1-to-string object)
413 (prin1-to-string identifier)
415 (prin1-to-string rights))))
417 (defun acap-deleteacl (process object &optional identifier)
418 "Execute DELETEACL command on PROCESS."
419 (acap-send-command-wait process
422 (prin1-to-string object)
424 (concat " " (prin1-to-string identifier))))))
426 (defun acap-myrights (process object)
427 "Execute MYRIGHTS command on PROCESS."
428 (acap-send-command-wait process
431 (prin1-to-string object))))
433 (defun acap-listrights (process object identifier)
434 "Execute LISTRIGHTS command on PROCESS."
435 (acap-send-command-wait process
438 (prin1-to-string object)
440 (prin1-to-string identifier))))
442 (defun acap-getquota (process dataset)
443 "Execute GETQUOTA command on PROCESS."
444 (acap-send-command-wait process
447 (prin1-to-string dataset))))
449 ;;; response accessor.
450 (defun acap-response-ok-p (response)
451 (assq 'done-ok response))
453 (defun acap-response-bye-p (response)
454 (assq 'bye response))
456 (defun acap-response-bye-message (response)
457 (nth 1 (cdr (assq 'bye response))))
459 (defun acap-response-cont-p (response)
460 (assq 'cont response))
462 (defun acap-response-cont-string (response)
463 (cdr (assq 'cont response)))
465 (defun acap-response-body (response)
466 (cdr (or (assq 'done-ok response)
467 (assq 'done-no response)
468 (assq 'done-bad response))))
470 (defun acap-response-entries (response)
472 (dolist (ent response)
473 (if (eq (car ent) 'entry)
474 (setq entries (cons ent entries))))
477 (defun acap-response-entry-entry (entry)
480 (defun acap-response-entry-return-data-list (entry)
483 (defun acap-response-return-data-list-get-value (name return-data-list)
484 (nth 1 (assoc name return-data-list)))
486 (defun acap-response-listrights (response)
487 (cdr (assq 'listrights response)))
489 ;;; Send command, data.
490 (defun acap-response-clear (process)
491 (with-current-buffer (process-buffer process)
492 (setq acap-response nil)))
494 (defun acap-send-command-wait (process command)
495 (acap-wait-for-response process (acap-send-command process command)))
497 (defun acap-send-data-wait (process string tag)
498 (cond ((stringp string)
499 (acap-send-command-1 process 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)
509 (process-send-string process acap-client-eol)))
510 (acap-debug (concat (buffer-string) acap-client-eol)))))
511 (acap-wait-for-response process tag))
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)))
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)))
523 (setq cmdstr (concat (number-to-string acap-tag) " "))
524 (while (setq cmd (pop command))
526 (setq cmdstr (concat cmdstr cmd)))
528 (with-current-buffer cmd
529 (setq cmdstr (concat cmdstr (format "{%d}" (buffer-size)))))
532 (acap-send-command-1 process cmdstr)
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)
540 (process-send-string process acap-client-eol))))))
541 (t (error "Unknown command type"))))
543 (acap-send-command-1 process cmdstr))
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)
552 (setq acap-response nil)
554 (prog1 (acap-response-bye-message acap-response)
555 (setq acap-response nil)))))
556 (or (and (not (memq (process-status process) '(open run)))
558 (let ((len (/ (point-max) 1024))
561 (message "acap read: %dk" len))
562 (accept-process-output process 1))))
566 ;;; Sentinel, Filter.
567 (defun acap-sentinel (process string)
568 (delete-process process))
570 (defun acap-find-next-line ()
571 (when (re-search-forward (concat acap-server-eol "\\|{\\([0-9+]+\\)}"
575 (if (< (point-max) (+ (point) (string-to-number (match-string 1))))
577 (goto-char (+ (point) (string-to-number (match-string 1))))
578 (acap-find-next-line))
581 (defun acap-arrival-filter (proc string)
582 "ACAP process filter."
584 (with-current-buffer (process-buffer proc)
585 (goto-char (point-max))
588 (goto-char (point-min))
589 (while (setq end (acap-find-next-line))
591 (narrow-to-region (point-min) end)
592 (delete-backward-char (length acap-server-eol))
593 (goto-char (point-min))
595 (cond ((or (eq acap-state 'auth)
596 (eq acap-state 'initial)
597 (eq acap-state 'nonauth))
598 (acap-parse-response))
600 (message "Unknown state %s in arrival filter"
602 (delete-region (point-min) (point-max))))))))
605 (defsubst acap-forward ()
606 (or (eobp) (forward-char)))
608 (defsubst acap-parse-number ()
609 (when (looking-at "[0-9]+")
611 (string-to-number (match-string 0))
612 (goto-char (match-end 0)))))
614 (defsubst acap-parse-literal ()
615 (when (looking-at "{\\([0-9]+\\)}\r\n")
616 (let ((pos (match-end 0))
617 (len (string-to-number (match-string 1))))
618 (if (< (point-max) (+ pos len))
620 (goto-char (+ pos len))
621 (buffer-substring pos (+ pos len))))))
623 (defun acap-parse-greeting ()
624 (when (looking-at "* ACAP")
625 (goto-char (match-end 0))
628 (while (eq (char-after (point)) ?\()
629 (push (read (current-buffer)) capabilities)
631 (nreverse capabilities))))
633 ;; resp-body = ["(" resp-code ")" SP] quoted
634 (defun acap-parse-resp-body ()
635 (let ((body (read (current-buffer))))
636 (if (listp body) ; resp-code
637 (list body (read (current-buffer)))
638 (list nil body) ; no resp-code.
641 ;; string = quoted / literal
643 ;; quoted = DQUOTE *QUOTED-CHAR DQUOTE
645 ;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
646 ;; "\" quoted-specials
648 ;; quoted-specials = DQUOTE / "\"
650 ;; TEXT-CHAR = <any CHAR except CR and LF>
652 (defsubst acap-parse-string ()
653 (cond ((eq (char-after) ?\")
655 (let ((p (point)) (name ""))
656 (skip-chars-forward "^\"\\\\")
657 (setq name (buffer-substring p (point)))
658 (while (eq (char-after) ?\\)
659 (setq p (1+ (point)))
661 (skip-chars-forward "^\"\\\\")
662 (setq name (concat name (buffer-substring p (point)))))
665 ((eq (char-after) ?{)
666 (acap-parse-literal))))
670 (defsubst acap-parse-nil ()
671 (if (looking-at "NIL")
672 (goto-char (match-end 0))))
674 ;; entry = entry-name / entry-path
675 ;; entry-name = string-utf8
676 ;; ;; entry name MUST NOT contain slash
677 ;; ;; MUST NOT begin with "."
678 ;; entry-path = string-utf8
679 ;; ;; slash-separated path to entry
680 ;; ;; begins with slash
682 (defsubst acap-parse-quoted ()
683 (if (eq (char-after) ?\")
684 (read (current-buffer))))
686 (defun acap-parse-entry ()
690 (defun acap-parse-value ()
693 ;; value-list = "(" [value *(SP value)] ")"
694 (defun acap-parse-value-list ()
696 (when (eq (char-after (point)) ?\()
698 (while (not (eq (char-after (point)) ?\)))
700 (push (acap-parse-value) values))
705 ;; return-data-list = return-data *(SP return-data)
707 ;; return-data = return-metadata / return-metalist /
710 (defun acap-parse-return-data-list ()
712 (setq rlist (list (acap-parse-return-metadata-or-return-metalist)))
714 (while (setq r (acap-parse-return-metadata-or-return-metalist))
715 (setq rlist (nconc rlist (list r)))
719 (defun acap-parse-return-metadata-or-return-metalist ()
720 (or (acap-parse-string)
721 (acap-parse-value-or-return-metalist)
722 (and (acap-parse-nil) nil)))
724 (defun acap-parse-value-or-return-metalist ()
725 (when (eq (char-after (point)) ?\()
727 (while (not (eq (char-after (point)) ?\)))
729 (push (or (acap-parse-value)
730 (acap-parse-return-metalist))
735 ;; return-metalist = "(" return-metadata *(SP return-metadata) ")"
736 ;; ;; occurs when multiple metadata items requested
738 (defun acap-parse-return-metalist ()
739 (when (eq (char-after (point)) ?\()
741 (while (not (eq (char-after (point)) ?\)))
743 (push (acap-parse-return-metadata) metadatas))
745 (nreverse metadatas))))
747 ;; return-metadata = nil / string / value-list / acl
748 (defun acap-parse-return-metadata ()
749 (or (acap-parse-string)
750 (acap-parse-value-list)
751 (and (acap-parse-nil) nil)
752 ;; (acap-parse-acl) acl is same as value-list.
755 ;; return-attr-list = "(" return-metalist *(SP return-metalist) ")"
756 ;; ;; occurs when "*" in RETURN pattern on SEARCH
757 (defun acap-parse-return-attr-list ()
758 (when (eq (char-after (point)) ?\()
760 (while (not (eq (char-after (point)) ?\)))
762 (push (acap-parse-return-metalist) metalists))
764 (nreverse metalists))))
766 (defun acap-parse-time ()
769 ;; quoted *(SP quoted)
770 (defun acap-parse-quoted-list ()
772 (setq qlist (list (acap-parse-quoted)))
774 (while (setq q (acap-parse-quoted))
775 (setq qlist (nconc qlist (list q)))
779 (defun acap-parse-any ()
780 (read (current-buffer)))
782 (defun acap-parse-extension-data ()
784 (setq elist (list (acap-parse-any)))
786 (while (setq e (acap-parse-any))
787 (setq elist (nconc elist (list e)))
791 (defun acap-parse-response ()
792 "Parse a ACAP command response."
793 (let ((token (read (current-buffer)))
801 (cons 'cont (acap-parse-string)))
803 ;; untagged response.
804 (case (prog1 (setq token (read (current-buffer)))
807 (list (acap-parse-quoted)
816 (acap-parse-return-data-list)))))
817 (ALERT ;(cons 'alert (acap-parse-resp-body))
818 (message "%s" (nth 1 (acap-parse-resp-body))))
820 (cons 'bye (acap-parse-resp-body)))
821 (CHANGE (cons 'change
822 (list (acap-parse-quoted)
834 (acap-parse-return-data-list)))))
835 (LANG (cons 'lang (list (acap-parse-quoted-list))))
837 (OK (cons 'stat-ok (acap-parse-resp-body)))
838 (NO (cons 'stat-no (acap-parse-resp-body)))
839 (BAD ;(cons 'stat-bad (acap-parse-resp-body))
840 ;; XXX cyrus-sml-acap does not return tagged bad response?
841 (error "%s" (nth 1 (acap-parse-resp-body))))))
845 (case (prog1 (setq token (read (current-buffer)))
847 (DELETED (cons 'deleted (acap-parse-quoted)))
849 ((OK Ok ok) (prog1 (cons 'done-ok (acap-parse-resp-body))
850 (setq acap-reached-tag tag)))
851 ((NO No no) (prog1 (cons 'done-no (acap-parse-resp-body))
852 (setq acap-reached-tag tag)))
853 ((BAD Bad bad) (prog1 (cons 'done-bad (acap-parse-resp-body))
854 (setq acap-reached-tag tag)))
858 (progn (acap-forward)
859 (acap-parse-return-data-list)))))
860 (LISTRIGHTS (cons 'listrights
861 (acap-parse-quoted-list)))
862 (MODTIME (cons 'modtime (acap-parse-time)))
863 (MYRIGHTS (cons 'myrights (acap-parse-quoted)))
865 (list (acap-parse-quoted)
872 (acap-parse-extension-data))))
873 (REFER (cons 'refer (list (acap-parse-quoted)
874 (acap-parse-quoted))))
875 (REMOVEFROM (cons 'removefrom
876 (list (acap-parse-quoted)
882 (acap-parse-number)))))
885 (cons 'extend (list token (acap-parse-extension-data))))))
887 (list 'garbage token)))
891 (defun acap-flatten (l)
892 "Flatten list-of-list."
899 (acap-flatten (cdr l)))))
901 (defun acap-flatten-r (l)
902 "Flatten list-of-list recursively."
906 (append (acap-flatten (car l)) (acap-flatten (cdr l))))
909 (defun acap-encode-time (time)
910 (format-time-string "%Y%m%d%H%M%S" (current-time) t)) ; Universal time.
912 (defun acap-decode-time (acap-time)
913 (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)
914 (encode-time (string-to-number (match-string 6 acap-time))
915 (string-to-number (match-string 5 acap-time))
916 (string-to-number (match-string 4 acap-time))
917 (string-to-number (match-string 3 acap-time))
918 (string-to-number (match-string 2 acap-time))
919 (string-to-number (match-string 1 acap-time))
924 ;;; acap.el ends here