;;; guile-email --- Guile email parser ;;; Copyright © 2018, 2020 Arun Isaac ;;; Copyright © 2023 Andrew Whatson ;;; ;;; This file is part of guile-email. ;;; ;;; guile-email is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU Affero General Public License as ;;; published by the Free Software Foundation; either version 3 of the ;;; License, or (at your option) any later version. ;;; ;;; guile-email 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 ;;; Affero General Public License for more details. ;;; ;;; You should have received a copy of the GNU Affero General Public ;;; License along with guile-email. If not, see ;;; . (define-module (email quoted-printable) #:use-module (rnrs bytevectors) #:use-module (rnrs io ports) #:use-module (ice-9 format) #:use-module (ice-9 match) #:use-module (srfi srfi-26) #:export (quoted-printable-decode quoted-printable-encode q-encoding-decode q-encoding-encode)) ;; TODO: Error out on invalid quoted-printable input (define quoted-printable-decode (match-lambda* (((? string? str)) (call-with-input-string str quoted-printable-decode)) (((? port? in)) (call-with-bytevector-output-port (cut quoted-printable-decode in <>))) (((? port? in) (? port? out)) (let ((c (read-char in))) (cond ((eof-object? c) out) ((char=? c #\=) (let ((c1 (read-char in))) ;; Skip if \n was read. (unless (char=? c1 #\Newline) (let ((c2 (read-char in))) ;; Skip if \r\n was read. (unless (and (char=? c1 #\Return) (char=? c2 #\Newline)) (put-u8 out (string->number (string c1 c2) 16)))))) (quoted-printable-decode in out)) (else (put-u8 out (char->integer c)) (quoted-printable-decode in out))))))) (define* (quoted-printable-style-encode in out literal-char-set #:optional (number-of-chars-left-on-this-line 76)) (let ((x (get-u8 in)) (put-into-output (lambda (str) (let* ((len (string-length str)) (break-line? (<= number-of-chars-left-on-this-line len))) (put-string out (string-append (if break-line? "=\n" "") str)) (if break-line? (- 76 len) (- number-of-chars-left-on-this-line len)))))) (unless (eof-object? x) (let ((c (integer->char x))) (quoted-printable-style-encode in out literal-char-set (put-into-output (if (char-set-contains? literal-char-set c) (string c) (format #f "=~:@(~2,'0x~)" x)))))))) ;; Character set of characters to be represented by themselves in ;; quoted-printable encoding (define %quoted-printable-literal-char-set (char-set-delete (ucs-range->char-set (char->integer #\space) (char->integer #\delete)) #\=)) ;; Character set of characters to be represented by themselves in Q ;; encoding (define %q-encoding-literal-char-set (char-set-delete %quoted-printable-literal-char-set #\? #\_)) (define quoted-printable-encode (match-lambda* (((? bytevector? bv)) (call-with-port (open-bytevector-input-port bv) quoted-printable-encode)) (((? port? in)) (call-with-output-string (cut quoted-printable-encode in <>))) (((? port? in) (? port? out)) (quoted-printable-style-encode in out %quoted-printable-literal-char-set)))) (define (q-encoding-decode str) (quoted-printable-decode (string-map (lambda (c) (if (char=? c #\_) #\Space c)) str))) (define (q-encoding-encode bv) (string-map (lambda (c) (if (char=? c #\Space) #\_ c)) (call-with-port (open-bytevector-input-port bv) (lambda (in) (call-with-output-string (cut quoted-printable-style-encode in <> %q-encoding-literal-char-set))))))