git.fiddlerwoaroof.com
Browse code

2006-12-11 Ahmon Dancy <dancy@dancy>

layer authored on 11/12/2006 22:45:38
Showing 4 changed files
... ...
@@ -1,3 +1,12 @@
1
+2006-12-11  Ahmon Dancy  <dancy@dancy>
2
+
3
+	* mime-api.cl: New map-over-parts function. 
4
+
5
+	* mime-parse.cl: Updated mime structure parsing code.
6
+
7
+	* mime-transfer-encoding.cl: New: qp-{en,de}code-stream,
8
+	with-decoded-part-body-stream.
9
+
1 10
 2006-11-16  Ahmon Dancy  <dancy@dancy>
2 11
 
3 12
 	bug16495: 
... ...
@@ -1,8 +1,9 @@
1 1
 #+(version= 8 0)
2
-(sys:defpatch "mime" 2
2
+(sys:defpatch "mime" 3
3 3
   "v0: New module.  See documentation.;
4 4
 v1: Improve default transfer encoding determination;
5
-v2: make-mime-part: Default external-format is :utf8."
5
+v2: make-mime-part: Default external-format is :utf8;
6
+v3: add mime structure parsing support."
6 7
   :type :system
7 8
   :post-loadable t)
8 9
 
... ...
@@ -14,7 +15,7 @@ v2: make-mime-part: Default external-format is :utf8."
14 15
   :type :system
15 16
   :post-loadable t)
16 17
 
17
-;; $Id: mime-api.cl,v 1.4 2006/11/17 00:32:07 layer Exp $
18
+;; $Id: mime-api.cl,v 1.5 2006/12/11 22:45:38 layer Exp $
18 19
 
19 20
 (defpackage :net.post-office
20 21
   (:use #:lisp #:excl)
... ...
@@ -24,6 +25,7 @@ v2: make-mime-part: Default external-format is :utf8."
24 25
    #:mime-part-writer
25 26
    #:mime-part-p
26 27
    #:mime-part-constructed-p
28
+   #:map-over-parts
27 29
    
28 30
    ;; macros
29 31
    #:mime-get-header
... ...
@@ -330,6 +332,17 @@ This is a multi-part message in MIME format.~%"))
330 332
   `(excl::with-function-input-stream (,stream #'mime-part-writer-1 ,part)
331 333
      ,@body))
332 334
 
335
+
336
+;; misc
337
+
338
+(defun map-over-parts (part function)
339
+  (funcall function part)
340
+  (if* (multipart-p part)
341
+     then (dolist (p (mime-part-parts part))
342
+	    (map-over-parts p function))
343
+   elseif (message-rfc822-p (mime-part-type part) (mime-part-subtype part))
344
+     then (map-over-parts (mime-part-message part) function)))
345
+
333 346
 ;; Stuff ripped off from aserve
334 347
 
335 348
 (defun split-namestring (file)
... ...
@@ -1,10 +1,11 @@
1
-;; $Id: mime-parse.cl,v 1.1 2006/01/26 23:53:27 dancy Exp $
1
+;; $Id: mime-parse.cl,v 1.2 2006/12/11 22:45:38 layer Exp $
2 2
 
3 3
 (defpackage :net.post-office
4 4
   (:use #:lisp #:excl)
5 5
   (:export
6 6
    #:parse-mime-structure
7 7
    #:mime-dequote
8
+   #:with-part-stream
8 9
    
9 10
    ;; accessors
10 11
    #:mime-part-headers-size
... ...
@@ -24,11 +25,12 @@
24 25
 (eval-when (compile)
25 26
   (declaim (optimize (speed 3))))
26 27
 
28
+(eval-when (compile load eval)
29
+  (require :streamp))
30
+
27 31
 ;;; MIME structure parser.
28 32
 ;;; Ref: RFC2045/2046
29 33
 
30
-(defconstant *whitespace* '(#\space #\tab #\return #\newline))
31
-
32 34
 (defclass mime-part-parsed (mime-part)
33 35
   (
34 36
    (headers-size ;; in bytes. Includes the bytes for the blank line
... ...
@@ -45,20 +47,22 @@
45 47
     ;; This will be a mime-part
46 48
     :accessor mime-part-message :initform nil)))
47 49
 
48
-(excl::defresource mime-line 
49
-  :constructor (lambda () (make-array 16000 :element-type 'character
50
-				      :fill-pointer 0))
51
-  :reinitializer (lambda (x) (setf (fill-pointer x) 0)))
50
+(defmacro get-header (name headers)
51
+  `(cdr (assoc ,name ,headers :test #'equalp)))
52 52
 
53
-;; Return values:
54
-;;  First is the part.  
55
-;;  Second is :eof if end of file was reached or
56
-;;            :boundary if a boundary was reached
57
-;;            nil, otherwise
58
-;;  Third is new position
59
-(defun parse-mime-structure (stream &key boundary digest (pos 0) mbox)
60
-  (parse-mime-structure-1 stream boundary digest pos mbox))
53
+(defun parse-mime-structure (stream &key mbox)
54
+  (multiple-value-bind (part stop newpos)
55
+      (parse-mime-structure-1 stream nil nil 0 mbox)
56
+    (declare (ignore stop))
57
+    (values part newpos)))
58
+
59
+;; Returns values:
60
+;; 1) The part
61
+;; 2) The stop reason (:eof, :close-boundary, nil (meaning regular boundary))
62
+;; 3) The new position
61 63
 
64
+;: mime-parse-message-rfc822, parse-mime-structure, mime-parse-multipart
65
+;: 
62 66
 (defun parse-mime-structure-1 (stream boundary digest pos mbox)
63 67
   (let ((part (make-instance 'mime-part-parsed)))
64 68
     (setf (mime-part-position part) pos)
... ...
@@ -70,45 +74,54 @@
70 74
       (setf (mime-part-body-position part) pos)
71 75
       (setf (mime-part-headers part) headers)
72 76
       
73
-      (let ((content-type (mime-get-header "content-type" part)))
74
-	(setf (mime-part-id part) (mime-get-header "Content-Id" part))
77
+      (let ((content-type (get-header "content-type" headers)))
78
+	(setf (mime-part-id part) (get-header "Content-Id" headers))
75 79
 	(setf (mime-part-description part) 
76
-	  (mime-get-header "Content-description" part))
80
+	  (get-header "Content-description" headers))
77 81
 	(setf (mime-part-encoding part) 
78
-	  (or (mime-get-header "Content-transfer-encoding" part)
82
+	  (or (get-header "Content-transfer-encoding" headers)
79 83
 	      "7bit"))
80 84
 	
81 85
 	(multiple-value-bind (type subtype params)
82 86
 	    (parse-content-type content-type)
83 87
 	  
84 88
 	  (if* (null type)
85
-	     then
86
-		  (if* digest
87
-		     then
88
-			  (setf (mime-part-type part) "message")
89
+	     then (if* digest
90
+		     then (setf (mime-part-type part) "message")
89 91
 			  (setf (mime-part-subtype part) "rfc822")
90 92
 			  (setf (mime-part-parameters part) 
91 93
 			    '(("charset" . "us-ascii")))
92 94
 			  (mime-parse-message-rfc822 part stream boundary pos
93 95
 						     mbox)
94
-		     else
95
-			  (setup-text-plain-part part stream boundary pos mbox))
96
-	     else
97
-		  (setf (mime-part-type part) type)
96
+		     else (setup-text-plain-part part stream boundary pos 
97
+						 mbox))
98
+	     else (setf (mime-part-type part) type)
98 99
 		  (setf (mime-part-subtype part) subtype)
99 100
 		  (setf (mime-part-parameters part) params)
100 101
 		  
101 102
 		  (cond 
102 103
 		   ((equalp type "multipart")
103
-		    (mime-parse-multipart part stream boundary pos mbox))
104
+		    (mime-parse-multipart part stream boundary pos 
105
+					  mbox))
104 106
 		   ((message-rfc822-p type subtype)
105
-		    (mime-parse-message-rfc822 part stream boundary pos mbox))
107
+		    (mime-parse-message-rfc822 part stream boundary pos 
108
+					       mbox))
106 109
 		   (t
107
-		    (mime-parse-non-multipart part stream boundary pos mbox)))))))))
110
+		    (mime-parse-non-multipart part stream boundary pos 
111
+					      mbox)))))))))
112
+
113
+;: skip-whitespace, parse-header-line, parse-headers
114
+;: 
115
+(defmacro whitespace-char-p (char)
116
+  (let ((c (gensym)))
117
+    `(let ((,c ,char))
118
+       (or (char= ,c #\space) (char= ,c #\tab) (char= ,c #\newline)))))
108 119
 
109 120
 ;; OK if 'string' is nil.
110 121
 ;; Might return nil
111 122
 ;; called by parse-mime-structure-1
123
+;: parse-mime-structure-1
124
+;: 
112 125
 (defun parse-content-type (string)
113 126
   (block nil
114 127
     (if (null string)
... ...
@@ -122,7 +135,7 @@
122 135
       
123 136
       (setf pos (skip-whitespace string pos max))
124 137
       
125
-      (if (or (>= pos max) (char/= (char string pos) #\/))
138
+      (if (or (>= pos max) (char/= (schar string pos) #\/))
126 139
 	  (return)) ;; bogus input
127 140
       
128 141
       (multiple-value-setq (subtype pos)
... ...
@@ -133,12 +146,18 @@
133 146
       
134 147
       (values type subtype (parse-parameters string pos max)))))
135 148
 
149
+
150
+
151
+
152
+
136 153
 ;; called by parse-content-type.
154
+;: parse-content-type
155
+;: 
137 156
 (defun parse-parameters (string pos max)
138 157
   (let (char pairs param value)
139 158
     (while (< pos max)
140 159
       (setf pos (skip-whitespace string pos max))
141
-      (setf char (char string pos))
160
+      (setf char (schar string pos))
142 161
       
143 162
       (if (char/= char #\;)
144 163
 	  (return))
... ...
@@ -146,7 +165,7 @@
146 165
       (multiple-value-setq (param pos)
147 166
 	(mime-get-token string (1+ pos) max))
148 167
       (setf pos (skip-whitespace string pos max))
149
-      (if (or (>= pos max) (char/= (char string pos) #\=))
168
+      (if (or (>= pos max) (char/= (schar string pos) #\=))
150 169
 	  (return))
151 170
       (multiple-value-setq (value pos)
152 171
 	(mime-get-parameter-value string (1+ pos) max))
... ...
@@ -160,59 +179,62 @@
160 179
       #\, #\; #\: #\\ #\" 
161 180
       #\/ #\[ #\] #\? #\=))
162 181
 
182
+;: parse-content-type, parse-parameters, mime-get-parameter-value
183
+;: mime-get-token, blank-line-p, parse-header-line
184
+;: 
163 185
 (defun skip-whitespace (string pos max)
164
-  (declare (optimize (speed 3))
186
+  (declare (optimize (speed 3) (safety 0))
165 187
 	   (fixnum pos max))
166 188
   (while (< pos max)
167
-    (if (not (excl::whitespace-char-p (schar string pos)))
189
+    (if (not (whitespace-char-p (schar string pos)))
168 190
 	(return))
169 191
     (incf pos))
170 192
   pos)
171 193
 
194
+;: parse-parameters
195
+;: 
172 196
 (defun mime-get-parameter-value (string pos max)
173 197
   (setf pos (skip-whitespace string pos max))
174 198
   (if* (>= pos max)
175
-     then
176
-	  (values "" pos)
177
-     else
178
-	  (if (char= (char string pos) #\")
199
+     then (values "" pos)
200
+     else (if (char= (schar string pos) #\")
179 201
 	      (mime-get-quoted-string string pos max)
180 202
 	    (mime-get-token string pos max))))
181 203
 
204
+;: parse-content-type, parse-parameters, mime-get-parameter-value
205
+;: 
182 206
 (defun mime-get-token (string pos max)
183 207
   (setf pos (skip-whitespace string pos max))
184 208
   (let ((startpos pos)
185 209
 	char)
186 210
     (while (< pos max)
187
-      (setf char (char string pos))
211
+      (setf char (schar string pos))
188 212
       (if (or (char= #\space char) (member char *mime-tspecials*))
189 213
 	  (return))
190 214
       (incf pos))
191 215
     (values (subseq string startpos pos) pos)))
192 216
 
193 217
 ;; Doesn't attempt to dequote
218
+;: mime-get-parameter-value
219
+;: 
194 220
 (defun mime-get-quoted-string (string pos max)
195 221
   (let ((res (make-string (- max pos)))
196 222
 	(outpos 0)
197 223
 	char inquote inbackslash)
198 224
     (while (< pos max)
199
-      (setf char (char string pos))
225
+      (setf char (schar string pos))
226
+      
227
+      (when (and (char= char #\") (not inbackslash))
228
+	(if* inquote
229
+	   then	(setf (schar res outpos) char)
230
+		(incf outpos)
231
+		(incf pos)
232
+		(return))
233
+	(setf inquote t))
200 234
       
201
-      (if* (and (char= char #\") (not inbackslash))
202
-	 then
203
-	      (if* inquote
204
-		 then
205
-		      (setf (schar res outpos) char)
206
-		      (incf outpos)
207
-		      (incf pos)
208
-		      (return))
209
-	      (setf inquote t))
210
-
211 235
       (if* inbackslash
212
-	 then
213
-	      (setf inbackslash nil)
214
-	 else
215
-	      (if (char= char #\\)
236
+	 then (setf inbackslash nil)
237
+	 else (if (char= char #\\)
216 238
 		  (setf inbackslash t)))
217 239
       
218 240
       (setf (schar res outpos) char)
... ...
@@ -221,9 +243,11 @@
221 243
     
222 244
     (values (subseq res 0 outpos) pos)))
223 245
 
246
+;; mime-parse-multipart
247
+;: 
224 248
 (defun mime-dequote (string)
225 249
   (block nil
226
-    (if (or (string= string "") (char/= (char string 0) #\"))
250
+    (if (or (string= string "") (char/= (schar string 0) #\"))
227 251
 	(return string))
228 252
     
229 253
     (let* ((max (length string))
... ...
@@ -233,40 +257,44 @@
233 257
 	   char inbackslash)
234 258
       
235 259
       (while (< pos max)
236
-	(setf char (char string pos))
260
+	(setf char (schar string pos))
237 261
 	
238 262
 	(if (and (char= char #\") (not inbackslash))
239 263
 	    (return))
240 264
 	
241 265
 	(if* (and (not inbackslash) (char= char #\\))
242
-	   then
243
-		(setf inbackslash t)
266
+	   then	(setf inbackslash t)
244 267
 		(incf pos)
245
-	   else
246
-		(setf (schar res outpos) char)
268
+	   else	(setf (schar res outpos) char)
247 269
 		(incf outpos)
248 270
 		(incf pos)
249 271
 		(setf inbackslash nil)))
250 272
       
251 273
       (subseq res 0 outpos))))
252 274
 
275
+;: parse-mime-structure-1
276
+;: 
253 277
 (defun setup-text-plain-part (part stream boundary pos mbox)
254 278
   (setf (mime-part-type part) "text")
255 279
   (setf (mime-part-subtype part) "plain")
256 280
   (setf (mime-part-parameters part) '(("charset" . "us-ascii")))
257 281
   (mime-parse-non-multipart part stream boundary pos mbox))
258 282
 
283
+;: setup-text-plain-part, parse-mime-structure-1
284
+;: 
259 285
 (defun mime-parse-non-multipart (part stream boundary pos mbox)
260 286
   (let ((startpos pos))
261
-    (multiple-value-bind (endpos lines eof pos)
287
+    (multiple-value-bind (size lines eof pos)
262 288
 	(read-until-boundary stream boundary pos mbox)
263 289
       
264 290
       (setf (mime-part-lines part) lines)
265 291
       (setf (mime-part-body-position part) startpos)
266
-      (setf (mime-part-body-size part) (- endpos startpos))
292
+      (setf (mime-part-body-size part) size)
267 293
       
268 294
       (values part eof pos))))
269 295
 
296
+;: parse-mime-structure-1
297
+;: 
270 298
 (defun mime-parse-message-rfc822 (part stream boundary pos mbox)
271 299
   (let ((startpos pos))
272 300
     (multiple-value-bind (message eof pos)
... ...
@@ -280,6 +308,8 @@
280 308
       (values part eof pos))))
281 309
   
282 310
 
311
+;: parse-mime-structure-1
312
+;: 
283 313
 (defun mime-parse-multipart (part stream parent-boundary pos mbox)
284 314
   (let* ((params (mime-part-parameters part))
285 315
 	 (boundary (cdr (assoc "boundary" params :test #'equalp)))
... ...
@@ -317,152 +347,295 @@
317 347
       (setf (mime-part-body-size part) (- pos startpos))
318 348
       
319 349
       (values part eof pos))))
350
+
351
+
320 352
 ;; support
321 353
 
322
-;; Returns headers alist and the number of bytes read.
323
-(defun parse-headers (stream mbox)
324
-  (declare (optimize (speed 3) (safety 0)))
325
-  (let ((count 0) headers colonpos name value)
326
-    (excl::with-resource (line mime-line)
327
-      (loop
328
-	(let ((bytes (mime-read-line line stream mbox)))
329
-	  (if (null bytes)
330
-	      (return))
331
-	  
332
-	  (incf count bytes)
354
+(defconstant *whitespace* '(#\space #\tab #\return #\newline))
355
+
333 356
 
334
-	  (mime-line-string-right-trim line)
335
-	  (if (string= line "")
336
-	      (return))
357
+;: parse-headers
358
+;: 
359
+(defun blank-line-p (line len)
360
+  (declare (optimize (speed 3) (safety 0))
361
+	   (fixnum len))
362
+  (= len (skip-whitespace line 0 len)))
363
+
364
+;: parse-headers
365
+;: 
366
+(defun parse-header-line (line len)
367
+  (declare (optimize (speed 3) (safety 0)))
368
+  (let ((pos 0)
369
+	colonpos
370
+	spacepos)
371
+    (declare (fixnum len pos spacepos))
372
+    
373
+    (while (< pos len)
374
+      (let ((char (schar line pos))) 
375
+	(when (char= char #\:)
376
+	  (setf colonpos pos)
377
+	  (return))
337 378
 	
338
-	  ;; Continuation line
339
-	  (if* (and (excl::whitespace-char-p (char line 0)) headers)
340
-	     then ;; yes
341
-		  (setf (cdr (car headers)) 
342
-		    (concatenate 'string (cdr (car headers)) " "
343
-				 (string-left-trim *whitespace* line)))
344
-	     else (setf colonpos (position #\: line))
345
-		  (if (null colonpos) ;; bogus input
346
-		      (return))
347
-		  (setf name 
348
-		    (string-trim *whitespace* (subseq line 0 colonpos)))
349
-		  (let ((startpos (position-if-not #'excl::whitespace-char-p
350
-					       line :start (1+ colonpos))))
351
-		    (setf value 
352
-		      (if* (null startpos)
353
-			 then ""
354
-			 else (subseq line startpos))))
355
-
356
-		  (push (cons name value) headers)))))
379
+	(if (and (null spacepos) (whitespace-char-p char))
380
+	    (setf spacepos pos)))
381
+      
382
+      (incf pos))
383
+ 
384
+    (if (null colonpos) ;; bogus header line
385
+	(return-from parse-header-line))
357 386
     
358
-    (values (nreverse headers) count)))
359
-
360
-;; Returns: (1) position of the end of the part
361
-;;          (2) number of lines read
362
-;;          (3) :eof if EOF, :boundary if close delimiter was seen, else nil
363
-;;          (4) new stream position (post boundary read)
364
-(defun read-until-boundary (stream boundary pos mbox)
365
-  (let ((lines 0)
366
-	(lastpos pos)
367
-	bytes delimiter close-delimiter)
387
+    (if (null spacepos)
388
+	(setf spacepos colonpos))
368 389
     
369
-    (excl::with-resource (line mime-line)
390
+    (if (= 0 spacepos) ;; bogus header line (no name)
391
+	(return-from parse-header-line))
370 392
     
371
-      (when boundary
372
-	(setf delimiter (concatenate 'string "--" boundary))
373
-	(setf close-delimiter (concatenate 'string delimiter "--")))
374
-      
375
-      (loop
376
-	(setf bytes (mime-read-line line stream mbox))
393
+    (values (subseq line 0 spacepos)
394
+	    (subseq line (skip-whitespace line (1+ colonpos) len) len))))
395
+
396
+;; Returns offset of end of line in buffer.  Or nil if EOF
397
+;; Second value is the number of characters read (including EOL chars)
398
+;; This is slower than a read-line call, but in the long run can
399
+;; lead to big savings in gc time.
400
+;: parse-headers, read-until-boundary, collect-message-data-from-mbox
401
+;: 
402
+(defun mime-read-line (stream buffer)
403
+  (declare (optimize (speed 3) (safety 0)))
404
+  (let ((pos 0)
405
+	(end (length buffer))
406
+	(count 0)
407
+	char)
408
+    (declare (fixnum pos end count))
409
+    
410
+    (while (and (< pos end) (setf char (read-char stream nil nil)))
411
+      (incf count)
412
+      (if (char= char #\newline)
413
+	  (return))
414
+      (setf (schar buffer pos) char)
415
+      (incf pos))
416
+    
417
+    (if* (= count 0)
418
+       then nil ;; EOF
419
+       else ;; Check for CR/LF combo
420
+	    (if (and (> pos 0) (char= (schar buffer (1- pos)) #\return))
421
+		(decf pos))
422
+	    
423
+	    (values pos count))))
424
+	    
425
+
426
+;; Returns:
427
+;; 1) headers alist
428
+;; 2) # of characters composing the header and terminator.
429
+;:
430
+;: parse-mime-structure-1
431
+;: 
432
+(defun parse-headers (stream mbox)
433
+  (declare (optimize (speed 3) (safety 0)))
434
+  (let ((count 0)
435
+	(line (make-array 1024 :element-type 'character))
436
+	headers 
437
+	lastcons
438
+	current)
439
+    (declare (fixnum count)
440
+	     (dynamic-extent line))
441
+
442
+    (loop
443
+      (multiple-value-bind (end bytes)
444
+	  (mime-read-line stream line)
445
+	(declare (fixnum end))
377 446
 	
378
-	(if (or (null bytes)
379
-		(and delimiter (prefixp delimiter line)))
447
+	(if (or (null end)
448
+		(and mbox (my-prefixp "From " line end)))
380 449
 	    (return))
450
+
451
+	(incf count bytes)
381 452
 	
382
-	(incf pos bytes)
453
+	(if (blank-line-p line end)
454
+	    (return))
383 455
 	
384
-	(setf lastpos pos)
385
-	(incf lines))
386
-      
387
-      (values lastpos 
388
-	      lines
389
-	      (cond ((null bytes)
390
-		     :eof)
391
-		    ((and close-delimiter (prefixp close-delimiter line))
392
-		     :boundary)
393
-		    (t nil))
394
-	      pos))))
456
+	(if* (whitespace-char-p (schar line 0))
457
+	   then ;; Continuation line
458
+		(if (null current)
459
+		    (return)) 
460
+	      
461
+		(let ((newcons (cons (subseq line 0 end) nil)))
462
+		  (setf (cdr lastcons) newcons)
463
+		  (setf lastcons newcons))
464
+	      
465
+	   else ;; Fresh header line
466
+		(multiple-value-bind (name value)
467
+		    (parse-header-line line end)
468
+		  (if (null name)
469
+		      (return)) 
470
+		
471
+		  (setf lastcons (cons value nil))
472
+		  (setf current (cons name lastcons))
473
+		  (push current headers)))))
474
+
475
+    ;; Finalize strings.
476
+    (dolist (header headers)
477
+      (setf (cdr header) (coalesce-header header)))
478
+    
479
+    (values (nreverse headers) count)))
395 480
 
396
-;; Returns values:
397
-;; Number of characters read, including CR/LFs. Returns nil if EOF.
398
-(defun mime-read-line (buffer stream mbox)
481
+;: parse-headers
482
+;: 
483
+(defun coalesce-header (header)
399 484
   (declare (optimize (speed 3) (safety 0)))
400
-  (excl::with-underlying-simple-vector (buffer sbuf)
401
-    (declare (type string sbuf))
402
-    (let* ((pos 0)
403
-	   (count 0)
404
-	   (max (array-dimension buffer 0))
405
-	   (crlf (eq (eol-convention stream) :dos))
406
-	   char)
407
-      (declare (fixnum pos count max))
408
-      (while (and (< pos max) (setf char (read-char stream nil nil)))
409
-	(incf count)
410
-	(when (char= char #\newline)
411
-	  (if crlf
412
-	      (incf count)) ;; account for carriage return as well
485
+  (let ((stringlist (cdr header)))
486
+    (if* (= (length stringlist) 1)
487
+       then (first stringlist)
488
+       else (let ((len 0))
489
+	      (declare (fixnum len))
490
+	      (dolist (string stringlist)
491
+		(incf len (1+ (the fixnum (length string)))))
492
+	      (decf len)
493
+	      (let ((res (make-string len))
494
+		    (pos 0)
495
+		    (first t))
496
+		(declare (fixnum pos))
497
+		(dolist (string stringlist)
498
+		  (if* first
499
+		     then (setf first nil)
500
+		     else (setf (schar res pos) #\newline)
501
+			  (incf pos))
502
+		  (dotimes (n (length string))
503
+		    (declare (fixnum n))
504
+		    (setf (schar res pos) (schar string n))
505
+		    (incf pos)))
506
+		res)))))
507
+
508
+;; Returns: (1) size of part 
509
+;;          (2) number of lines read
510
+;;          (3) stop reason (:eof, :close-boundary, or nil (meaning regular
511
+;;                                                          boundary)
512
+;;          (4) new stream position (post boundary read)
513
+;: mime-parse-multipart, mime-parse-non-multipart
514
+;: 
515
+(defun read-until-boundary (stream boundary pos mbox)
516
+  (declare (optimize (speed 3) (safety 0))
517
+	   (fixnum pos))
518
+  (if* (and (null boundary) (null mbox))
519
+     then 
520
+	  (multiple-value-bind (lines count)
521
+	      (count-lines-to-eof stream)
522
+	    (declare (fixnum count))
523
+	    (values count lines :eof (+ pos count)))
524
+     else 
525
+	  (let ((line (make-array 16000 :element-type 'character))
526
+		(size 0)
527
+		(lines 0)
528
+		(stop-reason :eof)
529
+		delimiter close-delimiter)
530
+	    (declare (dynamic-extent line)
531
+		     (fixnum count size lines))
532
+	    
533
+	    (when boundary
534
+	      (setf delimiter (concatenate 'string "--" boundary))
535
+	      (setf close-delimiter (concatenate 'string delimiter "--")))
536
+	    
537
+	    (loop
538
+	      (multiple-value-bind (end bytes)
539
+		  (mime-read-line stream line)
540
+		(declare (fixnum end bytes))
541
+		
542
+		(if (or (null end)
543
+			(and mbox (my-prefixp "From " line end)))
544
+		    (return))
545
+		
546
+		(incf pos bytes)
547
+		
548
+		(when (my-prefixp delimiter line end)
549
+		  (if* (my-prefixp close-delimiter line end)
550
+		     then (setf stop-reason :close-boundary)
551
+		     else (setf stop-reason nil))
552
+		  (return))
553
+		
554
+		(incf size bytes)
555
+		(incf lines))) 
556
+	    
557
+	    (values size lines stop-reason pos))))
558
+
559
+;; Returns:
560
+;; 1) number of lines
561
+;; 2) number of bytes read
562
+;: read-until-boundary
563
+;: 
564
+(defun count-lines-to-eof (stream)
565
+  (declare (optimize (speed 3) (safety 0)))
566
+  (let ((buffer (make-array 65536 :element-type '(unsigned-byte 8)))
567
+	(lines 0)
568
+	(pos 0)
569
+	(lastbyte -1)
570
+	(count 0)
571
+	end)
572
+    (declare (dynamic-extent buffer)
573
+	     (fixnum lines pos end lastbyte count))
574
+    ;; count 10's
575
+    ;; XXX: The count will be off if the file has CR/LF convention and
576
+    ;; there are bare LFs.  
577
+    (loop
578
+      (setf end (read-vector buffer stream))
579
+      (incf count end)
580
+      
581
+      (if (= end 0)
413 582
 	  (return))
414
-	
415
-	(setf (schar sbuf pos) char)
416
-	(incf pos))
417 583
       
418
-      (setf (fill-pointer buffer) pos)
419
-
420
-      ;; Treat mbox "From " line as EOF 
421
-      (if (and mbox (prefixp "From " buffer))
422
-	  (setf count 0))
584
+      (while (< pos end)
585
+	(if (= (aref buffer pos) 10)
586
+	    (incf lines))
587
+	(incf pos))
423 588
       
424
-      (if (/= count 0) count))))
425
-
426
-(defun mime-line-string-right-trim (line)
427
-  (let ((pos (position-if-not #'excl::whitespace-char-p line :from-end t)))
428
-    (if pos
429
-	(setf (fill-pointer line) (1+ pos)))))
430
-
431
-;;; body streams stuff
432
-
433
-(defun body-stream-func (outstream instream boundary)
434
-  (let ((delimiter (if boundary (concatenate 'string "--" boundary)))
435
-	line)
589
+      (setf lastbyte (aref buffer (1- pos))))
436 590
     
437
-    (while (setf line (read-line instream nil nil))
438
-      (if (and delimiter (prefixp delimiter line))
439
-	  (return))
440
-      
441
-      (write-line line outstream))))
591
+    ;; Count last partial line.
592
+    (if (and (> lastbyte 0) (/= lastbyte 10))
593
+	(incf lines))
594
+    
595
+    (values lines count)))
442 596
 
443
-(defun body-stream-func-with-count (outstream instream count)
597
+(defun my-prefixp (prefix string &optional end)
598
+  (declare (optimize (speed 3) (safety 0)))
599
+  (let ((lenprefix (length prefix))
600
+	(end (or end (length string))))
601
+    (declare (fixnum lenprefix lenstring end))
602
+    (when (>= end lenprefix)
603
+      (dotimes (n lenprefix)
604
+	(declare (fixnum n))
605
+	(if (char/= (schar prefix n) (schar string n))
606
+	    (return-from my-prefixp)))
607
+      t)))
608
+
609
+;;; misc
610
+
611
+(defun stream-to-stream-copy (outstream instream count)
444 612
   (declare (optimize (speed 3))
445 613
 	   (fixnum count))
446
-  (let (char)
447
-    (dotimes (n count)
448
-      (declare (fixnum n))
449
-      (setf char (read-char instream nil nil))
450
-      (if* (null char)
451
-	 then (return)
452
-	 else (write-char char outstream)))))
453
-
454
-
455
-(defmacro with-part-body-stream ((sym instream part &key count) &body body)
456
-  (if* count
457
-     then
458
-	  `(with-function-input-stream (,sym #'body-stream-func-with-count
459
-					     ,instream ,count)
460
-	     ,@body)
461
-     else
462
-	  `(with-function-input-stream (,sym #'body-stream-func
463
-					     ,instream 
464
-					     (mime-part-boundary ,part))
465
-	     ,@body)))
614
+  (let ((buf (make-array 4096 :element-type '(unsigned-byte 8))))
615
+    (declare (dynamic-extent buf))
616
+    (while (> count 0)
617
+      (let ((got (read-sequence buf instream :end (min count 4096))))
618
+	(declare (fixnum got))
619
+	(if (zerop got)
620
+	    (error "Unexpected EOF while reading from ~a" instream))
621
+	(write-sequence buf outstream :end got)
622
+	(decf count got)))))
623
+
624
+;; 'instream' must be positioned appropriately by the caller beforehand.
625
+(defmacro with-part-stream ((sym part instream &key (header t)) &body body)
626
+  (let ((p (gensym))
627
+	(stream (gensym))
628
+	(count (gensym)))
629
+    `(let* ((,p ,part)
630
+	    (,stream ,instream)
631
+	    (,count (mime-part-body-size ,p)))
632
+       (if ,header
633
+	   (incf ,count (mime-part-headers-size ,p)))
634
+       (excl:with-function-input-stream 
635
+	   (,sym #'stream-to-stream-copy ,stream ,count)
636
+	 ,@body))))
637
+	 
638
+
466 639
 
467 640
 ;;; testing
468 641
 
... ...
@@ -1,11 +1,14 @@
1
-;; $Id: mime-transfer-encoding.cl,v 1.3 2006/01/30 18:26:39 layer Exp $
1
+;; $Id: mime-transfer-encoding.cl,v 1.4 2006/12/11 22:45:38 layer Exp $
2 2
 
3 3
 (defpackage :net.post-office
4 4
   (:use #:lisp #:excl)
5
-  (:import-from #:excl #:base64-encode-stream)
5
+  (:import-from #:excl #:base64-encode-stream #:base64-decode-stream)
6 6
   (:export
7 7
    #:base64-encode-stream
8
-   #:qp-encode-stream))
8
+   #:base64-decode-stream
9
+   #:qp-encode-stream
10
+   #:qp-decode-stream
11
+   #:with-decoded-part-body-stream))
9 12
 
10 13
 (in-package :net.post-office)
11 14
 
... ...
@@ -87,75 +90,95 @@
87 90
 	(setf byte 0)
88 91
 	(check-deferred)))))
89 92
 
90
-
91
-#|
92
-
93
+;; Decoding stuff
94
+
95
+;; 'instream' must be positioned at the beginning of the part body 
96
+;; by the caller beforehand.
97
+(defmacro with-decoded-part-body-stream ((sym part instream) &body body)
98
+  (let ((bodystream (gensym))
99
+	(p (gensym))
100
+	(encoding (gensym)))
101
+    `(let* ((,p ,part)
102
+	    (,encoding (mime-part-encoding ,p)))
103
+       (with-part-stream (,bodystream ,p ,instream :header nil)
104
+	 (excl:with-function-input-stream (,sym #'mime-decode-transfer-encoding
105
+						,bodystream
106
+						,encoding)
107
+	   ,@body)))))
108
+					  
93 109
 (defun mime-decode-transfer-encoding (outstream instream encoding)
94
-  (cond
95
-   ((equalp encoding "quoted-printable")
96
-    (decode-quoted-printable outstream instream))
97
-   (t
98
-    (decode-unmodified outstream instream))))
99
-
100
-(defmacro with-decoded-transfer-encoding-stream ((sym instream encoding) &body body)
101
-  `(with-function-input-stream (,sym #'mime-decode-transfer-encoding ,instream 
102
-				     ,encoding)
103
-     ,@body))
104
-
105
-(defun decoded-part-body-stream-func (outstream instream part)
106
-  (with-part-body-stream (part-body-stream instream part)
107
-    (mime-decode-transfer-encoding outstream part-body-stream 
108
-				   (mime-part-encoding part))))
109
-
110
-(defmacro with-decoded-part-body-stream ((sym instream part) &body body)
111
-  `(with-function-input-stream (,sym #'decoded-part-body-stream-func
112
-				     ,instream ,part)
113
-     ,@body))
114
-
115
-
116
-;; The decoders
117
-
118
-(defun decode-unmodified (outstream instream)
119
-  (let (line)
120
-    (while (setf line (read-line instream nil nil))
121
-      (write-line line outstream))))
122
-
123
-(defun decode-quoted-printable (outstream instream)
110
+  (funcall 
111
+   (cond
112
+    ((equalp encoding "quoted-printable")
113
+     #'qp-decode-stream)
114
+    ((equalp encoding "base64")
115
+     #'excl::base64-decode-stream)
116
+    (t
117
+     #'sys:copy-file))
118
+   instream outstream))
119
+
120
+(defun qp-decode-stream (instream outstream)
124 121
   (declare (optimize (speed 3)))
125
-  (let (line max pos char char2 softlinebreak)
126
-    (while (setf line (read-line instream nil nil))
127
-      (setf max (length line))
128
-      (setf pos 0)
129
-      
130
-      (macrolet ((getchar () 
131
-		   `(if* (>= pos max)
132
-		       then (setf softlinebreak t)
133
-			    (return)
134
-		       else (prog1 (schar line pos) (incf pos)))))
122
+  (let ((linebuf (make-array 4096 :element-type 'character))
123
+	pos char char2 softlinebreak)
124
+    (declare (dynamic-extent linebuf)
125
+	     (fixnum pos))
126
+    
127
+    (loop
128
+      (multiple-value-bind (line dummy max)
129
+	  (simple-stream-read-line instream nil nil linebuf)
130
+	(declare (ignore dummy)
131
+		 (fixnum max)
132
+		 (simple-string line))
133
+	(if (null line)
134
+	    (return))
135 135
 	
136
-	(while (< pos max)
137
-	  (setf char (getchar))
138
-	  
139
-	  (if* (char= char #\=)
140
-	     then ;; If EOL occurs during the attempt to get the next
141
-		  ;; two chars, it will be treated as a soft line break.
142
-		  (setf char (getchar))
143
-		  (setf char2 (getchar))
144
-		  
145
-		  (let ((value (logior 
146
-				(ash (or (position char *qp-hex-digits*) -1) 4)
147
-				(or (position char2 *qp-hex-digits*) -1))))
148
-		    (if* (> value -1)
149
-		       then
150
-			    (write-byte value outstream)
151
-		       else
152
-			    ;; We got some bogus input.  Leave it untouched
153
-			    (write-char #\= outstream)
154
-			    (write-char char outstream)
155
-			    (write-char char2 outstream)))
156
-	     else (write-char char outstream)))
136
+	(if (null max)
137
+	    (setf max (length line)))
138
+	
139
+	(setf pos 0)
157 140
 	
158
-	(if* softlinebreak
159
-	   then (setf softlinebreak nil)
160
-	   else (write-char #\newline outstream))))))
161
-|#
141
+	(macrolet ((getchar () 
142
+		     `(if (< pos max)
143
+			  (prog1 (schar line pos) (incf pos))))
144
+		   (decode-dig (char)
145
+		     `(the (integer 0 256) (decode-qp-hex-digit ,char))))
146
+		   
147
+	  (while (< pos max)
148
+	    (setf char (getchar))
149
+	    
150
+	    (if* (eq char #\=)
151
+	       then ;; Check for soft line break.
152
+		    (if* (= pos max)
153
+		       then (setf softlinebreak t)
154
+		       else (setf char (getchar))
155
+			    (setf char2 (getchar))
156
+			    
157
+			    (let ((value (logior 
158
+					  (ash (decode-dig char) 4)
159
+					  (decode-dig char2))))
160
+			      (if* (< value 256)
161
+				 then (write-byte value outstream)
162
+				 else ;; We got some bogus input.  
163
+				      ;; Leave it untouched
164
+				      (write-char #\= outstream)
165
+				      (if char
166
+					  (write-char char outstream))
167
+				      (if char2
168
+					  (write-char char2 outstream)))))
169
+	       else (write-char char outstream)))
170
+	  ;; outside 'while' loop.
171
+	
172
+	  (if* softlinebreak
173
+	     then (setf softlinebreak nil)
174
+	     else (write-char #\newline outstream)))))))
175
+
176
+
177
+(defun decode-qp-hex-digit (char)
178
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
179
+  (if* (char<= #\0 char #\9)
180
+     then (- (the (integer 0 255) (char-code char)) #.(char-code #\0))
181
+   elseif (char<= #\A char #\F)
182
+     then (- (the (integer 0 255) (char-code char)) #.(- (char-code #\A) 10))
183
+     else 256))
184
+