3

I have in my application a image upload method that need to send a image and a string to my server.

The problem is that the server receives the content (image and string) but when it saves the image on the disk it is corrupted and can't be opened.

This is the relevant part of the script.

HttpPost httpPost = new HttpPost(url);

Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();

bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);

byte[] byteArray = stream.toByteArray();
String byteStr = new String(byteArray);

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("--"+boundary+"\r\n");
stringBuilder.append("Content-Disposition: form-data; name=\"content\"\r\n\r\n");
stringBuilder.append(message+"\r\n");

stringBuilder.append("--"+boundary+"\r\n");
stringBuilder.append("Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r\n");

stringBuilder.append("Content-Type: image/jpeg\r\n\r\n");

stringBuilder.append(byteStr);
stringBuilder.append("\r\n");

stringBuilder.append("--"+boundary+"--\r\n");
StringEntity entity = new StringEntity(stringBuilder.toString());
httpPost.setEntity(entity);

I can't change the server because other clients use it and it works for them. I just need to understand why the image is being corrupted.

Nicos Karalis
  • 3,724
  • 4
  • 33
  • 62

2 Answers2

3

When you do new String(byteArray), it's converting binary into the default character set (which is typically UTF-8). Most character sets aren't a suitable encoding for binary data. In other words if you were to encode certain binary strings to UTF-8 and then decode back to binary, you would not get the same binary string.

Since you're using multipart encoding, you need to write directly to the stream of the entity. Apache HTTP Client has helpers for doing this. See this guide, or this Android guide to uploading with multipart.

If you NEED to using strings only, you can safely convert your byte array to a string with

String byteStr = android.util.Base64.encode(byteArray, android.util.Base64.DEFAULT);

But it's important to note that your server will need to Base64 decode the string back to a byte array and save it to an image. Further, the transfer size will be greater because Base64 encoding isn't as space efficient as raw binary.

Samuel
  • 16,923
  • 6
  • 62
  • 75
  • That doesn't work for us. We don't want to use that library. If we can I prefer to use only strings to send the information. – Nicos Karalis Mar 07 '16 at 23:00
  • @NicosKaralis It doesn't matter what you prefer.You don't have the choice. You must write the bytes directly, not via a `String`. – user207421 Mar 07 '16 at 23:03
  • @NicosKaralis if you have control over the server code, you could alternatively encode the image with Base64 encoding (which is suitable for encoding binary data) and then decode and save the image on the server side. This would probably take extra effort. – Samuel Mar 07 '16 at 23:05
  • @Samuel we have control over the server, but I'm not changing 5 different clients because android has its problems. Every other app is doing this: convert the image to bytes and send it to the server. – Nicos Karalis Mar 07 '16 at 23:07
  • @EJP Why java can't allow this? A string is a set of bytes. Why cant I put the bytes I want? – Nicos Karalis Mar 07 '16 at 23:07
  • @NicosKaralis Because in a nutshell `String` is not a container for binary data. It is a container for UTF-16 characters. The round trip between binary and String is not guaranteed to work. However you don't need to convert in even one direction. I am unable to believe you have five working applications that use Strings for this purpose. – user207421 Mar 07 '16 at 23:11
  • @NicosKaralis if you post the code to one of your other clients, we can tell you why that one is working, and this Android one isn't. It's likely that the other client is appending the raw binary and not converting to text first. – Samuel Mar 07 '16 at 23:12
  • 1
    The swift versions are using `UIImageJPEGRepresentation` to get the bytes and put it all in a string that is sent to the server. The browser version also sends the image as a string. – Nicos Karalis Mar 07 '16 at 23:18
0

Your solutions above is not working because you are using new String(byteArray). The constructor encodes the byte array using the default encoding - see What is the default encoding - and it is very likely, that you have byte sequences in your data that cannot be encoded into a character.

To be more precise, a charset defines how characters are represented as bytes. Most charsets have more than 256 characters. That is why you need more than one byte to represent a character. UTF-8 and UTF-16 uses up to four bytes. So you have a mapping between the number space and the character space and this mapping is not bejectiv a priori. So it is very likely that there exist a number in the number space that have no character mapped to it.

The solution @Samuel suggested is foolproof because Base64 uses A–Z, a–z, 0–9, + , / and terminates with = to represent a byte. I would prefer this solution!

If you don't want or cannot use Base64, than you can try just to throw in every byte as it is into the StringBuilder hoping that the server does not do any encoding before you get it.

for (byte b : byteArray) {
  stringBuilder.append((char)b);
}

I do not recommand that solution in general, but it may help you to get your stuff done.

Community
  • 1
  • 1
Peter
  • 4,752
  • 2
  • 20
  • 32