git.fiddlerwoaroof.com
Browse code

Copy imap.cl to imap.lisp

Orivej Desh authored on 10/02/2012 10:13:29
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,2182 @@
1
+#+(version= 7 0)
2
+(sys:defpatch "imap" 1
3
+  "v1: fetch-letter-sequence support."
4
+  :type :system
5
+  :post-loadable t)
6
+
7
+#+(version= 8 0)
8
+(sys:defpatch "imap" 1
9
+  "v1: fetch-letter-sequence support."
10
+  :type :system
11
+  :post-loadable t)
12
+
13
+#+(version= 8 1)
14
+(sys:defpatch "imap" 1
15
+  "v1: Add ssl/tls support for both imap/pop connections."
16
+  :type :system
17
+  :post-loadable t)
18
+
19
+;; -*- mode: common-lisp; package: net.post-office -*-
20
+;;
21
+;; imap.cl
22
+;; imap and pop interface
23
+;;
24
+;; copyright (c) 1999-2002 Franz Inc, Berkeley, CA - All rights reserved.
25
+;; copyright (c) 2002-2007 Franz Inc, Oakland, CA - All rights reserved.
26
+;;
27
+;; This code is free software; you can redistribute it and/or
28
+;; modify it under the terms of the version 2.1 of
29
+;; the GNU Lesser General Public License as published by 
30
+;; the Free Software Foundation, as clarified by the AllegroServe
31
+;; prequel found in license-allegroserve.txt.
32
+;;
33
+;; This code is distributed in the hope that it will be useful,
34
+;; but without any warranty; without even the implied warranty of
35
+;; merchantability or fitness for a particular purpose.  See the GNU
36
+;; Lesser General Public License for more details.
37
+;;
38
+;; $Id: imap.cl,v 1.32 2009/03/25 22:46:02 layer Exp $
39
+
40
+;; Description:
41
+;;- This code in this file obeys the Lisp Coding Standard found in
42
+;;- http://www.franz.com/~jkf/coding_standards.html
43
+;;-
44
+
45
+
46
+(defpackage :net.post-office
47
+  (:use :lisp :excl)
48
+  (:export 
49
+   #:address-name
50
+   #:address-additional
51
+   #:address-mailbox
52
+   #:address-host
53
+   
54
+   #:alter-flags
55
+   #:close-connection
56
+   #:close-mailbox
57
+   #:copy-to-mailbox
58
+   #:create-mailbox
59
+   #:delete-letter
60
+   #:delete-mailbox
61
+   
62
+   #:envelope-date
63
+   #:envelope-subject
64
+   #:envelope-from
65
+   #:envelope-sender
66
+   #:envelope-reply-to
67
+   #:envelope-to
68
+   #:envelope-cc
69
+   #:envelope-bcc
70
+   #:envelope-in-reply-to
71
+   #:envelope-message-id
72
+   
73
+   #:expunge-mailbox
74
+   #:fetch-field
75
+   #:fetch-letter
76
+   #:fetch-letter-sequence
77
+   #:end-of-letter-p
78
+   #:with-fetch-letter-sequence
79
+   #:fetch-parts
80
+   #:*imap-version-number*
81
+   #:make-envelope-from-text
82
+   #:mailbox-flags      ; accessor
83
+   #:mailbox-permanent-flags ; acc
84
+   #:mailbox-list
85
+   #:mailbox-list-flags
86
+   #:mailbox-list-separator
87
+   #:mailbox-list-name
88
+   #:mailbox-message-count ; accessor
89
+   #:mailbox-recent-messages ; ac
90
+   #:mailbox-separator  ; accessor
91
+   #:mailbox-uidvalidity
92
+   #:mailbox-uidnext
93
+   #:make-imap-connection
94
+   #:make-pop-connection
95
+   #:with-imap-connection
96
+   #:with-pop-connection
97
+   #:noop
98
+   #:parse-mail-header
99
+   #:top-lines	; pop only
100
+   #:unique-id  ; pop only
101
+   
102
+   #:po-condition
103
+   #:po-condition-identifier
104
+   #:po-condition-server-string
105
+   #:po-error
106
+   
107
+   #:rename-mailbox
108
+   #:reset-mailbox
109
+   #:search-mailbox
110
+   #:select-mailbox
111
+   
112
+   )
113
+  )
114
+
115
+(in-package :net.post-office)
116
+
117
+(provide :imap)
118
+
119
+(defparameter *imap-version-number* '(:major 1 :minor 14)) ; major.minor
120
+
121
+;; todo
122
+;;  have the list of tags selected done on a per connection basis to
123
+;;  eliminate any possible multithreading problems
124
+;;
125
+;;
126
+
127
+(defvar *debug-imap* nil)
128
+
129
+
130
+
131
+
132
+
133
+(defclass post-office ()
134
+  ((socket :initarg :socket
135
+	   :accessor post-office-socket)
136
+   
137
+   (host :initarg :host
138
+	 :accessor  post-office-host
139
+	 :initform nil)
140
+   (user  :initarg :user
141
+	  :accessor post-office-user
142
+	  :initform nil)
143
+   
144
+   (state :accessor post-office-state
145
+	  :initarg :state
146
+	  :initform :unconnected)
147
+   
148
+   (timeout 
149
+    ;; time to wait for network activity for actions that should
150
+    ;; happen very quickly when things are operating normally
151
+    :initarg :timeout
152
+    :initform 60
153
+    :accessor timeout) 
154
+  ))
155
+
156
+(defclass imap-mailbox (post-office)
157
+  ((mailbox-name   ; currently selected mailbox
158
+    :accessor mailbox-name
159
+    :initform nil)
160
+
161
+   (separator 
162
+    ;; string that separates mailbox names in the hierarchy
163
+    :accessor mailbox-separator
164
+    :initform "")
165
+   
166
+   ;;; these slots hold information about the currently selected mailbox:
167
+   
168
+    (message-count  ; how many in the mailbox
169
+    :accessor mailbox-message-count
170
+    :initform 0)
171
+   
172
+   (recent-messages ; how many messages since we last checked
173
+    :accessor mailbox-recent-messages
174
+    :initform 0)
175
+   
176
+   (uidvalidity  ; used to denote messages uniquely
177
+    :accessor mailbox-uidvalidity 
178
+    :initform 0)
179
+   
180
+   (uidnext 
181
+    :accessor mailbox-uidnext ;; predicted next uid
182
+    :initform 0)
183
+   
184
+   (flags	; list of flags that can be stored in a message
185
+    :accessor mailbox-flags 
186
+    :initform nil)
187
+   
188
+   (permanent-flags  ; list of flags that be stored permanently
189
+    :accessor mailbox-permanent-flags
190
+    :initform nil)
191
+   
192
+   (first-unseen   ; number of the first unseen message
193
+    :accessor first-unseen
194
+    :initform 0)
195
+   
196
+   ;;; end list of values for the currently selected mailbox
197
+   
198
+   ;;; state information for fetch-letter-sequence
199
+   (fetch-letter-offset 
200
+    :accessor fetch-letter-offset)
201
+   (fetch-letter-number 
202
+    :accessor fetch-letter-number)
203
+   (fetch-letter-uid
204
+    :accessor fetch-letter-uid)
205
+   (fetch-letter-finished
206
+    :accessor fetch-letter-finished)
207
+   )
208
+  )
209
+
210
+
211
+(defclass pop-mailbox (post-office)
212
+  ((message-count  ; how many in the mailbox
213
+    :accessor mailbox-message-count
214
+    :initform 0)
215
+   (fetch-letter-state 
216
+    :accessor state
217
+    :initform :invalid)))
218
+    
219
+
220
+
221
+
222
+(defstruct (mailbox-list (:type list))
223
+  ;; a list of these are returned by mailbox-list
224
+  flags
225
+  separator
226
+  name)
227
+
228
+
229
+
230
+(defstruct (envelope (:type list))
231
+  ;; returned by fetch-letter as the value of the envelope property
232
+  date
233
+  subject
234
+  from
235
+  sender
236
+  reply-to
237
+  to
238
+  cc
239
+  bcc
240
+  in-reply-to
241
+  message-id)
242
+
243
+
244
+(defstruct (address (:type list))
245
+  name     ;; often the person's full name
246
+  additional
247
+  mailbox  ;; the login name
248
+  host	   ;; the name of the machine 
249
+  )
250
+
251
+
252
+
253
+;--------------------------------
254
+; conditions
255
+;
256
+; We define a set of conditions that are signalled due to events
257
+; in the imap interface.
258
+; Each condition has an indentifier which is a keyword.  That can
259
+; be used in the handling code to identify the class of error.
260
+; All our conditions are po-condition or po-error (which is a subclass of
261
+; po-condition).
262
+;
263
+; A condition will have a server-string value if it as initiated by 
264
+; something returned by the server.
265
+; A condition will have a format-control value if we want to display 
266
+; something we generated in response to 
267
+; 
268
+;
269
+;
270
+;; identifiers used in conditions/errors
271
+
272
+; :problem  condition
273
+;	the server responded with 'no' followed by an explanation.
274
+;	this mean that something unusual happend and doesn't necessarily
275
+;	mean that the command has completely failed (but it might).
276
+;	
277
+; :unknown-ok   condition
278
+;	the server responded with an 'ok' followed by something
279
+;	we don't recognize.  It's probably safe to ignore this.
280
+;
281
+;  :unknown-untagged condition
282
+;	the server responded with some untagged command we don't
283
+;	recognize.  it's probaby ok to ignore this.
284
+;
285
+;  :error-response  error
286
+;	the command failed.
287
+;
288
+;  :syntax-error   error
289
+;	the data passed to a function in this interface was malformed
290
+;
291
+;  :unexpected    error
292
+;	the server responded an unexpected way.
293
+;
294
+;  :server-shutdown-connection error
295
+;	the server has shut down the connection, don't attempt to
296
+;       send any more commands to this connection, or even close it.
297
+;
298
+;  :timeout  error
299
+;	server failed to respond within the timeout period
300
+;
301
+;  :response-too-large error
302
+;	contents of a response is too large to store in a Lisp array.
303
+
304
+
305
+;; conditions
306
+(define-condition po-condition ()
307
+  ;; used to notify user of things that shouldn't necessarily stop
308
+  ;; program flow
309
+  ((identifier 
310
+    ;; keyword identifying the error (or :unknown)
311
+    :reader po-condition-identifier	
312
+    :initform :unknown
313
+    :initarg :identifier
314
+    )
315
+   (server-string 
316
+    ;; message from the imap server
317
+    :reader po-condition-server-string
318
+    :initform ""
319
+    :initarg :server-string
320
+    ))
321
+  (:report
322
+   (lambda (con stream)
323
+     (with-slots (identifier server-string) con
324
+       ;; a condition either has a server-string or it has a 
325
+       ;; format-control string
326
+       (format stream "Post Office condition: ~s~%" identifier)
327
+       (if* (and (slot-boundp con 'excl::format-control)
328
+		 (excl::simple-condition-format-control con))
329
+	  then (apply #'format stream
330
+		      (excl::simple-condition-format-control con)
331
+		      (excl::simple-condition-format-arguments con)))
332
+       (if* server-string
333
+	  then (format stream
334
+		       "~&Message from server: ~s"
335
+		       (string-left-trim " " server-string)))))))
336
+	       
337
+    
338
+
339
+(define-condition po-error (po-condition error) 
340
+  ;; used to denote things that should stop program flow
341
+  ())
342
+
343
+
344
+
345
+;; aignalling the conditions
346
+
347
+(defun po-condition (identifier &key server-string format-control 
348
+			  format-arguments)
349
+  (signal (make-instance 'po-condition
350
+	    :identifier identifier
351
+	    :server-string server-string
352
+	    :format-control format-control
353
+	    :format-arguments format-arguments
354
+	    )))
355
+	    
356
+(defun po-error (identifier &key server-string
357
+		      format-control format-arguments)
358
+  (error (make-instance 'po-error
359
+	    :identifier identifier
360
+	    :server-string server-string
361
+	    :format-control format-control
362
+	    :format-arguments format-arguments)))
363
+
364
+			   
365
+
366
+;----------------------------------------------
367
+
368
+
369
+
370
+
371
+
372
+
373
+(defparameter *imap-tags* '("t01" "t02" "t03" "t04" "t05" "t06" "t07"))
374
+(defvar *cur-imap-tags* nil)
375
+
376
+(defvar *crlf*
377
+    (let ((str (make-string 2)))
378
+      (setf (aref str 0) #\return)
379
+      (setf (aref str 1) #\linefeed)
380
+      str))
381
+
382
+;; returns values: socket starttls
383
+;; server is a cons of the form:
384
+;; (server-name &key (port 25) (ssl nil) (starttls nil) ...ssl-client-keywords...)
385
+(defun connect-to-imap/pop-server (server-info server-type)
386
+  (macrolet ((pop-keyword (k l) `(prog1 (getf ,l ,k) (remf ,l ,k)))
387
+	     (server-port (ssl type) `(cond ((eq ,type :imap) (if ,ssl 993 143))
388
+					    ((eq ,type :pop) (if ,ssl 995 110)))))
389
+    (let* ((server (car server-info))
390
+	   (ssl-args (cdr server-info))
391
+	   ssl port starttls sock)
392
+      (setq ssl (pop-keyword :ssl ssl-args))
393
+      (setq port (or (pop-keyword :port ssl-args) (server-port ssl server-type)))
394
+      (setq starttls (pop-keyword :starttls ssl-args))
395
+      (setq sock (socket:make-socket :remote-host server
396
+				     :remote-port port))
397
+      (when ssl
398
+	(setq sock (apply #'socket:make-ssl-client-stream sock ssl-args)))
399
+      
400
+      (values sock starttls))) )
401
+
402
+(defun make-imap-connection (host &key (port 143) 
403
+				       user 
404
+				       password
405
+				       (timeout 30))
406
+  (multiple-value-bind (sock starttls)
407
+      (if (consp host)
408
+	  (connect-to-imap/pop-server host :imap)
409
+	(socket:make-socket :remote-host host :remote-port port))
410
+    (let ((imap (make-instance 'imap-mailbox
411
+		  :socket sock
412
+		  :host   host
413
+		  :timeout timeout
414
+		  :state :unauthorized)))
415
+    
416
+    (multiple-value-bind (tag cmd count extra comment)
417
+	(get-and-parse-from-imap-server imap)
418
+      (declare (ignorable cmd count extra))
419
+      (if* (not (eq :untagged tag))
420
+	 then  (po-error :error-response
421
+			 :server-string comment)))
422
+      
423
+    ; check for starttls negotiation
424
+    (when starttls
425
+      (let (capabilities)
426
+	(send-command-get-results
427
+	 imap "CAPABILITY"
428
+	 #'(lambda (mb cmd count extra comment)
429
+	     (declare (ignorable mb cmd count extra))
430
+	     (setq capabilities comment))
431
+	 #'(lambda (mb cmd count extra comment)
432
+	     (check-for-success mb cmd count extra comment
433
+				"CAPABILITY")))
434
+	(when (and capabilities (match-re "STARTTLS" capabilities :case-fold t
435
+					  :return nil))
436
+	  ;; negotiate starttls
437
+	  (send-command-get-results imap "STARTTLS"
438
+				    #'handle-untagged-response
439
+				    #'(lambda (mb cmd count extra comment)
440
+					(check-for-success mb cmd count extra comment
441
+							   "STARTTLS")
442
+					(setf (post-office-socket mb)
443
+					  (socket:make-ssl-client-stream
444
+					   (post-office-socket mb) :method :tlsv1)))))))
445
+
446
+    ; now login
447
+    (send-command-get-results imap 
448
+			      (format nil "login ~a ~a" user password)
449
+			      #'handle-untagged-response
450
+			      #'(lambda (mb command count extra comment)
451
+				  (check-for-success mb command count extra
452
+						     comment
453
+						     "login")))
454
+    
455
+    ; find the separator character
456
+    (let ((res (mailbox-list imap)))
457
+      ;; 
458
+      (let ((sep (cadr  (car res))))
459
+	(if* sep
460
+	   then (setf (mailbox-separator imap) sep))))
461
+    
462
+				    
463
+				    
464
+    imap)))
465
+
466
+
467
+(defmethod close-connection ((mb imap-mailbox))
468
+  
469
+  (let ((sock (post-office-socket mb)))
470
+    (if* sock
471
+       then (ignore-errors
472
+	     (send-command-get-results 
473
+	      mb
474
+	      "logout"
475
+	      ; don't want to get confused by untagged
476
+	      ; bye command, which is expected here
477
+	      #'(lambda (mb command count extra)
478
+		  (declare (ignore mb command count extra))
479
+		  nil)
480
+	      #'(lambda (mb command count extra comment)
481
+		  (check-for-success mb command count extra
482
+				     comment
483
+				     "logout")))))
484
+    (setf (post-office-socket mb) nil)
485
+    (if* sock then (ignore-errors (close sock)))
486
+    t))
487
+
488
+
489
+(defmethod close-connection ((pb pop-mailbox))
490
+  (let ((sock (post-office-socket pb)))
491
+    (if* sock
492
+       then (ignore-errors
493
+	     (send-pop-command-get-results 
494
+	      pb
495
+	      "QUIT")))
496
+    (setf (post-office-socket pb) nil)
497
+    (if* sock then (ignore-errors (close sock)))
498
+    t))
499
+
500
+
501
+
502
+(defun make-pop-connection (host &key (port 110)
503
+				      user
504
+				      password
505
+				      (timeout 30))
506
+  (multiple-value-bind (sock starttls)
507
+      (if (consp host)
508
+	  (connect-to-imap/pop-server host :pop)
509
+	(socket:make-socket :remote-host host :remote-port port))
510
+    (let ((pop (make-instance 'pop-mailbox
511
+		:socket sock
512
+		:host   host
513
+		:timeout timeout
514
+		:state :unauthorized)))
515
+    
516
+    (multiple-value-bind (result)
517
+	(get-and-parse-from-pop-server pop)
518
+      (if* (not (eq :ok result))
519
+	 then  (po-error :error-response
520
+			 :format-control
521
+			 "unexpected line from server after connect")))
522
+      
523
+    ; check for starttls negotiation
524
+    (when starttls
525
+      (let ((capabilities (send-pop-command-get-results pop "capa" t)))
526
+	(when (and capabilities (match-re "STLS" capabilities :case-fold t
527
+					  :return nil))
528
+	  (send-pop-command-get-results pop "STLS")		   
529
+	  (setf (post-office-socket pop) (socket:make-ssl-client-stream 
530
+					  (post-office-socket pop) :method :tlsv1)))))
531
+    
532
+    ; now login
533
+    (send-pop-command-get-results pop (format nil "user ~a" user))
534
+    (send-pop-command-get-results pop (format nil "pass ~a" password))
535
+
536
+    (let ((res (send-pop-command-get-results pop "stat")))
537
+      (setf (mailbox-message-count pop) (car res)))
538
+    
539
+    			    
540
+				    
541
+    pop)))
542
+			    
543
+
544
+(defmethod send-command-get-results ((mb imap-mailbox) 
545
+				     command untagged-handler tagged-handler)
546
+  ;; send a command and retrieve results until we get the tagged
547
+  ;; response for the command we sent
548
+  ;;
549
+  (let ((tag (get-next-tag)))
550
+    (format (post-office-socket mb)
551
+	    "~a ~a~a" tag command *crlf*)
552
+    (force-output (post-office-socket mb))
553
+    
554
+    (if* *debug-imap*
555
+       then (format t
556
+		    "~a ~a~a" tag command *crlf*)
557
+	    (force-output))
558
+    (loop
559
+      (multiple-value-bind (got-tag cmd count extra comment)
560
+	  (get-and-parse-from-imap-server mb)
561
+	(if* (eq got-tag :untagged)
562
+	   then (funcall untagged-handler mb cmd count extra comment)
563
+	 elseif (equal tag got-tag)
564
+	   then (funcall tagged-handler mb cmd count extra comment)
565
+		(return)
566
+	   else (po-error :error-response
567
+			  :format-control "received tag ~s out of order" 
568
+			  :format-arguments (list got-tag)
569
+			  :server-string comment))))))
570
+
571
+
572
+(defun get-next-tag ()
573
+  (let ((tag (pop *cur-imap-tags*)))
574
+    (if*  tag
575
+       thenret
576
+       else (setq *cur-imap-tags* *imap-tags*)
577
+	    (pop *cur-imap-tags*))))
578
+
579
+(defun handle-untagged-response (mb command count extra comment)
580
+  ;; default function to handle untagged responses, which are 
581
+  ;; really just returning general state information about
582
+  ;; the mailbox
583
+  (case command
584
+    (:exists (setf (mailbox-message-count mb) count))
585
+    (:recent (setf (mailbox-recent-messages mb) count))
586
+    (:flags  (setf (mailbox-flags mb) (kwd-intern-possible-list extra)))
587
+    (:bye ; occurs when connection times out or mailbox lock is stolen
588
+     (ignore-errors (close (post-office-socket mb)))
589
+     (po-error :server-shutdown-connection
590
+		 :server-string "server shut down the connection"))
591
+    (:no ; used when grabbing a lock from another process
592
+     (po-condition :problem :server-string comment))
593
+    (:ok ; a whole variety of things
594
+     (if* extra
595
+	then (if* (equalp (car extra) "unseen")
596
+		then (setf (first-unseen mb) (cadr extra))
597
+	      elseif (equalp (car extra) "uidvalidity")
598
+		then (setf (mailbox-uidvalidity mb) (cadr extra))
599
+	      elseif (equalp (car extra) "uidnext")
600
+		then (setf (mailbox-uidnext mb) (cadr extra))
601
+	      elseif (equalp (car extra) "permanentflags")
602
+		then (setf (mailbox-permanent-flags mb) 
603
+		       (kwd-intern-possible-list (cadr extra)))
604
+		else (po-condition :unknown-ok :server-string comment))))
605
+    (t (po-condition :unknown-untagged :server-string comment)))
606
+	     
607
+  )
608
+
609
+
610
+(defmethod begin-extended-results-sequence ((mb pop-mailbox))
611
+  (setf (state mb) 1))
612
+
613
+(defmethod get-extended-results-sequence ((mb pop-mailbox) buffer &key (start 0) (end (length buffer)))
614
+  (declare (optimize (speed 3) (safety 1)))
615
+  (let ((inpos start)
616
+	(outpos start)
617
+	(sock (post-office-socket mb))
618
+	ch
619
+	stop)
620
+    (macrolet ((add-to-buffer () 
621
+		 `(progn
622
+		    (setf (schar buffer outpos) ch)
623
+		    (incf outpos))))
624
+      (while (and (< inpos end) (/= (state mb) 4))
625
+	(setf stop (read-sequence buffer sock :start inpos :end end :partial-fill t))
626
+	(while (< inpos stop)
627
+	  (setf ch (schar buffer inpos))
628
+	  (if* (eq ch #\return)
629
+	     thenret			; ignore crs
630
+	     else (ecase (state mb)
631
+		    (1 (if* (eq ch #\.)	; at beginning of line
632
+			  then (setf (state mb) 2)
633
+			elseif (eq ch #\linefeed)
634
+			  then 
635
+			       (add-to-buffer) ; state stays at 1
636
+			  else 
637
+			       (setf (state mb) 3)
638
+			       (add-to-buffer)))
639
+		    (2			; seen first dot
640
+		     (if* (eq ch #\linefeed)
641
+			then		; end of results
642
+			     (setf (state mb) 4)
643
+			     (return) 
644
+			else 
645
+			     (setf (state mb) 3)
646
+			     (add-to-buffer))) ; normal reading
647
+		    (3			; middle of line
648
+		     (if* (eq ch #\linefeed)
649
+			then (setf (state mb) 1))
650
+		     (add-to-buffer))))
651
+	  (incf inpos))
652
+	(setf inpos outpos))
653
+      outpos)))
654
+
655
+(defmacro end-of-extended-results-p (mb)
656
+  `(= (state ,mb) 4))
657
+
658
+(defmethod end-extended-results-sequence ((mb pop-mailbox))
659
+  (declare (optimize (speed 3) (safety 1)))
660
+  (let ((buffer (make-string 4096)))
661
+    (until (end-of-extended-results-p mb)
662
+      (get-extended-results-sequence mb buffer)))
663
+  (setf (state mb) :invalid-state)
664
+  t)
665
+
666
+(defmacro with-extended-results-sequence ((mailbox) &body body)
667
+  (let ((mb (gensym)))
668
+    `(let ((,mb ,mailbox))
669
+       (begin-extended-results-sequence ,mb)
670
+       (unwind-protect
671
+	   (progn
672
+	     ,@body)
673
+	 ;; cleanup
674
+	 (end-extended-results-sequence ,mb)))))
675
+
676
+
677
+  
678
+
679
+(defun send-pop-command-get-results (pop command &optional extrap)
680
+  (declare (optimize (speed 3) (safety 1)))
681
+  ;; send the given command to the pop server
682
+  ;; if extrap is true and if the response is +ok, then data
683
+  ;;  will follow the command (up to and excluding the first line consisting 
684
+  ;;  of just a period)
685
+  ;; 
686
+  ;; if the pop server returns an error code we signal a lisp error.
687
+  ;; otherwise
688
+  ;; return
689
+  ;;  extrap is nil -- return the list of tokens on the line after +ok
690
+  ;;  extrap is true -- return the extra object (a big string)
691
+  ;;
692
+  (format (post-office-socket pop) "~a~a" command *crlf*)
693
+  (force-output (post-office-socket pop))
694
+  
695
+  (if* *debug-imap*
696
+     then (format t "~a~a" command *crlf*)
697
+	  (force-output t))
698
+
699
+  (multiple-value-bind (result parsed line)
700
+      (get-and-parse-from-pop-server pop)
701
+    (if* (not (eq result :ok))
702
+       then (po-error :error-response
703
+		      :server-string line))
704
+
705
+    (if* extrap
706
+       then ;; get the rest of the data
707
+	    ;; many but not all pop servers return the size of the data
708
+	    ;; after the +ok, so we use that to initially size the 
709
+	    ;; retreival buffer.
710
+	    (let* ((buf (get-line-buffer (+ (if* (fixnump (car parsed))
711
+					       then (car parsed) 
712
+					       else 2048 ; reasonable size
713
+						    )
714
+					    50)))
715
+		   (buflen (length buf))
716
+		   (pos 0))
717
+	      (with-extended-results-sequence (pop)
718
+		(until (end-of-extended-results-p pop)
719
+		  (if* (>= pos buflen)
720
+		     then    ;; grow buffer
721
+			  (if* (>= buflen (1- array-total-size-limit))
722
+			     then	; can't grow it any further
723
+				  (po-error
724
+				   :response-too-large
725
+				   :format-control
726
+				   "response from mail server is too large to hold in a lisp array"))
727
+			  (let ((new-buf (get-line-buffer (* buflen 2))))
728
+			    (init-line-buffer new-buf buf)
729
+			    (free-line-buffer buf)
730
+			    (setq buf new-buf)
731
+			    (setq buflen (length buf))))
732
+		  (setf pos (get-extended-results-sequence pop buf :start pos :end buflen))))
733
+	      (prog1 (subseq buf 0 pos)
734
+		(free-line-buffer buf)))
735
+       else parsed)))
736
+  
737
+
738
+
739
+
740
+(defun convert-flags-plist (plist)
741
+  ;; scan the plist looking for "flags" indicators and 
742
+  ;; turn value into a list of symbols rather than strings
743
+  (do ((xx plist (cddr xx)))
744
+      ((null xx) plist)
745
+    (if* (equalp "flags" (car xx))
746
+       then (setf (cadr xx) (mapcar #'kwd-intern (cadr xx))))))
747
+
748
+
749
+(defmethod select-mailbox ((mb imap-mailbox) name)
750
+  ;; select the given mailbox
751
+  (send-command-get-results mb
752
+			    (format nil "select ~a" name)
753
+			    #'handle-untagged-response
754
+			    #'(lambda (mb command count extra comment)
755
+				(declare (ignore mb count extra))
756
+				(if* (not (eq command :ok))
757
+				   then (po-error 
758
+					 :problem
759
+					 :format-control 
760
+					 "imap mailbox select failed"
761
+					 :server-string comment))))
762
+  (setf (mailbox-name mb) name)
763
+  t
764
+  )
765
+
766
+
767
+(defmethod fetch-letter ((mb imap-mailbox) number &key uid)
768
+  ;; return the whole letter
769
+  (fetch-field number "body[]"
770
+	       (fetch-parts mb number "body[]" :uid uid)
771
+	       :uid uid))
772
+
773
+
774
+(defmethod fetch-letter ((pb pop-mailbox) number &key uid)
775
+  (declare (ignore uid))
776
+  (send-pop-command-get-results pb 
777
+				(format nil "RETR ~d" number) 
778
+				t ; extra stuff
779
+				))
780
+
781
+(defmethod begin-fetch-letter-sequence ((mb imap-mailbox) number &key uid)
782
+  (setf (fetch-letter-offset mb) 0)
783
+  (setf (fetch-letter-number mb) number)
784
+  (setf (fetch-letter-uid mb) uid)
785
+  (setf (fetch-letter-finished mb) nil))
786
+
787
+
788
+(defmethod begin-fetch-letter-sequence ((mb pop-mailbox) number &key uid)
789
+  (declare (ignore uid))
790
+  (send-pop-command-get-results mb (format nil "RETR ~d" number))
791
+  (begin-extended-results-sequence mb))
792
+
793
+(defmethod fetch-letter-sequence ((mb imap-mailbox) buffer 
794
+				  &key (start 0) (end (length buffer)))
795
+  (let* ((num (fetch-letter-number mb))
796
+	 (offset (fetch-letter-offset mb))
797
+	 (uid (fetch-letter-uid mb))
798
+	 (buflen (- end start))
799
+	 (data (fetch-field num (format nil "body[]<~d>" offset) 
800
+			    (fetch-parts mb num 
801
+					 (format nil "body[]<~d.~d>" offset buflen)
802
+					 :uid uid)
803
+			    :uid uid))
804
+	 (datalen (length data)))
805
+
806
+    (setf (subseq buffer start end) data)
807
+    
808
+    (if* (and (> buflen 0) (= datalen 0))
809
+       then (setf (fetch-letter-finished mb) t))
810
+    
811
+    (setf (fetch-letter-offset mb) (+ offset buflen))
812
+    
813
+    (+ start datalen)))
814
+		       
815
+
816
+(defmethod fetch-letter-sequence ((mb pop-mailbox) buffer &key (start 0) (end (length buffer)))
817
+  (get-extended-results-sequence mb buffer :start start :end end))
818
+
819
+(defmethod end-fetch-letter-sequence ((mb imap-mailbox))
820
+  )
821
+
822
+(defmethod end-fetch-letter-sequence ((mb pop-mailbox))
823
+  (end-extended-results-sequence mb))
824
+
825
+(defmethod end-of-letter-p ((mb imap-mailbox))
826
+  (fetch-letter-finished mb))
827
+
828
+(defmethod end-of-letter-p ((mb pop-mailbox))
829
+  (end-of-extended-results-p mb))
830
+
831
+(defmacro with-fetch-letter-sequence ((mailbox &rest args) &body body)
832
+  (let ((mb (gensym)))
833
+    `(let ((,mb ,mailbox))
834
+       (begin-fetch-letter-sequence ,mb ,@args)
835
+       (unwind-protect
836
+	   (progn
837
+	     ,@body)
838
+	 ;; cleanup
839
+	 (end-fetch-letter-sequence ,mb)))))
840
+	    
841
+(defmethod fetch-parts ((mb imap-mailbox) number parts &key uid)
842
+  (let (res)
843
+    (send-command-get-results 
844
+     mb
845
+     (format nil "~afetch ~a ~a"
846
+	     (if* uid then "uid " else "")
847
+	     (message-set-string number)
848
+	     (or parts "body[]")
849
+	     )
850
+     #'(lambda (mb command count extra comment)
851
+	 (if* (eq command :fetch)
852
+	    then (push (list count (internalize-flags extra)) res)
853
+	    else (handle-untagged-response
854
+		  mb command count extra comment)))
855
+     #'(lambda (mb command count extra comment)
856
+	 (declare (ignore mb count extra))
857
+	 (if* (not (eq command :ok))
858
+	    then (po-error :problem
859
+			   :format-control "imap mailbox fetch failed"
860
+			   :server-string comment))))
861
+    res))
862
+
863
+		      
864
+(defun fetch-field (letter-number field-name info &key uid)
865
+  ;; given the information from a fetch-letter, return the 
866
+  ;; particular field for the particular letter
867
+  ;;
868
+  ;; info is as returned by fetch
869
+  ;; field-name is a string, case doesn't matter.
870
+  ;;
871
+  (dolist (item info)
872
+    ;; item is (messagenumber plist-info)
873
+    ;; the same messagenumber may appear in multiple items
874
+    (let (use-this)
875
+      (if* uid
876
+	 then ; uid appears as a property in the value, not
877
+	      ; as the top level message sequence number
878
+	      (do ((xx (cadr item) (cddr xx)))
879
+		  ((null xx))
880
+		(if* (equalp "uid" (car xx))
881
+		   then (if* (eql letter-number (cadr xx))
882
+			   then (return (setq use-this t))
883
+			   else (return))))
884
+	 else ; just a message sequence number
885
+	      (setq use-this (eql letter-number (car item))))
886
+    
887
+      (if* use-this
888
+	 then (do ((xx (cadr item) (cddr xx)))
889
+		  ((null xx))
890
+		(if* (equalp field-name (car xx))
891
+		   then (return-from fetch-field (cadr xx))))))))
892
+
893
+	 
894
+
895
+(defun internalize-flags (stuff)
896
+  ;; given a plist like object, look for items labelled "flags" and 
897
+  ;; convert the contents to internal flags objects
898
+  (do ((xx stuff (cddr xx)))
899
+      ((null xx))
900
+    (if* (equalp (car xx) "flags")
901
+       then ; we can end up with sublists of forms if we 
902
+	    ; do add-flags with a list of flags.  this seems like
903
+	    ; a bug in the imap server.. but we have to deal with it
904
+	      (setf (cadr xx) (kwd-intern-possible-list (cadr xx)))
905
+	      (return)))
906
+  
907
+  stuff)
908
+
909
+					
910
+
911
+
912
+(defmethod delete-letter ((mb imap-mailbox) messages &key (expunge t) uid)
913
+  ;; delete all the mesasges and do the expunge to make 
914
+  ;; it permanent if expunge is true
915
+  (alter-flags mb messages :add-flags :\\deleted :uid uid)
916
+  (if* expunge then (expunge-mailbox mb)))
917
+
918
+(defmethod delete-letter ((pb pop-mailbox) messages  &key (expunge nil) uid)
919
+  ;; delete all the messages.   We can't expunge without quitting so
920
+  ;; we don't expunge
921
+  (declare (ignore expunge uid))
922
+  
923
+  (if* (or (numberp messages) 
924
+	   (and (consp messages) (eq :seq (car messages))))
925
+     then (setq messages (list messages)))
926
+  
927
+  (if* (not (consp messages))
928
+     then (po-error :syntax-error
929
+		    :format-control "expect a mesage number or list of messages, not ~s"
930
+		 :format-arguments (list messages)))
931
+  
932
+  (dolist (message messages)
933
+    (if* (numberp message)
934
+       then (send-pop-command-get-results pb
935
+					  (format nil "DELE ~d" message))
936
+     elseif (and (consp message) (eq :seq (car message)))
937
+       then (do ((start (cadr message) (1+ start))
938
+		 (end (caddr message)))
939
+		((> start end))
940
+	      (send-pop-command-get-results pb
941
+					    (format nil "DELE ~d" start)))
942
+       else (po-error :syntax-error
943
+		      :format-control "bad message number ~s" 
944
+		      :format-arguments (list message)))))
945
+	    
946
+	    
947
+			    
948
+					
949
+
950
+(defmethod noop ((mb imap-mailbox))
951
+  ;; just poke the server... keeping it awake and checking for
952
+  ;; new letters
953
+  (send-command-get-results mb
954
+			    "noop"
955
+			    #'handle-untagged-response
956
+			    #'(lambda (mb command count extra comment)
957
+				(check-for-success
958
+				 mb command count extra
959
+				 comment
960
+				 "noop"))))
961
+
962
+
963
+(defmethod noop ((pb pop-mailbox))
964
+  ;; send the stat command instead so we can update the message count
965
+  (let ((res (send-pop-command-get-results pb "stat")))
966
+      (setf (mailbox-message-count pb) (car res)))
967
+  )
968
+
969
+
970
+(defmethod unique-id ((pb pop-mailbox) &optional message)
971
+  ;; if message is given, return the unique id of that
972
+  ;; message, 
973
+  ;; if message is not given then return a list of lists:
974
+  ;;  (message  unique-id)
975
+  ;; for all messages not marked as deleted
976
+  ;;
977
+  (if* message
978
+     then (let ((res (send-pop-command-get-results pb
979
+						   (format nil 
980
+							   "UIDL ~d" 
981
+							   message))))
982
+	    (cadr res))
983
+     else ; get all of them
984
+	  (let* ((res (send-pop-command-get-results pb "UIDL" t))
985
+		 (end (length res))
986
+		 kind
987
+		 mnum
988
+		 mid
989
+		 (next 0))
990
+		      
991
+		
992
+	    (let ((coll))
993
+	      (loop
994
+		(multiple-value-setq (kind mnum next) 
995
+		  (get-next-token res next end))
996
+		
997
+		(if* (eq :eof kind) then (return))
998
+		
999
+		(if* (not (eq :number kind))
1000
+		   then ; hmm. bogus
1001
+			(po-error :unexpected
1002
+				  :format-control "uidl returned illegal message number in ~s"
1003
+				  :format-arguments (list res)))
1004
+		
1005
+		; now get message id
1006
+		
1007
+		(multiple-value-setq (kind mid next)
1008
+		    (get-next-token res next end))
1009
+		
1010
+		(if* (eq :number kind)
1011
+		   then ; looked like a number to the tokenizer,
1012
+			; make it a string to be consistent
1013
+			(setq mid (format nil "~d" mid))
1014
+		 elseif (not (eq :string kind))
1015
+		   then ; didn't find the uid
1016
+			(po-error :unexpected
1017
+				  :format-control "uidl returned illegal message id in ~s"
1018
+				  :format-arguments (list res)))
1019
+		
1020
+		(push (list mnum mid) coll))
1021
+	      
1022
+	      (nreverse coll)))))
1023
+
1024
+(defmethod top-lines ((pb pop-mailbox) message lines)
1025
+  ;; return the header and the given number of top lines of the message
1026
+  
1027
+  (let ((res (send-pop-command-get-results pb
1028
+					   (format nil 
1029
+						   "TOP ~d ~d"
1030
+						   message
1031
+						   lines)
1032
+					   t ; extra
1033
+					   )))
1034
+    res))
1035
+			     
1036
+			
1037
+
1038
+
1039
+(defmethod reset-mailbox ((pb pop-mailbox))
1040
+  ;; undo's deletes
1041
+  (send-pop-command-get-results pb "RSET")
1042
+  )
1043
+						   
1044
+
1045
+
1046
+(defun check-for-success (mb command count extra comment command-string )
1047
+  (declare (ignore mb count extra))
1048
+  (if* (not (eq command :ok))
1049
+     then (po-error :error-response
1050
+		    :format-control "imap ~a failed" 
1051
+		    :format-arguments (list command-string)
1052
+		    :server-string comment)))
1053
+
1054
+  
1055
+			    
1056
+
1057
+
1058
+(defmethod mailbox-list ((mb imap-mailbox) &key (reference "") (pattern ""))
1059
+  ;; return a list of mailbox names with respect to a given
1060
+  (let (res)
1061
+    (send-command-get-results mb
1062
+			      (format nil "list ~s ~s" reference pattern)
1063
+			      #'(lambda (mb command count extra comment)
1064
+				  (if* (eq command :list)
1065
+				     then (push extra res)
1066
+				     else (handle-untagged-response
1067
+					   mb command count extra
1068
+					   comment)))
1069
+			      #'(lambda (mb command count extra comment)
1070
+				  (check-for-success 
1071
+				   mb command count extra 
1072
+				   comment "list")))
1073
+    
1074
+    ;; the car of each list is a set of keywords, make that so
1075
+    (dolist (rr res)
1076
+      (setf (car rr) (mapcar #'kwd-intern (car rr))))
1077
+    
1078
+    res
1079
+				
1080
+  
1081
+    ))
1082
+
1083
+
1084
+(defmethod create-mailbox ((mb imap-mailbox) mailbox-name)
1085
+  ;; create a mailbox name of the given name.
1086
+  ;; use mailbox-separator if you want to create a hierarchy
1087
+  (send-command-get-results mb
1088
+			    (format nil "create ~s" mailbox-name)
1089
+			    #'handle-untagged-response
1090
+			    #'(lambda (mb command count extra comment)
1091
+				  (check-for-success 
1092
+				   mb command count extra 
1093
+				   comment "create")))
1094
+  t)
1095
+
1096
+
1097
+(defmethod delete-mailbox ((mb imap-mailbox) mailbox-name)
1098
+  ;; create a mailbox name of the given name.
1099
+  ;; use mailbox-separator if you want to create a hierarchy
1100
+  (send-command-get-results mb
1101
+			    (format nil "delete ~s" mailbox-name)
1102
+			    #'handle-untagged-response
1103
+			    #'(lambda (mb command count extra comment)
1104
+				  (check-for-success 
1105
+				   mb command count extra 
1106
+				   comment "delete"))))
1107
+
1108
+(defmethod rename-mailbox ((mb imap-mailbox) old-mailbox-name new-mailbox-name)
1109
+  ;; create a mailbox name of the given name.
1110
+  ;; use mailbox-separator if you want to create a hierarchy
1111
+  (send-command-get-results mb
1112
+			    (format nil "rename ~s ~s" 
1113
+				    old-mailbox-name
1114
+				    new-mailbox-name)
1115
+			    #'handle-untagged-response
1116
+			    #'(lambda (mb command count extra comment)
1117
+				  (check-for-success 
1118
+				   mb command count extra 
1119
+				   comment
1120
+				   "rename"))))
1121
+
1122
+
1123
+
1124
+(defmethod alter-flags ((mb imap-mailbox)
1125
+			messages &key (flags nil flags-p) 
1126
+				      add-flags remove-flags
1127
+				      silent uid)
1128
+  ;;
1129
+  ;; change the flags using the store command
1130
+  ;;
1131
+  (let (cmd val res)
1132
+    (if* flags-p
1133
+       then (setq cmd "flags" val flags)
1134
+     elseif add-flags
1135
+       then (setq cmd "+flags" val add-flags)
1136
+     elseif remove-flags
1137
+       then (setq cmd "-flags" val remove-flags)
1138
+       else (return-from alter-flags nil))
1139
+    
1140
+    (if* (atom val) then (setq val (list val)))
1141
+    
1142
+    (send-command-get-results mb
1143
+			      (format nil "~astore ~a ~a~a ~a"
1144
+				      (if* uid then "uid " else "")
1145
+				      (message-set-string messages)
1146
+				      cmd
1147
+				      (if* silent 
1148
+					 then ".silent"
1149
+					 else "")
1150
+				      (if* val
1151
+					 thenret
1152
+					 else "()"))
1153
+			      #'(lambda (mb command count extra comment)
1154
+				  (if* (eq command :fetch)
1155
+				     then (push (list count 
1156
+						      (convert-flags-plist
1157
+						       extra))
1158
+						res)
1159
+				     else (handle-untagged-response
1160
+					   mb command count extra
1161
+					   comment)))
1162
+			      
1163
+			      #'(lambda (mb command count extra comment)
1164
+				  (check-for-success 
1165
+				   mb command count extra 
1166
+				   comment "store")))
1167
+    res))
1168
+
1169
+
1170
+(defun message-set-string (messages)
1171
+  ;; return a string that describes the messages which may be a
1172
+  ;; single number or a sequence of numbers
1173
+  
1174
+  (if* (atom messages)
1175
+     then (format nil "~a" messages)
1176
+     else (if* (and (consp messages)
1177
+		    (eq :seq (car messages)))
1178
+	     then (format nil "~a:~a" (cadr messages) (caddr messages))
1179
+	     else (let ((str (make-string-output-stream))
1180
+			(precomma nil))
1181
+		    (dolist (msg messages)
1182
+		      (if* precomma then (format str ","))
1183
+		      (if* (atom msg)
1184
+			 then (format str "~a" msg)
1185
+		       elseif (eq :seq (car msg))
1186
+			 then (format str
1187
+				      "~a:~a" (cadr msg) (caddr msg))
1188
+			 else (po-error :syntax-error
1189
+					:format-control "bad message list ~s" 
1190
+					:format-arguments (list msg)))
1191
+		      (setq precomma t))
1192
+		    (get-output-stream-string str)))))
1193
+				   
1194
+				   
1195
+				   
1196
+			      
1197
+					      
1198
+     
1199
+(defmethod expunge-mailbox ((mb imap-mailbox))
1200
+  ;; remove messages marked as deleted
1201
+  (let (res)
1202
+    (send-command-get-results mb
1203
+			      "expunge"
1204
+			      #'(lambda (mb command count extra
1205
+					 comment)
1206
+				  (if* (eq command :expunge)
1207
+				     then (push count res)
1208
+				     else (handle-untagged-response
1209
+					   mb command count extra
1210
+					   comment)))
1211
+			      #'(lambda (mb command count extra comment)
1212
+				  (check-for-success 
1213
+				   mb command count extra 
1214
+				   comment "expunge")))
1215
+    (nreverse res)))
1216
+    
1217
+    
1218
+	    
1219
+(defmethod close-mailbox ((mb imap-mailbox))
1220
+  ;; remove messages marked as deleted
1221
+  (send-command-get-results mb
1222
+			    "close"
1223
+			    #'handle-untagged-response
1224
+			      
1225
+			    #'(lambda (mb command count extra comment)
1226
+				(check-for-success 
1227
+				 mb command count extra 
1228
+				 comment "close")))
1229
+  t)
1230
+  
1231
+
1232
+
1233
+(defmethod copy-to-mailbox ((mb imap-mailbox) message-list destination
1234
+			    &key uid)
1235
+  (send-command-get-results mb
1236
+			    (format nil "~acopy ~a ~s"
1237
+				    (if* uid then "uid " else "")
1238
+				    (message-set-string message-list)
1239
+				    destination)
1240
+			    #'handle-untagged-response
1241
+			    #'(lambda (mb command count extra comment)
1242
+				(check-for-success 
1243
+				 mb command count extra 
1244
+				 comment "copy")))
1245
+  t)
1246
+
1247
+
1248
+;; search command
1249
+
1250
+(defmethod search-mailbox ((mb imap-mailbox) search-expression &key uid)
1251
+  (let (res)
1252
+    (send-command-get-results mb
1253
+			      (format nil "~asearch ~a" 
1254
+				      (if* uid then "uid " else "")
1255
+				      (build-search-string search-expression))
1256
+			      #'(lambda (mb command count extra comment)
1257
+				  (if* (eq command :search)
1258
+				     then (setq res (append res extra))
1259
+				     else (handle-untagged-response
1260
+					   mb command count extra
1261
+					   comment)))
1262
+			      #'(lambda (mb command count extra comment)
1263
+				  (check-for-success 
1264
+				   mb command count extra 
1265
+				   comment "search")))
1266
+    res))
1267
+    
1268
+		       
1269
+(defmacro defsearchop (name &rest operands)
1270
+  (if* (null operands)
1271
+     then `(setf (get ',name 'imap-search-no-args) t)
1272
+     else `(setf (get ',name 'imap-search-args) ',operands)))
1273
+
1274
+(defsearchop :all)
1275
+(defsearchop :answered)
1276
+(defsearchop :bcc :str)
1277
+(defsearchop :before :date)
1278
+(defsearchop :body :str)
1279
+(defsearchop :cc :str)
1280
+(defsearchop :deleted)
1281
+(defsearchop :draft)
1282
+(defsearchop :flagged)
1283
+(defsearchop :from :str)
1284
+(defsearchop :header :str :str)
1285
+(defsearchop :keyword :flag)
1286
+(defsearchop :larger :number)
1287
+(defsearchop :new)
1288
+(defsearchop :old)
1289
+(defsearchop :on :date)
1290
+(defsearchop :recent)
1291
+(defsearchop :seen)
1292
+(defsearchop :sentbefore :date)
1293
+(defsearchop :senton :date)
1294
+(defsearchop :sentsince :date)
1295
+(defsearchop :since :date)
1296
+(defsearchop :smaller :number)
1297
+(defsearchop :subject :str)
1298
+(defsearchop :text :str)
1299
+(defsearchop :to :str)
1300
+(defsearchop :uid :messageset)
1301
+(defsearchop :unanswered)
1302
+(defsearchop :undeleted)
1303
+(defsearchop :undraft)
1304
+(defsearchop :unflagged)
1305
+(defsearchop :unkeyword :flag)
1306
+(defsearchop :unseen)
1307
+
1308
+
1309
+
1310
+(defun build-search-string (search)
1311
+  ;; take the lisp search form and turn it into a string that can be
1312
+  ;; passed to imap
1313
+
1314
+  (if* (null search)
1315
+     then ""
1316
+     else (let ((str (make-string-output-stream)))
1317
+	    (bss-int search str)
1318
+	    (get-output-stream-string str))))
1319
+
1320
+(defun bss-int (search str)
1321
+  ;;* it turns out that imap (on linux) is very picky about spaces....
1322
+  ;; any extra whitespace will result in failed searches
1323
+  ;;
1324
+  (labels ((and-ify (srch str)
1325
+	     (let ((spaceout nil))
1326
+	       (dolist (xx srch) 
1327
+		 (if* spaceout then (format str " "))
1328
+		 (bss-int xx str)
1329
+		 (setq spaceout t))))
1330
+	   (or-ify (srch str)
1331
+	     ; only binary or allowed in imap but we support n-ary 
1332
+	     ; or in this interface
1333
+	     (if* (null (cdr srch))
1334
+		then (bss-int (car srch) str)
1335
+	      elseif (cddr srch)
1336
+		then ; over two clauses
1337
+		     (format str "or (")
1338
+		     (bss-int (car srch) str)
1339
+		     (format str  ") (")
1340
+		     (or-ify (cdr srch) str)
1341
+		     (format str ")")
1342
+		else ; 2 args
1343
+		     (format str "or (" )
1344
+		     (bss-int (car srch) str)
1345
+		     (format str ") (")
1346
+		     (bss-int (cadr srch) str)
1347
+		     (format str ")")))
1348
+	   (set-ify (srch str)
1349
+	     ;; a sequence of messages
1350
+	     (do* ((xsrch srch (cdr xsrch))
1351
+		   (val (car xsrch) (car xsrch)))
1352
+		 ((null xsrch))
1353
+	       (if* (integerp val)
1354
+		  then (format str "~s" val)
1355
+		elseif (and (consp val) 
1356
+			    (eq :seq (car val))
1357
+			    (eq 3 (length val)))
1358
+		  then (format str "~s:~s" (cadr val) (caddr val))
1359
+		  else (po-error :syntax-error
1360
+				 :format-control "illegal set format ~s" 
1361
+				 :format-arguments (list val)))
1362
+	       (if* (cdr xsrch) then (format str ","))))
1363
+	   (arg-process (str args arginfo)
1364
+	     ;; process and print each arg to str
1365
+	     ;; assert (length of args and arginfo are the same)
1366
+	     (do* ((x-args args (cdr x-args))
1367
+		   (val (car x-args) (car x-args))
1368
+		   (x-arginfo arginfo (cdr x-arginfo)))
1369
+		 ((null x-args))
1370
+	       (ecase (car x-arginfo)
1371
+		 (:str
1372
+		  ; print it as a string
1373
+		  (format str " \"~a\"" (car x-args)))
1374
+		 (:date
1375
+		  
1376
+		  (if* (integerp val)
1377
+		     then (setq val (universal-time-to-rfc822-date
1378
+				     val))
1379
+		   elseif (not (stringp val))
1380
+		     then (po-error :syntax-error
1381
+				    :format-control "illegal value for date search ~s"
1382
+				    :format-arguments (list val)))
1383
+		  ;; val is now a string
1384
+		  (format str " ~s" val))
1385
+		 (:number
1386
+		  
1387
+		  (if* (not (integerp val))
1388
+		     then (po-error :syntax-error
1389
+				    :format-control "illegal value for number in search ~s" 
1390
+				    :format-arguments (list val)))
1391
+		  (format str " ~s" val))
1392
+		 (:flag
1393
+		  
1394
+		  ;; should be a symbol in the kwd package
1395
+		  (setq val (string val))
1396
+		  (format str " ~s" val))
1397
+		 (:messageset
1398
+		  (if* (numberp val) 
1399
+		     then (format str " ~s" val)
1400
+		   elseif (consp val)
1401
+		     then (set-ify val str)
1402
+		     else (po-error :syntax-error
1403
+				    :format-control "illegal message set ~s" 
1404
+				    :format-arguments (list val))))
1405
+		  
1406
+		 ))))
1407
+    
1408
+    (if* (symbolp search)
1409
+       then (if* (get search 'imap-search-no-args)
1410
+	       then (format str "~a"  (string-upcase
1411
+				       (string search)))
1412
+	       else (po-error :syntax-error
1413
+			      :format-control "illegal search word: ~s" 
1414
+			      :format-arguments (list search)))
1415
+     elseif (consp search)
1416
+       then (case (car search)
1417
+	      (and (if* (null (cdr search))
1418
+		      then (bss-int :all str)
1419
+		    elseif (null (cddr search))
1420
+		      then (bss-int (cadr search) str)
1421
+		      else (and-ify (cdr search)  str)))
1422
+	      (or  (if* (null (cdr search))
1423
+		      then (bss-int :all str)
1424
+		    elseif (null (cddr search))
1425
+		      then (bss-int (cadr search) str)
1426
+		      else (or-ify (cdr search)  str)))
1427
+	      (not (if* (not (eql (length search) 2))
1428
+		      then (po-error :syntax-error 
1429
+				     :format-control "not takes one argument: ~s" 
1430
+				     :format-arguments (list search)))
1431
+		   (format str "not (" )
1432
+		   (bss-int (cadr search) str)
1433
+		   (format str ")"))
1434
+	      (:seq
1435
+	       (set-ify (list search) str))
1436
+	      (t (let (arginfo) 
1437
+		   (if* (and (symbolp (car search))
1438
+			     (setq arginfo (get (car search)
1439
+						'imap-search-args)))
1440
+		      then 
1441
+			   (format str "~a" (string-upcase
1442
+					     (string (car search))))
1443
+			   (if* (not (equal (length (cdr search))
1444
+					    (length arginfo)))
1445
+			      then (po-error :syntax-error 
1446
+					     :format-control "wrong number of arguments to ~s" 
1447
+					     :format-arguments search))
1448
+			   
1449
+			   (arg-process str (cdr search) arginfo)
1450
+			   
1451
+		    elseif (integerp (car search))
1452
+		      then (set-ify search str)
1453
+		      else (po-error :syntax-error 
1454
+				     :format-control "Illegal form ~s in search string" 
1455
+				     :format-arguments (list search))))))
1456
+     elseif (integerp search)
1457
+       then ;  a message number
1458
+	    (format str "~s" search)
1459
+       else (po-error :syntax-error
1460
+		      :format-control "Illegal form ~s in search string" 
1461
+		      :format-arguments (list search)))))
1462
+
1463
+
1464
+
1465
+
1466
+
1467
+(defun parse-mail-header (text)  
1468
+  ;; given the partial text of a mail message that includes
1469
+  ;; at least the header part, return an assoc list of
1470
+  ;; (header . content)  items
1471
+  ;; Note that the header is string with most likely mixed case names
1472
+  ;; as it's conventional to capitalize header names.
1473
+  (let ((next 0)
1474
+	(end (length text))
1475
+	header
1476
+	value
1477
+	kind
1478
+	headers)
1479
+    (labels ((next-header-line ()
1480
+	       ;; find the next header line return
1481
+	       ;; :eof - no more
1482
+	       ;; :start - beginning of header value, header and
1483
+	       ;;	         value set
1484
+	       ;; :continue - continuation of previous header line
1485
+	     
1486
+		       
1487
+	       (let ((state 1)
1488
+		     beginv  ; charpos beginning value
1489
+		     beginh  ; charpos beginning header
1490
+		     ch
1491
+		     )
1492
+		 (tagbody again
1493
+		   
1494
+		   (return-from next-header-line
1495
+		     
1496
+		     (loop  ; for each character
1497
+		       
1498
+		       (if* (>= next end)
1499
+			  then (return :eof))
1500
+		 
1501
+		       (setq ch (char text next))
1502
+		       (if* (eq ch #\return) 
1503
+			  thenret  ; ignore return, (handle following linefeed)
1504
+			  else (case state
1505
+				 (1 ; no characters seen
1506
+				  (if* (eq ch #\linefeed)
1507
+				     then (incf next)
1508
+					  (return :eof)
1509
+				   elseif (member ch
1510
+						  '(#\space
1511
+						    #\tab))
1512
+				     then ; continuation
1513
+					  (setq state 2)
1514
+				     else (setq beginh next)
1515
+					  (setq state 3)
1516
+					  ))
1517
+				 (2 ; looking for first non blank in value
1518
+				  (if* (eq ch #\linefeed)
1519
+				     then ; empty continuation line, ignore
1520
+					  (incf next)
1521
+					  (if* header
1522
+					     then ; header and no value
1523
+						  (setq value "")
1524
+						  (return :start))
1525
+					  (setq state 1)
1526
+					  (go again)
1527
+				   elseif (not (member ch
1528
+						       (member ch
1529
+							       '(#\space
1530
+								 #\tab))))
1531
+				     then ; begin value part
1532
+					  (setq beginv next)
1533
+					  (setq state 4)))
1534
+				 (3 ; reading the header
1535
+				  (if* (eq ch #\linefeed)
1536
+				     then ; bogus header line, ignore
1537
+					  (setq state 1)
1538
+					  (go again)
1539
+				   elseif (eq ch #\:)
1540
+				     then (setq header
1541
+					    (subseq text beginh next))
1542
+					  (setq state 2)))
1543
+				 (4 ; looking for the end of the value
1544
+				  (if* (eq ch #\linefeed)
1545
+				     then (setq value
1546
+					    (subseq text beginv 
1547
+						    (if* (eq #\return
1548
+							     (char text
1549
+								   (1- next)))
1550
+						       then (1- next)
1551
+						       else next)))
1552
+					  (incf next)
1553
+					  (return (if* header
1554
+						     then :start
1555
+						     else :continue))))))
1556
+		       (incf next)))))))
1557
+					 
1558
+	       
1559
+    
1560
+      (loop ; for each header line
1561
+	(setq header nil)
1562
+	(if* (eq :eof (setq kind (next-header-line)))
1563
+	   then (return))
1564
+	(case kind
1565
+	  (:start (push (cons header value) headers))
1566
+	  (:continue
1567
+	   (if* headers
1568
+	      then ; append to previous one
1569
+		   (setf (cdr (car headers))
1570
+		     (concatenate 'string (cdr (car headers))
1571
+				  " " 
1572
+				  value)))))))
1573
+    (values headers
1574
+	    (subseq text next end))))
1575
+
1576
+
1577
+(defun make-envelope-from-text (text)
1578
+  ;; given at least the headers part of a message return
1579
+  ;; an envelope structure containing the contents
1580
+  ;; This is useful for parsing the headers of things returned by
1581
+  ;; a pop server
1582
+  ;;
1583
+  (let ((headers (parse-mail-header text)))
1584
+  
1585
+    (make-envelope
1586
+     :date     (cdr (assoc "date" headers :test #'equalp))
1587
+     :subject  (cdr (assoc "subject" headers :test #'equalp))
1588
+     :from     (cdr (assoc "from" headers :test #'equalp))
1589
+     :sender   (cdr (assoc "sender" headers :test #'equalp))
1590
+     :reply-to (cdr (assoc "reply-to" headers :test #'equalp))
1591
+     :to       (cdr (assoc "to" headers :test #'equalp))
1592
+     :cc       (cdr (assoc "cc" headers :test #'equalp))
1593
+     :bcc      (cdr (assoc "bcc" headers :test #'equalp))
1594
+     :in-reply-to (cdr (assoc "in-reply-to" headers :test #'equalp))
1595
+     :message-id (cdr (assoc "message-id" headers :test #'equalp))
1596
+     )))
1597
+
1598
+		  
1599
+	      
1600
+				 
1601
+	      
1602
+
1603
+
1604
+
1605
+
1606
+    
1607
+(defmethod get-and-parse-from-imap-server ((mb imap-mailbox))
1608
+  ;; read the next line and parse it
1609
+  ;;
1610
+  ;;
1611
+  (multiple-value-bind (line count)
1612
+      (get-line-from-server mb)
1613
+    (if* *debug-imap* 
1614
+       then (format t "from server: ")
1615
+	    (dotimes (i count)(write-char (schar line i)))
1616
+	    (terpri)
1617
+	    (force-output))
1618
+    
1619
+    (parse-imap-response line count)
1620
+    ))
1621
+
1622
+
1623
+
1624
+(defmethod get-and-parse-from-pop-server ((mb pop-mailbox))
1625
+  ;; read the next line from the pop server
1626
+  ;;
1627
+  ;; return 3 values:
1628
+  ;;   :ok or :error 
1629
+  ;;   a list of rest of the tokens on the line
1630
+  ;;   the whole line after the +ok or -err
1631
+
1632
+  (multiple-value-bind (line count)
1633
+      (get-line-from-server mb)
1634
+    
1635
+    (if* *debug-imap* 
1636
+       then (format t "from server: " count)
1637
+	    (dotimes (i count)(write-char (schar line i)))
1638
+	    (terpri))
1639
+    
1640
+    (parse-pop-response line count)))
1641
+
1642
+  
1643
+  
1644
+;; Parse and return the data from each line
1645
+;; values returned
1646
+;;  tag -- either a string or the symbol :untagged
1647
+;;  command -- a keyword symbol naming the command, like :ok
1648
+;;  count -- a number which preceeded the command, or nil if
1649
+;;	     there wasn't a command
1650
+;;  bracketted - a list of objects found in []'s after the command
1651
+;;            or in ()'s after the command  or sometimes just 
1652
+;;	      out in the open after the command (like the search)
1653
+;;  comment  -- the whole of the part after the command
1654
+;;
1655
+(defun parse-imap-response (line end)
1656
+  (let (kind value next
1657
+	tag count command extra-data
1658
+	comment)
1659
+    
1660
+    ;; get tag
1661
+    (multiple-value-setq (kind value next)
1662
+      (get-next-token line 0 end))
1663
+    
1664
+    (case kind
1665
+      (:string (setq tag (if* (equal value "*")
1666
+			    then :untagged
1667
+			    else value)))
1668
+      (t (po-error :unexpected
1669
+		   :format-control "Illegal tag on response: ~s" 
1670
+		   :format-arguments (list (subseq line 0 count))
1671
+		   :server-string (subseq line 0 end)
1672
+		   )))
1673
+      
1674
+    ;; get command
1675
+    (multiple-value-setq (kind value next)
1676
+      (get-next-token line next end))
1677
+      
1678
+    (tagbody again
1679
+      (case kind
1680
+	(:number (setq count value)
1681
+		 (multiple-value-setq (kind value next)
1682
+		   (get-next-token line next end))
1683
+		 (go again))
1684
+	(:string (setq command (kwd-intern value)))
1685
+	(t (po-error :unexpected 
1686
+		     :format-control "Illegal command on response: ~s" 
1687
+		     :format-arguments (list (subseq line 0 count))
1688
+		     :server-string (subseq line 0 end)))))
1689
+
1690
+    (setq comment (subseq line next end))
1691
+    
1692
+    ;; now the part after the command... this gets tricky
1693
+    (loop
1694
+      (multiple-value-setq (kind value next)
1695
+	(get-next-token line next end))
1696
+      
1697
+      (case kind
1698
+	((:lbracket :lparen)
1699
+	 (multiple-value-setq (kind value next)
1700
+	   (get-next-sexpr line (1- next) end))
1701
+	 (case kind
1702
+	   (:sexpr (push value extra-data))
1703
+	   (t (po-error :syntax-error :format-control "bad sexpr form"))))
1704
+	(:eof (return nil))
1705
+	((:number :string :nil) (push value extra-data))
1706
+	(t  ; should never happen
1707
+	 (return)))
1708
+      
1709
+      (if* (not (member command '(:list :search) :test #'eq))
1710
+	 then ; only one item returned
1711
+	      (setq extra-data (car extra-data))
1712
+	      (return)))
1713
+
1714
+    (if* (member command '(:list :search) :test #'eq)
1715
+       then (setq extra-data (nreverse extra-data)))
1716
+    
1717
+      
1718
+    (values tag command count extra-data comment)))
1719
+      
1720
+
1721
+
1722
+(defun get-next-sexpr (line start end)
1723
+  ;; read a whole s-expression
1724
+  ;; return 3 values
1725
+  ;;   kind -- :sexpr  or :rparen or :rbracket
1726
+  ;;   value - the sexpr value
1727
+  ;;   next  - next charpos to scan
1728
+  ;;  
1729
+  (let ( kind value next)
1730
+    (multiple-value-setq (kind value next) (get-next-token line start end))
1731
+    
1732
+    (case kind
1733
+      ((:string :number :nil)
1734
+       (values :sexpr value next))
1735
+      (:eof (po-error :syntax-error 
1736
+		      :format-control "eof inside sexpr"))
1737
+      ((:lbracket :lparen)
1738
+       (let (res)
1739
+	 (loop
1740
+	   (multiple-value-setq (kind value next)
1741
+	     (get-next-sexpr line next end))
1742
+	   (case kind
1743
+	     (:sexpr (push value res))
1744
+	     ((:rparen :rbracket) 
1745
+	      (return (values :sexpr (nreverse res) next)))
1746
+	     (t (po-error :syntax-error
1747
+			  :format-control "bad sexpression"))))))
1748
+      ((:rbracket :rparen)
1749
+       (values kind nil next))
1750
+      (t (po-error :syntax-error
1751
+		   :format-control "bad sexpression")))))
1752
+
1753
+
1754
+(defun parse-pop-response (line end)
1755
+  ;; return 3 values:
1756
+  ;;   :ok or :error 
1757
+  ;;   a list of rest of the tokens on the line, the tokens
1758
+  ;;	 being either strings or integers
1759
+  ;;   the whole line after the +ok or -err
1760
+  ;;
1761
+  (let (res lineres result)
1762
+    (multiple-value-bind (kind value next)
1763
+	(get-next-token line 0 end)
1764
+    
1765
+      (case kind
1766
+	(:string (setq result (if* (equal "+OK" value) 
1767
+				 then :ok
1768
+				 else :error)))
1769
+	(t (po-error :unexpected
1770
+		     :format-control "bad response from server" 
1771
+		     :server-string (subseq line 0 end))))
1772
+    
1773
+      (setq lineres (subseq line next end))
1774
+
1775
+      (loop
1776
+	(multiple-value-setq (kind value next)
1777
+	  (get-next-token line next end))
1778
+	
1779
+	(case kind
1780
+	  (:eof (return))
1781
+	  ((:string :number) (push value res))))
1782
+      
1783
+      (values result (nreverse res) lineres))))
1784
+    
1785
+	
1786
+    
1787
+    
1788
+    
1789
+    
1790
+      
1791
+      
1792
+			 
1793
+    
1794
+(defparameter *char-to-kind*
1795
+    (let ((arr (make-array 256 :initial-element nil)))
1796
+      
1797
+      (do ((i #.(char-code #\0) (1+ i)))
1798
+	  ((> i #.(char-code #\9)))
1799
+	(setf (aref arr i) :number))
1800
+      
1801
+      (setf (aref arr #.(char-code #\space)) :space)
1802
+      (setf (aref arr #.(char-code #\tab)) :space)
1803
+      (setf (aref arr #.(char-code #\return)) :space)
1804
+      (setf (aref arr #.(char-code #\linefeed)) :space)
1805
+      
1806
+      (setf (aref arr #.(char-code #\[)) :lbracket)
1807
+      (setf (aref arr #.(char-code #\])) :rbracket)
1808
+      (setf (aref arr #.(char-code #\()) :lparen)
1809
+      (setf (aref arr #.(char-code #\))) :rparen)
1810
+      (setf (aref arr #.(char-code #\")) :dquote)
1811
+      
1812
+      (setf (aref arr #.(char-code #\^b)) :big-string) ; our own invention
1813
+      
1814
+      arr))
1815
+	
1816
+      
1817
+(defun get-next-token (line start end)
1818
+  ;; scan past whitespace for the next token
1819
+  ;; return three values:
1820
+  ;;  kind:  :string , :number, :eof, :lbracket, :rbracket,
1821
+  ;;		:lparen, :rparen
1822
+  ;;  value:  the value, either a string or number or nil
1823
+  ;;  next:   the character pos to start scanning for the next token
1824
+  ;;
1825
+  (let (ch chkind colstart (count 0) (state :looking)
1826
+	collector right-bracket-is-normal) 
1827
+    (loop 
1828
+      ; pick up the next character
1829
+      (if* (>= start end)
1830
+	 then (if* (eq state :looking)
1831
+		 then (return (values :eof nil start))
1832
+		 else (setq ch #\space))
1833
+	 else (setq ch (schar line start)))
1834
+      
1835
+      (setq chkind (aref *char-to-kind* (char-code ch)))
1836
+      
1837
+      (case state
1838
+	(:looking
1839
+	 (case chkind
1840
+	   (:space nil)
1841
+	   (:number (setq state :number)
1842
+		    (setq colstart start)
1843
+		    (setq count (- (char-code ch) #.(char-code #\0))))
1844
+	   ((:lbracket :lparen :rbracket :rparen)
1845
+	    (return (values chkind nil (1+ start))))
1846
+	   (:dquote
1847
+	    (setq collector (make-array 10 
1848
+					:element-type 'character
1849
+					:adjustable t 
1850
+					:fill-pointer 0))
1851
+	    (setq state :qstring))
1852
+	   (:big-string
1853
+	    (setq colstart (1+ start))
1854
+	    (setq state :big-string))
1855
+	   (t (setq colstart start)
1856
+	      (setq state :literal))))
1857
+	(:number
1858
+	 (case chkind
1859
+	   ((:space :lbracket :lparen :rbracket :rparen 
1860
+	     :dquote) ; end of number
1861
+	    (return (values :number count  start)))
1862
+	   (:number ; more number
1863
+	    (setq count (+ (* count 10) 
1864
+			   (- (char-code ch) #.(char-code #\0)))))
1865
+	   (t ; turn into an literal
1866
+	    (setq state :literal))))
1867
+	(:literal
1868
+	 (case chkind
1869
+	   ((:space :rbracket :lparen :rparen :dquote) ; end of literal
1870
+	    (if* (and (eq chkind :rbracket)
1871
+		      right-bracket-is-normal)
1872
+	       then nil ; don't stop now
1873
+	       else (let ((seq (subseq line colstart start)))
1874
+		      (if* (equal "NIL" seq)
1875
+			 then (return (values :nil
1876
+					      nil
1877
+					      start))
1878
+			 else (return (values :string 
1879
+					      seq
1880
+					      start))))))
1881
+	   (t (if* (eq chkind :lbracket)
1882
+		 then ; imbedded left bracket so right bracket isn't
1883
+		      ; a break char
1884
+		      (setq right-bracket-is-normal t))
1885
+	      nil)))
1886
+	(:qstring
1887
+	 ;; quoted string
1888
+	 ; (format t "start is ~s  kind is ~s~%" start chkind)
1889
+	 (case chkind
1890
+	   (:dquote
1891
+	    ;; end of string
1892
+	    (return (values :string collector (1+ start))))
1893
+	   (t (if* (eq ch #\\)
1894
+		 then ; escaping the next character
1895
+		      (incf start)
1896
+		      (if* (>= start end)
1897
+			 then (po-error :unexpected
1898
+					:format-control "eof in string returned"))
1899
+		      (setq ch (schar line start)))
1900
+	      (vector-push-extend ch collector)
1901
+	      
1902
+	      (if* (>= start end)
1903
+		 then ; we overran the end of the input
1904
+		      (po-error :unexpected
1905
+				:format-control "eof in string returned")))))
1906
+	(:big-string
1907
+	 ;; super string... just a block of data
1908
+	 ; (format t "start is ~s  kind is ~s~%" start chkind)
1909
+	 (case chkind
1910
+	   (:big-string
1911
+	    ;; end of string
1912
+	    (return (values :string 
1913
+			    (subseq line colstart start)
1914
+			    (1+ start))))
1915
+	   (t nil)))
1916
+	
1917
+		      
1918
+	)
1919
+      
1920
+      (incf start))))
1921
+	    
1922
+	    
1923
+
1924
+;  this used to be exported from the excl package
1925
+#+(version>= 6 0)
1926
+(defvar *keyword-package* (find-package :keyword))
1927
+
1928
+(defun kwd-intern-possible-list (form)
1929
+  (if* (null form)
1930
+     then nil
1931
+   elseif (atom form)
1932
+     then (kwd-intern form)
1933
+     else (mapcar #'kwd-intern-possible-list form)))
1934
+
1935
+      
1936
+(defun kwd-intern (string)
1937
+  ;; convert the string to the current preferred case
1938
+  ;; and then intern
1939
+  (intern (case excl::*current-case-mode*
1940
+	    ((:case-sensitive-lower
1941
+	      :case-insensitive-lower) (string-downcase string))
1942
+	    (t (string-upcase string)))
1943
+	  *keyword-package*))
1944
+      
1945
+      
1946
+      
1947
+    
1948
+      
1949
+      
1950
+	
1951
+      
1952
+    
1953
+
1954
+  
1955
+    
1956
+    
1957
+  
1958
+;; low level i/o to server
1959
+
1960
+(defun get-line-from-server (mailbox)
1961
+  ;; Return two values:  a buffer and a character count.
1962
+  ;; The character count includes up to but excluding the cr lf that
1963
+  ;;  was read from the socket.
1964
+  ;; 
1965
+  (let* ((buff (get-line-buffer 0))
1966
+	 (len  (length buff))
1967
+	 (i 0)
1968
+	 (p (post-office-socket mailbox))
1969
+	 (ch nil)
1970
+	 (whole-count) 
1971
+	 )
1972
+
1973
+    (handler-case 
1974
+	(flet ((grow-buffer (size)
1975
+		 (let ((newbuff (get-line-buffer size)))
1976
+		   (dotimes (j i)
1977
+		     (setf (schar newbuff j) (schar buff j)))
1978
+		   (free-line-buffer buff)
1979
+		   (setq buff newbuff)
1980
+		   (setq len (length buff)))))
1981
+	     
1982
+	  ;; increase the buffer to at least size
1983
+	  ;; this is somewhat complex to ensure that we aren't doing
1984
+	  ;; buffer allocation within the with-timeout form, since 
1985
+	  ;; that could trigger a gc which could then cause the 
1986
+	  ;; with-timeout form to expire.
1987
+	  (loop
1988
+      
1989
+	    (if* whole-count
1990
+	       then ; we should now read in this may bytes and 
1991
+		    ; append it to this buffer
1992
+		    (multiple-value-bind (ans this-count)
1993
+			(get-block-of-data-from-server mailbox whole-count)
1994
+		      ; now put this data in the current buffer
1995
+		      (if* (> (+ i whole-count 5) len)
1996
+			 then  ; grow the initial buffer
1997
+			      (grow-buffer (+ i whole-count 100)))
1998
+		
1999
+		      (dotimes (ind this-count)
2000
+			(setf (schar buff i) (schar ans ind))
2001
+			(incf i))
2002
+		      (setf (schar buff i) #\^b) ; end of inset string
2003
+		      (incf i)
2004
+		      (free-line-buffer ans)
2005
+		      (setq whole-count nil)
2006
+		      )
2007
+	     elseif ch
2008
+	       then ; we're growing the buffer holding the line data
2009
+		    (grow-buffer (+ len 200))
2010
+		    (setf (schar buff i) ch)
2011
+		    (incf i))
2012
+
2013
+	    
2014
+	    (block timeout
2015
+	      (mp:with-timeout ((timeout mailbox)
2016
+				(po-error :timeout
2017
+					  :format-control "imap server failed to respond"))
2018
+		;; read up to lf  (lf most likely preceeded by cr)
2019
+		(loop
2020
+		  (setq ch (read-char p))
2021
+		  (if* (eq #\linefeed ch)
2022
+		     then ; end of line. Don't save the return
2023
+			  (if* (and (> i 0)
2024
+				    (eq (schar buff (1- i)) #\return))
2025
+			     then ; remove #\return, replace with newline
2026
+				  (decf i)
2027
+				  (setf (schar buff i) #\newline)
2028
+				  )
2029
+			  ;; must check for an extended return value which
2030
+			  ;; is indicated by a {nnn} at the end of the line
2031
+			  (block count-check
2032
+			    (let ((ind (1- i)))
2033
+			      (if* (and (>= i 0) (eq (schar buff ind) #\}))
2034
+				 then (let ((count 0)
2035
+					    (mult 1))
2036
+					(loop
2037
+					  (decf ind)
2038
+					  (if* (< ind 0) 
2039
+					     then ; no of the form {nnn}
2040
+						  (return-from count-check))
2041
+					  (setf ch (schar buff ind))
2042
+					  (if* (eq ch #\{)
2043
+					     then ; must now read that many bytes
2044
+						  (setf (schar buff ind) #\^b)
2045
+						  (setq whole-count count)
2046
+						  (setq i (1+ ind))
2047
+						  (return-from timeout)
2048
+					   elseif (<= #.(char-code #\0)
2049
+						      (char-code ch)
2050
+						      #.(char-code #\9))
2051
+					     then ; is a digit
2052
+						  (setq count 
2053
+						    (+ count
2054
+						       (* mult
2055
+							  (- (char-code ch)
2056
+							     #.(char-code #\0)))))
2057
+						  (setq mult (* 10 mult))
2058
+					     else ; invalid form, get out
2059
+						  (return-from count-check)))))))
2060
+					
2061
+		  
2062
+			  (return-from get-line-from-server
2063
+			    (values buff i))
2064
+		     else ; save character
2065
+			  (if* (>= i len)
2066
+			     then ; need bigger buffer
2067
+				  (return))
2068
+			  (setf (schar buff i) ch)
2069
+			  (incf i)))))))
2070
+      (error (con)
2071
+	;; most likely error is that the server went away
2072
+	(ignore-errors (close p))
2073
+	(po-error :server-shutdown-connection
2074
+		  :format-control "condition  signalled: ~a~%most likely server shut down the connection."
2075
+		  :format-arguments (list con)))
2076
+      )))
2077
+
2078
+
2079
+(defun get-block-of-data-from-server  (mb count &key save-returns)
2080
+  ;; read count bytes from the server returning it in a line buffer object
2081
+  ;; return as a second value the number of characters saved 
2082
+  ;; (we drop #\return's so that lines are separated by a #\newline
2083
+  ;; like lisp likes).
2084
+  ;;
2085
+  (let ((buff (get-line-buffer count))
2086
+	(p (post-office-socket mb))
2087
+	(ind 0))
2088
+    (mp:with-timeout ((timeout mb)
2089
+		      (po-error :timeout
2090
+				:format-control "imap server timed out"))
2091
+      
2092
+      (dotimes (i count)
2093
+	(if* (eq #\return (setf (schar buff ind) (read-char p)))
2094
+	   then (if* save-returns then (incf ind)) ; drop #\returns
2095
+	   else (incf ind)))
2096
+	
2097
+      
2098
+      (values buff ind))))
2099
+      
2100
+    
2101
+;;-- reusable line buffers
2102
+
2103
+(defvar *line-buffers* nil)
2104
+
2105
+#+(version>= 8 1)
2106
+(defvar *line-buffers-lock* (make-basic-lock :name "line-buffers"))
2107
+
2108
+(defmacro with-locked-line-buffers (&rest body)
2109
+#+(version>= 8 1)
2110
+  `(with-locked-structure (*line-buffers-lock*
2111
+			   :non-smp :without-scheduling)
2112
+     ,@body)
2113
+#-(version>= 8 1)
2114
+  `(sys::without-scheduling ,@body)
2115
+  )
2116
+
2117
+(defun get-line-buffer (size)
2118
+  ;; get a buffer of at least size bytes
2119
+  (setq size (min size (1- array-total-size-limit)))
2120
+  (let ((found 
2121
+	 (with-locked-line-buffers
2122
+	   (dolist (buff *line-buffers*)
2123
+	     (if* (>= (length buff) size)
2124
+		then ;; use this one
2125
+		     (setq *line-buffers* (delete buff *line-buffers*))
2126
+		     (return buff))))))
2127
+    (or found  (make-string size))))
2128
+
2129
+(defun free-line-buffer (buff)
2130
+  (with-locked-line-buffers
2131
+    (push buff *line-buffers*)))
2132
+
2133
+(defun init-line-buffer (new old)
2134
+  ;; copy old into new
2135
+  (declare (optimize (speed 3)))
2136
+  (dotimes (i (length old))
2137
+    (declare (fixnum i))
2138
+    (setf (schar new i) (schar old i))))
2139
+
2140
+  
2141
+
2142
+  ;;;;;;;
2143
+
2144
+; date functions
2145
+
2146
+(defun universal-time-to-rfc822-date (ut)
2147
+  ;; convert a lisp universal time to rfc 822 date
2148
+  ;;
2149
+  (multiple-value-bind
2150
+      (sec min hour date month year day-of-week dsp time-zone)
2151
+      (decode-universal-time ut 0)
2152
+    (declare (ignore time-zone sec min hour day-of-week dsp time-zone))
2153
+    (format nil "~d-~a-~d"
2154
+	    date
2155
+	    (svref
2156
+	     '#(nil "Jan" "Feb" "Mar" "Apr" "May" "Jun"
2157
+		"Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
2158
+	     month
2159
+	     )
2160
+	    year)))
2161
+  
2162
+			  
2163
+
2164
+
2165
+;; utility
2166
+
2167
+(defmacro with-imap-connection ((mb &rest options) &body body)
2168
+  `(let ((,mb (make-imap-connection ,@options)))
2169
+     (unwind-protect
2170
+	 (progn
2171
+	   ,@body)
2172
+       (close-connection ,mb))))
2173
+
2174
+
2175
+(defmacro with-pop-connection ((mb &rest options) &body body)
2176
+  `(let ((,mb (make-pop-connection ,@options)))
2177
+     (unwind-protect
2178
+	 (progn
2179
+	   ,@body)
2180
+       (close-connection ,mb))))
2181
+
2182
+