Skip to content

Commit 3dc9dd1

Browse files
committed
@raw 支持 key:value 和 @having@having@column 一样支持 function(arg,&char,!) 中包含不符合 字段命名 的字符;优化代码和报错提示;
1 parent d959c6c commit 3dc9dd1

File tree

2 files changed

+112
-42
lines changed

2 files changed

+112
-42
lines changed

APIJSONORM/src/main/java/apijson/orm/AbstractParser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,10 @@ public JSONObject parseResponse(JSONObject request) {
385385
requestObject.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount());
386386
requestObject.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth());
387387
requestObject.put("time:start|duration|end", startTime + "|" + duration + "|" + endTime);
388+
if (error != null) {
389+
requestObject.put("throw", error.getClass().getName());
390+
requestObject.put("trace", error.getStackTrace());
391+
}
388392
}
389393

390394
onClose();

APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java

Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ public abstract class AbstractSQLConfig implements SQLConfig {
8181
public static String DEFAULT_SCHEMA = "sys";
8282
public static String PREFFIX_DISTINCT = "DISTINCT ";
8383

84+
// * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句!
85+
private static final Pattern PATTERN_RANGE;
86+
private static final Pattern PATTERN_FUNCTION;
87+
8488
/**
8589
* 表名映射,隐藏真实表名,对安全要求很高的表可以这么做
8690
*/
@@ -89,7 +93,11 @@ public abstract class AbstractSQLConfig implements SQLConfig {
8993
public static final List<String> DATABASE_LIST;
9094
// 自定义原始 SQL 片段 Map<key, substring>:当 substring 为 null 时忽略;当 substring 为 "" 时整个 value 是 raw SQL;其它情况则只是 substring 这段为 raw SQL
9195
public static final Map<String, String> RAW_MAP;
92-
static {
96+
static { // 凡是 SQL 边界符、分隔符、注释符 都不允许,例如 ' " ` ( ) ; # -- ,以免拼接 SQL 时被注入意外可执行指令
97+
PATTERN_RANGE = Pattern.compile("^[0-9%,!=\\<\\>/\\.\\+\\-\\*\\^]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过!
98+
PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%,:_@&~!=\\<\\>\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\$]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值
99+
100+
93101
TABLE_KEY_MAP = new HashMap<String, String>();
94102
TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME);
95103
TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME);
@@ -119,6 +127,7 @@ public abstract class AbstractSQLConfig implements SQLConfig {
119127
RAW_MAP = new LinkedHashMap<>(); // 保证顺序,避免配置冲突等意外情况
120128
}
121129

130+
122131
@Override
123132
public boolean limitSQLCount() {
124133
return Log.DEBUG == false || AbstractVerifier.SYSTEM_ACCESS_MAP.containsKey(getTable()) == false;
@@ -487,7 +496,7 @@ public AbstractSQLConfig setHaving(String having) {
487496
this.having = having;
488497
return this;
489498
}
490-
/**
499+
/**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" }
491500
* @return HAVING conditoin0 AND condition1 OR condition2 ...
492501
*/
493502
@JSONField(serialize = false)
@@ -517,12 +526,17 @@ public String getHavingString(boolean hasPrefix) {
517526
}
518527
}
519528

520-
having = StringUtil.getTrimedString(having);
521-
String[] keys = StringUtil.split(having, ";");
529+
String[] keys = StringUtil.split(getHaving(), ";");
522530
if (keys == null || keys.length <= 0) {
523531
return StringUtil.isEmpty(joinHaving, true) ? "" : (hasPrefix ? " HAVING " : "") + joinHaving;
524532
}
533+
534+
String quote = getQuote();
535+
String tableAlias = getAliasWithQuote();
525536

537+
List<String> raw = getRaw();
538+
boolean containRaw = raw != null && raw.contains(KEY_HAVING);
539+
526540
String expression;
527541
String method;
528542
//暂时不允许 String prefix;
@@ -533,13 +547,31 @@ public String getHavingString(boolean hasPrefix) {
533547

534548
//fun(arg0,arg1,...)
535549
expression = keys[i];
550+
if (containRaw) {
551+
try {
552+
String rawSQL = getRawSQL(KEY_HAVING, expression);
553+
if (rawSQL != null) {
554+
keys[i] = rawSQL;
555+
continue;
556+
}
557+
} catch (Exception e) {
558+
Log.e(TAG, "newSQLConfig rawColumnSQL == null >> try { "
559+
+ " String rawSQL = ((AbstractSQLConfig) config).getRawSQL(KEY_COLUMN, fk); ... "
560+
+ "} catch (Exception e) = " + e.getMessage());
561+
}
562+
}
563+
564+
if (expression.length() > 50) {
565+
throw new UnsupportedOperationException("@having:value 的 value 中字符串 " + expression + " 不合法!"
566+
+ "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!");
567+
}
536568

537569
int start = expression.indexOf("(");
538570
if (start < 0) {
539-
if (isPrepared() && PATTERN_HAVING.matcher(expression).matches() == false) {
571+
if (isPrepared() && PATTERN_FUNCTION.matcher(expression).matches() == false) {
540572
throw new UnsupportedOperationException("字符串 " + expression + " 不合法!"
541573
+ "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\""
542-
+ " 中 column?value 必须符合正则表达式 ^[A-Za-z0-9%!=<>]+$ !不允许空格!");
574+
+ " 中 column?value 必须符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许空格!");
543575
}
544576
continue;
545577
}
@@ -560,24 +592,40 @@ public String getHavingString(boolean hasPrefix) {
560592

561593
suffix = expression.substring(end + 1, expression.length());
562594

563-
if (isPrepared() && PATTERN_HAVING_SUFFIX.matcher((String) suffix).matches() == false) {
595+
if (isPrepared() && (((String) suffix).contains("--") || PATTERN_RANGE.matcher((String) suffix).matches() == false)) {
564596
throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!"
565597
+ "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\""
566-
+ " 中 ?value 必须符合正则表达式 ^[0-9%!=<>]+$ !不允许空格!");
598+
+ " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- !不允许多余的空格!");
567599
}
568600

569601
String[] ckeys = StringUtil.split(expression.substring(start + 1, end));
570602

571603
if (ckeys != null) {
572604
for (int j = 0; j < ckeys.length; j++) {
605+
String origin = ckeys[j];
606+
607+
if (isPrepared()) {
608+
if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) {
609+
throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!"
610+
+ "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\""
611+
+ " 中所有 column, arg 都必须是1个不以 _ 开头的单词 或者 符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !不允许多余的空格!");
612+
}
613+
}
573614

574-
if (isPrepared() && (StringUtil.isName(ckeys[j]) == false || ckeys[j].startsWith("_"))) {
575-
throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!"
576-
+ "预编译模式下 @having:\"column?value;function(arg0,arg1,...)?value...\""
577-
+ " 中所有 arg 都必须是1个不以 _ 开头的单词!并且不要有空格!");
615+
//JOIN 副表不再在外层加副表名前缀 userId AS `Commet.userId`, 而是直接 userId AS `userId`
616+
boolean isName = false;
617+
if (StringUtil.isNumer(origin)) {
618+
//do nothing
619+
}
620+
else if (StringUtil.isName(origin)) {
621+
origin = quote + origin + quote;
622+
isName = true;
623+
}
624+
else {
625+
origin = getValue(origin).toString();
578626
}
579627

580-
ckeys[j] = getKey(ckeys[j]);
628+
ckeys[j] = (isName && isKeyPrefix() ? tableAlias + "." : "") + origin;
581629
}
582630
}
583631

@@ -876,8 +924,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
876924
//fun(arg0,arg1,...)
877925
expression = keys[i];
878926

879-
if (containRaw) {
880-
if (RAW_MAP.containsValue(expression) || "".equals(RAW_MAP.get(expression))) { // newSQLConfig 提前处理好的
927+
if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快
928+
if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的
881929
continue;
882930
}
883931

@@ -892,6 +940,11 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
892940
// }
893941
}
894942

943+
if (expression.length() > 50) {
944+
throw new UnsupportedOperationException("@column:value 的 value 中字符串 " + expression + " 不合法!"
945+
+ "不允许传超过 50 个字符的函数或表达式!请用 @raw 简化传参!");
946+
}
947+
895948

896949
int start = expression.indexOf("(");
897950
int end = 0;
@@ -944,10 +997,10 @@ public String getColumnString(boolean inSQLJoin) throws Exception {
944997
}
945998
else {
946999
// if ((StringUtil.isName(origin) == false || origin.startsWith("_"))) {
947-
if (origin.startsWith("_") || PATTERN_FUNCTION.matcher(origin).matches() == false) {
1000+
if (origin.startsWith("_") || origin.contains("--") || PATTERN_FUNCTION.matcher(origin).matches() == false) {
9481001
throw new IllegalArgumentException("字符 " + ckeys[j] + " 不合法!"
9491002
+ "预编译模式下 @column:\"column0,column1:alias;function0(arg0,arg1,...);function1(...):alias...\""
950-
+ " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
1003+
+ " 中所有 arg 都必须是1个不以 _ 开头的单词 或者符合正则表达式 " + PATTERN_FUNCTION + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!");
9511004
}
9521005
}
9531006
}
@@ -1626,16 +1679,16 @@ else if (key.endsWith("<")) {
16261679

16271680
switch (keyType) {
16281681
case 1:
1629-
return getSearchString(key, value);
1682+
return getSearchString(key, value, rawSQL);
16301683
case -2:
16311684
case 2:
1632-
return getRegExpString(key, value, keyType < 0);
1685+
return getRegExpString(key, value, keyType < 0, rawSQL);
16331686
case 3:
16341687
return getBetweenString(key, value, rawSQL);
16351688
case 4:
16361689
return getRangeString(key, value, rawSQL);
16371690
case 5:
1638-
return getExistsString(key, value);
1691+
return getExistsString(key, value, rawSQL);
16391692
case 6:
16401693
return getContainString(key, value, rawSQL);
16411694
case 7:
@@ -1647,13 +1700,13 @@ else if (key.endsWith("<")) {
16471700
case 10:
16481701
return getCompareString(key, value, "<", rawSQL);
16491702
default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格!
1650-
return getEqualString(key, value);
1703+
return getEqualString(key, value, rawSQL);
16511704
}
16521705
}
16531706

16541707

16551708
@JSONField(serialize = false)
1656-
public String getEqualString(String key, Object value) throws Exception {
1709+
public String getEqualString(String key, Object value, String rawSQL) throws Exception {
16571710
if (JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) {
16581711
throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !");
16591712
}
@@ -1666,7 +1719,7 @@ public String getEqualString(String key, Object value) throws Exception {
16661719
throw new IllegalArgumentException(key + ":value 中key不合法!不支持 ! 以外的逻辑符 !");
16671720
}
16681721

1669-
return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : getValue(value));
1722+
return getKey(key) + (not ? " != " : " = ") + (value instanceof Subquery ? getSubqueryString((Subquery) value) : (rawSQL != null ? rawSQL : getValue(value)));
16701723
}
16711724

16721725
@JSONField(serialize = false)
@@ -1729,7 +1782,10 @@ public AbstractSQLConfig setPreparedValueList(List<Object> preparedValueList) {
17291782
* @throws IllegalArgumentException
17301783
*/
17311784
@JSONField(serialize = false)
1732-
public String getSearchString(String key, Object value) throws IllegalArgumentException {
1785+
public String getSearchString(String key, Object value, String rawSQL) throws IllegalArgumentException {
1786+
if (rawSQL != null) {
1787+
throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !");
1788+
}
17331789
if (value == null) {
17341790
return "";
17351791
}
@@ -1789,7 +1845,10 @@ public String getLikeString(String key, Object value) {
17891845
* @throws IllegalArgumentException
17901846
*/
17911847
@JSONField(serialize = false)
1792-
public String getRegExpString(String key, Object value, boolean ignoreCase) throws IllegalArgumentException {
1848+
public String getRegExpString(String key, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException {
1849+
if (rawSQL != null) {
1850+
throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !");
1851+
}
17931852
if (value == null) {
17941853
return "";
17951854
}
@@ -1925,18 +1984,6 @@ public String getBetweenString(String key, Object start, Object end) throws Ille
19251984

19261985
//{} range <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
19271986

1928-
// * 和 / 不能同时出现,防止 /* */ 段注释! # 和 -- 不能出现,防止行注释! ; 不能出现,防止隔断SQL语句!空格不能出现,防止 CRUD,DROP,SHOW TABLES等语句!
1929-
private static final Pattern PATTERN_RANGE;
1930-
private static final Pattern PATTERN_FUNCTION;
1931-
private static final Pattern PATTERN_HAVING;
1932-
private static final Pattern PATTERN_HAVING_SUFFIX;
1933-
static {
1934-
PATTERN_RANGE = Pattern.compile("^[0-9%!=<>,]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过!
1935-
PATTERN_FUNCTION = Pattern.compile("^[A-Za-z0-9%-_:!=<> ]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值
1936-
PATTERN_HAVING = Pattern.compile("^[A-Za-z0-9%!=<>]+$"); //TODO 改成更好的正则,校验前面为单词,中间为操作符,后面为值
1937-
PATTERN_HAVING_SUFFIX = Pattern.compile("^[0-9%!=<>]+$"); // ^[a-zA-Z0-9_*%!=<>(),"]+$ 导致 exists(select*from(Comment)) 通过!
1938-
}
1939-
19401987

19411988
/**WHERE key > 'key0' AND key <= 'key1' AND ...
19421989
* @param key
@@ -2070,7 +2117,10 @@ public String getInString(String key, Object[] in, boolean not) throws NotExistE
20702117
* @throws NotExistException
20712118
*/
20722119
@JSONField(serialize = false)
2073-
public String getExistsString(String key, Object value) throws Exception {
2120+
public String getExistsString(String key, Object value, String rawSQL) throws Exception {
2121+
if (rawSQL != null) {
2122+
throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !");
2123+
}
20742124
if (value == null) {
20752125
return "";
20762126
}
@@ -2562,6 +2612,22 @@ public static SQLConfig newSQLConfig(RequestMethod method, String table, String
25622612
}
25632613
}
25642614

2615+
if (idIn instanceof List) { // 排除掉 0, 负数, 空字符串 等无效 id 值
2616+
List<?> ids = ((List<?>) idIn);
2617+
List<Object> newIdIn = new ArrayList<>();
2618+
Object d;
2619+
for (int i = 0; i < ids.size(); i++) { //不用 idIn.contains(id) 因为 idIn 里存到很可能是 Integer,id 又是 Long!
2620+
d = ids.get(i);
2621+
if ((d instanceof Number && ((Number) d).longValue() > 0) || (d instanceof String && StringUtil.isNotEmpty(d, true))) {
2622+
newIdIn.add(d);
2623+
}
2624+
}
2625+
if (newIdIn.isEmpty()) {
2626+
throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()");
2627+
}
2628+
idIn = newIdIn;
2629+
}
2630+
25652631
//对id和id{}处理,这两个一定会作为条件
25662632
Object id = request.get(idKey);
25672633
if (id != null) { //null无效
@@ -2786,11 +2852,11 @@ else if (whereList != null && whereList.contains(key)) {
27862852

27872853
List<String> cs = new ArrayList<>();
27882854

2789-
String rawColumnSQL = null;
27902855
List<String> rawList = config.getRaw();
2791-
boolean containRaw = rawList != null && rawList.contains(KEY_COLUMN);
2856+
boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN);
27922857

2793-
if (containRaw) {
2858+
String rawColumnSQL = null;
2859+
if (containColumnRaw) {
27942860
try {
27952861
rawColumnSQL = config.getRawSQL(KEY_COLUMN, column);
27962862
if (rawColumnSQL != null) {
@@ -2809,7 +2875,7 @@ else if (whereList != null && whereList.contains(key)) {
28092875
if (fks != null) {
28102876
String[] ks;
28112877
for (String fk : fks) {
2812-
if (containRaw) {
2878+
if (containColumnRaw) {
28132879
try {
28142880
String rawSQL = config.getRawSQL(KEY_COLUMN, fk);
28152881
if (rawSQL != null) {

0 commit comments

Comments
 (0)