REBOL [
    Title:
        "decode-multipart-form-data"
    Authors:
        ["Andreas Bolka"]
    Contributors:
        []
    Date:
        2014-05-21
    History:
        [
            2002-06-18 abolka "initial release"
            2003-02-21 abolka "major bugfixes and cleanup. example improved."
            2003-02-22 abolka "another parsing bug fixed"
            2003-09-12 abolka "fixed n/v-handling bug, noted by Marc Meurrens"
            2004-07-18 abolka "major restructuring"
            2014-05-21 abolka "relicense under apache license, version 2.0"
        ]
    Rights: {
        Copyright (C) 2002-2014 Andreas Bolka

        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at

            http://www.apache.org/licenses/LICENSE-2.0

        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.
    }
    File:
        %decode-multipart-form-data.r
    Version:
        1.5
    Purpose: {
        Decodes POST-data used in "Form-based File Upload in HTML" as specified
        in RFC 1867, encoded as "multipart/form-data" as specified in RFC 2388.
    }
    Usage: {
        decode-multipart-form-data's output is compatible to decode-cgi
        wherever possible. So the output contains a list of set-word's and
        values, one pair for each data field. example:

            [field1: "foo" field2: "bar"]

        Parts of the form-data with content-type text/plain and no filename
        attribute in the content dispostition will be translated to basic name
        value pairs as in the example above.

        Parts having with a content-type different from text/plain and/or a
        filename attribute in their content disposition will be translated to
        object!'s with the following fields: filename, type, content.

        An example. Imagine an HTML form like the following:

            
Once this form is submitted with "foo" in field1 and a file called "bar.txt" containing the three bytes "nuf" in field2, this will result in the following to be returned from 'decode-multipart-form-data: [ field1: "foo" field2: make object! [ filename: "bar.txt" type: "text/plain" content: "nuf" ] ] A typical call of decode-multipart-form-data looks like the following example: decode-multipart-form-data system/options/cgi/content-type post-data } ] ; ; @@ ; - add multipart/mixed support ; - add content-transfer-encoding support ; - improve parse-multipart (handle arbitrary header) ; context [ parse-boundary: func [content-type /local boundary] [ boundary: none parse/all content-type [ thru "boundary=" opt #"^"" copy boundary [to #"^"" | to #";" | to #"," | to end] ] return boundary ] parse-multipart: func [ boundary entity-body /local parts part-beg part-end dispo type content ] [ parts: copy [] part-beg: rejoin ["--" boundary crlf] part-end: rejoin [crlf "--" boundary] parse/all entity-body [ to part-beg some [ (dispo: none type: copy "text/plain" content: none) part-beg "content-disposition: " copy dispo to crlf crlf opt ["content-type: " copy type to crlf crlf] crlf copy content to part-end crlf (repend parts [dispo type content]) ] "--" crlf ] parts ] set 'decode-multipart-form-data func [ content-type entity-body /local bd parts r n v p ] [ if any [ (not find content-type "multipart/form-data") (none? bd: parse-boundary content-type) ] [ make error! "Invalid Content-Type or no boundary parameter." ] parts: parse-multipart bd entity-body r: copy [] foreach [pdispo ptype pcontent] parts [ pdispo: next parse pdispo {;="} n: to-set-word select pdispo "name" v: pcontent if any [ (select pdispo "filename") (not find ptype "text/plain") ] [ v: make object! compose [ filename: (select pdispo "filename") type: (ptype) content: (pcontent) ] ] either p: find r n [change/only next p reduce [(first next p) v]] [repend r [n v]] ] r ] ] ;; context [ ct1: {multipart/form-data; boundary=---------------------------5884326489707} eb1: {-----------------------------5884326489707^M Content-Disposition: form-data; name="name"^M ^M asdf^M -----------------------------5884326489707^M Content-Disposition: form-data; name="name"^M ^M bsdf^M -----------------------------5884326489707^M Content-Disposition: form-data; name="file"; filename="test.txt"^M Content-Type: text/plain^M ^M test:^M ^M -----------------------------5884326489707^M Content-Disposition: form-data; name="text"^M ^M ^M -----------------------------5884326489707--^M } exp1: compose [ (to-set-word 'name) ["asdf" "bsdf"] (to-set-word 'file) (make object! [ filename: "test.txt" type: "text/plain" content: "test:^M^/" ]) (to-set-word 'text) (none) ] regress: has [got1] [ either = mold/all exp1 mold/all got1: decode-multipart-form-data ct1 eb1 [print "OK"] [print "ERR: " probe got1] ] ]