--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: imail-core.scm,v 1.1 2000/01/04 22:50:53 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: core definitions
+
+;;; **** Implement file backup.
+;;; **** Strip IMAIL headers when importing or exporting messages.
+;;; (What does that mean, precisely?)
+
+(declare (usual-integrations))
+\f
+;;;; URL type
+
+(define-class <url> ())
+
+;; Return the canonical name of URL's protocol as a string.
+(define-generic url-protocol (url))
+
+;; Return the body of URL as a string.
+(define-generic url-body (url))
+
+(define (guarantee-url url procedure)
+ (if (not (url? url))
+ (error:wrong-type-argument url "IMAIL url" procedure)))
+
+(define-method write-instance ((url <url>) port)
+ (write-instance-helper 'URL url port
+ (lambda ()
+ (write-char #\space port)
+ (write (url->string url) port))))
+
+(define (make-url protocol body)
+ (string->url (string-append protocol ":" body)))
+
+(define (->url object)
+ (cond ((url? object) object)
+ ((string? object) (string->url object))
+ (else (error:wrong-type-argument object "URL" '->URL))))
+
+(define (string->url string)
+ (or (hash-table/get saved-urls string #f)
+ (let ((url
+ (let ((colon (string-find-next-char string #\:)))
+ (if (not colon)
+ (error "Malformed URL string:" string))
+ ((get-url-protocol-parser (string-head string colon))
+ (string-tail string (fix:+ colon 1))))))
+ (hash-table/put! saved-urls string url)
+ url)))
+
+(define saved-urls
+ (make-string-hash-table))
+
+(define (url->string url)
+ (string-append (url-protocol url) ":" (url-body url)))
+
+(define (define-url-protocol name class parser)
+ (define-method url-protocol ((url class)) url name)
+ (hash-table/put! url-protocol-parsers (string-downcase name) parser))
+
+(define (get-url-protocol-parser name)
+ (let ((parser
+ (hash-table/get url-protocol-parsers (string-downcase name) #f)))
+ (if (not parser)
+ (error:bad-range-argument name 'GET-URL-PROTOCOL-PARSER))
+ parser))
+
+(define url-protocol-parsers
+ (make-string-hash-table))
+
+(define (get-memoized-folder url)
+ (let ((folder (hash-table/get memoized-folders url #f)))
+ (and folder
+ (if (%folder-valid? folder)
+ folder
+ (begin
+ (unmemoize-folder url)
+ #f)))))
+
+(define (memoize-folder folder)
+ (hash-table/put! memoized-folders (folder-url folder) folder)
+ folder)
+
+(define (unmemoize-folder url)
+ (hash-table/remove! memoized-folders url))
+
+(define memoized-folders
+ (make-eq-hash-table))
+\f
+;;;; Server operations
+
+;; Open the folder named URL.
+(define (open-folder url)
+ (let ((url (->url url)))
+ (or (get-memoized-folder url)
+ (memoize-folder (%open-folder url)))))
+
+(define-generic %open-folder (url))
+
+;; Create a new folder named URL. Signal an error if the folder
+;; already exists or can't be created.
+(define (new-folder url)
+ (let ((url (->url url)))
+ (if (get-memoized-folder url)
+ (error "Folder already exists:" url)
+ (memoize-folder (%new-folder url)))))
+
+(define-generic %new-folder (url))
+
+;; Delete the folder named URL. Signal an error if the folder doesn't
+;; exist or if it can't be deleted.
+(define (delete-folder url)
+ (let ((url (->url url)))
+ (unmemoize-folder url)
+ (%delete-folder url)))
+
+(define-generic %delete-folder (url))
+
+;; Move the folder named URL to NEW-URL. Signal an error if the
+;; folder doesn't exist, if NEW-URL already refers to a folder, or if
+;; the move can't be performed for some reason. This operation can be
+;; also be used to convert between protocols, e.g. to move a folder
+;; from a server to a file.
+(define (move-folder url new-url)
+ (let ((url (->url url))
+ (new-url (->url new-url)))
+ (unmemoize-folder url)
+ (%move-folder url new-url)))
+
+(define-generic %move-folder (url new-url))
+
+(define-method %move-folder ((url <url>) (new-url <url>))
+ (%copy-folder url new-url)
+ (%delete-folder url))
+
+;; Copy the folder named URL to be NEW-URL. Signal an error if the
+;; folder doesn't exist, if NEW-URL already refers to a folder, or if
+;; the copy can't be performed for some reason.
+(define (copy-folder url new-url)
+ (%copy-folder (->url url) (->url new-url)))
+
+(define-generic %copy-folder (url new-url))
+
+(define-method %copy-folder ((url <url>) (new-url <url>))
+ (%write-folder (open-folder url) new-url))
+\f
+;; Return a list of URLs for folders that match URL-PATTERN.
+;; URL-PATTERN can contain wildcards.
+(define-generic available-folder-names (url-pattern))
+
+;; [This is an IMAP command that appears to be designed to support
+;; delivery of usenet news.]
+(define-generic subscribed-folder-names (url-pattern))
+
+;; Define AUTHENTICATOR to be the authenticator to use in the dynamic
+;; extent of THUNK.
+
+;; AUTHENTICATOR is a procedure that performs authentication, for
+;; protocols that require it. AUTHENTICATOR is called with URL as its
+;; only argument and must return the authentication information,
+;; usually a username/password, as multiple values.
+
+;; For protocols that don't require authentication, AUTHENTICATOR is
+;; not called, and BIND-AUTHENTICATOR need not be used.
+
+;; [AUTHENTICATOR can be called at a variety of times; these will be
+;; made more explicit when known.]
+
+(define (bind-authenticator authenticator thunk)
+ (fluid-let ((authenticate authenticator))
+ (thunk)))
+
+(define authenticate)
+\f
+;;;; Folder type
+
+(define-class <folder> ())
+
+(define (guarantee-folder folder procedure)
+ (if (not (folder? folder))
+ (error:wrong-type-argument folder "IMAIL folder" procedure)))
+
+;; Return the URL of FOLDER.
+(define-generic folder-url (folder))
+
+;; Return #T if FOLDER represents a real folder, i.e. has a
+;; corresponding file or server entry.
+(define (folder-valid? folder)
+ (eq? folder (get-memoized-folder (folder-url folder))))
+
+(define-generic %folder-valid? (folder))
+
+;; Return the number of messages in FOLDER.
+(define-generic count-messages (folder))
+
+;; Get the INDEX'th message in FOLDER and return it. Signal an
+;; error for invalid INDEX.
+(define (get-message folder index)
+ (guarantee-index index 'GET-MESSAGE)
+ (if (not (fix:< index (length (count-messages folder))))
+ (error:bad-range-argument index 'GET-MESSAGE))
+ (%get-message folder index))
+
+(define-generic %get-message (folder index))
+
+;; Insert a copy of MESSAGE in FOLDER at INDEX; pre-existing messages
+;; with indices of INDEX or higher have their indices incremented.
+;; Unspecified result.
+(define (insert-message folder index message)
+ (guarantee-index index 'INSERT-MESSAGE)
+ (if (not (fix:<= index (length (count-messages folder))))
+ (error:bad-range-argument index 'INSERT-MESSAGE))
+ (guarantee-message message 'INSERT-MESSAGE)
+ (%insert-message folder index message))
+
+(define-generic %insert-message (folder index message))
+
+;;; Insert a copy of MESSAGE in FOLDER at the end of the existing
+;;; messages. Unspecified result.
+(define (append-message folder message)
+ (guarantee-message message 'APPEND-MESSAGE)
+ (%append-message folder message))
+
+(define-generic %append-message (folder message))
+
+;; Remove all messages in FOLDER that are marked for deletion.
+;; Unspecified result.
+(define-generic expunge-deleted-messages (folder))
+\f
+;; Search FOLDER for messages matching CRITERIA, returning them in a
+;; list. [Possible values for CRITERIA not yet defined.] Returns a
+;; list of messages.
+(define-generic search-folder (folder criteria))
+
+;; Poll the inbox associated with FOLDER to see if there is new mail.
+;; If so, the mail is appended to FOLDER. Return the number of new
+;; messages. Return #F if FOLDER has no associated inbox.
+(define-generic poll-folder (folder))
+
+;; Synchronize the local copy of FOLDER with the server's copy.
+;; Unspecified result. Meaningless for file-based protocols.
+(define-generic synchronize-folder (folder))
+
+;; Save any changes made to FOLDER. This permits the use of caches
+;; for improved performance.
+(define (save-folder folder)
+ (%save-folder folder))
+
+(define-generic %save-folder (folder))
+
+(define-method %save-folder ((folder <folder>))
+ (%write-folder folder (folder-url folder)))
+
+;; Write the contents of FOLDER to URL.
+(define-generic %write-folder (folder url))
+
+;; [These are IMAP commands that appear to be designed to support
+;; delivery of usenet news.]
+(define-generic subscribe-folder (folder))
+(define-generic unsubscribe-folder (folder))
+\f
+;;;; Message type
+
+(define-structure (message (type-descriptor message-rtd)
+ (safe-accessors #t))
+ header-fields
+ body
+ flags
+ properties)
+
+(define (guarantee-message message procedure)
+ (if (not (message? message))
+ (error:wrong-type-argument message "IMAIL message" procedure)))
+
+(define-generic header-fields (object))
+
+(define-method header-fields ((message message-rtd))
+ (message-header-fields message))
+
+(define (copy-message message)
+ (make-message (map copy-header-field (message-header-fields message))
+ (message-body message)
+ (list-copy (message-flags message))
+ (alist-copy (message-properties message))))
+
+(define (make-standard-message headers body)
+ (let loop ((headers headers) (headers* '()) (flags '()) (properties '()))
+ (cond ((not (pair? headers))
+ (make-message (reverse! headers*)
+ body
+ (reverse! flags)
+ (reverse! properties)))
+ ((header-field->message-flags (car headers))
+ => (lambda (flags*)
+ (loop (cdr headers)
+ headers*
+ (append! (reverse! (cdr flags*)) flags)
+ properties)))
+ ((header-field->message-property header)
+ => (lambda (property)
+ (loop (cdr headers)
+ headers*
+ flags
+ (cons property properties))))
+ (else
+ (loop (cdr headers)
+ (cons (car headers) headers*)
+ flags
+ properties)))))
+\f
+;;;; Message flags
+
+;;; Flags are markers that can be attached to messages. They indicate
+;;; state about the message, such as whether it has been deleted,
+;;; seen, etc. A flag is represented by a symbol or a string; symbols
+;;; represent standard flags with predefined meanings, while strings
+;;; represent user-defined flags.
+
+(define (message-flagged? message flag)
+ (guarantee-message-flag flag 'MESSAGE-FLAGGED?)
+ (if (member flag (message-flags message)) #t #f))
+
+(define (set-message-flag message flag)
+ (guarantee-message-flag flag 'SET-MESSAGE-FLAG)
+ (let ((flags (message-flags message)))
+ (if (not (member flag flags))
+ (set-message-flags! message (cons flag flags)))))
+
+(define (clear-message-flag message flag)
+ (set-message-flags! message (delete flag (message-flags message))))
+
+(define (message-flag? object)
+ (or (memq object standard-message-flags)
+ (header-field-name? object)))
+
+(define (guarantee-message-flag object procedure)
+ (if (not (message-flag? object))
+ (error:wrong-type-argument object "message flag" procedure)))
+
+(define standard-message-flags
+ ;; **** Use SEEN rather than UNSEEN?
+ '(DELETED ANSWERED UNSEEN FILED FORWARDED EDITED RESENT))
+
+(define (message-deleted? message)
+ (message-flagged? message 'DELETED))
+
+(define (delete-message message)
+ (set-message-flag message 'DELETED))
+
+(define (undelete-message message)
+ (clear-message-flag message 'DELETED))
+
+(define (message-flags->header-field flags)
+ (make-header-field
+ message-flags:name
+ (apply string-append
+ (map (lambda (flag)
+ (if (symbol? flag)
+ (string-append " :" (symbol->string flag))
+ (string-append " " flag)))
+ flags))))
+
+(define (header-field->message-flags header)
+ (and (string-ci=? message-flags:name (header-field-name header))
+ ;; Extra pair needed to distinguish #F from ().
+ (cons 'YUK
+ (map (lambda (token)
+ (if (char=? #\: (string-ref token 0))
+ (intern (string-tail token 1))
+ token))
+ (burst-string (header-field-value header)
+ char-set:lwsp
+ #t)))))
+
+(define message-flags:name "X-IMAIL-FLAGS")
+\f
+;;;; Message properties
+
+;;; Properties are used to associate information with a message. A
+;;; property is a distinguished header field that carries information
+;;; intended for the mail reader rather than the user.
+
+(define (get-message-property message name default)
+ (guarantee-message-property-name name 'GET-MESSAGE-PROPERTY)
+ (let loop ((headers (message-properties message)))
+ (if (pair? headers)
+ (if (string-ci=? name (caar headers))
+ (cdar headers)
+ (loop (cdr headers)))
+ default)))
+
+(define (set-message-property message name value)
+ (guarantee-message-property-name name 'SET-MESSAGE-PROPERTY)
+ (guarantee-message-property-value value 'SET-MESSAGE-PROPERTY)
+ (let ((headers (message-properties message)))
+ (let loop ((headers headers))
+ (if (pair? headers)
+ (if (string-ci=? name (caar headers))
+ (set-cdr! (car headers) value)
+ (loop (cdr headers)))
+ (set-message-properties! message
+ (cons (cons name value) headers))))))
+
+(define (message-property-name? object)
+ (header-field-name? object))
+
+(define (message-property-value? object)
+ (or (header-field-value? object)
+ (and (list? object)
+ (for-all? object header-field?))))
+
+(define (guarantee-message-property-name name procedure)
+ (if (not (message-property-name? name))
+ (error:wrong-type-argument name "message-property name" procedure)))
+
+(define (guarantee-message-property-value value procedure)
+ (if (not (message-property-value? value))
+ (error:wrong-type-argument value "message-property value" procedure)))
+
+(define (message-property->header-field name value)
+ (make-header-field
+ (string-append message-property:prefix name)
+ (if (header-field-value? value)
+ (string-append message-property:single-marker value)
+ (apply string-append
+ message-property:multiple-marker
+ (map (lambda (line)
+ (string-append "\n" line))
+ (quote-lines
+ (append-map (lambda (header)
+ (header-field->lines header))
+ value)))))))
+
+(define (header-field->message-property header)
+ (and (string-prefix-ci? message-property:prefix (header-field-name header))
+ (cons (string-tail (header-field-name header)
+ (string-length message-property:prefix))
+ (let ((value (header-field-value header)))
+ (cond ((string-prefix? message-property:single-marker value)
+ (string-tail
+ value
+ (string-length message-property:single-marker)))
+ ((string-prefix? message-property:multiple-marker value)
+ (lines->header-fields
+ (unquote-lines
+ (cdr (burst-string value #\newline #f)))))
+ (else
+ (error "Malformed message-property value:" value)))))))
+
+(define message-property:prefix "X-IMAIL-PROPERTY-")
+(define message-property:single-marker "[single]")
+(define message-property:multiple-marker "[multiple]")
+\f
+;;;; Header fields
+
+(define-structure (header-field
+ (type-descriptor header-field-rtd)
+ (safe-accessors #t)
+ (constructor #f)
+ (print-procedure
+ (standard-unparser-method 'HEADER-FIELD
+ (lambda (header port)
+ (write-char #\space port)
+ (write (header-field-name header) port)))))
+ (name #f read-only #t)
+ (value #f read-only #t))
+
+(define make-header-field
+ (let ((constructor (record-constructor header-field-rtd)))
+ (lambda (name value)
+ (guarantee-header-field-name name 'MAKE-HEADER-FIELD)
+ (guarantee-header-field-value value 'MAKE-HEADER-FIELD)
+ (constructor name value))))
+
+(define (copy-header-field header)
+ (record-copy header))
+
+(define (get-first-header-field headers name error?)
+ (let loop
+ ((headers
+ (if (or (pair? headers) (null? headers))
+ headers
+ (header-fields headers))))
+ (cond ((pair? headers)
+ (if (string-ci=? name (header-field-name (car headers)))
+ (car headers)
+ (loop (cdr headers))))
+ (error? (error:bad-range-argument name 'GET-FIRST-HEADER-FIELD))
+ (else #f))))
+
+(define (get-last-header-field headers name error?)
+ (let loop
+ ((headers
+ (if (or (pair? headers) (null? headers))
+ headers
+ (header-fields headers)))
+ (winner #f))
+ (cond ((pair? headers)
+ (loop (cdr headers)
+ (if (string-ci=? name (header-field-name (car headers)))
+ (car headers)
+ winner)))
+ ((and (not winner) error?)
+ (error:bad-range-argument name 'GET-LAST-HEADER-FIELD))
+ (else winner))))
+
+(define (get-all-header-fields headers name)
+ (list-transform-positive headers
+ (lambda (header)
+ (string-ci=? name (header-field-name header)))))
+
+(define (get-first-header-field-value headers name error?)
+ (let ((header (get-first-header-field headers name error?)))
+ (and header
+ (header-field-value header))))
+
+(define (get-last-header-field-value headers name error?)
+ (let ((header (get-last-header-field headers name error?)))
+ (and header
+ (header-field-value header))))
+
+(define (get-all-header-field-values headers name)
+ (map header-field-value (get-all-header-fields headers name)))
+\f
+(define (header-field-name? object)
+ (and (string? object)
+ (%header-field-name? object 0 (string-length object))))
+
+(define %header-field-name?
+ (let ((excluded-chars
+ (char-set-invert
+ (char-set-difference (ascii-range->char-set 33 127)
+ (char-set #\:)))))
+ (lambda (string start end)
+ (and (fix:< start end)
+ (not (substring-find-next-char-in-set string start end
+ excluded-chars))))))
+
+(define (header-field-value? object)
+ (and (string? object)
+ (let ((end (string-length object)))
+ (let loop ((index 0))
+ (let ((nl (substring-find-next-char object index end #\newline)))
+ (or (not nl)
+ (and (fix:< (fix:+ nl 1) end)
+ (char-lwsp? (string-ref object (fix:+ nl 1)))
+ (loop (fix:+ nl 2)))))))))
+
+(define (guarantee-header-field-name object procedure)
+ (if (not (header-field-name? object))
+ (error:wrong-type-argument object "header-field name" procedure)))
+
+(define (guarantee-header-field-value object procedure)
+ (if (not (header-field-value? object))
+ (error:wrong-type-argument object "header-field value" procedure)))
+
+(define (header-field->lines header)
+ (let ((lines (string->lines (header-field-value header))))
+ (cons (string-append (header-field-name header) ":" (car lines))
+ (cdr lines))))
+
+(define (lines->header-field lines)
+ (let ((colon
+ (and (pair? lines)
+ (string-find-next-char (car lines) #\:))))
+ (if (not colon)
+ (error "Malformed header-field lines:" lines))
+ (make-header-field (string-head (car lines) colon)
+ (apply string-append
+ (string-tail (car lines) (fix:+ colon 1))
+ (map (lambda (line)
+ (string-append "\n" line))
+ (cdr lines))))))
+
+(define (lines->header-fields lines)
+ (map lines->header-field
+ (burst-list lines header-field-initial-line?)))
+
+(define (header-field-initial-line? line)
+ (let ((colon (string-find-next-char line #\:)))
+ (and colon
+ (%header-field-name? line 0 colon))))
+
+(define (header-field-continuation-line? line)
+ (and (not (string-null? line))
+ (char-lwsp? (string-ref line 0))))
\ No newline at end of file
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: imail-file.scm,v 1.1 2000/01/04 22:50:56 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: file-based folder support
+
+(declare (usual-integrations))
+\f
+;;;; URL
+
+(define-class <file-url> (<url>)
+ (pathname define accessor))
+
+(define-method url-body ((url <file-url>))
+ (pathname->short-name (file-url-pathname url)))
+
+;;;; Server operations
+
+(define-method %delete-folder ((url <file-url>))
+ (delete-file (file-url-pathname url)))
+
+;;; The next two methods only work when operating on two URLs of the
+;;; same class. Otherwise, it's necessary to do format conversions;
+;;; this is handled at a higher level in the class heirarchy.
+
+(define-computed-method %move-folder ((uc1 <file-url>) (uc2 <file-url>))
+ (and (eq? uc1 uc2)
+ (lambda (url new-url)
+ ;; **** Not really correct -- must handle cases where RENAME-FILE
+ ;; fails, such as moving across file systems under unix.
+ (rename-file (file-url-pathname url) (file-url-pathname new-url)))))
+
+(define-computed-method %copy-folder ((uc1 <file-url>) (uc2 <file-url>))
+ (and (eq? uc1 uc2)
+ (lambda (url new-url)
+ (copy-file (file-url-pathname url) (file-url-pathname new-url)))))
+
+(define-method available-folder-names ((url <file-url>))
+ url
+ (error "Unimplemented operation:" 'AVAILABLE-FOLDER-NAMES))
+
+(define-method subscribed-folder-names ((url <file-url>))
+ url
+ (error "Unimplemented operation:" 'SUBSCRIBED-FOLDER-NAMES))
+\f
+;;;; Folder
+
+(define-class <file-folder> (<folder>)
+ (url accessor folder-url)
+ (messages define standard))
+
+(define-method %folder-valid? ((folder <file-folder>))
+ (file-exists? (file-url-pathname (folder-url folder))))
+
+(define-method count-messages ((folder <file-folder>))
+ (length (file-folder-messages folder)))
+
+(define-method %get-message ((folder <file-folder>) index)
+ (list-ref (file-folder-messages folder) index))
+
+(define-method %insert-message ((folder <file-folder>) index message)
+ (let ((message (copy-message message))
+ (messages (file-folder-messages folder)))
+ (if (fix:= 0 index)
+ (set-file-folder-messages! folder (cons message messages))
+ (let loop ((index* 1) (prev messages) (this (cdr messages)))
+ (if (fix:= index index*)
+ (set-cdr! prev (cons message this))
+ (loop (fix:+ index* 1) this (cdr this)))))))
+
+(define-method %append-message ((folder <file-folder>) message)
+ (set-file-folder-messages! folder
+ (append! (file-folder-messages folder)
+ (list (copy-message message)))))
+
+(define-method expunge-deleted-messages ((folder <file-folder>))
+ (set-file-folder-messages!
+ folder
+ (list-transform-negative (file-folder-messages folder) message-deleted?)))
+
+(define-method search-folder ((folder <file-folder>) criteria)
+ folder criteria
+ (error "Unimplemented operation:" 'SEARCH-FOLDER))
+
+(define-method synchronize-folder ((folder <file-folder>))
+ folder
+ unspecific)
+
+(define-method subscribe-folder ((folder <file-folder>))
+ folder
+ (error "Unimplemented operation:" 'SUBSCRIBE-FOLDER))
+
+(define-method unsubscribe-folder ((folder <file-folder>))
+ folder
+ (error "Unimplemented operation:" 'UNSUBSCRIBE-FOLDER))
\ No newline at end of file
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: imail-rmail.scm,v 1.1 2000/01/04 22:51:02 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: RMAIL back end
+
+(declare (usual-integrations))
+\f
+;;;; URL
+
+(define-class (<rmail-url> (constructor (pathname))) (<file-url>))
+
+(define-url-protocol "rmail" <rmail-url>
+ (lambda (string)
+ (make-rmail-url (short-name->pathname string))))
+
+;;;; Server operations
+
+(define-method %open-folder ((url <rmail-url>))
+ (read-rmail-file url))
+
+(define-method %new-folder ((url <rmail-url>))
+ (let ((folder (make-rmail-folder url 'COMPUTE '())))
+ (save-folder folder)
+ folder))
+
+;;;; Folder
+
+(define-class (<rmail-folder> (constructor (url header-fields messages)))
+ (<file-folder>)
+ (header-fields define standard accessor header-fields))
+
+(define-method %write-folder ((folder <folder>) (url <rmail-url>))
+ (write-rmail-file folder (file-url-pathname url)))
+
+(define-method poll-folder ((folder <rmail-folder>))
+ (rmail-get-new-mail folder))
+
+(define-method initialize-instance ((folder <rmail-folder>))
+ (if (eq? 'COMPUTE (header-fields folder))
+ (set-rmail-folder-header-fields!
+ folder
+ (compute-rmail-folder-header-fields folder))))
+
+(define-method header-fields ((folder <folder>))
+ (compute-rmail-folder-header-fields folder))
+
+(define (compute-rmail-folder-header-fields folder)
+ (list (make-header-field "Version" " 5")
+ (make-header-field
+ "Labels"
+ (let ((labels (compute-rmail-folder-labels folder)))
+ (if (pair? labels)
+ (apply string-append
+ (car labels)
+ (map (lambda (label) (string-append "," label))
+ (cdr labels)))
+ "")))
+ (make-header-field "Note" " This is the header of an rmail file.")
+ (make-header-field "Note" " If you are seeing it in rmail,")
+ (make-header-field "Note"
+ " it means the file has no messages in it.")))
+
+(define (compute-rmail-folder-labels folder)
+ (flags->rmail-labels
+ (let ((n (count-messages folder)))
+ (let loop ((index 0) (flags '()))
+ (if (fix:< index n)
+ (loop (fix:+ index 1)
+ (union-of-lists (message-flags (get-message folder index))
+ flags))
+ flags)))))
+\f
+;;;; Read RMAIL file
+
+(define (read-rmail-file url)
+ (let* ((pathname (file-url-pathname url))
+ (namestring (->namestring pathname)))
+ (call-with-input-file pathname
+ (lambda (port)
+ (let ((folder-headers (read-rmail-prolog port)))
+ (make-rmail-folder url
+ folder-headers
+ (read-rmail-messages port)))))))
+
+(define (read-rmail-prolog port)
+ (if (not (string-prefix? "BABYL OPTIONS:" (read-required-line port)))
+ (error "Not an RMAIL file:" port))
+ (lines->header-fields (read-lines-to-eom port)))
+
+(define (read-rmail-messages port)
+ (source->list (lambda () (read-rmail-message port))))
+
+(define (read-rmail-message port)
+ ;; **** This must be generalized to recognize an RMAIL file that has
+ ;; unix-mail format messages appended to it.
+ (let ((line (read-line port)))
+ (cond ((eof-object? line)
+ line)
+ ((and (fix:= 1 (string-length line))
+ (char=? rmail-message:start-char (string-ref line 0)))
+ (read-rmail-message-1 port))
+ (else
+ (error "Malformed RMAIL file:" port)))))
+
+(define (read-rmail-message-1 port)
+ (call-with-values
+ (lambda () (parse-attributes-line (read-required-line port)))
+ (lambda (formatted? flags)
+ (let* ((headers (read-rmail-header-fields port))
+ (displayed-headers
+ (lines->header-fields (read-header-lines port)))
+ (body (read-to-eom port))
+ (finish
+ (lambda (headers)
+ (let ((message (make-standard-message headers body)))
+ (for-each (lambda (flag)
+ (set-message-flag message flag))
+ flags)
+ (let ((headers (message-header-fields message)))
+ (if (and (pair? headers)
+ (string-ci=? "summary-line"
+ (header-field-name (car headers))))
+ (begin
+ (set-message-property
+ message
+ (header-field-name (car headers))
+ (header-field-value (car headers)))
+ (set-message-header-fields! message (cdr headers)))))
+ message))))
+ (if formatted?
+ (let ((message (finish headers)))
+ (set-message-property message
+ "displayed-header-fields"
+ displayed-headers)
+ message)
+ (finish displayed-headers))))))
+\f
+(define (parse-attributes-line line)
+ (let ((parts (map string-trim (burst-string line #\, #f))))
+ (if (not (and (fix:= 2 (count-matching-items parts string-null?))
+ (or (string=? "0" (car parts))
+ (string=? "1" (car parts)))
+ (string-null? (car (last-pair parts)))))
+ (error "Malformed RMAIL message-attributes line:" line))
+ (call-with-values
+ (lambda () (cut-list! (except-last-pair (cdr parts)) string-null?))
+ (lambda (attributes labels)
+ (values (string=? "1" (car parts))
+ (rmail-markers->flags attributes
+ (if (pair? labels)
+ (cdr labels)
+ labels)))))))
+
+(define (read-rmail-header-fields port)
+ (lines->header-fields
+ (source->list
+ (lambda ()
+ (let ((line (read-required-line port)))
+ (cond ((string-null? line)
+ (if (not (string=? rmail-message:headers-separator
+ (read-required-line port)))
+ (error "Missing RMAIL message-header separator string:"
+ port))
+ (make-eof-object port))
+ ((string=? rmail-message:headers-separator line)
+ (make-eof-object port))
+ (else line)))))))
+\f
+;;;; Write RMAIL file
+
+(define (write-rmail-file folder url)
+ ;; **** Do backup of file here.
+ (call-with-output-file (file-url-pathname url)
+ (lambda (port)
+ (write-rmail-prolog (header-fields folder) port)
+ (write-rmail-messages (file-folder-messages folder) port))))
+
+(define (write-rmail-prolog header-fields port)
+ (write-string "BABYL OPTIONS: -*- rmail -*-" port)
+ (newline port)
+ (write-header-fields header-fields port)
+ (write-char rmail-message:end-char port))
+
+(define (write-rmail-messages messages port)
+ (for-each (lambda (message) (write-rmail-message message port)) messages))
+
+(define (write-rmail-message message port)
+ (write-char rmail-message:start-char port)
+ (newline port)
+ (let ((headers (message-header-fields message))
+ (displayed-headers
+ (get-message-property message "displayed-header-fields" 'NONE)))
+ (write-rmail-attributes-line message displayed-headers port)
+ (if (not (eq? 'NONE displayed-headers))
+ (begin
+ (write-rmail-properties message port)
+ (write-header-fields headers port)
+ (newline port)))
+ (write-string rmail-message:headers-separator port)
+ (newline port)
+ (if (eq? 'NONE displayed-headers)
+ (begin
+ (write-rmail-properties message port)
+ (write-header-fields headers port))
+ (write-header-fields displayed-headers port))
+ (newline port)
+ (write-string (message-body message) port)
+ (fresh-line port)
+ (write-char rmail-message:end-char port)))
+
+(define (write-rmail-attributes-line message formatted? port)
+ (write-char (if formatted? #\1 #\0) port)
+ (write-char #\, port)
+ (call-with-values (lambda () (flags->rmail-markers (message-flags message)))
+ (lambda (attributes labels)
+ (let ((write-markers
+ (lambda (markers)
+ (for-each (lambda (marker)
+ (write-char #\space port)
+ (write-string marker port)
+ (write-char #\, port))
+ markers))))
+ (write-markers attributes)
+ (write-char #\, port)
+ (write-markers labels))))
+ (newline port))
+
+(define (write-rmail-properties message port)
+ (let ((alist (message-properties message)))
+ (let ((summary-line
+ (list-search-positive alist
+ (lambda (n.v)
+ (string-ci=? "summary-line" (car n.v))))))
+ (if summary-line
+ (%write-header-field (car n.v) (cdr n.v) port)))
+ (for-each
+ (lambda (n.v)
+ (if (not (or (string-ci=? "summary-line" (car n.v))
+ (string-ci=? "displayed-header-fields" (car n.v))))
+ (write-header-field
+ (message-property->header-field (car n.v) (cdr n.v))
+ port)))
+ alist)))
+\f
+;;;; Get new mail
+
+(define (rmail-get-new-mail folder)
+ (let ((pathnames (rmail-folder-inbox-list folder)))
+ (if (null? pathnames)
+ #f
+ (let ((initial-count (count-messages folder)))
+ (let ((inbox-folders
+ (map (lambda (pathname)
+ (let ((inbox (read-rmail-inbox folder pathname #t)))
+ (let ((n (count-messages inbox)))
+ (do ((index 0 (fix:+ index 1)))
+ ((fix:= i n))
+ (append-message folder
+ (get-message inbox index))))
+ inbox))
+ pathnames)))
+ (save-folder folder)
+ (for-each (lambda (folder)
+ (if folder
+ (delete-folder (folder-url folder))))
+ inbox-folders))
+ (fix:- (count-messages folder) initial-count)))))
+
+(define (rmail-folder-inbox-list folder)
+ (let ((url (folder-url folder))
+ (inboxes (get-first-header-field-value folder "mail" #f)))
+ (cond (inboxes
+ (map (let ((directory
+ (directory-pathname (file-url-pathname url))))
+ (lambda (filename)
+ (merge-pathnames (string-trim filename) directory)))
+ (burst-string inboxes #\, #f)))
+ ((pathname=? (rmail-primary-folder-name) (url-body url))
+ (rmail-primary-inbox-list))
+ (else '()))))
+
+(define (rmail-primary-folder-name)
+ "RMAIL")
+
+(define (rmail-primary-inbox-list)
+ (error "Unimplemented procedure:" 'RMAIL-PRIMARY-INBOX-LIST))
+
+(define (rmail-spool-directory)
+ (error "Unimplemented procedure:" 'RMAIL-SPOOL-DIRECTORY))
+\f
+(define (read-rmail-inbox folder pathname rename?)
+ (let ((pathname
+ (cond ((not rename?)
+ pathname)
+ ((pathname=? (rmail-spool-directory)
+ (directory-pathname pathname))
+ (rename-inbox-using-movemail
+ pathname
+ (directory-pathname
+ (file-url-pathname (folder-url folder)))))
+ (else
+ (rename-inbox-using-rename pathname)))))
+ (and (file-exists? pathname)
+ (open-folder (make-url "umail" (pathname->short-name pathname))))))
+
+(define (rename-inbox-using-movemail pathname directory)
+ (let ((pathname
+ ;; On some systems, /usr/spool/mail/foo is a directory and
+ ;; the actual inbox is /usr/spool/mail/foo/foo.
+ (if (file-directory? pathname)
+ (merge-pathnames (file-pathname pathname)
+ (pathname-as-directory pathname))
+ pathname))
+ (target (merge-pathnames ".newmail" directory)))
+ (if (and (file-exists? pathname)
+ (not (file-exists? target)))
+ (let ((port (make-accumulator-output-port)))
+ (let ((result
+ (run-shell-command
+ (string-append "movemail "
+ (->namestring pathname)
+ " "
+ (->namestring target))
+ 'OUTPUT port)))
+ (if (not (= 0 result))
+ (error "Movemail failure:"
+ (get-output-from-accumulator port))))))
+ target))
+
+(define (rename-inbox-using-rename pathname)
+ (let ((target
+ (merge-pathnames (string-append (file-namestring pathname) "+")
+ (directory-pathname pathname))))
+ (if (and (file-exists? pathname)
+ (not (file-exists? target)))
+ (rename-file pathname target))
+ target))
+\f
+;;;; Attributes and labels
+
+(define (rmail-markers->flags attributes labels)
+ (let loop
+ ((strings (remove-equal-duplicates (append attributes labels)))
+ (flags '()))
+ (if (pair? strings)
+ (loop (cdr strings)
+ (cons (if (list-search-positive rmail-attributes
+ (lambda (attribute)
+ (string-ci=? attribute (car strings))))
+ (rmail-attribute->flag (car strings))
+ (rmail-label->flag (car strings)))
+ flags))
+ (reverse! flags))))
+
+(define (flags->rmail-markers flags)
+ (let loop ((flags flags) (attributes '()) (labels '()))
+ (if (pair? flags)
+ (if (flag-is-rmail-attribute? (car flags))
+ (loop (cdr flags)
+ (cons (flag->rmail-attribute (car flags)) attributes)
+ labels)
+ (loop (cdr flags)
+ attributes
+ (cons (flag->rmail-label (car flags)) labels)))
+ (values (reverse! attributes) (reverse! labels)))))
+
+(define (flags->rmail-labels flags)
+ (call-with-values (lambda () (flags->rmail-markers flags))
+ (lambda (attributes labels)
+ attributes
+ labels)))
+
+(define (flag-is-rmail-attribute? flag)
+ (memq flag rmail-attribute-flags))
+
+(define (flag->rmail-attribute flag)
+ (symbol->string flag))
+
+(define (rmail-attribute->flag attribute)
+ (intern attribute))
+
+(define (flag->rmail-label flag)
+ (if (symbol? flag)
+ (string-append "standard:" (symbol->string flag))
+ flag))
+
+(define (rmail-label->flag label)
+ (if (string-prefix? "standard:" label)
+ (intern (string-tail label 9))
+ label))
+\f
+;;;; Syntactic Markers
+
+(define rmail-message:headers-separator
+ "*** EOOH ***")
+
+(define rmail-message:start-char
+ #\page)
+
+(define rmail-message:end-char
+ (integer->char #x1f))
+
+(define rmail-message:end-char-set
+ (char-set rmail-message:end-char))
+
+(define rmail-attributes
+ '("deleted" "answered" "unseen" "filed" "forwarded" "edited" "resent"))
+
+(define rmail-attribute-flags
+ (map intern rmail-attributes))
+
+;;;; Utilities
+
+(define (read-lines-to-eom port)
+ (source->list
+ (lambda ()
+ (if (eqv? rmail-message:end-char (peek-char port))
+ (begin
+ (read-char port) ;discard
+ (make-eof-object port))
+ (read-required-line port)))))
+
+(define (read-to-eom port)
+ (let ((string (read-string rmail-message:end-char-set port)))
+ (if (or (eof-object? string)
+ (eof-object? (read-char port)))
+ (error "EOF while reading RMAIL message body:" port))
+ string))
\ No newline at end of file
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: imail-top.scm,v 1.1 2000/01/04 22:51:05 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: top level
+
+(declare (usual-integrations))
+\f
+(define-command imail
+ "Read and edit incoming mail."
+ ()
+ (lambda ()
+ (let ((connection
+ (let ((backend (get-backend (ref-variable imail-backend-type))))
+ (open-connection
+ backend
+ (and (backend-requires-hostname? backend)
+ (ref-variable imail-backend-server))))))
+ (authenticate-connection connection
+ (or (ref-variable imail-user-name)
+ (current-user-name))
+ (lambda (prompt-string)
+ (call-with-pass-phrase prompt-string string-copy)))
+ (let ((name (ref-variable imail-primary-folder)))
+ (let ((folder (get-folder connection name)))
+ (let ((buffer
+ (or (imail-folder->buffer folder)
+ (let ((buffer
+ (new-buffer
+ (imail-folder-name->buffer-name name))))
+ (buffer-put! buffer 'IMAIL-FOLDER folder)
+ (select-message
+ buffer
+ (let ((count (count-messages folder)))
+ (if (= 0 count)
+ count
+ (- count 1))))
+ buffer))))
+ (select-buffer buffer)))))
+ ((ref-command imail-get-new-mail) #f)))
+
+(define (imail-folder->buffer folder)
+ )
+
+(define (imail-folder-name->buffer-name folder)
+ )
+
+(define-command imail-get-new-mail
+ "Get new mail from this folder's inbox."
+ ()
+ (lambda ()
+ (let ((buffer (current-buffer)))
+ (rmail-find-file-revert buffer)
+ (let ((n-messages
+ (let ((memo (buffer-msg-memo buffer)))
+ (if (msg-memo? memo)
+ (msg-memo/number (msg-memo/last memo))
+ 0))))
+ (with-buffer-open buffer
+ (lambda ()
+ (with-buffer-undo-disabled buffer
+ (lambda ()
+ (get-new-mail buffer
+ (ref-variable rmail-inbox-list)
+ #t)))))
+ (show-message
+ buffer
+ (let ((memo (buffer-msg-memo buffer)))
+ (cond ((not (msg-memo? memo)) 0)
+ ((> (msg-memo/number (msg-memo/last memo)) n-messages)
+ (+ n-messages 1))
+ (else (msg-memo/number memo)))))
+ (event-distributor/invoke! (ref-variable rmail-new-mail-hook))))))
\ No newline at end of file
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: imail-umail.scm,v 1.1 2000/01/04 22:51:09 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: RMAIL back end
+
+(declare (usual-integrations))
+\f
+;;;; URL
+
+(define-class (<umail-url> (constructor (pathname))) (<file-url>))
+
+(define-url-protocol "umail" <umail-url>
+ (lambda (string)
+ (make-umail-url (short-name->pathname string))))
+
+;;;; Server operations
+
+(define-method %open-folder ((url <umail-url>))
+ (read-umail-file url))
+
+(define-method %new-folder ((url <umail-url>))
+ (let ((folder (make-umail-folder url '())))
+ (save-folder folder)
+ folder))
+
+;;;; Folder
+
+(define-class (<umail-folder> (constructor (url messages))) (<file-folder>))
+
+(define-method %write-folder ((folder <folder>) (url <umail-url>))
+ (write-umail-file folder (file-url-pathname url)))
+
+(define-method poll-folder ((folder <umail-folder>))
+ folder
+ #f)
+\f
+;;;; Read unix mail file
+
+(define (read-umail-file url)
+ (let* ((pathname (file-url-pathname url))
+ (namestring (->namestring pathname)))
+ (call-with-input-file pathname
+ (lambda (port)
+ (make-umail-folder url (read-umail-messages port namestring))))))
+
+(define (read-umail-messages port namestring)
+ (map (lambda (lines)
+ (parse-umail-message lines namestring))
+ (let ((groups
+ (burst-list (read-lines port)
+ (lambda (line)
+ (re-string-match unix-mail-delimiter line)))))
+ (if (and (pair? groups)
+ (not (null? (caar groups))))
+ (error "Malformed unix mail file:" namestring))
+ groups)))
+
+(define (parse-umail-message lines namestring)
+ (let loop ((ls (cdr lines)) (header-lines '()))
+ (if (pair? ls)
+ (if (string-null? (car ls))
+ (let ((message
+ (make-standard-message headers (lines->string (cdr ls)))))
+ (set-message-property message "umail-from-line" (car lines))
+ message)
+ (loop (cdr ls) (cons (car ls) header-lines)))
+ (error "Malformed unix mail file:" namestring))))
+\f
+;;;; Write unix mail file
+
+(define (write-umail-file folder pathname)
+ (call-with-output-file (file-url-pathname url)
+ (lambda (port)
+ (write-umail-messages (file-folder-messages folder) port))))
+
+(define (write-umail-messages messages port)
+ (for-each (lambda (message) (write-umail-message message port)) messages))
+
+(define (write-umail-message message port)
+ (write-string
+ (let ((header (get-first-header-field message "umail-from-line" #f)))
+ (if header
+ (string-trim (header-field-value header))
+ (string-append "From "
+ (or (rfc822-first-address
+ (get-first-header-field message "from" #t))
+ "unknown")
+ " "
+ (universal-time->string (get-universal-time)))))
+ port)
+ (newline port)
+ (write-header-field (message-flags->header-field (message-flags message))
+ port)
+ (for-each (lambda (n.v)
+ (if (not (string-ci=? "umail-from-line" (car n.v)))
+ (write-header-field
+ (message-property->header-field (car n.v) (cdr n.v))
+ port)))
+ (message-properties message))
+ (write-header-fields (message-header-fields message) port)
+ (newline port)
+ (write-string (message-body message) port)
+ (fresh-line port))
+\f
+;;;; Detection of unix "from" lines.
+
+(define unix-mail-delimiter
+ ;; This very complex regular expression taken from Emacs 20.
+ (let ((time-zone-regexp
+ (string-append
+ (regexp-group "[A-Z]?[A-Z]?[A-Z][A-Z]\\( DST\\)?"
+ "[-+]?[0-9][0-9][0-9][0-9]"
+ "")
+ " *")))
+ (string-append
+ "^From "
+
+ ;; Many things can happen to an RFC 822 mailbox before it is put into
+ ;; a `From' line. The leading phrase can be stripped, e.g.
+ ;; `Joe <@w.x:joe@y.z>' -> `<@w.x:joe@y.z>'. The <> can be stripped, e.g.
+ ;; `<@x.y:joe@y.z>' -> `@x.y:joe@y.z'. Everything starting with a CRLF
+ ;; can be removed, e.g.
+ ;; From: joe@y.z (Joe K
+ ;; User)
+ ;; can yield `From joe@y.z (Joe K Fri Mar 22 08:11:15 1996', and
+ ;; From: Joe User
+ ;; <joe@y.z>
+ ;; can yield `From Joe User Fri Mar 22 08:11:15 1996'.
+ ;; The mailbox can be removed or be replaced by white space, e.g.
+ ;; From: "Joe User"{space}{tab}
+ ;; <joe@y.z>
+ ;; can yield `From {space}{tab} Fri Mar 22 08:11:15 1996',
+ ;; where {space} and {tab} represent the Ascii space and tab characters.
+ ;; We want to match the results of any of these manglings.
+ ;; The following regexp rejects names whose first characters are
+ ;; obviously bogus, but after that anything goes.
+ "\\([^\0-\b\n-\r\^?].*\\)? "
+
+ ;; The time the message was sent.
+ "\\([^\0-\r \^?]+\\) +" ; day of the week
+ "\\([^\0-\r \^?]+\\) +" ; month
+ "\\([0-3]?[0-9]\\) +" ; day of month
+ "\\([0-2][0-9]:[0-5][0-9]\\(:[0-6][0-9]\\)?\\) *" ; time of day
+
+ ;; Perhaps a time zone, specified by an abbreviation, or by a
+ ;; numeric offset.
+ time-zone-regexp
+
+ ;; The year.
+ " \\([0-9][0-9]+\\) *"
+
+ ;; On some systems the time zone can appear after the year, too.
+ time-zone-regexp
+
+ ;; Old uucp cruft.
+ "\\(remote from .*\\)?"
+
+ "$")))
\ No newline at end of file
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: imail-util.scm,v 1.1 2000/01/04 22:51:15 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: utilities
+
+(declare (usual-integrations))
+\f
+(define (guarantee-index index procedure)
+ (if (not (index-fixnum? index))
+ (error:wrong-type-argument index "index" procedure)))
+
+(define (union-of-lists l1 l2)
+ (let loop ((l1 l1) (l2 l2))
+ (if (pair? l1)
+ (loop (cdr l1)
+ (if (member (car l1) l2)
+ l2
+ (cons (car l1) l2)))
+ l2)))
+
+(define (source->list source)
+ (let ((item (source)))
+ (if (eof-object? item)
+ '()
+ (let ((head (list item)))
+ (let loop ((prev head))
+ (let ((item (source)))
+ (if (eof-object? item)
+ head
+ (let ((this (list item)))
+ (set-cdr! prev this)
+ (loop this)))))))))
+
+(define (list->source items)
+ (lambda ()
+ (if (pair? items)
+ (let ((item (car items)))
+ (set! items (cdr items))
+ item)
+ (make-eof-object #f))))
+
+(define (cut-list items predicate)
+ (if (or (not (pair? items)) (predicate (car items)))
+ (values '() items)
+ (let ((head (list (car items))))
+ (values head
+ (let loop ((prev head) (this (cdr items)))
+ (if (or (not (pair? this)) (predicate (car this)))
+ this
+ (let ((next (list (car this))))
+ (set-cdr! prev next)
+ (loop next (cdr this)))))))))
+
+(define (cut-list! items predicate)
+ (if (or (not (pair? items)) (predicate (car items)))
+ (values '() items)
+ (let loop ((prev items) (this (cdr items)))
+ (if (or (not (pair? this)) (predicate (car this)))
+ (begin
+ (set-cdr! prev '())
+ (values items this))
+ (loop this (cdr this))))))
+
+(define (burst-list items predicate)
+ (let loop ((items items) (groups '()))
+ (if (pair? items)
+ (let find-next ((items (cdr items)) (group (list (car items))))
+ (if (and (pair? items) (not (predicate (car items))))
+ (find-next (cdr items) (cons (car items) group))
+ (loop items (cons (reverse! group) groups))))
+ (reverse! groups))))
+\f
+;; The cryptic LWSP means Linear White SPace. We use it because it
+;; is the terminology from RFC 822.
+
+(define (char-lwsp? char)
+ (or (char=? #\space char)
+ (char=? #\tab char)))
+
+(define char-set:lwsp
+ (char-set #\space #\tab))
+
+(define (quote-lines lines)
+ (map (lambda (line)
+ (string-append "\t" line))
+ lines))
+
+(define (unquote-lines lines)
+ (map (lambda (line)
+ (if (and (fix:> (string-length line) 0)
+ (char=? #\tab (string-ref line 0)))
+ (string-tail line 1)
+ (error "Unquoted line:" line)))
+ lines))
+
+(define (string->lines string)
+ (let ((lines (burst-string string #\newline #f)))
+ (if (string-null? (car (last-pair lines)))
+ (except-last-pair! lines)
+ lines)))
+
+(define (lines->string lines)
+ (apply string-append
+ (map (lambda (line)
+ (string-append line "\n"))
+ lines)))
+
+(define (short-name->pathname name)
+ (merge-pathnames name (current-home-directory)))
+
+(define (pathname->short-name pathname)
+ (enough-namestring pathname (current-home-directory)))
+
+(define (write-header-fields headers port)
+ (for-each (lambda (header)
+ (write-header-field header port))
+ headers))
+
+(define (write-header-field header port)
+ (%write-header-field (header-field-name header)
+ (header-field-value header)
+ port))
+
+(define (%write-header-field name value port)
+ (write-string name port)
+ (write-char #\: port)
+ (write-string value port)
+ (newline port))
+
+(define (separated-append tokens separator)
+ (if (pair? tokens)
+ (if (pair? (cdr tokens))
+ (let loop ((tokens (cdr tokens)) (tokens* (list (car tokens))))
+ (if (pair? tokens)
+ (loop (cdr tokens) (cons* (car tokens) separator tokens*))
+ (apply string-append (reverse! tokens*))))
+ (car tokens))
+ ""))
+\f
+(define (read-header-lines port)
+ (source->list
+ (lambda ()
+ (let ((line (read-required-line port)))
+ (if (string-null? line)
+ (make-eof-object port)
+ line)))))
+
+(define (read-required-line port)
+ (let ((line (read-line port)))
+ (if (eof-object? line)
+ (error "Premature end of file:" port))
+ line))
+
+(define (remove-equal-duplicates items)
+ (if (pair? items)
+ (if (member (car items) (cdr items))
+ (remove-equal-duplicates (cdr items))
+ (cons (car items) (remove-equal-duplicates (cdr items))))
+ '()))
+
+(define (count-matching-items items predicate)
+ (let loop ((items items) (count 0))
+ (if (pair? items)
+ (loop (cdr items)
+ (if (predicate (car items))
+ (fix:+ count 1)
+ count))
+ count)))
\ No newline at end of file
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: load.scm,v 1.1 2000/01/04 22:51:42 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: loader
+
+(load-option 'SOS)
+(load "imail-util")
+(load "rfc822")
+(load "imail-core")
+(load "imail-file")
+(load "imail-rmail")
+(load "imail-umail")
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+#
+# $Id: print.sh,v 1.1 2000/01/04 22:52:21 cph Exp $
+#
+# Copyright (c) 1999 Massachusetts Institute of Technology
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+prlist imail-core.scm imail-file.scm imail-rmail.scm imail-umail.scm rfc822.scm imail-util.scm
--- /dev/null
+;;; -*-Scheme-*-
+;;;
+;;; $Id: rfc822.scm,v 1.1 2000/01/04 22:51:45 cph Exp $
+;;;
+;;; Copyright (c) 1999 Massachusetts Institute of Technology
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation; either version 2 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;;; IMAIL mail reader: RFC-822 support
+
+(declare (usual-integrations))
+\f
+(define (rfc822-first-address field)
+ (let ((addresses (rfc822-strip-quoted-names field)))
+ (and (pair? addresses)
+ (car addresses))))
+
+(define (rfc822-addresses->string addresses)
+ (if (null? addresses)
+ ""
+ (separated-append addresses ", ")))
+\f
+;;;; Address extractor
+
+(define (rfc822-strip-quoted-names string)
+ (let ((address-list
+ (rfc822-strip-quoted-names-1 (string->rfc822-tokens string))))
+ (if (and address-list (null? (cdr address-list)))
+ (car address-list)
+ (map string-trim (burst-string string #\, #f)))))
+
+(define (rfc822-strip-quoted-names-1 tokens)
+ (define (parse-addr-spec tokens)
+ (let ((local-part (parse-list tokens parse-word #\.)))
+ (and local-part
+ (not (null? (cdr local-part)))
+ (eqv? #\@ (cadr local-part))
+ (let ((domain (parse-domain (cddr local-part))))
+ (and domain
+ (cons (string-append (separated-append (car local-part) ".")
+ "@"
+ (separated-append (car domain) "."))
+ (cdr domain)))))))
+ (define (parse-domain tokens)
+ (parse-list tokens
+ (lambda (tokens)
+ (and (not (null? tokens))
+ (string? (car tokens))
+ (not (eqv? #\" (string-ref (car tokens) 0)))
+ tokens))
+ #\.))
+ (define (parse-list tokens parse-element separator)
+ (let ((first (parse-element tokens)))
+ (and first
+ (let loop ((tokens (cdr first)) (words (list (car first))))
+ (let ((next
+ (and (not (null? tokens))
+ (eqv? separator (car tokens))
+ (parse-element (cdr tokens)))))
+ (if next
+ (loop (cdr next) (cons (car next) words))
+ (cons (reverse! words) tokens)))))))
+ (define (parse-word tokens)
+ (and (not (null? tokens))
+ (string? (car tokens))
+ (not (eqv? #\[ (string-ref (car tokens) 0)))
+ tokens))
+ (parse-list
+ tokens
+ (lambda (tokens)
+ (or (parse-addr-spec tokens)
+ (let ((word (parse-word tokens)))
+ (and word
+ (let ((tokens
+ (let loop ((tokens (cdr word)))
+ (let ((word (parse-word tokens)))
+ (if word
+ (loop (cdr word))
+ tokens)))))
+ (and (not (null? tokens))
+ (eqv? #\< (car tokens))
+ (let ((addr-spec
+ (parse-addr-spec
+ (let ((domains
+ (parse-list
+ (cdr tokens)
+ (lambda (tokens)
+ (and (not (null? tokens))
+ (eqv? #\@ (car tokens))
+ (parse-domain (cdr tokens))))
+ #\,)))
+ (if (and domains
+ (not (null? (cdr domains)))
+ (eqv? #\: (cadr domains)))
+ (cddr domains)
+ (cdr tokens))))))
+ (and addr-spec
+ (not (null? (cdr addr-spec)))
+ (eqv? #\> (cadr addr-spec))
+ (cons (car addr-spec) (cddr addr-spec))))))))))
+ #\,))
+\f
+;;;; Parser
+
+(define (string->rfc822-tokens string)
+ (rfc822-clean-tokens (rfc822-read-tokens (string->input-port string))))
+
+(define (rfc822-clean-tokens tokens)
+ (let loop ((tokens tokens))
+ (if (null? tokens)
+ '()
+ (let ((rest (loop (cdr tokens))))
+ (if (cond ((char? (car tokens))
+ (eqv? #\space (car tokens)))
+ ((string? (car tokens))
+ (char=? #\( (string-ref (car tokens) 0)))
+ (else true))
+ rest
+ (cons (car tokens) rest))))))
+
+(define rfc822-read-tokens
+ (let* ((special-chars
+ (char-set #\( #\) #\[ #\] #\< #\> #\@ #\, #\; #\: #\\ #\" #\.))
+ (atom-chars
+ (char-set-difference (ascii-range->char-set #x21 #x7F)
+ special-chars)))
+ (lambda (port)
+ (let ((special-char?
+ (lambda (char) (char-set-member? special-chars char)))
+ (atom-char? (lambda (char) (char-set-member? atom-chars char)))
+ (lwsp?
+ (lambda (char) (or (char=? #\space char) (char=? #\tab char))))
+ (loser
+ (lambda (chars)
+ (list (cons 'UNTERMINATED (apply string (reverse! chars)))))))
+ (let dispatch ()
+ (let ((char (input-port/read-char port)))
+ (cond ((eof-object? char)
+ '())
+ ((lwsp? char)
+ (do ()
+ ((not (lwsp? (input-port/peek-char port))))
+ (input-port/discard-char port))
+ (cons #\space (dispatch)))
+ ((atom-char? char)
+ ;; atom
+ (let loop ((chars (list char)))
+ (let ((char (input-port/peek-char port)))
+ (if (and (not (eof-object? char))
+ (atom-char? char))
+ (begin
+ (input-port/discard-char port)
+ (loop (cons char chars)))
+ (cons (apply string (reverse! chars))
+ (dispatch))))))
+ ((char=? #\" char)
+ ;; quoted string
+ (let loop ((chars (list char)))
+ (let ((char (input-port/read-char port)))
+ (cond ((eof-object? char)
+ (loser chars))
+ ((char=? #\" char)
+ (cons (apply string (reverse! (cons char chars)))
+ (dispatch)))
+ ((char=? #\\ char)
+ (let ((char (input-port/read-char port))
+ (chars (cons char chars)))
+ (if (eof-object? char)
+ (loser chars)
+ (loop (cons char chars)))))
+ ((char=? #\newline char)
+ (let ((char (input-port/peek-char port)))
+ (if (lwsp? char)
+ (begin
+ (input-port/discard-char port)
+ (loop (cons char chars)))
+ (loser chars))))
+ (else
+ (loop (cons char chars)))))))
+\f
+ ((char=? #\( char)
+ ;; comment
+ (let loop ((level 1) (chars (list char)))
+ (let ((char (input-port/read-char port)))
+ (cond ((eof-object? char)
+ (loser chars))
+ ((char=? #\( char)
+ (loop (+ level 1) (cons char chars)))
+ ((char=? #\) char)
+ (let ((chars (cons char chars)))
+ (if (= level 1)
+ (cons (apply string (reverse! chars))
+ (dispatch))
+ (loop (- level 1) chars))))
+ ((char=? #\\ char)
+ (let ((char (input-port/read-char port))
+ (chars (cons char chars)))
+ (if (eof-object? char)
+ (loser chars)
+ (loop level (cons char chars)))))
+ ((char=? #\newline char)
+ (let ((char (input-port/peek-char port)))
+ (if (lwsp? char)
+ (begin
+ (input-port/discard-char port)
+ (loop level (cons char chars)))
+ (loser chars))))
+ (else
+ (loop level (cons char chars)))))))
+ ((char=? #\[ char)
+ ;; domain literal
+ (let loop ((chars (list char)))
+ (let ((char (input-port/peek-char port)))
+ (cond ((or (eof-object? char)
+ (char=? #\[ char))
+ (loser chars))
+ ((char=? #\] char)
+ (input-port/discard-char port)
+ (cons (apply string (reverse! (cons char chars)))
+ (dispatch)))
+ ((char=? #\\ char)
+ (input-port/discard-char port)
+ (let ((char (input-port/read-char port))
+ (chars (cons char chars)))
+ (if (eof-object? char)
+ (loser chars)
+ (loop (cons char chars)))))
+ ((char=? #\newline char)
+ (input-port/discard-char port)
+ (let ((char (input-port/peek-char port)))
+ (if (lwsp? char)
+ (begin
+ (input-port/discard-char port)
+ (loop (cons char chars)))
+ (loser chars))))
+ (else
+ (input-port/discard-char port)
+ (loop (cons char chars)))))))
+ ((char=? #\newline char)
+ (let ((char (input-port/peek-char port)))
+ (if (and (not (eof-object? char))
+ (lwsp? char))
+ (dispatch)
+ '())))
+ (else
+ (cons (if (special-char? char)
+ char
+ (cons 'ILLEGAL char))
+ (dispatch))))))))))
\ No newline at end of file