View Javadoc
1   package com.jsql.util;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.exception.JSqlException;
5   import com.jsql.model.injection.method.AbstractMethodInjection;
6   import org.apache.commons.lang3.StringUtils;
7   import org.apache.logging.log4j.LogManager;
8   import org.apache.logging.log4j.Logger;
9   import org.json.JSONArray;
10  import org.json.JSONException;
11  import org.json.JSONObject;
12  
13  import java.util.AbstractMap.SimpleEntry;
14  import java.util.ArrayList;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.regex.Pattern;
18  
19  public class JsonUtil {
20      
21      /**
22       * Log4j logger sent to view.
23       */
24      private static final Logger LOGGER = LogManager.getRootLogger();
25  
26      private final InjectionModel injectionModel;
27      
28      public JsonUtil(InjectionModel injectionModel) {
29          this.injectionModel = injectionModel;
30      }
31  
32      public static boolean isJson(String param) {
33          
34          var isJson = false;
35          
36          try {
37              // Test for JSON Object
38              new JSONObject(param);
39              isJson = true;
40              
41          } catch (JSONException exceptionJSONObject) {
42              try {
43                  // Test for JSON Array
44                  new JSONArray(param);
45                  isJson = true;
46                  
47              } catch (JSONException exceptionJSONArray) {
48                  // Not a JSON entity
49              }
50          }
51          
52          return isJson;
53      }
54  
55      public static Object getJson(String param) {
56  
57          Object jsonEntity;  // Will test if current value is a JSON entity
58          
59          try {
60              jsonEntity = new JSONObject(param);  // Test for JSON Object
61          } catch (JSONException exceptionJSONObject) {
62              try {
63                  jsonEntity = new JSONArray(param);  // Test for JSON Array
64              } catch (JSONException exceptionJSONArray) {
65                  jsonEntity = new Object();  // Not a JSON entity
66              }
67          }
68          
69          return jsonEntity;
70      }
71  
72      public static List<SimpleEntry<String, String>> createEntries(Object jsonEntity, String parentName, SimpleEntry<String, String> parentXPath) {
73          
74          List<SimpleEntry<String, String>> attributesXPath = new ArrayList<>();
75          
76          if (jsonEntity instanceof JSONObject) {
77              JsonUtil.scanJsonObject(jsonEntity, parentName, parentXPath, attributesXPath);
78          } else if (jsonEntity instanceof JSONArray) {
79              JsonUtil.scanJsonArray(jsonEntity, parentName, parentXPath, attributesXPath);
80          }
81          
82          return attributesXPath;
83      }
84  
85      private static void scanJsonArray(Object jsonEntity, String parentName, SimpleEntry<String, String> parentXPath, List<SimpleEntry<String, String>> attributesXPath) {
86          
87          var jsonArrayEntity = (JSONArray) jsonEntity;
88          
89          for (var i = 0; i < jsonArrayEntity.length(); i++) {
90              
91              Object value = jsonArrayEntity.get(i);
92              String xpath = parentName +"["+ i +"]";
93              
94              // Not possible to make generic with scanJsonObject() because of JSONArray.put(int) != JSONObject.put(String)
95              if (value instanceof JSONArray || value instanceof JSONObject) {
96                  attributesXPath.addAll(JsonUtil.createEntries(value, xpath, parentXPath));
97              } else if (value instanceof String) {
98                  
99                  SimpleEntry<String, String> stringValue = new SimpleEntry<>(xpath, (String) value);
100                 attributesXPath.add(stringValue);
101                 
102                 if (parentXPath == null) {
103                     jsonArrayEntity.put(i, value.toString().replaceAll(Pattern.quote(InjectionModel.STAR) +"$", StringUtils.EMPTY));
104                 } else if (stringValue.equals(parentXPath)) {
105                     jsonArrayEntity.put(i, value + InjectionModel.STAR);
106                 }
107             }
108         }
109     }
110 
111     private static void scanJsonObject(Object jsonEntity, String parentName, SimpleEntry<String, String> parentXPath, List<SimpleEntry<String, String>> attributesXPath) {
112         
113         var jsonObjectEntity = (JSONObject) jsonEntity;
114         
115         Iterator<?> keys = jsonObjectEntity.keys();
116         
117         while (keys.hasNext()) {
118             
119             String key = (String) keys.next();
120             var value = jsonObjectEntity.get(key);
121             String xpath = parentName +"."+ key;
122             
123             // Not possible to make generic with scanJsonObject() because of JSONArray.put(int) != JSONObject.put(String)
124             if (value instanceof JSONArray || value instanceof JSONObject) {
125                 attributesXPath.addAll(JsonUtil.createEntries(value, xpath, parentXPath));
126             } else if (value instanceof String) {
127                 
128                 SimpleEntry<String, String> stringValue = new SimpleEntry<>(xpath, (String) value);
129                 attributesXPath.add(stringValue);
130                 
131                 if (parentXPath == null) {
132                     jsonObjectEntity.put(key, value.toString().replaceAll(Pattern.quote(InjectionModel.STAR) +"$", StringUtils.EMPTY));
133                 } else if (stringValue.equals(parentXPath)) {
134                     jsonObjectEntity.put(key, value + InjectionModel.STAR);
135                 }
136             }
137         }
138     }
139     
140     public boolean testJsonParam(AbstractMethodInjection methodInjection, SimpleEntry<String, String> paramStar) {
141         
142         var hasFoundInjection = false;
143         
144         // Remove STAR at the end of parameter, STAR will be added inside json data instead
145         paramStar.setValue(paramStar.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY));
146         
147         // Will test if current value is a JSON entity
148         Object jsonEntity = JsonUtil.getJson(paramStar.getValue());
149         
150         // Define a tree of JSON attributes with path as the key: root.a => value of a
151         List<SimpleEntry<String, String>> attributesJson = JsonUtil.createEntries(jsonEntity, "root", null);
152         
153         // Loop through each JSON values
154         for (SimpleEntry<String, String> parentXPath: attributesJson) {
155             
156             JsonUtil.createEntries(jsonEntity, "root", null);  // Erase previously defined *
157             JsonUtil.createEntries(jsonEntity, "root", parentXPath);  // Add * to current parameter's value
158 
159             paramStar.setValue(jsonEntity.toString());  // Replace param value by marked one
160             
161             try {
162                 LOGGER.log(
163                     LogLevelUtil.CONSOLE_INFORM,
164                     "Checking JSON {} parameter {}={}",
165                     methodInjection::name,
166                     parentXPath::getKey,
167                     () -> parentXPath.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY)
168                 );
169                 
170                 // Test current JSON value marked with * for injection
171                 // Keep original param
172                 hasFoundInjection = this.injectionModel.getMediatorStrategy().testStrategies(paramStar);
173                 
174                 // Injection successful
175                 break;
176                 
177             } catch (JSqlException e) {
178                 
179                 // Injection failure
180                 LOGGER.log(
181                     LogLevelUtil.CONSOLE_ERROR,
182                     String.format(
183                         "No injection found for JSON %s parameter %s=%s",
184                         methodInjection.name(),
185                         parentXPath.getKey(),
186                         parentXPath.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY)
187                     )
188                 );
189             } finally {
190                 
191                 // Erase * at the end of each params
192                 // TODO useless
193                 methodInjection.getParams()
194                     .forEach(e -> e.setValue(
195                         e.getValue().replaceAll(Pattern.quote(InjectionModel.STAR) +"$", StringUtils.EMPTY)
196                     ));
197                 
198                 // Erase * from JSON if failure
199                 if (!hasFoundInjection) {
200                     paramStar.setValue(
201                         paramStar.getValue().replace(InjectionModel.STAR, StringUtils.EMPTY)
202                     );
203                 }
204             }
205         }
206         
207         return hasFoundInjection;
208     }
209 }