git.fiddlerwoaroof.com
Browse code

version 1.2

jkf authored on 27/10/1999 19:16:31
Showing 4 changed files
... ...
@@ -1,3 +1,11 @@
1
+1999-10-27  John Foderaro  <jkf@tiger.franz.com>
2
+version 1.2
3
+	
4
+	* imap.cl - add condtions
5
+	* imap.html - document conditions
6
+	* t-imap.cl - fix test suite
7
+	
8
+
1 9
 1999-09-29  John Foderaro  <jkf@tiger.franz.com>
2 10
 version 1.1
3 11
 	
... ...
@@ -19,7 +19,7 @@
19 19
 ;; Commercial Software developed at private expense as specified in
20 20
 ;; DOD FAR Supplement 52.227-7013 (c) (1) (ii), as applicable.
21 21
 ;;
22
-;; $Id: imap.cl,v 1.5 1999/09/29 16:25:23 jkf Exp $
22
+;; $Id: imap.cl,v 1.6 1999/10/27 19:16:31 jkf Exp $
23 23
 
24 24
 ;; Description:
25 25
 ;;
... ...
@@ -75,6 +75,12 @@
75 75
    #:make-imap-connection
76 76
    #:make-pop-connection
77 77
    #:noop
78
+   
79
+   #:po-condition
80
+   #:po-condition-indentifier
81
+   #:po-condition-server-string
82
+   #:po-error
83
+   
78 84
    #:rename-mailbox
79 85
    #:search-mailbox
80 86
    #:select-mailbox
... ...
@@ -83,8 +89,9 @@
83 89
 
84 90
 (in-package :post-office)
85 91
 
92
+(provide :imap)
86 93
 
87
-(defparameter *imap-version-number* '(:major 1 :minor 1)) ; major.minor
94
+(defparameter *imap-version-number* '(:major 1 :minor 2)) ; major.minor
88 95
 
89 96
 (defvar *debug-imap* nil)
90 97
 
... ...
@@ -194,6 +201,125 @@
194 201
   )
195 202
 
196 203
 
204
+
205
+;--------------------------------
206
+; conditions
207
+;
208
+; We define a set of conditions that are signalled due to events
209
+; in the imap interface.
210
+; Each condition has an indentifier which is a keyword.  That can
211
+; be used in the handling code to identify the class of error.
212
+; All our conditions are po-condition or po-error (which is a subclass of
213
+; po-condition).
214
+;
215
+; A condition will have a server-string value if it as initiated by 
216
+; something returned by the server.
217
+; A condition will have a format-control value if we want to display 
218
+; something we generated in response to 
219
+; 
220
+;
221
+;
222
+;; identifiers used in conditions/errors
223
+
224
+; :problem  condition
225
+;	the server responded with 'no' followed by an explanation.
226
+;	this mean that something unusual happend and doesn't necessarily
227
+;	mean that the command has completely failed (but it might).
228
+;	
229
+; :unknown-ok   condition
230
+;	the server responded with an 'ok' followed by something
231
+;	we don't recognize.  It's probably safe to ignore this.
232
+;
233
+;  :unknown-untagged condition
234
+;	the server responded with some untagged command we don't
235
+;	recognize.  it's probaby ok to ignore this.
236
+;
237
+;  :error-response  error
238
+;	the command failed.
239
+;
240
+;  :syntax-error   error
241
+;	the data passed to a function in this interface was malformed
242
+;
243
+;  :unexpected    error
244
+;	the server responded an unexpected way.
245
+;
246
+;  :server-shutdown-connection error
247
+;	the server has shut down the connection, don't attempt to
248
+;       send any more commands to this connection, or even close it.
249
+;
250
+;  :timeout  error
251
+;	server failed to respond within the timeout period
252
+
253
+
254
+
255
+;; conditions
256
+(define-condition po-condition ()
257
+  ;; used to notify user of things that shouldn't necessarily stop
258
+  ;; program flow
259
+  ((identifier 
260
+    ;; keyword identifying the error (or :unknown)
261
+    :reader po-condition-identifier	
262
+    :initform :unknown
263
+    :initarg :identifier
264
+    )
265
+   (server-string 
266
+    ;; message from the imap server
267
+    :reader po-condition-server-string
268
+    :initform ""
269
+    :initarg :server-string
270
+    ))
271
+  (:report
272
+   (lambda (con stream)
273
+     (with-slots (identifier server-string) con
274
+       ;; a condition either has a server-string or it has a 
275
+       ;; format-control string
276
+       (format stream "Post Office condition: ~s~%" identifier)
277
+       (if* (and (slot-boundp con 'excl::format-control)
278
+		 (excl::simple-condition-format-control con))
279
+	  then (apply #'format stream
280
+		      (excl::simple-condition-format-control con)
281
+		      (excl::simple-condition-format-arguments con)))
282
+       (if* server-string
283
+	  then (format stream
284
+		       "~&Message from server: ~s"
285
+		       (string-left-trim " " server-string)))))))
286
+	       
287
+    
288
+
289
+(define-condition po-error (po-condition error) 
290
+  ;; used to denote things that should stop program flow
291
+  ())
292
+
293
+
294
+
295
+;; aignalling the conditions
296
+
297
+(defun po-condition (identifier &key server-string format-control 
298
+			  format-arguments)
299
+  (signal (make-instance 'po-condition
300
+	    :identifier identifier
301
+	    :server-string server-string
302
+	    :format-control format-control
303
+	    :format-arguments format-arguments
304
+	    )))
305
+	    
306
+(defun po-error (identifier &key server-string
307
+		      format-control format-arguments)
308
+  (error (make-instance 'po-error
309
+	    :identifier identifier
310
+	    :server-string server-string
311
+	    :format-control format-control
312
+	    :format-arguments format-arguments)))
313
+
314
+			   
315
+
316
+;----------------------------------------------
317
+
318
+
319
+
320
+
321
+
322
+
197 323
 (defparameter *imap-tags* '("t01" "t02" "t03" "t04" "t05" "t06" "t07"))
198 324
 (defvar *cur-imap-tags* nil)
199 325
 
... ...
@@ -215,17 +341,20 @@
215 341
 		 :timeout timeout
216 342
 		 :state :unauthorized)))
217 343
     
218
-    (multiple-value-bind (tag)
344
+    (multiple-value-bind (tag cmd count extra comment)
219 345
 	(get-and-parse-from-imap-server imap)
346
+      (declare (ignore cmd count extra))
220 347
       (if* (not (eq :untagged tag))
221
-	 then  (error "unexpected line from server after connect")))
348
+	 then  (po-error :error-response
349
+			 :server-string comment)))
222 350
       
223 351
     ; now login
224 352
     (send-command-get-results imap 
225 353
 			      (format nil "login ~a ~a" user password)
226 354
 			      #'handle-untagged-response
227
-			      #'(lambda (mb command count extra)
355
+			      #'(lambda (mb command count extra comment)
228 356
 				  (check-for-success mb command count extra
357
+						     comment
229 358
 						     "login")))
230 359
     
231 360
     ; find the separator character
... ...
@@ -253,8 +382,9 @@
253 382
 	      #'(lambda (mb command count extra)
254 383
 		  (declare (ignore mb command count extra))
255 384
 		  nil)
256
-	      #'(lambda (mb command count extra)
385
+	      #'(lambda (mb command count extra comment)
257 386
 		  (check-for-success mb command count extra
387
+				     comment
258 388
 				     "logout")))))
259 389
     (setf (post-office-socket mb) nil)
260 390
     (if* sock then (ignore-errors (close sock)))
... ...
@@ -289,7 +419,9 @@
289 419
     (multiple-value-bind (result)
290 420
 	(get-and-parse-from-pop-server pop)
291 421
       (if* (not (eq :ok result))
292
-	 then  (error "unexpected line from server after connect")))
422
+	 then  (po-error :error-response
423
+			 :format-control
424
+			 "unexpected line from server after connect")))
293 425
       
294 426
     ; now login
295 427
     (send-pop-command-get-results pop (format nil "user ~a" user))
... ...
@@ -318,12 +450,12 @@
318 450
 		    "~a ~a~a" tag command *crlf*)
319 451
 	    (force-output))
320 452
     (loop
321
-      (multiple-value-bind (got-tag cmd count extra)
453
+      (multiple-value-bind (got-tag cmd count extra comment)
322 454
 	  (get-and-parse-from-imap-server mb)
323 455
 	(if* (eq got-tag :untagged)
324
-	   then (funcall untagged-handler mb cmd count extra)
456
+	   then (funcall untagged-handler mb cmd count extra comment)
325 457
 	 elseif (equal tag got-tag)
326
-	   then (funcall tagged-handler mb cmd count extra)
458
+	   then (funcall tagged-handler mb cmd count extra comment)
327 459
 		(return)
328 460
 	   else (warn "received tag ~s out of order" got-tag))))))
329 461
 
... ...
@@ -335,7 +467,7 @@
335 467
        else (setq *cur-imap-tags* *imap-tags*)
336 468
 	    (pop *cur-imap-tags*))))
337 469
 
338
-(defun handle-untagged-response (mb command count extra)
470
+(defun handle-untagged-response (mb command count extra comment)
339 471
   ;; default function to handle untagged responses, which are 
340 472
   ;; really just returning general state information about
341 473
   ;; the mailbox
... ...
@@ -345,9 +477,10 @@
345 477
     (:flags  (setf (mailbox-flags mb) (mapcar #'kwd-intern extra)))
346 478
     (:bye ; occurs when connection times out or mailbox lock is stolen
347 479
      (ignore-errors (close (post-office-socket mb)))
348
-     (error "connection to the imap server was closed by the server"))
480
+     (po-error :server-shutdown-connection
481
+		 :server-string "server shut down the connection"))
349 482
     (:no ; used when grabbing a lock from another process
350
-     (warn "grabbing mailbox lock from another process"))
483
+     (po-condition :problem :server-string comment))
351 484
     (:ok ; a whole variety of things
352 485
      (if* extra
353 486
 	then (if* (equalp (car extra) "unseen")
... ...
@@ -359,8 +492,8 @@
359 492
 	      elseif (equalp (car extra) "permanentflags")
360 493
 		then (setf (mailbox-permanent-flags mb) 
361 494
 		       (mapcar #'kwd-intern (cadr extra)))
362
-		else (warn "unknown ok response ~s" extra))))
363
-    (t (warn "unknown untagged response ~a ~a" command extra)))
495
+		else (po-condition :unknown-ok :server-string comment))))
496
+    (t (po-condition :unknown-untagged :server-string comment)))
364 497
 	     
365 498
   )
366 499
 
... ...
@@ -378,7 +511,8 @@
378 511
   (multiple-value-bind (result parsed line)
379 512
       (get-and-parse-from-pop-server pop)
380 513
     (if* (not (eq result :ok))
381
-       then (error "error from pop server: ~a" line))
514
+       then (po-error :error-response
515
+		      :server-string line))
382 516
 
383 517
     (if* extrap
384 518
        then ; get the rest of the data
... ...
@@ -393,13 +527,17 @@
393 527
 		  (sock (post-office-socket pop)))
394 528
 	      (flet ((add-to-buffer (ch)
395 529
 		       (if* (>= pos (length buf))
396
-			  then (error "missinfomation from pop")
530
+			  then (po-error :unexpected
531
+					 :format-control 
532
+					 "missinfomation from pop"
533
+					 :server-string line)
397 534
 			  else (setf (schar buf pos) ch)
398 535
 			       (incf pos))))
399 536
 		(loop
400 537
 		  (let ((ch (read-char sock nil nil)))
401 538
 		    (if* (null ch)
402
-		       then (error "premature end of file from server"))
539
+		       then (po-error :unexpected
540
+				      :format-control "premature end of file from server"))
403 541
 		    (if* (eq ch #\return)
404 542
 		       thenret ; ignore crs
405 543
 		       else (case state
... ...
@@ -441,10 +579,14 @@
441 579
   (send-command-get-results mb
442 580
 			    (format nil "select ~a" name)
443 581
 			    #'handle-untagged-response
444
-			    #'(lambda (mb command count extra)
582
+			    #'(lambda (mb command count extra comment)
445 583
 				(declare (ignore mb count extra))
446 584
 				(if* (not (eq command :ok))
447
-				   then (error "imap mailbox select failed"))))
585
+				   then (po-error 
586
+					 :problem
587
+					 :format-control 
588
+					 "imap mailbox select failed"
589
+					 :server-string comment))))
448 590
   (setf (mailbox-name mb) name)
449 591
   t
450 592
   )
... ...
@@ -473,15 +615,17 @@
473 615
 	     (message-set-string number)
474 616
 	     (or parts "body[]")
475 617
 	     )
476
-     #'(lambda (mb command count extra)
618
+     #'(lambda (mb command count extra comment)
477 619
 	 (if* (eq command :fetch)
478 620
 	    then (push (list count (internalize-flags extra)) res)
479 621
 	    else (handle-untagged-response
480
-		  mb command count extra)))
481
-     #'(lambda (mb command count extra)
622
+		  mb command count extra comment)))
623
+     #'(lambda (mb command count extra comment)
482 624
 	 (declare (ignore mb count extra))
483 625
 	 (if* (not (eq command :ok))
484
-	    then (error "imap mailbox fetch failed"))))
626
+	    then (po-error :problem
627
+			   :format-control "imap mailbox fetch failed"
628
+			   :server-string comment))))
485 629
     res))
486 630
 
487 631
 		      
... ...
@@ -546,8 +690,9 @@
546 690
      then (setq messages (list messages)))
547 691
   
548 692
   (if* (not (consp messages))
549
-     then (error "expect a mesage number or list of messages, not ~s"
550
-		 messages))
693
+     then (po-error :syntax-error
694
+		    :format-control "expect a mesage number or list of messages, not ~s"
695
+		 :format-arguments (list messages)))
551 696
   
552 697
   (dolist (message messages)
553 698
     (if* (numberp message)
... ...
@@ -559,7 +704,9 @@
559 704
 		((> start end))
560 705
 	      (send-pop-command-get-results pb
561 706
 					    (format nil "DELE ~d" start)))
562
-       else (error "bad message number ~s" message))))
707
+       else (po-error :syntax-error
708
+		      :format-control "bad message number ~s" 
709
+		      :format-arguments (list message)))))
563 710
 	    
564 711
 	    
565 712
 			    
... ...
@@ -571,9 +718,10 @@
571 718
   (send-command-get-results mb
572 719
 			    "noop"
573 720
 			    #'handle-untagged-response
574
-			    #'(lambda (mb command count extra)
721
+			    #'(lambda (mb command count extra comment)
575 722
 				(check-for-success
576 723
 				 mb command count extra
724
+				 comment
577 725
 				 "noop"))))
578 726
 
579 727
 
... ...
@@ -584,10 +732,13 @@
584 732
   )
585 733
 
586 734
 
587
-(defun check-for-success (mb command count extra command-string)
735
+(defun check-for-success (mb command count extra comment command-string )
588 736
   (declare (ignore mb count extra))
589 737
   (if* (not (eq command :ok))
590
-     then (error "imap ~a failed" command-string)))
738
+     then (po-error :error-response
739
+		    :format-control "imap ~a failed" 
740
+		    :format-arguments (list command-string)
741
+		    :server-string comment)))
591 742
 
592 743
   
593 744
 			    
... ...
@@ -598,14 +749,16 @@
598 749
   (let (res)
599 750
     (send-command-get-results mb
600 751
 			      (format nil "list ~s ~s" reference pattern)
601
-			      #'(lambda (mb command count extra)
752
+			      #'(lambda (mb command count extra comment)
602 753
 				  (if* (eq command :list)
603 754
 				     then (push extra res)
604 755
 				     else (handle-untagged-response
605
-					   mb command count extra)))
606
-			      #'(lambda (mb command count extra)
756
+					   mb command count extra
757
+					   comment)))
758
+			      #'(lambda (mb command count extra comment)
607 759
 				  (check-for-success 
608
-				   mb command count extra "list")))
760
+				   mb command count extra 
761
+				   comment "list")))
609 762
     
610 763
     ;; the car of each list is a set of keywords, make that so
611 764
     (dolist (rr res)
... ...
@@ -623,9 +776,10 @@
623 776
   (send-command-get-results mb
624 777
 			    (format nil "create ~s" mailbox-name)
625 778
 			    #'handle-untagged-response
626
-			    #'(lambda (mb command count extra)
779
+			    #'(lambda (mb command count extra comment)
627 780
 				  (check-for-success 
628
-				   mb command count extra "create")))
781
+				   mb command count extra 
782
+				   comment "create")))
629 783
   t)
630 784
 
631 785
 
... ...
@@ -635,9 +789,10 @@
635 789
   (send-command-get-results mb
636 790
 			    (format nil "delete ~s" mailbox-name)
637 791
 			    #'handle-untagged-response
638
-			    #'(lambda (mb command count extra)
792
+			    #'(lambda (mb command count extra comment)
639 793
 				  (check-for-success 
640
-				   mb command count extra "delete"))))
794
+				   mb command count extra 
795
+				   comment "delete"))))
641 796
 
642 797
 (defmethod rename-mailbox ((mb imap-mailbox) old-mailbox-name new-mailbox-name)
643 798
   ;; create a mailbox name of the given name.
... ...
@@ -647,9 +802,11 @@
647 802
 				    old-mailbox-name
648 803
 				    new-mailbox-name)
649 804
 			    #'handle-untagged-response
650
-			    #'(lambda (mb command count extra)
805
+			    #'(lambda (mb command count extra comment)
651 806
 				  (check-for-success 
652
-				   mb command count extra "rename"))))
807
+				   mb command count extra 
808
+				   comment
809
+				   "rename"))))
653 810
 
654 811
 
655 812
 
... ...
@@ -682,18 +839,20 @@
682 839
 				      (if* val
683 840
 					 thenret
684 841
 					 else "()"))
685
-			      #'(lambda (mb command count extra)
842
+			      #'(lambda (mb command count extra comment)
686 843
 				  (if* (eq command :fetch)
687 844
 				     then (push (list count 
688 845
 						      (convert-flags-plist
689 846
 						       extra))
690 847
 						res)
691 848
 				     else (handle-untagged-response
692
-					   mb command count extra)))
849
+					   mb command count extra
850
+					   comment)))
693 851
 			      
694
-			      #'(lambda (mb command count extra)
852
+			      #'(lambda (mb command count extra comment)
695 853
 				  (check-for-success 
696
-				   mb command count extra "store")))
854
+				   mb command count extra 
855
+				   comment "store")))
697 856
     res))
698 857
 
699 858
 
... ...
@@ -715,7 +874,9 @@
715 874
 		       elseif (eq :seq (car msg))
716 875
 			 then (format str
717 876
 				      "~a:~a" (cadr msg) (caddr msg))
718
-			 else (error "bad message list ~s" msg))
877
+			 else (po-error :syntax-error
878
+					:format-control "bad message list ~s" 
879
+					:format-arguments (list msg)))
719 880
 		      (setq precomma t))
720 881
 		    (get-output-stream-string str)))))
721 882
 				   
... ...
@@ -729,14 +890,17 @@
729 890
   (let (res)
730 891
     (send-command-get-results mb
731 892
 			      "expunge"
732
-			      #'(lambda (mb command count extra)
893
+			      #'(lambda (mb command count extra
894
+					 comment)
733 895
 				  (if* (eq command :expunge)
734 896
 				     then (push count res)
735 897
 				     else (handle-untagged-response
736
-					   mb command count extra)))
737
-			      #'(lambda (mb command count extra)
898
+					   mb command count extra
899
+					   comment)))
900
+			      #'(lambda (mb command count extra comment)
738 901
 				  (check-for-success 
739
-				   mb command count extra "expunge")))
902
+				   mb command count extra 
903
+				   comment "expunge")))
740 904
     (nreverse res)))
741 905
     
742 906
     
... ...
@@ -747,9 +911,10 @@
747 911
 			    "close"
748 912
 			    #'handle-untagged-response
749 913
 			      
750
-			    #'(lambda (mb command count extra)
914
+			    #'(lambda (mb command count extra comment)
751 915
 				(check-for-success 
752
-				 mb command count extra "close")))
916
+				 mb command count extra 
917
+				 comment "close")))
753 918
   t)
754 919
   
755 920
 
... ...
@@ -762,9 +927,10 @@
762 927
 				    (message-set-string message-list)
763 928
 				    destination)
764 929
 			    #'handle-untagged-response
765
-			    #'(lambda (mb command count extra)
930
+			    #'(lambda (mb command count extra comment)
766 931
 				(check-for-success 
767
-				 mb command count extra "copy")))
932
+				 mb command count extra 
933
+				 comment "copy")))
768 934
   t)
769 935
 
770 936
 
... ...
@@ -776,14 +942,16 @@
776 942
 			      (format nil "~asearch ~a" 
777 943
 				      (if* uid then "uid " else "")
778 944
 				      (build-search-string search-expression))
779
-			      #'(lambda (mb command count extra)
945
+			      #'(lambda (mb command count extra comment)
780 946
 				  (if* (eq command :search)
781 947
 				     then (setq res (append res extra))
782 948
 				     else (handle-untagged-response
783
-					   mb command count extra)))
784
-			      #'(lambda (mb command count extra)
949
+					   mb command count extra
950
+					   comment)))
951
+			      #'(lambda (mb command count extra comment)
785 952
 				  (check-for-success 
786
-				   mb command count extra "search")))
953
+				   mb command count extra 
954
+				   comment "search")))
787 955
     res))
788 956
     
789 957
 		       
... ...
@@ -877,7 +1045,9 @@
877 1045
 			    (eq :seq (car val))
878 1046
 			    (eq 3 (length val)))
879 1047
 		  then (format str "~s:~s" (cadr val) (caddr val))
880
-		  else (error "illegal set format ~s" val))
1048
+		  else (po-error :syntax-error
1049
+				 :format-control "illegal set format ~s" 
1050
+				 :format-arguments (list val)))
881 1051
 	       (if* (cdr xsrch) then (format str ","))))
882 1052
 	   (arg-process (str args arginfo)
883 1053
 	     ;; process and print each arg to str
... ...
@@ -896,14 +1066,17 @@
896 1066
 		     then (setq val (universal-time-to-rfc822-date
897 1067
 				     val))
898 1068
 		   elseif (not (stringp val))
899
-		     then (error "illegal value for date search ~s"
900
-				 val))
1069
+		     then (po-error :syntax-error
1070
+				    :format-control "illegal value for date search ~s"
1071
+				    :format-arguments (list val)))
901 1072
 		  ;; val is now a string
902 1073
 		  (format str " ~s" val))
903 1074
 		 (:number
904 1075
 		  
905 1076
 		  (if* (not (integerp val))
906
-		     then (error "illegal value for number in search ~s" val))
1077
+		     then (po-error :syntax-error
1078
+				    :format-control "illegal value for number in search ~s" 
1079
+				    :format-arguments (list val)))
907 1080
 		  (format str " ~s" val))
908 1081
 		 (:flag
909 1082
 		  
... ...
@@ -915,15 +1088,19 @@
915 1088
 		     then (format str " ~s" val)
916 1089
 		   elseif (consp val)
917 1090
 		     then (set-ify val str)
918
-		     else (error "illegal message set ~s" val)))
1091
+		     else (po-error :syntax-error
1092
+				    :format-control "illegal message set ~s" 
1093
+				    :format-arguments (list val))))
919 1094
 		  
920 1095
 		 ))))
921 1096
     
922 1097
     (if* (symbolp search)
923 1098
        then (if* (get search 'imap-search-no-args)
924 1099
 	       then (format str "~a"  (string-upcase
925
-					(string search)))
926
-	       else (error "illegal search word: ~s" search))
1100
+				       (string search)))
1101
+	       else (po-error :syntax-error
1102
+			      :format-control "illegal search word: ~s" 
1103
+			      :format-arguments (list search)))
927 1104
      elseif (consp search)
928 1105
        then (case (car search)
929 1106
 	      (and (if* (null (cdr search))
... ...
@@ -937,7 +1114,9 @@
937 1114
 		      then (bss-int (cadr search) str)
938 1115
 		      else (or-ify (cdr search)  str)))
939 1116
 	      (not (if* (not (eql (length search) 2))
940
-		      then (error "not takes one argument: ~s" search))
1117
+		      then (po-error :syntax-error 
1118
+				     :format-control "not takes one argument: ~s" 
1119
+				     :format-arguments (list search)))
941 1120
 		   (format str "not (" )
942 1121
 		   (bss-int (cadr search) str)
943 1122
 		   (format str ")"))
... ...
@@ -949,20 +1128,26 @@
949 1128
 						'imap-search-args)))
950 1129
 		      then 
951 1130
 			   (format str "~a" (string-upcase
952
-					       (string (car search))))
1131
+					     (string (car search))))
953 1132
 			   (if* (not (equal (length (cdr search))
954 1133
 					    (length arginfo)))
955
-			      then (error "wrong number of arguments to ~s" search))
1134
+			      then (po-error :syntax-error 
1135
+					     :format-control "wrong number of arguments to ~s" 
1136
+					     :format-arguments search))
956 1137
 			   
957 1138
 			   (arg-process str (cdr search) arginfo)
958 1139
 			   
959 1140
 		    elseif (integerp (car search))
960 1141
 		      then (set-ify search str)
961
-		      else (error "Illegal form ~s in search string" search)))))
1142
+		      else (po-error :syntax-error 
1143
+				     :format-control "Illegal form ~s in search string" 
1144
+				     :format-arguments (list search))))))
962 1145
      elseif (integerp search)
963 1146
        then ;  a message number
964 1147
 	    (format str "~s" search)
965
-       else (error "Illegal form ~s in search string" search))))
1148
+       else (po-error :syntax-error
1149
+		      :format-control "Illegal form ~s in search string" 
1150
+		      :format-arguments (list search)))))
966 1151
 
967 1152
 
968 1153
 
... ...
@@ -1013,10 +1198,12 @@
1013 1198
 ;;  bracketted - a list of objects found in []'s after the command
1014 1199
 ;;            or in ()'s after the command  or sometimes just 
1015 1200
 ;;	      out in the open after the command (like the search)
1201
+;;  comment  -- the whole of the part after the command
1016 1202
 ;;
1017 1203
 (defun parse-imap-response (line end)
1018 1204
   (let (kind value next
1019
-	tag count command extra-data)
1205
+	tag count command extra-data
1206
+	comment)
1020 1207
     
1021 1208
     ;; get tag
1022 1209
     (multiple-value-setq (kind value next)
... ...
@@ -1026,7 +1213,11 @@
1026 1213
       (:string (setq tag (if* (equal value "*")
1027 1214
 			    then :untagged
1028 1215
 			    else value)))
1029
-      (t (error "Illegal tag on response: ~s" (subseq line 0 count))))
1216
+      (t (po-error :unexpected
1217
+		   :format-control "Illegal tag on response: ~s" 
1218
+		   :format-arguments (list (subseq line 0 count))
1219
+		   :server-string (subseq line 0 end)
1220
+		   )))
1030 1221
       
1031 1222
     ;; get command
1032 1223
     (multiple-value-setq (kind value next)
... ...
@@ -1039,8 +1230,13 @@
1039 1230
 		   (get-next-token line next end))
1040 1231
 		 (go again))
1041 1232
 	(:string (setq command (kwd-intern value)))
1042
-	(t (error "Illegal command on response: ~s" (subseq line 0 count)))))
1043
-      
1233
+	(t (po-error :unexpected 
1234
+		     :format-control "Illegal command on response: ~s" 
1235
+		     :format-arguments (list (subseq line 0 count))
1236
+		     :server-string (subseq line 0 end)))))
1237
+
1238
+    (setq comment (subseq line next end))
1239
+    
1044 1240
     ;; now the part after the command... this gets tricky
1045 1241
     (loop
1046 1242
       (multiple-value-setq (kind value next)
... ...
@@ -1052,7 +1248,7 @@
1052 1248
 	   (get-next-sexpr line (1- next) end))
1053 1249
 	 (case kind
1054 1250
 	   (:sexpr (push value extra-data))
1055
-	   (t (error "bad sexpr form"))))
1251
+	   (t (po-error :syntax-error :format-control "bad sexpr form"))))
1056 1252
 	(:eof (return nil))
1057 1253
 	((:number :string :nil) (push value extra-data))
1058 1254
 	(t  ; should never happen
... ...
@@ -1067,7 +1263,7 @@
1067 1263
        then (setq extra-data (nreverse extra-data)))
1068 1264
     
1069 1265
       
1070
-    (values tag command count extra-data)))
1266
+    (values tag command count extra-data comment)))
1071 1267
       
1072 1268
 
1073 1269
 
... ...
@@ -1083,8 +1279,9 @@
1083 1279
     
1084 1280
     (case kind
1085 1281
       ((:string :number :nil)
1086
-        (values :sexpr value next))
1087
-      (:eof (error "eof inside sexpr"))
1282
+       (values :sexpr value next))
1283
+      (:eof (po-error :syntax-error 
1284
+		      :format-control "eof inside sexpr"))
1088 1285
       ((:lbracket :lparen)
1089 1286
        (let (res)
1090 1287
 	 (loop
... ...
@@ -1094,10 +1291,12 @@
1094 1291
 	     (:sexpr (push value res))
1095 1292
 	     ((:rparen :rbracket) 
1096 1293
 	      (return (values :sexpr (nreverse res) next)))
1097
-	     (t (error "bad sexpression"))))))
1294
+	     (t (po-error :syntax-error
1295
+			  :format-control "bad sexpression"))))))
1098 1296
       ((:rbracket :rparen)
1099 1297
        (values kind nil next))
1100
-      (t (error "bad sexpression")))))
1298
+      (t (po-error :syntax-error
1299
+		   :format-control "bad sexpression")))))
1101 1300
 
1102 1301
 
1103 1302
 (defun parse-pop-response (line end)
... ...
@@ -1114,7 +1313,9 @@
1114 1313
 	(:string (setq result (if* (equal "+OK" value) 
1115 1314
 				 then :ok
1116 1315
 				 else :error)))
1117
-	(t (error "bad response from server: ~s" (subseq line 0 end))))
1316
+	(t (po-error :unexpected
1317
+		     :format-control "bad response from server" 
1318
+		     :server-string (subseq line 0 end))))
1118 1319
     
1119 1320
       (setq lineres (subseq line next end))
1120 1321
 
... ...
@@ -1238,13 +1439,15 @@
1238 1439
 		 then ; escaping the next character
1239 1440
 		      (incf start)
1240 1441
 		      (if* (>= start end)
1241
-			 then (error "eof in string returned"))
1442
+			 then (po-error :unexpected
1443
+					:format-control "eof in string returned"))
1242 1444
 		      (setq ch (schar line start)))
1243 1445
 	      (vector-push-extend ch collector)
1244 1446
 	      
1245 1447
 	      (if* (>= start end)
1246 1448
 		 then ; we overran the end of the input
1247
-		      (error "eof in string returned")))))
1449
+		      (po-error :unexpected
1450
+				:format-control "eof in string returned")))))
1248 1451
 	(:big-string
1249 1452
 	 ;; super string... just a block of data
1250 1453
 	 ; (format t "start is ~s  kind is ~s~%" start chkind)
... ...
@@ -1302,99 +1505,108 @@
1302 1505
 	 (whole-count) 
1303 1506
 	 )
1304 1507
 
1305
-    (flet ((grow-buffer (size)
1306
-	     (let ((newbuff (get-line-buffer size)))
1307
-	       (dotimes (j i)
1308
-		 (setf (schar newbuff j) (schar buff j)))
1309
-	       (free-line-buffer buff)
1310
-	       (setq buff newbuff)
1311
-	       (setq len (length buff)))))
1508
+    (handler-case 
1509
+	(flet ((grow-buffer (size)
1510
+		 (let ((newbuff (get-line-buffer size)))
1511
+		   (dotimes (j i)
1512
+		     (setf (schar newbuff j) (schar buff j)))
1513
+		   (free-line-buffer buff)
1514
+		   (setq buff newbuff)
1515
+		   (setq len (length buff)))))
1312 1516
 	     
1313
-      ;; increase the buffer to at least size
1314
-      ;; this is somewhat complex to ensure that we aren't doing
1315
-      ;; buffer allocation within the with-timeout form, since 
1316
-      ;; that could trigger a gc which could then cause the 
1317
-      ;; with-timeout form to expire.
1318
-      (loop
1517
+	  ;; increase the buffer to at least size
1518
+	  ;; this is somewhat complex to ensure that we aren't doing
1519
+	  ;; buffer allocation within the with-timeout form, since 
1520
+	  ;; that could trigger a gc which could then cause the 
1521
+	  ;; with-timeout form to expire.
1522
+	  (loop
1319 1523
       
1320
-	(if* whole-count
1321
-	   then ; we should now read in this may bytes and 
1322
-		; append it to this buffer
1323
-		(multiple-value-bind (ans this-count)
1324
-		    (get-block-of-data-from-server mailbox whole-count)
1325
-		  ; now put this data in the current buffer
1326
-		  (if* (> (+ i whole-count 5) len)
1327
-		     then  ; grow the initial buffer
1328
-			  (grow-buffer (+ i whole-count 100)))
1524
+	    (if* whole-count
1525
+	       then ; we should now read in this may bytes and 
1526
+		    ; append it to this buffer
1527
+		    (multiple-value-bind (ans this-count)
1528
+			(get-block-of-data-from-server mailbox whole-count)
1529
+		      ; now put this data in the current buffer
1530
+		      (if* (> (+ i whole-count 5) len)
1531
+			 then  ; grow the initial buffer
1532
+			      (grow-buffer (+ i whole-count 100)))
1329 1533
 		
1330
-		  (dotimes (ind this-count)
1331
-		    (setf (schar buff i) (schar ans ind))
1534
+		      (dotimes (ind this-count)
1535
+			(setf (schar buff i) (schar ans ind))
1536
+			(incf i))
1537
+		      (setf (schar buff i) #\^b) ; end of inset string
1538
+		      (incf i)
1539
+		      (free-line-buffer ans)
1540
+		      )
1541
+	     elseif ch
1542
+	       then ; we're growing the buffer holding the line data
1543
+		    (grow-buffer (+ len 200))
1544
+		    (setf (schar buff i) ch)
1332 1545
 		    (incf i))
1333
-		  (setf (schar buff i) #\^b) ; end of inset string
1334
-		  (incf i)
1335
-		  (free-line-buffer ans)
1336
-		  )
1337
-	 elseif ch
1338
-	   then ; we're growing the buffer holding the line data
1339
-		(grow-buffer (+ len 200))
1340
-		(setf (schar buff i) ch)
1341
-		(incf i))
1342
-
1343
-	(block timeout
1344
-	  (mp:with-timeout ((timeout mailbox)
1345
-			    (error "imap server failed to respond"))
1346
-	    ;; read up to lf  (lf most likely preceeded by cr)
1347
-	    (loop
1348
-	      (setq ch (read-char p))
1349
-	      (if* (eq #\linefeed ch)
1350
-		 then ; end of line. Don't save the return
1351
-		      (if* (and (> i 0)
1352
-				(eq (schar buff (1- i)) #\return))
1353
-			 then ; remove #\return, replace with newline
1354
-			      (decf i)
1355
-			      (setf (schar buff i) #\newline)
1356
-			      )
1357
-		      ;; must check for an extended return value which
1358
-		      ;; is indicated by a {nnn} at the end of the line
1359
-		      (block count-check
1360
-			(let ((ind (1- i)))
1361
-			  (if* (and (>= i 0) (eq (schar buff ind) #\}))
1362
-			     then (let ((count 0)
1363
-					(mult 1))
1364
-				    (loop
1365
-				      (decf ind)
1366
-				      (if* (< ind 0) 
1367
-					 then ; no of the form {nnn}
1368
-					      (return-from count-check))
1369
-				      (setf ch (schar buff ind))
1370
-				      (if* (eq ch #\{)
1371
-					 then ; must now read that many bytes
1372
-					      (setf (schar buff ind) #\^b)
1373
-					      (setq whole-count count)
1374
-					      (setq i (1+ ind))
1375
-					      (return-from timeout)
1376
-				       elseif (<= #.(char-code #\0)
1377
-						 (char-code ch)
1378
-						 #.(char-code #\9))
1379
-					 then ; is a digit
1380
-					      (setq count 
1381
-						(+ count
1382
-						   (* mult
1383
-						      (- (char-code ch)
1384
-							 #.(char-code #\0)))))
1385
-					      (setq mult (* 10 mult))
1386
-					 else ; invalid form, get out
1387
-					      (return-from count-check)))))))
1546
+
1547
+	    (block timeout
1548
+	      (mp:with-timeout ((timeout mailbox)
1549
+				(po-error :timeout
1550
+					  :format-control "imap server failed to respond"))
1551
+		;; read up to lf  (lf most likely preceeded by cr)
1552
+		(loop
1553
+		  (setq ch (read-char p))
1554
+		  (if* (eq #\linefeed ch)
1555
+		     then ; end of line. Don't save the return
1556
+			  (if* (and (> i 0)
1557
+				    (eq (schar buff (1- i)) #\return))
1558
+			     then ; remove #\return, replace with newline
1559
+				  (decf i)
1560
+				  (setf (schar buff i) #\newline)
1561
+				  )
1562
+			  ;; must check for an extended return value which
1563
+			  ;; is indicated by a {nnn} at the end of the line
1564
+			  (block count-check
1565
+			    (let ((ind (1- i)))
1566
+			      (if* (and (>= i 0) (eq (schar buff ind) #\}))
1567
+				 then (let ((count 0)
1568
+					    (mult 1))
1569
+					(loop
1570
+					  (decf ind)
1571
+					  (if* (< ind 0) 
1572
+					     then ; no of the form {nnn}
1573
+						  (return-from count-check))
1574
+					  (setf ch (schar buff ind))
1575
+					  (if* (eq ch #\{)
1576
+					     then ; must now read that many bytes
1577
+						  (setf (schar buff ind) #\^b)
1578
+						  (setq whole-count count)
1579
+						  (setq i (1+ ind))
1580
+						  (return-from timeout)
1581
+					   elseif (<= #.(char-code #\0)
1582
+						      (char-code ch)
1583
+						      #.(char-code #\9))
1584
+					     then ; is a digit
1585
+						  (setq count 
1586
+						    (+ count
1587
+						       (* mult
1588
+							  (- (char-code ch)
1589
+							     #.(char-code #\0)))))
1590
+						  (setq mult (* 10 mult))
1591
+					     else ; invalid form, get out
1592
+						  (return-from count-check)))))))
1388 1593
 					
1389 1594
 		  
1390
-		      (return-from get-line-from-server
1391
-			(values buff i))
1392
-		 else ; save character
1393
-		      (if* (>= i len)
1394
-			 then ; need bigger buffer
1395
-			      (return))
1396
-		      (setf (schar buff i) ch)
1397
-		      (incf i)))))))))
1595
+			  (return-from get-line-from-server
1596
+			    (values buff i))
1597
+		     else ; save character
1598
+			  (if* (>= i len)
1599
+			     then ; need bigger buffer
1600
+				  (return))
1601
+			  (setf (schar buff i) ch)
1602
+			  (incf i)))))))
1603
+      (error (con)
1604
+	;; most likely error is that the server went away
1605
+	(ignore-errors (close p))
1606
+	(po-error :server-shutdown-connection
1607
+		  :format-control "condition  signalled: ~a~%most likely server shut down the connection."
1608
+		  :format-arguments (list con)))
1609
+      )))
1398 1610
 
1399 1611
 
1400 1612
 (defun get-block-of-data-from-server  (mb count &key save-returns)
... ...
@@ -1407,7 +1619,8 @@
1407 1619
 	(p (post-office-socket mb))
1408 1620
 	(ind 0))
1409 1621
     (mp:with-timeout ((timeout mb)
1410
-		      (error "imap server timed out"))
1622
+		      (po-error :timeout
1623
+				:format-control "imap server timed out"))
1411 1624
       
1412 1625
       (dotimes (i count)
1413 1626
 	(if* (eq #\return (setf (schar buff ind) (read-char p)))
... ...
@@ -27,6 +27,9 @@ interface.</p>
27 27
   </li>
28 28
   <li><p align="left"><a href="#pop">the <strong>pop</strong> interface</a></p>
29 29
   </li>
30
+  <li><p align="left"><a href="#conditions">the <strong>conditions</strong> signaled by the <strong>imap</strong>
31
+    and <strong>pop</strong> interfaces.</a></p>
32
+  </li>
30 33
   <li><p align="left"><a href="#smtp">the <strong>smtp</strong> interface</a> (used for
31 34
     sending mail)</p>
32 35
   </li>
... ...
@@ -314,6 +317,18 @@ this command has finished there is no currently selected mailbox.</p>
314 317
 
315 318
 <p align="left">&nbsp;</p>
316 319
 
320
+<p align="left"><strong><font face="Courier New">(copy-to-mailbox mailbox messages
321
+destination &amp;key uid)</font></strong></p>
322
+
323
+<p align="left">copies the specified <strong>messages </strong>from the currently selected
324
+mailbox to the mailbox named <strong>destination</strong> (given as a string). &nbsp; The
325
+flags are copied as well. The destination mailbox must already exist.&nbsp; The messages
326
+are <strong>not</strong> removed from the selected mailbox after the copy &nbsp; .If <strong>uid</strong>
327
+is true then the <strong>messages</strong> are considered to be unique ids rather than
328
+message sequence numbers. </p>
329
+
330
+<p align="left">&nbsp;</p>
331
+
317 332
 <p align="left"><font face="Courier New"><strong>(delete-letter mailbox messages &amp;key
318 333
 expunge uid</strong></font>)</p>
319 334
 
... ...
@@ -408,7 +423,7 @@ this can be check using <strong>mailbox-message-count</strong>.&nbsp;&nbsp;&nbsp
408 423
 
409 424
 <p align="left">&nbsp;</p>
410 425
 
411
-<p align="left"><font face="Courier New"><strong>(search-mailbox search-expression
426
+<p align="left"><font face="Courier New"><strong>(search-mailbox mailbox search-expression
412 427
 &amp;key uid)</strong></font></p>
413 428
 
414 429
 <p align="left">return a list of messages in the mailbox that satisfy the<strong>
... ...
@@ -830,7 +845,7 @@ t
830 845
 
831 846
 <h1><a name="pop"></a>The Pop interface</h1>
832 847
 
833
-<p>The <strong>pop</strong> protocol is a very simple means for retreiving messages from a
848
+<p>The <strong>pop</strong> protocol is a very simple means for retrieving messages from a
834 849
 single mailbox.&nbsp;&nbsp;&nbsp;&nbsp; The functions in the interface are:</p>
835 850
 
836 851
 <p>&nbsp;</p>
... ...
@@ -897,6 +912,109 @@ will contain the current count of messages in the mailbox.</p>
897 912
 
898 913
 <p>&nbsp;</p>
899 914
 
915
+<h1>Cond<a name="conditions"></a>itions</h1>
916
+
917
+<p>When an unexpected event occurs a condition is signaled.&nbsp;&nbsp; This applies to
918
+both the <strong>imap</strong> and <strong>pop</strong> interfaces.&nbsp; There are two
919
+classes of conditions signaled by this package:
920
+
921
+<ul>
922
+  <li><strong>po-condition</strong> - this class denotes conditions that need not and in fact
923
+    should not interrupt program flow.&nbsp;&nbsp; When the mailbox server is responding to a
924
+    command it sometimes sends informational warning messages and we turn them into
925
+    conditions.&nbsp;&nbsp;&nbsp; It's important for all messages from the server to be read
926
+    and processed otherwise the next command issued will see messages in response to the
927
+    previous command.&nbsp;&nbsp; Therefore the user code should never do a non-local-transfer
928
+    in response to a <strong>po-condition.</strong></li>
929
+  <li><strong>po-error - </strong> this class denotes conditions that will prevent execution
930
+    from continuing.&nbsp; If one of these errors is not caught, the interactive debugger will
931
+    be entered.</li>
932
+</ul>
933
+
934
+<p>Instances of both of these condition classes have these slots in addition to the
935
+standard condition slots:&nbsp; </p>
936
+
937
+<table border="1" width="100%">
938
+  <tr>
939
+    <td width="16%">Name</td>
940
+    <td width="24%">Accessor</td>
941
+    <td width="60%">Value</td>
942
+  </tr>
943
+  <tr>
944
+    <td width="16%">identifier</td>
945
+    <td width="24%">po-condition-identifier</td>
946
+    <td width="60%">keyword describing the kind of condition being signaled.&nbsp; See the
947
+    table below for the possible values.</td>
948
+  </tr>
949
+  <tr>
950
+    <td width="16%">server-string</td>
951
+    <td width="24%">po-condition-server-string</td>
952
+    <td width="60%">If the condition was created because of a messages sent from the mailbox
953
+    server then this is that message.</td>
954
+  </tr>
955
+</table>
956
+
957
+<p>The meaning of the identifier value is as follows</p>
958
+
959
+<table border="1" width="100%">
960
+  <tr>
961
+    <td width="11%"><strong>Identifier</strong></td>
962
+    <td width="13%">Kind</td>
963
+    <td width="76%">Meaning</td>
964
+  </tr>
965
+  <tr>
966
+    <td width="11%"><strong>:problem</strong></td>
967
+    <td width="13%">po-condition</td>
968
+    <td width="76%">The server has responded with a warning message.&nbsp;&nbsp; The most
969
+    likely warning is that the mailbox can only be opened in read-only mode due to another
970
+    processing using it.</td>
971
+  </tr>
972
+  <tr>
973
+    <td width="11%"><strong>:unknown-ok</strong></td>
974
+    <td width="13%">po-condition</td>
975
+    <td width="76%">The server has sent an informative message that we don't understand.
976
+    &nbsp; It's probably safe to ignore this.</td>
977
+  </tr>
978
+  <tr>
979
+    <td width="11%"><strong>:unknown-untagged</strong></td>
980
+    <td width="13%">po-condition</td>
981
+    <td width="76%">The server has sent an informative message that we don't understand.
982
+    &nbsp; It's probably safe to ignore this.</td>
983
+  </tr>
984
+  <tr>
985
+    <td width="11%"><strong>:error-response</strong></td>
986
+    <td width="13%">po-error</td>
987
+    <td width="76%">The server cannot execute the requested command.</td>
988
+  </tr>
989
+  <tr>
990
+    <td width="11%"><strong>:syntax-error</strong></td>
991
+    <td width="13%">po-error</td>
992
+    <td width="76%">The arguments to a function in this package are malformed.</td>
993
+  </tr>
994
+  <tr>
995
+    <td width="11%"><strong>:unexpected</strong></td>
996
+    <td width="13%">po-error</td>
997
+    <td width="76%">The server has responded a way we don't understand and which prevents us
998
+    from continuing</td>
999
+  </tr>
1000
+  <tr>
1001
+    <td width="11%"><strong>:server-shutdown-connection</strong></td>
1002
+    <td width="13%">po-error</td>
1003
+    <td width="76%">The connection to the server has been broken.&nbsp; This usually occurs
1004
+    when the connection has been idle for too long and the server intentionally disconnects.
1005
+    &nbsp;&nbsp; Just before this condition is signaled we close down the socket connection to
1006
+    free up the socket resource on our side.&nbsp; When this condition is signaled the user
1007
+    program should not use the mailbox object&nbsp; again (even to call <strong>close-connection</strong>
1008
+    on it).</td>
1009
+  </tr>
1010
+  <tr>
1011
+    <td width="11%"><strong>:timeout</strong></td>
1012
+    <td width="13%">po-error</td>
1013
+    <td width="76%">The server did not respond quickly enough.&nbsp;&nbsp; The timeout value
1014
+    is set in the call to <strong>make-imap-connection.</strong></td>
1015
+  </tr>
1016
+</table>
1017
+
900 1018
 <h1><a name="smtp"></a>The smtp interface</h1>
901 1019
 
902 1020
 <p>With the smtp interface, a Lisp program can contact a mail server and send electronic
... ...
@@ -184,10 +184,10 @@
184 184
     
185 185
     (test-eql 2 (and :third (po:mailbox-message-count pb)))
186 186
     
187
-    (po:fetch-letter mb 1)
188
-    (test-err (po:fetch-letter mb 2))
189
-    (test-err (po:fetch-letter mb 3))
190
-    (po:fetch-letter mb 4)
187
+    (po:fetch-letter pb 1)
188
+    (test-err (po:fetch-letter pb 2))
189
+    (test-err (po:fetch-letter pb 3))
190
+    (po:fetch-letter pb 4)
191 191
     
192 192
     (po:close-connection pb)
193 193
     
... ...
@@ -204,18 +204,22 @@
204 204
 	  
205 205
     
206 206
 (defun test-imap ()
207
-  (test-connect)
207
+  (handler-bind ((po:po-condition 
208
+		  #'(lambda (con)
209
+		      (format t "Got imap condition: ~a~%" con))))
210
+				       
211
+    (test-connect)
208 212
   
209
-  (test-sends)
213
+    (test-sends)
210 214
 
211
-  (test-flags)
215
+    (test-flags)
212 216
  
213
-  (test-mailboxes)
217
+    (test-mailboxes)
214 218
 
215
-  (test-pop)
219
+    (test-pop)
216 220
   
217 221
   
218
-  )
222
+    ))
219 223
 
220 224
 
221 225
 (if* *do-test* then (do-test :imap #'test-imap))