Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's wrong with my code to upload a file to AWS S3 using a pre-signed URL?

I want to upload a file from an iOS App to an AWS S3 bucket using a pre-signed URL. The URL is correct because it works with curl on the command line.

curl -v -k --upload-file FILENAME "https://MYBUCKET.amazonaws.com:443/KEYNAME?Signature=...&Expires=1391691489&AWSAccessKeyId=..."

With the following Objective-C code ...

- (void)upload:(NSString *)url fileData:(NSData *)fileData
{
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:[NSURL URLWithString:url]];
    [request setHTTPMethod:@"PUT"];
    [request setHTTPBody:fileData];
    [request setValue:[NSString stringWithFormat:@"%d", [fileData length]] forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"audio/mpeg" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"public-read" forHTTPHeaderField:@"x-amz-acl"];
    [request setValue:@"iPhone-OS/6.0 fr_FR NE" forHTTPHeaderField:@"User-Agent"];

    _connection = [NSURLConnection connectionWithRequest:request delegate:self];
    [_connection start];
}

... I get this error:

Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x9c49560 {NSErrorFailingURLStringKey=https://MYBUCKET.s3.amazonaws.com:443/KEYNAME?Signature=...&Expires=1391703958&AWSAccessKeyId=..., NSErrorFailingURLKey=https://MYBUCKET.amazonaws.com:443/KEYNAME?Signature=...&Expires=1391703958&AWSAccessKeyId=..., NSLocalizedDescription=The request timed out., NSUnderlyingError=0x9c48c80 "The request timed out."}

I used WireShark to see if there is any traffic and there is a lot of traffic.

I have no idea what's wrong with my code. It seems the file transfer does not terminate correctly.

like image 647
Jan Deinhard Avatar asked Feb 06 '14 16:02

Jan Deinhard


1 Answers

I solved this myself. The Content-Type header was the culprit. In total despair I tested my code with a very small text file and got 403 as the HTTP status code from S3. No timeout. Such progress. I also got a very informative error message:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><StringToSignBytes>...</StringToSignBytes><RequestId>...</RequestId><HostId>...</HostId><SignatureProvided>...</SignatureProvided>
    <StringToSign>PUT

    text/plain
    1391784394
    KEYNAME</StringToSign>
    <AWSAccessKeyId>...</AWSAccessKeyId>
</Error>

Obviously the content type string (text/plain in this case) is expected in the string-to-sign if it is provided as an HTTP header from the client. Don't ask me why this causes a timeout with large (5.5MB?) files. I hope this will save someone else a few hours of life time.

The easiest fix is to just remove the line

[request setValue:@"..." forHTTPHeaderField:@"Content-Type"];

If you know the content type when you create the pre-signed URL you can of course add the string to the string-to-sign then.

like image 69
Jan Deinhard Avatar answered Sep 19 '22 09:09

Jan Deinhard