net/mime: allow any subtype, exns as exn:fail subtypes; doc fixes

original commit: 7153fbd4d5
This commit is contained in:
Matthew Flatt 2012-01-08 09:54:27 -07:00
parent 6feb946842
commit e6d06d9bd5
4 changed files with 109 additions and 38 deletions

View File

@ -101,13 +101,8 @@
(define lowercase string-downcase) (define lowercase string-downcase)
(define warning (define warning
void
#;
(lambda (msg . args) (lambda (msg . args)
(fprintf (current-error-port) (log-warning (apply format (cons msg args)))))
(apply format (cons msg args)))
(newline (current-error-port)))
)
;; Copies its input `in' to its ouput port if given, it uses ;; Copies its input `in' to its ouput port if given, it uses
;; current-output-port if out is not provided. ;; current-output-port if out is not provided.

View File

@ -34,7 +34,7 @@
;; -- exceptions raised -- ;; -- exceptions raised --
(struct-out mime-error) (struct-out mime-error)
(struct-out unexpected-termination) (struct-out unexpected-termination)
(struct-out missing-multipart-boundary-parameter) (struct-out missing-multipart-boundary-parameter) ; this is the only one actually raised
(struct-out malformed-multipart-entity) (struct-out malformed-multipart-entity)
(struct-out empty-mechanism) (struct-out empty-mechanism)
(struct-out empty-type) (struct-out empty-type)
@ -77,6 +77,9 @@
("base64" . base64))) ("base64" . base64)))
(define ietf-extensions '()) (define ietf-extensions '())
;; We don't try to keep up with IANA substypes
#;
(define iana-extensions (define iana-extensions
'(;; text '(;; text
("plain" . plain) ("plain" . plain)
@ -140,9 +143,10 @@
#:mutable) #:mutable)
;; Exceptions ;; Exceptions
(define-struct mime-error ()) (define-struct (mime-error exn:fail) ())
(define-struct (unexpected-termination mime-error) (msg)) (define-struct (unexpected-termination mime-error) (msg))
(define-struct (missing-multipart-boundary-parameter mime-error) ()) (define-struct (missing-multipart-boundary-parameter mime-error) ())
(define-struct (malformed-multipart-entity mime-error) (msg)) (define-struct (malformed-multipart-entity mime-error) (msg))
(define-struct (empty-mechanism mime-error) ()) (define-struct (empty-mechanism mime-error) ())
(define-struct (empty-type mime-error) ()) (define-struct (empty-type mime-error) ())
@ -238,7 +242,9 @@
(let ([boundary (entity-boundary entity)]) (let ([boundary (entity-boundary entity)])
(when (not boundary) (when (not boundary)
(when (eq? 'multipart (entity-type entity)) (when (eq? 'multipart (entity-type entity))
(raise (make-missing-multipart-boundary-parameter)))) (raise (make-missing-multipart-boundary-parameter
"missing multipart \"boundary\" parameter"
(current-continuation-marks)))))
(set-entity-parts! entity (set-entity-parts! entity
(map (lambda (part) (map (lambda (part)
(mime-analyze part #t)) (mime-analyze part #t))
@ -615,9 +621,10 @@
;; iana-token := <A publicly-defined extension token. Tokens ;; iana-token := <A publicly-defined extension token. Tokens
;; of this form must be registered with IANA ;; of this form must be registered with IANA
;; as specified in RFC 2048.> ;; as specified in RFC 2048.>
;; Instead of trying to list all registered types
;; here, we just convert to a symbol.
(define (iana-token value) (define (iana-token value)
(let ([ans (assoc (lowercase (trim-spaces value)) iana-extensions)]) (string->symbol (lowercase (trim-spaces value))))
(and ans (cdr ans))))
;; parameter := attribute "=" value ;; parameter := attribute "=" value
(define re:parameter (regexp "([^=]+)=(.+)")) (define re:parameter (regexp "([^=]+)=(.+)"))

View File

@ -36,11 +36,16 @@ The library was written by Francisco Solsona.}
@section{Message Decoding} @section{Message Decoding}
@defproc[(mime-analyze [message-in (or/c bytes? input-port)] @defproc[(mime-analyze [message-in (or/c bytes? input-port)]
[part? any/c]) [part? any/c #f])
message?]{ message?]{
Parses @racket[message-in] and returns the parsed result as a Parses @racket[message-in] and returns the parsed result as a
@racket[message] instance.} @racket[message] instance.
If @racket[part?] is @racket[#f], then @racket[message-in] should
start with the header for a full message; otherwise,
@racket[message-in] should start with the header for a part within a
message.}
@defstruct[message ([version real?] @defstruct[message ([version real?]
[entity entity] [entity entity]
@ -61,16 +66,20 @@ field contains one string for each field in the message header.}
[other (listof string?)] [other (listof string?)]
[fields (listof string?)] [fields (listof string?)]
[parts (listof message?)] [parts (listof message?)]
[body (output-port? . -> . void?)])]{ [body (or/c (output-port? . -> . void?) null?)])]{
Represents the content of a message or a sub-part. Represents the content of a message or a sub-part. The
@racket[mime-analyze] function chooses default values for fields
when they are not specified in input.
Standard values for the @racket[type] field include @racket['text], Standard values for the @racket[type] field include @racket['text],
@racket['image], @racket['audio], @racket['video], @racket['image], @racket['audio], @racket['video],
@racket['application], @racket['message], and @racket['multipart]. @racket['application], @racket['message], and @racket['multipart].
Standard values for the @racket[subtype] field depend on the Standard values for the @racket[subtype] field depend on the
@racket[type] field, and include the following: @racket[type] field, and include the following, but any
@racket[subtype] is allowed as a downcased version of the
specification from the header.
@mime-table[ @mime-table[
( (
@ -150,11 +159,11 @@ messages. This list is non-empty only when @racket[type] is
@racket['multipart] or @racket['message]. @racket['multipart] or @racket['message].
The @racket[body] field represents the body as a function that The @racket[body] field represents the body as a function that
consumes an output out and writes the decoded message to the port. No consumes an output out and writes the decoded message to the port. If
bytes are written if @racket[type] is @racket['multipart] or @racket[type] is @racket['multipart] or @racket['message]., then
@racket['message]. All of the standard values of @racket[encoding] @racket[body] is @racket['()]. All of the standard values of
are supported. The procedure only works once (since the encoded body @racket[encoding] are supported. The procedure only works once (since
is pulled from a stream).} the encoded body is pulled from a stream).}
@defstruct[disposition ([type symbol?] @defstruct[disposition ([type symbol?]
[filename (or/c string? false/c)] [filename (or/c string? false/c)]
@ -189,48 +198,43 @@ the @racket["Content-Disposition"] header, if included in the message.}
@section[#:tag "mime-exns"]{Exceptions} @section[#:tag "mime-exns"]{Exceptions}
@defstruct[mime-error ()]{ @defstruct[(mime-error exn:fail) ()]{
The supertype of all MIME exceptions.} The supertype of all MIME exceptions. Only the subtype
@racket[missing-multipart-boundary-parameter] is ever actually
raised.}
@defstruct[(unexpected-termination mime-error) ([msg string?])]{ @defstruct[(unexpected-termination mime-error) ([msg string?])]{
Raised when an end-of-file is reached while parsing the headers of a Originally raised when an end-of-file is reached while parsing the
MIME entity. It usually means that the message does not conform headers of a MIME entity, but currently a mere warning is logged.}
to RFC 2045 and friends.}
@defstruct[(missing-multipart-boundary-parameter mime-error) ()]{ @defstruct[(missing-multipart-boundary-parameter mime-error) ()]{
Raised when a multipart type is specified, but no @racket["Boundary"] Raised when a multipart type is specified, but no @racket["Boundary"]
parameter is given or an end-of-file is encountered before the parameter is given.}
boundary.}
@defstruct[(malformed-multipart-entity mime-error) ([msg string?])]{ @defstruct[(malformed-multipart-entity mime-error) ([msg string?])]{
Similar to @racket[unexpected-termination], but used only while Never actually raised.}
scanning parts of a multipart message.}
@defstruct[(empty-mechanism mime-error) ()]{ @defstruct[(empty-mechanism mime-error) ()]{
Raised when no transport encoding mechanism was provided with the Never actually raised.}
@racket["Content-Transfer-Encoding"] field.}
@defstruct[(empty-type mime-error) ()]{ @defstruct[(empty-type mime-error) ()]{
Raised when no type is specified for @racket["Content-Type"], or when Never actually raised.}
the specification is incorrectly formatted.}
@defstruct[(empty-subtype mime-error) ()]{ @defstruct[(empty-subtype mime-error) ()]{
Raised when no sub-type is specified for @racket["Content-Type"], or Never actually raised.}
when the specification is incorrectly formatted.}
@defstruct[(empty-disposition-type mime-error) ()]{ @defstruct[(empty-disposition-type mime-error) ()]{
Raised when type specified for the @racket["Content-Disposition"] Never actually raised.}
field, or when the specification is incorrectly formatted.}
@; ---------------------------------------- @; ----------------------------------------

View File

@ -0,0 +1,65 @@
#lang racket/base
(require net/mime)
(define-syntax-rule (test expect expr)
(let ([val expr])
(unless (equal? expect val)
(error 'test "failed at ~s: ~e" 'expr val))))
;; This test is based on an example from Jordan Schatz
(define ip
(open-input-string
(regexp-replace* #rx"(\r\n|\n)"
#<<EOS
Server: MochiWeb/1.1 WebMachine/1.9.0 (someone had painted it blue)
Expires: Fri, 06 Jan 2012 02:01:12 GMT
Date: Fri, 06 Jan 2012 01:51:12 GMT
Content-Type: multipart/mixed; boundary=9nbsYRvJBLRyuL4VOuuejw9LcAy
Content-Length: 817
--9nbsYRvJBLRyuL4VOuuejw9LcAy
Content-Type: multipart/mixed; boundary=NdzDrpIQMsJKtfv9VrXmp4YwCPh
--NdzDrpIQMsJKtfv9VrXmp4YwCPh
X-Riak-Vclock: a85hYGBgzGDKBVIcypz/fvp9087NYEpkzGNlaGCpPMGXBQA=
Location: /buckets/invoices/keys/RAQpCw8SssXlXVhiGAGYXsVmwvk
Content-Type: application/json
Link: </buckets/invoices>; rel="up"
Etag: 1qS8Wrr2vkTBxkITOjo33K
Last-Modified: Wed, 04 Jan 2012 17:12:32 GMT
{"date": "11/02/2011"}
--NdzDrpIQMsJKtfv9VrXmp4YwCPh--
--9nbsYRvJBLRyuL4VOuuejw9LcAy--
EOS
"\r\n")))
(let* ([analyzed (mime-analyze ip)]
[our-entity (message-entity analyzed)]
[parts (entity-parts our-entity)]
[inner-message (car parts)]
[inner-entity (message-entity inner-message)]
[body-proc (entity-body inner-entity)]
[tmp (open-output-string)])
(test '("Server: MochiWeb/1.1 WebMachine/1.9.0 (someone had painted it blue)"
"Expires: Fri, 06 Jan 2012 02:01:12 GMT"
"Date: Fri, 06 Jan 2012 01:51:12 GMT")
(message-fields analyzed))
(test 1 (length parts))
(test '() body-proc)
(test 1 (length (entity-parts inner-entity)))
(define sub (message-entity (car (entity-parts inner-entity))))
(test 'application (entity-type sub))
(test 'json (entity-subtype sub))
((entity-body sub) tmp)
(test "{\"date\": \"11/02/2011\"}" (get-output-string tmp)))
(test 'not-there (with-handlers ([exn:fail?
(lambda (exn)
(and (missing-multipart-boundary-parameter? exn)
'not-there))])
(mime-analyze
(open-input-string "Content-Type: multipart/mixed\r\n\r\n"))))