1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts.scripting;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.UnsupportedEncodingException;
26 import java.net.URLDecoder;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Enumeration;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Properties;
36 import java.util.concurrent.ConcurrentHashMap;
37
38 import javax.script.Bindings;
39 import javax.script.ScriptContext;
40 import javax.script.ScriptEngineFactory;
41 import javax.script.ScriptEngineManager;
42 import javax.script.ScriptException;
43
44 import org.apache.struts.action.Action;
45 import org.apache.struts.action.ActionErrors;
46 import org.apache.struts.action.ActionForm;
47 import org.apache.struts.action.ActionForward;
48 import org.apache.struts.action.ActionMapping;
49 import org.apache.struts.action.ActionMessages;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import jakarta.servlet.ServletContext;
54 import jakarta.servlet.http.HttpServletRequest;
55 import jakarta.servlet.http.HttpServletResponse;
56 import jakarta.servlet.http.HttpSession;
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 public class ScriptAction extends Action {
114 private static final long serialVersionUID = -383996253054413439L;
115
116
117
118
119 private final static Logger LOG =
120 LoggerFactory.getLogger(ScriptAction.class);
121
122
123 private static final ScriptEngineManager SCRIPT_ENGINE_MANAGER = new ScriptEngineManager();
124
125
126 protected static final String PROPS_PATH = "/struts-scripting.properties";
127
128
129 protected static final String ENGINE_BASE = "struts-scripting.engine.";
130
131
132 protected static final String FILTERS_BASE = "struts-scripting.filters.";
133
134
135 private static ScriptContextFilter[] filters = null;
136
137
138 private ConcurrentHashMap<String, Script> scripts = new ConcurrentHashMap<>();
139
140 static {
141 final Properties props = new Properties();
142
143 InputStream in = null;
144 String propsPath = PROPS_PATH;
145 try {
146 in = ScriptAction.class.getClassLoader().getResourceAsStream(propsPath);
147 if (in == null) {
148 propsPath = "/struts-bsf.properties";
149 in = ScriptAction.class.getClassLoader().getResourceAsStream(propsPath);
150 if (in != null) {
151 LOG.warn("The struts-bsf.properties file has been "
152 + "deprecated. Please use "
153 + "struts-scripting.properties instead.");
154 } else {
155 LOG.warn("struts-scripting.properties not found, using "
156 + "default engine mappings.");
157 }
158 }
159
160 if (in != null) {
161 props.load(in);
162 }
163 } catch (Exception ex) {
164 LOG.warn("Unable to load struts-scripting.properties, using "
165 + " default engine mappings.");
166 } finally {
167 if (in != null) {
168 try {
169 in.close();
170 } catch (IOException ioe) {
171 LOG.warn("Error closing '{}'", propsPath, ioe);
172 }
173 }
174 }
175
176 final List<ScriptEngineFactory> allSefs = SCRIPT_ENGINE_MANAGER.getEngineFactories();
177
178 final int pos = ENGINE_BASE.length();
179 for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
180 final String propName = e.nextElement().toString();
181 if (propName.startsWith(ENGINE_BASE) && propName.endsWith(".extensions")) {
182 final String name = propName.substring(pos, propName.indexOf('.', pos));
183
184 ScriptEngineFactory[] sefs =
185 allSefs.stream().filter((sef) -> sef.getNames().contains(name))
186 .toArray(ScriptEngineFactory[]::new);
187
188 if (sefs.length == 0) {
189 LOG.warn("No ScriptEngineFactory found - name: '{}'", name);
190 continue;
191 } else if (sefs.length > 1) {
192 LOG.warn("More than one ScriptEngineFactory found, taking the first one - name: '{}'", name);
193 }
194
195 final ScriptEngineFactory sef = sefs[0];
196 LOG.info("Found ScriptingEngineFactory - name: {} language: {} {}",
197 name, sef.getLanguageName(), sef.getLanguageVersion());
198
199 final String propValue = props.getProperty(propName).trim();
200 final String[] exts = propValue.split(",");
201 if (exts.length == 0) {
202 continue;
203 }
204
205 LOG.atInfo().log(() -> {
206 final StringBuilder sb = new StringBuilder();
207 sb.append("Registering extension");
208 if (exts.length > 1) {
209 sb.append('s');
210 }
211 sb
212 .append(" to ScriptingEngineFactory - name: '")
213 .append(name)
214 .append("' ext");
215
216 if (exts.length > 1) {
217 sb.append('s');
218 }
219 sb.append(": ");
220 if (exts.length == 1) {
221 sb
222 .append('\'')
223 .append(exts[0])
224 .append('\'');
225 } else {
226 sb.append(Arrays.toString(exts));
227 }
228 return sb.toString();
229 });
230
231 for (String ext : exts) {
232 ext = ext.trim();
233 if (!ext.isEmpty()) {
234 SCRIPT_ENGINE_MANAGER.registerEngineExtension(ext, sef);
235 }
236 }
237 }
238 }
239 filters = loadFilters(props);
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255 public ActionForward execute(ActionMapping mapping,
256 ActionForm form,
257 HttpServletRequest request,
258 HttpServletResponse response)
259 throws Exception {
260
261 final Map<String, String> params = new LinkedHashMap<>();
262 final String scriptName = parseScriptName(mapping.getParameter(), params);
263 if (scriptName == null) {
264 LOG.error("No script specified in the parameter attribute");
265 throw new Exception("No script specified");
266 }
267
268 LOG.debug("Executing script: {}", scriptName);
269
270 HttpSession session = request.getSession();
271 ServletContext application = getServlet().getServletContext();
272
273 Script script = loadScript(scriptName, application);
274
275 Bindings bindings = script.getBindings();
276 bindings.putAll(params);
277
278 bindings.put("request", request);
279
280 bindings.put("response", response);
281
282 if (session == null) {
283 LOG.debug("HTTP session is null");
284 } else {
285 bindings.put("session", session);
286 }
287
288 bindings.put("application", application);
289
290 bindings.put("log", LOG);
291 StrutsInfo struts = new StrutsInfo(this, mapping, form,
292 getResources(request));
293 bindings.put("struts", struts);
294
295 final ScriptContext scriptContext = script.scriptEngine.getContext();
296 for (ScriptContextFilter filter : filters) {
297 filter.apply(scriptContext);
298 }
299
300 script.eval();
301
302 ActionForward af = struts.getForward();
303 return af;
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317 protected String parseScriptName(String url, Map<String, String> params) {
318
319 LOG.debug("Parsing {}", url);
320
321 if (url == null) {
322 return null;
323 }
324
325 final String[] parsed = url.split("\\?", 2);
326
327 if (parsed.length == 0) {
328 return null;
329 }
330
331 if (parsed.length == 1) {
332 LOG.debug("No query string: {}", parsed[0]);
333 return parsed[0];
334 }
335
336 LOG.debug("Found a query string");
337
338 final String[] args = parsed[1].split("&");
339 for (String arg : args) {
340 final int i = arg.indexOf('=');
341 String key = urlDecode(i > 0 ? arg.substring(0, i) : arg);
342
343 while (params.containsKey(key)) {
344 LOG.warn("Script variable {} already exists", key);
345 key = "_" + key;
346 }
347
348 final String value = i > 0 && arg.length() > i + 1
349 ? urlDecode(arg.substring(i + 1))
350 : null;
351
352 params.put(key, value);
353 LOG.debug("Registering param {} with value {}",
354 key, value);
355 }
356
357 return parsed[0];
358 }
359
360
361
362
363
364
365
366
367
368
369
370 private String urlDecode(final String s) {
371 if (s == null) {
372 return null;
373 }
374
375 try {
376 return URLDecoder.decode(s, StandardCharsets.UTF_8.toString());
377 } catch (UnsupportedEncodingException e) {
378
379 LOG.error("URL-Decode: ", e);
380 }
381
382 return null;
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398 protected Script loadScript(final String name, final ServletContext context)
399 throws IOException, ScriptException {
400
401 final Script script = scripts.compute(name, (key, oldValue) -> {
402 if (oldValue == null) {
403 return new Script(SCRIPT_ENGINE_MANAGER, context, key);
404 }
405
406 oldValue.checkNewContent();
407 return oldValue;
408 });
409
410 try {
411 script.checkExceptions();
412 } catch (IOException e) {
413 LOG.error("Unable to load script: {}", script.name, e);
414 throw e;
415 } catch (ScriptException e) {
416 LOG.error("Unable to compile script: {}", script.name, e);
417 throw e;
418 }
419 return script;
420 }
421
422
423
424
425
426
427
428
429 protected static ScriptContextFilter[] loadFilters(Properties props) {
430 ArrayList<ScriptContextFilter> list = new ArrayList<>();
431 for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
432 final String propName = e.nextElement().toString().trim();
433 if (propName.startsWith(FILTERS_BASE) && propName.endsWith("class")) {
434 String name = propName.substring(FILTERS_BASE.length(),
435 propName.indexOf(".", FILTERS_BASE.length()));
436 String clazz = props.getProperty(propName).trim();
437 try {
438 Class<? extends ScriptContextFilter> cls =
439 Class.forName(clazz).asSubclass(ScriptContextFilter.class);
440 ScriptContextFilter f = cls.getDeclaredConstructor().newInstance();
441 f.init(name, props);
442 list.add(f);
443 LOG.info("Loaded {} filter: {}", name, clazz);
444 } catch (Exception ex) {
445 LOG.error("Unable to load {} filter: {}", name, clazz);
446 }
447 }
448 }
449 return list.toArray(new ScriptContextFilter[0]);
450 }
451
452
453
454
455
456
457
458
459
460
461 public void saveToken(HttpServletRequest req) {
462 super.saveToken(req);
463 }
464
465
466
467
468
469
470
471 public boolean isCancelled(HttpServletRequest req) {
472 return super.isCancelled(req);
473 }
474
475
476
477
478
479
480
481 public boolean isTokenValid(HttpServletRequest req) {
482 return super.isTokenValid(req);
483 }
484
485
486
487
488
489
490 public void resetToken(HttpServletRequest req) {
491 super.resetToken(req);
492 }
493
494
495
496
497
498
499
500 public Locale getLocale(HttpServletRequest req) {
501 return super.getLocale(req);
502 }
503
504
505
506
507
508
509
510 public void saveMessages(HttpServletRequest req, ActionMessages mes) {
511 super.saveMessages(req, mes);
512 }
513
514
515
516
517
518
519
520
521
522
523 @Deprecated
524 public void saveErrors(HttpServletRequest req, ActionErrors errs) {
525 super.saveErrors(req, errs);
526 }
527 }