CalDAV now goes through Minamp's own HTTP/TLS client (Bouncy Castle TLS 1.2, Mozilla roots, keep-alive) instead of Apache HttpClient on Android 2.3's TLS 1.0, as the iPod version does with BearSSL. The phone reaches the normal https:// Nextcloud address at home and away; a pinned self-signed server or the old bridge still works. - NetHttp transport; requests run on big-stack threads (the handshake overflows AsyncTask/IntentService stacks on Android 2.x) - http:// -> https:// redirects on the same host are followed; other hosts still refused - bridge wording replaced in Galician and English; README updated - NetTest (TLS + CalDAV discovery through the new transport) replaces BridgeTest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.1 KiB
Java
79 lines
3.1 KiB
Java
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 Minamp):
|
|
* 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<Resp>() {
|
|
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<String, String> h = new LinkedHashMap<String, String>();
|
|
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
|
|
}
|
|
}
|