From a49bcac81be2e3146f8c06e3a53906d2e8b4e7a0 Mon Sep 17 00:00:00 2001 From: Sefa Ilkimen Date: Thu, 15 Feb 2018 11:10:19 +0100 Subject: [PATCH] Android: support guessing charset when header value is not set --- .../github/kevinsawicki/http/HttpRequest.java | 17 ++++-- .../synconset/cordovahttp/CordovaHttp.java | 60 +++++++++++++++++-- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/android/com/github/kevinsawicki/http/HttpRequest.java b/src/android/com/github/kevinsawicki/http/HttpRequest.java index d6b057b..3b48f22 100644 --- a/src/android/com/github/kevinsawicki/http/HttpRequest.java +++ b/src/android/com/github/kevinsawicki/http/HttpRequest.java @@ -1945,16 +1945,24 @@ public class HttpRequest { } /** - * Get the response body as a {@link String} and set it as the value of the + * Get the response body as ByteBuffer and set it as the value of the * given reference. * * @param output * @return this request * @throws HttpRequestException */ - public HttpRequest body(final AtomicReference output) throws HttpRequestException { - output.set(body()); - return this; + public HttpRequest body(final AtomicReference output) throws HttpRequestException { + final ByteArrayOutputStream outputStream = byteStream(); + + try { + copy(buffer(), outputStream); + output.set(ByteBuffer.wrap(outputStream.toByteArray())); + + return this; + } catch (IOException e) { + throw new HttpRequestException(e); + } } /** @@ -1971,7 +1979,6 @@ public class HttpRequest { return this; } - /** * Is the response body empty? * diff --git a/src/android/com/synconset/cordovahttp/CordovaHttp.java b/src/android/com/synconset/cordovahttp/CordovaHttp.java index fbd075e..3d24021 100644 --- a/src/android/com/synconset/cordovahttp/CordovaHttp.java +++ b/src/android/com/synconset/cordovahttp/CordovaHttp.java @@ -12,6 +12,14 @@ import org.json.JSONObject; import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.MalformedInputException; + import javax.net.ssl.SSLHandshakeException; import java.util.ArrayList; @@ -19,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.Iterator; import android.text.TextUtils; @@ -28,7 +37,7 @@ import com.github.kevinsawicki.http.HttpRequest.HttpRequestException; abstract class CordovaHttp { protected static final String TAG = "CordovaHTTP"; - protected static final String[] ACCEPTED_CHARSETS = new String[] {HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1}; + protected static final String[] ACCEPTED_CHARSETS = new String[] { HttpRequest.CHARSET_UTF8, HttpRequest.CHARSET_LATIN1 }; private static AtomicBoolean sslPinning = new AtomicBoolean(false); private static AtomicBoolean acceptAllCerts = new AtomicBoolean(false); @@ -231,25 +240,68 @@ abstract class CordovaHttp { request.uncompress(true); } + private CharsetDecoder createCharsetDecoder(final String charsetName) { + return Charset.forName(charsetName).newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + } + + private String decodeBody(AtomicReference rawOutput, String charsetName) + throws CharacterCodingException, MalformedInputException { + + if (charsetName == null) { + return tryDecodeByteBuffer(rawOutput); + } + + return decodeByteBuffer(rawOutput, charsetName); + } + + private String tryDecodeByteBuffer(AtomicReference rawOutput) + throws CharacterCodingException, MalformedInputException { + + for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) { + try { + return decodeByteBuffer(rawOutput, ACCEPTED_CHARSETS[i]); + } catch (MalformedInputException e) { + continue; + } catch (CharacterCodingException e) { + continue; + } + } + + return decodeBody(rawOutput, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]); + } + + private String decodeByteBuffer(AtomicReference rawOutput, String charsetName) + throws CharacterCodingException, MalformedInputException { + + return createCharsetDecoder(charsetName).decode(rawOutput.get()).toString(); + } + protected void returnResponseObject(HttpRequest request) throws HttpRequestException { try { JSONObject response = new JSONObject(); int code = request.code(); - String body = request.body(request.charset()); + AtomicReference rawOutputReference = new AtomicReference(); + request.body(rawOutputReference); response.put("status", code); response.put("url", request.url().toString()); this.addResponseHeaders(request, response); if (code >= 200 && code < 300) { - response.put("data", body); + response.put("data", decodeBody(rawOutputReference, request.charset())); this.getCallbackContext().success(response); } else { - response.put("error", body); + response.put("error", decodeBody(rawOutputReference, request.charset())); this.getCallbackContext().error(response); } } catch(JSONException e) { this.respondWithError("There was an error generating the response"); + } catch(MalformedInputException e) { + this.respondWithError("Could not decode response data due to malformed data"); + } catch(CharacterCodingException e) { + this.respondWithError("Could not decode response data due to invalid or unknown charset encoding"); } }