2021-08-21 15:29:43 +02:00
|
|
|
;;; eds.lisp --- En/De-coding Streams
|
|
|
|
|
|
|
|
;;; Copyright (C) 2012 by Walter C. Pelissero
|
2021-08-02 15:15:39 +02:00
|
|
|
;;; Copyright (C) 2021 by the TVL Authors
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
;;; Author: Walter C. Pelissero <walter@pelissero.de>
|
|
|
|
;;; Project: mime4cl
|
|
|
|
|
|
|
|
#+cmu (ext:file-comment "$Module: eds.lisp")
|
|
|
|
|
|
|
|
;;; This library is free software; you can redistribute it and/or
|
|
|
|
;;; modify it under the terms of the GNU Lesser General Public License
|
|
|
|
;;; as published by the Free Software Foundation; either version 2.1
|
|
|
|
;;; of the License, or (at your option) any later version.
|
|
|
|
;;; This library 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
|
|
|
|
;;; Lesser General Public License for more details.
|
|
|
|
;;; You should have received a copy of the GNU Lesser General Public
|
|
|
|
;;; License along with this library; if not, write to the Free
|
|
|
|
;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
|
|
;;; 02111-1307 USA
|
|
|
|
|
|
|
|
(in-package :mime4cl)
|
|
|
|
|
|
|
|
#+cmu
|
|
|
|
(eval-when (:load-toplevel :compile-toplevel :execute)
|
|
|
|
;; CMUCL doesn't provide the STREAM-FILE-POSITION method in its
|
|
|
|
;; implementation of Gray streams. We patch it in ourselves.
|
|
|
|
(defgeneric stream-file-position (stream &optional position))
|
|
|
|
(defun my-file-position (stream &optional position)
|
|
|
|
(stream-file-position stream position))
|
|
|
|
(defvar *original-file-position-function*
|
|
|
|
(prog1
|
2022-01-19 14:39:58 +01:00
|
|
|
(symbol-function 'file-position)
|
2021-08-21 15:29:43 +02:00
|
|
|
(setf (symbol-function 'file-position) (symbol-function 'my-file-position))))
|
|
|
|
(defmethod stream-file-position (stream &optional position)
|
|
|
|
(if position
|
2022-01-19 14:39:58 +01:00
|
|
|
(funcall *original-file-position-function* stream position)
|
|
|
|
(funcall *original-file-position-function* stream)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
;; oddly CMUCL doesn't seem to provide a default for STREAM-READ-SEQUENCE
|
|
|
|
(defmacro make-read-sequence (stream-type element-reader)
|
|
|
|
`(defmethod stream-read-sequence ((stream ,stream-type) seq &optional start end)
|
|
|
|
(unless start
|
2022-01-19 14:39:58 +01:00
|
|
|
(setf start 0))
|
2021-08-21 15:29:43 +02:00
|
|
|
(unless end
|
2022-01-19 14:39:58 +01:00
|
|
|
(setf end (length seq)))
|
2021-08-21 15:29:43 +02:00
|
|
|
(loop
|
2022-01-19 14:39:58 +01:00
|
|
|
for i from start below end
|
|
|
|
for b = (,element-reader stream)
|
|
|
|
until (eq b :eof)
|
|
|
|
do (setf (elt seq i) b)
|
|
|
|
finally (return i))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(make-read-sequence fundamental-binary-input-stream stream-read-byte)
|
|
|
|
(make-read-sequence fundamental-character-input-stream stream-read-char))
|
|
|
|
|
|
|
|
(defclass coder-stream-mixin ()
|
|
|
|
((real-stream :type stream
|
2022-01-19 14:39:58 +01:00
|
|
|
:initarg :stream
|
|
|
|
:reader real-stream)
|
2021-08-21 15:29:43 +02:00
|
|
|
(dont-close :initform nil
|
2022-01-19 14:39:58 +01:00
|
|
|
:initarg :dont-close)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod stream-file-position ((stream coder-stream-mixin) &optional position)
|
2021-08-02 15:15:39 +02:00
|
|
|
(apply #'file-position (remove nil (list (slot-value stream 'real-stream)
|
|
|
|
position))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defclass coder-input-stream-mixin (fundamental-binary-input-stream coder-stream-mixin)
|
|
|
|
())
|
|
|
|
(defclass coder-output-stream-mixin (fundamental-binary-output-stream coder-stream-mixin)
|
|
|
|
())
|
|
|
|
|
|
|
|
|
|
|
|
(defclass quoted-printable-decoder-stream (coder-input-stream-mixin quoted-printable-decoder) ())
|
|
|
|
(defclass base64-decoder-stream (coder-input-stream-mixin base64-decoder) ())
|
|
|
|
(defclass 8bit-decoder-stream (coder-input-stream-mixin 8bit-decoder) ())
|
|
|
|
|
|
|
|
(defclass quoted-printable-encoder-stream (coder-output-stream-mixin quoted-printable-encoder) ())
|
|
|
|
(defclass base64-encoder-stream (coder-output-stream-mixin base64-encoder) ())
|
|
|
|
(defclass 8bit-encoder-stream (coder-output-stream-mixin 8bit-encoder) ())
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defmethod initialize-instance :after ((stream coder-stream-mixin) &key &allow-other-keys)
|
|
|
|
(unless (slot-boundp stream 'real-stream)
|
|
|
|
(error "REAL-STREAM is unbound. Must provide a :STREAM argument.")))
|
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream coder-output-stream-mixin) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(unless (slot-boundp stream 'output-function)
|
|
|
|
(setf (slot-value stream 'output-function)
|
2022-01-19 14:39:58 +01:00
|
|
|
#'(lambda (char)
|
|
|
|
(write-char char (slot-value stream 'real-stream))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream coder-input-stream-mixin) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(unless (slot-boundp stream 'input-function)
|
|
|
|
(setf (slot-value stream 'input-function)
|
2022-01-19 14:39:58 +01:00
|
|
|
#'(lambda ()
|
|
|
|
(read-char (slot-value stream 'real-stream) nil)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod stream-read-byte ((stream coder-input-stream-mixin))
|
|
|
|
(or (decoder-read-byte stream)
|
|
|
|
:eof))
|
|
|
|
|
|
|
|
(defmethod stream-write-byte ((stream coder-output-stream-mixin) byte)
|
|
|
|
(encoder-write-byte stream byte))
|
|
|
|
|
|
|
|
(defmethod close ((stream coder-stream-mixin) &key abort)
|
|
|
|
(with-slots (real-stream dont-close) stream
|
|
|
|
(unless dont-close
|
|
|
|
(close real-stream :abort abort))))
|
|
|
|
|
|
|
|
(defmethod close ((stream coder-output-stream-mixin) &key abort)
|
|
|
|
(unless abort
|
|
|
|
(encoder-finish-output stream))
|
|
|
|
(call-next-method))
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defclass encoder-input-stream (fundamental-character-input-stream coder-stream-mixin)
|
|
|
|
((encoder)
|
|
|
|
(buffer-queue :initform (make-queue)))
|
|
|
|
(:documentation
|
|
|
|
"This is the base class for encoders with the direction swapped. It
|
|
|
|
reads from REAL-STREAM a stream of bytes, encodes it and returnes it
|
|
|
|
in a stream of character."))
|
|
|
|
|
|
|
|
(defclass quoted-printable-encoder-input-stream (encoder-input-stream) ())
|
|
|
|
(defclass base64-encoder-input-stream (encoder-input-stream) ())
|
|
|
|
(defclass 8bit-encoder-input-stream (fundamental-character-input-stream coder-stream-mixin) ())
|
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream quoted-printable-encoder-input-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(with-slots (encoder buffer-queue) stream
|
|
|
|
(setf encoder
|
2022-01-19 14:39:58 +01:00
|
|
|
(make-instance 'quoted-printable-encoder
|
|
|
|
:output-function #'(lambda (char)
|
|
|
|
(queue-append buffer-queue char))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream base64-encoder-input-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(with-slots (encoder buffer-queue) stream
|
|
|
|
(setf encoder
|
2022-01-19 14:39:58 +01:00
|
|
|
(make-instance 'base64-encoder
|
|
|
|
:output-function #'(lambda (char)
|
|
|
|
(queue-append buffer-queue char))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod stream-read-char ((stream encoder-input-stream))
|
|
|
|
(with-slots (encoder buffer-queue real-stream) stream
|
|
|
|
(loop
|
|
|
|
while (queue-empty-p buffer-queue)
|
|
|
|
do (be byte (read-byte real-stream nil)
|
2022-01-19 14:39:58 +01:00
|
|
|
(if byte
|
|
|
|
(encoder-write-byte encoder byte)
|
|
|
|
(progn
|
|
|
|
(encoder-finish-output encoder)
|
|
|
|
(queue-append buffer-queue :eof)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(queue-pop buffer-queue)))
|
|
|
|
|
|
|
|
|
|
|
|
(defmethod stream-read-char ((stream 8bit-encoder-input-stream))
|
|
|
|
(with-slots (real-stream) stream
|
|
|
|
(aif (read-byte real-stream nil)
|
2022-01-19 14:39:58 +01:00
|
|
|
(code-char it)
|
|
|
|
:eof)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defclass input-adapter-stream ()
|
|
|
|
((source :initarg :source)
|
|
|
|
(real-stream)
|
|
|
|
(input-function)))
|
|
|
|
|
|
|
|
(defclass binary-input-adapter-stream (fundamental-binary-input-stream input-adapter-stream) ())
|
|
|
|
|
|
|
|
(defclass character-input-adapter-stream (fundamental-character-input-stream input-adapter-stream) ())
|
|
|
|
|
|
|
|
(defmethod stream-element-type ((stream binary-input-adapter-stream))
|
|
|
|
'(unsigned-byte 8))
|
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream input-adapter-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(assert (slot-boundp stream 'source)))
|
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream binary-input-adapter-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
;; REAL-STREAM slot is set only if we are going to close it later on
|
|
|
|
(with-slots (source real-stream input-function) stream
|
|
|
|
(etypecase source
|
|
|
|
(string
|
|
|
|
(setf real-stream (make-string-input-stream source)
|
2022-01-19 14:39:58 +01:00
|
|
|
input-function #'(lambda ()
|
|
|
|
(awhen (read-char real-stream nil)
|
|
|
|
(char-code it)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
((vector (unsigned-byte 8))
|
|
|
|
(be i 0
|
2022-01-19 14:39:58 +01:00
|
|
|
(setf input-function #'(lambda ()
|
|
|
|
(when (< i (length source))
|
|
|
|
(prog1 (aref source i)
|
|
|
|
(incf i)))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(stream
|
|
|
|
(assert (input-stream-p source))
|
|
|
|
(setf input-function (if (subtypep (stream-element-type source) 'character)
|
2022-01-19 14:39:58 +01:00
|
|
|
#'(lambda ()
|
|
|
|
(awhen (read-char source nil)
|
|
|
|
(char-code it)))
|
|
|
|
#'(lambda ()
|
|
|
|
(read-byte source nil)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(pathname
|
|
|
|
(setf real-stream (open source :element-type '(unsigned-byte 8))
|
2022-01-19 14:39:58 +01:00
|
|
|
input-function #'(lambda ()
|
|
|
|
(read-byte real-stream nil))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(file-portion
|
|
|
|
(setf real-stream (open-decoded-file-portion source)
|
2022-01-19 14:39:58 +01:00
|
|
|
input-function #'(lambda ()
|
|
|
|
(read-byte real-stream nil)))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream character-input-adapter-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
;; REAL-STREAM slot is set only if we are going to close later on
|
|
|
|
(with-slots (source real-stream input-function) stream
|
|
|
|
(etypecase source
|
|
|
|
(string
|
|
|
|
(setf real-stream (make-string-input-stream source)
|
2022-01-19 14:39:58 +01:00
|
|
|
input-function #'(lambda ()
|
|
|
|
(read-char real-stream nil))))
|
2021-08-21 15:29:43 +02:00
|
|
|
((vector (unsigned-byte 8))
|
|
|
|
(be i 0
|
2022-01-19 14:39:58 +01:00
|
|
|
(setf input-function #'(lambda ()
|
|
|
|
(when (< i (length source))
|
|
|
|
(prog1 (code-char (aref source i))
|
|
|
|
(incf i)))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(stream
|
|
|
|
(assert (input-stream-p source))
|
|
|
|
(setf input-function (if (subtypep (stream-element-type source) 'character)
|
2022-01-19 14:39:58 +01:00
|
|
|
#'(lambda ()
|
|
|
|
(read-char source nil))
|
|
|
|
#'(lambda ()
|
|
|
|
(awhen (read-byte source nil)
|
|
|
|
(code-char it))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(pathname
|
|
|
|
(setf real-stream (open source :element-type 'character)
|
2022-01-19 14:39:58 +01:00
|
|
|
input-function #'(lambda ()
|
|
|
|
(read-char real-stream nil))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(file-portion
|
|
|
|
(setf real-stream (open-decoded-file-portion source)
|
2022-01-19 14:39:58 +01:00
|
|
|
input-function #'(lambda ()
|
|
|
|
(awhen (read-byte real-stream nil)
|
|
|
|
(code-char it))))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod close ((stream input-adapter-stream) &key abort)
|
|
|
|
(when (slot-boundp stream 'real-stream)
|
|
|
|
(with-slots (real-stream) stream
|
|
|
|
(close real-stream :abort abort))))
|
|
|
|
|
|
|
|
(defmethod stream-read-byte ((stream binary-input-adapter-stream))
|
|
|
|
(with-slots (input-function) stream
|
|
|
|
(or (funcall input-function)
|
2022-01-19 14:39:58 +01:00
|
|
|
:eof)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod stream-read-char ((stream character-input-adapter-stream))
|
|
|
|
(with-slots (input-function) stream
|
|
|
|
(or (funcall input-function)
|
2022-01-19 14:39:58 +01:00
|
|
|
:eof)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defclass delimited-input-stream (fundamental-character-input-stream coder-stream-mixin)
|
|
|
|
((start-offset :initarg :start
|
2022-01-19 14:39:58 +01:00
|
|
|
:initform 0
|
|
|
|
:reader stream-start
|
|
|
|
:type integer)
|
2021-08-21 15:29:43 +02:00
|
|
|
(end-offset :initarg :end
|
2022-01-19 14:39:58 +01:00
|
|
|
:initform nil
|
|
|
|
:reader stream-end
|
|
|
|
:type (or null integer))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod print-object ((object delimited-input-stream) stream)
|
|
|
|
(if *print-readably*
|
|
|
|
(call-next-method)
|
|
|
|
(with-slots (start-offset end-offset) object
|
2022-01-19 14:39:58 +01:00
|
|
|
(print-unreadable-object (object stream :type t :identity t)
|
|
|
|
(format stream "start=~A end=~A" start-offset end-offset)))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defun base-stream (stream)
|
|
|
|
(if (typep stream 'delimited-input-stream)
|
|
|
|
(base-stream (real-stream stream))
|
|
|
|
stream))
|
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream delimited-input-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(unless (slot-boundp stream 'real-stream)
|
|
|
|
(error "REAL-STREAM is unbound. Must provide a :STREAM argument."))
|
|
|
|
(with-slots (start-offset) stream
|
|
|
|
(when start-offset
|
|
|
|
(file-position stream start-offset))))
|
|
|
|
|
|
|
|
(defmethod stream-read-char ((stream delimited-input-stream))
|
|
|
|
(with-slots (real-stream end-offset) stream
|
|
|
|
(if (or (not end-offset)
|
2022-01-19 14:39:58 +01:00
|
|
|
(< (file-position real-stream) end-offset))
|
|
|
|
(or (read-char real-stream nil)
|
|
|
|
:eof)
|
|
|
|
:eof)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
#+(OR)(defmethod stream-read-byte ((stream delimited-input-stream))
|
|
|
|
(with-slots (real-stream end-offset) stream
|
|
|
|
(if (or (not end-offset)
|
2022-01-19 14:39:58 +01:00
|
|
|
(< (file-position real-stream) end-offset))
|
|
|
|
(or (read-byte real-stream nil)
|
|
|
|
:eof)
|
|
|
|
:eof)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defclass my-string-input-stream (fundamental-character-input-stream coder-stream-mixin)
|
|
|
|
((string :initarg :string
|
2022-01-19 14:39:58 +01:00
|
|
|
:reader stream-string)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defmethod initialize-instance ((stream my-string-input-stream) &key &allow-other-keys)
|
|
|
|
(call-next-method)
|
|
|
|
(assert (slot-boundp stream 'string))
|
|
|
|
(with-slots (string real-stream) stream
|
|
|
|
(setf real-stream (make-string-input-stream string))))
|
|
|
|
|
|
|
|
(defmethod stream-read-char ((stream my-string-input-stream))
|
|
|
|
(with-slots (real-stream) stream
|
|
|
|
(or (read-char real-stream nil)
|
2022-01-19 14:39:58 +01:00
|
|
|
:eof)))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(defstruct file-portion
|
|
|
|
data ; string or a pathname
|
|
|
|
encoding
|
|
|
|
start
|
|
|
|
end)
|
|
|
|
|
|
|
|
(defun open-file-portion (file-portion)
|
|
|
|
(be data (file-portion-data file-portion)
|
|
|
|
(etypecase data
|
|
|
|
(pathname
|
|
|
|
(be stream (open data)
|
2022-01-19 14:39:58 +01:00
|
|
|
(make-instance 'delimited-input-stream
|
|
|
|
:stream stream
|
|
|
|
:start (file-portion-start file-portion)
|
|
|
|
:end (file-portion-end file-portion))))
|
2021-08-21 15:29:43 +02:00
|
|
|
(string
|
|
|
|
(make-instance 'delimited-input-stream
|
2022-01-19 14:39:58 +01:00
|
|
|
:stream (make-string-input-stream data)
|
|
|
|
:start (file-portion-start file-portion)
|
|
|
|
:end (file-portion-end file-portion)))
|
2021-08-21 15:29:43 +02:00
|
|
|
(stream
|
|
|
|
(make-instance 'delimited-input-stream
|
2022-01-19 14:39:58 +01:00
|
|
|
:stream data
|
|
|
|
:dont-close t
|
|
|
|
:start (file-portion-start file-portion)
|
|
|
|
:end (file-portion-end file-portion))))))
|
2021-08-21 15:29:43 +02:00
|
|
|
|
|
|
|
(defun open-decoded-file-portion (file-portion)
|
|
|
|
(make-instance (case (file-portion-encoding file-portion)
|
2022-01-19 14:39:58 +01:00
|
|
|
(:quoted-printable 'quoted-printable-decoder-stream)
|
|
|
|
(:base64 'base64-decoder-stream)
|
|
|
|
(t '8bit-decoder-stream))
|
|
|
|
:stream (open-file-portion file-portion)))
|