Browse code
2006-12-11 Ahmon Dancy <dancy@dancy>
layer authored on 11/12/2006 22:45:38
Showing 4 changed files
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 |
+ |