package org.fdroid.fdroid.net.bluetooth.httpish; import android.util.Log; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.net.bluetooth.BluetoothConnection; import org.fdroid.fdroid.net.bluetooth.FileDetails; import org.fdroid.fdroid.net.bluetooth.httpish.headers.Header; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.HashMap; import java.util.Locale; import java.util.Map; public class Response { private static final String TAG = "bluetooth.Response"; private final int statusCode; private final Map<String, String> headers; private final InputStream contentStream; public Response(int statusCode, Map<String, String> headers) { this(statusCode, headers, null); } /** * This class expects 'contentStream' to be open, and ready for use. * It will not close it either. However it will block wile doing things * so you can call a method, wait for it to finish, and then close * it afterwards if you like. */ public Response(int statusCode, Map<String, String> headers, InputStream contentStream) { this.statusCode = statusCode; this.headers = headers; this.contentStream = contentStream; } public Response(int statusCode, String mimeType, String content) { this.statusCode = statusCode; this.headers = new HashMap<>(); this.headers.put("Content-Type", mimeType); try { this.contentStream = new ByteArrayInputStream(content.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { // Not quite sure what to do in the case of a phone not supporting UTF-8, so lets // throw a runtime exception and hope that we get good bug reports if this ever happens. Log.e(TAG, "Device does not support UTF-8", e); throw new IllegalStateException("Device does not support UTF-8.", e); } } public Response(int statusCode, String mimeType, InputStream contentStream) { this.statusCode = statusCode; this.headers = new HashMap<>(); this.headers.put("Content-Type", mimeType); this.contentStream = contentStream; } public void addHeader(String key, String value) { headers.put(key, value); } public int getStatusCode() { return statusCode; } public int getFileSize() { if (headers != null) { for (Map.Entry<String, String> entry : headers.entrySet()) { if ("content-length".equals(entry.getKey().toLowerCase(Locale.ENGLISH))) { try { return Integer.parseInt(entry.getValue()); } catch (NumberFormatException e) { return -1; } } } } return -1; } /** * Extracts meaningful headers from the response into a more useful and safe * {@link org.fdroid.fdroid.net.bluetooth.FileDetails} object. */ public FileDetails toFileDetails() { FileDetails details = new FileDetails(); for (Map.Entry<String, String> entry : headers.entrySet()) { Header.process(details, entry.getKey(), entry.getValue()); } return details; } public InputStream toContentStream() throws UnsupportedOperationException { if (contentStream == null) { throw new UnsupportedOperationException("This kind of response doesn't have a content stream. Did you perform a HEAD request instead of a GET request?"); } return contentStream; } public void send(BluetoothConnection connection) throws IOException { Utils.debugLog(TAG, "Sending Bluetooth HTTP-ish response..."); Writer output = new OutputStreamWriter(connection.getOutputStream()); output.write("HTTP(ish)/0.1 200 OK\n"); for (Map.Entry<String, String> entry : headers.entrySet()) { output.write(entry.getKey()); output.write(": "); output.write(entry.getValue()); output.write("\n"); } output.write("\n"); output.flush(); if (contentStream != null) { Utils.copy(contentStream, connection.getOutputStream()); } output.flush(); } public static class Builder { private InputStream contentStream; private int statusCode = 200; private int fileSize = -1; private String etag; public Builder() { } public Builder(InputStream contentStream) { this.contentStream = contentStream; } public Builder setStatusCode(int statusCode) { this.statusCode = statusCode; return this; } public Builder setFileSize(int fileSize) { this.fileSize = fileSize; return this; } public Builder setETag(String etag) { this.etag = etag; return this; } public Response build() { Map<String, String> headers = new HashMap<>(3); if (fileSize > 0) { headers.put("Content-Length", Integer.toString(fileSize)); } if (etag != null) { headers.put("ETag", etag); } return new Response(statusCode, headers, contentStream); } } }