The final delimiter (the "encapsulation boundary") following the last multipart, is the same as for a body parts plus two hyphens.
Thus, the final delimiter looks as follows:
as in your implementation ;)
A CRLF following the last delimiter won't hurt, also.
A more extensive answer
The corresponding specification of a
multipart/form-data is "Form-based File Upload in HTML" RFC 1867 and RFC 2388.
multipart media type is defined more precisely in RFC 2046 (which obsoletes RFC 1521, RFC 1522, RFC 1590).
The following are relevant excerpts from RFC 2388 and RFC 2046:
According RFC 2388, a
multipart/form-data contains a series of parts. Each part must contain a particular content-disposition header (see RFC 2183) where the disposition type is "form-data" and where the disposition has an additional parameter of "name". For example:
Content-Disposition: form-data; name="user"
Note: The most recent RFC for content-disposition header is defined in RFC 2231 (Updates: 2045, 2047, 2183; Obsoletes RFC 2184)).
multipart/form-data strictly follows the rules of all multipart MIME data streams:
Subtypes of the "multipart" type must use an identical syntax. Subtypes may differ in their semantics, and may impose additional restrictions on syntax, but must conform to the required syntax for the "multipart" type.
Basically, the body of a multipart must contain one or more body parts, each preceded by a boundary delimiter line, and the last one followed by a closing boundary delimiter line.
Each body part consists of a header area, a blank line, and a body area. The header area is allowed to be empty.
Note: a body part is an entity, and not a RFC 882 message. That is, no headers are actually required in body parts. The absence of a Content-Type header usually indicates that the corresponding body has a content-type of "text/plain; charset=US-ASCII".
If the contents of a file is to be transfered, then "Content-Type" shall be set to the file's media type, if known, otherwise "application/octet-stream".
Hint: The only header fields that have defined meaning for body parts are those whose names begin with "Content-". All other header fields may be ignored in body parts.
Note: If multiple files are to be returned as the result of a single form entry, they should be represented as a
multipart/mixed part embedded within the
The original local file name may be supplied as well, either as a "filename" parameter either of the "content-disposition: form-data" header or, in the case of multiple files, in a "content-disposition: file" header of the subpart.
The common syntax for the
multipart media type is defined in RFC 2046 § 5.1.1
Here is a simplified more comprehensive form:
A multipart must have a Content-Type. For example:
Content-Type: multipart/subtype; boundary=gc0p4Jq0M2Yt08j34c0p
For the boundary there are certain limitations (please see RFC 2046). In practice, enclosing it in double quotes makes it more robust:
Content-Type: multipart/subtype; boundary="---- boundary which requires quotes -----"
Each part is preceded by a boundary delimiter. The boundary delimiter MUST occur at the beginning of a line (that is, following a CRLF). Conceptually, the CRLF belongs to the boundary, rather to the preceding element:
boundary-delimiter := CRLF "--" boundary
Note: the boundary may be followed by zero or more whitespace, which is not shown in this BNF.
A multipart body consists of one or more encapsulations followed by a closing delimiter:
multipart-body := +encapsulation
where an encapsulation is a boundary delimiter followed by a CRLF followed by the body part:
encapsulation := boundary-delimiter CRLF
The body part (the entity) consists of entity-headers and the body:
body-part := MIME-part-headers [CRLF *OCTET]
Note: An entity-header (a MIME-part-header) will be delimited by a CRLF - as any other header.
The last part is followed by a closing delimiter:
end-boundary-delimiter := CRLF "--" boundary "--"
Despite the massive amount of papers, the above definition for boundaries is still unclear - if not ambiguous!
In RFC 2046, § 5.1.1 Common Syntax, it states:
It (the boundary) is then terminated by either another CRLF and the header fields for the next part, or by two CRLFs, in which case there are no header fields for the next part.
The CRLF preceding the boundary delimiter line is conceptually attached to the boundary...
A body-part may be completely empty (according the BNF). A boundary will be followed by one CRLF and then followed by either a boundary-delimiter or a end-boundary-delimiter. Only since the next delimiter starts itself with a CRLF, the preceding boundary has two subsequent CRLFs!
Note, all CR and LF are shown explicitly.
Example: multipart-body with one part, two entity headers:
boundary = "1234567890"
Example: multipart-body with two parts:
Example: multipart-body with no headers:
Example: multipart-body with empty body-part: