1 /*
2 * $Id$
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21 package org.apache.struts.util;
22
23 import java.lang.reflect.InvocationTargetException;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.Enumeration;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.regex.Pattern;
34
35 import org.apache.commons.beanutils.BeanUtils;
36 import org.apache.commons.beanutils.PropertyUtils;
37 import org.apache.struts.Globals;
38 import org.apache.struts.action.ActionForm;
39 import org.apache.struts.action.ActionMapping;
40 import org.apache.struts.action.ActionRedirect;
41 import org.apache.struts.action.ActionServlet;
42 import org.apache.struts.action.ActionServletWrapper;
43 import org.apache.struts.config.ActionConfig;
44 import org.apache.struts.config.FormBeanConfig;
45 import org.apache.struts.config.ForwardConfig;
46 import org.apache.struts.config.ModuleConfig;
47 import org.apache.struts.upload.FormFile;
48 import org.apache.struts.upload.MultipartRequestHandler;
49 import org.apache.struts.upload.MultipartRequestWrapper;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import jakarta.servlet.ServletContext;
54 import jakarta.servlet.ServletException;
55 import jakarta.servlet.http.HttpServletRequest;
56 import jakarta.servlet.http.HttpSession;
57
58 /**
59 * <p>General purpose utility methods related to processing a servlet request
60 * in the Struts controller framework.</p>
61 *
62 * @version $Rev$ $Date$
63 */
64 public class RequestUtils {
65 // ------------------------------------------------------- Static Variables
66
67 /**
68 * The {@code Log} instance for this class.
69 */
70 private final static Logger LOG =
71 LoggerFactory.getLogger(RequestUtils.class);
72
73 /**
74 * <p>Pattern matching 'class' access.</p>
75 */
76 protected static final Pattern CLASS_ACCESS_PATTERN = Pattern
77 .compile("(.*\\.|^|.*|\\[('|\"))class(\\.|('|\")]|\\[).*",
78 Pattern.CASE_INSENSITIVE);
79
80 // --------------------------------------------------------- Public Methods
81
82 /**
83 * <p>Create and return an absolute URL for the specified context-relative
84 * path, based on the server and context information in the specified
85 * request.</p>
86 *
87 * @param request The servlet request we are processing
88 * @param path The context-relative path (must start with '/')
89 * @return absolute URL based on context-relative path
90 * @throws MalformedURLException if we cannot create an absolute URL
91 */
92 public static URL absoluteURL(HttpServletRequest request, String path)
93 throws MalformedURLException {
94 return (new URL(serverURL(request), request.getContextPath() + path));
95 }
96
97 /**
98 * <p>Return the <code>Class</code> object for the specified fully
99 * qualified class name, from this web application's class loader.</p>
100 *
101 * @param className Fully qualified class name to be loaded
102 * @return Class object
103 * @throws ClassNotFoundException if the class cannot be found
104 */
105 public static Class<?> applicationClass(String className)
106 throws ClassNotFoundException {
107 return applicationClass(className, null);
108 }
109
110 /**
111 * <p>Return the <code>Class</code> object for the specified fully
112 * qualified class name, from this web application's class loader.</p>
113 *
114 * @param className Fully qualified class name to be loaded
115 * @param classLoader The desired classloader to use
116 * @return Class object
117 * @throws ClassNotFoundException if the class cannot be found
118 */
119 public static Class<?> applicationClass(String className,
120 ClassLoader classLoader)
121 throws ClassNotFoundException {
122 if (classLoader == null) {
123 // Look up the class loader to be used
124 classLoader = Thread.currentThread().getContextClassLoader();
125
126 if (classLoader == null) {
127 classLoader = RequestUtils.class.getClassLoader();
128 }
129 }
130
131 // Attempt to load the specified class
132 return (classLoader.loadClass(className));
133 }
134
135 /**
136 * <p>Return a new instance of the specified fully qualified class name,
137 * after loading the class from this web application's class loader. The
138 * specified class <strong>MUST</strong> have a public zero-arguments
139 * constructor.</p>
140 *
141 * @param className Fully qualified class name to use
142 * @return new instance of class
143 * @throws ClassNotFoundException if the class cannot be found
144 * @throws IllegalAccessException if the class or its constructor is not
145 * accessible
146 * @throws InstantiationException if this class represents an abstract
147 * class, an interface, an array class, a
148 * primitive type, or void
149 * @throws InstantiationException if this class has no zero-arguments
150 * constructor
151 */
152 public static Object applicationInstance(String className)
153 throws ClassNotFoundException, IllegalAccessException,
154 InstantiationException {
155 return applicationInstance(className, null);
156 }
157
158 /**
159 * <p>Return a new instance of the specified fully qualified class name,
160 * after loading the class from this web application's class loader. The
161 * specified class <strong>MUST</strong> have a public zero-arguments
162 * constructor.</p>
163 *
164 * @param className Fully qualified class name to use
165 * @param classLoader The desired classloader to use
166 * @return new instance of class
167 * @throws ClassNotFoundException if the class cannot be found
168 * @throws IllegalAccessException if the class or its constructor is not
169 * accessible
170 * @throws InstantiationException if this class represents an abstract
171 * class, an interface, an array class, a
172 * primitive type, or void
173 * @throws InstantiationException if this class has no zero-arguments
174 * constructor
175 */
176 public static Object applicationInstance(String className,
177 ClassLoader classLoader)
178 throws ClassNotFoundException, IllegalAccessException,
179 InstantiationException {
180
181 try {
182 return applicationClass(className, classLoader)
183 .getDeclaredConstructor()
184 .newInstance();
185 } catch (IllegalArgumentException | InvocationTargetException
186 | NoSuchMethodException | SecurityException
187 | ClassNotFoundException e) {
188 InstantiationException e2 =
189 new InstantiationException("Error creating " + className + " instance");
190 e2.initCause(e);
191 throw e2;
192 }
193 }
194
195 /**
196 * <p>Create (if necessary) and return an <code>ActionForm</code> instance
197 * appropriate for this request. If no <code>ActionForm</code> instance
198 * is required, return <code>null</code>.</p>
199 *
200 * @param request The servlet request we are processing
201 * @param mapping The action mapping for this request
202 * @param moduleConfig The configuration for this module
203 * @param servlet The action servlet
204 * @return ActionForm instance associated with this request
205 */
206 public static ActionForm createActionForm(HttpServletRequest request,
207 ActionMapping mapping, ModuleConfig moduleConfig, ActionServlet servlet) {
208 // Is there a form bean associated with this mapping?
209 String attribute = mapping.getAttribute();
210
211 if (attribute == null) {
212 return (null);
213 }
214
215 // Look up the form bean configuration information to use
216 String name = mapping.getName();
217 FormBeanConfig config = moduleConfig.findFormBeanConfig(name);
218
219 if (config == null) {
220 LOG.warn("No FormBeanConfig found under '{}'", name);
221
222 return (null);
223 }
224
225 ActionForm instance =
226 lookupActionForm(request, attribute, mapping.getScope());
227
228 // Can we recycle the existing form bean instance (if there is one)?
229 if ((instance != null) && config.canReuse(instance)) {
230 return (instance);
231 }
232
233 return createActionForm(config, servlet);
234 }
235
236 private static ActionForm lookupActionForm(HttpServletRequest request,
237 String attribute, String scope) {
238 // Look up any existing form bean instance
239 LOG.debug(" Looking for ActionForm bean instance in scope '{}' "
240 + "under attribute key '{}'", scope, attribute);
241
242 ActionForm instance = null;
243 HttpSession session = null;
244
245 if ("request".equals(scope)) {
246 instance = (ActionForm) request.getAttribute(attribute);
247 } else {
248 session = request.getSession();
249 instance = (ActionForm) session.getAttribute(attribute);
250 }
251
252 return (instance);
253 }
254
255 /**
256 * <p>Create and return an <code>ActionForm</code> instance appropriate to
257 * the information in <code>config</code>.</p>
258 *
259 * <p>Does not perform any checks to see if an existing ActionForm exists
260 * which could be reused.</p>
261 *
262 * @param config The configuration for the Form bean which is to be
263 * created.
264 * @param servlet The action servlet
265 * @return ActionForm instance associated with this request
266 */
267 public static ActionForm createActionForm(FormBeanConfig config,
268 ActionServlet servlet) {
269 if (config == null) {
270 return (null);
271 }
272
273 ActionForm instance = null;
274
275 // Create and return a new form bean instance
276 try {
277 instance = config.createActionForm(servlet);
278
279 LOG.atDebug()
280 .setMessage(" Creating new {} instance of type '{}'")
281 .addArgument(() -> config.getDynamic() ? "DynaActionForm" : "ActionForm")
282 .addArgument(config.getType())
283 .log();
284 LOG.trace(" --> {}", instance);
285 } catch (Throwable t) {
286 LOG.atError()
287 .setMessage(() -> servlet.getInternal().getMessage("formBean",
288 config.getType()))
289 .setCause(t)
290 .log();
291 }
292
293 return (instance);
294 }
295
296 /**
297 * <p>Retrieves the servlet mapping pattern for the specified {@link ActionServlet}.</p>
298 *
299 * @return the servlet mapping
300 * @see Globals#SERVLET_KEY
301 * @since Struts 1.3.6
302 */
303 public static String getServletMapping(ActionServlet servlet) {
304 ServletContext servletContext = servlet.getServletConfig().getServletContext();
305 return (String)servletContext.getAttribute(Globals.SERVLET_KEY);
306 }
307
308 /**
309 * <p>Look up and return current user locale, based on the specified
310 * parameters.</p>
311 *
312 * @param request The request used to lookup the Locale
313 * @param locale Name of the session attribute for our user's Locale. If
314 * this is <code>null</code>, the default locale key is
315 * used for the lookup.
316 * @return current user locale
317 * @since Struts 1.2
318 */
319 public static Locale getUserLocale(HttpServletRequest request, String locale) {
320 Locale userLocale = null;
321 HttpSession session = request.getSession(false);
322
323 if (locale == null) {
324 locale = Globals.LOCALE_KEY;
325 }
326
327 // Only check session if sessions are enabled
328 if (session != null) {
329 userLocale = (Locale) session.getAttribute(locale);
330 }
331
332 if (userLocale == null) {
333 // Returns Locale based on Accept-Language header or the server default
334 userLocale = request.getLocale();
335 }
336
337 return userLocale;
338 }
339
340 /**
341 * <p>Populate the properties of the specified JavaBean from the specified
342 * HTTP request, based on matching each parameter name against the
343 * corresponding JavaBeans "property setter" methods in the bean's class.
344 * Suitable conversion is done for argument types as described under
345 * <code>convert()</code>.</p>
346 *
347 * @param bean The JavaBean whose properties are to be set
348 * @param request The HTTP request whose parameters are to be used to
349 * populate bean properties
350 * @throws ServletException if an exception is thrown while setting
351 * property values
352 */
353 public static void populate(Object bean, HttpServletRequest request)
354 throws ServletException {
355 populate(bean, null, null, request);
356 }
357
358 /**
359 * <p>Populate the properties of the specified JavaBean from the specified
360 * HTTP request, based on matching each parameter name (plus an optional
361 * prefix and/or suffix) against the corresponding JavaBeans "property
362 * setter" methods in the bean's class. Suitable conversion is done for
363 * argument types as described under <code>setProperties</code>.</p>
364 *
365 * <p>If you specify a non-null <code>prefix</code> and a non-null
366 * <code>suffix</code>, the parameter name must match
367 * <strong>both</strong> conditions for its value(s) to be used in
368 * populating bean properties. If the request's content type is
369 * "multipart/form-data" and the method is "POST", the
370 * <code>HttpServletRequest</code> object will be wrapped in a
371 * <code>MultipartRequestWrapper</code> object.</p>
372 *
373 * @param bean The JavaBean whose properties are to be set
374 * @param prefix The prefix (if any) to be prepend to bean property names
375 * when looking for matching parameters
376 * @param suffix The suffix (if any) to be appended to bean property
377 * names when looking for matching parameters
378 * @param request The HTTP request whose parameters are to be used to
379 * populate bean properties
380 * @throws ServletException if an exception is thrown while setting
381 * property values
382 */
383 public static void populate(Object bean, String prefix, String suffix,
384 HttpServletRequest request)
385 throws ServletException {
386 // Build a list of relevant request parameters from this request
387 HashMap<String, Object> properties = new HashMap<>();
388
389 // Iterator of parameter names
390 Enumeration<String> names = null;
391
392 // Map for multipart parameters
393 Map<String, Object> multipartParameters = null;
394
395 String contentType = request.getContentType();
396 String method = request.getMethod();
397 boolean isMultipart = false;
398
399 if (bean instanceof ActionForm) {
400 ((ActionForm) bean).setMultipartRequestHandler(null);
401 }
402
403 MultipartRequestHandler multipartHandler = null;
404 if ((contentType != null)
405 && (contentType.startsWith("multipart/form-data"))
406 && (method.equalsIgnoreCase("POST"))) {
407 // Get the ActionServletWrapper from the form bean
408 ActionServletWrapper servlet;
409
410 if (bean instanceof ActionForm) {
411 servlet = ((ActionForm) bean).getServletWrapper();
412 } else {
413 throw new ServletException("bean that's supposed to be "
414 + "populated from a multipart request is not of type "
415 + "\"org.apache.struts.action.ActionForm\", but type "
416 + "\"" + bean.getClass().getName() + "\"");
417 }
418
419 // Obtain a MultipartRequestHandler
420 multipartHandler = getMultipartHandler(request);
421
422 if (multipartHandler != null) {
423 isMultipart = true;
424
425 // Set servlet and mapping info
426 servlet.setServletFor(multipartHandler);
427 multipartHandler.setMapping((ActionMapping) request
428 .getAttribute(Globals.MAPPING_KEY));
429
430 // Initialize multipart request class handler
431 multipartHandler.handleRequest(request);
432
433 //stop here if the maximum length has been exceeded
434 Boolean maxLengthExceeded =
435 (Boolean) request.getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
436
437 if ((maxLengthExceeded != null)
438 && (maxLengthExceeded.booleanValue())) {
439 ((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
440 return;
441 }
442
443 //retrieve form values and put into properties
444 multipartParameters =
445 getAllParametersForMultipartRequest(request,
446 multipartHandler);
447 names = Collections.enumeration(multipartParameters.keySet());
448 }
449 }
450
451 if (!isMultipart) {
452 names = request.getParameterNames();
453 }
454
455 while (names.hasMoreElements()) {
456 String name = names.nextElement();
457 String stripped = name;
458
459 if (prefix != null) {
460 if (!stripped.startsWith(prefix)) {
461 continue;
462 }
463
464 stripped = stripped.substring(prefix.length());
465 }
466
467 if (suffix != null) {
468 if (!stripped.endsWith(suffix)) {
469 continue;
470 }
471
472 stripped =
473 stripped.substring(0, stripped.length() - suffix.length());
474 }
475
476 Object parameterValue = null;
477
478 if (isMultipart) {
479 parameterValue = multipartParameters.get(name);
480 parameterValue = rationalizeMultipleFileProperty(bean, name, parameterValue);
481 } else {
482 parameterValue = request.getParameterValues(name);
483 }
484
485 // 2014/05/13 - CVE-2014-0114 security problem patch.
486 // Author: NTT DATA Corporation
487 if (CLASS_ACCESS_PATTERN.matcher(stripped).matches()) {
488 // this log output is only for detection of invalid parameters and not an integral part of the bug fix
489 LOG.trace("ignore parameter: paramName={}", stripped);
490 continue;
491 }
492
493 // Populate parameters, except "standard" struts attributes
494 // such as 'org.apache.struts.action.CANCEL'
495 if (!(stripped.startsWith("org.apache.struts."))) {
496 properties.put(stripped, parameterValue);
497 }
498 }
499
500 // Set the corresponding properties of our bean
501 try {
502 BeanUtils.populate(bean, properties);
503 } catch (Exception e) {
504 throw new ServletException("BeanUtils.populate", e);
505 } finally {
506 if (multipartHandler != null) {
507 // Set the multipart request handler for our ActionForm.
508 // If the bean isn't an ActionForm, an exception would have been
509 // thrown earlier, so it's safe to assume that our bean is
510 // in fact an ActionForm.
511 ((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
512 }
513 }
514 }
515
516 /**
517 * <p>Populates the parameters of the specified ActionRedirect from
518 * the specified HTTP request.</p>
519 *
520 * @param redirect The ActionRedirect whose parameters are to be set
521 * @param request The HTTP request whose parameters are to be used
522 * @since Struts 1.4
523 */
524 public static void populate(ActionRedirect redirect, HttpServletRequest request) {
525 assert (redirect != null) : "redirect is required";
526 assert (request != null) : "request is required";
527
528 Enumeration<String> e = request.getParameterNames();
529 while (e.hasMoreElements()) {
530 String name = e.nextElement();
531 String[] values = request.getParameterValues(name);
532 redirect.addParameter(name, values);
533 }
534 }
535
536 /**
537 * If the specified property has multiple FormFile objects, but the form
538 * bean type can only process a single FormFile object, then this method
539 * attempts to take this situation into account.
540 *
541 * @param bean the FormBean-object
542 * @param name the name of the property
543 * @param parameterValue the value of the property
544 *
545 * @return parameterValue with expected type
546 *
547 * @throws ServletException if the introspection has any errors.
548 */
549 private static Object rationalizeMultipleFileProperty(Object bean,
550 String name, Object parameterValue) throws ServletException {
551
552 if (!(parameterValue instanceof FormFile[])) {
553 return parameterValue;
554 }
555
556 final FormFile[] formFileValue = (FormFile[]) parameterValue;
557 try {
558 final Class<?> propertyType =
559 PropertyUtils.getPropertyType(bean, name);
560
561 if (propertyType == null) {
562 return parameterValue;
563 }
564
565 if (List.class.isAssignableFrom(propertyType)) {
566 return Arrays.asList(formFileValue);
567 }
568
569 if (FormFile.class.isAssignableFrom(propertyType)) {
570 switch (formFileValue.length) {
571 case 0:
572 LOG.error("FormFile-parameter \"{}\" for FormBean "
573 + "\"{}\" has unexpected length of null!",
574 name, bean.getClass().getName());
575 return null;
576
577 case 1:
578 return formFileValue[0];
579
580 default:
581 LOG.error("FormFile-parameter \"{}\" for FormBean "
582 + "\"{}\" should have an Array or a List as "
583 + "method parameter!", name,
584 bean.getClass().getName());
585 return formFileValue[0];
586 }
587 }
588 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
589 throw new ServletException(e);
590 }
591
592 // no changes
593 return parameterValue;
594 }
595
596 /**
597 * <p>Try to locate a multipart request handler for this request. First,
598 * look for a mapping-specific handler stored for us under an attribute.
599 * If one is not present, use the global multipart handler, if there is
600 * one.</p>
601 *
602 * @param request The HTTP request for which the multipart handler should
603 * be found.
604 * @return the multipart handler to use, or null if none is found.
605 * @throws ServletException if any exception is thrown while attempting to
606 * locate the multipart handler.
607 */
608 private static MultipartRequestHandler getMultipartHandler(
609 HttpServletRequest request)
610 throws ServletException {
611 MultipartRequestHandler multipartHandler = null;
612 String multipartClass =
613 (String) request.getAttribute(Globals.MULTIPART_KEY);
614
615 request.removeAttribute(Globals.MULTIPART_KEY);
616
617 // Try to initialize the mapping specific request handler
618 if (multipartClass != null) {
619 try {
620 multipartHandler =
621 (MultipartRequestHandler) applicationInstance(multipartClass);
622 } catch (ClassNotFoundException cnfe) {
623 LOG.error("MultipartRequestHandler class \"{}\" "
624 + "in mapping class not found, "
625 + "defaulting to global multipart class", multipartClass);
626 } catch (InstantiationException ie) {
627 LOG.error("InstantiationException when instantiating "
628 + "MultipartRequestHandler \"{}\", "
629 + "defaulting to global multipart class, exception: {}",
630 multipartClass, ie.getMessage());
631 } catch (IllegalAccessException iae) {
632 LOG.error("IllegalAccessException when instantiating "
633 + "MultipartRequestHandler \"{}\", "
634 + "defaulting to global multipart class, exception: {}",
635 multipartClass, iae.getMessage());
636 }
637
638 if (multipartHandler != null) {
639 return multipartHandler;
640 }
641 }
642
643 ModuleConfig moduleConfig =
644 ModuleUtils.getInstance().getModuleConfig(request);
645
646 multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
647
648 // Try to initialize the global request handler
649 if (multipartClass != null) {
650 try {
651 multipartHandler =
652 (MultipartRequestHandler) applicationInstance(multipartClass);
653 } catch (ClassNotFoundException cnfe) {
654 throw new ServletException("Cannot find multipart class \""
655 + multipartClass + "\"", cnfe);
656 } catch (InstantiationException ie) {
657 throw new ServletException(
658 "InstantiationException when instantiating "
659 + "multipart class \"" + multipartClass + "\"", ie);
660 } catch (IllegalAccessException iae) {
661 throw new ServletException(
662 "IllegalAccessException when instantiating "
663 + "multipart class \"" + multipartClass + "\"", iae);
664 }
665
666 if (multipartHandler != null) {
667 return multipartHandler;
668 }
669 }
670
671 return multipartHandler;
672 }
673
674 /**
675 * <p>Create a <code>Map</code> containing all of the parameters supplied
676 * for a multipart request, keyed by parameter name. In addition to text
677 * and file elements from the multipart body, query string parameters are
678 * included as well.</p>
679 *
680 * @param request The (wrapped) HTTP request whose parameters are
681 * to be added to the map.
682 * @param multipartHandler The multipart handler used to parse the
683 * request.
684 * @return the map containing all parameters for this multipart request.
685 */
686 private static Map<String, Object> getAllParametersForMultipartRequest(
687 HttpServletRequest request, MultipartRequestHandler multipartHandler) {
688
689 final Map<String, Object> parameters =
690 new HashMap<>(multipartHandler.getAllElements());
691
692 if (request instanceof MultipartRequestWrapper) {
693 request =
694 (HttpServletRequest) ((MultipartRequestWrapper) request)
695 .getRequest();
696
697 parameters.putAll(request.getParameterMap());
698 } else {
699 LOG.debug("Gathering multipart parameters for unwrapped request");
700 }
701
702 return parameters;
703 }
704
705 /**
706 * <p>Compute the printable representation of a URL, leaving off the
707 * scheme/host/port part if no host is specified. This will typically be
708 * the case for URLs that were originally created from relative or
709 * context-relative URIs.</p>
710 *
711 * @param url URL to render in a printable representation
712 * @return printable representation of a URL
713 */
714 public static String printableURL(URL url) {
715 if (url.getHost() != null) {
716 return (url.toString());
717 }
718
719 String file = url.getFile();
720 String ref = url.getRef();
721
722 if (ref == null) {
723 return (file);
724 } else {
725 StringBuilder sb = new StringBuilder(file);
726
727 sb.append('#');
728 sb.append(ref);
729
730 return (sb.toString());
731 }
732 }
733
734 /**
735 * <p>Return the context-relative URL that corresponds to the specified
736 * {@link ActionConfig}, relative to the module associated with the
737 * current modules's {@link ModuleConfig}.</p>
738 *
739 * @param request The servlet request we are processing
740 * @param action ActionConfig to be evaluated
741 * @param pattern URL pattern used to map the controller servlet
742 * @return context-relative URL relative to the module
743 * @since Struts 1.1
744 */
745 public static String actionURL(HttpServletRequest request,
746 ActionConfig action, String pattern) {
747 StringBuilder sb = new StringBuilder();
748
749 if (pattern.endsWith("/*")) {
750 sb.append(pattern.substring(0, pattern.length() - 2));
751 sb.append(action.getPath());
752 } else if (pattern.startsWith("*.")) {
753 ModuleConfig appConfig =
754 ModuleUtils.getInstance().getModuleConfig(request);
755
756 sb.append(appConfig.getPrefix());
757 sb.append(action.getPath());
758 sb.append(pattern.substring(1));
759 } else {
760 throw new IllegalArgumentException(pattern);
761 }
762
763 return sb.toString();
764 }
765
766 /**
767 * <p>Return the context-relative URL that corresponds to the specified
768 * <code>ForwardConfig</code>. The URL is calculated based on the
769 * properties of the {@link ForwardConfig} instance as follows:</p>
770 *
771 * <ul>
772 *
773 *
774 * <li>If the <code>contextRelative</code> property is set, it is assumed
775 * that the <code>path</code> property contains a path that is already
776 * context-relative:
777 *
778 * <ul>
779 *
780 * <li>If the <code>path</code> property value starts with a slash, it is
781 * returned unmodified.</li> <li>If the <code>path</code> property value
782 * does not start with a slash, a slash is prepended.</li>
783 *
784 * </ul></li>
785 *
786 * <li>Acquire the <code>forwardPattern</code> property from the
787 * <code>ControllerConfig</code> for the application module used to
788 * process this request. If no pattern was configured, default to a
789 * pattern of <code>$M$P</code>, which is compatible with the hard-coded
790 * mapping behavior in Struts 1.0.</li>
791 *
792 * <li>Process the acquired <code>forwardPattern</code>, performing the
793 * following substitutions:
794 *
795 * <ul>
796 *
797 * <li><strong>$M</strong> - Replaced by the module prefix for the
798 * application module processing this request.</li>
799 *
800 * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
801 * the specified {@link ForwardConfig}, prepended with a slash if it does
802 * not start with one.</li>
803 *
804 * <li><strong>$$</strong> - Replaced by a single dollar sign
805 * character.</li>
806 *
807 * <li><strong>$x</strong> (where "x" is any charater not listed above) -
808 * Silently omit these two characters from the result value. (This has
809 * the side effect of causing all other $+letter combinations to be
810 * reserved.)</li>
811 *
812 * </ul></li>
813 *
814 * </ul>
815 *
816 * @param request The servlet request we are processing
817 * @param forward ForwardConfig to be evaluated
818 * @return context-relative URL
819 * @since Struts 1.1
820 */
821 public static String forwardURL(HttpServletRequest request,
822 ForwardConfig forward) {
823 return forwardURL(request, forward, null);
824 }
825
826 /**
827 * <p>Return the context-relative URL that corresponds to the specified
828 * <code>ForwardConfig</code>. The URL is calculated based on the
829 * properties of the {@link ForwardConfig} instance as follows:</p>
830 *
831 * <ul>
832 *
833 * <li>If the <code>contextRelative</code> property is set, it is assumed
834 * that the <code>path</code> property contains a path that is already
835 * context-relative: <ul>
836 *
837 * <li>If the <code>path</code> property value starts with a slash, it is
838 * returned unmodified.</li> <li>If the <code>path</code> property value
839 * does not start with a slash, a slash is prepended.</li>
840 *
841 * </ul></li>
842 *
843 * <li>Acquire the <code>forwardPattern</code> property from the
844 * <code>ControllerConfig</code> for the application module used to
845 * process this request. If no pattern was configured, default to a
846 * pattern of <code>$M$P</code>, which is compatible with the hard-coded
847 * mapping behavior in Struts 1.0.</li>
848 *
849 * <li>Process the acquired <code>forwardPattern</code>, performing the
850 * following substitutions: <ul> <li><strong>$M</strong> - Replaced by the
851 * module prefix for the application module processing this request.</li>
852 *
853 * <li><strong>$P</strong> - Replaced by the <code>path</code> property of
854 * the specified {@link ForwardConfig}, prepended with a slash if it does
855 * not start with one.</li>
856 *
857 * <li><strong>$$</strong> - Replaced by a single dollar sign
858 * character.</li>
859 *
860 * <li><strong>$x</strong> (where "x" is any charater not listed above) -
861 * Silently omit these two characters from the result value. (This has
862 * the side effect of causing all other $+letter combinations to be
863 * reserved.)</li>
864 *
865 * </ul></li></ul>
866 *
867 * @param request The servlet request we are processing
868 * @param forward ForwardConfig to be evaluated
869 * @param moduleConfig Base forward on this module config.
870 * @return context-relative URL
871 * @since Struts 1.2
872 */
873 public static String forwardURL(HttpServletRequest request,
874 ForwardConfig forward, ModuleConfig moduleConfig) {
875 //load the current moduleConfig, if null
876 if (moduleConfig == null) {
877 moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
878 }
879
880 String path = forward.getPath();
881
882 //load default prefix
883 String prefix = moduleConfig.getPrefix();
884
885 //override prefix if supplied by forward
886 if (forward.getModule() != null) {
887 prefix = forward.getModule();
888
889 if ("/".equals(prefix)) {
890 prefix = "";
891 }
892 }
893
894 StringBuilder sb = new StringBuilder();
895
896 // Calculate a context relative path for this ForwardConfig
897 String forwardPattern =
898 moduleConfig.getControllerConfig().getForwardPattern();
899
900 if (forwardPattern == null) {
901 // Performance optimization for previous default behavior
902 sb.append(prefix);
903
904 // smoothly insert a '/' if needed
905 if (!path.startsWith("/")) {
906 sb.append("/");
907 }
908
909 sb.append(path);
910 } else {
911 boolean dollar = false;
912
913 for (int i = 0; i < forwardPattern.length(); i++) {
914 char ch = forwardPattern.charAt(i);
915
916 if (dollar) {
917 switch (ch) {
918 case 'M':
919 sb.append(prefix);
920
921 break;
922
923 case 'P':
924
925 // add '/' if needed
926 if (!path.startsWith("/")) {
927 sb.append("/");
928 }
929
930 sb.append(path);
931
932 break;
933
934 case '$':
935 sb.append('$');
936
937 break;
938
939 default:
940 ; // Silently swallow
941 }
942
943 dollar = false;
944
945 continue;
946 } else if (ch == '$') {
947 dollar = true;
948 } else {
949 sb.append(ch);
950 }
951 }
952 }
953
954 return (sb.toString());
955 }
956
957 /**
958 * <p>Return the URL representing the current request. This is equivalent
959 * to {@code HttpServletRequest.getRequestURL} in Servlet 2.3.</p>
960 *
961 * @param request The servlet request we are processing
962 * @return URL representing the current request
963 * @throws MalformedURLException if a URL cannot be created
964 */
965 public static URL requestURL(HttpServletRequest request)
966 throws MalformedURLException {
967 StringBuilder url = requestToServerUriStringBuilder(request);
968
969 return (new URL(url.toString()));
970 }
971
972 /**
973 * <p>Return the URL representing the scheme, server, and port number of
974 * the current request. Server-relative URLs can be created by simply
975 * appending the server-relative path (starting with '/') to this.</p>
976 *
977 * @param request The servlet request we are processing
978 * @return URL representing the scheme, server, and port number of the
979 * current request
980 * @throws MalformedURLException if a URL cannot be created
981 */
982 public static URL serverURL(HttpServletRequest request)
983 throws MalformedURLException {
984 StringBuilder url = requestToServerStringBuilder(request);
985
986 return (new URL(url.toString()));
987 }
988
989 /**
990 * <p>Return the string representing the scheme, server, and port number
991 * of the current request. Server-relative URLs can be created by simply
992 * appending the server-relative path (starting with '/') to this.</p>
993 *
994 * @param request The servlet request we are processing
995 * @return URL representing the scheme, server, and port number of the
996 * current request
997 * @since Struts 1.2.0
998 */
999 public static StringBuilder requestToServerUriStringBuilder(
1000 HttpServletRequest request) {
1001 StringBuilder serverUri =
1002 createServerUriStringBuilder(request.getScheme(),
1003 request.getServerName(), request.getServerPort(),
1004 request.getRequestURI());
1005
1006 return serverUri;
1007 }
1008
1009 /**
1010 * <p>Return {@code StringBuilder} representing the scheme, server,
1011 * and port number of the current request. Server-relative URLs can be
1012 * created by simply appending the server-relative path (starting with
1013 * '/') to this.</p>
1014 *
1015 * @param request The servlet request we are processing
1016 * @return URL representing the scheme, server, and port number of the
1017 * current request
1018 * @since Struts 1.2.0
1019 */
1020 public static StringBuilder requestToServerStringBuilder(
1021 HttpServletRequest request) {
1022 return createServerStringBuilder(request.getScheme(),
1023 request.getServerName(), request.getServerPort());
1024 }
1025
1026 /**
1027 * <p>Return {@code StringBuilder} representing the scheme, server,
1028 * and port number of the current request.</p>
1029 *
1030 * @param scheme The scheme name to use
1031 * @param server The server name to use
1032 * @param port The port value to use
1033 * @return StringBuilder in the form scheme: server: port
1034 * @since Struts 1.2.0
1035 */
1036 public static StringBuilder createServerStringBuilder(String scheme,
1037 String server, int port) {
1038 StringBuilder url = new StringBuilder();
1039
1040 if (port < 0) {
1041 port = 80; // Work around java.net.URL bug
1042 }
1043
1044 url.append(scheme);
1045 url.append("://");
1046 url.append(server);
1047
1048 if ((scheme.equals("http") && (port != 80))
1049 || (scheme.equals("https") && (port != 443))) {
1050 url.append(':');
1051 url.append(port);
1052 }
1053
1054 return url;
1055 }
1056
1057 /**
1058 * <p>Return {@code StringBuilder} representing the scheme, server,
1059 * and port number of the current request.</p>
1060 *
1061 * @param scheme The scheme name to use
1062 * @param server The server name to use
1063 * @param port The port value to use
1064 * @param uri The uri value to use
1065 * @return StringBuilder in the form scheme: server: port
1066 * @since Struts 1.2.0
1067 */
1068 public static StringBuilder createServerUriStringBuilder(String scheme,
1069 String server, int port, String uri) {
1070 StringBuilder serverUri = createServerStringBuilder(scheme, server, port);
1071
1072 serverUri.append(uri);
1073
1074 return serverUri;
1075 }
1076
1077 /**
1078 * <p>Returns the true path of the destination action if the specified forward
1079 * is an action-aliased URL. This method version forms the URL based on
1080 * the current request; selecting the current module if the forward does not
1081 * explicitly contain a module path.</p>
1082 *
1083 * @param forward the forward config
1084 * @param request the current request
1085 * @param servlet the servlet handling the current request
1086 * @return the context-relative URL of the action if the forward has an action identifier; otherwise <code>null</code>.
1087 * @since Struts 1.3.6
1088 */
1089 public static String actionIdURL(ForwardConfig forward, HttpServletRequest request, ActionServlet servlet) {
1090 ModuleConfig moduleConfig = null;
1091 if (forward.getModule() != null) {
1092 String prefix = forward.getModule();
1093 moduleConfig = ModuleUtils.getInstance().getModuleConfig(prefix, servlet.getServletContext());
1094 } else {
1095 moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
1096 }
1097 return actionIdURL(forward.getPath(), moduleConfig, servlet);
1098 }
1099
1100 /**
1101 * <p>Returns the true path of the destination action if the specified forward
1102 * is an action-aliased URL. This method version forms the URL based on
1103 * the specified module.
1104 *
1105 * @param originalPath the action-aliased path
1106 * @param moduleConfig the module config for this request
1107 * @param servlet the servlet handling the current request
1108 * @return the context-relative URL of the action if the path has an action identifier; otherwise <code>null</code>.
1109 * @since Struts 1.3.6
1110 */
1111 public static String actionIdURL(String originalPath, ModuleConfig moduleConfig, ActionServlet servlet) {
1112 if (originalPath.startsWith("http") || originalPath.startsWith("/")) {
1113 return null;
1114 }
1115
1116 // Split the forward path into the resource and query string;
1117 // it is possible a forward (or redirect) has added parameters.
1118 String actionId = null;
1119 String qs = null;
1120 int qpos = originalPath.indexOf("?");
1121 if (qpos == -1) {
1122 actionId = originalPath;
1123 } else {
1124 actionId = originalPath.substring(0, qpos);
1125 qs = originalPath.substring(qpos);
1126 }
1127
1128 // Find the action of the given actionId
1129 ActionConfig actionConfig = moduleConfig.findActionConfigId(actionId);
1130 if (actionConfig == null) {
1131 LOG.debug("No actionId found for {}", actionId);
1132 return null;
1133 }
1134
1135 String path = actionConfig.getPath();
1136 String mapping = RequestUtils.getServletMapping(servlet);
1137 StringBuilder actionIdPath = new StringBuilder();
1138
1139 // Form the path based on the servlet mapping pattern
1140 if (mapping.startsWith("*")) {
1141 actionIdPath.append(path);
1142 actionIdPath.append(mapping.substring(1));
1143 } else if (mapping.startsWith("/")) { // implied ends with a *
1144 mapping = mapping.substring(0, mapping.length() - 1);
1145 if (mapping.endsWith("/") && path.startsWith("/")) {
1146 actionIdPath.append(mapping);
1147 actionIdPath.append(path.substring(1));
1148 } else {
1149 actionIdPath.append(mapping);
1150 actionIdPath.append(path);
1151 }
1152 } else {
1153 LOG.warn("Unknown servlet mapping pattern");
1154 actionIdPath.append(path);
1155 }
1156
1157 // Lastly add any query parameters (the ? is part of the query string)
1158 if (qs != null) {
1159 actionIdPath.append(qs);
1160 }
1161
1162 // Return the path
1163 LOG.debug("{} unaliased to {}", originalPath, actionIdPath);
1164 return actionIdPath.toString();
1165 }
1166
1167 /**
1168 * Determines whether the current request is forwarded.
1169 *
1170 * @param request current HTTP request
1171 * @return true if the request is forwarded; otherwise false
1172 * @since Struts 1.4
1173 */
1174 public static boolean isRequestForwarded(HttpServletRequest request) {
1175 return (request.getAttribute("jakarta.servlet.forward.request_uri") != null);
1176 }
1177
1178 /**
1179 * Determines whether the current request is included.
1180 *
1181 * @param request current HTTP request
1182 * @return true if the request is included; otherwise false
1183 * @since Struts 1.4
1184 */
1185 public static boolean isRequestIncluded(HttpServletRequest request) {
1186 return (request.getAttribute("jakarta.servlet.include.request_uri") != null);
1187 }
1188
1189 /**
1190 * Verifies whether current request is forwarded from one action to
1191 * another or not.
1192 *
1193 * @param request current HTTP request
1194 * @return true if the request is chained; otherwise false
1195 * @since Struts 1.4
1196 */
1197 public static boolean isRequestChained(HttpServletRequest request) {
1198 return (request.getAttribute(Globals.CHAIN_KEY) != null);
1199 }
1200 }