package com.android.hotspot2.utils; import android.util.Base64; import android.util.Log; import com.android.hotspot2.osu.OSUManager; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; public class HTTPResponse implements HTTPMessage { private final int mStatusCode; private final Map mHeaders = new LinkedHashMap<>(); private final ByteBuffer mBody; private static final String csIndicator = "charset="; public HTTPResponse(InputStream in) throws IOException { int expected = Integer.MAX_VALUE; int offset = 0; int body = -1; byte[] input = new byte[RX_BUFFER]; int statusCode = -1; int bodyPattern = 0; while (offset < expected) { int amount = in.read(input, offset, input.length - offset); Log.d(OSUManager.TAG, String.format("Reading into %d from %d, amount %d -> %d", input.length, offset, input.length - offset, amount)); if (amount < 0) { throw new EOFException(); } //Log.d("ZXZ", "HTTP response: '" // + new String(input, 0, offset + amount, StandardCharsets.ISO_8859_1)); if (body < 0) { for (int n = offset; n < offset + amount; n++) { bodyPattern = (bodyPattern << 8) | (input[n] & 0xff); if (bodyPattern == 0x0d0a0d0a) { body = n + 1; statusCode = parseHeader(input, body, mHeaders); expected = calculateLength(body, mHeaders); if (expected > input.length) { input = Arrays.copyOf(input, expected); } break; } } } offset += amount; if (offset < expected && offset == input.length) { input = Arrays.copyOf(input, input.length * 2); } } mStatusCode = statusCode; mBody = ByteBuffer.wrap(input, body, expected - body); } private static int parseHeader(byte[] input, int body, Map headers) throws IOException { String headerText = new String(input, 0, body - BODY_SEPARATOR_LENGTH, StandardCharsets.ISO_8859_1); //System.out.println("Received header: " + headerText); Iterator headerLines = Arrays.asList(headerText.split(CRLF)).iterator(); if (!headerLines.hasNext()) { throw new IOException("Bad HTTP Request"); } int statusCode; String line0 = headerLines.next(); String[] status = line0.split(" "); if (status.length != 3 || !"HTTP/1.1".equals(status[0])) { throw new IOException("Bad HTTP Result: " + line0); } try { statusCode = Integer.parseInt(status[1].trim()); } catch (NumberFormatException nfe) { throw new IOException("Bad HTTP header line: '" + line0 + "'"); } while (headerLines.hasNext()) { String line = headerLines.next(); int keyEnd = line.indexOf(':'); if (keyEnd < 0) { throw new IOException("Bad header line: '" + line + "'"); } String key = line.substring(0, keyEnd).trim(); String value = line.substring(keyEnd + 1).trim(); headers.put(key, value); } return statusCode; } private static int calculateLength(int body, Map headers) throws IOException { String contentLength = headers.get(LengthHeader); if (contentLength == null) { throw new IOException("No " + LengthHeader); } try { return body + Integer.parseInt(contentLength); } catch (NumberFormatException nfe) { throw new IOException("Bad " + LengthHeader + ": " + contentLength); } } public int getStatusCode() { return mStatusCode; } @Override public Map getHeaders() { return Collections.unmodifiableMap(mHeaders); } public String getHeader(String key) { return mHeaders.get(key); } @Override public InputStream getPayloadStream() { return new ByteArrayInputStream(mBody.array(), mBody.position(), mBody.limit() - mBody.position()); } @Override public ByteBuffer getPayload() { return mBody.duplicate(); } @Override public ByteBuffer getBinaryPayload() { byte[] data = new byte[mBody.remaining()]; mBody.duplicate().get(data); byte[] binary = Base64.decode(data, Base64.DEFAULT); return ByteBuffer.wrap(binary); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Status: ").append(mStatusCode).append(CRLF); for (Map.Entry entry : mHeaders.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append(CRLF); } sb.append(CRLF); Charset charset; try { charset = Charset.forName(getCharset()); } catch (IllegalArgumentException iae) { charset = StandardCharsets.ISO_8859_1; } sb.append(new String(mBody.array(), mBody.position(), mBody.limit() - mBody.position(), charset)); return sb.toString(); } public String getCharset() { String contentType = mHeaders.get(ContentTypeHeader); if (contentType == null) { return null; } int csPos = contentType.indexOf(csIndicator); return csPos < 0 ? null : contentType.substring(csPos + csIndicator.length()).trim(); } private static boolean equals(byte[] b1, int offset, byte[] pattern) { for (int n = 0; n < pattern.length; n++) { if (b1[n + offset] != pattern[n]) { return false; } } return true; } }