Skip to content

Commit

Permalink
Merge pull request HXSecurity#585 from java-sec/java-sec-dev-obj2Stri…
Browse files Browse the repository at this point in the history
…ng-squeeze-dry

fix: fix obj2string bug
  • Loading branch information
CC11001100 authored Sep 6, 2023
2 parents cd5e4d3 + 7c47ba7 commit 6bf9ee5
Show file tree
Hide file tree
Showing 24 changed files with 673 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.dongtai.iast.common.exception;

/**
* 动态Agent整个项目内异常的基类,以后的异常尽量都继承这个类
*
* @author CC11001100
* @since 1.13.2
*/
public class DongTaiIastException extends Exception {

public DongTaiIastException() {
}

public DongTaiIastException(String message) {
super(message);
}

public DongTaiIastException(String message, Throwable cause) {
super(message, cause);
}

public DongTaiIastException(Throwable cause) {
super(cause);
}

public DongTaiIastException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.dongtai.iast.common.string;

/**
* 用于表示对 对象格式化的结果
*
* @author CC11001100
* @since 1.13.2
*/
public class ObjectFormatResult {

// 对象格式化后的字符串,可能不是原始的完整的字符串是被格式化过的,仅作为展示之类的使用
public String objectFormatString;

// 原始的字符串长度,对象格式化可以认为有三个步骤:
//
// object --> original string --> format string
//
// 其中original string通常是调用object的toString()得到的,长度可能比较短,也可能老长老长了
// format string这一步相当于是对original string进行截断,控制字符串的长度
public int originalLength;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package io.dongtai.iast.common.string;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

/**
* 用于把对象格式化为字符串
*
* @author CC11001100
* @since 1.13.2
*/
public class ObjectFormatter {

/**
* 把对象格式化为字符串,高频调用要尽可能快
*
* @param value 要转换为字符串的对象
* @param charLimit 转换时的字符长度限制,超过此长度将被格式化为一个祖传下来的表示字符串省略的格式 :)
* @return 比如"aaa",如果超长可能会发生省略: "aaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaa"
* @see ObjectFormatResult
*/
public static ObjectFormatResult formatObject(Object value, int charLimit) {

ObjectFormatResult r = new ObjectFormatResult();

if (null == value) {
return r;
}

try {
if (value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive()) {
// 判断是否是基本类型的数组,基本类型的数组无法类型转换为Object[],导致java.lang.ClassCastException异常
Object[] taints = (Object[]) value;
return objArray2StringV2(taints, charLimit);
} else if (value instanceof StringWriter) {
String s = ((StringWriter) value).getBuffer().toString();
r.originalLength = s.length();
r.objectFormatString = StringUtils.normalize(s, charLimit);
return r;
} else {
String s = value.toString();
r.originalLength = s.length();
r.objectFormatString = StringUtils.normalize(s, charLimit);
return r;
}
} catch (Throwable e) {
// org.jruby.RubyBasicObject.hashCode() may cause NullPointerException when RubyBasicObject.metaClass is null
String typeName = value.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(value));
r.originalLength = typeName.length();
r.objectFormatString = StringUtils.normalize(typeName, charLimit);
return r;
}
}

/**
* 对象数组转为字符串,会往下穿透到第二层
*
* @param objArray 要转换为字符串的对象数组
* @param charLimit 同 {{@link #formatObject(Object, int)}}
* @return
* @see #formatObject
*/
private static ObjectFormatResult objArray2StringV2(Object[] objArray, int charLimit) {

ObjectFormatResult r = new ObjectFormatResult();

// 第一步,先把对象都收集一下,把要处理的对象打平
List<Object> objList = new ArrayList<>();
for (Object taint : objArray) {
if (taint != null) {
if (taint.getClass().isArray() && !taint.getClass().getComponentType().isPrimitive()) {
Object[] subTaints = (Object[]) taint;
for (Object subTaint : subTaints) {
if (subTaint == null) {
continue;
}
objList.add(subTaint);
}
} else {
objList.add(taint);
}
}
}

// 从前往后开始读取
StringBuilder header = new StringBuilder();
int headIndex = 0;
while (headIndex < objList.size()) {

String s = objList.get(headIndex).toString();
headIndex++;
// 如果这个地方的字符串比较长怎么办?是不是应该截断一下?还是有优化空间的
header.append(s);
r.originalLength += s.length();

// 如果进来的话,说明长度是超了,这个时候应该做的是从尾部读取一部分进来,然后等会儿做截断用
if (header.length() > charLimit) {

// 然后就从尾部开始向前读取
int readCount = 0;
LinkedList<String> tailStringList = new LinkedList<>();
int needReadChar = charLimit / 2 + charLimit % 2;
for (int tailIndex = objList.size() - 1; tailIndex >= headIndex; tailIndex--) {
s = objList.get(tailIndex).toString();
// 仅读取需要的字符数,超过的话则不再读取
if (readCount < needReadChar) {
readCount += s.length();
tailStringList.addFirst(s);
}
// 但是长度是要整个计算的
r.originalLength += s.length();
}

// 然后开始拼接处理
tailStringList.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
// 是不是应该避免一下不必要的拼接拷贝?某些特殊数据下还是可能会发生占用较长时间
header.append(s);
}
});

break;
}
}

r.objectFormatString = StringUtils.normalize(header.toString(), charLimit);
return r;
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.dongtai.iast.core.utils;
package io.dongtai.iast.common.string;

/**
* @author dongzhiyong@huoxian.cn
Expand Down Expand Up @@ -32,6 +32,17 @@ public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}

/**
* 判断字符串是否为空白字符串,比如 " " 会被认为是空白字符串
*
* @param s
* @return
* @since 1.13.2
*/
public static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}

public static String normalize(String str, int maxLength) {
int max = Math.max(maxLength, 5);
if (str != null && str.length() != 0 && str.length() > max) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package io.dongtai.iast.common.string;

import org.junit.Assert;
import org.junit.Test;

/**
* @author CC11001100
*/
public class ObjectFormatterTest {

@Test
public void formatObject() {

// 普通的字符串
ObjectFormatResult r = ObjectFormatter.formatObject("CC11001100", 1024);
Assert.assertNotNull(r);
Assert.assertEquals(10, r.originalLength);
Assert.assertEquals("CC11001100", r.objectFormatString);

// 超过长度限制的字符串
r = ObjectFormatter.formatObject("Dongtai IAST is an open-source Interactive Application Security Testing (IAST) tool that enables real-time detection of common vulnerabilities in Java applications and third-party components through passive instrumentation. It is particularly suitable for use in the testing phase of the development pipeline.", 100);
Assert.assertNotNull(r);
Assert.assertEquals(309, r.originalLength);
Assert.assertEquals("Dongtai IAST is an open-source Interactive Applic...n the testing phase of the development pipeline.", r.objectFormatString);

// int
r = ObjectFormatter.formatObject(10086, 100);
Assert.assertNotNull(r);
Assert.assertEquals(5, r.originalLength);
Assert.assertEquals("10086", r.objectFormatString);

// null
r = ObjectFormatter.formatObject(null, 100);
Assert.assertNotNull(r);
Assert.assertEquals(0, r.originalLength);
Assert.assertNull(r.objectFormatString);

// 数组,加起来长度超了
r = ObjectFormatter.formatObject(new String[]{
"foo",
"bar",
"blablablablablablablablablablablabla"
}, 10);
Assert.assertNotNull(r);
Assert.assertEquals(42, r.originalLength);
Assert.assertEquals("foob...bla", r.objectFormatString);

// 数组,第一个长度超了
r = ObjectFormatter.formatObject(new String[]{
"foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo",
"bar",
"blabla"
}, 10);
Assert.assertNotNull(r);
Assert.assertEquals(117, r.originalLength);
Assert.assertEquals("foof...bla", r.objectFormatString);

// 数组,第一个长度没超,加起来超了
r = ObjectFormatter.formatObject(new String[]{
"foofoo",
"barbarbarbarbar",
"blabla"
}, 10);
Assert.assertNotNull(r);
Assert.assertEquals(27, r.originalLength);
Assert.assertEquals("foof...bla", r.objectFormatString);

// 数组,第一个长度没超,加起来也没超
r = ObjectFormatter.formatObject(new String[]{
"foo",
"bar",
"bla"
}, 10);
Assert.assertNotNull(r);
Assert.assertEquals(9, r.originalLength);
Assert.assertEquals("foobarbla", r.objectFormatString);

// 对象数组
r = ObjectFormatter.formatObject(new Object[]{
new Object(),
new Object(),
new Object(),
new Object()
}, 40);
Assert.assertNotNull(r);
Assert.assertTrue(r.originalLength > 0);
Assert.assertNotNull(r.objectFormatString);

// null数组
r = ObjectFormatter.formatObject(new Object[]{
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
}, 40);
Assert.assertNotNull(r);
Assert.assertEquals(0, r.originalLength);
Assert.assertEquals("", r.objectFormatString);

// Integer数组
r = ObjectFormatter.formatObject(new Integer[]{
1,
2,
3,
4,
5
}, 40);
Assert.assertNotNull(r);
Assert.assertEquals(5, r.originalLength);
Assert.assertEquals("12345", r.objectFormatString);

// int数组
r = ObjectFormatter.formatObject(new int[]{
1,
2,
3,
4,
5
}, 40);
Assert.assertNotNull(r);
Assert.assertEquals(11, r.originalLength);
Assert.assertNotNull(r.objectFormatString);

// 数组中偶尔有null的
r = ObjectFormatter.formatObject(new Integer[]{
1,
null,
3,
null,
5
}, 40);
Assert.assertNotNull(r);
Assert.assertEquals(3, r.originalLength);
Assert.assertEquals("135", r.objectFormatString);

}

}
Loading

0 comments on commit 6bf9ee5

Please sign in to comment.