Refactor MIME support,
authorTaylor R. Campbell <net/mumble/campbell>
Mon, 8 Sep 2008 03:55:18 +0000 (03:55 +0000)
committerTaylor R. Campbell <net/mumble/campbell>
Mon, 8 Sep 2008 03:55:18 +0000 (03:55 +0000)
- to make sense,
- to be more robust,
- to better reflect the terminology of the RFCs,
- to simplify code that uses MIME bodies,
- to move all generic MIME code into imail-mime.scm, and
- to fix a number of small bugs.

The documentation will need to be updated, since some user-visible
commands are now named with the word `body' rather than `entity'.

Undoubtedly, refactoring added many small bugs, too, still to be
weeded out.

v7/src/imail/ed-ffi.scm
v7/src/imail/imail-core.scm
v7/src/imail/imail-imap.scm
v7/src/imail/imail-mime.scm
v7/src/imail/imail-top.scm
v7/src/imail/imail-util.scm
v7/src/imail/imail.pkg

index 16cf958af87199d5776f0c5d88c185ae8866c535..82241f47c2193164ec576109b4b727be26b6598d 100644 (file)
@@ -1,6 +1,6 @@
 #| -*-Scheme-*-
 
-$Id: ed-ffi.scm,v 1.26 2008/01/30 20:02:09 cph Exp $
+$Id: ed-ffi.scm,v 1.27 2008/09/08 03:55:14 riastradh Exp $
 
 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
     1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
@@ -32,7 +32,7 @@ USA.
     ("imail-core"      (edwin imail))
     ("imail-file"      (edwin imail file-folder))
     ("imail-imap"      (edwin imail imap-folder))
-    ("imail-mime"       (edwin imail mime))
+    ("imail-mime"      (edwin imail mime))
     ("imail-rmail"     (edwin imail file-folder rmail-folder))
     ("imail-summary"   (edwin imail front-end summary))
     ("imail-top"       (edwin imail front-end))
index 9efeb97ab14bfc2593be92ad2daedb07fe5364bd..280a243092c4c810334021429983be0042472241 100644 (file)
@@ -1,6 +1,6 @@
 #| -*-Scheme-*-
 
-$Id: imail-core.scm,v 1.174 2008/08/31 23:02:17 riastradh Exp $
+$Id: imail-core.scm,v 1.175 2008/09/08 03:55:17 riastradh Exp $
 
 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
     1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
@@ -29,82 +29,6 @@ USA.
 
 (declare (usual-integrations))
 \f
-;;;; Properties
-
-(define-class <property-mixin> ()
-  (alist define (accessor modifier)
-        accessor object-properties
-        modifier set-object-properties!
-        initial-value '()))
-
-(define (get-property object key default)
-  (let ((entry (assq key (object-properties object))))
-    (if entry
-       (cdr entry)
-       default)))
-
-(define (store-property! object key datum)
-  (let ((alist (object-properties object)))
-    (let ((entry (assq key alist)))
-      (if entry
-         (set-cdr! entry datum)
-         (set-object-properties! object (cons (cons key datum) alist))))))
-
-(define (remove-property! object key)
-  (set-object-properties! object (del-assq! key (object-properties object))))
-
-;;;; Modification events
-
-(define-class <modification-event-mixin> ()
-  (modification-count define (accessor modifier)
-                     accessor object-modification-count
-                     modifier set-object-modification-count!
-                     initial-value 0)
-  (modification-event define accessor
-                     accessor object-modification-event
-                     initializer make-event-distributor))
-
-(define (receive-modification-events object procedure)
-  (add-event-receiver! (object-modification-event object) procedure))
-
-(define (ignore-modification-events object procedure)
-  (remove-event-receiver! (object-modification-event object) procedure))
-
-(define (object-modified! object type . arguments)
-  (without-interrupts
-   (lambda ()
-     (set-object-modification-count!
-      object
-      (+ (object-modification-count object) 1))))
-  (apply signal-modification-event object type arguments))
-
-(define (signal-modification-event object type . arguments)
-  (if *deferred-modification-events*
-      (set-cdr! *deferred-modification-events*
-               (cons (cons* object type arguments)
-                     (cdr *deferred-modification-events*)))
-      (begin
-       (if imap-trace-port
-           (begin
-             (write-line (cons* 'OBJECT-EVENT object type arguments)
-                         imap-trace-port)
-             (flush-output imap-trace-port)))
-       (event-distributor/invoke! (object-modification-event object)
-                                  object
-                                  type
-                                  arguments))))
-
-(define (with-modification-events-deferred thunk)
-  (let ((events (list 'EVENTS)))
-    (let ((v
-          (fluid-let ((*deferred-modification-events* events))
-            (thunk))))
-      (for-each (lambda (event) (apply signal-modification-event event))
-               (reverse! (cdr events)))
-      v)))
-
-(define *deferred-modification-events* #f)
-\f
 ;;;; URL type
 
 (define-class <url> (<property-mixin>)
@@ -646,19 +570,24 @@ USA.
 (define-generic message-internal-time (message))
 (define-generic message-length (message))
 
-(define-generic message-body (message))
-(define-method message-body ((message <message>))
-  (let ((string (call-with-output-string
-                  (lambda (output-port)
-                    (write-message-body message output-port)))))
-    (values string 0 (string-length string))))
-
 (define (message-index message)
   (let ((index (%message-index message))
        (folder (message-folder message)))
     (if folder
        (unmap-folder-index folder index)
        index)))
+
+;;; Messages are MIME entities.
+
+(define-method mime-entity? ((message <message>))
+  message                               ;ignore
+  #t)
+
+(define-method mime-entity-header-fields ((message <message>))
+  (message-header-fields message))
+
+(define-method write-mime-entity-body ((message <message>) port)
+  (write-message-body message port))
 \f
 (define %set-message-flags!
   (let ((modifier (slot-modifier <message> 'FLAGS)))
@@ -1204,229 +1133,3 @@ USA.
 
 (define internal-header-field-prefix-length
   (string-length internal-header-field-prefix))
-\f
-;;;; MIME structure
-
-(define-generic mime-message-body-structure (message))
-(define-generic write-mime-message-body-part (message selector cache? port))
-
-(define-class <mime-body> (<property-mixin>)
-  (parameters define accessor)
-  (disposition define accessor)
-  (language define accessor)
-  (enclosure define standard initial-value #f))
-
-(define-generic mime-body-type (body))
-(define-generic mime-body-subtype (body))
-
-(define (mime-body-type-string body)
-  (string-append (symbol->string (mime-body-type body))
-                "/"
-                (symbol->string (mime-body-subtype body))))
-
-(define (mime-body-parameter body key default)
-  (let ((entry (assq key (mime-body-parameters body))))
-    (if entry
-       (cdr entry)
-       default)))
-
-(define (mime-body-disposition-filename body)
-  (let ((disposition (mime-body-disposition body)))
-    (and disposition
-        (let ((entry (assq 'FILENAME (cdr disposition))))
-          (and entry
-               (cdr entry))))))
-
-(define-method write-instance ((body <mime-body>) port)
-  (write-instance-helper 'MIME-BODY body port
-    (lambda ()
-      (write-char #\space port)
-      (write-string (mime-body-type-string body) port))))
-
-(define (mime-body-enclosed? b1 b2)
-  (or (eq? b1 b2)
-      (let ((enclosure (mime-body-enclosure b1)))
-       (and enclosure
-            (mime-body-enclosed? enclosure b2)))))
-\f
-(define-class <mime-body-one-part> (<mime-body>)
-  (id define accessor)
-  (description define accessor)
-  (encoding define accessor)
-  (n-octets define accessor)
-  (md5 define accessor))
-
-(define-class (<mime-body-message>
-              (constructor (parameters id description encoding n-octets
-                                       envelope body n-lines
-                                       md5 disposition language)))
-    (<mime-body-one-part>)
-  (envelope define accessor)           ;<mime-envelope> instance
-  (body define accessor)               ;<mime-body> instance
-  (n-lines define accessor))
-
-(define-method mime-body-type ((body <mime-body-message>)) body 'MESSAGE)
-(define-method mime-body-subtype ((body <mime-body-message>)) body 'RFC822)
-
-(define-class (<mime-body-text>
-              (constructor (subtype parameters id description encoding
-                                    n-octets n-lines
-                                    md5 disposition language)))
-    (<mime-body-one-part>)
-  (subtype accessor mime-body-subtype)
-  (n-lines define accessor))
-
-(define-method mime-body-type ((body <mime-body-text>)) body 'TEXT)
-
-(define-class (<mime-body-basic>
-              (constructor (type subtype parameters id description encoding
-                                 n-octets md5 disposition language)))
-    (<mime-body-one-part>)
-  (type accessor mime-body-type)
-  (subtype accessor mime-body-subtype))
-
-(define-class (<mime-body-multipart>
-              (constructor (subtype parameters parts disposition language)))
-    (<mime-body>)
-  (subtype accessor mime-body-subtype)
-  (parts define accessor))
-
-(define-method mime-body-type ((body <mime-body-multipart>)) body 'MULTIPART)
-
-(define-class (<mime-envelope>
-              (constructor (date subject from sender reply-to to cc bcc
-                                 in-reply-to message-id)))
-    ()
-  (date define accessor)
-  (subject define accessor)
-  (from define accessor)
-  (sender define accessor)
-  (reply-to define accessor)
-  (to define accessor)
-  (cc define accessor)
-  (bcc define accessor)
-  (in-reply-to define accessor)
-  (message-id define accessor))
-
-(define-class (<mime-address> (constructor (name source-route mailbox host)))
-    ()
-  (name define accessor)
-  (source-route define accessor)
-  (mailbox define accessor)
-  (host define accessor))
-\f
-;;;; MIME Encoding Registry
-;;; This should probably be moved to the runtime's MIME codec package.
-
-(define-structure (mime-encoding
-                   (conc-name mime-encoding/)
-                   (print-procedure
-                    (standard-unparser-method 'MIME-ENCODING
-                      (lambda (encoding output-port)
-                        (write-char #\space output-port)
-                        (write (mime-encoding/name encoding) output-port))))
-                   (constructor %make-mime-encoding))
-  (name                          #f read-only #t)
-  (identity?                     #f read-only #t)
-  (encoder-initializer           #f read-only #t)
-  (encoder-finalizer             #f read-only #t)
-  (encoder-updater               #f read-only #t)
-  (decoder-initializer           #f read-only #t)
-  (decoder-finalizer             #f read-only #t)
-  (decoder-updater               #f read-only #t)
-  (decoding-port-maker           #f read-only #t)
-  (caller-with-decoding-port     #f read-only #t))
-
-(define-guarantee mime-encoding "MIME codec")
-
-(define mime-encodings
-  (make-eq-hash-table))
-
-(define (define-mime-encoding name
-                             encode:initialize encode:finalize encode:update
-                             decode:initialize decode:finalize decode:update
-                             make-port call-with-port)
-  (hash-table/put!
-   mime-encodings
-   name
-   (%make-mime-encoding name #f
-                       encode:initialize encode:finalize encode:update
-                       decode:initialize decode:finalize decode:update
-                       make-port call-with-port))
-  name)
-
-(define (define-identity-mime-encoding name)
-  (hash-table/put! mime-encodings
-                  name
-                  (%make-mime-encoding name #t
-
-                                       (lambda (port text?) text? port)
-                                       output-port/flush-output
-                                       output-port/write-string
-
-                                       (lambda (port text?) text? port)
-                                       output-port/flush-output
-                                       output-port/write-string
-
-                                       (lambda (port text?) text? port)
-                                       (lambda (port text? generator)
-                                         text?
-                                         (generator port)))))
-
-(define (named-mime-encoding name)
-  (or (hash-table/get mime-encodings name #f)
-      (let ((encoding (make-unknown-mime-encoding name)))
-       (hash-table/put! mime-encodings name encoding)
-       encoding)))
-
-(define (make-unknown-mime-encoding name)
-  (let ((lose (lambda args args (error "Unknown MIME encoding name:" name))))
-    (%make-mime-encoding name #f
-                        lose lose lose
-                        lose lose lose
-                        lose lose)))
-
-(define (call-with-mime-decoding-output-port encoding port text? generator)
-  ((mime-encoding/caller-with-decoding-port
-    (if (symbol? encoding)
-       (named-mime-encoding encoding)
-       (begin
-         (guarantee-mime-encoding encoding
-                                  'CALL-WITH-MIME-DECODING-OUTPUT-PORT)
-         encoding)))
-   port text? generator))
-\f
-(define-identity-mime-encoding '7BIT)
-(define-identity-mime-encoding '8BIT)
-(define-identity-mime-encoding 'BINARY)
-;; Next two are random values sometimes used by Outlook.
-(define-identity-mime-encoding '7-BIT)
-(define-identity-mime-encoding '8-BIT)
-
-(define-mime-encoding 'QUOTED-PRINTABLE
-  encode-quoted-printable:initialize
-  encode-quoted-printable:finalize
-  encode-quoted-printable:update
-  decode-quoted-printable:initialize
-  decode-quoted-printable:finalize
-  decode-quoted-printable:update
-  make-decode-quoted-printable-port
-  call-with-decode-quoted-printable-output-port)
-
-(define-mime-encoding 'BASE64
-  encode-base64:initialize
-  encode-base64:finalize
-  encode-base64:update
-  decode-base64:initialize
-  decode-base64:finalize
-  decode-base64:update
-  make-decode-base64-port
-  call-with-decode-base64-output-port)
-
-(define-mime-encoding 'BINHEX40
-  #f #f #f                              ;No BinHex encoder.
-  decode-binhex40:initialize
-  decode-binhex40:finalize
-  decode-binhex40:update
-  make-decode-binhex40-port
-  call-with-decode-binhex40-output-port)
index c92e977776324cbf43d2737edb25dbc1f2be316a..cc6588d679902e0a983fad6cc68f0bc60e2e8451 100644 (file)
@@ -1,6 +1,6 @@
 #| -*-Scheme-*-
 
-$Id: imail-imap.scm,v 1.232 2008/09/02 17:19:10 riastradh Exp $
+$Id: imail-imap.scm,v 1.233 2008/09/08 03:55:18 riastradh Exp $
 
 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
     1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
@@ -357,7 +357,7 @@ USA.
         url
         ;; Some IMAP servers don't like a mailbox of `/%' in LIST
         ;; commands, and others simply returna uselessly empty
-         ;; result, so we have a special case for the root mailbox.
+        ;; result, so we have a special case for the root mailbox.
         (if (string=? prefix "/")
             "%"
             (string-append (imap-mailbox/url->server url prefix) "%"))))
@@ -372,7 +372,7 @@ USA.
                          ;; container URL as an answer to the LIST
                          ;; command, but it is uninteresting here, so
                          ;; we filter it out.  (Should this filtering
-                          ;; be done by RUN-LIST-COMMAND?)
+                         ;; be done by RUN-LIST-COMMAND?)
                          (if (eq? container-url url)
                              results
                              (cons container-url results))))
@@ -1123,13 +1123,11 @@ USA.
   (length)
   (envelope)
   (bodystructure)
-  (body-parts define standard initial-value '())
+  (body-parts define standard initializer (lambda () (weak-cons #f '())))
   (cached-keywords define standard initial-value '()))
 
 (define-generic imap-message-uid (message))
-(define-generic imap-message-length (message))
 (define-generic imap-message-envelope (message))
-(define-generic imap-message-bodystructure (message))
 
 (define-method set-message-flags! ((message <imap-message>) flags)
   (with-imap-message-open message
@@ -1178,12 +1176,6 @@ USA.
 (define-method message-internal-time ((message <imap-message>))
   (fetch-one-message-item message 'INTERNALDATE "internal date"))
 
-(define-method message-length ((message <imap-message>))
-  (with-imap-message-open message
-    (lambda (connection)
-      connection
-      (imap-message-length message))))
-
 (define (with-imap-message-open message receiver)
   (let ((folder (message-folder message)))
     (if folder
@@ -1237,8 +1229,8 @@ USA.
             (guarantee-slot-initialized message initpred noun keywords)
             (accessor message))))))
   (reflector message-flags 'FLAGS "flags" '(FLAGS))
-  (reflector imap-message-length 'LENGTH "length" '(RFC822.SIZE))
-  (reflector imap-message-bodystructure 'BODYSTRUCTURE "MIME structure"
+  (reflector message-length 'LENGTH "length" '(RFC822.SIZE))
+  (reflector mime-entity-body-structure 'BODYSTRUCTURE "MIME structure"
             '(BODYSTRUCTURE)))
 \f
 ;;; Some hair to keep weak references to header fields and envelopes,
@@ -1310,13 +1302,13 @@ USA.
         (lambda (index message)
           (if (zero? (remainder index 10))
               (imail-ui:progress-meter index length))
-          (cond ((imap-message-bodystructure message)
+          (cond ((mime-entity-body-structure message)
                  => (lambda (body-structure)
                       (walk-mime-body message body-structure
-                        (lambda (selector)
+                        (lambda (body-part)
                           (fetch-message-body-part-to-cache
                            message
-                           (mime-selector->imap-section selector))))))
+                           (imap-mime-body-section-text body-part))))))
                 (else
                  (fetch-message-body-part-to-cache message '(TEXT))))))))))
 
@@ -1400,59 +1392,184 @@ USA.
 \f
 ;;;; MIME support
 
-(define-method mime-message-body-structure ((message <imap-message>))
-  (imap-message-bodystructure message))
-
+(define-class <imap-mime-body> ()
+  (message define accessor)
+  (section define accessor)
+  (header-fields))
+
+(let ((accessor (slot-accessor <imap-mime-body> 'HEADER-FIELDS))
+      (modifier (slot-modifier <imap-mime-body> 'HEADER-FIELDS))
+      (initpred (slot-initpred <imap-mime-body> 'HEADER-FIELDS)))
+  (define (fetch body store)
+    (let ((value
+          (lines->header-fields
+           (string->lines
+            (fetch-message-body-part
+             (imap-mime-body-message body)
+             `(,@(imap-mime-body-section body) MIME))))))
+      (store value)
+      value))
+  (define-method mime-body-header-fields ((body <imap-mime-body>))
+    (if (initpred body)
+       (let* ((pair (accessor body))
+              (header-fields (weak-car pair)))
+         (if (weak-pair/car? pair)
+             header-fields
+             (fetch body
+                    (lambda (header-fields)
+                      (weak-set-car! pair header-fields)))))
+       (fetch body
+              (lambda (header-fields)
+                (modifier body (weak-cons header-fields '())))))))
+
+(define-class (<imap-mime-body-basic>
+              (constructor (message
+                            section
+                            type subtype parameters id description encoding
+                            n-octets
+                            md5 disposition language)))
+    (<mime-body-basic> <imap-mime-body>))
+
+(define-class (<imap-mime-body-text>
+              (constructor (message
+                            section
+                            subtype parameters id description encoding
+                            n-octets n-lines md5 disposition language)))
+    (<mime-body-text> <imap-mime-body>))
+
+(define-class (<imap-mime-body-message>
+              (constructor (message
+                            section
+                            parameters id description encoding n-octets
+                            envelope body n-lines md5 disposition language)))
+    (<mime-body-message> <imap-mime-body>))
+
+(define-class (<imap-mime-body-multipart>
+              (constructor (message
+                            section
+                            subtype parameters parts disposition language)))
+    (<mime-body-multipart> <imap-mime-body>))
+\f
 (define-method write-message-body ((message <imap-message>) port)
-  (write-mime-message-body-part
-   message '(TEXT) (imap-message-length message) port))
-
-(define (mime-selector->imap-section selector)
-  (if (pair? selector)
-      (map (lambda (x)
-            (if (exact-nonnegative-integer? x)
-                (+ x 1)
-                x))
-          selector)
-      '(TEXT)))
-
-(define-method write-mime-message-body-part
-    ((message <imap-message>) selector cache? port)
-  (let ((section (mime-selector->imap-section selector)))
-    (let ((entry
-          (list-search-positive (imap-message-body-parts message)
-            (lambda (entry)
-              (equal? (car entry) section)))))
-      (cond (entry
-            (write-string (cdr entry) port))
-           ((and cache?
-                 (let ((limit (imail-ui:body-cache-limit message)))
-                   (and limit
-                        (if (and (exact-nonnegative-integer? cache?)
-                                 (exact-nonnegative-integer? limit))
-                            (< cache? limit)
-                            #t))))
-            (let ((part (fetch-message-body-part message section)))
-              (set-imap-message-body-parts!
-               message
-               (cons (cons section part)
-                     (imap-message-body-parts message)))
-              (write-string part port)))
-           (else
-            (fetch-message-body-part-to-port message section port))))))
+  (write-imap-message-section message '(TEXT) (message-length message) port))
+
+(define-method write-mime-body ((body <imap-mime-body>) port)
+  (write-imap-message-section
+   (imap-mime-body-message body)
+   (imap-mime-body-section-text body)
+   ;++ Kludge.  The IMAP includes the length in octets only for
+   ;++ one-part bodies.
+   (and (mime-body-one-part? body)
+       (mime-body-one-part-n-octets body))
+   port))
+
+(define (imap-mime-body-section-text body)
+  `(,@(imap-mime-body-section body)
+    ,@(if (let ((enclosure (mime-body-enclosure body)))
+           (or (not enclosure)
+               (mime-body-message? enclosure)))
+         '(TEXT)
+         '())))
+
+(define-method mime-body-message-header-fields ((body <mime-body-message>))
+  (lines->header-fields
+   (string->lines
+    (call-with-output-string
+      (lambda (port)
+       (write-imap-message-section (imap-mime-body-message body)
+                                   `(,@(imap-mime-body-section body) HEADER)
+                                   #f
+                                   port))))))
+
+(define (write-imap-message-section message section length port)
+  (cond ((search-imap-message-body-parts message section)
+        => (lambda (entry)
+             (write-string (cdr entry) port)))
+       ((and length
+             (let ((limit (imail-ui:body-cache-limit message)))
+               (and limit
+                    (if (and (exact-nonnegative-integer? length)
+                             (exact-nonnegative-integer? limit))
+                        (< length limit)
+                        #t))))
+        (let ((part (fetch-message-body-part message section)))
+          (cache-imap-message-body-part message section part)
+          (write-string part port)))
+       (else
+        (fetch-message-body-part-to-port message section port))))
+
+(define (search-imap-message-body-parts message section)
+  (define (scan-positive body-parts previous)
+    (and (weak-pair? body-parts)
+        (let ((entry (weak-car body-parts)))
+          (if entry
+              (if (equal? section (car entry))
+                  entry
+                  (scan-positive (weak-cdr body-parts) body-parts))
+              (scan-negative (weak-cdr body-parts) previous)))))
+  (define (scan-negative body-parts previous)
+    (if (weak-pair? body-parts)
+       (let ((entry (weak-car body-parts)))
+         (if entry
+             (begin
+               (weak-set-cdr! previous body-parts)
+               (if (equal? section (car entry))
+                   entry
+                   (scan-positive (weak-cdr body-parts) body-parts)))
+             (scan-negative (weak-cdr body-parts) previous)))
+       (begin
+         (weak-set-cdr! previous '())
+         #f)))
+  (let ((initial (imap-message-body-parts message)))
+    (scan-positive (weak-cdr initial) initial)))
+
+(define (cache-imap-message-body-part message section part)
+  (let ((pair (imap-message-body-parts message)))
+    (weak-set-cdr! pair (weak-cons (cons section part) (weak-cdr pair)))))
 \f
-(define (parse-mime-body body)
-  (cond ((not (and (pair? body) (list? body))) (parse-mime-body:lose body))
-       ((string? (car body)) (parse-mime-body:one-part body))
-       ((pair? (car body)) (parse-mime-body:multi-part body))
-       (else (parse-mime-body:lose body))))
+(define (parse-mime-body body message section)
+  (cond ((not (and (pair? body) (list? body)))
+        (parse-mime-body:lose body message section))
+       ((string? (car body))
+        (parse-mime-body:one-part body message section))
+       ((pair? (car body))
+        (parse-mime-body:multi-part body message section))
+       (else
+        (parse-mime-body:lose body message section))))
 
-(define (parse-mime-body:one-part body)
+(define (parse-mime-body:multi-part body message section)
+  (let loop ((tail body) (index 0))
+    (if (not (pair? tail))
+       (parse-mime-body:lose body))
+    (if (string? (car tail))
+       (let ((enclosed
+              (map (lambda (body index)
+                     (parse-mime-body body message `(,@section ,index)))
+                   (sublist body 0 index)
+                   (iota index 1)))
+             (extensions
+              (parse-mime-body:extensions (cdr tail))))
+         (let ((enclosure
+                (make-imap-mime-body-multipart message
+                                               section
+                                               (intern (car tail))
+                                               (parse-mime-parameters
+                                                (car extensions))
+                                               enclosed
+                                               (cadr extensions)
+                                               (caddr extensions))))
+           (for-each (lambda (enclosed)
+                       (set-mime-body-enclosure! enclosed enclosure))
+                     enclosed)
+           enclosure))
+       (loop (cdr tail) (fix:+ index 1)))))
+\f
+(define (parse-mime-body:one-part body message section)
   (let ((n (length body)))
     (cond ((string-ci=? "text" (car body))
           (if (not (fix:>= n 8))
-              (parse-mime-body:lose body))
-          (apply make-mime-body-text
+              (parse-mime-body:lose body message section))
+          (apply make-imap-mime-body-text message section
                  (intern (list-ref body 1))
                  (parse-mime-parameters (list-ref body 2))
                  (list-ref body 3)
@@ -1464,10 +1581,11 @@ USA.
          ((and (string-ci=? "message" (car body))
                (string-ci=? "rfc822" (cadr body)))
           (if (not (fix:>= n 10))
-              (parse-mime-body:lose body))
-          (let* ((enclosed (parse-mime-body (list-ref body 8)))
+              (parse-mime-body:lose body message section))
+          (let* ((enclosed
+                  (parse-mime-body (list-ref body 8) message section))
                  (enclosure
-                  (apply make-mime-body-message
+                  (apply make-imap-mime-body-message message section
                          (parse-mime-parameters (list-ref body 2))
                          (list-ref body 3)
                          (list-ref body 4)
@@ -1481,8 +1599,8 @@ USA.
             enclosure))
          (else
           (if (not (fix:>= n 7))
-              (parse-mime-body:lose body))
-          (apply make-mime-body-basic
+              (parse-mime-body:lose body message section))
+          (apply make-imap-mime-body-basic message section
                  (intern (list-ref body 0))
                  (intern (list-ref body 1))
                  (parse-mime-parameters (list-ref body 2))
@@ -1491,26 +1609,6 @@ USA.
                  (intern (list-ref body 5))
                  (list-ref body 6)
                  (parse-mime-body:extensions (list-tail body 7)))))))
-
-(define (parse-mime-body:multi-part body)
-  (let loop ((tail body) (index 0))
-    (if (not (pair? tail))
-       (parse-mime-body:lose body))
-    (if (string? (car tail))
-       (let ((enclosed (map parse-mime-body (sublist body 0 index)))
-             (extensions (parse-mime-body:extensions (cdr tail))))
-         (let ((enclosure
-                (make-mime-body-multipart (intern (car tail))
-                                          (parse-mime-parameters
-                                           (car extensions))
-                                          enclosed
-                                          (cadr extensions)
-                                          (caddr extensions))))
-           (for-each (lambda (enclosed)
-                       (set-mime-body-enclosure! enclosed enclosure))
-                     enclosed)
-           enclosure))
-       (loop (cdr tail) (fix:+ index 1)))))
 \f
 (define (parse-mime-body:extensions tail)
   (if (pair? tail)
@@ -1522,8 +1620,8 @@ USA.
          (list (car tail) #f #f))
       (list #f #f #f)))
 
-(define (parse-mime-body:lose body)
-  (error "Unrecognized MIME bodystructure:" body))
+(define (parse-mime-body:lose body message section)
+  (error "Unrecognized MIME bodystructure:" body message section))
 
 (define (parse-mime-parameters parameters)
   (if parameters
@@ -1719,15 +1817,15 @@ USA.
                        (cons keyword
                              (if (memq keyword imap-dynamic-keywords)
                                  '()
-                                  (let ((pathname
-                                         (message-item-pathname message
+                                 (let ((pathname
+                                        (message-item-pathname message
                                                                keyword)))
-                                    (if (file-exists? pathname)
-                                        (list
-                                         (read-cached-message-item message
-                                                                   keyword
-                                                                   pathname))
-                                        '())))))
+                                   (if (file-exists? pathname)
+                                       (list
+                                        (read-cached-message-item message
+                                                                  keyword
+                                                                  pathname))
+                                       '())))))
                      keywords)))
            (let ((uncached
                   (list-transform-positive alist
@@ -1784,7 +1882,7 @@ USA.
 \f
 (define (fetch-message-body-part-to-cache message section)
   (let ((cache-keyword (imap-body-section->keyword section))
-        (imap-keyword (imap-body-section->keyword/peek section)))
+       (imap-keyword (imap-body-section->keyword/peek section)))
     (with-folder-locked (message-folder message)
       (lambda ()
        (let ((pathname (message-item-pathname message cache-keyword)))
@@ -1866,7 +1964,7 @@ USA.
 
 (define (%imap-body-section->keyword section prefix)
   (string-append prefix
-                 "["
+                "["
                 (decorated-string-append
                  "" "." ""
                  (map (lambda (x)
@@ -2704,7 +2802,8 @@ USA.
 (define (process-fetch-attribute message keyword datum)
   (case keyword
     ((BODYSTRUCTURE)
-     (%set-imap-message-bodystructure! message (parse-mime-body datum)))
+     (%set-imap-message-bodystructure! message
+                                      (parse-mime-body datum message '())))
     ((FLAGS)
      (%set-message-flags! message (map imap-flag->imail-flag datum)))
     ((RFC822.SIZE)
index 2438c64c86bc1aa0f4755a5add4337f9c94c33c5..6ea4081e15f85d27f4ace4366735deb5de70bfee 100644 (file)
@@ -1,8 +1,11 @@
 #| -*-Scheme-*-
 
-$Id: imail-mime.scm,v 1.11 2008/08/15 15:44:37 riastradh Exp $
+$Id: imail-mime.scm,v 1.12 2008/09/08 03:55:18 riastradh Exp $
 
-Copyright 2005 Taylor Campbell
+Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
+    1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
+    2006, 2007, 2008 Massachusetts Institute of Technology
+Copyright (C) 2005, 2006, 2007, 2008 Taylor R. Campbell
 
 This file is part of MIT/GNU Scheme.
 
@@ -27,121 +30,282 @@ USA.
 
 (declare (usual-integrations))
 \f
-(define-method mime-message-body-structure ((message <message>))
-  (or (get-property message 'MIME-MESSAGE-BODY-STRUCTURE #f)
-      (cond ((mime:get-version-header message)
-             => (lambda (version-string)
-                  (if (mime:version-1.0? version-string)
-                      (let ((body-structure
-                             (mime:parse-body-structure message)))
-                        (store-property! message
-                                         'MIME-MESSAGE-BODY-STRUCTURE
-                                         body-structure)
-                        body-structure)
-                      (error "MIME version not 1.0:"
-                             version-string
-                             message))))
-            (else #f))))
-
-(define (mime:parse-body-structure message)
-  (let ((content-type (mime:get-content-type message))
-        (encoding (mime:get-content-transfer-encoding message)))
+;;;; MIME Entities
+
+;;; Any kind of object can be a MIME entity, provided that it
+;;; implements MIME-ENTITY-BODY-STRUCTURE.  A default method is
+;;; provided if it instead implements MIME-ENTITY-HEADER-FIELDS and
+;;; either MIME-ENTITY-BODY-SUBSTRING or WRITE-ENTITY-MIME-BODY, which
+;;; yield the literal text of the entity's body without decoding or
+;;; interpretation.  MIME-ENTITY-BODY-STRUCTURE should return a
+;;; <MIME-BODY> instance.
+;;;
+;;; The reason that we do not have a specific class for MIME entities
+;;; is that many objects are implicitly MIME entities, such as RFC
+;;; (2)822 messages, whose header may contain MIME header fields and
+;;; whose body may be a MIME body, but which may otherwise have other
+;;; structure unrelated to MIME.
+
+(define-generic mime-entity? (object))
+(define-generic mime-entity-header-fields (mime-entity))
+(define-generic mime-entity-body-structure (mime-entity))
+(define-generic mime-entity-body-substring (mime-entity))
+(define-generic write-mime-entity-body (mime-entity port))
+
+(define-method mime-entity? (object) object #f)
+
+(define-guarantee mime-entity "MIME entity")
+
+(define-method mime-entity-body-substring (mime-entity)
+  (guarantee-mime-entity mime-entity 'MIME-ENTITY-BODY-SUBSTRING)
+  (let ((string
+        (call-with-output-string
+          (lambda (output-port)
+            (write-mime-entity-body mime-entity output-port)))))
+    (values string 0 (string-length string))))
+
+(define-method write-mime-entity-body (mime-entity port)
+  (guarantee-mime-entity mime-entity 'WRITE-MIME-ENTITY-BODY)
+  (receive (string start end) (mime-entity-body-substring mime-entity)
+    (write-substring string start end port)))
+\f
+;;;; MIME Bodies
+
+;;; A MIME body is an instance of a subclass of <MIME-BODY>.  It must
+;;; implement MIME-BODY-TYPE, MIME-BODY-SUBTYPE,
+;;; MIME-BODY-HEADER-FIELDS, and either MIME-BODY-SUBSTRING or
+;;; WRITE-MIME-BODY.
+
+(define-class <mime-body> (<property-mixin>)
+  (parameters define accessor)
+  (disposition define accessor)
+  (language define accessor)
+  (enclosure define standard initial-value #f))
+
+(define-generic mime-body-type (body))
+(define-generic mime-body-subtype (body))
+(define-generic mime-body-header-fields (body))
+(define-generic mime-body-substring (mime-body))
+(define-generic write-mime-body (mime-body port))
+
+(define-method mime-body-substring ((body <mime-body>))
+  (let ((string
+         (call-with-output-string
+           (lambda (output-port)
+             (write-mime-body body output-port)))))
+    (values string 0 (string-length string))))
+
+(define-method write-mime-body ((body <mime-body>) port)
+  (receive (string start end) (mime-body-substring body)
+    (write-substring string start end port)))
+
+(define (mime-body-type-string body)
+  (string-append (symbol->string (mime-body-type body))
+                 "/"
+                 (symbol->string (mime-body-subtype body))))
+
+(define (mime-body-parameter body key default)
+  (let ((entry (assq key (mime-body-parameters body))))
+    (if entry
+        (cdr entry)
+        default)))
+
+(define (mime-body-disposition-filename body)
+  (let ((disposition (mime-body-disposition body)))
+    (and disposition
+         (let ((entry (assq 'FILENAME (cdr disposition))))
+           (and entry
+                (cdr entry))))))
+
+(define-method write-instance ((body <mime-body>) port)
+  (write-instance-helper 'MIME-BODY body port
+    (lambda ()
+      (write-char #\space port)
+      (write-string (mime-body-type-string body) port))))
+
+(define (mime-body-enclosed? b1 b2)
+  (or (eq? b1 b2)
+      (let ((enclosure (mime-body-enclosure b1)))
+        (and enclosure
+             (mime-body-enclosed? enclosure b2)))))
+\f
+(define-class <mime-body-substring> ()
+  (header-fields accessor mime-body-header-fields)
+  (string define accessor)
+  (start define accessor)
+  (end define accessor))
+
+(define-method mime-body-substring ((body <mime-body-substring>))
+  (values (mime-body-substring-string body)
+          (mime-body-substring-start body)
+          (mime-body-substring-end body)))
+
+(define-class <mime-body-one-part> (<mime-body>)
+  (id define accessor)
+  (description define accessor)
+  (encoding define accessor)
+  (n-octets define accessor)
+  ;++ This is a random artefact of the IMAP.  We don't use it.
+  (md5 define accessor))
+
+(define-class <mime-body-basic> (<mime-body-one-part>)
+  (type accessor mime-body-type)
+  (subtype accessor mime-body-subtype))
+
+(define-class (<mime-body-basic-substring>
+               (constructor (header-fields
+                             string start end type subtype parameters id
+                             description encoding n-octets md5 disposition
+                             language)))
+    (<mime-body-basic> <mime-body-substring>))
+
+(define-class <mime-body-text> (<mime-body-one-part>)
+  (subtype accessor mime-body-subtype)
+  (n-lines define accessor))
+
+(define-method mime-body-type ((body <mime-body-text>)) body 'TEXT)
+
+(define-class (<mime-body-text-substring>
+               (constructor (header-fields
+                             string start end subtype parameters id description
+                             encoding n-octets n-lines md5 disposition
+                             language)))
+    (<mime-body-text> <mime-body-substring>))
+\f
+(define-class <mime-body-message> (<mime-body-one-part>)
+  (envelope define accessor)            ;<mime-envelope> instance
+  (body define accessor)                ;<mime-body> instance
+  (n-lines define accessor))
+
+(define-method mime-body-type ((body <mime-body-message>)) body 'MESSAGE)
+(define-method mime-body-subtype ((body <mime-body-message>)) body 'RFC822)
+
+(define-generic mime-body-message-header-fields (mime-body-message))
+
+;;; In a <MIME-BODY-MESSAGE-SUBSTRING> instance, the HEADER-FIELDS
+;;; slot contains the MIME header fields for the enclosure, and the
+;;; substring contains the complete RFC 822 message, including header
+;;; and body.
+
+(define-class (<mime-body-message-substring>
+               (constructor (header-fields
+                            message-header-fields
+                             string start end parameters id description
+                             encoding envelope body n-octets n-lines md5
+                             disposition language)))
+    (<mime-body-message> <mime-body-substring>)
+  (message-header-fields accessor mime-body-message-header-fields))
+
+(define-class (<mime-envelope>
+               (constructor (date subject from sender reply-to to cc bcc
+                                  in-reply-to message-id)))
+    ()
+  (date define accessor)
+  (subject define accessor)
+  (from define accessor)
+  (sender define accessor)
+  (reply-to define accessor)
+  (to define accessor)
+  (cc define accessor)
+  (bcc define accessor)
+  (in-reply-to define accessor)
+  (message-id define accessor))
+
+(define-class (<mime-address> (constructor (name source-route mailbox host)))
+    ()
+  (name define accessor)
+  (source-route define accessor)
+  (mailbox define accessor)
+  (host define accessor))
+
+(define-class <mime-body-multipart> (<mime-body>)
+  (subtype accessor mime-body-subtype)
+  (parts define accessor))
+
+(define-method mime-body-type ((body <mime-body-multipart>)) body 'MULTIPART)
+
+(define-class (<mime-body-multipart-substring>
+              (constructor (header-fields
+                            string start end
+                            subtype parameters parts disposition language)))
+    (<mime-body-multipart> <mime-body-substring>))
+\f
+;;;; MIME Parser
+
+(define-method mime-entity-body-structure (entity)
+  (and (mime-entity? entity)
+       (let ((header-fields (mime-entity-header-fields entity)))
+         (and header-fields
+              (let ((version (mime:get-version-string header-fields)))
+                (and version
+                     (mime:version-1.0? version)
+                     (receive (string start end)
+                         (mime-entity-body-substring entity)
+                       (mime:parse-body-structure header-fields
+                                                  string
+                                                  start
+                                                  end))))))))
+
+;;; In MIME entities that have properties, we cache the body
+;;; structures, but weakly, because they may involve very large
+;;; strings not already stored in the entity, if parts of the body
+;;; require decoding.  This should almost be an around method (if SOS
+;;; supported such things), but in some cases, such as IMAP messages,
+;;; caching is already handled by another mechanism.  So this is
+;;; really useful only for use with the default MIME parser.
+
+(define-method mime-entity-body-structure ((entity <property-mixin>))
+  (define (next store)
+    (let ((body-structure (call-next-method entity)))
+      (store body-structure)
+      body-structure))
+  (let ((cache (get-property entity 'MIME-ENTITY-BODY-STRUCTURE #f)))
+    (if cache
+        (let ((body-structure (weak-car cache)))
+          (if (weak-pair/car? cache)
+              body-structure
+              (next (lambda (value) (weak-set-car! cache value)))))
+        (next (lambda (value)
+                (store-property! entity
+                                 'MIME-ENTITY-BODY-STRUCTURE
+                                 (weak-cons value '())))))))
+
+(define (mime:parse-body-structure header-fields string start end)
+  (let ((content-type (mime:get-content-type header-fields)))
     (let ((type (car content-type))
           (subtype (cadr content-type))
           (parameters (cddr content-type)))
       ((let ((top-level (assq type mime:media-parsers))
-            (default mime:basic-media-parser))
-        (cond ((not top-level) default)
-              ((assq subtype (cddr top-level)) => cdr)
-              ((cadr top-level))
-              (else default)))
-       message type subtype parameters encoding))))
-
-(define (mime:get-content-type message)
-  (parse-first-named-header message
-                            "Content-Type"
-                            mime:default-content-type
-                            mime:parse-content-type))
-
-(define (mime:get-content-transfer-encoding message)
-  (named-mime-encoding
-   (or (parse-first-named-header message
-                                 "Content-Transfer-Encoding"
-                                 mime:default-encoding
-                                 mime:parse-encoding)
-       '7BIT)))
-\f
-(define-method write-mime-message-body-part
-    ((message <message>) selector cache? port)
-  cache?
-  (if (not (pair? selector))
-      (write-message-body message port)
-      (let ((lose
-             (lambda ()
-               (error "Invalid message MIME body selector:"
-                      selector
-                      message))))
-        (let loop ((selector selector)
-                   (part (mime-message-body-structure message)))
-          (let ((item (car selector))
-                (selector (cdr selector)))
-            (cond ((exact-nonnegative-integer? item)
-                   (if (not (mime-body-multipart? part))
-                       (error "Selecting part of non-multipart:"
-                              part
-                              selector))
-                   (let ((subpart
-                          (list-ref (mime-body-multipart-parts part)
-                                    item)))
-                     (if (pair? selector)
-                         (loop selector subpart)
-                         (begin
-                           (if (message? subpart)
-                               (begin
-                                 (write-header-fields
-                                  (message-header-fields subpart)
-                                  port)
-                                 (newline port)))
-                           (write-message-body subpart port)))))
-                  ((not (pair? selector))
-                   (case item
-                     ((TEXT)
-                      (write-message-body part port))
-                     ((HEADER)
-                      (write-header-fields (message-header-fields part)
-                                           port))
-                     (else (lose))))
-                  (else (lose))))))))
-\f
-;;;; MIME-Version Header Field
+             (default mime:basic-media-parser))
+         (cond ((not top-level) default)
+               ((assq subtype (cddr top-level)) => cdr)
+               ((cadr top-level))
+               (else default)))
+       header-fields string start end type subtype parameters))))
 
-(define (mime:get-version-header message)
-  (get-first-header-field-value (message-header-fields message)
-                                "MIME-Version"
-                                ;; No error if not found.
-                                #f))
+(define (mime:get-version-string header-fields)
+  (get-first-header-field-value header-fields "MIME-Version" #f))
 
 (define (mime:version-1.0? string)
   (let ((tokens (mime:string->non-ignored-tokens string)))
-    (let loop ((in tokens)
-               (out '()))
+    (let loop ((in tokens) (out '()))
       (if (pair? in)
-          (let ((token (car in))
-                (in (cdr in)))
-            (cond ((string? token)
-                   (loop in (cons token out)))
-                  ((char? token)
-                   (loop in (cons (string token) out)))
+          (let ((token (car in)) (in (cdr in)))
+            (cond ((string? token) (loop in (cons token out)))
+                  ((char? token) (loop in (cons (string token) out)))
                   (else #f)))
-          (string=? (apply string-append (reverse! out))
-                    "1.0")))))
-
+          (string=? "1.0" (apply string-append (reverse! out)))))))
+\f
 (define mime:media-parsers '())
 
-;++ What about top-level media types whose subtypes are mandated to
-;++ have common syntax?
-
 (define (define-mime-media-parser type subtype parser)
+  (guarantee-interned-symbol type 'DEFINE-MIME-MEDIA-PARSER)
+  (if subtype
+      (guarantee-interned-symbol subtype 'DEFINE-MIME-MEDIA-PARSER))
+  (guarantee-procedure-of-arity
+   parser
+   (length '(HEADER-FIELDS STRING START END TYPE SUBTYPE PARAMETERS))
+   'DEFINE-MIME-MEDIA-PARSER)
   (cond ((assq type mime:media-parsers)
          => (lambda (top-level)
               (if subtype
@@ -173,140 +337,238 @@ USA.
                                (list #f (cons subtype parser))
                                (list parser)))
                      mime:media-parsers))
-        unspecific)))
+         unspecific)))
 \f
-(define-class <message-part> ()
-  (string define accessor)
-  (start  define accessor)
-  (end    define accessor))
-
-(define-method message-body ((message <message-part>))
-  (values (message-part-string message)
-          (message-part-start  message)
-          (message-part-end    message)))
-
-(define-method write-message-body ((message <message-part>) port)
-  (write-substring (message-part-string message)
-                   (message-part-start  message)
-                   (message-part-end    message)
-                   port))
-
-(define-class (<mime-body-basic-part>
-               (constructor make-mime-body-basic-part
-                            (string
-                             start end
-                             type subtype parameters
-                             id description
-                             encoding
-                             n-octets
-                             md5
-                             disposition language)))
-    (<mime-body-basic> <message-part>))
+(define (substring-header&body-bounds string start end)
+  (cond ((= start end)
+         (values start start start start))
+        ((char=? #\newline (string-ref string start))
+         (values start start (+ start 1) end))
+        (else
+         (let ((index (substring-search-forward "\n\n" string start end)))
+           (if index
+               (values start (+ index 1) (+ index 2) end)
+               (values start end end end))))))
+
+(define (mime:parse-entity string start end)
+  (receive (header-start header-end body-start body-end)
+      (substring-header&body-bounds string start end)
+    (mime:parse-body-structure
+     (lines->header-fields (substring->lines string header-start header-end))
+     string
+     body-start
+     body-end)))
 
 ;;; This is the default media parser, equivalent to a Content-Type of
 ;;; APPLICATION/OCTET-STREAM.
 
 (define mime:basic-media-parser
-  (lambda (message type subtype parameters encoding)
-    (receive (string start end) (message-body message)
-      (make-mime-body-basic-part
-       string start end
-       type subtype parameters
-       (mime:get-content-id message)
-       (mime:get-content-description message)
-       (mime-encoding/name encoding)
-       (message-length message)
-       (ignore-errors (lambda () (md5-substring string start end))
-                      (lambda (condition) condition #f))
-       (mime:get-content-disposition message)
-       (mime:get-content-language message)))))
+  (lambda (header-fields string start end type subtype parameters)
+    (make-mime-body-basic-substring
+     header-fields string start end
+     type subtype parameters
+     (mime:get-content-id header-fields)
+     (mime:get-content-description header-fields)
+     (mime:get-content-transfer-encoding header-fields)
+     (- end start)
+     (ignore-errors (lambda () (md5-substring string start end))
+                    (lambda (condition) condition #f))
+     (mime:get-content-disposition header-fields)
+     (mime:get-content-language header-fields))))
 
 ;;; This is unnecessary, but it's nice to make things explicit.
 
 (define-mime-media-parser 'APPLICATION 'OCTET-STREAM
   mime:basic-media-parser)
 \f
-(define-class (<mime-body-text-part>
-               (constructor make-mime-body-text-part
-                            (string
-                             start end
-                             subtype parameters
-                             id description
-                             encoding
-                             n-octets n-lines
-                             md5
-                             disposition language)))
-    (<mime-body-text> <message-part>))
-
 (define-mime-media-parser 'TEXT #f
-  (lambda (message type subtype parameters encoding)
+  (lambda (header-fields string start end type subtype parameters)
     type                                ;ignore
-    (receive (string start end) (message-body message)
-      (make-mime-body-text-part
-       string start end
-       subtype parameters
-       (mime:get-content-id message)
-       (mime:get-content-description message)
-       (mime-encoding/name encoding)
-       (- end start)                             ;Octets
-       (substring-n-newlines string start end)   ;Lines
-       (ignore-errors (lambda () (md5-substring string start end))
-                      (lambda (condition) condition #f))
-       (mime:get-content-disposition message)
-       (mime:get-content-language message)))))
+    (make-mime-body-text-substring
+     header-fields string start end
+     subtype parameters
+     (mime:get-content-id header-fields)
+     (mime:get-content-description header-fields)
+     (mime:get-content-transfer-encoding header-fields)
+     (- end start)
+     (substring-n-newlines string start end)
+     (ignore-errors (lambda () (md5-substring string start end))
+                    (lambda (condition) condition #f))
+     (mime:get-content-disposition header-fields)
+     (mime:get-content-language header-fields))))
+
+(define-mime-media-parser 'MESSAGE 'RFC822
+  (lambda (header-fields string start end type subtype parameters)
+    type subtype                        ;ignore
+    (let ((body (mime:parse-entity string start end)))
+      ((lambda (enclosure)
+         (set-mime-body-enclosure! body enclosure)
+         enclosure)
+       (make-mime-body-message-substring
+        header-fields (mime-body-header-fields body) string start end
+        parameters
+        (mime:get-content-id header-fields)
+        (mime:get-content-description header-fields)
+        (mime:get-content-transfer-encoding header-fields)
+        (mime:parse-envelope
+         (receive (header-start header-end body-start body-end)
+             (substring-header&body-bounds string start end)
+           body-start body-end          ;ignore
+           (lines->header-fields
+            (substring->lines string header-start header-end))))
+        body
+        (- end start)
+        (substring-n-newlines string start end)
+        (ignore-errors (lambda () (md5-substring string start end))
+                       (lambda (condition) condition #f))
+        (mime:get-content-disposition header-fields)
+        (mime:get-content-language header-fields))))))
+
+(define (mime:parse-envelope header-fields)
+  (make-mime-envelope
+   (get-first-header-field-value header-fields "date" #f)
+   (get-first-header-field-value header-fields "subject" #f)
+   (parse-first-named-header header-fields "from" #f mime:parse-addresses)
+   (parse-first-named-header header-fields "sender" #f mime:parse-addresses)
+   (parse-first-named-header header-fields "reply-to" #f mime:parse-addresses)
+   (parse-first-named-header header-fields "to" #f mime:parse-addresses)
+   (parse-first-named-header header-fields "cc" #f mime:parse-addresses)
+   (parse-first-named-header header-fields "bcc" #f mime:parse-addresses)
+   (get-first-header-field-value header-fields "in-reply-to" #f)
+   (get-first-header-field-value header-fields "message-id" #f)))
+\f
+;++ Provisional crock.  No group address or source route syntax.
+
+(define (mime:parse-addresses string)
+  (let* ((tokens (rfc822:string->tokens string))
+         (result (rfc822:parse-list tokens #\, rfc822:parse-address)))
+    (and result
+         (let ((addresses (car result)) (tokens (cdr result)))
+           (and (not (pair? tokens)) addresses)))))
+
+(define (rfc822:parse-address tokens)
+  (or (rfc822:parse-name-addr tokens)
+      (rfc822:parse-addr-spec tokens)))
+
+(define (rfc822:parse-name-addr tokens)
+  (define (finish name mailbox host tokens)
+    (cons (make-mime-address name #f mailbox host) tokens))
+  (let loop ((tokens tokens) (name-tokens '()))
+    (and (pair? tokens)
+         (cond ((eqv? (car tokens) #\<)
+                (let ((name (rfc822:tokens->string (reverse name-tokens)))
+                      (result (rfc822:parse-angle-addr tokens)))
+                  (and result
+                       (let ((local-part (caar result))
+                             (domain (cadar result))
+                             (tokens (cdr result)))
+                         (let ((result
+                                (rfc822:parse-comment-names name tokens)))
+                           (and (pair? result)
+                                (let ((name (car result))
+                                      (tokens (cdr result)))
+                                  (finish name local-part domain tokens))))))))
+               (else
+                (and (or (eqv? (car tokens) #\space)
+                         (and (string? (car tokens))
+                              (not (char=? #\[ (string-ref (car tokens) 0)))))
+                     (loop (cdr tokens)
+                           (cons (car tokens) name-tokens))))))))
+
+(define (rfc822:parse-comment-names name tokens)
+  (define (finish names tokens)
+    (cons (rfc822:tokens->string (reverse (map string-trim names))) tokens))
+  (let loop ((tokens tokens)
+             (names (if (string-null? name) '() (list name))))
+    (if (not (pair? tokens))
+        (finish names tokens)
+        (let ((token (car tokens)))
+          (if (and (string? token) (char=? #\( (string-ref token 0)))
+              (loop (cdr tokens)
+                    (cons (if (pair? names)
+                              (substring token 1 (- (string-length token) 1))
+                              token)
+                          names))
+              (finish names tokens))))))
+
+(define (rfc822:parse-angle-addr tokens)
+  (and (pair? tokens)
+       (eqv? #\< (car tokens))
+       (let ((result (rfc822:parse-addr-spec (cdr tokens))))
+         (and (pair? result)
+              (let ((addr-spec (car result)) (tokens (cdr result)))
+                (and (pair? tokens)
+                     (eqv? #\> (car tokens))
+                     (cons addr-spec (cdr tokens))))))))
+
+(define (rfc822:parse-addr-spec tokens)
+  (let ((result (rfc822:parse-list tokens #\. rfc822:parse-word)))
+    (and (pair? result)
+         (let ((local-part (decorated-string-append "" "." "" (car result)))
+               (tokens (cdr result)))
+           (and (pair? tokens)
+                (eqv? #\@ (car tokens))
+                (let ((result (rfc822:parse-domain (cdr tokens))))
+                  (and (pair? result)
+                       (let ((domain
+                              (decorated-string-append "" "." "" (car result)))
+                             (tokens
+                              (cdr result)))
+                         (cons (list local-part domain) tokens)))))))))
 \f
 ;;;; Multipart Media
 
 (define-mime-media-parser 'MULTIPART #f
-  (lambda (message type subtype parameters encoding)
+  (lambda (header-fields string start end type subtype parameters)
     type                                ;ignore
-    (mime:parse-multipart message subtype parameters encoding)))
+    (mime:parse-multipart header-fields string start end subtype parameters)))
 
 (define-mime-media-parser 'MULTIPART 'DIGEST
-  (lambda (message type subtype parameters encoding)
+  (lambda (header-fields string start end type subtype parameters)
     type                                ;ignore
     (fluid-let ((mime:default-content-type '(MESSAGE RFC822)))
-      (mime:parse-multipart message subtype parameters encoding))))
-
-(define (mime:parse-multipart message subtype parameters encoding)
-  (let* ((parts
-          (mime:parse-multipart-subparts message parameters encoding))
-         (enclosure
-          (make-mime-body-multipart subtype parameters parts
-                                    (mime:get-content-disposition message)
-                                    (mime:get-content-language message))))
-    (for-each (lambda (part)
-                (set-mime-body-enclosure! part enclosure))
-              parts)
-    enclosure))
-
-(define (mime:parse-multipart-subparts message parameters encoding)
-  (let ((boundary (mime:get-boundary parameters message)))
-    (define (parse-body body start end)
-      (mime:parse-parts
-       body
-       (mime:multipart-message-parts body start end boundary)))
-    (if (mime-encoding/identity? message)
-        (call-with-values (lambda () (message-body message))
-          parse-body)
+      (mime:parse-multipart header-fields string start end
+                            subtype parameters))))
+
+(define (mime:parse-multipart header-fields string start end
+                              subtype parameters)
+  (let ((boundary (mime:get-boundary parameters)))
+    (and boundary
+         (let ((parts
+                (mime:parse-multipart-parts header-fields string start end
+                                            boundary)))
+           (and parts
+                (let* ((enclosure
+                        (make-mime-body-multipart-substring
+                         header-fields string start end
+                        subtype parameters parts
+                         (mime:get-content-disposition header-fields)
+                         (mime:get-content-language header-fields))))
+                  (for-each (lambda (part)
+                              (set-mime-body-enclosure! part enclosure))
+                            parts)
+                  enclosure))))))
+
+(define (mime:parse-multipart-parts header-fields string start end boundary)
+  (let ((encoding
+         (named-mime-encoding
+          (mime:get-content-transfer-encoding header-fields))))
+    (if (mime-encoding/identity? encoding)
+        (mime:parse-multipart-parts-1 string start end boundary)
         ((lambda (body)
-           (parse-body body 0 (string-length body)))
+           (mime:parse-multipart-parts-1 body 0 (string-length body) boundary))
          (call-with-output-string
            (lambda (output-port)
-             (call-with-mime-decoding-output-port
-               encoding output-port #t
+             (call-with-mime-decoding-output-port encoding output-port #t
                (lambda (output-port)
-                 (write-message-body message output-port)))))))))
+                 (write-substring string start end output-port)))))))))
 
-(define (mime:get-boundary parameters message)
-  (cond ((assq 'BOUNDARY parameters)
-         => (lambda (probe)
-              (string-append "--" (cdr probe))))
-        (else
-         (error "MIME multipart message has no boundary:"
-                message))))
+(define (mime:get-boundary parameters)
+  (let ((parameter (assq 'BOUNDARY parameters)))
+    (and parameter
+         (string-append "--" (cdr parameter)))))
 \f
-(define (mime:multipart-message-parts string start end boundary)
+(define (mime:parse-multipart-parts-1 string start end boundary)
   (let ((boundary-length (string-length boundary)))
 
     (define (loop part-start search-start parts)
@@ -346,88 +608,59 @@ USA.
            (char=? #\- (string-ref string (+ boundary-end 1)))))
 
     (define (win parts)
-      (cdr (reverse! parts)))
+      (map (lambda (start.end)
+             (mime:parse-entity string (car start.end) (cdr start.end)))
+           ;; Strip the leading text, which is not a proper part --
+           ;; usually it is just a message to the effect that this is
+           ;; a MIME-formatted message which your mail reader can't
+           ;; read.
+           (cdr (reverse! parts))))
 
     (define (lose parts)
-      ;; (error "Malformed MIME multipart:" ...)
-      (if (pair? parts)
-          (cdr (reverse! parts))
-          '()))
+      ;; If we got at least one part and the leading text, then win
+      ;; with that much -- at least we sha'n't be discarding any
+      ;; information, since the last part will include the rest of the
+      ;; message that we weren't able to parse.
+      (if (and (pair? parts)
+               (pair? (cdr parts)))
+          (win parts)
+          #f))
 
     (loop start start '())))
 \f
-;;;;; MIME Part Messages
-
-(define-class (<message-part-message>
-               (constructor make-message-part-message
-                            (header-fields length string start end)))
-    ;** Do not rearrange this!  The MESSAGE-BODY method on
-    ;** <MESSAGE-PART> must be given precedence over that on
-    ;** <MESSAGE>!
-    (<message-part> <message>)
-  (length accessor message-length))
-
-(define (mime:parse-part body start end)
-  (cond ((char=? #\newline (string-ref body start))
-         ;; If the body begins with a newline, then there are
-         ;; no header fields, so the header end is the same
-         ;; as the content start.
-         (mime:parse-part/no-header body start (+ start 1) end))
-        ((substring-search-forward "\n\n" body start end)
-         => (lambda (header-end)
-              ;; End the header between the two newlines.
-              (mime:parse-part/header body start (+ header-end 1) end)))
-        (else
-         ;; Assume that the absence of a blank line means no
-         ;; header fields at all.
-         (mime:parse-part/no-header body start start end))))
-
-(define (mime:parse-part/header string start header-end end)
-  (mime:parse-body-structure
-   (make-message-part-message
-    (lines->header-fields (substring->lines string start header-end))
-    (- end start)
-    string
-    (+ header-end 1)                    ;Exclude the blank line.
-    end)))
-
-(define (mime:parse-part/no-header string start content-start end)
-  (mime:parse-body-structure
-   (make-message-part-message '() (- end start) string content-start end)))
-
-(define (mime:parse-parts body parts)
-  (map (lambda (part)
-         (mime:parse-part body (car part) (cdr part)))
-       parts))
-\f
-;;;; Content-Type Header Fields
+;;;; MIME Header Fields
+
+(define (mime:get-content-type header-fields)
+  (parse-first-named-header header-fields
+                            "Content-Type"
+                            mime:default-content-type
+                            mime:parse-content-type))
 
 (define mime:default-content-type '(TEXT PLAIN (CHARSET . "us-ascii")))
 
 (define (mime:parse-content-type string)
   (let ((tokens (mime:string->non-ignored-tokens string)))
     (if (pair? tokens)
-        (let ((type (car tokens))
-              (tokens (cdr tokens)))
-          (if (and (string? type)
-                   (pair? tokens))
-              (let ((slash (car tokens))
-                    (tokens (cdr tokens)))
-                (if (and (eqv? slash #\/)
-                         (pair? tokens))
-                    (let ((subtype (car tokens))
-                          (tokens (cdr tokens)))
+        (let ((type (car tokens)) (tokens (cdr tokens)))
+          (if (and (string? type) (pair? tokens))
+              (let ((slash (car tokens)) (tokens (cdr tokens)))
+                (if (and (eqv? slash #\/) (pair? tokens))
+                    (let ((subtype (car tokens)) (tokens (cdr tokens)))
                       (if (string? subtype)
                           (cons* (intern type)
                                  (intern subtype)
-                                 (mime:parse-parameters tokens
-                                                       "Content-Type"))
+                                 (mime:parse-parameters tokens))
                           #f))
                     #f))
               #f))
         #f)))
 
-;;;; Other Content-... Fields
+(define (mime:get-content-transfer-encoding header-fields)
+  (or (parse-first-named-header header-fields
+                                "Content-Transfer-Encoding"
+                                mime:default-encoding
+                                mime:parse-encoding)
+      mime:default-encoding))
 
 (define mime:default-encoding '7BIT)
 
@@ -438,35 +671,37 @@ USA.
              (null? (cdr tokens)))
         (intern (car tokens))
         #f)))
+\f
+(define (mime:get-content-id header-fields)
+  (parse-first-named-header header-fields "Content-ID" #f rfc822:parse-msg-id))
 
-(define (mime:get-content-id message)
-  (parse-first-named-header message "Content-ID" #f rfc822:parse-msg-id))
-
-(define (mime:get-content-description message)
-  (parse-first-named-header message "Content-Description" #f
+(define (mime:get-content-description header-fields)
+  (parse-first-named-header header-fields
+                            "Content-Description"
+                            #f
                             mime:parse-encoded-header-value))
 
 (define (mime:parse-encoded-header-value value)
   ;++ implement
   value)
 
-(define (mime:get-content-disposition message)
-  (parse-first-named-header message "Content-Disposition" #f
+(define (mime:get-content-disposition header-fields)
+  (parse-first-named-header header-fields
+                            "Content-Disposition"
+                            #f
                             mime:parse-disposition))
 
 (define (mime:parse-disposition disposition)
   (let ((tokens (mime:string->non-ignored-tokens disposition)))
     (if (pair? tokens)
-        (let ((type (car tokens))
-              (tokens (cdr tokens)))
+        (let ((type (car tokens)) (tokens (cdr tokens)))
           (if (string? type)
               (cons (intern type)
-                    (mime:parse-parameters tokens
-                                           "Content-Disposition"))
+                    (mime:parse-parameters tokens))
               #f))
         #f)))
 
-(define (mime:get-content-language message)
+(define (mime:get-content-language header-fields)
   ;++ implement
   #f)
 \f
@@ -474,8 +709,8 @@ USA.
 
 (define mime:special-chars
   (char-set #\( #\) #\< #\> #\@
-           #\, #\; #\: #\\ #\"
-           #\/ #\[ #\] #\? #\=))
+            #\, #\; #\: #\\ #\"
+            #\/ #\[ #\] #\? #\=))
 
 ;;; STRING->TOKENS includes whitespace & parenthesis comments;
 ;;; STRING->NON-IGNORED-TOKENS omits them.
@@ -489,31 +724,133 @@ USA.
 ;;; Too bad the parser language works only on strings; it would be
 ;;; nice to be able to use it for general tokens, like RFC822 tokens.
 
-(define (mime:parse-parameters tokens header-name)
-  (let ((lose (lambda (tokens)
-                (warn (string-append "Malformed " header-name
-                                     " parameter tokens:")
-                      tokens)
-                '())))
-    (let recur ((tokens tokens))
-      (if (pair? tokens)
-          (let ((lose (lambda () (lose tokens))))
-            (let ((semi (car tokens))
-                  (tokens (cdr tokens)))
-              (if (and (eqv? semi #\;)
-                       (pair? tokens))
-                  (let ((attribute (car tokens))
-                        (tokens (cdr tokens)))
-                    (if (pair? tokens)
-                        (let ((equals (car tokens))
-                              (tokens (cdr tokens)))
-                          (if (and (eqv? equals #\=)
-                                   (pair? tokens))
-                              (cons (cons (intern attribute)
-                                          (rfc822:unquote-string
-                                           (car tokens)))
-                                    (recur (cdr tokens)))
-                              (lose)))
-                        (lose)))
-                  (lose))))
-          '()))))
+(define (mime:parse-parameters tokens)
+  (let recur ((tokens tokens))
+    (if (pair? tokens)
+        (let ((semi (car tokens)) (tokens (cdr tokens)))
+          (if (and (eqv? semi #\;) (pair? tokens))
+              (let ((attribute (car tokens)) (tokens (cdr tokens)))
+                (if (pair? tokens)
+                    (let ((equals (car tokens)) (tokens (cdr tokens)))
+                      (if (and (eqv? equals #\=) (pair? tokens))
+                          (cons (cons (intern attribute)
+                                      (rfc822:unquote-string (car tokens)))
+                                (recur (cdr tokens)))
+                          '()))
+                    '()))
+              '()))
+        '())))
+\f
+;;;; MIME Encoding Registry
+
+(define-structure (mime-encoding
+                   (conc-name mime-encoding/)
+                   (print-procedure
+                    (standard-unparser-method 'MIME-ENCODING
+                      (lambda (encoding output-port)
+                        (write-char #\space output-port)
+                        (write (mime-encoding/name encoding) output-port))))
+                   (constructor %make-mime-encoding))
+  (name                          #f read-only #t)
+  (identity?                     #f read-only #t)
+  (encoder-initializer           #f read-only #t)
+  (encoder-finalizer             #f read-only #t)
+  (encoder-updater               #f read-only #t)
+  (decoder-initializer           #f read-only #t)
+  (decoder-finalizer             #f read-only #t)
+  (decoder-updater               #f read-only #t)
+  (decoding-port-maker           #f read-only #t)
+  (caller-with-decoding-port     #f read-only #t))
+
+(define-guarantee mime-encoding "MIME codec")
+
+(define mime-encodings
+  (make-eq-hash-table))
+
+(define (define-mime-encoding name
+          encode:initialize encode:finalize encode:update
+          decode:initialize decode:finalize decode:update
+          make-port call-with-port)
+  (hash-table/put!
+   mime-encodings
+   name
+   (%make-mime-encoding name #f
+                        encode:initialize encode:finalize encode:update
+                        decode:initialize decode:finalize decode:update
+                        make-port call-with-port))
+  name)
+
+(define (define-identity-mime-encoding name)
+  (hash-table/put! mime-encodings
+                   name
+                   (%make-mime-encoding name #t
+                                        (lambda (port text?) text? port)
+                                        output-port/flush-output
+                                        output-port/write-string
+                                        (lambda (port text?) text? port)
+                                        output-port/flush-output
+                                        output-port/write-string
+                                        (lambda (port text?) text? port)
+                                        (lambda (port text? generator)
+                                          text?
+                                          (generator port)))))
+
+(define (known-mime-encoding? name)
+  (and (hash-table/get mime-encodings name #f)
+       #t))
+
+(define (named-mime-encoding name)
+  (or (hash-table/get mime-encodings name #f)
+      (let ((encoding (make-unknown-mime-encoding name)))
+        (hash-table/put! mime-encodings name encoding)
+        encoding)))
+
+(define (make-unknown-mime-encoding name)
+  (let ((lose (lambda args args (error "Unknown MIME encoding name:" name))))
+    (%make-mime-encoding name #f lose lose lose lose lose lose lose lose)))
+
+(define (call-with-mime-decoding-output-port encoding port text? generator)
+  ((mime-encoding/caller-with-decoding-port
+    (if (symbol? encoding)
+        (named-mime-encoding encoding)
+        (begin
+          (guarantee-mime-encoding encoding
+                                   'CALL-WITH-MIME-DECODING-OUTPUT-PORT)
+          encoding)))
+   port text? generator))
+\f
+(define-identity-mime-encoding '7BIT)
+(define-identity-mime-encoding '8BIT)
+(define-identity-mime-encoding 'BINARY)
+
+;; Next two are random values sometimes used by Outlook.
+(define-identity-mime-encoding '7-BIT)
+(define-identity-mime-encoding '8-BIT)
+
+(define-mime-encoding 'QUOTED-PRINTABLE
+  encode-quoted-printable:initialize
+  encode-quoted-printable:finalize
+  encode-quoted-printable:update
+  decode-quoted-printable:initialize
+  decode-quoted-printable:finalize
+  decode-quoted-printable:update
+  make-decode-quoted-printable-port
+  call-with-decode-quoted-printable-output-port)
+
+(define-mime-encoding 'BASE64
+  encode-base64:initialize
+  encode-base64:finalize
+  encode-base64:update
+  decode-base64:initialize
+  decode-base64:finalize
+  decode-base64:update
+  make-decode-base64-port
+  call-with-decode-base64-output-port)
+
+(define-mime-encoding 'BINHEX40
+  #f #f #f                              ;No BinHex encoder.
+  decode-binhex40:initialize
+  decode-binhex40:finalize
+  decode-binhex40:update
+  make-decode-binhex40-port
+  call-with-decode-binhex40-output-port)
index e0ad9bdf906e8aaa633a7f6da6a545593e9ef00d..3499c372107fd9bdc6913f3f514dd5aef89bb118 100644 (file)
@@ -1,6 +1,6 @@
 #| -*-Scheme-*-
 
-$Id: imail-top.scm,v 1.311 2008/08/15 22:46:42 riastradh Exp $
+$Id: imail-top.scm,v 1.312 2008/09/08 03:55:18 riastradh Exp $
 
 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
     1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
@@ -167,17 +167,17 @@ Text messages using these character sets are displayed inline;
 (define-variable imail-inline-mime-text-subtypes
   "List of MIME text subtypes that should be shown inline.
 The value of this variable is a list of symbols.
-A text entity that appears at the top level of a message
+A text body that appears at the top level of a message
  is always shown inline, regardless of its subtype.
-Likewise, a text/plain entity is always shown inline.
+Likewise, a text/plain body is always shown inline.
 Note that this variable does not affect subparts of multipart/alternative."
   '(HTML ENRICHED)
   list-of-strings?)
 
 (define-variable imail-inline-mime-text-limit
-  "Size limit in octets for showing MIME text message parts in-line.
+  "Size limit in octets for showing MIME text message parts inline.
 MIME text message parts less than this size are shown in-line by default.
-This variable can also be #F; then all parts will be shown in-line."
+This variable can also be #F; then all parts will be shown inline."
   65536
   (lambda (x) (or (boolean? x) (exact-nonnegative-integer? x))))
 
@@ -207,6 +207,13 @@ Otherwise, only one of the parts is shown."
   'SIMPLE
   (lambda (x) (memq x '(SIMPLE SGML ORIGINAL))))
 
+(define-variable imail-mime-show-headers
+  "If true, show MIME headers in expanded body parts.
+Headers are shown only for parts that are displayed out-of-line by
+  default."
+  #f
+  boolean?)
+
 (define-variable imail-global-mail-notification
   "If true, all buffer modelines say if there is unseen mail.
  (This checks only for unseen mail in the primary folder.)
@@ -397,7 +404,7 @@ Instead, these commands are available:
 \\[imail-file-message] Append this message to a specified file.
          (The message is written in a human-readable format.)
 \\[imail-save-attachment]      Save a MIME attachment to a file.
-\\[imail-save-mime-entity]     Save an arbitrary MIME entity to a file.
+\\[imail-save-mime-body]       Save an arbitrary MIME body to a file.
 
 \\[imail-add-flag]     Add flag to message.  It will be displayed in the mode line.
 \\[imail-kill-flag]    Remove flag from message.
@@ -415,7 +422,7 @@ Instead, these commands are available:
 \\[imail-summary-by-regexp]    Like \\[imail-summary] only just messages matching regular expression.
 
 \\[imail-toggle-header]                Toggle between full headers and reduced headers.
-\\[imail-toggle-mime-entity]   Toggle MIME entity between expanded and collapsed formats.
+\\[imail-toggle-mime-body]     Toggle MIME body between expanded and collapsed formats.
 \\[imail-toggle-message]       Toggle between standard and raw message formats.
 
 \\[imail-create-folder]        Create a new folder.  (Normally not needed as output commands
@@ -447,6 +454,7 @@ Instead, these commands are available:
             (error "Unknown folder-sync status:" status))))
        (begin
          (discard-folder-cache folder)
+         (buffer-remove! buffer 'IMAIL-MIME-EXPANSIONS)
          (select-message
           folder
           (or (selected-message #f buffer)
@@ -487,7 +495,7 @@ Instead, these commands are available:
 (define-key 'imail #\m-s       'imail-search)
 (define-key 'imail #\u         'imail-undelete-previous-message)
 (define-key 'imail #\m-u       'imail-first-unseen-message)
-(define-key 'imail #\w         'imail-save-mime-entity)
+(define-key 'imail #\w         'imail-save-mime-body)
 (define-key 'imail #\x         'imail-expunge)
 (define-key 'imail #\.         'beginning-of-buffer)
 (define-key 'imail #\<         'imail-first-message)
@@ -504,10 +512,10 @@ Instead, these commands are available:
 (define-key 'imail '(#\c-c #\c-s #\c-r)        'imail-sort-by-recipient)
 (define-key 'imail '(#\c-c #\c-s #\c-s)        'imail-sort-by-subject)
 (define-key 'imail '(#\c-c #\c-s #\c-v)        'imail-sort-by-arrival)
-(define-key 'imail '(#\c-c #\c-t #\c-e)        'imail-toggle-mime-entity)
+(define-key 'imail '(#\c-c #\c-t #\c-e)        'imail-toggle-mime-body)
 (define-key 'imail '(#\c-c #\c-t #\c-h)        'imail-toggle-header)
 (define-key 'imail '(#\c-c #\c-t #\c-m)        'imail-toggle-message)
-(define-key 'imail '(#\c-c #\c-t #\c-w)        'imail-toggle-wrap-entity)
+(define-key 'imail '(#\c-c #\c-t #\c-w)        'imail-toggle-wrap-body)
 (define-key 'imail #\M-o       'imail-file-message)
 
 ;; Putting these after the group above exploits behavior in the comtab
@@ -525,7 +533,7 @@ Instead, these commands are available:
 (define-key 'imail #\D         'imail-delete-folder)
 (define-key 'imail #\R         'imail-rename-folder)
 (define-key 'imail #\+         'imail-create-folder)
-(define-key 'imail button3-down 'imail-mouse-save-mime-entity)
+(define-key 'imail button3-down 'imail-mouse-save-mime-body)
 
 ;; These commands not yet implemented.
 ;;(define-key 'imail #\m-m     'imail-retry-failure)
@@ -917,20 +925,20 @@ With prefix argument, prompt even when point is on an attachment."
   "P"
   (lambda (always-prompt?)
     (let ((buffer (imail-folder->buffer (selected-folder) #t)))
-      (save-mime-entity (car (maybe-prompt-for-mime-info "Save attachment"
-                                                        (buffer-point buffer)
-                                                        always-prompt?
-                                                        mime-attachment?))
-                       buffer))))
-
-(define-command imail-mouse-save-mime-entity
-  "Save the MIME entity that mouse is on."
+      (save-mime-body (car (maybe-prompt-for-mime-info "Save attachment"
+                                                      (buffer-point buffer)
+                                                      always-prompt?
+                                                      mime-attachment?))
+                     buffer))))
+
+(define-command imail-mouse-save-mime-body
+  "Save the MIME body that mouse is on."
   ()
   (lambda ()
     (let ((button-event (current-button-event)))
       (let ((window (button-event/window button-event)))
        (let ((buffer (window-buffer window)))
-         (save-mime-entity
+         (save-mime-body
           (let ((info
                  (mark-mime-info
                   (or (window-coordinates->mark
@@ -939,40 +947,38 @@ With prefix argument, prompt even when point is on an attachment."
                        (button-event/y button-event))
                       (buffer-end buffer)))))
             (if (not info)
-                (editor-error "Mouse not on a MIME entity."))
+                (editor-error "Mouse not on a MIME body."))
             info)
           buffer))))))
 
-(define-command imail-save-mime-entity
-  "Save the MIME entity at point."
+(define-command imail-save-mime-body
+  "Save the MIME body at point."
   ()
   (lambda ()
-    (save-mime-entity (car (current-mime-entity)) (selected-buffer))))
+    (save-mime-body (car (current-mime-body)) (selected-buffer))))
 
-(define-command imail-toggle-mime-entity
-  "Expand or collapse the MIME entity at point."
+(define-command imail-toggle-mime-body
+  "Expand or collapse the MIME body at point."
   ()
   (lambda ()
-    (let ((i.m (current-mime-entity))
-         (message (selected-message)))
+    (let ((i.m (current-mime-body)))
       (let ((info (car i.m))
            (mark (cdr i.m)))
-       (set-mime-info-expanded?!
-        info mark message
-        (not (mime-info-expanded? info mark message)))
-       (re-render-mime-entity info mark message)))))
+       (set-mime-info-expanded?! info
+                                 mark
+                                 (not (mime-info-expanded? info mark)))
+       (re-render-mime-body info mark)))))
 
-(define-command imail-toggle-wrap-entity
-  "Toggle auto-wrap on or off for the MIME entity at point."
+(define-command imail-toggle-wrap-body
+  "Toggle auto-wrap on or off for the MIME body at point."
   ()
   (lambda ()
-    (let ((i.m (current-mime-entity))
-         (message (selected-message)))
+    (let ((i.m (current-mime-body)))
       (let ((info (car i.m))
            (mark (cdr i.m)))
        (mime-body-wrapped! (mime-info-body info)
                            (not (mime-body-wrapped? (mime-info-body info))))
-       (re-render-mime-entity info mark message)))))
+       (re-render-mime-body info mark)))))
 
 (define (mime-body-wrapped? body)
   (get-property body 'WRAP? #t))
@@ -982,17 +988,17 @@ With prefix argument, prompt even when point is on an attachment."
       (remove-property! body 'WRAP?)
       (store-property! body 'WRAP? value)))
 \f
-(define (re-render-mime-entity info mark message)
-  (let ((region (mime-entity-region mark))
+(define (re-render-mime-body info mark)
+  (let ((region (mime-body-region mark))
        (buffer (mark-buffer mark)))
     (if (not region)
-       (error "No MIME entity at mark:" mark))
+       (error "No MIME body at mark:" mark))
     (let ((point (mark-right-inserting-copy (buffer-point buffer))))
       (with-read-only-defeated mark
        (lambda ()
          (region-delete! region)
          (let ((mark (mark-left-inserting-copy (region-start region))))
-           (insert-mime-info info message mark)
+           (insert-mime-info info mark)
            (mark-temporary! mark))))
       (mark-temporary! point)
       (set-buffer-point! buffer point))
@@ -1043,24 +1049,22 @@ With prefix argument, prompt even when point is on an attachment."
                    converted))
        (reverse! converted))))
 
-(define (current-mime-entity)
+(define (current-mime-body)
   (let ((point (current-point)))
     (let ((info (mark-mime-info point)))
       (if (not info)
-         (editor-error "Point not on a MIME entity."))
+         (editor-error "Point not on a MIME body."))
       (cons info point))))
 \f
-(define (save-mime-entity info buffer)
-  (let ((body (mime-info-body info))
-       (selector (mime-info-selector info))
-       (message (selected-message #t buffer)))
+(define (save-mime-body info buffer)
+  (let ((body (mime-info-body info)))
     (let ((filename
           (let ((history 'IMAIL-SAVE-ATTACHMENT))
             (prompt-for-file
              (string-append "Save "
                             (if (mime-attachment? info)
                                 "attachment"
-                                "MIME entity")
+                                "MIME body")
                             " as")
              (let ((filename
                     (let ((filename (mime-body-disposition-filename body)))
@@ -1099,7 +1103,7 @@ With prefix argument, prompt even when point is on an attachment."
              port
              text?
              (lambda (port)
-               (write-mime-message-body-part message selector #f port)))))))))
+               (write-mime-body body port)))))))))
 
 (define (filter-mime-attachment-filename filename)
   (let ((filename
@@ -1763,20 +1767,19 @@ WARNING: With a prefix argument, this command may take a very long
      (selected-folder)
      (let ((buffer (selected-buffer)))
        (lambda (message body-structure cache-procedure)
-         (define (cache message body selector context buffer)
-           message body context buffer
-           (cache-procedure selector))
-         (define (ignore message body selector context buffer)
-           message body selector context buffer
-           unspecific)
-         (walk-mime-message-part
-          message
-          body-structure
-          '()
-          (make-walk-mime-context #f 0 #f '())
-          buffer
-          cache
-          (if argument cache ignore)))))))
+        (define (cache entity body selector context buffer)
+          entity selector context buffer
+          (cache-procedure body))
+        (define (ignore entity body selector context buffer)
+          entity body selector context buffer
+          unspecific)
+        (walk-mime-body message
+                        body-structure
+                        '()
+                        (make-walk-mime-context #f 0 #f '())
+                        buffer
+                        cache
+                        (if argument cache ignore)))))))
 \f
 ;;;; URLs
 
@@ -2210,10 +2213,11 @@ WARNING: With a prefix argument, this command may take a very long
     (if (and count (= (cdr count) mod-count))
        (car count)
        (let ((n (folder-length folder)))
-         (do ((i 0 (+ i 1))
+         (do ((i (first-unseen-message-index folder) (+ i 1))
               (unseen 0
                       (if (let loop
-                              ((flags (message-flags (get-message folder i))))
+                              ((flags
+                                (message-flags (%get-message folder i))))
                             (and (pair? flags)
                                  (or (string-ci=? "seen" (car flags))
                                      (string-ci=? "deleted" (car flags))
@@ -2271,10 +2275,10 @@ WARNING: With a prefix argument, this command may take a very long
     (insert-header-fields message (and raw? (not (eq? raw? 'BODY-ONLY))) mark)
     (cond ((and raw? (not (eq? raw? 'HEADERS-ONLY)))
           (insert-message-body message mark))
-         ((mime-message-body-structure message)
+         ((mime-entity-body-structure message)
           => (lambda (body-structure)
-                (insert-mime-message-body message body-structure
-                                          mark inline-only? left-margin)))
+               (insert-mime-body message body-structure
+                                 mark inline-only? left-margin)))
          (else
           (call-with-auto-wrapped-output-mark mark left-margin message
             (lambda (port)
@@ -2346,16 +2350,15 @@ WARNING: With a prefix argument, this command may take a very long
 \f
 ;;;; MIME message formatting
 
-(define (insert-mime-message-body message body-structure
-                                  mark inline-only? left-margin)
-  (walk-mime-message-part
+(define (insert-mime-body message body-structure mark inline-only? left-margin)
+  (walk-mime-body
    message
    body-structure
    '()
    (make-walk-mime-context inline-only? left-margin #f '())
    mark
-   insert-mime-message-inline
-   insert-mime-message-outline))
+   insert-mime-body-inline
+   insert-mime-body-outline))
 
 (define-structure walk-mime-context
   (inline-only? #f read-only #t)
@@ -2370,16 +2373,18 @@ WARNING: With a prefix argument, this command may take a very long
                          (cons (cons boundary (not boundary))
                                (walk-mime-context-boundaries context))))
 
-(define (mime-enclosure-type? context type subtype)
+(define (mime-enclosure-type? context type #!optional subtype)
   (let ((enclosure (walk-mime-context-enclosure context)))
     (and enclosure
         (mime-type? enclosure type subtype))))
 
-(define (mime-type? body type subtype)
+(define (mime-type? body type #!optional subtype)
   (and (eq? (mime-body-type body) type)
-       (eq? (mime-body-subtype body) subtype)))
+       (or (default-object? subtype)
+          (not subtype)
+          (eq? (mime-body-subtype body) subtype))))
 
-(define (maybe-insert-mime-boundary context mark)
+(define (maybe-insert-mime-boundary context selector mark)
   (let ((boundary
         (let loop ((boundaries (walk-mime-context-boundaries context)))
           (and (pair? boundaries)
@@ -2394,7 +2399,11 @@ WARNING: With a prefix argument, this command may take a very long
            (loop (cdr boundaries)))))
     (if boundary
        (begin
-         (insert-newline mark)
+         (if (not (and (mime-enclosure-type? context 'MULTIPART)
+                       (mime-type? (walk-mime-context-enclosure context)
+                                   'MULTIPART)
+                       (zero? (last selector))))
+             (insert-newline mark))
          (cond ((string? boundary)
                 (insert-string "--" mark)
                 (insert-string boundary mark))
@@ -2417,25 +2426,25 @@ WARNING: With a prefix argument, this command may take a very long
        encoding
        (mime-body-one-part-encoding body))))
 \f
-(define-generic walk-mime-message-part
-  (message body selector context mark if-inline if-outline))
-(define-generic inline-message-part? (body context mark))
+(define-generic walk-mime-body
+  (entity body selector context mark if-inline if-outline))
+(define-generic inline-mime-part? (body context mark))
 
-(define-method walk-mime-message-part
-    (message (body <mime-body>) selector context mark if-inline if-outline)
-  ((if (inline-message-part? body context mark) if-inline if-outline)
-   message body selector context mark))
+(define-method walk-mime-body
+    (entity (body <mime-body>) selector context mark if-inline if-outline)
+  ((if (inline-mime-part? body context mark) if-inline if-outline)
+   entity body selector context mark))
 
-(define-method inline-message-part? ((body <mime-body>) context mark)
+(define-method inline-mime-part? ((body <mime-body>) context mark)
   context mark
   (mime-type? body 'MESSAGE 'DELIVERY-STATUS))
 
-(define-method inline-message-part? ((body <mime-body-message>) context mark)
+(define-method inline-mime-part? ((body <mime-body-message>) context mark)
   body
   (not (and (mime-enclosure-type? context 'MULTIPART 'DIGEST)
            (ref-variable imail-mime-collapse-digest mark))))
 
-(define-method inline-message-part? ((body <mime-body-text>) context mark)
+(define-method inline-mime-part? ((body <mime-body-text>) context mark)
   (and (let ((disposition (mime-body-disposition body)))
         (if disposition
             (eq? (car disposition) 'INLINE)
@@ -2455,12 +2464,12 @@ WARNING: With a prefix argument, this command may take a very long
        (mime-body-parameter body 'CHARSET "us-ascii")
        #t)
        (let ((limit (ref-variable imail-inline-mime-text-limit mark)))
-         (or (not limit)
-             (< (mime-body-one-part-n-octets body) limit)))))
+        (or (not limit)
+            (< (mime-body-one-part-n-octets body) limit)))))
 
-(define-method walk-mime-message-part
-    (message (body <mime-body-multipart>) selector context
-             mark if-inline if-outline)
+(define-method walk-mime-body
+    (entity (body <mime-body-multipart>) selector context mark
+           if-inline if-outline)
   (let ((context
         (make-walk-mime-subcontext
          context
@@ -2473,58 +2482,50 @@ WARNING: With a prefix argument, this command may take a very long
     (if (eq? (mime-body-subtype body) 'ALTERNATIVE)
        (if (pair? parts)
            (begin
-             (walk-mime-message-part message
-                                     (car parts)
-                                     `(,@selector 0)
-                                     context
-                                     mark if-inline if-outline)
+             (walk-mime-body entity (car parts) `(,@selector 0)
+                             context mark if-inline if-outline)
              (if (ref-variable imail-mime-show-alternatives mark)
                  (do ((parts (cdr parts) (cdr parts))
                       (i 1 (fix:+ i 1)))
                      ((null? parts))
-                   (if-outline message
-                                (car parts)
-                                `(,@selector ,i)
-                                context
-                                mark)))))
+                   (if-outline entity (car parts) `(,@selector ,i) context
+                               mark)))))
        (do ((parts parts (cdr parts))
             (i 0 (fix:+ i 1)))
            ((null? parts))
-         (walk-mime-message-part message
-                                 (car parts)
-                                 `(,@selector ,i)
-                                 context
-                                 mark if-inline if-outline)))))
+         (walk-mime-body entity (car parts) `(,@selector ,i)
+                         context mark if-inline if-outline)))))
 \f
-(define (insert-mime-message-inline message body selector context mark)
-  (maybe-insert-mime-boundary context mark)
-  (insert-mime-info (make-mime-info #t body selector context)
-                   message
-                   mark))
+(define (insert-mime-body-inline entity body selector context mark)
+  (maybe-insert-mime-boundary context selector mark)
+  (insert-mime-info (make-mime-info #t entity body selector context) mark))
 
-(define (insert-mime-message-outline message body selector context mark)
+(define (insert-mime-body-outline entity body selector context mark)
   (if (not (walk-mime-context-inline-only? context))
       (begin
-       (maybe-insert-mime-boundary context mark)
-       (insert-mime-info (make-mime-info #f body selector context)
-                         message
+       (maybe-insert-mime-boundary context selector mark)
+       (insert-mime-info (make-mime-info #f entity body selector context)
                          mark))))
 
-(define (insert-mime-info info message mark)
+(define (insert-mime-info info mark)
   (let ((start (mark-right-inserting-copy mark))
+       (entity (mime-info-entity info))
        (body (mime-info-body info))
+       (selector (mime-info-selector info))
        (context (mime-info-context info)))
-    (if (mime-info-expanded? info mark message)
-       (insert-mime-message-inline* message
-                                    body
-                                    (mime-info-selector info)
-                                    context
-                                    mark)
+    (if (mime-info-expanded? info mark)
+       (begin
+         (if (and (ref-variable imail-mime-show-headers mark)
+                  (not (inline-mime-part? body context mark))
+                  (mime-enclosure-type? context 'MULTIPART))
+             (insert-header-fields (mime-body-header-fields body) #t mark))
+         (insert-mime-body-inline* entity body selector context mark))
        (insert-mime-outline
-        (compute-mime-message-outline body
-                                      (mime-attachment-name info #f)
-                                      context)
+        (compute-mime-body-outline body
+                                   (mime-attachment-name info #f)
+                                   context)
         mark))
+    (insert-newline mark)
     (attach-mime-info start mark info)
     (mark-temporary! start)))
 
@@ -2546,13 +2547,23 @@ WARNING: With a prefix argument, this command may take a very long
                      (insert-newline mark))))
              parameters)
     (insert-string indentation mark)
-    (insert-string "/>" mark)
-    (insert-newline mark)))
+    (insert-string "/>" mark)))
 \f
-(define-generic insert-mime-message-inline* (msg body selector context mark))
+(define-generic insert-mime-body-inline* (entity body selector context mark))
 
-(define-method insert-mime-message-inline*
-    (message (body <mime-body>) selector context mark)
+(define-method insert-mime-body-inline*
+    (entity (body <mime-body>) selector context mark)
+  entity body selector context         ;ignore
+  (call-with-auto-wrapped-output-mark
+   mark
+   (walk-mime-context-left-margin context)
+   body
+   (lambda (port)
+     (write-mime-body body port))))
+
+(define-method insert-mime-body-inline*
+    (entity (body <mime-body-one-part>) selector context mark)
+  entity selector                      ;ignore
   (call-with-auto-wrapped-output-mark
    mark
    (walk-mime-context-left-margin context)
@@ -2563,43 +2574,35 @@ WARNING: With a prefix argument, this command may take a very long
       port
       #t
       (lambda (port)
-       (write-mime-message-body-part
-        message
-        (if (or (not (walk-mime-context-enclosure context))
-                (mime-enclosure-type? context 'MESSAGE 'RFC822))
-            `(,@selector TEXT)
-            selector)
-        (mime-body-one-part-n-octets body)
-        port))))))
-
-(define-method insert-mime-message-inline*
-    (message (body <mime-body-message>) selector context mark)
-  (insert-header-fields (call-with-output-string
-                        (lambda (port)
-                          (write-mime-message-body-part message
-                                                        `(,@selector HEADER)
-                                                        #t
-                                                        port)))
-                       #f
-                       mark)
-  (walk-mime-message-part message
-                         (mime-body-message-body body)
-                         selector
-                         (make-walk-mime-subcontext context body #f)
-                         mark
-                          insert-mime-message-inline
-                          insert-mime-message-outline))
-
-(define-generic compute-mime-message-outline (body name context))
-
-(define-method compute-mime-message-outline ((body <mime-body>) name context)
+       (write-mime-body body port))))))
+
+(define-method insert-mime-body-inline*
+    (entity (body <mime-body-message>) selector context mark)
+  (insert-header-fields (mime-body-message-header-fields body) #f mark)
+  (walk-mime-body entity
+                 (mime-body-message-body body)
+                 selector
+                 (make-walk-mime-subcontext context body #f)
+                 mark
+                 insert-mime-body-inline
+                 insert-mime-body-outline))
+
+(define-method insert-mime-body-inline*
+    (entity (body <mime-body-multipart>) selector context mark)
+  (walk-mime-body entity body selector context mark
+                 insert-mime-body-inline
+                 insert-mime-body-outline))
+\f
+(define-generic compute-mime-body-outline (body name context))
+
+(define-method compute-mime-body-outline ((body <mime-body>) name context)
   context
   (list (and name (cons "name" name))
        (cons "type" (mime-body-type-string body))
        (and (eq? (mime-body-type body) 'TEXT)
             (cons "charset" (mime-body-parameter body 'CHARSET "us-ascii")))))
 
-(define-method compute-mime-message-outline
+(define-method compute-mime-body-outline
     ((body <mime-body-one-part>) name context)
   context
   (append (call-next-method body name context)
@@ -2608,7 +2611,7 @@ WARNING: With a prefix argument, this command may take a very long
                       (cons "encoding" encoding)))
                (cons "length" (mime-body-one-part-n-octets body)))))
 
-(define-method compute-mime-message-outline
+(define-method compute-mime-body-outline
     ((body <mime-body-message>) name context)
   name
   (let ((envelope (mime-body-message-envelope body)))
@@ -2626,15 +2629,10 @@ WARNING: With a prefix argument, this command may take a very long
            (and subject
                 (cons "subject" subject)))
          (cons "length" (mime-body-one-part-n-octets body)))))
-\f
-(define (known-mime-encoding? encoding)
-  (memq encoding
-       '(7BIT 8BIT BINARY QUOTED-PRINTABLE BASE64
-              ;; Microsoft sometimes uses these non-standard values:
-              7-BIT 8-BIT)))
 
 (define (mime-attachment-name info provide-default?)
   (or (mime-body-parameter (mime-info-body info) 'NAME #f)
+      (mime-body-disposition-filename (mime-info-body info))
       (and provide-default?
           (string-append (if (mime-info-inline? info)
                              "inline-"
@@ -2681,7 +2679,7 @@ WARNING: With a prefix argument, this command may take a very long
            (loop mark attachments)
            (reverse! attachments))))))
 
-(define (mime-entity-region mark)
+(define (mime-body-region mark)
   (specific-property-region mark 'IMAIL-MIME-INFO
     (lambda (i1 i2)
       (mime-body-enclosed? (mime-info-body i1) (mime-info-body i2)))))
@@ -2691,21 +2689,22 @@ WARNING: With a prefix argument, this command may take a very long
 
 (define-structure mime-info
   (inline? #f)
+  (entity #f read-only #t)
   (body #f read-only #t)
   (selector #f read-only #t)
   (context #f read-only #t))
 
-(define (mime-info-expanded? info mark message)
+(define (mime-info-expanded? info mark)
   (let ((expansions (buffer-get (->buffer mark) 'IMAIL-MIME-EXPANSIONS #f))
-       (key (cons message (mime-info-selector info)))
+       (key (cons (mime-info-entity info) (mime-info-selector info)))
        (inline? (mime-info-inline? info)))
     (if expansions
        (hash-table/get expansions key inline?)
        inline?)))
 
-(define (set-mime-info-expanded?! info mark message expanded?)
+(define (set-mime-info-expanded?! info mark expanded?)
   (let ((buffer (->buffer mark))
-       (key (cons message (mime-info-selector info))))
+       (key (cons (mime-info-entity info) (mime-info-selector info))))
     (if (if (mime-info-inline? info) expanded? (not expanded?))
        (cond ((buffer-get buffer 'IMAIL-MIME-EXPANSIONS #f)
               => (lambda (expansions)
@@ -2718,7 +2717,7 @@ WARNING: With a prefix argument, this command may take a very long
               (buffer-put! buffer 'IMAIL-MIME-EXPANSIONS expansions)
               expansions))
         key
-         expanded?))))
+        expanded?))))
 \f
 ;;;; Automatic wrap/fill
 
index 4647e070f5a626570c2175ec2f63845292692a7a..9557bb9ceed15c7ef846d35a856123258f5eaec8 100644 (file)
@@ -1,6 +1,6 @@
 #| -*-Scheme-*-
 
-$Id: imail-util.scm,v 1.52 2008/07/11 05:26:42 cph Exp $
+$Id: imail-util.scm,v 1.53 2008/09/08 03:55:18 riastradh Exp $
 
 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
     1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
@@ -572,4 +572,80 @@ USA.
              (set-istate-position! state 0)
              (set-istate-buffer-start! state 0)
              (set-istate-buffer-end! state 0)))))))
-   #f))
\ No newline at end of file
+   #f))
+\f
+;;;; Properties
+
+(define-class <property-mixin> ()
+  (alist define (accessor modifier)
+        accessor object-properties
+        modifier set-object-properties!
+        initial-value '()))
+
+(define (get-property object key default)
+  (let ((entry (assq key (object-properties object))))
+    (if entry
+       (cdr entry)
+       default)))
+
+(define (store-property! object key datum)
+  (let ((alist (object-properties object)))
+    (let ((entry (assq key alist)))
+      (if entry
+         (set-cdr! entry datum)
+         (set-object-properties! object (cons (cons key datum) alist))))))
+
+(define (remove-property! object key)
+  (set-object-properties! object (del-assq! key (object-properties object))))
+
+;;;; Modification events
+
+(define-class <modification-event-mixin> ()
+  (modification-count define (accessor modifier)
+                     accessor object-modification-count
+                     modifier set-object-modification-count!
+                     initial-value 0)
+  (modification-event define accessor
+                     accessor object-modification-event
+                     initializer make-event-distributor))
+
+(define (receive-modification-events object procedure)
+  (add-event-receiver! (object-modification-event object) procedure))
+
+(define (ignore-modification-events object procedure)
+  (remove-event-receiver! (object-modification-event object) procedure))
+
+(define (object-modified! object type . arguments)
+  (without-interrupts
+   (lambda ()
+     (set-object-modification-count!
+      object
+      (+ (object-modification-count object) 1))))
+  (apply signal-modification-event object type arguments))
+
+(define (signal-modification-event object type . arguments)
+  (if *deferred-modification-events*
+      (set-cdr! *deferred-modification-events*
+               (cons (cons* object type arguments)
+                     (cdr *deferred-modification-events*)))
+      (begin
+       (if imap-trace-port
+           (begin
+             (write-line (cons* 'OBJECT-EVENT object type arguments)
+                         imap-trace-port)
+             (flush-output imap-trace-port)))
+       (event-distributor/invoke! (object-modification-event object)
+                                  object
+                                  type
+                                  arguments))))
+
+(define (with-modification-events-deferred thunk)
+  (let ((events (list 'EVENTS)))
+    (let ((v
+          (fluid-let ((*deferred-modification-events* events))
+            (thunk))))
+      (for-each (lambda (event) (apply signal-modification-event event))
+               (reverse! (cdr events)))
+      v)))
+
+(define *deferred-modification-events* #f)
index 0e269ac77f2f6c46c235f40be4ddf939976f7bbc..41a5b231e37e3b491e319c256b8910e9e4b73696 100644 (file)
@@ -1,6 +1,6 @@
 #| -*-Scheme-*-
 
-$Id: imail.pkg,v 1.106 2008/07/07 01:36:24 riastradh Exp $
+$Id: imail.pkg,v 1.107 2008/09/08 03:55:18 riastradh Exp $
 
 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
     1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
@@ -34,22 +34,10 @@ USA.
 
 (define-package (edwin imail)
   (files "imail-util"
+        "imail-mime"
         "imail-core")
   (parent (edwin)))
 
-(define-package (edwin imail mime)
-  (files "imail-mime")
-  (parent (edwin imail))
-  (export (edwin imail)
-          define-mime-media-parser
-          mime:basic-media-parser
-          mime:parse-multipart
-          mime:default-content-type
-          <message-part>
-          message-part-string
-          message-part-start
-          message-part-end))
-
 (define-package (edwin imail file-folder)
   (files "imail-file")
   (parent (edwin imail))
@@ -203,7 +191,7 @@ USA.
          edwin-command$imail-kill-flag
          edwin-command$imail-last-message
          edwin-command$imail-mail
-         edwin-command$imail-mouse-save-mime-entity
+         edwin-command$imail-mouse-save-mime-body
          edwin-command$imail-next-flagged-message
          edwin-command$imail-next-message
          edwin-command$imail-next-same-subject
@@ -219,12 +207,12 @@ USA.
          edwin-command$imail-resend
          edwin-command$imail-save-attachment
          edwin-command$imail-save-folder
-         edwin-command$imail-save-mime-entity
+         edwin-command$imail-save-mime-body
          edwin-command$imail-search
          edwin-command$imail-select-message
          edwin-command$imail-toggle-header
          edwin-command$imail-toggle-message
-         edwin-command$imail-toggle-mime-entity
+         edwin-command$imail-toggle-mime-body
          edwin-command$imail-undelete-backward
          edwin-command$imail-undelete-forward
          edwin-command$imail-undelete-previous-message
@@ -250,6 +238,7 @@ USA.
          edwin-variable$imail-mime-boundary-style
          edwin-variable$imail-mime-collapse-digest
          edwin-variable$imail-mime-show-alternatives
+         edwin-variable$imail-mime-show-headers
          edwin-variable$imail-mode-hook
          edwin-variable$imail-output-default
          edwin-variable$imail-primary-folder