Commit 82f8f5d4 authored by Administrator's avatar Administrator

剥离RatelExtension代码

parents
Pipeline #1339 canceled with stages
/build
.gradle/
.idea/
*.iml
local.properties
\ No newline at end of file
# RatelExtension
这是Ratel(平头哥)的RatelExtension代码模块,剥离自Ratel整体工程,以git父子module进行关联。
剥离RatelExtension工程的目的是使得用户可以通过代码使用RatelEngine已经支持,但是RatelExtension还没有发布到jar包的功能。
由于RatelExtension是一个可选模块,使用compile作用域引入,所以如果release版本中的代码存在bug,你可以直接通过修改这个工程的代码来fix
# 外部工程通过git submodule依赖本项目的方法
1. 创建子module,注意path需要提供``二级``目录
```
git submodule add git@git.virjar.com:ratel/ratelextension.git base-lib-ratel-extension/ratelextension
```
此时工程将会初始化到``base-lib-ratel-extension/ratelextension``下面
2. 在settings.gradle里面配置该子项目 ``':base-lib-ratel-extension'``,请注意不要带上``/ratelextension``,否则将会使用ratelextension下面的build.gradle文件。
这是由于ratel-api自带的编译规则可能有他的独立性。
3.``base-lib-ratel-extension``目录下创建``build.gradle``并写入如下内容
```
apply plugin: 'java-library'
sourceSets {
main {
resources {
srcDir 'ratel-api/src/main/resources'
}
java {
srcDir 'ratel-api/src/main/java'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.google.android:android:4.1.1.4'
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
compileOnly 'com.android.support:support-annotations:28.0.0'
}
```
4. 在你的工程中添加本项目依赖
dependencies {
api ':base-lib-ratel-extension'
}
repositories {
repositories {
maven {
name "aliyunmaven"
url "https://maven.aliyun.com/repository/public"
}
maven {
name "aliyunGoogle"
url "https://maven.aliyun.com/repository/google"
}
maven {
name "contralSnapshot"
url "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
}
apply plugin: 'java-library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.google.android:android:4.1.1.4'
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
compileOnly 'com.android.support:support-annotations:28.0.0'
compileOnly 'com.virjar:ratel-api:1.3.2'
compileOnly 'com.virjar:sekiro-api:1.0.1'
}
def JDK_VERSION = 1.7
sourceCompatibility = JDK_VERSION
targetCompatibility = JDK_VERSION
group 'com.virjar'
version '1.0.5-SNAPSHOT'
compileJava {
sourceCompatibility = JDK_VERSION
targetCompatibility = JDK_VERSION
[compileJava]*.options*.encoding = 'UTF-8'
}
compileTestJava {
sourceCompatibility = JDK_VERSION
targetCompatibility = JDK_VERSION
[compileTestJava]*.options*.encoding = 'UTF-8'
}
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allJava
}
// 生成 javadoc jar
task javadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc.destinationDir
}
// javadoc 配置,这里是自定义了 java doc 的一些配置
javadoc {
description = "Generates project-level javadoc for use in -javadoc jar"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.author = true
options.version = true
options.header = project.name
options.addStringOption('Xdoclint:none', '-quiet')
// suppress warnings due to cross-module @see and @link references;
// note that global 'api' task does display all warnings.
logging.captureStandardError LogLevel.INFO
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
options.encoding = "UTF-8"
options.charSet = 'UTF-8'
}
def getRepositoryUsername() {
return hasProperty('MAVEN_USERNAME') ? MAVEN_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('MAVEN_PASSWORD') ? MAVEN_PASSWORD : ""
}
publishing {
publications {
mavenJava(MavenPublication) {
groupId project.group
artifactId 'ratel-extersion'
version "${version}"
from components.java
artifact sourcesJar
artifact javadocJar
// https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html
pom {
name = "Ratel API"
description = "a extension for ratel framework, support more reverse engineering tools based on ratel"
url = "https://git.virjar.com:ratel/ratel"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
id = "weijia.deng"
name = "dengweijia"
email = "virjar@virjar.com"
}
}
scm {
connection = "scm:git@git.virjar.com:ratel/ratel.git"
developerConnection = "scm:git@git.virjar.com:ratel/ratel.git"
url = "http://git.virjar.com:ratel/ratel"
}
}
}
}
repositories {
// releases 仓库
maven {
// 在对 task 中会生成对应的名称 publishMavenJavaPublicationToxxx
// 后面的 xxx 就是你这里的名称,表示你要把jar 上传到这个仓库中
name 'sonatypeRepository' // 为你这个仓库起名
url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
credentials {
username = getRepositoryUsername() // 之前在 sonatype 注册的账户名
password = getRepositoryPassword() // 对应的密码
}
}
// snapshots 仓库
maven {
name = 'sonatypeSnapshotRepository'
url = 'https://oss.sonatype.org/content/repositories/snapshots/'
credentials {
username = getRepositoryUsername()
password = getRepositoryPassword()
}
}
}
}
// 签名配置,注意这里的顺序,今天第一次知道 gradle 中的 task 等配置也是有顺序的
// 必须在 publishing 配置之后
signing {
sign publishing.publications.mavenJava
}
\ No newline at end of file
#Sat Feb 08 17:06:46 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
This diff is collapsed.
package com.virjar.ratel.api.extension.socketmonitor;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamWrapper extends InputStream {
private InputStream delegate;
private MonitorMode monitorMode;
private static ThreadLocal<Object> reentryFlag = new ThreadLocal<>();
@Override
public int read() throws IOException {
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
int data = delegate.read();
if (reEntry) {
return data;
}
if (data > 0) {
monitorMode.inputWrite(data);
}
return data;
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public InputStreamWrapper(InputStream delegate, MonitorMode monitorMode) {
this.delegate = delegate;
this.monitorMode = monitorMode;
}
@Override
public int read(byte[] bytes) throws IOException {
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
int readSize = delegate.read(bytes);
if (reEntry) {
return readSize;
}
if (readSize > 0) {
monitorMode.inputWrite(bytes, 0, readSize);
}
return readSize;
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
@Override
public int read(byte[] bytes, int off, int len) throws IOException {
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
int readSize = delegate.read(bytes, off, len);
if (reEntry) {
return readSize;
}
if (readSize > 0) {
monitorMode.inputWrite(bytes, off, readSize);
}
return readSize;
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
@Override
public long skip(long l) throws IOException {
return delegate.skip(l);
}
@Override
public int available() throws IOException {
return delegate.available();
}
@Override
public void close() throws IOException {
delegate.close();
monitorMode.close();
}
@Override
public synchronized void mark(int i) {
delegate.mark(i);
}
@Override
public synchronized void reset() throws IOException {
delegate.reset();
}
@Override
public boolean markSupported() {
return delegate.markSupported();
}
}
package com.virjar.ratel.api.extension.socketmonitor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class InputStreamWrapperInvocationHandler implements InvocationHandler {
private InputStream delegate;
private MonitorMode monitorMode;
private static Map<Method, Method> methodMapping = new ConcurrentHashMap<>();
private static ThreadLocal<Object> reentryFlag = new ThreadLocal<>();
InputStreamWrapperInvocationHandler(InputStream delegate, MonitorMode monitorMode) {
this.delegate = delegate;
this.monitorMode = monitorMode;
}
private synchronized void genCache(Method fromMethod) {
for (Method thisMethod : this.getClass().getDeclaredMethods()) {
if (MethodUtils.isMethodSame(thisMethod, fromMethod)) {
methodMapping.put(fromMethod, thisMethod);
return;
}
}
methodMapping.put(fromMethod, null);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if (!methodMapping.containsKey(method)) {
genCache(method);
}
Method thisMethod = methodMapping.get(method);
if (thisMethod == null) {
return method.invoke(delegate, objects);
}
monitorMode.makeSureReadTrace();
return thisMethod.invoke(this, objects);
}
public int read() throws IOException {
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
int data = delegate.read();
if (reEntry) {
return data;
}
if (data > 0) {
monitorMode.inputWrite(data);
}
return data;
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public int read(byte[] bytes) throws IOException {
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
int readSize = delegate.read(bytes);
if (reEntry) {
return readSize;
}
if (readSize > 0) {
monitorMode.inputWrite(bytes, 0, readSize);
}
return readSize;
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public int read(byte[] bytes, int off, int len) throws IOException {
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
int readSize = delegate.read(bytes, off, len);
if (reEntry) {
return readSize;
}
if (readSize > 0) {
monitorMode.inputWrite(bytes, off, readSize);
}
return readSize;
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public void close() throws IOException {
delegate.close();
monitorMode.getInputStreamData().close();
monitorMode.close();
}
//
// public long skip(long l) throws IOException {
// return delegate.skip(l);
// }
//
//
// public int available() throws IOException {
// return delegate.available();
// }
//
//
//
// public synchronized void mark(int i) {
// delegate.mark(i);
// }
//
// public synchronized void reset() throws IOException {
// delegate.reset();
// }
//
// public boolean markSupported() {
// return delegate.markSupported();
// }
}
package com.virjar.ratel.api.extension.socketmonitor;
import java.lang.reflect.Method;
public class MethodUtils {
public static boolean isMethodSame(Method method1, Method method2) {
if (!method1.getName().equals(method2.getName())) {
return false;
}
if (!method1.getReturnType().equals(method2.getReturnType())) {
return false;
}
return getParametersString(method1.getParameterTypes()).equals(getParametersString(method2.getParameterTypes()));
}
private static String getParametersString(Class<?>... clazzes) {
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Class<?> clazz : clazzes) {
if (first)
first = false;
else
sb.append(",");
if (clazz != null)
sb.append(clazz.getCanonicalName());
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}
}
package com.virjar.ratel.api.extension.socketmonitor;
import com.virjar.ratel.api.extension.socketmonitor.observer.SocketDataObserver;
import com.virjar.ratel.api.extension.socketmonitor.protocol.AbstractProtocol;
import com.virjar.ratel.api.extension.socketmonitor.protocol.UnknownProtocol;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import external.org.apache.commons.io.input.ClosedInputStream;
import external.org.apache.commons.io.output.ByteArrayOutputStream;
public class MonitorMode {
//被监控的socket
private Socket socket;
private ByteArrayOutputStream inputStreamData = new ByteArrayOutputStream();
private ByteArrayOutputStream outputSteamData = new ByteArrayOutputStream();
private Throwable readStacktrace;
private Throwable writeStacktrace;
private boolean closed = false;
private AbstractProtocol abstractProtocol = new UnknownProtocol(this);
public MonitorMode(Socket socket) {
this.socket = socket;
}
public ByteArrayOutputStream getInputStreamData() {
return inputStreamData;
}
public ByteArrayOutputStream getOutputSteamData() {
return outputSteamData;
}
public void makeSureReadTrace() {
if (readStacktrace == null) {
readStacktrace = new Throwable();
}
}
public void makeSureWriteTrace() {
if (writeStacktrace == null) {
writeStacktrace = new Throwable();
}
}
public void close() {
MonitorModeRegistry.destroy(this);
closed = true;
check();
}
public Socket getSocket() {
return socket;
}
public void outputWrite(int i) {
makeSureWriteTrace();
outputSteamData.write(i);
check();
}
public void outputWrite(byte[] bytes) throws IOException {
makeSureWriteTrace();
outputSteamData.write(bytes);
check();
}
public void outputWrite(byte[] bytes, int i, int i1) {
makeSureWriteTrace();
outputSteamData.write(bytes, i, i1);
check();
}
public void inputWrite(int i) {
makeSureReadTrace();
inputStreamData.write(i);
check();
}
public void inputWrite(byte[] bytes) throws IOException {
makeSureReadTrace();
inputStreamData.write(bytes);
check();
}
public void inputWrite(byte[] bytes, int i, int i1) {
makeSureReadTrace();
inputStreamData.write(bytes, i, i1);
check();
}
private void check() {
if (!abstractProtocol.hasData()) {
return;
}
abstractProtocol = abstractProtocol.process();
if (abstractProtocol.needPrint()) {
InputStream inputStream = abstractProtocol.finalInputStream();
InputStream outputStream = abstractProtocol.finalOutputStream();
if (outputStream != null && !(outputStream instanceof ClosedInputStream)) {
SocketDataObserver.DataModel dataModel = new SocketDataObserver.DataModel();
dataModel.data = outputStream;
dataModel.in = false;
dataModel.socket = socket;
makeSureWriteTrace();
dataModel.stackTrace = writeStacktrace;
SocketMonitor.callDataObserver(dataModel);
}
if (inputStream != null && !(inputStream instanceof ClosedInputStream)) {
SocketDataObserver.DataModel dataModel = new SocketDataObserver.DataModel();
dataModel.data = inputStream;
dataModel.in = true;
dataModel.socket = socket;
makeSureReadTrace();
dataModel.stackTrace = readStacktrace;
SocketMonitor.callDataObserver(dataModel);
}
//resetStatus();
}
}
//连接复用场景下,报文业务变更,但是连接不中断。我们需要记录为下一个event
public void resetStatus() {
readStacktrace = null;
writeStacktrace = null;
//考虑tcp 粘包风险,这里让handler端负责buffer重置
//inputStreamData = new ByteArrayOutputStream();
//outputSteamData = new ByteArrayOutputStream();
abstractProtocol = new UnknownProtocol(this);
}
public boolean isClosed() {
return closed;
}
}
package com.virjar.ratel.api.extension.socketmonitor;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
class MonitorModeRegistry {
private static Map<Socket, MonitorMode> allMonitor = new ConcurrentHashMap<>();
static MonitorMode createOrGet(Socket socket) {
MonitorMode monitorMode = allMonitor.get(socket);
if (monitorMode != null) {
return monitorMode;
}
synchronized (MonitorModeRegistry.class) {
monitorMode = allMonitor.get(socket);
if (monitorMode != null) {
return monitorMode;
}
allMonitor.put(socket, new MonitorMode(socket));
}
return allMonitor.get(socket);
}
static void destroy(MonitorMode monitorMode) {
allMonitor.remove(monitorMode.getSocket());
}
}
package com.virjar.ratel.api.extension.socketmonitor;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamWrapper extends OutputStream {
private OutputStream delegate;
private MonitorMode monitorMode;
private static ThreadLocal<Object> reentryFlag = new ThreadLocal<>();
OutputStreamWrapper(OutputStream delegate, MonitorMode monitorMode) {
this.delegate = delegate;
this.monitorMode = monitorMode;
}
@Override
public void write(byte[] bytes) throws IOException {
delegate.write(bytes);
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
if (reEntry) {
return;
}
monitorMode.outputWrite(bytes);
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
@Override
public void write(byte[] bytes, int i, int i1) throws IOException {
delegate.write(bytes, i, i1);
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
if (reEntry) {
return;
}
monitorMode.outputWrite(bytes, i, i1);
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
@Override
public void write(int i) throws IOException {
delegate.write(i);
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
if (reEntry) {
return;
}
monitorMode.outputWrite(i);
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
@Override
public void flush() throws IOException {
delegate.flush();
}
@Override
public void close() throws IOException {
delegate.close();
monitorMode.close();
}
}
package com.virjar.ratel.api.extension.socketmonitor;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class OutputStreamWrapperInvocationHandler implements InvocationHandler {
private OutputStream delegate;
private MonitorMode monitorMode;
private static Map<Method, Method> methodMapping = new ConcurrentHashMap<>();
private static ThreadLocal<Object> reentryFlag = new ThreadLocal<>();
OutputStreamWrapperInvocationHandler(OutputStream delegate, MonitorMode monitorMode) {
this.delegate = delegate;
this.monitorMode = monitorMode;
}
private synchronized void genCache(Method fromMethod) {
for (Method thisMethod : this.getClass().getDeclaredMethods()) {
if (MethodUtils.isMethodSame(thisMethod, fromMethod)) {
methodMapping.put(fromMethod, thisMethod);
return;
}
}
methodMapping.put(fromMethod, null);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if (!methodMapping.containsKey(method)) {
genCache(method);
}
Method thisMethod = methodMapping.get(method);
if (thisMethod == null) {
return method.invoke(delegate, objects);
}
monitorMode.makeSureWriteTrace();
return thisMethod.invoke(this, objects);
}
public void write(int i) throws IOException {
delegate.write(i);
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
if (reEntry) {
return;
}
monitorMode.outputWrite(i);
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public void write(byte[] bytes) throws IOException {
delegate.write(bytes);
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
if (reEntry) {
return;
}
monitorMode.outputWrite(bytes);
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public void write(byte[] bytes, int i, int i1) throws IOException {
delegate.write(bytes, i, i1);
boolean reEntry = reentryFlag.get() != null;
if (!reEntry) {
reentryFlag.set(new Object());
}
try {
if (reEntry) {
return;
}
monitorMode.outputWrite(bytes, i, i1);
} finally {
if (!reEntry) {
reentryFlag.remove();
}
}
}
public void flush() throws IOException {
delegate.flush();
monitorMode.getOutputSteamData().flush();
}
public void close() throws IOException {
delegate.close();
monitorMode.getOutputSteamData().close();
monitorMode.close();
}
}
package com.virjar.ratel.api.extension.socketmonitor;
import android.util.Log;
import com.virjar.ratel.api.extension.socketmonitor.observer.SocketDataObserver;
import com.virjar.ratel.api.inspect.ClassLoadMonitor;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import com.virjar.ratel.api.rposed.RposedBridge;
import com.virjar.ratel.api.rposed.RposedHelpers;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLSocket;
public class SocketMonitor {
public static final String TAG = "SocketMonitor";
private static Set<Method> hookedMethod = Collections.newSetFromMap(new ConcurrentHashMap<Method, Boolean>());
private static Set<SocketDataObserver> socketDataObservers = new HashSet<>();
private static ThreadLocal<Boolean> disableMonitor = new ThreadLocal<>();
static {
startMonitorInternal();
}
public static void setCurrentThreadMonitorFlag(boolean needMonitor) {
disableMonitor.set(!needMonitor);
}
private static boolean skipMonitor() {
Boolean aBoolean = disableMonitor.get();
if (aBoolean == null) {
return false;
}
return aBoolean;
}
private static void startMonitorInternal() {
//由于我们的arthook不支持hook虚方法,所以无法通过监控构造函数的方法拦截所有socket
monitorSocketClass(SSLSocket.class);
monitorSocketClass(Socket.class);
//这是Android内置的socket对象,由于在BootClassLoader中提前初始化了,所以通过ClassLoadMonitor无法监控到
Class<?> Java8FileDescriptorSocketClass = RposedHelpers.findClassIfExists("com.android.org.conscrypt.Java8FileDescriptorSocket", ClassLoader.getSystemClassLoader());
if (Java8FileDescriptorSocketClass != null) {
monitorSocketClass(Java8FileDescriptorSocketClass);
}
Class<?> ConscryptEngineSocketClass = RposedHelpers.findClassIfExists("com.android.org.conscrypt.ConscryptEngineSocket", ClassLoader.getSystemClassLoader());
if (ConscryptEngineSocketClass != null) {
monitorSocketClass(ConscryptEngineSocketClass);
}
Class<?> ConscryptFileDescriptorSocketClass = RposedHelpers.findClassIfExists("com.android.org.conscrypt.ConscryptFileDescriptorSocket", ClassLoader.getSystemClassLoader());
if (ConscryptFileDescriptorSocketClass != null) {
monitorSocketClass(ConscryptFileDescriptorSocketClass);
}
//如果没有在运行前初始化,那么可以通过ClassLoadMonitor来监控
ClassLoadMonitor.addClassLoadMonitor(new ClassLoadMonitor.OnClassLoader() {
@Override
public void onClassLoad(Class<?> clazz) {
if (Socket.class.isAssignableFrom(clazz)) {
monitorSocketClass(clazz);
}
}
});
}
private static void monitorSocketClass(Class socketClass) {
//monitor socket input,
Method getInputStreamMethod = RposedHelpers.findMethodBestMatch(socketClass, "getInputStream");
if (getInputStreamMethod != null && !hookedMethod.contains(getInputStreamMethod)) {
Log.i(TAG, "add monitor socket class: " + socketClass.getName());
RposedBridge.hookMethod(getInputStreamMethod, new RC_MethodHook() {
@Override
protected void afterHookedMethod(RC_MethodHook.MethodHookParam param) {
if (skipMonitor()) {
return;
}
InputStream inputStream = (InputStream) param.getResult();
if (inputStream == null) {
return;
}
if (inputStream instanceof InputStreamWrapper) {
return;
}
param.setResult(new InputStreamWrapper(inputStream, MonitorModeRegistry.createOrGet((Socket) param.thisObject)));
}
});
hookedMethod.add(getInputStreamMethod);
}
//monitor socket output,
Method getOutputStreamMethod = RposedHelpers.findMethodBestMatch(socketClass, "getOutputStream");
if (getOutputStreamMethod != null && !hookedMethod.contains(getOutputStreamMethod)) {
RposedBridge.hookMethod(getOutputStreamMethod, new RC_MethodHook() {
@Override
protected void afterHookedMethod(RC_MethodHook.MethodHookParam param) {
if (skipMonitor()) {
return;
}
OutputStream outputStream = (OutputStream) param.getResult();
if (outputStream == null) {
return;
}
if (outputStream instanceof OutputStreamWrapper) {
return;
}
param.setResult(new OutputStreamWrapper(outputStream, MonitorModeRegistry.createOrGet((Socket) param.thisObject)));
// 实践发现动态代理机制无法使用,很多class都是private的,无法正常被继承
}
});
hookedMethod.add(getOutputStreamMethod);
}
Method closeMethod = RposedHelpers.findMethodBestMatch(socketClass, "close");
if (closeMethod != null && !hookedMethod.contains(closeMethod)) {
RposedBridge.hookMethod(closeMethod, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
MonitorModeRegistry.createOrGet((Socket) param.thisObject).close();
}
});
}
}
public static synchronized void addPacketEventObserver(SocketDataObserver socketDataObserver) {
socketDataObservers.add(socketDataObserver);
}
public static synchronized void setPacketEventObserver(SocketDataObserver eventObserver) {
socketDataObservers.clear();
socketDataObservers.add(eventObserver);
}
static void callDataObserver(SocketDataObserver.DataModel dataModel) {
for (SocketDataObserver socketDataObserver : socketDataObservers) {
socketDataObserver.onDecodeSocketData(dataModel);
}
}
}
package com.virjar.ratel.api.extension.socketmonitor.http;
import android.util.Log;
import com.virjar.ratel.api.extension.socketmonitor.MonitorMode;
import com.virjar.ratel.api.extension.socketmonitor.SocketMonitor;
import com.virjar.ratel.api.extension.socketmonitor.protocol.AbstractProtocol;
import java.io.IOException;
import java.io.InputStream;
public class HttpProtocol extends AbstractProtocol {
private HttpBaseInfoDecoder requestDecoder;
private HttpBaseInfoDecoder responseDecoder;
private InputStream nowRequestInputStream = null;
private InputStream nowResponseInputStream = null;
public HttpProtocol(MonitorMode monitorMode) {
super(monitorMode);
requestDecoder = new HttpBaseInfoDecoder(monitorMode.getOutputSteamData(), true);
responseDecoder = new HttpBaseInfoDecoder(monitorMode.getInputStreamData(), false);
}
@Override
public AbstractProtocol process() {
try {
nowRequestInputStream = requestDecoder.parse();
nowResponseInputStream = responseDecoder.parse();
} catch (IOException e) {
//not happen
Log.e(SocketMonitor.TAG, "http decode error", e);
}
return super.process();
}
@Override
public boolean needPrint() {
return nowRequestInputStream != null || nowResponseInputStream != null || monitorMode.isClosed();
}
@Override
public InputStream finalOutputStream() {
if (nowRequestInputStream != null) {
return nowRequestInputStream;
}
if (monitorMode.isClosed()) {
return super.finalOutputStream();
}
return null;
}
@Override
public InputStream finalInputStream() {
if (nowResponseInputStream != null) {
return nowResponseInputStream;
}
if (monitorMode.isClosed()) {
return super.finalInputStream();
}
return null;
}
}
package com.virjar.ratel.api.extension.socketmonitor.http;
import java.util.HashMap;
import java.util.Map;
import external.org.apache.commons.lang3.StringUtils;
public class HttpProtocolUtil {
private static final String httpResponseMagic = "HTTP/";
public static boolean maybeHttpResponse(byte[] data, long dataLength) {
//first,find the start of data
int pos = 0;
while (pos < dataLength) {
if (!isWhitespace(data[pos])) {
break;
}
pos++;
}
if (pos + httpResponseMagic.length() >= dataLength) {
return false;
}
//then the HTTP/1.1 200 OK
//read 5 byte
return StringUtils.equalsIgnoreCase(httpResponseMagic, new String(data, pos, httpResponseMagic.length()));
}
private static boolean isWhitespace(final byte ch) {
return ch == HttpStreamUtil.SP || ch == HttpStreamUtil.HT || ch == HttpStreamUtil.CR || ch == HttpStreamUtil.LF;
}
enum Method {
GET,
PUT,
POST,
DELETE,
HEAD,
OPTIONS,
TRACE,
CONNECT,
PATCH,
PROPFIND,
PROPPATCH,
MKCOL,
MOVE,
COPY,
LOCK,
UNLOCK;
}
private static class Trie {
private Map<Byte, Trie> values = new HashMap<>();
private String method = null;
void addToTree(byte[] data, int index, String worldEntry) {
if (index >= data.length) {
//the last
if (this.method == null) {
this.method = worldEntry;
}
return;
}
Trie trie = values.get(data[index]);
if (trie == null) {
trie = new Trie();
values.put(data[index], trie);
}
trie.addToTree(data, index + 1, worldEntry);
}
String find(byte[] pingyings, int index) {
if (index >= pingyings.length) {
return this.method;
}
Trie trie = values.get(pingyings[index]);
if (trie == null) {
return this.method;
// return null;
}
return trie.find(pingyings, index + 1);
}
}
private static Trie methodCharacterTree = new Trie();
static {
for (Method method : Method.values()) {
String name = method.name();
methodCharacterTree.addToTree(name.getBytes(), 0, name);
}
}
public static boolean maybeHttpRequest(byte[] data) {
return StringUtils.isNotBlank(methodCharacterTree.find(data, 0));
}
}
package com.virjar.ratel.api.extension.socketmonitor.http;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import external.org.apache.commons.io.output.ByteArrayOutputStream;
/**
* Created by virjar on 2018/4/26.<br>
* common util for http stream decoder
*/
public class HttpStreamUtil {
public static final int CR = 13; // <US-ASCII CR, carriage return (13)>
public static final int LF = 10; // <US-ASCII LF, linefeed (10)>
public static final int SP = 32; // <US-ASCII SP, space (32)>
public static final int HT = 9; // <US-ASCII HT, horizontal-tab (9)>
public static final int BUFSIZE = 8192;
/**
* Find byte index separating header from body. It must be the last byte of
* the first two sequential new lines.
*/
public static int findHeaderEnd(final byte[] buf, int rlen) {
int splitbyte = 0;
while (splitbyte + 1 < rlen) {
// RFC2616
if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && splitbyte + 3 < rlen && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
return splitbyte + 4;
}
// tolerance
if (buf[splitbyte] == '\n' && buf[splitbyte + 1] == '\n') {
return splitbyte + 2;
}
splitbyte++;
}
return 0;
}
/**
* 寻找回车换行结束,注意这个函数无法处理单纯的换行。如果寻找失败,返回-1
*
* @param buf
* @param rlen
* @return
*/
public static int findLineEnd(final byte[] buf, int rlen) {
int splitbyte = 0;
while (splitbyte + 1 < rlen) {
// RFC2616
if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n') {
// if(splitbyte == 0) continue; //说明分包长度前面有\r\n
return splitbyte + 2;
}
splitbyte++;
}
return -1;
}
public static String readLine(InputStream inputStream, int max) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int i = 0;
while (i < max) {
int char1 = inputStream.read();
if (char1 == -1) {
throw new IllegalStateException("eof of stream");
}
i++;
if (char1 == '\r') {
int char2 = inputStream.read();
i++;
if (char2 == -1) {
throw new IllegalStateException("eof of stream");
}
if (char2 == '\n') {
return byteArrayOutputStream.toString(StandardCharsets.UTF_8);
}
byteArrayOutputStream.write(char1);
byteArrayOutputStream.write(char2);
}
if (char1 == '\n') {
return byteArrayOutputStream.toString(StandardCharsets.UTF_8);
}
byteArrayOutputStream.write(char1);
}
throw new IOException("Maximum line length limit exceeded");
}
public static long consume(InputStream inputStream, long n) throws IOException {
long remaining = n;
long nr;
if (n <= 0) {
return 0;
}
while (remaining > 0) {
nr = inputStream.skip(remaining);
if (nr <= 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
public static int readFully(InputStream inputStream, byte[] buffer) throws IOException {
return readFully(inputStream, buffer, buffer.length);
}
public static int readFully(InputStream inputStream, byte[] buffer, int length) throws IOException {
if (length > buffer.length) {
throw new IOException("buffer size must less than length");
}
if (length <= 0) {
return 0;
}
int read = inputStream.read(buffer, 0, length);
if (read == length) {
return read;
}
while (read < length) {
int nr = inputStream.read(buffer, read, length - read);
if (nr < 0) {
break;
}
read += nr;
}
return read;
}
}
package com.virjar.ratel.api.extension.socketmonitor.observer;
import android.util.Log;
import com.virjar.ratel.api.extension.socketmonitor.SocketMonitor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import external.org.apache.commons.io.IOUtils;
public class FileLogEventObserver implements SocketDataObserver {
private File dir;
private Map<Socket, EventAppender> socketEventAppenderMap = new ConcurrentHashMap<>();
public FileLogEventObserver(File dir) {
this.dir = dir;
}
@Override
public void onDecodeSocketData(DataModel dataModel) {
EventAppender eventAppender = makeSureAppender(dataModel.socket);
if (eventAppender == null) {
//not happen
return;
}
eventAppender.appendEvent(dataModel);
}
private EventAppender makeSureAppender(Socket socket) {
EventAppender eventAppender = socketEventAppenderMap.get(socket);
if (eventAppender != null) {
return eventAppender;
}
synchronized (this) {
eventAppender = socketEventAppenderMap.get(socket);
if (eventAppender != null) {
return eventAppender;
}
try {
if (!dir.exists()) {
if (!dir.mkdirs()) {
throw new IllegalStateException("can not create directory: " + dir.getAbsolutePath());
}
}
eventAppender = new EventAppender(new File(dir, System.currentTimeMillis() + "_socket.txt"));
socketEventAppenderMap.put(socket, eventAppender);
return eventAppender;
} catch (IOException e) {
Log.e(SocketMonitor.TAG, "failed to write data", e);
return null;
}
}
}
private class EventAppender {
private FileOutputStream fileOutputStream;
public EventAppender(File theLogFile) throws IOException {
if (!theLogFile.exists()) {
if (!theLogFile.createNewFile()) {
throw new IOException("can not create file: " + theLogFile.getAbsolutePath());
}
}
fileOutputStream = new FileOutputStream(theLogFile, true);
}
public synchronized void appendEvent(DataModel socketPackEvent) {
int localPort = socketPackEvent.socket.getLocalPort();
int remotePort = socketPackEvent.socket.getPort();
InetAddress inetAddress = socketPackEvent.socket.getInetAddress();
String remoteAddress;
if (inetAddress != null) {
remoteAddress = inetAddress.getHostAddress();
} else {
remoteAddress = socketPackEvent.socket.toString();
}
StringBuilder headerBuilder = new StringBuilder();
headerBuilder.append("\n\n\n");
headerBuilder.append("Socket ");
if (socketPackEvent.in) {
headerBuilder.append("response");
} else {
headerBuilder.append("request");
}
headerBuilder.append(" local port:").append(localPort)
.append(" remote address:").append(remoteAddress).append(":").append(remotePort)
.append("\n").append("StackTrace:");
try {
//输出头部数据
fileOutputStream.write(headerBuilder.toString().getBytes(StandardCharsets.UTF_8));
//输出堆栈
PrintStream printStream = new PrintStream(fileOutputStream);
socketPackEvent.stackTrace.printStackTrace(printStream);
printStream.flush();
printStream.write(newLineBytes);
//输出报文内容
IOUtils.copy(socketPackEvent.data, fileOutputStream);
fileOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static final byte[] newLineBytes = "\n\n".getBytes(StandardCharsets.UTF_8);
}
package com.virjar.ratel.api.extension.socketmonitor.observer;
import java.io.InputStream;
import java.net.Socket;
public interface SocketDataObserver {
class DataModel {
public Socket socket;
public boolean in = false;
public InputStream data;
public Throwable stackTrace;
}
void onDecodeSocketData(DataModel dataModel);
}
package com.virjar.ratel.api.extension.socketmonitor.protocol;
import com.virjar.ratel.api.extension.socketmonitor.MonitorMode;
import java.io.InputStream;
public class AbstractProtocol {
protected MonitorMode monitorMode;
public AbstractProtocol(MonitorMode monitorMode) {
this.monitorMode = monitorMode;
}
public AbstractProtocol process() {
return this;
}
public InputStream finalOutputStream() {
return monitorMode.getOutputSteamData().toInputStream();
}
public InputStream finalInputStream() {
return monitorMode.getInputStreamData().toInputStream();
}
public boolean needPrint() {
return monitorMode.isClosed();
}
public boolean hasData() {
return monitorMode.getOutputSteamData().size() > 0 || monitorMode.getInputStreamData().size() > 0;
}
}
package com.virjar.ratel.api.extension.socketmonitor.protocol;
import android.util.Log;
import com.virjar.ratel.api.extension.socketmonitor.MonitorMode;
import com.virjar.ratel.api.extension.socketmonitor.SocketMonitor;
import com.virjar.ratel.api.extension.socketmonitor.http.HttpProtocol;
import com.virjar.ratel.api.extension.socketmonitor.http.HttpProtocolUtil;
import com.virjar.ratel.api.extension.socketmonitor.http.HttpStreamUtil;
import java.io.IOException;
import java.io.InputStream;
import external.org.apache.commons.io.output.ByteArrayOutputStream;
public class UnknownProtocol extends AbstractProtocol {
public UnknownProtocol(MonitorMode monitorMode) {
super(monitorMode);
}
@Override
public AbstractProtocol process() {
if (isHttp()) {
return new HttpProtocol(monitorMode);
}
return super.process();
}
private boolean isHttp() {
if (monitorMode.getInputStreamData().size() < 10 && monitorMode.getOutputSteamData().size() < 10) {
return false;
}
//检查响应报文是否为http
ByteArrayOutputStream inputStreamData = monitorMode.getInputStreamData();
//test if the input data may be a http response
byte[] buf = new byte[10];
if (inputStreamData.size() >= 10) {
//这个inputStream是一个view,不会消耗额外空间资源,所以我们使用了就丢弃
InputStream inputStream = inputStreamData.toInputStream();
try {
long read = HttpStreamUtil.readFully(inputStream, buf, buf.length);
if (read < 10) {
return false;
}
if (HttpProtocolUtil.maybeHttpResponse(buf, read)) {
return true;
}
} catch (IOException e) {
//the exception not happened
Log.e(SocketMonitor.TAG, "http response magic detect failed", e);
}
}
ByteArrayOutputStream outputSteamData = monitorMode.getOutputSteamData();
if (outputSteamData.size() >= 10) {
InputStream inputStream = outputSteamData.toInputStream();
try {
long read = HttpStreamUtil.readFully(inputStream, buf, buf.length);
if (read < 10) {
return false;
}
if (HttpProtocolUtil.maybeHttpRequest(buf)) {
return true;
}
} catch (IOException e) {
//the exception not happened
Log.e(SocketMonitor.TAG, "http request magic detect failed", e);
}
}
return false;
}
}
package com.virjar.ratel.api.extension.superappium;
public class SuperAppium {
public static final String className = "className";
public static final String baseClassName = "baseClassName";
public static final String hint = "hint";
public static final String mUri = "mUri";
public static final String text = "text";
public static final String clickable = "clickable";
public static final String contentDescription = "contentDescription";
public static final String enable = "enable";
public static final String focusable = "focusable";
public static final String id = "id";
public static final String longClickable = "longClickable";
public static final String packageName = "packageName";
public static final String selected = "selected";
public static final String index = "index";
public static final String hash = "hash";
public static String TAG = "SuperAppium";
}
package com.virjar.ratel.api.extension.superappium;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.view.MotionEvent;
import java.lang.ref.WeakReference;
/**
* <pre>
* author : xiaweizi
* class : com.example.touch.GestureTouchUtils
* e-mail : 1012126908@qq.com
* time : 2018/05/08
* desc : 模拟手势的触摸滑动操作
* </pre>
* migrated from :https://github.com/xiaweizi/ScollDemo/blob/master/touch/src/main/java/com/example/touch/GestureTouchUtils.java
*/
public class SwipeUtils {
public static final int HIGH = 10;
public static final int NORMAL = 100;
public static final int LOW = 1000;
private static final long DEFAULT_DURATION = 1500;
/**
* 模拟手势滑动
*
* @param view 滑动的 view
* @param startX 起始位置 x
* @param startY 起始位置 y
* @param endX 终点位置 x
* @param endY 终点位置 y
*/
public static void simulateScroll(ViewImage view, int startX, int startY, int endX, int endY) {
simulateScroll(view, startX, startY, endX, endY, DEFAULT_DURATION);
}
/**
* 模拟手势滑动
*
* @param view 滑动的 view
* @param startX 起始位置 x
* @param startY 起始位置 y
* @param endX 终点位置 x
* @param endY 终点位置 y
* @param duration 滑动时长 单位:ms
*/
public static void simulateScroll(ViewImage view, int startX, int startY, int endX, int endY, long duration) {
simulateScroll(view, startX, startY, endX, endY, duration, NORMAL);
}
/**
* 模拟手势滑动
*
* @param view 滑动的 view
* @param startX 起始位置 x
* @param startY 起始位置 y
* @param endX 终点位置 x
* @param endY 终点位置 y
* @param duration 滑动时长 单位:ms
* @param period 滑动周期
* {@link #LOW} 慢
* {@link #NORMAL} 正常
* {@link #HIGH} 高
*/
public static void simulateScroll(ViewImage view, int startX, int startY, int endX, int endY, long duration, int period) {
dealSimulateScroll(view, startX, startY, endX, endY, duration, period);
}
/**
* 模拟手势滑动
*
* @param activity 当前的 activity
* @param startX 起始位置 x
* @param startY 起始位置 y
* @param endX 终点位置 x
* @param endY 终点位置 y
* @param duration 滑动时长 单位 ms
* @param period 滑动周期
* {@link #LOW} 慢
* {@link #NORMAL} 正常
* {@link #HIGH} 高
*/
public static void simulateScroll(ViewImage activity, float startX, float startY, float endX, float endY, long duration, int period) {
dealSimulateScroll(activity, startX, startY, endX, endY, duration, period);
}
private static void dealSimulateScroll(ViewImage object, float startX, float startY, float endX, float endY, long duration, int period) {
long downTime = SystemClock.uptimeMillis();
Handler handler = new ViewHandler(object);
//重置相对偏移
int[] loca = new int[2];
object.rootViewImage().getOriginView().getLocationOnScreen(loca);
startX -= loca[0];
endX -= loca[0];
startY -= loca[1];
endY -= loca[1];
object.dispatchInputEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, startX, startY, 0));
GestureBean bean = new GestureBean(startX, startY, endX, endY, duration, period);
Message.obtain(handler, 1, bean).sendToTarget();
}
static class ViewHandler extends Handler {
WeakReference<ViewImage> mView;
ViewHandler(ViewImage activity) {
super(Looper.getMainLooper());
mView = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
ViewImage theView = mView.get();
if (theView == null) {
return;
}
long downTime = SystemClock.uptimeMillis();
GestureBean bean = (GestureBean) msg.obj;
long count = bean.count;
if (count >= bean.totalCount) {
theView.dispatchInputEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_UP, bean.endX, bean.endY, 0));
} else {
theView.dispatchInputEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_MOVE, bean.startX + bean.ratioX * count, bean.startY + bean.ratioY * count, 0));
bean.count++;
Message message = new Message();
message.obj = bean;
sendMessageDelayed(message, bean.period);
}
}
}
static class GestureBean {
/**
* 起始位置 X
*/
float startX;
/**
* 起始位置 Y
*/
float startY;
/**
* 终点位置 X
*/
float endX;
/**
* 终点位置 Y
*/
float endY;
/**
* 每个周期 x 移动的位置
*/
float ratioX;
/**
* 每个周期 y 移动的位置
*/
float ratioY;
/**
* 总共周期
*/
long totalCount;
/**
* 当前周期
*/
long count = 0;
int period = NORMAL;
GestureBean(float startX, float startY, float endX, float endY, long duration, int speed) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
this.period = speed;
totalCount = duration / speed;
ratioX = (endX - startX) / totalCount;
ratioY = (endY - startY) / totalCount;
}
}
}
package com.virjar.ratel.api.extension.superappium;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ViewImages extends ArrayList<ViewImage> {
public ViewImages(ViewImage... parentNodes) {
addAll(Arrays.asList(parentNodes));
}
public ViewImages() {
}
public ViewImages(List<ViewImage> tempList) {
super(tempList);
}
public ViewImages(int initialCapacity) {
super(initialCapacity);
}
}
package com.virjar.ratel.api.extension.superappium.sekiro;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import com.virjar.ratel.api.extension.superappium.PageTriggerManager;
import com.virjar.ratel.api.extension.superappium.SuperAppium;
import com.virjar.ratel.api.extension.superappium.ViewImage;
import com.virjar.ratel.api.extension.superappium.ViewImages;
import com.virjar.ratel.api.extension.superappium.traversor.Collector;
import com.virjar.ratel.api.extension.superappium.traversor.Evaluator;
import com.virjar.ratel.api.extension.superappium.traversor.SuperAppiumDumper;
import com.virjar.ratel.api.extension.superappium.xpath.XpathParser;
import com.virjar.ratel.api.extension.superappium.xpath.model.XNode;
import com.virjar.ratel.api.extension.superappium.xpath.model.XNodes;
import com.virjar.ratel.api.rposed.RposedHelpers;
import com.virjar.sekiro.api.SekiroRequest;
import com.virjar.sekiro.api.SekiroRequestHandler;
import com.virjar.sekiro.api.SekiroResponse;
import com.virjar.sekiro.api.databind.AutoBind;
import java.util.ArrayList;
import java.util.List;
import external.com.alibaba.fastjson.JSON;
import external.com.alibaba.fastjson.JSONObject;
public class DumpTopActivityHandler implements SekiroRequestHandler {
@AutoBind
private String viewHash;
@AutoBind
private String xpath;
@AutoBind
private String type = "activity";
@AutoBind
private boolean webView = false;
@Override
public void handleRequest(final SekiroRequest sekiroRequest, final SekiroResponse sekiroResponse) {
PageTriggerManager.getMainLooperHandler().post(new Runnable() {
private void dumpInternal() {
View topRootView = null;
if ("dialog".equalsIgnoreCase(type)) {
Window topDialogWindow = PageTriggerManager.getTopDialogWindow();
if (topDialogWindow != null) {
topRootView = topDialogWindow.getDecorView();
}
} else if ("popupWindow".equalsIgnoreCase(type)) {
topRootView = PageTriggerManager.getTopPupWindowView();
}
if (topRootView == null) {
topRootView = PageTriggerManager.getTopRootView();
}
if (topRootView == null) {
sekiroResponse.failed("no topRootView found");
return;
}
ViewImage viewImage = new ViewImage(topRootView);
if (webView) {
handleWebViewDump(viewImage);
} else {
handleNativeDump(viewImage);
}
}
private void handleWebViewDump(ViewImage viewImage) {
final WebView webView = viewImage.findWebViewIfExist();
if (webView == null) {
sekiroResponse.failed("no WebView found");
return;
}
new Handler((Looper) RposedHelpers.getObjectField(webView, "mWebViewThread")).post(new Runnable() {
@Override
public void run() {
//android.webkit.WebView#evaluateJavascript(String script, @RecentlyNullable ValueCallback<String> resultCallback) {
RposedHelpers.callMethod(webView, "evaluateJavascript", "document.getElementsByTagName('html')[0].innerHTML", new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
JSONObject jsonObject = JSON.parseObject("{\"data\":" + s + "}");
sekiroResponse.send("text/html", jsonObject.getString("data"));
}
});
}
});
}
private void handleNativeDump(ViewImage viewImage) {
if (!TextUtils.isEmpty(viewHash)) {
ViewImages collect = Collector.collect(new Evaluator.ByHash(viewHash), viewImage);
if (collect.isEmpty()) {
sekiroResponse.failed("no data");
return;
}
sekiroResponse.success(JSON.parseObject(SuperAppiumDumper.dumpToJson(collect.get(0))));
return;
} else if (!TextUtils.isEmpty(xpath)) {
XNodes xNodes = XpathParser.compileNoError(xpath).evaluate(new XNodes(XNode.e(viewImage)));
List<Object> dumpedData = new ArrayList<>();
for (XNode xNode : xNodes) {
if (xNode.isText()) {
dumpedData.add(xNode.getTextVal());
} else {
dumpedData.add(JSON.parseObject(SuperAppiumDumper.dumpToJson(xNode.getElement())));
}
}
if (dumpedData.size() == 1) {
sekiroResponse.success(dumpedData.get(0));
return;
}
sekiroResponse.success(dumpedData);
return;
}
sekiroResponse.success(JSON.parse(SuperAppiumDumper.dumpToJson(viewImage)));
}
@Override
public void run() {
try {
dumpInternal();
} catch (Exception e) {
sekiroResponse.failed(-1, e);
Log.w(SuperAppium.TAG, "dump activity error", e);
}
}
});
}
}
package com.virjar.ratel.api.extension.superappium.sekiro;
import android.text.TextUtils;
import android.view.View;
import com.virjar.ratel.api.extension.superappium.PageTriggerManager;
import com.virjar.ratel.api.extension.superappium.ViewImage;
import com.virjar.ratel.api.extension.superappium.traversor.SuperAppiumDumper;
import com.virjar.ratel.api.rposed.RposedHelpers;
import com.virjar.sekiro.api.SekiroRequest;
import com.virjar.sekiro.api.SekiroRequestHandler;
import com.virjar.sekiro.api.SekiroResponse;
import com.virjar.sekiro.api.databind.AutoBind;
import java.util.ArrayList;
import java.util.List;
import external.com.alibaba.fastjson.JSON;
import external.com.alibaba.fastjson.JSONObject;
public class DumpTopFragmentHandler implements SekiroRequestHandler {
@AutoBind
private String fragmentClass;
@Override
public void handleRequest(SekiroRequest sekiroRequest, final SekiroResponse sekiroResponse) {
PageTriggerManager.getMainLooperHandler().post(new Runnable() {
@Override
public void run() {
List<Object> topFragment;
if (TextUtils.isEmpty(fragmentClass)) {
topFragment = PageTriggerManager.getTopFragment();
} else {
topFragment = new ArrayList<>();
Object topFragment1 = PageTriggerManager.getTopFragment(fragmentClass);
if (topFragment1 != null) {
topFragment.add(topFragment1);
}
}
List<ViewImage> viewImages = new ArrayList<>();
for (Object fragment : topFragment) {
viewImages.add(new ViewImage((View) RposedHelpers.callMethod(fragment, "getView")));
}
if (viewImages.size() == 0) {
sekiroResponse.failed("no data");
return;
}
if (viewImages.size() == 1) {
sekiroResponse.success(JSON.parse(SuperAppiumDumper.dumpToJson(viewImages.get(0))));
return;
}
List<JSONObject> jsonObjects = new ArrayList<>();
for (ViewImage viewImage : viewImages) {
jsonObjects.add(JSON.parseObject(SuperAppiumDumper.dumpToJson(viewImage)));
}
sekiroResponse.success(jsonObjects);
}
});
}
}
package com.virjar.ratel.api.extension.superappium.sekiro;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import com.virjar.ratel.api.extension.superappium.PageTriggerManager;
import com.virjar.ratel.api.extension.superappium.SuperAppium;
import com.virjar.ratel.api.extension.superappium.ViewImage;
import com.virjar.ratel.api.rposed.RposedHelpers;
import com.virjar.sekiro.api.SekiroRequest;
import com.virjar.sekiro.api.SekiroRequestHandler;
import com.virjar.sekiro.api.SekiroResponse;
import com.virjar.sekiro.api.databind.AutoBind;
import external.com.alibaba.fastjson.JSON;
import external.com.alibaba.fastjson.JSONObject;
public class ExecuteJsOnWebViewHandler implements SekiroRequestHandler {
@AutoBind
private String jsCode;
@AutoBind
private String type = "activity";
@Override
public void handleRequest(final SekiroRequest sekiroRequest, final SekiroResponse sekiroResponse) {
PageTriggerManager.getMainLooperHandler().post(new Runnable() {
private void dumpInternal() {
View topRootView = null;
if ("dialog".equalsIgnoreCase(type)) {
Window topDialogWindow = PageTriggerManager.getTopDialogWindow();
if (topDialogWindow != null) {
topRootView = topDialogWindow.getDecorView();
}
} else if ("popupWindow".equalsIgnoreCase(type)) {
topRootView = PageTriggerManager.getTopPupWindowView();
}
if (topRootView == null) {
topRootView = PageTriggerManager.getTopRootView();
}
if (topRootView == null) {
sekiroResponse.failed("no topRootView found");
return;
}
ViewImage viewImage = new ViewImage(topRootView);
final WebView webView = viewImage.findWebViewIfExist();
if (webView == null) {
sekiroResponse.failed("no WebView found");
return;
}
new Handler((Looper) RposedHelpers.getObjectField(webView, "mWebViewThread")).post(new Runnable() {
@Override
public void run() {
//android.webkit.WebView#evaluateJavascript(String script, @RecentlyNullable ValueCallback<String> resultCallback) {
RposedHelpers.callMethod(webView, "evaluateJavascript", jsCode, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
JSONObject jsonObject = JSON.parseObject("{\"data\":" + s + "}");
String data = jsonObject.getString("data");
if (data == null) {
data = "undefined";
}
sekiroResponse.send("text/html", data);
}
});
}
});
}
@Override
public void run() {
try {
dumpInternal();
} catch (Exception e) {
sekiroResponse.failed(-1, e);
Log.w(SuperAppium.TAG, "dump activity error", e);
}
}
});
}
}
package com.virjar.ratel.api.extension.superappium.sekiro;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.Window;
import com.virjar.ratel.api.extension.superappium.PageTriggerManager;
import com.virjar.ratel.api.extension.superappium.SuperAppium;
import com.virjar.ratel.api.extension.superappium.ViewImage;
import com.virjar.ratel.api.extension.superappium.ViewImages;
import com.virjar.ratel.api.extension.superappium.traversor.Collector;
import com.virjar.ratel.api.extension.superappium.traversor.Evaluator;
import com.virjar.sekiro.api.SekiroRequest;
import com.virjar.sekiro.api.SekiroRequestHandler;
import com.virjar.sekiro.api.SekiroResponse;
import com.virjar.sekiro.api.databind.AutoBind;
import external.org.apache.commons.io.output.ByteArrayOutputStream;
public class ScreenShotHandler implements SekiroRequestHandler {
@AutoBind
private int quality = 50;
@AutoBind
private String viewHash;
@AutoBind
private String type = "activity";
@Override
public void handleRequest(SekiroRequest sekiroRequest, final SekiroResponse sekiroResponse) {
Activity topActivity = PageTriggerManager.getTopActivity();
if (topActivity == null) {
sekiroResponse.failed("no data");
return;
}
if (quality < 10) {
quality = 10;
} else if (quality > 100) {
quality = 100;
}
PageTriggerManager.getMainLooperHandler().post(new Runnable() {
private void screenShotInternal() {
View decorView = null;
if ("dialog".equalsIgnoreCase(type)) {
Window topDialogWindow = PageTriggerManager.getTopDialogWindow();
if (topDialogWindow != null) {
decorView = topDialogWindow.getDecorView();
}
} else if ("popupWindow".equalsIgnoreCase(type)) {
decorView = PageTriggerManager.getTopPupWindowView();
}
if (decorView == null) {
decorView = PageTriggerManager.getTopRootView();
}
if (decorView == null) {
sekiroResponse.failed("no data");
return;
}
if (!TextUtils.isEmpty(viewHash)) {
ViewImages collect = Collector.collect(new Evaluator.ByHash(viewHash), new ViewImage(decorView));
if (collect.isEmpty()) {
sekiroResponse.failed("no data");
return;
}
decorView = collect.get(0).getOriginView();
}
if (decorView.getWidth() <= 0 || decorView.getHeight() <= 0) {
sekiroResponse.failed("zero img");
return;
}
Bitmap bitmap = Bitmap.createBitmap(decorView.getWidth(), decorView.getHeight(), Bitmap.Config.RGB_565);
decorView.draw(new Canvas(bitmap));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
sekiroResponse.send("image/jpeg", byteArrayOutputStream.toByteArray());
}
@Override
public void run() {
try {
screenShotInternal();
} catch (Throwable throwable) {
Log.e(SuperAppium.TAG, "screenShot failed", throwable);
sekiroResponse.failed(-1, throwable);
}
}
});
}
}
package com.virjar.ratel.api.extension.superappium.sekiro;
import android.util.Log;
import com.virjar.ratel.api.extension.superappium.PageTriggerManager;
import com.virjar.ratel.api.extension.superappium.SuperAppium;
import com.virjar.sekiro.api.SekiroClient;
import java.util.UUID;
public class SekiroStarter {
public static String sekiroGroup = "ratel-appium";
private static final String dumpTopActivity = "dumpActivity";
private static final String dumpTopFragment = "dumpFragment";
private static final String screenShot = "screenShot";
private static final String executeJsOnWebView = "ExecuteJsOnWebView";
private static boolean isStarted = false;
public static void startService(String host, int port, String token) {
if (isStarted) {
return;
}
Log.i(SuperAppium.TAG, "start a supperAppium client: " + token);
PageTriggerManager.getTopFragment("insureComponentStarted");
SekiroClient.start(host, port, token, sekiroGroup)
.registerHandler(dumpTopActivity, new DumpTopActivityHandler())
.registerHandler(dumpTopFragment, new DumpTopFragmentHandler())
.registerHandler(screenShot, new ScreenShotHandler())
.registerHandler(executeJsOnWebView, new ExecuteJsOnWebViewHandler())
;
isStarted = true;
}
public static void startService(String host, int port) {
startService(host, port, UUID.randomUUID().toString());
}
}
package com.virjar.ratel.api.extension.superappium.traversor;
import com.virjar.ratel.api.extension.superappium.ViewImage;
import com.virjar.ratel.api.extension.superappium.ViewImages;
import java.util.List;
public class Collector {
private Collector() {
}
/**
* Build a list of elements, by visiting root and every descendant of root, and testing it against the evaluator.
*
* @param eval Evaluator to test elements against
* @param root root of tree to descend
* @return list of matches; empty if none
*/
public static ViewImages collect(Evaluator eval, ViewImage root) {
ViewImages elements = new ViewImages();
new NodeTraversor(new Accumulator(root, elements, eval)).traverse(root);
return elements;
}
private static class Accumulator implements NodeVisitor {
private final ViewImage root;
private final List<ViewImage> elements;
private final Evaluator eval;
Accumulator(ViewImage root, List<ViewImage> elements, Evaluator eval) {
this.root = root;
this.elements = elements;
this.eval = eval;
}
public boolean head(ViewImage node, int depth) {
if (eval.matches(root, node)) {
elements.add(node);
return eval.onlyOne();
}
return false;
}
public boolean tail(ViewImage node, int depth) {
// void
return false;
}
}
}
package com.virjar.ratel.api.extension.superappium.traversor;
import com.virjar.ratel.api.extension.superappium.SuperAppium;
import com.virjar.ratel.api.extension.superappium.ViewImage;
public abstract class Evaluator {
protected Evaluator() {
}
/**
* Test if the element meets the evaluator's requirements.
*
* @param root Root of the matching subtree
* @param element tested element
* @return Returns <tt>true</tt> if the requirements are met or
* <tt>false</tt> otherwise
*/
public abstract boolean matches(ViewImage root, ViewImage element);
public boolean onlyOne() {
return false;
}
public static class AllElements extends Evaluator {
@Override
public boolean matches(ViewImage root, ViewImage element) {
return true;
}
}
public static class ByTag extends Evaluator {
private String tag;
public ByTag(String tag) {
this.tag = tag;
}
@Override
public boolean matches(ViewImage root, ViewImage element) {
return element.getType().equals(tag);
}
}
public static class ClickAble extends Evaluator {
@Override
public boolean matches(ViewImage root, ViewImage element) {
return element.attribute(SuperAppium.clickable);
}
}
public static class ByHash extends Evaluator {
private String hash;
public ByHash(String hash) {
this.hash = hash;
}
@Override
public boolean matches(ViewImage root, ViewImage element) {
return String.valueOf(element.getOriginView().hashCode()).equals(hash);
}
@Override
public boolean onlyOne() {
return true;
}
}
}
package com.virjar.ratel.api.extension.superappium.traversor;
import com.virjar.ratel.api.extension.superappium.ViewImage;
public class NodeTraversor {
private NodeVisitor visitor;
/**
* Create a new traversor.
*
* @param visitor a class implementing the {@link NodeVisitor} interface, to be called when visiting each node.
*/
public NodeTraversor(NodeVisitor visitor) {
this.visitor = visitor;
}
/**
* Start a depth-first traverse of the root and all of its descendants.
*
* @param root the root node point to traverse.
*/
public void traverse(ViewImage root) {
ViewImage node = root;
int depth = 0;
while (node != null) {
if (visitor.head(node, depth)) {
return;
}
if (node.childCount() > 0) {
node = node.childAt(0);
depth++;
} else {
while (node.nextSibling() == null && depth > 0) {
if (visitor.tail(node, depth)) {
return;
}
node = node.parentNode();
depth--;
}
if (visitor.tail(node, depth)) {
return;
}
if (node == root)
break;
node = node.nextSibling();
}
}
}
}
package com.virjar.ratel.api.extension.superappium.traversor;
import com.virjar.ratel.api.extension.superappium.ViewImage;
public interface NodeVisitor {
/**
* Callback for when a node is first visited.
*
* @param node the node being visited.
* @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node
* of that will have depth 1.
*/
boolean head(ViewImage node, int depth);
/**
* Callback for when a node is last visited, after all of its descendants have been visited.
*
* @param node the node being visited.
* @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node
* of that will have depth 1.
*/
boolean tail(ViewImage node, int depth);
}
package com.virjar.ratel.api.extension.superappium.traversor;
import android.util.Log;
import android.util.Xml;
import com.virjar.ratel.api.extension.superappium.SuperAppium;
import com.virjar.ratel.api.extension.superappium.ViewImage;
import com.virjar.ratel.api.extension.superappium.ViewImages;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.StringWriter;
public class SuperAppiumDumper {
public static String dumpToXml(ViewImage viewImage) {
try {
XmlSerializer serializer = Xml.newSerializer();
StringWriter stringWriter = new StringWriter();
serializer.setOutput(stringWriter);
serializer.startDocument("UTF-8", true);
serializer.startTag("", "hierarchy");
serializer.attribute("", "comment", "dumped by super-appium, notice this not compatible with uiautomator");
dumpNodeRec(viewImage, serializer);
serializer.endTag("", "hierarchy");
serializer.endDocument();
return stringWriter.toString();
} catch (IOException e) {
Log.e(SuperAppium.TAG, "failed to dump window to file", e);
}
return null;
}
private static void dumpNodeRec(ViewImage node, XmlSerializer serializer) throws IOException {
String tag = String.valueOf(node.attribute(SuperAppium.baseClassName));
serializer.startTag("", tag);
for (String attrKey : node.attributeKeys()) {
if (attrKey.equals(SuperAppium.baseClassName)) {
continue;
}
Object value = node.attribute(attrKey);
if (value == null) {
continue;
}
serializer.attribute("", attrKey, String.valueOf(value));
}
int count = node.childCount();
for (int i = 0; i < count; i++) {
ViewImage child = node.childAt(i);
if (child != null) {
dumpNodeRec(child, serializer);
} else {
Log.i(SuperAppium.TAG, String.format("Null child %d/%d, parent: %s",
i, count, node.toString()));
}
}
serializer.endTag("", tag);
}
public static String dumpToJson(ViewImage viewImage) {
return dumpToJsonObject(viewImage).toString();
}
public static String dumpToJson(ViewImages viewImages) {
JSONArray jsonArray = new JSONArray();
for (ViewImage viewImage : viewImages) {
jsonArray.put(dumpToJsonObject(viewImage));
}
return jsonArray.toString();
}
public static JSONObject dumpToJsonObject(ViewImage viewImage) {
JSONObject jsonObject = new JSONObject();
dumpNodeRec(viewImage, jsonObject);
return jsonObject;
}
private static void dumpNodeRec(ViewImage node, JSONObject container) {
for (String attrKey : node.attributeKeys()) {
Object value = node.attribute(attrKey);
if (value == null) {
continue;
}
try {
container.putOpt(attrKey, value);
} catch (JSONException e) {
e.printStackTrace();
}
}
int count = node.childCount();
if (count <= 0) {
return;
}
JSONArray jsonArray = new JSONArray();
for (int i = 0; i < count; i++) {
ViewImage child = node.childAt(i);
if (child == null) {
jsonArray.put((Object) null);
} else {
JSONObject childContainer = new JSONObject();
dumpNodeRec(child, childContainer);
jsonArray.put(childContainer);
}
}
try {
container.put("children", jsonArray);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
package com.virjar.ratel.api.extension.superappium.xmodel;
import com.virjar.ratel.api.extension.superappium.ViewImage;
public class LazyValueGetter<T> {
private ValueGetter<T> delegate;
private T theValue;
private boolean hasDelegateCalled = false;
private ViewImage viewImage;
LazyValueGetter(ValueGetter<T> delegate, ViewImage viewImage) {
this.delegate = delegate;
this.viewImage = viewImage;
}
public boolean support(Class type) {
return delegate.support(type);
}
public T get() {
if (hasDelegateCalled) {
return theValue;
}
synchronized (this) {
if (hasDelegateCalled) {
return theValue;
}
theValue = delegate.get(viewImage);
hasDelegateCalled = true;
}
return theValue;
}
public String attr() {
return delegate.attr();
}
}
package com.virjar.ratel.api.extension.superappium.xmodel;
import com.virjar.ratel.api.extension.superappium.ViewImage;
public interface ValueGetter<T> {
T get(ViewImage viewImage);
boolean support(Class type);
String attr();
}
package com.virjar.ratel.api.extension.superappium.xmodel;
public class Values {
// public static ValueGetter<String> className(ViewModel view) {
// return cache(new ClassNameGetter(view.getOriginView()));
// }
//
// public static ValueGetter<String> contentDescription(ViewModel view) {
// return cache(new ContentDescriptionValueGetter(view.getOriginView()));
// }
// @SuppressWarnings("unchecked")
// private static <T> ValueGetter<T> cache(ValueGetter<T> valueGetter) {
// return new LazyValueGetter<T>(valueGetter);
// }
}
package com.virjar.ratel.api.extension.superappium.xpath.exception;
public class NoSuchAxisException extends XpathSyntaxErrorException {
public NoSuchAxisException(int errorPos, String msg) {
super(errorPos, msg);
}
}
package com.virjar.ratel.api.extension.superappium.xpath.function;
public interface NameAware {
String getName();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment