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