diff options
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Server.java')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Server.java | 96 |
1 files changed, 68 insertions, 28 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Server.java b/app/src/main/java/invalid/lena/scrcpy/Server.java index 284dd6c..dbc6738 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Server.java +++ b/app/src/main/java/invalid/lena/scrcpy/Server.java @@ -2,16 +2,15 @@ package invalid.lena.scrcpy; import android.content.Context; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; -import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; import io.github.muntashirakon.adb.AdbStream; @@ -43,17 +42,12 @@ public final class Server { public final AdbStream videoAds, audioAds, controlAds; public final InputStream videoIn, audioIn, controlIn; public final OutputStream controlOut; - public final String deviceName; - public final String scid; - public final String version; Streams(AdbStream va, AdbStream aa, AdbStream ca, - InputStream vi, InputStream ai, InputStream ci, OutputStream co, - String name, String scid, String version) { + InputStream vi, InputStream ai, InputStream ci, OutputStream co) { this.videoAds = va; this.audioAds = aa; this.controlAds = ca; this.videoIn = vi; this.audioIn = ai; this.controlIn = ci; this.controlOut = co; - this.deviceName = name; this.scid = scid; this.version = version; } } @@ -101,9 +95,8 @@ public final class Server { InputStream ci = ca.openInputStream(); OutputStream co = ca.openOutputStream(); - String name = readDeviceMeta(vi); - Log.i("device name=%s", name); - streams = new Streams(va, aa, ca, vi, ai, ci, co, name, scid, version); + Log.i("device name=%s", readDeviceMeta(vi)); + streams = new Streams(va, aa, ca, vi, ai, ci, co); committed = true; return streams; } finally { @@ -140,8 +133,11 @@ public final class Server { shellPump = null; closeQuietly(s); if (t != null) { - try { t.join(CLOSE_GRACE_MS); } - catch (InterruptedException ignored) {} + try { + t.join(CLOSE_GRACE_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } if (t.isAlive()) t.interrupt(); } } @@ -158,11 +154,19 @@ public final class Server { private String readVersion() throws IOException { try (InputStream in = ctx.getAssets().open(ASSET_VERSION)) { byte[] buf = new byte[64]; - int n = 0, r; - while ((r = in.read(buf, n, buf.length - n)) > 0) n += r; - String v = new String(buf, 0, n, StandardCharsets.UTF_8).trim(); - if (v.isEmpty()) { - throw new IOException("scrcpy-server.version is empty"); + int n = 0; + while (n < buf.length) { + int r = in.read(buf, n, buf.length - n); + if (r < 0) break; + if (r == 0) throw new IOException("scrcpy-server.version read made no progress"); + n += r; + } + if (n == buf.length && in.read() >= 0) { + throw new IOException("scrcpy-server.version is too long"); + } + String v = Wire.decodeUtf8(buf, 0, n).trim(); + if (!v.matches("[0-9]+(\\.[0-9]+)*")) { + throw new IOException("scrcpy-server.version is invalid"); } return v; } @@ -171,7 +175,7 @@ public final class Server { private static String newScid() { // 31-bit random, 8 lowercase hex chars - matches scrcpy upstream client. int v = new SecureRandom().nextInt() & 0x7fffffff; - return String.format("%08x", v); + return String.format(Locale.ROOT, "%08x", v); } private String buildCmdline(String version, String scid) { @@ -196,7 +200,9 @@ public final class Server { args.add("max_size=" + maxSize); args.add("video_bit_rate=" + videoBitR); if (maxFps > 0) args.add("max_fps=" + maxFps); - args.add("clipboard_autosync=true"); + // Off means off at the source: the server never sends the + // target's clipboard, rather than us receiving and discarding it. + args.add("clipboard_autosync=" + Settings.clipboardSync(ctx)); args.add("tunnel_forward=true"); args.add("cleanup=true"); args.add("power_on=true"); @@ -205,9 +211,9 @@ public final class Server { private AdbStream openAbstract(String scid) throws Exception { String name = "scrcpy_" + scid; - long deadline = System.currentTimeMillis() + LISTENER_DEADLINE_MS; + long deadline = monotonicMs() + LISTENER_DEADLINE_MS; ConnectException last = null; - while (System.currentTimeMillis() < deadline) { + while (monotonicMs() < deadline) { if (serverEof) { throw new IOException("server exited before opening " + name, last); } @@ -230,18 +236,27 @@ public final class Server { private static String readDeviceMeta(InputStream in) throws IOException { byte[] probe = new byte[1]; Wire.readFully(in, probe); + if (probe[0] != 0) throw new IOException("invalid scrcpy probe byte"); byte[] name = new byte[64]; Wire.readFully(in, name); int n = 0; while (n < name.length && name[n] != 0) n++; - return new String(name, 0, n, StandardCharsets.UTF_8); + for (int i = n; i < name.length; i++) { + if (name[i] != 0) throw new IOException("invalid device-name padding"); + } + return safeLogText(Wire.decodeUtf8(name, 0, n)); } private void pump(InputStream in) { - try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - Log.i("server: %s", line); + // Do not use BufferedReader.readLine(): a hostile target can emit an + // unterminated line of arbitrary size and make it allocate until OOM. + byte[] bytes = new byte[2048]; + try (InputStream source = in) { + int n; + while ((n = source.read(bytes)) >= 0) { + if (n == 0) throw new IOException("server stdout made no progress"); + String chunk = safeLogBytes(bytes, n).trim(); + if (!chunk.isEmpty()) Log.i("server: %s", chunk); } } catch (IOException e) { if (!Thread.currentThread().isInterrupted()) Log.w("server-stdout closed: %s", e); @@ -266,4 +281,29 @@ public final class Server { try { sync.close(); } catch (IOException ignored) {} } } + + private static String safeLogBytes(byte[] data, int len) { + StringBuilder out = new StringBuilder(len); + for (int i = 0; i < len; i++) { + int b = data[i] & 0xff; + if (b == '\n' || b == '\r' || b == '\t') out.append(' '); + else if (b >= 0x20 && b <= 0x7e) out.append((char) b); + else out.append('.'); + } + return out.toString(); + } + + private static String safeLogText(String text) { + StringBuilder out = new StringBuilder(text.length()); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + int type = Character.getType(c); + out.append(Character.isISOControl(c) || type == Character.FORMAT ? '?' : c); + } + return out.toString(); + } + + private static long monotonicMs() { + return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + } } |