Titanium Community Questions & Answer Archive

We felt that 6+ years of knowledge should not die so this is the Titanium Community Questions & Answer Archive

REST PUT to AWS S3 -- works on iOS, not Android

What works

We have successfully come up with the code to upload a file using a RESTful PUT to Amazon's S3 simple storage service. We have shared all of our code and required libraries at the links at the bottom of this question. Hopefully others find this helpful when uploading files from iOS to S3.

The problem

The upload does not complete successfully on the Android Simulator or an Android device. The error we get is:

InvalidArgument: Authorization header is invalid – one and only one ' ' (space) required

We have checked extremely thoroughly for extra spaces. The upload would fail on iOS as well if there were actually extra spaces in our authorization header. Yet this code works great for successfully uploading files using an iOS simulator and device.

Our Setup

Titanium SDK: 1.6.2

iOS SDK 4.3

Android SDK: APIs 2.2 Screen: HVGA

Android Device: Motorolla Droid Pro running 2.2.1

The crux of our code

    xhr.open('PUT', 'https://s3.amazonaws.com/'+AWSBucketName+'/' + fileName, false);
    var StringToSign = 'PUT\n\n\n'+fileContents.mimeType+'\n' + currentDateTime + '\n/'+AWSBucketName+'/' + fileName;
    var AWSSignature = b64_hmac_sha1(AWSSecretAccessKey, Utf8.encode(StringToSign));
    var AuthorizationHeader = AWSAccessKeyID.concat(AWSSignature);
    xhr.setRequestHeader('Authorization', Ti.Utils.base64encode(AuthorizationHeader).toString()); 
    xhr.setRequestHeader('Content-Type', fileContents.mimeType);
    xhr.setRequestHeader('Host', 's3.amazonaws.com');
    xhr.setRequestHeader('Date', currentDateTime);
    xhr.send(fileContents);

Our complete code and libraries used

Our Complete S3Upload.js Code

http://pastie.org/1996251

SHA Library from AWS Project (save as sha-aws.js)

http://pastie.org/1996385

UTF8 Library (save as UTF8.js)

http://www.webtoolkit.info/javascript-utf8.html

Date formatting library (save as date.js)

http://www.mattkruse.com/javascript/date/source.html

— asked May 31st 2011 by Matthew Taylor
  • amazon
  • android
  • aws
  • createhttpclient
  • iphone
  • mobile
  • put
  • rest
  • s3
  • upload
  • xhr
7 Comments
  • It's worth noting that we've tried this with the very latest nightly build, and the error still occurs.

    Any help is greatly appreciated!

    — commented June 1st 2011 by Matthew Taylor
  • Hi Matthew, Did you ever get this working properly? thanks

    — commented February 19th 2012 by Leonardo Amigoni
  • I got similar issue, those code work on iOS but not in android simulator.
    Just wanna to know if it is a bug in Titanium Android code.

    I am using Titanium SDK 1.8.2 and Android APIs 2.2

    — commented March 4th 2012 by Sam Wong
  • Unfortunately, no, we never were able to get this working. It is hard to believe it's been almost a year and no real answer from Appcelerator or other user. If anybody has premium support or connection to the staff, steering them to this question would be much appreciated by all who share this problem!

    — commented March 7th 2012 by Matthew Taylor
  • Ok thanks Matthew I'll stop banging my head against a wall then. Can I ask did you use another storage service to fix the issue or did you just can the project? I'm thinking of looking at Google Storage or similar. Just wondered if you had found an API that accepts the PUT request from Android. Many Thanks.

    — commented March 9th 2012 by Thomas Arnold
  • We had to can the project due to this issue. You might also check out Azure Blob Storage, but back when we were working on this, their REST API didn't have many examples or sample code so we never made any headway with that.

    — commented March 9th 2012 by Matthew Taylor
  • Hello,

    I have a problem with download or GET the zip file from the Amazone s3 with authorization.

    When i download zip file without authorization then it will be working fine but in with authorization, it gave me Access Denied Error.
    If Anyone have a solution then please tell me.

    Thanks in advance

    — commented March 6th 2013 by Jigar Maheshwari

10 Answers

  • I don't know about Android, but I had to really hack the original code up to make it work in iOS on SDK 2.1.2. I had to tweak the date format generated, cleaned up the building of the Authorization header (which was being generated incorrectly), and cleaned up the generation of the specific URLs.

    see: https://gist.github.com/3607211

    — answered September 3rd 2012 by Zach Hendershot
    permalink
    1 Comment
    • Agreed. Thanks to Matthew for the original code, but the authentication didn't work for me - even on iOS. It would only work if the bucked allowed "Everyone" level access. Zach's github authentication worked on iOS straight away. (SDK 1.6.2)

      — commented October 3rd 2012 by Mel Bradley
  • I'm using a the HTML POST solution… It requires 1.7 RC ~> see my gist here: https://gist.github.com/1012632, this lets me fetch a signature + policy from my web server and then send the file to s3 using a multi part form post.

    — answered June 8th 2011 by Todd Fisher
    permalink
    0 Comments
  • Matthew - did you crack this eventually.

    I too have iOS working fine. I can also get a small file into s3 from Android but it doesn't contain the picture. Very odd.

    Anything you can share would be appreciated.

    — answered March 7th 2012 by Thomas Arnold
    permalink
    3 Comments
    • Thomas
      Do you mean you can send a file to S3 while "PUT"?

      var xhr.open('PUT', 'https://s3.amazonaws.com/'+AWSBucketName+'/' + fileName, false);
      
      //something setting
      
        var f = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,'data.txt');        
      var blobtxt ;
      if (f.exists()) {
           blobtxt = f.read();
      }
        xhr.send(blobtxt);
      

      But if it is a image, it doesn't work?

      var xhr.open('PUT', 'https://s3.amazonaws.com/'+AWSBucketName+'/' + fileName, false);
      
      //something setting
      
        var f = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,'data.jpg');        
      var blobImage;
      if (f.exists()) {
           blobImage = f.read();
      }
        xhr.send(blobImage);
      

      — commented March 8th 2012 by Sam Wong
    • Yes - although now I have broken it again! Essentially I had iOS sending the image whilst Android was entering what seemed to be only the meta data into s3.

      Very odd - essentially it was just a small 15 byte file that was in s3.

      I'll try the txt field option to double check this for you. I'll try and get back to you today. I really need to crack this ASAP.

      — commented March 8th 2012 by Thomas Arnold
    • Hi Sam,

      Struggling to get the data.txt file into the Android data application filesystem so I can't test the data.txt option.

      However I have got the code back to working on iOS and putting the smaller 'none' file from Android. I did it by NOT sending the authorisation header. Crazy I know but it works on iOS.

      — commented March 8th 2012 by Thomas Arnold
  • Ok I can confirm that after for testing the situation is this;

    The code supplied in this thread does support iOS uploads but not Android (the file simply never makes it to s3 due to the fact that the 'Authorization header is invalid – one and only one ' ' (space) required' error occurs"

    However if you comment out the authorization header iOS still uploads the full file and Android uploads a very small 15 byte file, but no actual image behind it. Very, very odd.

    Does that happen to you Sam?

    — answered March 8th 2012 by Thomas Arnold
    permalink
    0 Comments
  • Just to add more to this it appears that SDK 1.8.2 has issues uploading images when using httpclient.

    Guess I'll just have to wait for the latest SDK upgrade - hopefully 1.9.

    — answered March 9th 2012 by Thomas Arnold
    permalink
    5 Comments
    • Hi Thomas

      More specific to say, in android

      xhr.send(blobImage);
      

      Instead of sending a blob, it will send a String [Ti Blob Object].

      You may open your 15byte JPEG file in S3, there are '[Ti Blob Object]'.

      — commented March 12th 2012 by Sam Wong
    • Yes thats exactly what happens Sam. Have you found a way round this?

      — commented March 12th 2012 by Thomas Arnold
    • The Doc said httpclient send should work with blob. I think the sdk is doing something automatical blob convertion while the httpclient send request.

      It is supposed to work in "POST" but not luck in "PUT" in Android. I just want to report it as a bug before we add something in our client code.

      — commented March 12th 2012 by Sam Wong
    • Did anyone ever solve this problem?

      If so it would be great if you would share your solution.

      — commented June 4th 2012 by Chuck Bentley
    • Sam - did you file a bug about this? If so, can you post a link here so we can watch and comment on it? Thanks!

      — commented June 8th 2012 by Mark Ross
  • Hi,

       Even am facing the same and authentication problem too, did you people get authentication if so can you please help me out, for iphone it shows as authenticated user but file dosent get uploaded until we change settings as for "All users" in aws in andorid also same issue but it throws" one and only one space required" error for authentication
    
    — answered March 13th 2012 by VSR Kishore
    permalink
    0 Comments
  • Is authentication working for you if so can you please help me out,
    In iphone it says your file is uploaded but if check on s3 its not getting
    uploaded until i change s3 settings to ALL Users and in android it is throwing " one
    and only one space required" error. If your done with authentication please help me out.

    Thank You

    — answered March 13th 2012 by VSR Kishore
    permalink
    0 Comments
  • I have a defect file with Appcelerator with regards to the people having issues with Android uploading 15 bytes instead of an image. See here:

    https://jira.appcelerator.org/browse/TIMOB-9590

    — answered June 16th 2012 by Mark Ross
    permalink
    0 Comments
  • This bug was fixed on Tuesday (Aug 14th). Can someone download the nightly build and see if it's fixed?

    — answered August 18th 2012 by Matt
    permalink
    0 Comments
  • Has anyone got this to work?

    https://jira.appcelerator.org/browse/TIMOB-9590 says it is resolved as of release 3.0.0, but I am using Titanium Studio, build: 3.4.1.201410281727 and I am still getting this issue. I am using the same segment of code as shown in the test case in the PR, except with my own filenames, URLs, etc. It works on iOS simulator but not my Android device. I get the following error message:

    InvalidArgument: Authorization header is invalid – one and only one ' ' (space) required

    I'm wondering if maybe this problem started to happen again because of something in the recent builds? My version of Titanium is apparently either reading "[object TiBlob]" instead of the actual image data when I do fileContents = uploadFile.read(); or it is just not sending the actual image data when I later do xhr.send(fileContents);

    What should I do?

    — answered February 19th 2015 by Karl Schultz
    permalink
    4 Comments
    • We never got timely support or answers from the appcelerator team, so no, we never got it to work. You might try posting a new question to see if you might be able to get some help. Best of luck to you.

      — commented February 19th 2015 by Matthew Taylor
    • Thanks, I hope so. REST API with PUT seems like it ought to be nailed down by now!

      — commented February 19th 2015 by Karl Schultz
    • We're in luck! The REST PUT actually does work. If I comment out the line in my code that adds the Authorization Header for AWS, it magically begins uploading my images:

      xhr.setRequestHeader('Authorization', Ti.Utils.base64encode(AuthorizationHeader).toString());
      

      where AuthorizationHeader is defined elsewhere in my code. The funny thing is this line works in iOS but not in Android. The definition of the AuthorizationHeader is nothing but simple string processing, which I assume works the same with both platforms. This leads me to believe there is something wrong with the implementation of base64encode() in Android.

      I'm going to test with a third-party implementation of base64encode() and report back my findings.

      — commented February 20th 2015 by Karl Schultz
    • As near as I can tell, this behavior is caused by https://jira.appcelerator.org/browse/TIMOB-9111. Does anyone have a good work around?

      — commented February 20th 2015 by Karl Schultz
The ownership of individual contributions to this community generated content is retained by the authors of their contributions.
All trademarks remain the property of the respective owner.