package org.gingercal; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import org.gingercal.net.CertVerifier; /** * Android 2.3 transport through the bundled TLS 1.2 client (org.gingercal.net, from Plexamp Lite): * the phone's own TLS 1.0 can't reach Nextcloud, so this talks to the normal https:// address * directly, any method (PROPFIND/REPORT included), with keep-alive. A pinned certificate (a * self-signed server or an old bridge) is still honoured. */ public class NetHttp implements Http { /** AsyncTask and IntentService threads have Android 2.x's small default stack, which the * TLS handshake overflows: requests run here, on big-stack threads, and the caller waits. */ private static final ExecutorService NET = org.gingercal.net.Threads.pool("net", 3); final String pin; final int timeoutMs; public NetHttp(String pin, int timeoutMs) { this.pin = pin == null ? "" : pin; this.timeoutMs = timeoutMs; } public Resp exec(final String method, final String url, final String[] headers, final String body, final String contentType) throws IOException { try { return NET.submit(new Callable() { public Resp call() throws IOException { return execHere(method, url, headers, body, contentType); } }).get(); } catch (ExecutionException e) { Throwable c = e.getCause(); if (c instanceof IOException) throw (IOException) c; if (c instanceof RuntimeException) throw (RuntimeException) c; if (c instanceof Error) throw (Error) c; throw new IOException(String.valueOf(c)); } catch (InterruptedException e) { throw new IOException(L.t("Interrupted")); } } Resp execHere(String method, String url, String[] headers, String body, String contentType) throws IOException { CertVerifier.setPin(pin); Map h = new LinkedHashMap(); for (int i = 0; i + 1 < headers.length; i += 2) h.put(headers[i], headers[i + 1]); byte[] data = null; if (body != null) { data = body.getBytes("UTF-8"); if (contentType != null) h.put("Content-Type", contentType); } org.gingercal.net.Http.Response r; try { // one hop at a time: Dav decides which redirects are safe to follow r = org.gingercal.net.Http.requestNoRedirect(method, url, h, data, timeoutMs); } catch (CertVerifier.Untrusted u) { throw new DavException.UntrustedCert(u.fingerprint, u.subject, u.issuer, u.expires, u.changed); } Resp out = new Resp(); out.code = r.code; out.etag = r.header("etag"); out.location = r.header("location"); out.body = r.readString(); return out; } public void close() { // pooled connections are shared and expire by themselves } }