1 package com.android.hotspot2.utils; 2 3 import android.util.Base64; 4 import android.util.Log; 5 6 import com.android.hotspot2.osu.OSUManager; 7 8 import java.io.ByteArrayInputStream; 9 import java.io.EOFException; 10 import java.io.IOException; 11 import java.io.InputStream; 12 import java.nio.ByteBuffer; 13 import java.nio.charset.Charset; 14 import java.nio.charset.StandardCharsets; 15 import java.util.Arrays; 16 import java.util.Collections; 17 import java.util.Iterator; 18 import java.util.LinkedHashMap; 19 import java.util.Map; 20 21 public class HTTPResponse implements HTTPMessage { 22 private final int mStatusCode; 23 private final Map<String, String> mHeaders = new LinkedHashMap<>(); 24 private final ByteBuffer mBody; 25 26 private static final String csIndicator = "charset="; 27 HTTPResponse(InputStream in)28 public HTTPResponse(InputStream in) throws IOException { 29 int expected = Integer.MAX_VALUE; 30 int offset = 0; 31 int body = -1; 32 byte[] input = new byte[RX_BUFFER]; 33 34 int statusCode = -1; 35 int bodyPattern = 0; 36 37 while (offset < expected) { 38 int amount = in.read(input, offset, input.length - offset); 39 Log.d(OSUManager.TAG, String.format("Reading into %d from %d, amount %d -> %d", 40 input.length, offset, input.length - offset, amount)); 41 if (amount < 0) { 42 throw new EOFException(); 43 } 44 //Log.d("ZXZ", "HTTP response: '" 45 // + new String(input, 0, offset + amount, StandardCharsets.ISO_8859_1)); 46 47 if (body < 0) { 48 for (int n = offset; n < offset + amount; n++) { 49 bodyPattern = (bodyPattern << 8) | (input[n] & 0xff); 50 if (bodyPattern == 0x0d0a0d0a) { 51 body = n + 1; 52 statusCode = parseHeader(input, body, mHeaders); 53 expected = calculateLength(body, mHeaders); 54 if (expected > input.length) { 55 input = Arrays.copyOf(input, expected); 56 } 57 break; 58 } 59 } 60 } 61 offset += amount; 62 if (offset < expected && offset == input.length) { 63 input = Arrays.copyOf(input, input.length * 2); 64 } 65 } 66 mStatusCode = statusCode; 67 mBody = ByteBuffer.wrap(input, body, expected - body); 68 } 69 parseHeader(byte[] input, int body, Map<String, String> headers)70 private static int parseHeader(byte[] input, int body, Map<String, String> headers) 71 throws IOException { 72 String headerText = new String(input, 0, body - BODY_SEPARATOR_LENGTH, 73 StandardCharsets.ISO_8859_1); 74 //System.out.println("Received header: " + headerText); 75 Iterator<String> headerLines = Arrays.asList(headerText.split(CRLF)).iterator(); 76 if (!headerLines.hasNext()) { 77 throw new IOException("Bad HTTP Request"); 78 } 79 80 int statusCode; 81 String line0 = headerLines.next(); 82 String[] status = line0.split(" "); 83 if (status.length != 3 || !"HTTP/1.1".equals(status[0])) { 84 throw new IOException("Bad HTTP Result: " + line0); 85 } 86 try { 87 statusCode = Integer.parseInt(status[1].trim()); 88 } catch (NumberFormatException nfe) { 89 throw new IOException("Bad HTTP header line: '" + line0 + "'"); 90 } 91 92 while (headerLines.hasNext()) { 93 String line = headerLines.next(); 94 int keyEnd = line.indexOf(':'); 95 if (keyEnd < 0) { 96 throw new IOException("Bad header line: '" + line + "'"); 97 } 98 String key = line.substring(0, keyEnd).trim(); 99 String value = line.substring(keyEnd + 1).trim(); 100 headers.put(key, value); 101 } 102 return statusCode; 103 } 104 calculateLength(int body, Map<String, String> headers)105 private static int calculateLength(int body, Map<String, String> headers) throws IOException { 106 String contentLength = headers.get(LengthHeader); 107 if (contentLength == null) { 108 throw new IOException("No " + LengthHeader); 109 } 110 try { 111 return body + Integer.parseInt(contentLength); 112 } catch (NumberFormatException nfe) { 113 throw new IOException("Bad " + LengthHeader + ": " + contentLength); 114 } 115 } 116 getStatusCode()117 public int getStatusCode() { 118 return mStatusCode; 119 } 120 121 @Override getHeaders()122 public Map<String, String> getHeaders() { 123 return Collections.unmodifiableMap(mHeaders); 124 } 125 getHeader(String key)126 public String getHeader(String key) { 127 return mHeaders.get(key); 128 } 129 130 @Override getPayloadStream()131 public InputStream getPayloadStream() { 132 return new ByteArrayInputStream(mBody.array(), mBody.position(), 133 mBody.limit() - mBody.position()); 134 } 135 136 @Override getPayload()137 public ByteBuffer getPayload() { 138 return mBody.duplicate(); 139 } 140 141 @Override getBinaryPayload()142 public ByteBuffer getBinaryPayload() { 143 byte[] data = new byte[mBody.remaining()]; 144 mBody.duplicate().get(data); 145 byte[] binary = Base64.decode(data, Base64.DEFAULT); 146 return ByteBuffer.wrap(binary); 147 } 148 149 @Override toString()150 public String toString() { 151 StringBuilder sb = new StringBuilder(); 152 sb.append("Status: ").append(mStatusCode).append(CRLF); 153 for (Map.Entry<String, String> entry : mHeaders.entrySet()) { 154 sb.append(entry.getKey()).append(": ").append(entry.getValue()).append(CRLF); 155 } 156 sb.append(CRLF); 157 Charset charset; 158 try { 159 charset = Charset.forName(getCharset()); 160 } catch (IllegalArgumentException iae) { 161 charset = StandardCharsets.ISO_8859_1; 162 } 163 sb.append(new String(mBody.array(), mBody.position(), 164 mBody.limit() - mBody.position(), charset)); 165 return sb.toString(); 166 } 167 getCharset()168 public String getCharset() { 169 String contentType = mHeaders.get(ContentTypeHeader); 170 if (contentType == null) { 171 return null; 172 } 173 int csPos = contentType.indexOf(csIndicator); 174 return csPos < 0 ? null : contentType.substring(csPos + csIndicator.length()).trim(); 175 } 176 equals(byte[] b1, int offset, byte[] pattern)177 private static boolean equals(byte[] b1, int offset, byte[] pattern) { 178 for (int n = 0; n < pattern.length; n++) { 179 if (b1[n + offset] != pattern[n]) { 180 return false; 181 } 182 } 183 return true; 184 } 185 } 186