home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / mitsch75.zip / scheme-7_5_17-src.zip / scheme-7.5.17 / src / runtime / mime-codec.scm < prev    next >
Text File  |  2001-02-08  |  32KB  |  891 lines

  1. ;;; -*-Scheme-*-
  2. ;;;
  3. ;;; $Id: mime-codec.scm,v 14.9 2001/02/08 17:16:05 cph Exp $
  4. ;;;
  5. ;;; Copyright (c) 2000 Massachusetts Institute of Technology
  6. ;;;
  7. ;;; This program is free software; you can redistribute it and/or
  8. ;;; modify it under the terms of the GNU General Public License as
  9. ;;; published by the Free Software Foundation; either version 2 of the
  10. ;;; License, or (at your option) any later version.
  11. ;;;
  12. ;;; This program is distributed in the hope that it will be useful,
  13. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15. ;;; General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with this program; if not, write to the Free Software
  19. ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  
  21. ;;;; IMAIL mail reader: MIME support
  22.  
  23. (declare (usual-integrations))
  24.  
  25. ;;;; Encode quoted-printable
  26.  
  27. ;;; Hair from two things: (1) delaying the decision to encode trailing
  28. ;;; whitespace until we see what comes after it on the line; and (2)
  29. ;;; an incremental line-breaking algorithm.
  30.  
  31. (define-structure (qp-encoding-context
  32.            (conc-name qp-encoding-context/)
  33.            (constructor encode-quoted-printable:initialize
  34.                 (port text?)))
  35.   (port #f read-only #t)
  36.   (text? #f read-only #t)
  37.   ;; Either #F, or an LWSP input that may or may not need to be
  38.   ;; encoded, depending on subsequent input.
  39.   (pending-lwsp #f)
  40.   ;; An exact integer between 0 and 75 inclusive, recording the number
  41.   ;; of characters that have been written on the current output line.
  42.   (column 0)
  43.   ;; Either #F, or an output string that may or may not fit on the
  44.   ;; current output line, depending on subsequent output.
  45.   (pending-output #f))
  46.  
  47. (define (encode-quoted-printable:finalize context)
  48.   (encode-qp-pending-lwsp context #f 'INPUT-END)
  49.   (write-qp-pending-output context #t))
  50.  
  51. (define (encode-quoted-printable:update context string start end)
  52.   (if (qp-encoding-context/text? context)
  53.       (let loop ((start start))
  54.     (let ((i (substring-find-next-char string start end #\newline)))
  55.       (if i
  56.           (begin
  57.         (encode-qp context string start i 'LINE-END)
  58.         (loop (fix:+ i 1)))
  59.           (encode-qp context string start end 'PARTIAL))))
  60.       (encode-qp context string start end 'PARTIAL)))
  61.  
  62. (define (encode-qp context string start end type)
  63.   (encode-qp-pending-lwsp context (fix:< start end) type)
  64.   (let loop ((start start))
  65.     (cond ((fix:< start end)
  66.        (let ((char (string-ref string start))
  67.          (start (fix:+ start 1)))
  68.          (cond ((not (char-lwsp? char))
  69.             (if (char-set-member? char-set:qp-encoded char)
  70.             (write-qp-encoded context char)
  71.             (write-qp-clear context char))
  72.             (loop start))
  73.            ((and (eq? type 'PARTIAL)
  74.              (not (fix:< start end)))
  75.             (set-qp-encoding-context/pending-lwsp! context char))
  76.            (else
  77.             (if (fix:< start end)
  78.             (write-qp-clear context char)
  79.             (write-qp-encoded context char))
  80.             (loop start)))))
  81.       ((eq? type 'LINE-END)
  82.        (write-qp-hard-break context)))))
  83.  
  84. (define (encode-qp-pending-lwsp context packet-not-empty? type)
  85.   (let ((pending (qp-encoding-context/pending-lwsp context)))
  86.     (if pending
  87.     (cond (packet-not-empty?
  88.            (set-qp-encoding-context/pending-lwsp! context #f)
  89.            (write-qp-clear context pending))
  90.           ((not (eq? type 'PARTIAL))
  91.            (set-qp-encoding-context/pending-lwsp! context #f)
  92.            (write-qp-encoded context pending))))))
  93.  
  94. (define (write-qp-clear context char)
  95.   (write-qp-pending-output context #f)
  96.   (let ((port (qp-encoding-context/port context))
  97.     (column (qp-encoding-context/column context)))
  98.     (cond ((fix:< column 75)
  99.        (write-char char port)
  100.        (set-qp-encoding-context/column! context (fix:+ column 1)))
  101.       ((not (qp-encoding-context/text? context))
  102.        (write-qp-soft-break context)
  103.        (write-char char port)
  104.        (set-qp-encoding-context/column! context 1))
  105.       (else
  106.        (set-qp-encoding-context/pending-output! context (string char))))))
  107.  
  108. (define (write-qp-encoded context char)
  109.   (write-qp-pending-output context #f)
  110.   (let ((port (qp-encoding-context/port context))
  111.     (column (qp-encoding-context/column context))
  112.     (d (char->integer char)))
  113.     (let ((c1 (hex-digit->char (fix:lsh d -4)))
  114.       (c2 (hex-digit->char (fix:and d #x0F))))
  115.       (if (fix:= column 73)
  116.       (set-qp-encoding-context/pending-output! context (string #\= c1 c2))
  117.       (begin
  118.         (if (fix:> column 73)
  119.         (write-qp-soft-break context))
  120.         (write-char #\= port)
  121.         (write-char c1 port)
  122.         (write-char c2 port)
  123.         (set-qp-encoding-context/column!
  124.          context
  125.          (fix:+ (qp-encoding-context/column context) 3)))))))
  126.  
  127. (define (write-qp-hard-break context)
  128.   (write-qp-pending-output context #t)
  129.   (newline (qp-encoding-context/port context))
  130.   (set-qp-encoding-context/column! context 0))
  131.  
  132. (define (write-qp-pending-output context newline?)
  133.   (let ((pending (qp-encoding-context/pending-output context)))
  134.     (if pending
  135.     (begin
  136.       (if (not newline?)
  137.           (write-qp-soft-break context))
  138.       (write-string pending (qp-encoding-context/port context))
  139.       (set-qp-encoding-context/pending-output! context #f)
  140.       (set-qp-encoding-context/column!
  141.        context
  142.        (fix:+ (qp-encoding-context/column context)
  143.           (string-length pending)))))))
  144.  
  145. (define (write-qp-soft-break context)
  146.   (let ((port (qp-encoding-context/port context)))
  147.     (write-char #\= port)
  148.     (newline port))
  149.   (set-qp-encoding-context/column! context 0))
  150.  
  151. ;;;; Decode quoted-printable
  152.  
  153. ;;; This decoder is unbelievably hairy.  The hair is due to the fact
  154. ;;; that the input to the decoder is arbitrarily packetized, and the
  155. ;;; encoder really wants to operate on units of input lines.  The
  156. ;;; strategy is that we process as much of the input packet as
  157. ;;; possible, then save enough state to continue when the next packet
  158. ;;; comes along.
  159.  
  160. (define (call-with-decode-quoted-printable-output-port port text? generator)
  161.   (let ((port (make-decode-quoted-printable-port port text?)))
  162.     (let ((v (generator port)))
  163.       (close-output-port port)
  164.       v)))
  165.  
  166. (define (make-decode-quoted-printable-port port text?)
  167.   (make-port decode-quoted-printable-port-type
  168.          (decode-quoted-printable:initialize port text?)))
  169.  
  170. (define decode-quoted-printable-port-type
  171.   (make-port-type
  172.    `((WRITE-SUBSTRING
  173.       ,(lambda (port string start end)
  174.      (decode-quoted-printable:update (port/state port) string start end)))
  175.      (CLOSE-OUTPUT
  176.       ,(lambda (port)
  177.      (decode-quoted-printable:finalize (port/state port)))))
  178.    #f))
  179.  
  180. (define-structure (qp-decoding-context
  181.            (conc-name qp-decoding-context/)
  182.            (constructor decode-quoted-printable:initialize
  183.                 (port text?)))
  184.   (port #f read-only #t)
  185.   (text? #f read-only #t)
  186.   ;; Pending input that can't be processed until more input is
  187.   ;; available.  Can take on one of the following values:
  188.   ;; * #F means no pending input.
  189.   ;; * A string, consisting entirely of LWSP characters, is whitespace
  190.   ;;   that appeared at the end of an input packet.  We are waiting to
  191.   ;;   see if it is followed by a newline, meaning it is to be
  192.   ;;   discarded.  Otherwise it is part of the output.
  193.   ;; * The character #\=, meaning that the equals-sign character has
  194.   ;;   been seen and we need more characters to decide what to do with
  195.   ;;   it.
  196.   ;; * A hexadecimal-digit character (0-9, A-F), meaning that an
  197.   ;;   equals sign and that character have been seen, and we are
  198.   ;;   waiting for the second hexadecimal digit to arrive.
  199.   (pending #f))
  200.  
  201. (define (decode-quoted-printable:finalize context)
  202.   (decode-qp context "" 0 0 'INPUT-END))
  203.  
  204. (define (decode-quoted-printable:update context string start end)
  205.   (let loop ((start start))
  206.     (let ((i (substring-find-next-char string start end #\newline)))
  207.       (if i
  208.       (begin
  209.         (decode-qp context
  210.                string start (skip-lwsp-backwards string start i)
  211.                'LINE-END)
  212.         (loop (fix:+ i 1)))
  213.       (decode-qp context string start end 'PARTIAL)))))
  214.  
  215. (define (decode-qp context string start end type)
  216.   (let ((port (qp-decoding-context/port context))
  217.     (end* (skip-lwsp-backwards string start end)))
  218.  
  219.     (define (loop start)
  220.       (let ((i
  221.          (substring-find-next-char-in-set string start end*
  222.                           char-set:qp-encoded)))
  223.     (if i
  224.         (begin
  225.           (write-substring string start i port)
  226.           (if (char=? (string-ref string i) #\=)
  227.           (handle-equals (fix:+ i 1))
  228.           ;; RFC 2045 recommends dropping illegal encoded char.
  229.           (loop (fix:+ i 1))))
  230.         (begin
  231.           (write-substring string start end* port)
  232.           (finish)))))
  233.  
  234.     (define (handle-equals start)
  235.       (if (fix:< (fix:+ start 1) end*)
  236.       (loop (decode-qp-hex context
  237.                    (string-ref string start)
  238.                    (string-ref string (fix:+ start 1))
  239.                    (fix:+ start 2)))
  240.       (begin
  241.         (if (fix:< start end*)
  242.         (let ((char (string-ref string start)))
  243.           (if (char-hex-digit? char)
  244.               (set-qp-decoding-context/pending! context char)
  245.               ;; Illegal: RFC 2045 recommends leaving as is.
  246.               (begin
  247.             (write-char #\= port)
  248.             (write-char char port))))
  249.         (set-qp-decoding-context/pending! context #\=))
  250.         (finish))))
  251.  
  252.     (define (finish)
  253.       (let ((pending (qp-decoding-context/pending context)))
  254.     (set-qp-decoding-context/pending! context #f)
  255.     (cond ((eq? type 'PARTIAL)
  256.            (set-qp-decoding-context/pending!
  257.         context
  258.         (decode-qp-pending-string pending string end* end)))
  259.           ((not pending)
  260.            (if (and (eq? type 'LINE-END)
  261.             (qp-decoding-context/text? context))
  262.            ;; Hard line break.
  263.            (newline port)))
  264.           ((eqv? pending #\=)
  265.            (if (eq? type 'LINE-END)
  266.            unspecific        ; Soft line break.
  267.            ;; Illegal: RFC 2045 recommends leaving as is.
  268.            (write-char #\= port)))
  269.           ((char? pending)
  270.            ;; Illegal: RFC 2045 recommends leaving as is.
  271.            (write-char #\= port)
  272.            (write-char pending port))
  273.           ((string? pending)
  274.            ;; Trailing whitespace: discard.
  275.            unspecific)
  276.           (else (error "Illegal PENDING value:" pending)))))
  277.  
  278.     (let ((pending (qp-decoding-context/pending context)))
  279.       (if (and pending (fix:< start end*))
  280.       (begin
  281.         (set-qp-decoding-context/pending! context #f)
  282.         (cond ((eqv? pending #\=)
  283.            (handle-equals start))
  284.           ((char? pending)
  285.            (loop (decode-qp-hex context
  286.                     pending
  287.                     (string-ref string start)
  288.                     (fix:+ start 1))))
  289.           ((string? pending)
  290.            (write-string pending port)
  291.            (loop start))
  292.           (else (error "Illegal PENDING value:" pending))))
  293.       (loop start)))))
  294.  
  295. (define (decode-qp-pending-string pending string start end)
  296.   (if (fix:< start end)
  297.       (if pending
  298.       (let ((s
  299.          (make-string
  300.           (fix:+ (string-length pending) (fix:- end start)))))
  301.         (substring-move! string start end
  302.                  s (string-move! pending s 0))
  303.         s)
  304.       (substring string start end))
  305.       pending))
  306.  
  307. (define char-set:qp-encoded
  308.   (char-set-invert
  309.    (char-set-union (char-set-difference (ascii-range->char-set #x21 #x7F)
  310.                     (char-set #\=))
  311.            (char-set #\space #\tab))))
  312.  
  313. (define (char-lwsp? char)
  314.   (or (char=? #\space char)
  315.       (char=? #\tab char)))
  316.  
  317. (define (skip-lwsp-backwards string start end)
  318.   (let loop ((end end))
  319.     (if (and (fix:< start end)
  320.          (char-lwsp? (string-ref string (fix:- end 1))))
  321.     (loop (fix:- end 1))
  322.     end)))
  323.  
  324. (define (decode-qp-hex context c1 c2 start)
  325.   (let ((port (qp-decoding-context/port context)))
  326.     (let ((char
  327.        (let ((d1 (char->hex-digit c1))
  328.          (d2 (char->hex-digit c2)))
  329.          (and (fix:< d1 #x10)
  330.           (fix:< d2 #x10)
  331.           (integer->char (fix:or (fix:lsh d1 4) d2))))))
  332.       (if char
  333.       (begin
  334.         (write-char char port)
  335.         start)
  336.       ;; This case is illegal.  RFC 2045 recommends
  337.       ;; leaving it unconverted.
  338.       (begin
  339.         (write-char #\= port)
  340.         (write-char c1 port)
  341.         (fix:- start 1))))))
  342.  
  343. (define-integrable (char-hex-digit? char)
  344.   (fix:< (char->hex-digit char) #x10))
  345.  
  346. (define-integrable (char->hex-digit char)
  347.   (vector-8b-ref hex-char-table (char->integer char)))
  348.  
  349. (define-integrable (hex-digit->char digit)
  350.   (string-ref hex-digit-table digit))
  351.  
  352. (define hex-char-table)
  353. (define hex-digit-table)
  354. (let ((char-table (make-string 256 (integer->char #xff)))
  355.       (digit-table (make-string 16)))
  356.   (define (do-range low high value)
  357.     (do-char low value)
  358.     (if (fix:< low high)
  359.     (do-range (fix:+ low 1) high (fix:+ value 1))))
  360.   (define (do-char code value)
  361.     (vector-8b-set! char-table code value)
  362.     (vector-8b-set! digit-table value code))
  363.   (do-range (char->integer #\0) (char->integer #\9) 0)
  364.   (do-range (char->integer #\a) (char->integer #\f) 10)
  365.   (do-range (char->integer #\A) (char->integer #\F) 10)
  366.   (set! hex-char-table char-table)
  367.   (set! hex-digit-table digit-table)
  368.   unspecific)
  369.  
  370. ;;;; Encode BASE64
  371.  
  372. (define-structure (base64-encoding-context
  373.            (conc-name base64-encoding-context/)
  374.            (constructor encode-base64:initialize (port text?)))
  375.   (port #f read-only #t)
  376.   (text? #f read-only #t)
  377.   (buffer (make-string 48) read-only #t)
  378.   (index 0))
  379.  
  380. (define (encode-base64:finalize context)
  381.   (write-base64-line context))
  382.  
  383. (define (encode-base64:update context string start end)
  384.   (if (base64-encoding-context/text? context)
  385.       (let loop ((start start))
  386.     (let ((index (substring-find-next-char string start end #\newline)))
  387.       (if index
  388.           (begin
  389.         (encode-base64 context string start index)
  390.         (encode-base64 context "\r\n" 0 2)
  391.         (loop (fix:+ index 1)))
  392.           (encode-base64 context string start end))))
  393.       (encode-base64 context string start end)))
  394.  
  395. (define (encode-base64 context string start end)
  396.   (let ((buffer (base64-encoding-context/buffer context)))
  397.     (let loop ((start start))
  398.       (if (fix:< start end)
  399.       (let ((i (base64-encoding-context/index context)))
  400.         (let ((start* (fix:min end (fix:+ start (fix:- 48 i)))))
  401.           (let ((i (substring-move! string start start* buffer i)))
  402.         (set-base64-encoding-context/index! context i)
  403.         (if (fix:= i 48)
  404.             (write-base64-line context)))
  405.           (loop start*)))))))
  406.  
  407. (define (write-base64-line context)
  408.   (let ((buffer (base64-encoding-context/buffer context))
  409.     (end (base64-encoding-context/index context))
  410.     (port (base64-encoding-context/port context)))
  411.     (if (fix:> end 0)
  412.     (begin
  413.       (let ((write-digit
  414.          (lambda (d)
  415.            (write-char (string-ref base64-digit-table (fix:and #x3F d))
  416.                    port))))
  417.         (let loop ((start 0))
  418.           (let ((n (fix:- end start)))
  419.         (cond ((fix:>= n 3)
  420.                (let ((d1 (vector-8b-ref buffer start))
  421.                  (d2 (vector-8b-ref buffer (fix:+ start 1)))
  422.                  (d3 (vector-8b-ref buffer (fix:+ start 2))))
  423.              (write-digit (fix:lsh d1 -2))
  424.              (write-digit (fix:or (fix:lsh d1 4) (fix:lsh d2 -4)))
  425.              (write-digit (fix:or (fix:lsh d2 2) (fix:lsh d3 -6)))
  426.              (write-digit d3))
  427.                (loop (fix:+ start 3)))
  428.               ((fix:= n 2)
  429.                (let ((d1 (vector-8b-ref buffer start))
  430.                  (d2 (vector-8b-ref buffer (fix:+ start 1))))
  431.              (write-digit (fix:lsh d1 -2))
  432.              (write-digit (fix:or (fix:lsh d1 4) (fix:lsh d2 -4)))
  433.              (write-digit (fix:lsh d2 2)))
  434.                (write-char #\= port))
  435.               ((fix:= n 1)
  436.                (let ((d1 (vector-8b-ref buffer start)))
  437.              (write-digit (fix:lsh d1 -2))
  438.              (write-digit (fix:lsh d1 4)))
  439.                (write-char #\= port)
  440.                (write-char #\= port))))))
  441.       (newline port)
  442.       (set-base64-encoding-context/index! context 0)))))
  443.  
  444. ;;;; Decode BASE64
  445.  
  446. (define (call-with-decode-base64-output-port port text? generator)
  447.   (let ((port (make-decode-base64-port port text?)))
  448.     (let ((v (generator port)))
  449.       (close-output-port port)
  450.       v)))
  451.  
  452. (define (make-decode-base64-port port text?)
  453.   (make-port decode-base64-port-type (decode-base64:initialize port text?)))
  454.  
  455. (define decode-base64-port-type
  456.   (make-port-type
  457.    `((WRITE-SUBSTRING
  458.       ,(lambda (port string start end)
  459.      (decode-base64:update (port/state port) string start end)))
  460.      (CLOSE-OUTPUT
  461.       ,(lambda (port)
  462.      (decode-base64:finalize (port/state port)))))
  463.    #f))
  464.  
  465. (define-structure (base64-decoding-context
  466.            (conc-name base64-decoding-context/)
  467.            (constructor decode-base64:initialize (port text?)))
  468.   (port #f read-only #t)
  469.   (text? #f read-only #t)
  470.   (input-buffer (make-string 4) read-only #t)
  471.   (input-index 0)
  472.   ;; Ugh bletch.  Add state to look for line starting with NON-BASE64
  473.   ;; character, and stop decoding there.  This works around problem
  474.   ;; that arises when mail-processing agents randomly glue text on the
  475.   ;; end of a MIME message.
  476.   (input-state 'LINE-START)
  477.   (output-buffer (make-string 3) read-only #t)
  478.   (pending-return? #f))
  479.  
  480. (define (decode-base64:finalize context)
  481.   (if (fix:> (base64-decoding-context/input-index context) 0)
  482.       (error "BASE64 input length is not a multiple of 4."))
  483.   (if (base64-decoding-context/pending-return? context)
  484.       (write-char #\return (base64-decoding-context/port context))))
  485.  
  486. (define (decode-base64:update context string start end)
  487.   (if (not (eq? 'FINISHED (base64-decoding-context/input-state context)))
  488.       (let ((buffer (base64-decoding-context/input-buffer context)))
  489.     (let loop
  490.         ((start start)
  491.          (index (base64-decoding-context/input-index context))
  492.          (state (base64-decoding-context/input-state context)))
  493.       (let ((done
  494.          (lambda (state)
  495.            (set-base64-decoding-context/input-index! context index)
  496.            (set-base64-decoding-context/input-state! context state))))
  497.         (if (fix:< start end)
  498.         (let* ((char (string-ref string start))
  499.                (continue
  500.             (lambda (index)
  501.               (loop (fix:+ start 1)
  502.                 index
  503.                 (if (char=? char #\newline)
  504.                     'LINE-START
  505.                     'IN-LINE)))))
  506.           (if (or (char=? char #\=)
  507.               (fix:< (vector-8b-ref base64-char-table
  508.                         (char->integer char))
  509.                  #x40))
  510.               (begin
  511.             (string-set! buffer index char)
  512.             (if (fix:< index 3)
  513.                 (continue (fix:+ index 1))
  514.                 (begin
  515.                   (decode-base64-quantum context)
  516.                   (continue 0))))
  517.               (if (eq? state 'LINE-START)
  518.               (done 'FINISHED)
  519.               (continue index))))
  520.         (done state)))))))
  521.  
  522. (define (decode-base64-quantum context)
  523.   (let ((input (base64-decoding-context/input-buffer context))
  524.     (output (base64-decoding-context/output-buffer context))
  525.     (port (base64-decoding-context/port context)))
  526.     (let ((n (decode-base64-quantum-1 input output)))
  527.       (if (base64-decoding-context/text? context)
  528.       (let loop
  529.           ((index 0)
  530.            (pending? (base64-decoding-context/pending-return? context)))
  531.         (if (fix:< index n)
  532.         (let ((char (string-ref output index)))
  533.           (if pending?
  534.               (if (char=? char #\linefeed)
  535.               (begin
  536.                 (newline port)
  537.                 (loop (fix:+ index 1) #f))
  538.               (begin
  539.                 (write-char #\return port)
  540.                 (loop index #f)))
  541.               (if (char=? char #\return)
  542.               (loop (fix:+ index 1) #t)
  543.               (begin
  544.                 (write-char char port)
  545.                 (loop (fix:+ index 1) #f)))))
  546.         (set-base64-decoding-context/pending-return?! context
  547.                                   pending?)))
  548.       (write-substring output 0 n port)))))
  549.  
  550. (define (decode-base64-quantum-1 input output)
  551.   (let ((d1 (decode-base64-char input 0))
  552.     (d2 (decode-base64-char input 1)))
  553.     (cond ((not (char=? (string-ref input 3) #\=))
  554.        (let ((n
  555.           (fix:+ (fix:+ (fix:lsh d1 18)
  556.                 (fix:lsh d2 12))
  557.              (fix:+ (fix:lsh (decode-base64-char input 2) 6)
  558.                 (decode-base64-char input 3)))))
  559.          (vector-8b-set! output 0 (fix:lsh n -16))
  560.          (vector-8b-set! output 1 (fix:and #xFF (fix:lsh n -8)))
  561.          (vector-8b-set! output 2 (fix:and #xFF n))
  562.          3))
  563.       ((not (char=? (string-ref input 2) #\=))
  564.        (let ((n
  565.           (fix:+ (fix:+ (fix:lsh d1 10) (fix:lsh d2 4))
  566.              (fix:lsh (decode-base64-char input 2) -2))))
  567.          (vector-8b-set! output 0 (fix:lsh n -8))
  568.          (vector-8b-set! output 1 (fix:and #xFF n)))
  569.        2)
  570.       (else
  571.        (vector-8b-set! output 0 (fix:+ (fix:lsh d1 2) (fix:lsh d2 -4)))
  572.        1))))
  573.  
  574. (define (decode-base64-char input index)
  575.   (let ((digit (vector-8b-ref base64-char-table (vector-8b-ref input index))))
  576.     (if (fix:> digit #x40)
  577.     (error "Misplaced #\= in BASE64 input."))
  578.     digit))
  579.  
  580. (define base64-char-table)
  581. (define base64-digit-table)
  582. (let ((char-table (make-string 256 (integer->char #xff)))
  583.       (digit-table (make-string 64)))
  584.   (define (do-range low high value)
  585.     (do-char low value)
  586.     (if (fix:< low high)
  587.     (do-range (fix:+ low 1) high (fix:+ value 1))))
  588.   (define (do-char code value)
  589.     (vector-8b-set! char-table code value)
  590.     (vector-8b-set! digit-table value code))
  591.   (do-range (char->integer #\A) (char->integer #\Z) 0)
  592.   (do-range (char->integer #\a) (char->integer #\z) 26)
  593.   (do-range (char->integer #\0) (char->integer #\9) 52)
  594.   (do-char (char->integer #\+) 62)
  595.   (do-char (char->integer #\/) 63)
  596.   (set! base64-char-table char-table)
  597.   (set! base64-digit-table digit-table)
  598.   unspecific)
  599.  
  600. ;;;; Decode BinHex 4.0
  601.  
  602. (define (call-with-decode-binhex40-output-port port text? generator)
  603.   (let ((port (make-decode-binhex40-port port text?)))
  604.     (let ((v (generator port)))
  605.       (close-output-port port)
  606.       v)))
  607.  
  608. (define (make-decode-binhex40-port port text?)
  609.   (make-port decode-binhex40-port-type
  610.          (decode-binhex40:initialize port text?)))
  611.  
  612. (define decode-binhex40-port-type
  613.   (make-port-type
  614.    `((WRITE-SUBSTRING
  615.       ,(lambda (port string start end)
  616.      (decode-binhex40:update (port/state port) string start end)))
  617.      (CLOSE-OUTPUT
  618.       ,(lambda (port)
  619.      (decode-binhex40:finalize (port/state port)))))
  620.    #f))
  621.  
  622. (define-structure (binhex40-decoding-context
  623.            (conc-name binhex40-decoding-context/)
  624.            (constructor make-binhex40-decoding-context (port)))
  625.   (port #f read-only #t)
  626.   (state 'SEEKING-COMMENT)
  627.   (line-buffer "")
  628.   (input-buffer (make-string 4) read-only #t)
  629.   (input-index 0)
  630.   (output-buffer (make-string 3) read-only #t))
  631.  
  632. (define (decode-binhex40:initialize port text?)
  633.   text?                    ;ignored
  634.   (make-binhex40-decoding-context
  635.    (make-binhex40-run-length-decoding-port
  636.     (make-binhex40-deconstructing-port port))))
  637.  
  638. (define (decode-binhex40:finalize context)
  639.   (let ((state (binhex40-decoding-context/state context)))
  640.     (case (binhex40-decoding-context/state context)
  641.       ((SEEKING-COMMENT)
  642.        (error "Missing BinHex 4.0 initial comment line."))
  643.       ((DECODING)
  644.        (error "Missing BinHex 4.0 terminating character."))
  645.       ((IGNORING)
  646.        (close-output-port (binhex40-decoding-context/port context)))
  647.       (else
  648.        (error "Illegal decoder state:" state)))))
  649.  
  650. (define (decode-binhex40:update context string start end)
  651.   (let ((state (binhex40-decoding-context/state context)))
  652.     (case (binhex40-decoding-context/state context)
  653.       ((SEEKING-COMMENT)
  654.        (decode-binhex40-seeking-comment context string start end))
  655.       ((DECODING)
  656.        (decode-binhex40-decoding context string start end))
  657.       ((IGNORING)
  658.        unspecific)
  659.       (else
  660.        (error "Illegal decoder state:" state)))))
  661.  
  662. (define (decode-binhex40-seeking-comment context string start end)
  663.   (let loop
  664.       ((s
  665.     (string-append (binhex40-decoding-context/line-buffer context)
  666.                (substring string start end))))
  667.     (let ((regs (re-string-match binhex40-header-regexp s)))
  668.       (if regs
  669.       (begin
  670.         (set-binhex40-decoding-context/state! context 'DECODING)
  671.         (set-binhex40-decoding-context/line-buffer! context #f)
  672.         (decode-binhex40:update context s
  673.                     (re-match-end-index 0 regs)
  674.                     (string-length s)))
  675.       (set-binhex40-decoding-context/line-buffer! context s)))))
  676.  
  677. (define binhex40-header-regexp
  678.   "[\r\n\t ]*(This file must be converted with BinHex.*[\r\n][\r\n\t ]*:")
  679.  
  680. (define (decode-binhex40-decoding context string start end)   
  681.   (let ((buffer (binhex40-decoding-context/input-buffer context)))
  682.     (let loop
  683.     ((start start)
  684.      (index (binhex40-decoding-context/input-index context)))
  685.       (if (fix:< start end)
  686.       (let ((char (string-ref string start))
  687.         (start (fix:+ start 1)))
  688.         (cond ((char=? char #\:)
  689.            (if (fix:> index 0)
  690.                (begin
  691.              (string-set! buffer index char)
  692.              (decode-binhex40-quantum context)))
  693.            (set-binhex40-decoding-context/state! context 'IGNORING))
  694.           ((fix:< (vector-8b-ref binhex40-char-table
  695.                      (char->integer char))
  696.               #x40)
  697.            (string-set! buffer index char)
  698.            (if (fix:< index 3)
  699.                (loop start (fix:+ index 1))
  700.                (begin
  701.              (decode-binhex40-quantum context)
  702.              (loop start 0))))
  703.           (else
  704.            (loop start index))))
  705.       (set-binhex40-decoding-context/input-index! context index)))))
  706.  
  707. (define (decode-binhex40-quantum context)
  708.   (let ((input (binhex40-decoding-context/input-buffer context))
  709.     (output (binhex40-decoding-context/output-buffer context))
  710.     (port (binhex40-decoding-context/port context)))
  711.     (write-substring output 0
  712.              (decode-binhex40-quantum-1 input output)
  713.              port)))
  714.  
  715. (define (decode-binhex40-quantum-1 input output)
  716.   (let ((d1 (decode-binhex40-char input 0))
  717.     (d2 (decode-binhex40-char input 1)))
  718.     (cond ((char=? (string-ref input 2) #\:)
  719.        (vector-8b-set! output 0 (fix:+ (fix:lsh d1 2) (fix:lsh d2 -4)))
  720.        1)
  721.       ((char=? (string-ref input 3) #\:)
  722.        (let ((n
  723.           (fix:+ (fix:+ (fix:lsh d1 10) (fix:lsh d2 4))
  724.              (fix:lsh (decode-binhex40-char input 2) -2))))
  725.          (vector-8b-set! output 0 (fix:lsh n -8))
  726.          (vector-8b-set! output 1 (fix:and #xFF n)))
  727.        2)
  728.       (else
  729.        (let ((n
  730.           (fix:+ (fix:+ (fix:lsh d1 18)
  731.                 (fix:lsh d2 12))
  732.              (fix:+ (fix:lsh (decode-binhex40-char input 2) 6)
  733.                 (decode-binhex40-char input 3)))))
  734.          (vector-8b-set! output 0 (fix:lsh n -16))
  735.          (vector-8b-set! output 1 (fix:and #xFF (fix:lsh n -8)))
  736.          (vector-8b-set! output 2 (fix:and #xFF n))
  737.          3)))))
  738.  
  739. (define (decode-binhex40-char input index)
  740.   (let ((digit
  741.      (vector-8b-ref binhex40-char-table (vector-8b-ref input index))))
  742.     (if (fix:> digit #x40)
  743.     (error "Illegal character in BinHex 4.0 input stream:"
  744.            (string-ref input index)))
  745.     digit))
  746.  
  747. (define binhex40-digit-table
  748.   "!\"#$%&\'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr")
  749.  
  750. (define binhex40-char-table
  751.   (make-string 256 (integer->char #xff)))
  752.  
  753. (do ((code 0 (fix:+ code 1)))
  754.     ((fix:= code 64))
  755.   (vector-8b-set! binhex40-char-table
  756.           (vector-8b-ref binhex40-digit-table code)
  757.           code))
  758.  
  759. ;;;; BinHex 4.0 run-length decoding
  760.  
  761. (define (make-binhex40-run-length-decoding-port port)
  762.   (make-port binhex40-run-length-decoding-port-type
  763.          (make-binhex40-rld-state port)))
  764.  
  765. (define binhex40-run-length-decoding-port-type
  766.   (make-port-type
  767.    `((WRITE-CHAR
  768.       ,(lambda (port char)
  769.      (let ((state (port/state port)))
  770.        (let ((port (binhex40-rld-state/port state))
  771.          (char* (binhex40-rld-state/char state)))
  772.          (cond ((binhex40-rld-state/marker-seen? state)
  773.             (let ((n (char->integer char)))
  774.               (cond ((fix:= n 0)
  775.                  (if char* (write-char char* port))
  776.                  (set-binhex40-rld-state/char!
  777.                   state binhex40-rld-marker))
  778.                 (char*
  779.                  (do ((i 0 (fix:+ i 1)))
  780.                  ((fix:= i n))
  781.                    (write-char char* port))
  782.                  (set-binhex40-rld-state/char! state #f))))
  783.             (set-binhex40-rld-state/marker-seen?! state #f))
  784.            ((char=? char binhex40-rld-marker)
  785.             (set-binhex40-rld-state/marker-seen?! state #t))
  786.            (else
  787.             (if char* (write-char char* port))
  788.             (set-binhex40-rld-state/char! state char)))))))
  789.      (CLOSE-OUTPUT
  790.       ,(lambda (port)
  791.      (let ((state (port/state port)))
  792.        (let ((port (binhex40-rld-state/port state))
  793.          (char* (binhex40-rld-state/char state)))
  794.          (if char*
  795.          (begin
  796.            (write-char char* port)
  797.            (set-binhex40-rld-state/char! state #f)))
  798.          (if (binhex40-rld-state/marker-seen? state)
  799.          (begin
  800.            (write-char binhex40-rld-marker port)
  801.            (set-binhex40-rld-state/marker-seen?! state #f)))
  802.          (close-output-port port))))))
  803.    #f))
  804.  
  805. (define-structure (binhex40-rld-state
  806.            (conc-name binhex40-rld-state/)
  807.            (constructor make-binhex40-rld-state (port)))
  808.   (port #f read-only #t)
  809.   (char #f)
  810.   (marker-seen? #f))
  811.  
  812. (define-integrable binhex40-rld-marker
  813.   (integer->char #x90))
  814.  
  815. ;;;; BinHex 4.0 deconstruction
  816.  
  817. (define (make-binhex40-deconstructing-port port)
  818.   (make-port binhex40-deconstructing-port-type
  819.          (make-binhex40-decon port)))
  820.  
  821. (define binhex40-deconstructing-port-type
  822.   (make-port-type
  823.    `((WRITE-CHAR
  824.       ,(lambda (port char)
  825.      (case (binhex40-decon/state (port/state port))
  826.        ((READING-HEADER) (binhex40-decon-reading-header port char))
  827.        ((COPYING-DATA) (binhex40-decon-copying-data port char))
  828.        ((SKIPPING-TAIL) (binhex40-decon-skipping-tail port))
  829.        ((FINISHED) unspecific)
  830.        (else (error "Illegal state in BinHex 4.0 deconstructor.")))))
  831.      (CLOSE-OUTPUT
  832.       ,(lambda (port)
  833.      (if (not (eq? (binhex40-decon/state (port/state port)) 'FINISHED))
  834.          (error "Premature EOF in BinHex 4.0 stream.")))))
  835.    #f))
  836.  
  837. (define (binhex40-decon-reading-header port char)
  838.   (let ((state (port/state port)))
  839.     (let ((index (binhex40-decon/index state)))
  840.       (if (fix:= index 0)
  841.       (begin
  842.         (set-binhex40-decon/header!
  843.          state (make-string (fix:+ 22 (char->integer char))))
  844.         (set-binhex40-decon/index! state 1))
  845.       (let ((header (binhex40-decon/header state)))
  846.         (string-set! header index char)
  847.         (let ((index (fix:+ index 1)))
  848.           (if (fix:< index (string-length header))
  849.           (set-binhex40-decon/index! state index)
  850.           (begin
  851.             (set-binhex40-decon/data-length!
  852.              state
  853.              (binhex40-4byte header (fix:- (string-length header) 10)))
  854.             (set-binhex40-decon/index! state 0)
  855.             (set-binhex40-decon/state! state 'COPYING-DATA)))))))))
  856.  
  857. (define (binhex40-decon-copying-data port char)
  858.   (let ((state (port/state port)))
  859.     (write-char char (binhex40-decon/port state))
  860.     (let ((index (+ (binhex40-decon/index state) 1)))
  861.       (if (< index (binhex40-decon/data-length state))
  862.       (set-binhex40-decon/index! state index)
  863.       (begin
  864.         (set-binhex40-decon/index! state 0)
  865.         (set-binhex40-decon/data-length!
  866.          state
  867.          (+ (let ((header (binhex40-decon/header state)))
  868.           (binhex40-4byte header (fix:- (string-length header) 6)))
  869.         4))
  870.         (set-binhex40-decon/state! state 'SKIPPING-TAIL))))))
  871.  
  872. (define (binhex40-decon-skipping-tail port)
  873.   (let ((state (port/state port)))
  874.     (let ((index (+ (binhex40-decon/index state) 1)))
  875.       (set-binhex40-decon/index! state index)
  876.       (if (>= index (binhex40-decon/data-length state))
  877.       (set-binhex40-decon/state! state 'FINISHED)))))
  878.  
  879. (define-structure (binhex40-decon (conc-name binhex40-decon/)
  880.                   (constructor make-binhex40-decon (port)))
  881.   (port #f read-only #t)
  882.   (state 'READING-HEADER)
  883.   (header #f)
  884.   (index 0)
  885.   (data-length))
  886.  
  887. (define (binhex40-4byte string index)
  888.   (+ (* (vector-8b-ref string index) #x1000000)
  889.      (* (vector-8b-ref string (fix:+ index 1)) #x10000)
  890.      (* (vector-8b-ref string (fix:+ index 2)) #x100)
  891.      (vector-8b-ref string (fix:+ index 3))))