View Javadoc
1   package com.github.mygreen.supercsv.expression;
2   
3   import java.util.Arrays;
4   import java.util.Collections;
5   import java.util.HashMap;
6   import java.util.Map;
7   import java.util.Objects;
8   import java.util.stream.Collectors;
9   
10  import org.apache.commons.jexl3.JexlBuilder;
11  import org.apache.commons.jexl3.JexlEngine;
12  import org.apache.commons.jexl3.JexlExpression;
13  import org.apache.commons.jexl3.MapContext;
14  import org.apache.commons.jexl3.introspection.JexlPermissions;
15  import org.slf4j.Logger;
16  import org.slf4j.LoggerFactory;
17  
18  import com.github.mygreen.supercsv.util.Utils;
19  
20  
21  /**
22   * 式言語<a href="http://commons.apache.org/proper/commons-jexl/" target="_blank">JEXL(Java Expression Language)</a>の実装。
23   * <p>利用する際には、JEXL v3.3以上のライブラリが必要です。
24   * <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式中で参照/実行可能なクラスを制限されます。
25   * <p>独自のCellProcessorなどを実装しているが場合は、システムプロパティ {@literal supercsv.annotation.jexlPermissions} で指定することができます。
26   *    複数指定する場合はカンマ区切りで指定します。
27   * </p>
28   *
29   * @version 2.4
30   * @since 2.0
31   * @author T.TSUCHIE
32   *
33   */
34  public class ExpressionLanguageJEXLImpl implements ExpressionLanguage {
35      
36      private static final Logger logger = LoggerFactory.getLogger(ExpressionLanguageJEXLImpl.class);
37      
38      /**
39       * システムプロパティ - JEXLをRESTRICTモードで使用するかどうかフラグ。
40       */
41      public static final String PROPERTY_JEXL_RESTRICTED = "supercsv.annotation.jexlRestricted";
42      
43      /**
44       * システムプロパティ - JEXLをRESTRICTモードで使用する場合のパーミッションを指定する
45       */
46      protected static final String[] LIB_PERMISSIONS = {"com.github.mygreen.supercsv.*"};
47      
48      /**
49       * 独自のJEXLのパーミッション。
50       */
51      protected static final String[] USER_PERMISSIONS;
52      static {
53          String value = System.getProperty("supercsv.annotation.jexlPermissions");
54          if(Utils.isNotEmpty(value)) {
55              USER_PERMISSIONS = Arrays.stream(value.split(","))
56                      .map(String::trim)
57                      .filter(Utils::isNotEmpty)
58                      .collect(Collectors.toList())
59                      .toArray(new String[0]);
60              
61          } else {
62              USER_PERMISSIONS = new String[] {};
63          }
64      }
65      
66      /**
67       * JEXLのキャッシュサイズ。
68       * <p>キャッシュする式の個数。
69       */
70      private static final int CACHE_SIZE = 256;
71      
72      private final JexlEngine jexlEngine;
73      
74      /**
75       * JEXLのパーミッションを指定するコンストラクタ。
76       * <p>関数として{@link CustomFunctions}が登録されており、接頭語 {@literal f:}で呼び出し可能です。
77       * 
78       * @param userPermissions JEXLのパーミッション。
79       *        詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
80       */
81      public ExpressionLanguageJEXLImpl(final String... userPermissions) {
82          this(Collections.emptyMap(), true, userPermissions);
83          
84      }
85      
86      /**
87       * JEXLの独自のEL関数とパーミッションを指定するコンストラクタ。
88       * <p>関数として{@link CustomFunctions}が登録されており、接頭語 {@literal f:}で呼び出し可能です。
89       * 
90       * @param userFunctions 独自のEL関数を指定します。keyは接頭語、valueはメソッドが定義されたクラス。
91       * @param userPermissions JEXLのパーミッション。
92       *        詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
93       */
94      public ExpressionLanguageJEXLImpl(final Map<String, Object> userFunctions, final boolean restricted, final String... userPermissions) {
95          
96          this.jexlEngine = new JexlBuilder()
97                  .namespaces(buildNamespace(userFunctions))
98                  .permissions(buildPermissions(restricted, userPermissions))
99                  .silent(true)
100                 .cache(CACHE_SIZE)
101                 .create();
102     }
103     
104     /**
105      * {@link JexlEngine}を指定するコンストラクタ。
106      * @param jexlEngine JEXLの処理エンジン。
107      */
108     public ExpressionLanguageJEXLImpl(final JexlEngine jexlEngine) {
109         this.jexlEngine = jexlEngine;
110     }
111     
112     /**
113      * EL関数の名前空間を組み立て、独自のEL関数を登録します。
114      * 
115      * @param userFunctions 独自のEL関数を指定します。keyは接頭語、valueはメソッドが定義されたクラス。
116      * @return EL関数の名前空間
117      */
118     protected Map<String, Object> buildNamespace(final Map<String, Object> userFunctions) {
119         
120         // EL式中で使用可能な関数の登録
121         Map<String, Object> functions = new HashMap<>();
122         functions.put("f", CustomFunctions.class);
123         
124         if (Utils.isNotEmpty(userFunctions)) {
125             functions.putAll(userFunctions);
126         }
127 
128         
129         return functions;
130     }
131     
132     /**
133      * JEXLのパーミッションを組み立てる。
134      * 
135      * @param userPermissions ユーザー指定のJEXLのパーミッション。
136      *        詳細は、<a href="https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html)">JexlPermissions</a> を参照。
137      * @return JEXLのパーミッション
138      */
139     protected JexlPermissions buildPermissions(final boolean restricted, final String... userPermissions) {
140         
141         final JexlPermissions permissions;
142         if(Utils.toBoolean(System.getProperty(PROPERTY_JEXL_RESTRICTED), restricted)) {
143             /*
144              * EL式で本ライブラリのクラス/メソッドのアクセスを許可する。
145              * ・CustomFunctions以外にも、TextPrinterを実装している各CellProcessorでも参照するため。
146              * ・JEXL3からサーバーサイド・テンプレート・インジェクション、コマンドインジェクション対策のために、
147              *   許可されたクラスしか参照できなくなったため、本ライブラリをEL式から参照可能に許可する。
148              */
149             String[] concateedUserPermission = Utils.concat(USER_PERMISSIONS, userPermissions);
150             permissions = JexlPermissions.RESTRICTED
151                     .compose(Utils.concat(LIB_PERMISSIONS, concateedUserPermission));
152             
153         } else {
154             // パーミッションによる制限を行わない。
155             permissions = JexlPermissions.UNRESTRICTED;
156         }
157         
158         return permissions;
159     }
160     
161     @Override
162     public Object evaluate(final String expression, final Map<String, Object> values) {
163         
164         Objects.requireNonNull(expression, "expression shoud not be null.");
165         Objects.requireNonNull(values, "values shoud not be null.");
166         
167         if(logger.isDebugEnabled()) {
168             logger.debug("Evaluating JEXL expression: {}", expression);
169         }
170         
171         try {
172             JexlExpression expr = jexlEngine.createExpression(expression);
173             return expr.evaluate(new MapContext((Map<String, Object>) values));
174             
175         } catch(Exception ex) {
176             throw new ExpressionEvaluationException(String.format("Evaluating [%s] script with JEXL failed.", expression), ex,
177                     expression, values);
178         }
179     }
180     
181     /**
182      * {@link JexlEngine}を取得する。
183      * @return
184      */
185     public JexlEngine getJexlEngine() {
186         return jexlEngine;
187     }
188     
189 }