| 1 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 2 |
;;;; base.lisp: The Program |
|---|
| 3 |
;;;; Copyright (C) 2004 Robert Marlow <rob@bobturf.org> |
|---|
| 4 |
;;;; |
|---|
| 5 |
;;;; This library is free software; you can redistribute it and/or |
|---|
| 6 |
;;;; modify it under the terms of the GNU Library General Public |
|---|
| 7 |
;;;; License as published by the Free Software Foundation; either |
|---|
| 8 |
;;;; version 2 of the License, or (at your option) any later version. |
|---|
| 9 |
;;;; |
|---|
| 10 |
;;;; This library is distributed in the hope that it will be useful, |
|---|
| 11 |
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 |
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 13 |
;;;; Library General Public License for more details. |
|---|
| 14 |
;;;; |
|---|
| 15 |
;;;; You should have received a copy of the GNU Library General Public |
|---|
| 16 |
;;;; License along with this library; if not, write to the |
|---|
| 17 |
;;;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|---|
| 18 |
;;;; Boston, MA 02111-1307, USA. |
|---|
| 19 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
(defpackage cl-qprint |
|---|
| 23 |
(:use :cl) |
|---|
| 24 |
(:nicknames :qprint) |
|---|
| 25 |
(:export :encode |
|---|
| 26 |
:decode)) |
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
(in-package :cl-qprint) |
|---|
| 30 |
|
|---|
| 31 |
(defun decode (input) |
|---|
| 32 |
"INPUT must be a string or a stream. Reads quoted-printable encoding |
|---|
| 33 |
from INPUT and produces the equivalent decoded string" |
|---|
| 34 |
(let ((out-stream (make-string-output-stream)) |
|---|
| 35 |
(in-stream |
|---|
| 36 |
(etypecase input |
|---|
| 37 |
(string (make-string-input-stream input)) |
|---|
| 38 |
(stream input)))) |
|---|
| 39 |
(do ((char (read-char in-stream nil 'eof) |
|---|
| 40 |
(read-char in-stream nil 'eof))) |
|---|
| 41 |
((eql char 'eof) |
|---|
| 42 |
(get-output-stream-string out-stream)) |
|---|
| 43 |
(princ (if (char= char #\=) |
|---|
| 44 |
(let ((char2 (read-char in-stream))) |
|---|
| 45 |
;; Check for and convert all newlines (LF or CRLF) |
|---|
| 46 |
;; to nothing. The = indicates a soft line break. |
|---|
| 47 |
(if (member char2 '(#\return #\linefeed) |
|---|
| 48 |
:test #'char=) |
|---|
| 49 |
(let ((char3 (read-char in-stream nil 'eof))) |
|---|
| 50 |
(cond |
|---|
| 51 |
((eql char3 'eof) "") |
|---|
| 52 |
((and (char= char3 #\linefeed) |
|---|
| 53 |
(char= char2 #\return)) "") |
|---|
| 54 |
(t char3))) |
|---|
| 55 |
;; If not a newline the = indicates encoding |
|---|
| 56 |
(code-char (parse-integer |
|---|
| 57 |
(format nil "~C~C" |
|---|
| 58 |
char2 |
|---|
| 59 |
(read-char in-stream nil 'eof)) |
|---|
| 60 |
:radix 16)))) |
|---|
| 61 |
char) |
|---|
| 62 |
out-stream)))) |
|---|
| 63 |
|
|---|
| 64 |
|
|---|
| 65 |
(defun cr-lf (stream) |
|---|
| 66 |
"Prints a CRLF sequence to STREAM. RFC 2045 mandates CRLF for newlines" |
|---|
| 67 |
(princ #\return stream) |
|---|
| 68 |
(princ #\linefeed stream)) |
|---|
| 69 |
|
|---|
| 70 |
|
|---|
| 71 |
(defun encode (input &key columns encode-newlines) |
|---|
| 72 |
"INPUT must be either a string or a stream. Reads from INPUT and produces |
|---|
| 73 |
a quoted-printable encoded string" |
|---|
| 74 |
(let ((out-stream (make-string-output-stream)) |
|---|
| 75 |
(in-stream |
|---|
| 76 |
(etypecase input |
|---|
| 77 |
(string (make-string-input-stream input)) |
|---|
| 78 |
(stream input))) |
|---|
| 79 |
(last-line-break 0) |
|---|
| 80 |
(ws nil)) |
|---|
| 81 |
|
|---|
| 82 |
(do ((c (read-char in-stream nil 'eof) |
|---|
| 83 |
(read-char in-stream nil 'eof)) |
|---|
| 84 |
(position 0 (file-position out-stream))) |
|---|
| 85 |
((eql c 'eof) |
|---|
| 86 |
(get-output-stream-string out-stream)) |
|---|
| 87 |
|
|---|
| 88 |
;; Put in a soft line break if the line's gotten too long |
|---|
| 89 |
(when (and columns |
|---|
| 90 |
(>= (- position last-line-break) (1- columns))) |
|---|
| 91 |
(princ #\= out-stream) |
|---|
| 92 |
(cr-lf out-stream) |
|---|
| 93 |
(setf last-line-break position)) |
|---|
| 94 |
|
|---|
| 95 |
;; ws on the end of a line must be encoded |
|---|
| 96 |
(when ws |
|---|
| 97 |
(if (char= c #\newline) |
|---|
| 98 |
(format out-stream "=~2,'0X" (char-code ws)) |
|---|
| 99 |
(princ ws out-stream))) |
|---|
| 100 |
|
|---|
| 101 |
(cond |
|---|
| 102 |
|
|---|
| 103 |
;; Ensure newlines are CR-LF |
|---|
| 104 |
((char= c #\newline) |
|---|
| 105 |
(if encode-newlines |
|---|
| 106 |
(format out-stream "=0D=0A") |
|---|
| 107 |
(cr-lf out-stream)) |
|---|
| 108 |
(setf last-line-break position)) |
|---|
| 109 |
|
|---|
| 110 |
;; Keep track of whitespace in case of following newlines |
|---|
| 111 |
((member c '(#\space #\tab) :test #'char=) |
|---|
| 112 |
(setf ws c)) |
|---|
| 113 |
|
|---|
| 114 |
;; Encode non-printable characters and = |
|---|
| 115 |
((or (char< c #\!) |
|---|
| 116 |
(char> c #\~) |
|---|
| 117 |
(char= c #\=)) |
|---|
| 118 |
(format out-stream "=~2,'0X" (char-code c))) |
|---|
| 119 |
|
|---|
| 120 |
;; Else just print the character. |
|---|
| 121 |
(t (princ c out-stream))) |
|---|
| 122 |
|
|---|
| 123 |
;; Keep track of whitespace in case we hit a newline |
|---|
| 124 |
(unless (member c '(#\space #\tab) :test #'char=) |
|---|
| 125 |
(setf ws nil))))) |
|---|
| 126 |
|
|---|
| 127 |
|
|---|