git.fiddlerwoaroof.com
jkf authored on 21/04/2000 21:52:15
Showing 4 changed files
... ...
@@ -1,3 +1,11 @@
1
+2000-04-21  John Foderaro  <jkf@tiger.franz.com>
2
+versio 1.4
3
+	* imap.cl: added pop commands unique-id and top-lines
4
+	    plus make-envelope-from-text
5
+
6
+	* imap.html - update document
7
+	
8
+
1 9
 1999-11-29  John Foderaro  <jkf@tiger.franz.com>
2 10
 version 1.3
3 11
 	* imap.cl - fixed bug where extra ^b's ended up in strings
... ...
@@ -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.9 2000/04/21 15:03:37 jkf Exp $
22
+;; $Id: imap.cl,v 1.10 2000/04/21 21:52:15 jkf Exp $
23 23
 
24 24
 ;; Description:
25 25
 ;;
... ...
@@ -62,6 +62,7 @@
62 62
    #:fetch-letter
63 63
    #:fetch-parts
64 64
    #:*imap-version-number*
65
+   #:make-envelope-from-text
65 66
    #:mailbox-flags      ; accessor
66 67
    #:mailbox-permanent-flags ; acc
67 68
    #:mailbox-list
... ...
@@ -75,6 +76,8 @@
75 76
    #:make-imap-connection
76 77
    #:make-pop-connection
77 78
    #:noop
79
+   #:top-lines	; pop only
80
+   #:unique-id  ; pop only
78 81
    
79 82
    #:po-condition
80 83
    #:po-condition-indentifier
... ...
@@ -84,6 +87,7 @@
84 87
    #:rename-mailbox
85 88
    #:search-mailbox
86 89
    #:select-mailbox
90
+   
87 91
    )
88 92
   )
89 93
 
... ...
@@ -91,7 +95,7 @@
91 95
 
92 96
 (provide :imap)
93 97
 
94
-(defparameter *imap-version-number* '(:major 1 :minor 3)) ; major.minor
98
+(defparameter *imap-version-number* '(:major 1 :minor 4)) ; major.minor
95 99
 
96 100
 ;; todo
97 101
 ;;  have the list of tags selected done on a per connection basis to
... ...
@@ -512,7 +516,17 @@
512 516
 
513 517
 
514 518
 (defun send-pop-command-get-results (pop command &optional extrap)
515
-  ;; if extrap is true then we're expecting data to follow an +ok
519
+  ;; send the given command to the pop server
520
+  ;; if extrap is true and if the response is +ok, then data
521
+  ;;  will follow the command (up to and excluding the first line consisting 
522
+  ;;  of just a period)
523
+  ;; 
524
+  ;; if the pop server returns an error code we signal a lisp error.
525
+  ;; otherwise
526
+  ;; return
527
+  ;;  extrap is nil -- return the list of tokens on the line after +ok
528
+  ;;  extrap is true -- return the extra object (a big string)
529
+  ;;
516 530
   (format (post-office-socket pop) "~a~a" command *crlf*)
517 531
   (force-output (post-office-socket pop))
518 532
   
... ...
@@ -527,9 +541,15 @@
527 541
 		      :server-string line))
528 542
 
529 543
     (if* extrap
530
-       then ; get the rest of the data
531
-	    
532
-	    (let ((buf (get-line-buffer (+ (car parsed) 50)))
544
+       then ;; get the rest of the data
545
+	    ;; many but not all pop servers return the size of the data
546
+	    ;; after the +ok, so we use that to initially size the 
547
+	    ;; retreival buffer.
548
+	    (let ((buf (get-line-buffer (+ (if* (fixnump (car parsed))
549
+					      then (car parsed) 
550
+					      else 2048 ; reasonable size
551
+						   )
552
+					   50)))
533 553
 		  (pos 0)
534 554
 		  ; states
535 555
 		  ;  1 - after lf
... ...
@@ -539,12 +559,14 @@
539 559
 		  (sock (post-office-socket pop)))
540 560
 	      (flet ((add-to-buffer (ch)
541 561
 		       (if* (>= pos (length buf))
542
-			  then (po-error :unexpected
543
-					 :format-control 
544
-					 "missinfomation from pop"
545
-					 :server-string line)
546
-			  else (setf (schar buf pos) ch)
547
-			       (incf pos))))
562
+			  then ; grow buffer
563
+			       (let ((new-buf (get-line-buffer
564
+					       (* (length buf) 2))))
565
+				 (init-line-buffer new-buf buf)
566
+				 (free-line-buffer buf)
567
+				 (setq buf new-buf)))
568
+		       (setf (schar buf pos) ch)
569
+		       (incf pos)))
548 570
 		(loop
549 571
 		  (let ((ch (read-char sock nil nil)))
550 572
 		    (if* (null ch)
... ...
@@ -744,6 +766,77 @@
744 766
   )
745 767
 
746 768
 
769
+(defmethod unique-id ((pb pop-mailbox) &optional message)
770
+  ;; if message is given, return the unique id of that
771
+  ;; message, 
772
+  ;; if message is not given then return a list of lists:
773
+  ;;  (message  unique-id)
774
+  ;; for all messages not marked as deleted
775
+  ;;
776
+  (if* message
777
+     then (let ((res (send-pop-command-get-results pb
778
+						   (format nil 
779
+							   "UIDL ~d" 
780
+							   message))))
781
+	    (cadr res))
782
+     else ; get all of them
783
+	  (let* ((res (send-pop-command-get-results pb "UIDL" t))
784
+		 (end (length res))
785
+		 kind
786
+		 mnum
787
+		 mid
788
+		 (next 0))
789
+		      
790
+		
791
+	    (let ((coll))
792
+	      (loop
793
+		(multiple-value-setq (kind mnum next) 
794
+		  (get-next-token res next end))
795
+		
796
+		(if* (eq :eof kind) then (return))
797
+		
798
+		(if* (not (eq :number kind))
799
+		   then ; hmm. bogus
800
+			(po-error :unexpected
801
+				  :format-control "uidl returned illegal message number in ~s"
802
+				  :format-arguments (list res)))
803
+		
804
+		; now get message id
805
+		
806
+		(multiple-value-setq (kind mid next)
807
+		    (get-next-token res next end))
808
+		
809
+		(if* (eq :number kind)
810
+		   then ; looked like a number to the tokenizer,
811
+			; make it a string to be consistent
812
+			(setq mid (format nil "~d" mid))
813
+		 elseif (not (eq :string kind))
814
+		   then ; didn't find the uid
815
+			(po-error :unexpected
816
+				  :format-control "uidl returned illegal message id in ~s"
817
+				  :format-arguments (list res)))
818
+		
819
+		(push (list mnum mid) coll))
820
+	      
821
+	      (nreverse coll)))))
822
+
823
+(defmethod top-lines ((pb pop-mailbox) message lines)
824
+  ;; return the header and the given number of top lines of the message
825
+  
826
+  (let ((res (send-pop-command-get-results pb
827
+					   (format nil 
828
+						   "TOP ~d ~d"
829
+						   message
830
+						   lines)
831
+					   t ; extra
832
+					   )))
833
+    res))
834
+			     
835
+			
836
+		
837
+						   
838
+
839
+
747 840
 (defun check-for-success (mb command count extra comment command-string )
748 841
   (declare (ignore mb count extra))
749 842
   (if* (not (eq command :ok))
... ...
@@ -1163,7 +1256,125 @@
1163 1256
 
1164 1257
 
1165 1258
 
1259
+
1260
+
1261
+(defun make-envelope-from-text (text)
1262
+  ;; given at least the headers part of a message return
1263
+  ;; an envelope structure containing the contents
1264
+  ;; This is useful for parsing the headers of things returned by
1265
+  ;; a pop server
1266
+  ;;
1267
+  (let ((next 0)
1268
+	(end (length text))
1269
+	header
1270
+	value
1271
+	kind
1272
+	headers)
1273
+    (labels ((next-header-line ()
1274
+	       ;; find the next header line return
1275
+	       ;; :eof - no more
1276
+	       ;; :start - beginning of header value, header and
1277
+	       ;;	         value set
1278
+	       ;; :continue - continuation of previous header line
1279
+	     
1280
+		       
1281
+	       (let ((state 1)
1282
+		     beginv  ; charpos beginning value
1283
+		     beginh  ; charpos beginning header
1284
+		     ch
1285
+		     )
1286
+		 (tagbody again
1166 1287
 		   
1288
+		   (return-from next-header-line
1289
+		     
1290
+		     (loop  ; for each character
1291
+		       
1292
+		       (if* (>= next end)
1293
+			  then (return :eof))
1294
+		 
1295
+		       (setq ch (char text next))
1296
+		 
1297
+		       (if* (eq ch #\return) 
1298
+			  thenret  ; ignore return, (handle following linefeed)
1299
+			  else (case state
1300
+				 (1 ; no characters seen
1301
+				  (if* (eq ch #\linefeed)
1302
+				     then (incf next)
1303
+					  (return :eof)
1304
+				   elseif (member ch
1305
+						  '(#\space
1306
+						    #\tab))
1307
+				     then ; continuation
1308
+					  (setq state 2)
1309
+				     else (setq beginh next)
1310
+					  (setq state 3)
1311
+					  ))
1312
+				 (2 ; looking for first non blank in value
1313
+				  (if* (eq ch #\linefeed)
1314
+				     then ; empty continuation line, ignore
1315
+					  (go again)
1316
+				   elseif (not (member ch
1317
+						       (member ch
1318
+							       '(#\space
1319
+								 #\tab))))
1320
+				     then ; begin value part
1321
+					  (setq beginv next)
1322
+					  (setq state 4)))
1323
+				 (3 ; reading the header
1324
+				  (if* (eq ch #\linefeed)
1325
+				     then ; bogus header line, ignore
1326
+					  (go again)
1327
+				   elseif (eq ch #\:)
1328
+				     then (setq header
1329
+					    (subseq text beginh next))
1330
+					  (setq state 2)))
1331
+				 (4 ; looking for the end of the value
1332
+				  (if* (eq ch #\linefeed)
1333
+				     then (setq value
1334
+					    (subseq text beginv 
1335
+						    (if* (eq #\return
1336
+							     (char text
1337
+								   (1- next)))
1338
+						       then (1- next)
1339
+						       else next)))
1340
+					  (incf next)
1341
+					  (return (if* header
1342
+						     then :start
1343
+						     else :continue)))))
1344
+			       (incf next))))))))
1345
+					 
1346
+	       
1347
+    
1348
+      (loop ; for each header line
1349
+	(setq header nil)
1350
+	(if* (eq :eof (setq kind (next-header-line)))
1351
+	   then (return))
1352
+	(case kind
1353
+	  (:start (push (cons header value) headers))
1354
+	  (:continue
1355
+	   (if* headers
1356
+	      then ; append to previous one
1357
+		   (setf (cdr (car headers))
1358
+		     (concatenate 'string (cdr (car headers))
1359
+				  " " 
1360
+				  value))))))
1361
+      
1362
+      (make-envelope
1363
+       :date     (cdr (assoc "date" headers :test #'equalp))
1364
+       :subject  (cdr (assoc "subject" headers :test #'equalp))
1365
+       :from     (cdr (assoc "from" headers :test #'equalp))
1366
+       :sender   (cdr (assoc "sender" headers :test #'equalp))
1367
+       :reply-to (cdr (assoc "reply-to" headers :test #'equalp))
1368
+       :to       (cdr (assoc "to" headers :test #'equalp))
1369
+       :cc       (cdr (assoc "cc" headers :test #'equalp))
1370
+       :bcc      (cdr (assoc "bcc" headers :test #'equalp))
1371
+       :in-reply-to (cdr (assoc "in-reply-to" headers :test #'equalp))
1372
+       :message-id (cdr (assoc "message-id" headers :test #'equalp))
1373
+       ))))
1374
+
1375
+		  
1376
+	      
1377
+				 
1167 1378
 	      
1168 1379
 
1169 1380
 
... ...
@@ -1171,8 +1382,8 @@
1171 1382
 
1172 1383
     
1173 1384
 (defmethod get-and-parse-from-imap-server ((mb imap-mailbox))
1174
-  ;; read the next line and parse it.... see parse-imap-response
1175
-  ;; for the return value of this function.
1385
+  ;; read the next line and parse it
1386
+  ;;
1176 1387
   ;;
1177 1388
   (multiple-value-bind (line count)
1178 1389
       (get-line-from-server mb)
... ...
@@ -1188,7 +1399,12 @@
1188 1399
 
1189 1400
 (defmethod get-and-parse-from-pop-server ((mb pop-mailbox))
1190 1401
   ;; read the next line from the pop server
1191
-  ;; return the result of parsing it
1402
+  ;;
1403
+  ;; return 3 values:
1404
+  ;;   :ok or :error 
1405
+  ;;   a list of rest of the tokens on the line
1406
+  ;;   the whole line after the +ok or -err
1407
+
1192 1408
   (multiple-value-bind (line count)
1193 1409
       (get-line-from-server mb)
1194 1410
     
... ...
@@ -1312,9 +1528,10 @@
1312 1528
 
1313 1529
 
1314 1530
 (defun parse-pop-response (line end)
1315
-  ;; return values:
1531
+  ;; return 3 values:
1316 1532
   ;;   :ok or :error 
1317
-  ;;   a list of rest of the tokens on the line
1533
+  ;;   a list of rest of the tokens on the line, the tokens
1534
+  ;;	 being either strings or integers
1318 1535
   ;;   the whole line after the +ok or -err
1319 1536
   ;;
1320 1537
   (let (res lineres result)
... ...
@@ -1359,6 +1576,8 @@
1359 1576
       
1360 1577
       (setf (aref arr #.(char-code #\space)) :space)
1361 1578
       (setf (aref arr #.(char-code #\tab)) :space)
1579
+      (setf (aref arr #.(char-code #\return)) :space)
1580
+      (setf (aref arr #.(char-code #\linefeed)) :space)
1362 1581
       
1363 1582
       (setf (aref arr #.(char-code #\[)) :lbracket)
1364 1583
       (setf (aref arr #.(char-code #\])) :rbracket)
... ...
@@ -1663,8 +1882,17 @@
1663 1882
   (mp:without-scheduling
1664 1883
     (push buff *line-buffers*)))
1665 1884
 
1885
+(defun init-line-buffer (new old)
1886
+  ;; copy old into new
1887
+  (declare (optimize (speed 3)))
1888
+  (dotimes (i (length old))
1889
+    (declare (fixnum i))
1890
+    (setf (schar new i) (schar old i))))
1891
+
1892
+
1893
+  
1666 1894
 
1667
-;;;;;;;
1895
+  ;;;;;;;
1668 1896
 
1669 1897
 ; date functions
1670 1898
 
... ...
@@ -1687,12 +1915,3 @@
1687 1915
 			  
1688 1916
 	  
1689 1917
 		  
1690
-      
1691
-  
1692
-  
1693
-
1694
-
1695
-
1696
-
1697
-
1698
-
... ...
@@ -900,6 +900,15 @@ error to attempt to fetch a letter marked for deletion.</p>
900 900
 
901 901
 <p>&nbsp;</p>
902 902
 
903
+<p><font face="Courier New"><strong>(make-envelope-from-text text)</strong></font></p>
904
+
905
+<p><strong>text</strong> is a string that is the first part of a mail message, including
906
+at least all of the headers lines and the blank line following the headers.&nbsp; This
907
+function parses the header lines and return an <strong>envelope</strong> structure
908
+containing information from the header.&nbsp;&nbsp;&nbsp; </p>
909
+
910
+<p>&nbsp;</p>
911
+
903 912
 <p><font face="Courier New"><strong>(noop mb)</strong></font></p>
904 913
 
905 914
 <p>This is the no-operation command.&nbsp; It is useful for letting the <strong>pop</strong>
... ...
@@ -912,11 +921,28 @@ will contain the current count of messages in the mailbox.</p>
912 921
 
913 922
 <p>&nbsp;</p>
914 923
 
924
+<p><font face="Courier New"><strong>(top-lines mb message line-count)</strong></font></p>
925
+
926
+<p>Return a string that contains all the header lines and the first <strong>line-count</strong>
927
+lines of the body of <strong>message</strong>.&nbsp;&nbsp; To just retrieve the headers a <strong>line-count</strong>
928
+of zero can be given.&nbsp; See the function <strong>make-envelope-from-text</strong> for
929
+a means of reading the information in the header.</p>
930
+
931
+<p>&nbsp;</p>
932
+
933
+<p><font face="Courier New"><strong>(unique-id mb &amp;optional message)</strong></font></p>
934
+
935
+<p>Return the unique indentifier for the given message, or for all non-deleted messages if
936
+<strong>message</strong> is nil.&nbsp;&nbsp; The unique identifier is is a string that is
937
+different for every message.&nbsp;&nbsp; If <strong> </strong>the <strong>message</strong>
938
+argument&nbsp; is not given then this command returns a list of lists where each list
939
+contains two items: the message number and the unique id.</p>
940
+
915 941
 <h1>Cond<a name="conditions"></a>itions</h1>
916 942
 
917 943
 <p>When an unexpected event occurs a condition is signaled.&nbsp;&nbsp; This applies to
918 944
 both the <strong>imap</strong> and <strong>pop</strong> interfaces.&nbsp; There are two
919
-classes of conditions signaled by this package:
945
+classes of conditions signaled by this package: 
920 946
 
921 947
 <ul>
922 948
   <li><strong>po-condition</strong> - this class denotes conditions that need not and in fact
... ...
@@ -926,7 +952,7 @@ classes of conditions signaled by this package:
926 952
     and processed otherwise the next command issued will see messages in response to the
927 953
     previous command.&nbsp;&nbsp; Therefore the user code should never do a non-local-transfer
928 954
     in response to a <strong>po-condition.</strong></li>
929
-  <li><strong>po-error - </strong> this class denotes conditions that will prevent execution
955
+  <li><strong>po-error - </strong>this class denotes conditions that will prevent execution
930 956
     from continuing.&nbsp; If one of these errors is not caught, the interactive debugger will
931 957
     be entered.</li>
932 958
 </ul>
... ...
@@ -177,8 +177,11 @@
177 177
     ; still from before
178 178
     (test-eql 4 (po:mailbox-message-count pb))
179 179
     
180
+    (test-eql 4 (length (po:unique-id pb)))
181
+			 
180 182
     (po:delete-letter pb '(:seq 2 3))
181 183
     
184
+    (test-eql 2 (length (po:unique-id pb)))
182 185
     
183 186
     (test-eql 4 (and :second (po:mailbox-message-count pb)))
184 187
     
... ...
@@ -201,6 +204,9 @@
201 204
     
202 205
     (po:fetch-letter pb 1) ; just make sure there's no error
203 206
     
207
+    (po:top-lines pb 1 1)  ; just make sure there's no error
208
+    (po:make-envelope-from-text (po:top-lines pb 1 0))
209
+    
204 210
     (po:close-connection pb)))
205 211
 
206 212