View Javadoc

1   /*
2    * This file is part of hyphenType. hyphenType is free software: you can
3    * redistribute it and/or modify it under the terms of the GNU General Public
4    * License as published by the Free Software Foundation, either version 3 of the
5    * License, or (at your option) any later version. hyphenType is distributed in
6    * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
8    * the GNU General Public License for more details. You should have received a
9    * copy of the GNU General Public License along with hyphenType. If not, see
10   * <http://www.gnu.org/licenses/>.
11   */
12  package org.hyphenType.optionprocessors.lib;
13  
14  import java.lang.annotation.ElementType;
15  import java.lang.annotation.Retention;
16  import java.lang.annotation.RetentionPolicy;
17  import java.lang.annotation.Target;
18  import java.lang.reflect.InvocationTargetException;
19  import java.lang.reflect.Method;
20  
21  import javax.script.ScriptEngine;
22  import javax.script.ScriptEngineManager;
23  import javax.script.ScriptException;
24  
25  import org.hyphenType.datastructure.Options;
26  import org.hyphenType.datastructure.annotations.ArgumentsObject;
27  import org.hyphenType.datastructure.annotations.Option;
28  import org.hyphenType.datastructure.annotations.OptionArgument;
29  import org.hyphenType.datastructure.annotations.SimpleArgument;
30  import org.hyphenType.documentation.Description;
31  import org.hyphenType.optionprocessors.ArgumentsProcessorEngine;
32  import org.hyphenType.util.DefaultAnnotation;
33  
34  /**
35   * Rules must be written in JavaScript and should refer to the names of the
36   * option methods. Return types of methods apply. I.e., if the return type is
37   * int, variables referring to this method will be considered int, similarly for
38   * boolean. One can turn all variables into booleans (converting non zero values
39   * to false) by choosing {@link BooleanValidator#allBooleans()} to be true.<br/>
40   * <br/>
41   * <strong>WARNING! SECURITY RISK.</strong> There is a risk of script injection
42   * if you allow the annotations to be loaded from properties files. The attacker
43   * needs to add a properties file with the same name as the options interface to
44   * the classpath. This malicious properties file may override the rules of a
45   * {@link BooleanValidator} annotation. This trick will cause this engine to
46   * execute the malicious scripts. Although this risk may look scary at first,
47   * the sort of access the attacker would need to add a file to the classpath is
48   * equivalent to the sort of access the attacker would need to have to replace
49   * files in another user's space. In other words, the security risk described
50   * here is a minor risk compared to the breach one needs in order to exploit the
51   * risk itself.
52   * 
53   * @author Aurelio Akira M. Matsui
54   */
55  public class BooleanValidatorEngine implements ArgumentsProcessorEngine<BooleanValidatorEngine.BooleanValidator> {
56  
57      @Retention(RetentionPolicy.RUNTIME)
58      @Target(ElementType.TYPE)
59      public @interface BooleanValidator {
60          @Description("The set of rules to be checked")
61          Rule[] rules();
62  
63          @Description("Whether the processor will output messages detailing the execution. Good for debugging.")
64          boolean verbose() default false;
65  
66          @Description("Whether the processor will output failure messages.")
67          boolean showFailureMessages() default false;
68  
69          @Description("Whether the processor will threat integers as booleans. I.e. if zero will be considered FALSE and all other values will be TRUE.")
70          boolean allBooleans() default false;
71      }
72  
73      @Retention(RetentionPolicy.RUNTIME)
74      @Target(ElementType.TYPE)
75      public @interface Rule {
76          String rule();
77  
78          String failureCodeName() default "";
79  
80          int failureCode() default 0;
81  
82          String failureMessage() default "";
83      }
84  
85      @SuppressWarnings("unchecked")
86      @Override
87      public <T extends Options<?>> void process(Class<T> interfaceClass, T options, BooleanValidator config) {
88  
89          ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
90          
91          for (Method method : interfaceClass.getMethods()) {
92              if (method.isAnnotationPresent(Option.class)) {
93                  try {
94                      Object val;
95                      if (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class))
96                          val = (Boolean) method.invoke(options);
97                      else {
98                          if (config.allBooleans())
99                              val = ((Integer) method.invoke(options)) != 0;
100                         else
101                             val = (Integer) method.invoke(options);
102                     }
103                     engine.put(method.getName(), val);
104                     continue;
105                 } catch (IllegalArgumentException e) {
106                     // TODO Auto-generated catch block
107                     e.printStackTrace();
108                 } catch (IllegalAccessException e) {
109                     // TODO Auto-generated catch block
110                     e.printStackTrace();
111                 } catch (InvocationTargetException e) {
112                     // TODO Auto-generated catch block
113                     e.printStackTrace();
114                 }
115             }
116             if (method.isAnnotationPresent(OptionArgument.class) || method.isAnnotationPresent(SimpleArgument.class)) {
117                 try {
118                     engine.put(method.getName(), method.invoke(options));
119                     continue;
120                 } catch (IllegalArgumentException e) {
121                     // TODO Auto-generated catch block
122                     e.printStackTrace();
123                 } catch (IllegalAccessException e) {
124                     // TODO Auto-generated catch block
125                     e.printStackTrace();
126                 } catch (InvocationTargetException e) {
127                     // TODO Auto-generated catch block
128                     e.printStackTrace();
129                 }
130             }
131         }
132 
133         for (Rule rule : config.rules()) {
134             try {
135                 int returnCode;
136                 if (!rule.failureCodeName().equals("")) {
137                     ArgumentsObject ao = DefaultAnnotation.getAnnotation(interfaceClass, ArgumentsObject.class);
138                     returnCode = Enum.valueOf((Class<? extends Enum>) ao.statusCodeEnum(), rule.failureCodeName()).ordinal();
139                 } else {
140                     returnCode = rule.failureCode();
141                 }
142 
143                 boolean val;
144                 Object obj = engine.eval(rule.rule());
145                 if (obj instanceof Double)
146                     val = ((Double) obj).doubleValue() > 0;
147                 else if (obj instanceof Boolean)
148                     val = (Boolean) obj;
149                 else {
150                     if (config.verbose()) {
151                         if (obj == null)
152                             System.err.println("Rule \"" + rule + "\" did not return anything. Should return a boolean or a number.");
153                         else
154                             System.err.println("Rule \"" + rule + "\" returned a strange object type: " + obj.getClass().getName());
155                     }
156                     options.exit(returnCode);
157                     return;
158                 }
159                 if (val) {
160                     if (config.verbose())
161                         System.err.println(String.format("Rule checking failure: \"%s\"", rule.rule()));
162                     if (config.showFailureMessages())
163                         System.err.print(rule.failureMessage());
164                     options.exit(returnCode);
165                     return;
166                 }
167 
168                 if (config.verbose())
169                     System.out.println(String.format("Rule checking success: \"%s\"", rule.rule()));
170             } catch (ScriptException e) {
171                 // TODO Auto-generated catch block
172                 e.printStackTrace();
173             }
174         }
175     }
176 }