Skip to content

Commit

Permalink
・JEXLをv2.1⇒v3.4へアップデート
Browse files Browse the repository at this point in the history
・カスタム関数の定義個所を式言語の実装個所に集約。
・式言語の接頭語をx:⇒f:に変更
  • Loading branch information
mygreen committed Jul 20, 2024
1 parent a43a5f3 commit 0b36e0c
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 65 deletions.
22 changes: 16 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ XlsMapper is Java Library for mapping Excel sheets to POJO.
<properties>
<java.version>1.8</java.version>
<poi.version>5.1.0</poi.version>
<slf4j.version>1.7.32</slf4j.version>
<spring.version>3.2.11.RELEASE</spring.version>
<jacoco.include.package>com.gh.mygreen.xlsmapper.*</jacoco.include.package>

<!-- SonarQubeの解析から除外したいファイル -->
Expand Down Expand Up @@ -447,7 +447,7 @@ $(document).ready(function() {
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
Expand All @@ -465,8 +465,18 @@ $(document).ready(function() {

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl</artifactId>
<version>2.1.1</version>
<artifactId>commons-jexl3</artifactId>
<version>3.4.0</version>
<exclusions>
<!--
commons-logging v1.3だとPOIでエラーが発生する。
POIのcommons-loggingに合わせるため、JEXLではcommons-loggingの依存を除外する。
-->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Bean Validation 1.1 系を利用する -->
Expand Down Expand Up @@ -514,13 +524,13 @@ $(document).ready(function() {
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.11.RELEASE</version>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.11.RELEASE</version>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>

Expand Down
18 changes: 2 additions & 16 deletions src/main/java/com/gh/mygreen/xlsmapper/Configuration.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package com.gh.mygreen.xlsmapper;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.gh.mygreen.xlsmapper.annotation.XlsSheet;
import com.gh.mygreen.xlsmapper.cellconverter.CellConverterRegistry;
import com.gh.mygreen.xlsmapper.expression.CustomFunctions;
import com.gh.mygreen.xlsmapper.expression.ExpressionLanguageJEXLImpl;
import com.gh.mygreen.xlsmapper.fieldprocessor.FieldProcessorRegistry;
import com.gh.mygreen.xlsmapper.localization.MessageInterpolator;
Expand Down Expand Up @@ -69,25 +66,14 @@ public class Configuration {
private SheetFinder sheetFinder = new SheetFinder();

/** 数式をフォーマットするクラス */
private MessageInterpolator formulaFormatter = new MessageInterpolator();
private MessageInterpolator formulaFormatter = new MessageInterpolator(new ExpressionLanguageJEXLImpl());

/** セルコメントを操作するクラス */
private CellCommentOperator commentOperator = new CellCommentOperator();

/** Beanに対するアノテーションのマッピング情報 */
private AnnotationMappingInfo annotationMapping = null;

public Configuration() {

// 数式をフォーマットする際のEL関数を登録する。
ExpressionLanguageJEXLImpl formulaEL = new ExpressionLanguageJEXLImpl();
Map<String, Object> funcs = new HashMap<>();
funcs.put("x", CustomFunctions.class);
formulaEL.getJexlEngine().setFunctions(funcs);

formulaFormatter.setExpressionLanguage(formulaEL);
}


/**
* 指定したクラスタイプのインスタンスを作成する
* @param clazz
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
package com.gh.mygreen.xlsmapper.expression;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gh.mygreen.xlsmapper.util.ArgUtils;
import com.gh.mygreen.xlsmapper.util.Utils;

/**
* 式言語「JEXL」の実装。
* <p>利用する際には、JEXL2.1のライブラリが必要です。
* <p>利用する際には、JEXL v3.3以上のライブラリが必要です。
* <p>JEXL v3.3から、ELインジェクション対策として、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a>によるEL式中で参照/実行可能なクラスを制限されます。
* <p>独自のCellConverter / FiledProcessosrなどを実装しているが場合は、システムプロパティ {@literal xlsmapper.jexlPermissions} で指定することができます。
* 複数指定する場合はカンマ区切りで指定します。
* </p>
*
* @version 2.3
* @since 1.5
* @author T.TSUCHIE
*
Expand All @@ -22,16 +34,89 @@ public class ExpressionLanguageJEXLImpl implements ExpressionLanguage {

private static final Logger logger = LoggerFactory.getLogger(ExpressionLanguageJEXLImpl.class);

/**
* 本ライブラリでJEXLからアクセス許可するパッケージ指定のパーミッション。
*/
private static final String[] LIB_PERMISSIONS =
{"com.gh.mygreen.xlsmapper.*"};

/**
* 独自のJEXLのパーミッション。
*/
private static final String[] USER_PERMISSIONS;
static {
String value = System.getProperty("xlsmapper.jexlPermissions");
if(Utils.isNotEmpty(value)) {
USER_PERMISSIONS = Arrays.stream(value.split(","))
.map(String::trim)
.filter(String::isEmpty)
.collect(Collectors.toList())
.toArray(new String[0]);

} else {
USER_PERMISSIONS = new String[] {};
}
}

/**
* JEXLのキャッシュサイズ。
* <p>キャッシュする式の個数。
*/
private static final int CACHE_SIZE = 256;

private final JexlEngine jexlEngine;

private final ObjectCache<String, Expression> expressionCache = new ObjectCache<>();
/**
* JEXLのパーミッションを指定するコンストラクタ。
* <p>関数として{@link CustomFunctions}が登録されており、接頭語 {@literal x:}で呼び出し可能です。
*
* @param userPermissions JEXLのパーミッション。
* 詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
*/
public ExpressionLanguageJEXLImpl(final String... userPermissions) {
this(Collections.emptyMap(), userPermissions);

}

public ExpressionLanguageJEXLImpl() {
this(new JexlEngine());
this.jexlEngine.setLenient(true);
this.jexlEngine.setSilent(true);
/**
* JEXLの独自のEL関数とパーミッションを指定するコンストラクタ。
* <p>関数として{@link CustomFunctions}が登録されており、接頭語 {@literal f:}で呼び出し可能です。
*
* @param userFunctions 独自のEL関数を指定します。keyは接頭語、valueはメソッドが定義されたクラス。
* @param userPermissions JEXLのパーミッション。
* 詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
*/
public ExpressionLanguageJEXLImpl(final Map<String, Object> userFunctions, final String... userPermissions) {

// EL式中で使用可能な関数の登録
Map<String, Object> functions = new HashMap<>();
functions.put("f", CustomFunctions.class);

if (Utils.isNotEmpty(userFunctions)) {
functions.putAll(userFunctions);
}

/*
* EL式で本ライブラリのクラス/メソッドのアクセスを許可する。
* ・CustomFunctions以外にも、CellConverter / FieldProcessorでも参照するため。
* ・JEXLv3からサーバーサイド・テンプレート・インジェクション、コマンドインジェクション対策のために、
* 許可されたクラスしか参照できなくなったため、本ライブラリをEL式から参照可能に許可する。
*/
String[] concateedUserPermission = Utils.concat(USER_PERMISSIONS, userPermissions);
JexlPermissions permissions = JexlPermissions.RESTRICTED
.compose(Utils.concat(LIB_PERMISSIONS, concateedUserPermission));

this.jexlEngine = new JexlBuilder()
.namespaces(functions)
.permissions(permissions)
.silent(true)
.strict(false) // JEXLv2相当の文法にする。
.cache(CACHE_SIZE)
.create();

}


/**
* {@link JexlEngine}を指定するコンストラクタ。
* @param jexlEngine JEXLの処理エンジン。
Expand All @@ -52,12 +137,7 @@ public Object evaluate(final String expression, final Map<String, ?> values) {
}

try {
Expression expr = expressionCache.get(expression);
if (expr == null) {
expr = jexlEngine.createExpression(expression);
expressionCache.put(expression, expr);
}

JexlExpression expr = jexlEngine.createExpression(expression);
return expr.evaluate(new MapContext((Map<String, Object>) values));

} catch(Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.gh.mygreen.xlsmapper.localization;

import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gh.mygreen.xlsmapper.expression.CustomFunctions;
import com.gh.mygreen.xlsmapper.expression.ExpressionEvaluationException;
import com.gh.mygreen.xlsmapper.expression.ExpressionLanguage;
import com.gh.mygreen.xlsmapper.expression.ExpressionLanguageJEXLImpl;
Expand Down Expand Up @@ -39,16 +38,13 @@ public class MessageInterpolator {

private ExpressionLanguage expressionLanguage;

/**
* デフォルトのコンストラクタ
* <p>式言語の処理実装として、JEXLの{@link ExpressionLanguageJEXLImpl} が設定されます。
*
*/
public MessageInterpolator() {

// EL式中で使用可能な関数の登録
ExpressionLanguageJEXLImpl el = new ExpressionLanguageJEXLImpl();

Map<String, Object> funcs = new HashMap<>();
funcs.put("f", CustomFunctions.class);
el.getJexlEngine().setFunctions(funcs);

setExpressionLanguage(el);
this.expressionLanguage = new ExpressionLanguageJEXLImpl();

}

Expand Down Expand Up @@ -262,16 +258,16 @@ protected String evaluateExpression(final String expression, final Map<String, ?
context.computeIfAbsent("formatter", key -> new Formatter());

/*
* JEXLで存在しない変数名の場合、nullが帰ってくるため、null判定を行う。
* 以下のケースの時、評価値はnullが返されるため、空文字に変換する。
* ・JEXLで存在しない変数名のとき。
* ・ELインジェクション対象の式のとき
*/
Object eval = expressionLanguage.evaluate(expression, context);
String value = eval == null ? "" : eval.toString();

String evalValue = Objects.toString(expressionLanguage.evaluate(expression, context), "");
if(logger.isTraceEnabled()) {
logger.trace("evaluate expression language: expression='{}' ===> value='{}'", expression, value);
logger.trace("evaluate expression language: expression='{}' ===> value='{}'", expression, evalValue);
}

return value;
return evalValue;
}

/**
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/com/gh/mygreen/xlsmapper/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
Expand All @@ -29,7 +30,7 @@
/**
* ユーティリティクラス。
*
* @version 2.0
* @version 2.3
* @author T.TSUCHIE
* @author Naoki Takezoe
* @author Mitsuyoshi Hasegawa
Expand Down Expand Up @@ -308,6 +309,23 @@ public static boolean isEmpty(final Collection<?> collection) {
public static boolean isNotEmpty(final Collection<?> collection) {
return !isEmpty(collection);
}

/**
* Mapが空か判定する。
* @param map
* @return nullまたはサイズが0のときにtrueを返す。
*/
public static boolean isEmpty(final Map<?, ?> map) {
if(map == null || map.isEmpty()) {
return true;
}

return false;
}

public static boolean isNotEmpty(final Map<?, ?> map) {
return !isEmpty(map);
}

/**
* 配列がが空か判定する。
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.gh.mygreen.xlsmapper.expression;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static com.gh.mygreen.xlsmapper.TestUtils.*;

import static org.junit.Assert.*;

import java.sql.Timestamp;
import java.util.Collections;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
Expand Down Expand Up @@ -120,12 +119,7 @@ public void testEvalate_error_exp() {
@Test
public void testEvalate_function() {

// 関数の登録
Map<String, Object> funcs = new HashMap<>();
funcs.put("x", CustomFunctions.class);
el.getJexlEngine().setFunctions(funcs);

String expression = "x:colToAlpha(columnNumber)";
String expression = "f:colToAlpha(columnNumber)";

Map<String, Object> vars = new HashMap<>();
vars.put("columnNumber", 1);
Expand All @@ -135,5 +129,20 @@ public void testEvalate_function() {

}

/**
* ELインジェクション
*/
@Test
public void test_injection() {

String expression = "''.getClass().forName('java.lang.Runtime').getRuntime().exec('notepad')";

Object eval = el.evaluate(expression, Collections.emptyMap());

// 評価に失敗しnullが返ってくる
assertThat(eval, nullValue());

}


}
Loading

0 comments on commit 0b36e0c

Please sign in to comment.