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