#lang scheme/base
(require "base.ss"
"depend.ss"
)
(define-struct (content-type kvs) (base sub))
(define (content-type/full ct)
(format "~a/~a" (content-type-base ct)
(content-type-sub ct)))
(define (normalize-params params)
(define (normalize-param-name name)
(let ((match (regexp-match #px"([^\\*])+\\*(\\d+)" name)))
(if (not match)
(cons name -inf.0)
(cons (cadr match) (caddr (string->number match))))))
(define (continue-parameter-helper rest ordered name acc)
(cond ((null? rest)
(reverse (cons (cons name (string-join (reverse acc) "")) ordered)))
((string-ci=? (caaar rest) name)
(continue-parameter-helper (cdr rest) ordered
name
(cons (cdar rest) acc)))
(else (parameter-order-helper rest
(cons (cons name (string-join (reverse acc) ""))
ordered)))))
(define (parameter-order-helper rest ordered)
(cond ((null? rest) (reverse ordered))
((eq? -inf.0 (cdaar rest))
(parameter-order-helper (cdr rest)
(cons (cons (caaar rest) (cdar rest)) ordered)))
(else (continue-parameter-helper (cdr rest)
ordered
(caaar rest)
(list (cdar rest))))))
(parameter-order-helper (sort (map (lambda (kv)
(cons (normalize-param-name (car kv))
(cdr kv)))
params)
(lambda (p1 p2)
(let ((n1 (car p1))
(n2 (car p2)))
(or (string-ci<? (car n1) (car n2))
(< (cdr n1) (cdr n2))))))
'()))
(define (build-content-type type params)
(define (helper base sub params)
(make-content-type (normalize-params params)
(string-downcase base)
(string-downcase sub)))
(if-it (regexp-match #px"^([^\\/]+)\\/([^\\/]+)$" type)
(helper (cadr it) (caddr it) params)
(helper "text" "plain" params)))
(define p:parameter (tokens/comment key <- p:atom
#\=
value <- p:param-value
(return (cons key value))))
(define (content-type-value maker)
(tokens/comment type <- p:atom
params <-
(zero-one (tokens/comment #\;
params <-
(make-kvs-parser p:atom #\=
p:param-value #\;)
(zero-one #\; #\;)
(return params))
'())
(return (maker type params))))
(define read-content-type (make-reader (content-type-value build-content-type)))
(define (write-content-type ct (type content-type/full))
(format "~a; ~a"
(type ct)
(kvs->string ct #:one (lambda (kv)
(kv->string kv #:encode-val encode-param-value)))))
(define content-type/default
(read-content-type "text/plain; charset=us-ascii;"))
(mime-header-reader-set! "Content-Type" read-content-type)
(string-converter-set! content-type? write-content-type)
(define content-type-reader-registry (make-kvs-registry '()))
(define (read-by-content-type in headers)
(let ((reader (registry-ref content-type-reader-registry
(content-type/full (kvs/list-ref headers
"Content-Type"
content-type/default))
#f)))
(if (procedure? reader)
(reader (if (input-port? in)
in
(open-input-abytes in)))
in)))
(define (content-type-reader-set! type reader)
(registry-set! content-type-reader-registry type reader))
(define (content-type-reader-del! type)
(registry-del! content-type-reader-registry type))
(define content-type->input-registry (make-cond-registry '()))
(define (content-type->input data)
((registry-ref content-type->input-registry
data
(lambda (data)
(error 'content-type->input "unknown type: ~a")))
data))
(define (content-type->input-set! type ->input)
(registry-set! content-type->input-registry type ->input))
(define (content-type->input-del! type)
(registry-del! content-type->input-registry type))
(content-type->input-set! string? open-input-abytes)
(content-type->input-set! bytes? open-input-abytes)
(provide/contract
(struct content-type ((inner any/c)
(base any/c)
(sub any/c)))
(p:parameter Parser/c)
(read-content-type Reader/c)
(content-type-value (-> procedure? Parser/c))
(write-content-type (->* (kvs/list?)
((-> kvs/list? any))
string?))
(content-type/full (-> content-type? string?))
(content-type/default content-type?)
(build-content-type (-> string? kvs/list? kvs?))
(read-by-content-type (-> (or/c string? bytes? input-port?) kvs/list? any))
(content-type-reader-set! (-> string? procedure? any))
(content-type-reader-del! (-> string? any))
(content-type->input (-> any/c input-port?))
(content-type->input-set! (-> procedure? procedure? any))
(content-type->input-del! (-> procedure? any))
)