NSURLRequest Upload Multiple Files
Asked Answered
L

2

1

I'm trying to upload multiple files to a server using a POST request, but for some reason, only one file is submitted. I suspect I'm doing something wrong with the boundary, but I'm not sure where...

What's wrong with my code?

    [_serverRequest setHTTPMethod:@"POST"];

    NSString *_boundary = @"14737809831466499882746641449";

    NSString *_contentType = [NSString stringWithFormat:@"multipart/form-data; charset=UTF-8; boundary=%@",_boundary];
    [_serverRequest setValue:_contentType forHTTPHeaderField:@"Content-Type"];

    /* reqeuest body */
    NSMutableData *_requestBody = [NSMutableData data];

    for (id _instance in self.currentBrowserInstances)
    {
        if ([_instance respondsToSelector:@selector(pathToDatabase)])
        {
            NSString *_databasePath = [_instance pathToDatabase];

            NSMutableString *_filename = [NSMutableString stringWithString:[self _generatedFilename]];
            [_filename appendFormat:@"_%@", [[_instance name] stringByReplacingOccurrencesOfString:@" " withString:@""]];

            if (_databasePath.pathExtension.length > 0)
                [_filename appendFormat:@".%@", _databasePath.pathExtension];

            /* Build Request Body */
            [_requestBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [_requestBody appendData:[[NSString stringWithFormat:@"Content-Disposition: multipart/form-data; name=\"databases\"; filename=\"%@\"\r\n", _filename] dataUsingEncoding:NSUTF8StringEncoding]];
            [_requestBody appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
            [_requestBody appendData:[NSData dataWithContentsOfFile:_databasePath]];
            [_requestBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        }
    }

    [_requestBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [_serverRequest setHTTPBody:_requestBody];

Here's the server response:

Array
(
    [databases] => Array
        (
            [name] => QTSKUFJM_test.dat
            [type] => application/octet-stream
            [tmp_name] => /private/var/tmp/php3w7qd5
            [error] => 0
            [size] => 64576
        )
)
Lindberg answered 18/9, 2013 at 18:18 Comment(2)
This doesn't answer your question, but the AFNetworking library has a great implementation of multipart/form-data that I'm using in a project at the moment. You can use that method without using anything else in the framework.Lurcher
@Lurcher In fact, this implementation as a number of issues.Abbotsen
A
7

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:

--<boundary>--

and not

--<boundary>--<boundary>--

as in your implementation ;)

A CRLF following the last delimiter won't hurt, also.


Edit:

A more extensive answer

The corresponding specification of a multipart/form-data can be read in Returning Values from Forms: multipart/form-data RFC 7578 and in Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types in Section 5.1 Multipart Media Type RFC 2388.

The 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)).

The media-type 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 multipart/form-data.

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.

Common Syntax

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
                          end-boundary-delimiter

where an encapsulation is a boundary delimiter followed by a CRLF followed by the body part:

encapsulation :=          boundary-delimiter CRLF 
                          body-part
                          
                          

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 "--"

Caution:

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.

and

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!


Examples

Note, all CR and LF are shown explicitly.

Example: multipart-body with one part, two entity headers:

boundary = "1234567890"

\r\n--1234567890\r\n
header1: value1\r\n
header2: value2\r\n
\r\n<data>
\r\n--1234567890--

Example: multipart-body with two parts:

\r\n--1234567890\r\n
header1: value1\r\n
header2: value2\r\n
\r\n<data1>
\r\n--1234567890\r\n
header1: value1\r\n
\r\n<data2>
\r\n--1234567890--

Example: multipart-body with no headers:

\r\n--1234567890\r\n
\r\n<data1>
\r\n--1234567890--

Example: multipart-body with empty body-part:

\r\n--1234567890\r\n
\r\n--1234567890--
Abbotsen answered 20/9, 2013 at 19:13 Comment(4)
I've made the edit (see updated code above), but this yields the same result unfortunately.Lindberg
There are countless reasons why this may not work. Setting up a multipart message is definitely error prone. But there might be other reasons, too. You should check the error and log them in the console. For reference, I've updated my quite brief answer to a ridiculous extensive one, which may help you - and hopefully others, too. ;)Abbotsen
Thank you so much for sharing your knowledge! :) this kind of answers is why I love SO! These many short on-minute-answers may keep the site alive and active but people like you make SO actually valuable ;) keep going :) I like some of your other answers, too ;)Hamnet
Thank you for taking the time to write such an extensive answer!! Much appreciated.Lindberg
S
0

Can't add a comment so i'll just post here...

Part of the reason why you have only one file is because when you have more than one file you need to use a [] for the name like so:

[_requestBody appendData:[[NSString stringWithFormat:@"Content-Disposition: \ 
multipart/form-data;  name=\"databases[]\"; filename=\"%@\"\r\n", _filename] \
dataUsingEncoding:NSUTF8StringEncoding]];

you will end up with an array instead of a single file.

Also make sure that _filenameis unique otherwise it will mess with the file structure.

Smirk answered 25/11, 2014 at 19:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.