|
| 1 | +/* |
| 2 | + * ============================================================================ |
| 3 | + * Name : AdbShellCommand.java |
| 4 | + * Author : IIAB Project |
| 5 | + * Copyright : Copyright (c) 2026 IIAB Project |
| 6 | + * Description : Domain rule: is a string safe to run as a single ADB "shell:" |
| 7 | + * command, i.e. it cannot chain/substitute extra commands. |
| 8 | + * Closes tech-debt item S4 (arbitrary on-device shell). |
| 9 | + * ============================================================================ |
| 10 | + */ |
| 11 | +package org.iiab.controller.adb.domain; |
| 12 | + |
| 13 | +/** |
| 14 | + * Pure (framework-free) guard for strings passed to |
| 15 | + * {@code IIABAdbManager.executeCommand}, which runs them as |
| 16 | + * {@code openStream("shell:" + command)} — an on-device shell over ADB. |
| 17 | + * |
| 18 | + * <p>Today the callers pass fixed, app-controlled commands, but the method |
| 19 | + * accepts any string; a value containing shell metacharacters ({@code ; | & $} |
| 20 | + * {@code ( ) < >}, backticks, quotes, newlines) could chain or substitute extra |
| 21 | + * commands run with ADB privileges — tech-debt item <b>S4</b>. |
| 22 | + * |
| 23 | + * <p>This rejects those metacharacters so only a single, self-contained command |
| 24 | + * (e.g. {@code settings put global <key> <value>}) can run. It does not try to |
| 25 | + * be a full shell parser; it fails closed on anything that could break out. |
| 26 | + * No {@code android.*}, so it is unit-testable on a plain JVM. |
| 27 | + */ |
| 28 | +public final class AdbShellCommand { |
| 29 | + |
| 30 | + private AdbShellCommand() { |
| 31 | + // Static utility; not instantiable. |
| 32 | + } |
| 33 | + |
| 34 | + /** |
| 35 | + * True if {@code command} is safe to run as a single ADB shell command: |
| 36 | + * non-empty and free of shell control/substitution/redirection/quote |
| 37 | + * characters and control characters. Normal tokens (letters, digits, spaces, |
| 38 | + * and {@code / . - _ = : ,}) are allowed. |
| 39 | + */ |
| 40 | + public static boolean isSafe(String command) { |
| 41 | + if (command == null || command.trim().isEmpty()) { |
| 42 | + return false; |
| 43 | + } |
| 44 | + for (int i = 0; i < command.length(); i++) { |
| 45 | + char c = command.charAt(i); |
| 46 | + switch (c) { |
| 47 | + case ';': // command separator |
| 48 | + case '&': // background / && |
| 49 | + case '|': // pipe / || |
| 50 | + case '$': // variable / $( ) substitution |
| 51 | + case '`': // backtick substitution |
| 52 | + case '(': |
| 53 | + case ')': |
| 54 | + case '<': // redirection |
| 55 | + case '>': |
| 56 | + case '\'': // quoting |
| 57 | + case '"': |
| 58 | + case '\\': // escaping |
| 59 | + case '\n': |
| 60 | + case '\r': |
| 61 | + case '\0': |
| 62 | + return false; |
| 63 | + default: |
| 64 | + if (c < 0x20) { // other control characters |
| 65 | + return false; |
| 66 | + } |
| 67 | + } |
| 68 | + } |
| 69 | + return true; |
| 70 | + } |
| 71 | +} |
0 commit comments