aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Sync.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Sync.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/Sync.java54
1 files changed, 48 insertions, 6 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Sync.java b/app/src/main/java/invalid/lena/scrcpy/Sync.java
index 6e37523..b7bb4b5 100644
--- a/app/src/main/java/invalid/lena/scrcpy/Sync.java
+++ b/app/src/main/java/invalid/lena/scrcpy/Sync.java
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.Objects;
// Pure-java adb sync v1 SEND framing. Extracted from Server.push() so
// it can be unit-tested without an AdbStream or any android coupling.
@@ -23,6 +24,7 @@ public final class Sync {
// FAIL responses carry a short human-readable message; cap what a
// corrupt or hostile length field can make us allocate.
private static final int MAX_FAIL_MSG = 4 * 1024;
+ private static final int MAX_PATH_BYTES = 1024;
private Sync() {}
@@ -30,10 +32,22 @@ public final class Sync {
// already-open `out` and `in` of an adb sync stream. Returns the
// total payload byte count.
public static long push(InputStream src, OutputStream out, InputStream in,
- String remotePath, int mode, int mtimeSec)
+ String remotePath, int mode, int mtimeSec)
throws IOException {
+ Objects.requireNonNull(src);
+ Objects.requireNonNull(out);
+ Objects.requireNonNull(in);
+ Objects.requireNonNull(remotePath);
+ if (remotePath.isEmpty() || remotePath.indexOf(',') >= 0
+ || remotePath.indexOf('\0') >= 0) {
+ throw new IllegalArgumentException("invalid sync path");
+ }
+ if (mode < 0) throw new IllegalArgumentException("invalid sync mode");
String header = remotePath + "," + mode;
byte[] hb = header.getBytes(StandardCharsets.UTF_8);
+ if (hb.length > MAX_PATH_BYTES) {
+ throw new IllegalArgumentException("sync path and mode are too long");
+ }
byte[] tag = new byte[8];
putTag(tag, 0, "SEND");
@@ -46,7 +60,8 @@ public final class Sync {
putTag(dataHdr, 0, "DATA");
long total = 0;
int n;
- while ((n = src.read(chunk)) > 0) {
+ while ((n = src.read(chunk)) >= 0) {
+ if (n == 0) throw new IOException("sync source made no progress");
Wire.writeLe32(dataHdr, 4, n);
out.write(dataHdr);
out.write(chunk, 0, n);
@@ -63,16 +78,43 @@ public final class Sync {
Wire.readFully(in, resp);
String code = new String(resp, 0, 4, StandardCharsets.US_ASCII);
int len = Wire.readLe32(resp, 4);
- if ("OKAY".equals(code)) return total;
+ if ("OKAY".equals(code)) {
+ if (len != 0) throw new IOException("sync OKAY length is not zero: " + len);
+ return total;
+ }
+ if (!"FAIL".equals(code)) {
+ throw new IOException("unknown sync response: " + safeCode(code));
+ }
- byte[] msg = new byte[Math.min(Math.max(0, len), MAX_FAIL_MSG)];
+ if (len < 0 || len > MAX_FAIL_MSG) {
+ throw new IOException("sync " + code + " response length out of range: " + len);
+ }
+ byte[] msg = new byte[len];
if (msg.length > 0) Wire.readFully(in, msg);
- throw new IOException("sync " + code + ": "
- + new String(msg, StandardCharsets.UTF_8));
+ throw new IOException("sync FAIL: " + safeMessage(Wire.decodeUtf8(msg)));
}
private static void putTag(byte[] dst, int off, String tag) {
byte[] b = tag.getBytes(StandardCharsets.US_ASCII);
System.arraycopy(b, 0, dst, off, 4);
}
+
+ private static String safeCode(String code) {
+ StringBuilder out = new StringBuilder(code.length());
+ for (int i = 0; i < code.length(); i++) {
+ char c = code.charAt(i);
+ out.append(c >= 0x20 && c <= 0x7e ? c : '?');
+ }
+ return out.toString();
+ }
+
+ private static String safeMessage(String message) {
+ StringBuilder out = new StringBuilder(message.length());
+ for (int i = 0; i < message.length(); i++) {
+ char c = message.charAt(i);
+ int type = Character.getType(c);
+ out.append(Character.isISOControl(c) || type == Character.FORMAT ? '?' : c);
+ }
+ return out.toString();
+ }
}