AFNetworking / AFAmazonS3Manager

AFNetworking Client for the Amazon S3 API
MIT License
362 stars 148 forks source link

Can't change content type of PUT file from octet/stream to image/jpeg #91

Closed ransomweaver closed 9 years ago

ransomweaver commented 9 years ago

I'm sending images to s3 like this:

[[[VS3 sharedVS3] s3Manager]
     putObjectWithFile:fileName
     destinationPath:remotePath
     parameters:nil
     progress:nil

and this works, but the files seem to be the default octet/stream on s3, which is not desirable. so we try to set the Content-type header to image/jpeg like this:

[[[VS3 sharedVS3] s3Manager]
     putObjectWithFile:fileName
     destinationPath:remotePath
     parameters:@{@"Content-Type":@"image/jpeg"}
     progress:nil

and that works (to set the header), but now we get 403 from s3:

The request signature we calculated does not match the signature you provided. 
Check your key and signing method

<StringToSign>PUT

image/jpeg
Fri, 29 May 2015 06:03:06 GMT
/vnimages/uploads/2015/05/29/11/E29128D6-FD26-4B0E-9268-BB3A04B13173.jpg</StringToSign>

Seems like the Content-Type parameter is missing from the signing of the request?

I see in AFAmazonS3Manager::putObjectWithFile that it does this to get the signed request:

request = [self.requestSerializer requestWithMethod:method URLString:[[self.baseURL URLByAppendingPathComponent:destinationPath] absoluteString] parameters:nil error:nil];

which shows that the parameters passed to putObjectWithFile never get to requestBySettingAuthorizationHeadersForRequest in the requestSerializer. But, changing that nil to parameters doesn't fix it for me.

mattt commented 9 years ago

Content-Type is a header, not a parameter. The correct MIME type should be automatically provided if the file extension is .jpg or .jpeg. If not, the request serializer can override this.

ransomweaver commented 9 years ago

You are sure this is the case for PUT, not POST? The code in AFAmazonS3Manager is this:

 NSData *data = [NSURLConnection sendSynchronousRequest:fileRequest returningResponse:&response error:&fileError];

If I log the mimetype:

NSLog(@"mimetype is %@", [response MIMEType]); // for me this shows "image/jpeg" for the local file

then POST OR PUT:

POST uses

[formData appendPartWithFileData:data name:@"file" 
fileName:[filePath lastPathComponent] mimeType:[response MIMEType]];

but PUT does

request = [self.requestSerializer requestWithMethod:method URLString:[[self.baseURL URLByAppendingPathComponent:destinationPath] absoluteString] parameters:nil error:nil];

    // S3 expects parameters as headers for PUT requests
    if (parameters != nil) {
        for (id key in parameters) {
            [request setValue:[parameters objectForKey:key] forHTTPHeaderField:key];
        }
    }

    request.HTTPBody = data;

The ".jpg" is at the end of destinationPath...is that how the request serializer is supposed to figure out the mime type? A custom request serializer is required here?

btrimble commented 9 years ago

This seems like a bug or a lack of functionality. putObject doesn't allow for setting headers that factor into the auth signature because the headers (passed as parameters) are set on the request after the auth signature is generated. This leads to a 403 from S3.

You can subclass the request serializer but it ends up duplicating code. Even then you can only set headers that are derived from information that's in the request like Content-Type from file extension or an MD5 hash. You can't specify 'x-amz-acl' header for instance.

chrisallick commented 9 years ago

Would love for this to be fixed.