package net.geedge.api.controller; import cn.hutool.core.codec.Base32Codec; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.log.Log; import jakarta.servlet.http.HttpServletResponse; import net.geedge.api.entity.EnvApiYml; import net.geedge.api.util.AdbUtil; import net.geedge.common.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/v1/env") public class APIController { private final static Log log = Log.get(); private final AdbUtil adbUtil; @Autowired public APIController(EnvApiYml envApiYml) { this.adbUtil = AdbUtil.getInstance(envApiYml.getAdb()); } @GetMapping("/status") public R status() { return R.ok(adbUtil.status()); } @PostMapping("/file") public R push(@RequestParam(value = "file") MultipartFile file, @RequestParam String path) throws IOException { File tempFile = null; try { tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); file.transferTo(tempFile); AdbUtil.CommandResult result = adbUtil.push(tempFile.getAbsolutePath(), path); if (0 != result.exitCode()) { return R.error(result.output()); } } finally { T.FileUtil.del(tempFile); } return R.ok(); } @GetMapping("/file/{fileId}") public void pull(@PathVariable String fileId, HttpServletResponse response) throws IOException { byte[] decode = Base32Codec.Base32Decoder.DECODER.decode(fileId); String filePath = T.StrUtil.str(decode, T.CharsetUtil.CHARSET_UTF_8); String fileName = T.FileUtil.getName(filePath); File tempFile = T.FileUtil.file(Constant.TEMP_PATH, fileName); try { AdbUtil.CommandResult result = adbUtil.pull(filePath, tempFile.getAbsolutePath()); if (0 != result.exitCode()) { throw new APIException(result.output()); } if (T.FileUtil.isDirectory(tempFile)) { File zip = T.ZipUtil.zip(tempFile); try { T.ResponseUtil.downloadFile(response, zip.getName(), T.FileUtil.readBytes(zip)); } finally { T.FileUtil.del(zip); } } else { T.ResponseUtil.downloadFile(response, fileName, T.FileUtil.readBytes(tempFile)); } } finally { T.FileUtil.del(tempFile); } } @GetMapping("/file") public R listDir(@RequestParam(defaultValue = "/") String path) { List listDir = adbUtil.listDir(path); Map data = T.MapUtil.builder() .put("path", path) .put("records", listDir) .build(); return R.ok(data); } @GetMapping("/app") public R listApp(@RequestParam(required = false) String arg) { return R.ok().putData("records", adbUtil.listApp(arg)); } @PostMapping("/app") public R install(@RequestParam(value = "file", required = false) MultipartFile file, @RequestParam(required = false) String path) throws IOException { if (file != null) { File tempFile = null; try { tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); file.transferTo(tempFile); AdbUtil.CommandResult result = adbUtil.install(tempFile.getAbsolutePath(), true, true); if (0 != result.exitCode()) { throw new APIException(result.output()); } return R.ok(); } finally { T.FileUtil.del(tempFile); } } if (T.StrUtil.isNotEmpty(path)) { AdbUtil.CommandResult result = adbUtil.install(path, true, true); if (0 != result.exitCode()) { throw new APIException(result.output()); } return R.ok(); } return R.error(RCode.BAD_REQUEST); } @DeleteMapping("/app") public R uninstall(@RequestParam String packageName) { AdbUtil.CommandResult result = adbUtil.uninstall(packageName); if (0 != result.exitCode()) { throw new APIException(result.output()); } return R.ok(); } @GetMapping("/pcap") public R listTcpdump() { return R.ok().putData("records", adbUtil.listTcpdump()); } @PostMapping("/pcap") public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) { AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName); if (0 != result.exitCode()) { throw new APIException("exec tcpdump error"); } return R.ok().putData("id", result.output()); } @DeleteMapping("/pcap") public synchronized void stopTcpdump(@RequestParam String id, @RequestParam(required = false, defaultValue = "false") Boolean returnFile, HttpServletResponse response) throws IOException { AdbUtil.CommandResult result = adbUtil.stopTcpdump(id); if (0 != result.exitCode()) { throw new APIException(result.output()); } String filePath = result.output(); try { if (returnFile) { // response pcap file File tempFile = T.FileUtil.file(Constant.TEMP_PATH, id + ".pcap"); try { if (T.StrUtil.isEmpty(filePath)) { throw new APIException(RCode.NOT_EXISTS); } AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath()); if (0 != pulled.exitCode()) { throw new APIException(pulled.output()); } T.ResponseUtil.downloadFile(response, tempFile.getName(), T.FileUtil.readBytes(tempFile)); } finally { T.FileUtil.del(tempFile); } } else { // response taskid response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData("id", id))); } } finally { if (T.StrUtil.isNotEmpty(filePath)) { // remove pcap file adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath)); } } } @PostMapping("/shell") public R execShellCmd(@RequestBody Map requestBody) { String cmd = T.MapUtil.getStr(requestBody, "cmd", ""); if (T.StrUtil.isEmpty(cmd)) { return R.error(RCode.BAD_REQUEST); } Integer timeout = T.MapUtil.getInt(requestBody, "timeout", 10); return R.ok().putData("result", adbUtil.execShellCommand(cmd, timeout)); } @GetMapping("/acl") public R listAcl() { return R.ok().putData("records", adbUtil.listAcl()); } @PostMapping("/acl") public R addAcl(@RequestBody Map requestBody) { String ip = T.MapUtil.getStr(requestBody, "ip"); String port = T.MapUtil.getStr(requestBody, "port"); if (T.StrUtil.isAllEmpty(ip, port)) { return R.error(RCode.BAD_REQUEST); } String protocol = T.MapUtil.getStr(requestBody, "protocol", "all"); if (!T.StrUtil.equalsAny(protocol, "tcp", "udp", "all")) { return R.error(RCode.BAD_REQUEST); } if ("all".equals(protocol) && T.StrUtil.isEmpty(ip)) { return R.error(RCode.BAD_REQUEST); } adbUtil.addAcl(protocol, ip, port); return R.ok().putData("records", adbUtil.listAcl()); } @DeleteMapping("/acl") public R deleteAcl(@RequestBody Map requestBody) { String ip = T.MapUtil.getStr(requestBody, "ip"); String port = T.MapUtil.getStr(requestBody, "port"); if (T.StrUtil.isAllEmpty(ip, port)) { return R.error(RCode.BAD_REQUEST); } String protocol = T.MapUtil.getStr(requestBody, "protocol", "all"); if (!T.StrUtil.equalsAny(protocol, "tcp", "udp", "all")) { return R.error(RCode.BAD_REQUEST); } if ("all".equals(protocol) && T.StrUtil.isEmpty(ip)) { return R.error(RCode.BAD_REQUEST); } adbUtil.deleteAcl(protocol, ip, port); return R.ok().putData("records", adbUtil.listAcl()); } @DeleteMapping("/acl/flush") public R flushAcl() { AdbUtil.CommandResult result = adbUtil.flushAcl(); if (0 != result.exitCode()) { return R.error(result.output()); } return R.ok(); } @PostMapping("/playbook") public R execPlaybook(@RequestParam("files") MultipartFile[] files, @RequestParam("packageName") String packageName) throws IOException { // save zip and apk File appFile = null; File playbookFile = null; try { for (MultipartFile file : files) { if (T.FileUtil.extName(file.getOriginalFilename()).equals("zip")) { playbookFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); file.transferTo(playbookFile); T.ZipUtil.unzip(playbookFile, Constant.PLAYBOOK_AIR_PATH); } else { appFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); file.transferTo(appFile); } } } finally { T.FileUtil.del(playbookFile); } String tid = T.StrUtil.uuid(); PlaybookRunnable playbookRunnable = new PlaybookRunnable(adbUtil, appFile, Constant.PLAYBOOK_AIR_PATH, tid, packageName); ThreadUtil.execAsync(playbookRunnable); return R.ok().putData("tid", tid); } @GetMapping("/playbook/{id}") public void getExecPlaybookResult( @PathVariable("id") String id, HttpServletResponse response) throws IOException { if (T.StrUtil.isEmpty(id)) { throw new APIException(RCode.BAD_REQUEST); } File tempFile = null; try { Map result = Constant.PLAYBOOK_RUN_RESULT.get(id); if (result != null) { if (T.MapUtil.getStr(result, "status").equals("done")) { String artifact = T.MapUtil.getStr(result, "artifact"); tempFile = T.FileUtil.file(artifact); T.ResponseUtil.downloadFile(response, tempFile.getName(), T.FileUtil.readBytes(tempFile)); } else { response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData(result))); } Constant.PLAYBOOK_RUN_RESULT.remove(id); } else { throw new APIException(RCode.BAD_REQUEST); } }finally { T.FileUtil.del(tempFile); } } public class PlaybookRunnable extends Thread { private AdbUtil adbUtil; private String tid; private File apkFile; private String packageName; private File playbookDir; public PlaybookRunnable(AdbUtil adbUtil, File apkFile, File playbookDir, String tid, String packageName) { this.adbUtil = adbUtil; this.tid = tid; this.apkFile = apkFile; this.packageName = packageName; this.playbookDir = playbookDir; } @Override public void run() { try { Map resultMap = T.MapUtil.builder() .put("status", "running") .build(); Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); // 1. install apk AdbUtil.CommandResult install = adbUtil.install(apkFile.getAbsolutePath(), true, true); if (0 != install.exitCode()) { throw new APIException(install.output()); } // 2. star tcpdump AdbUtil.CommandResult startTcpdump = adbUtil.startTcpdump(packageName); if (0 != startTcpdump.exitCode()) { throw new APIException("exec tcpdump error"); } // 3. exec playbook AdbUtil.CommandResult execResult = adbUtil.execPlaybook(playbookDir.getPath()); if (0 != execResult.exitCode()) { // exec playbook error, stop tcpdump and delete pcap AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output()); adbUtil.execShellCommand(String.format("shell rm -rf %s", stopTcpdump.output())); throw new APIException("exec playbook error"); } // 4. stop tcpdump AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output()); if (0 != stopTcpdump.exitCode()) { throw new APIException(stopTcpdump.output()); } // 5. pull pcap file String filePath = stopTcpdump.output(); File localPcapFile = T.FileUtil.file(Constant.TEMP_PATH, startTcpdump.output() + ".pcap"); if (T.StrUtil.isEmpty(filePath)) { throw new APIException(RCode.NOT_EXISTS); } AdbUtil.CommandResult pull = adbUtil.pull(filePath, localPcapFile.getAbsolutePath()); if (0 != pull.exitCode()) { throw new APIException(pull.output()); } // 6. delete android pcap adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath)); resultMap = T.MapUtil.builder() .put("status", "done") .put("artifact", localPcapFile.getAbsolutePath()) .build(); Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); }catch (Exception e) { log.error(e); Map resultMap = T.MapUtil.builder() .put("status", "error") .build(); Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); } finally { T.FileUtil.del(apkFile); T.FileUtil.clean(playbookDir); } } } }