001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.util; 029 030import org.opencms.flex.CmsFlexRequest; 031import org.opencms.flex.CmsFlexResponse; 032import org.opencms.i18n.CmsEncoder; 033import org.opencms.json.JSONArray; 034import org.opencms.json.JSONException; 035import org.opencms.json.JSONObject; 036import org.opencms.jsp.CmsJspActionElement; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.site.CmsSiteMatcher; 040 041import java.io.IOException; 042import java.io.UnsupportedEncodingException; 043import java.net.URI; 044import java.net.URLDecoder; 045import java.nio.charset.Charset; 046import java.util.ArrayList; 047import java.util.Enumeration; 048import java.util.HashMap; 049import java.util.Iterator; 050import java.util.List; 051import java.util.Map; 052import java.util.Map.Entry; 053 054import org.apache.commons.fileupload2.core.DiskFileItem; 055import org.apache.commons.fileupload2.core.DiskFileItemFactory; 056import org.apache.commons.fileupload2.core.FileUploadException; 057import org.apache.commons.fileupload2.jakarta.servlet5.JakartaServletDiskFileUpload; 058import org.apache.commons.fileupload2.jakarta.servlet5.JakartaServletFileUpload; 059import org.apache.commons.logging.Log; 060 061import com.google.common.collect.ArrayListMultimap; 062import com.google.common.collect.Multimap; 063 064import jakarta.servlet.ServletException; 065import jakarta.servlet.ServletRequest; 066import jakarta.servlet.ServletResponse; 067import jakarta.servlet.http.Cookie; 068import jakarta.servlet.http.HttpServletRequest; 069import jakarta.servlet.http.HttpServletRequestWrapper; 070import jakarta.servlet.http.HttpServletResponse; 071import jakarta.servlet.http.HttpServletResponseWrapper; 072import jakarta.servlet.http.HttpSession; 073 074/** 075 * Provides utility functions for dealing with values a <code>{@link HttpServletRequest}</code>.<p> 076 * 077 * @since 6.0.0 078 */ 079public final class CmsRequestUtil { 080 081 /** Request attribute that contains the original error code. */ 082 public static final String ATTRIBUTE_ERRORCODE = "org.opencms.util.CmsErrorCode"; 083 084 /** Flag to enable / disable backlink checks. */ 085 public static boolean backlinkCheckEnabled = true; 086 087 /** HTTP Accept Header for the cms:device-tag. */ 088 public static final String HEADER_ACCEPT = "Accept"; 089 090 /** HTTP Header "Accept-Ranges". */ 091 public static final String HEADER_ACCEPT_RANGES = "Accept-Ranges"; 092 093 /** HTTP Accept-Charset Header for internal requests used during static export. */ 094 public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; 095 096 /** HTTP Accept-Language Header for internal requests used during static export. */ 097 public static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language"; 098 099 /** HTTP Header "Cache-Control". */ 100 public static final String HEADER_CACHE_CONTROL = "Cache-Control"; 101 102 /** HTTP Header "Connection". */ 103 public static final String HEADER_CONNECTION = "Connection"; 104 105 /** The "Content-Disposition" http header. */ 106 public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition"; 107 108 /** The "Content-Length" http header. */ 109 public static final String HEADER_CONTENT_LENGTH = "Content-Length"; 110 111 /** The "Content-Range" http header. */ 112 public static final String HEADER_CONTENT_RANGE = "Content-Range"; 113 114 /** The "Content-Type" http header. */ 115 public static final String HEADER_CONTENT_TYPE = "Content-Type"; 116 117 /** HTTP Header "ETag". */ 118 public static final String HEADER_ETAG = "ETag"; 119 120 /** HTTP Header "Expires". */ 121 public static final String HEADER_EXPIRES = "Expires"; 122 123 /** HTTP Header "If-Range". */ 124 public static final String HEADER_IF_RANGE = "If-Range"; 125 126 /** HTTP Header "If-Modified-Since". */ 127 public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"; 128 129 /** HTTP Header "If-None-Match". */ 130 public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; 131 132 /** The Header that stores the session id (used by OpenCms upload applet). */ 133 public static final String HEADER_JSESSIONID = "JSESSIONID"; 134 135 /** HTTP Header "Last-Modified". */ 136 public static final String HEADER_LAST_MODIFIED = "Last-Modified"; 137 138 /** HTTP Header "Location". */ 139 public static final String HEADER_LOCATION = "Location"; 140 141 /** HTTP Header for internal requests used during static export. */ 142 public static final String HEADER_OPENCMS_EXPORT = "OpenCms-Export"; 143 144 /** HTTP Header "Pragma". */ 145 public static final String HEADER_PRAGMA = "Pragma"; 146 147 /** HTTP Header "Range". */ 148 public static final String HEADER_RANGE = "Range"; 149 150 /** HTTP Header "Server". */ 151 public static final String HEADER_SERVER = "Server"; 152 153 /** HTTP Header "user-agent". */ 154 public static final String HEADER_USER_AGENT = "user-agent"; 155 156 /** HTTP Header value "max-age=" (for "Cache-Control"). */ 157 public static final String HEADER_VALUE_MAX_AGE = "max-age="; 158 159 /** HTTP Header value "must-revalidate" (for "Cache-Control"). */ 160 public static final String HEADER_VALUE_MUST_REVALIDATE = "must-revalidate"; 161 162 /** HTTP Header value "no-cache" (for "Cache-Control"). */ 163 public static final String HEADER_VALUE_NO_CACHE = "no-cache"; 164 165 /** HTTP Header value "no-store" (for "Cache-Control"). */ 166 public static final String HEADER_VALUE_NO_STORE = "no-store"; 167 168 /** HTTP Header "WWW-Authenticate". */ 169 public static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; 170 171 /** Identifier for x-forwarded-for (i.e. proxied) request headers. */ 172 public static final String HEADER_X_FORWARDED_FOR = "x-forwarded-for"; 173 174 /** Assignment char between parameter name and values. */ 175 public static final String PARAMETER_ASSIGNMENT = "="; 176 177 /** Delimiter char between parameters. */ 178 public static final String PARAMETER_DELIMITER = "&"; 179 180 /** Delimiter char between url and query. */ 181 public static final String URL_DELIMITER = "?"; 182 183 /** The prefix for &. */ 184 private static final String AMP = "amp;"; 185 186 /** The log object for this class. */ 187 private static final Log LOG = CmsLog.getLog(CmsRequestUtil.class); 188 189 /** 190 * Default constructor (empty), private because this class has only 191 * static methods.<p> 192 */ 193 private CmsRequestUtil() { 194 195 // empty 196 } 197 198 /** 199 * Appends a request parameter to the given URL.<p> 200 * 201 * This method takes care about the adding the parameter as an additional 202 * parameter (appending <code>&param=value</code>) or as the first parameter 203 * (appending <code>?param=value</code>).<p> 204 * 205 * @param url the URL where to append the parameter to 206 * @param paramName the paramter name to append 207 * @param paramValue the parameter value to append 208 * 209 * @return the URL with the given parameter appended 210 */ 211 public static String appendParameter(String url, String paramName, String paramValue) { 212 213 if (CmsStringUtil.isEmpty(url)) { 214 return null; 215 } 216 int pos = url.indexOf(URL_DELIMITER); 217 StringBuffer result = new StringBuffer(256); 218 result.append(url); 219 if (pos >= 0) { 220 // url already has parameters 221 result.append(PARAMETER_DELIMITER); 222 } else { 223 // url does not have parameters 224 result.append(URL_DELIMITER); 225 } 226 result.append(paramName); 227 result.append(PARAMETER_ASSIGNMENT); 228 result.append(paramValue); 229 return result.toString(); 230 } 231 232 /** 233 * Appends a map of request parameters to the given URL.<p> 234 * 235 * The map can contains values of <code>String[]</code> or 236 * simple <code>String</code> values.<p> 237 * 238 * This method takes care about the adding the parameter as an additional 239 * parameter (appending <code>&param=value</code>) or as the first parameter 240 * (appending <code>?param=value</code>).<p> 241 * 242 * @param url the URL where to append the parameter to 243 * @param params the parameters to append 244 * @param encode if <code>true</code>, the parameter values are encoded before they are appended 245 * 246 * @return the URL with the given parameter appended 247 */ 248 public static String appendParameters(String url, Map<String, String[]> params, boolean encode) { 249 250 if (CmsStringUtil.isEmpty(url)) { 251 return null; 252 } 253 if ((params == null) || params.isEmpty()) { 254 return url; 255 } 256 int pos = url.indexOf(URL_DELIMITER); 257 StringBuffer result = new StringBuffer(256); 258 result.append(url); 259 if (pos >= 0) { 260 // url already has parameters 261 result.append(PARAMETER_DELIMITER); 262 } else { 263 // url does not have parameters 264 result.append(URL_DELIMITER); 265 } 266 // ensure all values are of type String[] 267 Iterator<Map.Entry<String, String[]>> i = params.entrySet().iterator(); 268 while (i.hasNext()) { 269 Map.Entry<String, String[]> entry = i.next(); 270 String key = entry.getKey(); 271 Object value = entry.getValue(); 272 // generics where added later, so make sure that the value really is a String[] 273 String[] values = value instanceof String[] ? (String[])value : new String[] {value.toString()}; 274 for (int j = 0; j < values.length; j++) { 275 String strValue = values[j]; 276 if (encode) { 277 strValue = CmsEncoder.encode(strValue); 278 } 279 result.append(key); 280 result.append(PARAMETER_ASSIGNMENT); 281 result.append(strValue); 282 if ((j + 1) < values.length) { 283 result.append(PARAMETER_DELIMITER); 284 } 285 } 286 if (i.hasNext()) { 287 result.append(PARAMETER_DELIMITER); 288 } 289 } 290 return result.toString(); 291 } 292 293 /** 294 * Checks that the given link is a valid backlink for editors. 295 * 296 * <p>This means that the link is either just a path, or starts with a scheme/domain/port that is either registered in the 297 * site configuration, or is the same as that of the current request. 298 * 299 * @param backlink the link to check 300 * @param optionalRequest the current request - may be null if no check against the current request is desired 301 * @return true if the link is a valid backlink 302 */ 303 public static boolean checkBacklink(String backlink, HttpServletRequest optionalRequest) { 304 305 if (!backlinkCheckEnabled) { 306 return true; 307 } 308 309 if (CmsStringUtil.isEmptyOrWhitespaceOnly(backlink)) { 310 return true; 311 } 312 313 if (backlink.startsWith("javascript")) { 314 return false; 315 } 316 317 if (backlink.startsWith("/")) { 318 return true; 319 } 320 CmsSiteMatcher matcher = new CmsSiteMatcher(backlink, 0); 321 if (OpenCms.getSiteManager().isMatching(matcher)) { 322 return true; 323 } 324 325 if (optionalRequest != null) { 326 if (optionalRequest.getServerName().equals(matcher.getServerName())) { 327 return true; 328 } 329 } 330 return false; 331 332 } 333 334 /** 335 * Creates a valid request parameter map from the given map, 336 * most notably changing the values form <code>String</code> 337 * to <code>String[]</code> if required.<p> 338 * 339 * If the given parameter map is <code>null</code>, then <code>null</code> is returned.<p> 340 * 341 * @param params the map of parameters to create a parameter map from 342 * @return the created parameter map, all values will be instances of <code>String[]</code> 343 */ 344 public static Map<String, String[]> createParameterMap(Map<String, ?> params) { 345 346 if (params == null) { 347 return null; 348 } 349 Map<String, String[]> result = new HashMap<String, String[]>(); 350 Iterator<?> i = params.entrySet().iterator(); 351 while (i.hasNext()) { 352 @SuppressWarnings("unchecked") 353 Map.Entry<String, ?> entry = (Entry<String, ?>)i.next(); 354 String key = entry.getKey(); 355 Object values = entry.getValue(); 356 if (values instanceof String[]) { 357 result.put(key, (String[])values); 358 } else { 359 if (values != null) { 360 result.put(key, new String[] {values.toString()}); 361 } 362 } 363 } 364 return result; 365 } 366 367 /** 368 * Parses the parameters of the given request query part and creates a parameter map out of them.<p> 369 * 370 * Please note: This does not parse a full request URI/URL, only the query part that 371 * starts after the "?". For example, in the URI <code>/system/index.html?a=b&c=d</code>, 372 * the query part is <code>a=b&c=d</code>.<p> 373 * 374 * If the given String is empty, an empty map is returned.<p> 375 * 376 * @param query the query to parse 377 * @return the parameter map created from the query 378 */ 379 public static Map<String, String[]> createParameterMap(String query) { 380 381 return createParameterMap(query, false, null); 382 } 383 384 /** 385 * Parses the parameters of the given request query part, optionally decodes them, and creates a parameter map out of them.<p> 386 * 387 * Please note: This does not parse a full request URI/URL, only the query part that 388 * starts after the "?". For example, in the URI <code>/system/index.html?a=b&c=d</code>, 389 * the query part is <code>a=b&c=d</code>.<p> 390 * 391 * If the given String is empty, an empty map is returned.<p> 392 * 393 * @param query the query to parse 394 * @param decodeParameters a flag, indicating if the parameters should be decoded. 395 * @param encoding the character encoding used while decoding. If <code>null</code>, the default character encoding is used. 396 * @return the parameter map created from the query 397 */ 398 public static Map<String, String[]> createParameterMap(String query, boolean decodeParameters, String encoding) { 399 400 if (CmsStringUtil.isEmpty(query)) { 401 // empty query 402 return new HashMap<String, String[]>(); 403 } 404 if (query.charAt(0) == URL_DELIMITER.charAt(0)) { 405 // remove leading '?' if required 406 query = query.substring(1); 407 } 408 // cut along the different parameters 409 String[] params = CmsStringUtil.splitAsArray(query, PARAMETER_DELIMITER); 410 Map<String, String[]> parameters = new HashMap<String, String[]>(params.length); 411 for (int i = 0; i < params.length; i++) { 412 String key = null; 413 String value = null; 414 // get key and value, separated by a '=' 415 int pos = params[i].indexOf(PARAMETER_ASSIGNMENT); 416 if (pos > 0) { 417 key = params[i].substring(0, pos); 418 value = params[i].substring(pos + 1); 419 } else if (pos < 0) { 420 key = params[i]; 421 value = ""; 422 } 423 // adjust the key if it starts with "amp;" 424 // this happens when "&" is used instead of a simple "&" 425 if ((key != null) && (key.startsWith(AMP))) { 426 key = key.substring(AMP.length()); 427 } 428 // now make sure the values are of type String[] 429 if (key != null) { 430 if (decodeParameters) { 431 key = CmsEncoder.decode(key, encoding); 432 value = CmsEncoder.decode(value, encoding); 433 } 434 String[] values = parameters.get(key); 435 if (values == null) { 436 // this is the first value, create new array 437 values = new String[] {value}; 438 } else { 439 // append to the existing value array 440 String[] copy = new String[values.length + 1]; 441 System.arraycopy(values, 0, copy, 0, values.length); 442 copy[copy.length - 1] = value; 443 values = copy; 444 } 445 parameters.put(key, values); 446 } 447 } 448 return parameters; 449 } 450 451 /** 452 * Sets HTTP response headers to disable embedding in iframes on different domains. 453 * 454 * @param response the response on which to set the HTTP headers 455 */ 456 public static void disableCrossSiteFrameEmbedding(HttpServletResponse response) { 457 458 response.addHeader("Content-Security-Policy", "frame-ancestors 'self';"); 459 response.addHeader("X-Frame-Options", "SAMEORIGIN"); 460 } 461 462 /** 463 * Returns all parameters of the given request 464 * as a request parameter URL String, that is in the form <code>key1=value1&key2=value2</code> etc. 465 * 466 * The result will be encoded using the <code>{@link CmsEncoder#encode(String)}</code> function.<p> 467 * 468 * @param req the request to read the parameters from 469 * 470 * @return all initialized parameters of the given request as request parameter URL String 471 */ 472 public static String encodeParams(HttpServletRequest req) { 473 474 StringBuffer result = new StringBuffer(512); 475 Map<String, String[]> params = CmsCollectionsGenericWrapper.map(req.getParameterMap()); 476 Iterator<Map.Entry<String, String[]>> i = params.entrySet().iterator(); 477 while (i.hasNext()) { 478 Map.Entry<String, String[]> entry = i.next(); 479 String param = entry.getKey(); 480 String[] values = entry.getValue(); 481 for (int j = 0; j < values.length; j++) { 482 result.append(param); 483 result.append("="); 484 result.append(CmsEncoder.encode(values[j])); 485 if ((j + 1) < values.length) { 486 result.append("&"); 487 } 488 } 489 if (i.hasNext()) { 490 result.append("&"); 491 } 492 } 493 return CmsEncoder.encode(result.toString()); 494 } 495 496 /** 497 * Encodes the given URI, with all parameters from the given request appended.<p> 498 * 499 * The result will be encoded using the <code>{@link CmsEncoder#encode(String)}</code> function.<p> 500 * 501 * @param req the request where to read the parameters from 502 * @param uri the URI to encode 503 * @return the encoded URI, with all parameters from the given request appended 504 */ 505 public static String encodeParamsWithUri(String uri, HttpServletRequest req) { 506 507 String result; 508 String params = encodeParams(req); 509 if (CmsStringUtil.isNotEmpty(params)) { 510 result = CmsEncoder.encode(uri + "?") + params; 511 } else { 512 result = CmsEncoder.encode(uri); 513 } 514 return result; 515 } 516 517 /** 518 * Forwards the response to the given target, which may contain parameters appended like for example <code>?a=b&c=d</code>.<p> 519 * 520 * Please note: If possible, use <code>{@link #forwardRequest(String, Map, HttpServletRequest, HttpServletResponse)}</code> 521 * where the parameters are passed as a map, since the parsing of the parameters may introduce issues with encoding 522 * and is in general much less effective.<p> 523 * 524 * The parsing of parameters will likely fail for "large values" (e.g. full blown web forms with <textarea> 525 * elements etc. Use this method only if you know that the target will just contain up to 3 parameters which 526 * are relatively short and have no encoding or line break issues.<p> 527 * 528 * @param target the target to forward to (may contain parameters like <code>?a=b&c=d</code>) 529 * @param req the request to forward 530 * @param res the response to forward 531 * 532 * @throws IOException in case the forwarding fails 533 * @throws ServletException in case the forwarding fails 534 */ 535 public static void forwardRequest(String target, HttpServletRequest req, HttpServletResponse res) 536 throws IOException, ServletException { 537 538 // clear the current parameters 539 CmsUriSplitter uri = new CmsUriSplitter(target); 540 Map<String, String[]> params = createParameterMap(uri.getQuery()); 541 forwardRequest(uri.getPrefix(), params, req, res); 542 } 543 544 /** 545 * Forwards the response to the given target, with the provided parameter map.<p> 546 * 547 * The target URI must NOT have parameters appended like for example <code>?a=b&c=d</code>. 548 * The values in the provided map must be of type <code>String[]</code>. If required, use 549 * <code>{@link #createParameterMap(Map)}</code> before calling this method to make sure 550 * all values are actually of the required array type.<p> 551 * 552 * @param target the target to forward to (may NOT contain parameters like <code>?a=b&c=d</code>) 553 * @param params the parameter map (the values must be of type <code>String[]</code> 554 * @param req the request to forward 555 * @param res the response to forward 556 * 557 * @throws IOException in case the forwarding fails 558 * @throws ServletException in case the forwarding fails 559 */ 560 public static void forwardRequest( 561 String target, 562 Map<String, String[]> params, 563 HttpServletRequest req, 564 HttpServletResponse res) 565 throws IOException, ServletException { 566 567 // get the flex request (which the servlet container may have wrapped) so the parameter map can be accessed 568 CmsFlexRequest f_req = getFlexRequest(req); 569 // set the parameters 570 f_req.setParameterMap(params); 571 // check for links "into" OpenCms, these may need the webapp name to be removed 572 String vfsPrefix = OpenCms.getStaticExportManager().getVfsPrefix(); 573 if (target.startsWith(vfsPrefix)) { 574 // remove VFS prefix (will also work for empty vfs prefix in ROOT webapp case with proxy rules) 575 target = target.substring(vfsPrefix.length()); 576 // append the servlet name 577 target = OpenCms.getSystemInfo().getServletPath() + target; 578 } 579 // forward the request 580 f_req.getRequestDispatcher(target).forward(f_req, res); 581 } 582 583 /** 584 * Exactly like getAttributeMap, but incorrectly spelled.<p> 585 * 586 * Kept for backward compatibility. 587 * 588 * @param req the request 589 * 590 * @return the attribute map 591 */ 592 public static Map<String, Object> getAtrributeMap(ServletRequest req) { 593 594 return getAttributeMap(req); 595 } 596 597 /** 598 * Returns a map with all request attributes.<p> 599 * 600 * @param req the request 601 * 602 * @return the attribute map 603 */ 604 public static Map<String, Object> getAttributeMap(ServletRequest req) { 605 606 CmsFlexRequest flexReq = getFlexRequest(req); 607 if (flexReq != null) { 608 return flexReq.getAttributeMap(); 609 } 610 Map<String, Object> attrs = new HashMap<String, Object>(); 611 Enumeration<String> atrrEnum = CmsCollectionsGenericWrapper.enumeration(req.getAttributeNames()); 612 while (atrrEnum.hasMoreElements()) { 613 String key = atrrEnum.nextElement(); 614 Object value = req.getAttribute(key); 615 attrs.put(key, value); 616 } 617 return attrs; 618 } 619 620 /** 621 * Returns the value of the cookie with the given name.<p/> 622 * 623 * @param jsp the CmsJspActionElement to use 624 * @param name the name of the cookie 625 * 626 * @return the value of the cookie with the given name or null, if no cookie exists with the name 627 */ 628 public static String getCookieValue(CmsJspActionElement jsp, String name) { 629 630 Cookie[] cookies = jsp.getRequest().getCookies(); 631 return getCookieValue(cookies, name); 632 } 633 634 /** 635 * Gets the value of a specific cookie from an array of cookies.<p> 636 * 637 * @param cookies the cookie array 638 * @param name the name of the cookie we want 639 * 640 * @return the cookie value, or null if cookie with the given name wasn't found 641 */ 642 public static String getCookieValue(Cookie[] cookies, String name) { 643 644 for (int i = 0; (cookies != null) && (i < cookies.length); i++) { 645 if (name.equalsIgnoreCase(cookies[i].getName())) { 646 return cookies[i].getValue(); 647 } 648 } 649 return null; 650 } 651 652 /** 653 * Tries to extract a wrapped CmsFlexRequest from a potentially nested servlet request object. 654 * 655 * @param request the request to get the flex request from 656 * @return the flex request, or null if none was found 657 */ 658 public static CmsFlexRequest getFlexRequest(ServletRequest request) { 659 660 while ((request != null) && !(request instanceof CmsFlexRequest)) { 661 if (request instanceof HttpServletRequestWrapper) { 662 request = ((HttpServletRequestWrapper)request).getRequest(); 663 } else { 664 // container response implementation that's not a wrapper 665 return null; 666 } 667 } 668 669 // works for both cases of the while loop exit condition 670 return (CmsFlexRequest)request; 671 } 672 673 /** 674 * Tries to extract a wrapped CmsFlexResponse from a potentially nested servlet response object. 675 * 676 * @param response the response to get the flex response from 677 * @return the flex response, or null if none was found 678 */ 679 public static CmsFlexResponse getFlexResponse(ServletResponse response) { 680 681 while ((response != null) && !(response instanceof CmsFlexResponse)) { 682 if (response instanceof HttpServletResponseWrapper) { 683 response = ((HttpServletResponseWrapper)response).getResponse(); 684 } else { 685 // container response implementation that's not a wrapper 686 return null; 687 } 688 } 689 690 // works for both cases of the while loop exit condition 691 return (CmsFlexResponse)response; 692 } 693 694 /** 695 * Converts the given parameter map into an JSON object.<p> 696 * 697 * @param params the parameters map to convert 698 * 699 * @return the JSON representation of the given parameter map 700 */ 701 public static JSONObject getJsonParameterMap(Map<String, String[]> params) { 702 703 JSONObject result = new JSONObject(); 704 for (Map.Entry<String, String[]> entry : params.entrySet()) { 705 String paramKey = entry.getKey(); 706 JSONArray paramValue = new JSONArray(); 707 for (int i = 0, l = entry.getValue().length; i < l; i++) { 708 paramValue.put(entry.getValue()[i]); 709 } 710 try { 711 result.putOpt(paramKey, paramValue); 712 } catch (JSONException e) { 713 // should never happen 714 LOG.warn(e.getLocalizedMessage(), e); 715 } 716 } 717 return result; 718 } 719 720 /** 721 * Reads value from the request parameters, 722 * will return <code>null</code> if the value is not available or only white space.<p> 723 * 724 * The value of the request will also be decoded using <code>{@link CmsEncoder#decode(String)}</code> 725 * and also trimmed using <code>{@link String#trim()}</code>.<p> 726 * 727 * @param request the request to read the parameter from 728 * @param paramName the parameter name to read 729 * 730 * @return the request parameter value for the given parameter 731 */ 732 public static String getNotEmptyDecodedParameter(HttpServletRequest request, String paramName) { 733 734 String result = getNotEmptyParameter(request, paramName); 735 if (result != null) { 736 result = CmsEncoder.decode(result.trim()); 737 } 738 return result; 739 } 740 741 /** 742 * Reads value from the request parameters, 743 * will return <code>null</code> if the value is not available or only white space.<p> 744 * 745 * @param request the request to read the parameter from 746 * @param paramName the parameter name to read 747 * 748 * @return the request parameter value for the given parameter 749 */ 750 public static String getNotEmptyParameter(HttpServletRequest request, String paramName) { 751 752 String result = request.getParameter(paramName); 753 if (CmsStringUtil.isEmptyOrWhitespaceOnly(result)) { 754 result = null; 755 } 756 return result; 757 } 758 759 /** 760 * Returns a map with all request parameters.<p> 761 * 762 * If the given request wraps a flex request, the parameters of that flex request are returned: 763 * a request wrapper added by the servlet container does not necessarily report the parameters 764 * of the request it wraps.<p> 765 * 766 * @param req the request 767 * 768 * @return the parameter map 769 */ 770 public static Map<String, String[]> getParameterMap(ServletRequest req) { 771 772 CmsFlexRequest flexReq = getFlexRequest(req); 773 if (flexReq != null) { 774 return flexReq.getParameterMap(); 775 } 776 return req.getParameterMap(); 777 } 778 779 /** 780 * Converts the given JSON object into a valid parameter map.<p> 781 * 782 * @param params the JSON object to convert 783 * 784 * @return the parameter map from the given JSON object 785 */ 786 public static Map<String, String[]> getParameterMapFromJSON(JSONObject params) { 787 788 Map<String, String[]> result = new HashMap<String, String[]>(); 789 Iterator<String> itKeys = params.keys(); 790 while (itKeys.hasNext()) { 791 String key = itKeys.next(); 792 JSONArray paramValue = params.optJSONArray(key); 793 result.put(key, new String[paramValue.length()]); 794 for (int i = 0, l = paramValue.length(); i < l; i++) { 795 result.get(key)[i] = paramValue.optString(i); 796 } 797 } 798 return result; 799 } 800 801 /** 802 * Parses parameter map from the given URI.<p> 803 * 804 * @param uri the URI 805 * @return the parameter map 806 */ 807 public static Multimap<String, String> getParameters(URI uri) { 808 809 return getParametersFromRawQuery(uri.getRawQuery()); 810 } 811 812 /** 813 * Parses the parameter map from a raw query string.<p> 814 * 815 * @param rawQuery the raw query string 816 * 817 * @return the parameter map 818 */ 819 public static Multimap<String, String> getParametersFromRawQuery(String rawQuery) { 820 821 Multimap<String, String> result = ArrayListMultimap.create(); 822 if (rawQuery != null) { 823 for (String keyValuePair : CmsStringUtil.splitAsList(rawQuery, "&")) { 824 try { 825 String decodedKeyValue = URLDecoder.decode(keyValuePair, "UTF-8"); 826 int eqPos = decodedKeyValue.indexOf("="); 827 if (eqPos < 0) { 828 decodedKeyValue = decodedKeyValue + "="; 829 eqPos = decodedKeyValue.indexOf("="); 830 } 831 String key = decodedKeyValue.substring(0, eqPos); 832 String value = decodedKeyValue.substring(eqPos + 1); 833 result.put(key, value); 834 } catch (UnsupportedEncodingException e) { 835 // UTF8 should be present 836 } 837 } 838 } 839 return result; 840 } 841 842 /** 843 * Returns the link without parameters from a String that is formatted for a GET request.<p> 844 * 845 * @param url the URL to remove the parameters from 846 * @return the URL without any parameters 847 */ 848 public static String getRequestLink(String url) { 849 850 if (CmsStringUtil.isEmpty(url)) { 851 return null; 852 } 853 int pos = url.indexOf(URL_DELIMITER); 854 if (pos >= 0) { 855 return url.substring(0, pos); 856 } 857 return url; 858 859 } 860 861 /** 862 * Reads an object from the session of the given HTTP request.<p> 863 * 864 * A session will be initialized if the request does not currently have a session. 865 * As a result, the request will always have a session after this method has been called.<p> 866 * 867 * Will return <code>null</code> if no corresponding object is found in the session.<p> 868 * 869 * @param request the request to get the session from 870 * @param key the key of the object to read from the session 871 * @return the object received form the session, or <code>null</code> 872 */ 873 public static Object getSessionValue(HttpServletRequest request, String key) { 874 875 HttpSession session = request.getSession(true); 876 return session.getAttribute(key); 877 } 878 879 /** 880 * Parses a request of the form <code>multipart/form-data</code>. 881 * 882 * The result list will contain items of type <code>{@link DiskFileItem}</code>. 883 * If the request is not of type <code>multipart/form-data</code>, then <code>null</code> is returned.<p> 884 * 885 * @param request the HTTP servlet request to parse 886 * 887 * @return the list of <code>{@link DiskFileItem}</code> extracted from the multipart request, 888 * or <code>null</code> if the request was not of type <code>multipart/form-data</code> 889 */ 890 public static List<DiskFileItem> readMultipartFileItems(HttpServletRequest request) { 891 892 return readMultipartFileItems(request, OpenCms.getSystemInfo().getPackagesRfsPath()); 893 } 894 895 /** 896 * Parses a request of the form <code>multipart/form-data</code>. 897 * 898 * The result list will contain items of type <code>{@link DiskFileItem}</code>. 899 * If the request is not of type <code>multipart/form-data</code>, then <code>null</code> is returned.<p> 900 * 901 * @param request the HTTP servlet request to parse 902 * @param tempFolderPath the real file system path to the temp file folder 903 * 904 * @return the list of <code>{@link DiskFileItem}</code> extracted from the multipart request, 905 * or <code>null</code> if the request was not of type <code>multipart/form-data</code> 906 */ 907 public static List<DiskFileItem> readMultipartFileItems(HttpServletRequest request, String tempFolderPath) { 908 909 if (!JakartaServletFileUpload.isMultipartContent(request)) { 910 return null; 911 } 912 DiskFileItemFactory.Builder builder = DiskFileItemFactory.builder(); 913 builder.setPath(tempFolderPath); 914 DiskFileItemFactory factory = builder.get(); 915 JakartaServletDiskFileUpload fu = new JakartaServletDiskFileUpload(factory); 916 // set encoding to correctly handle special chars (e.g. in filenames) 917 fu.setHeaderCharset(Charset.forName(request.getCharacterEncoding())); 918 List<DiskFileItem> result = new ArrayList<DiskFileItem>(); 919 try { 920 List<DiskFileItem> items = CmsCollectionsGenericWrapper.list(fu.parseRequest(request)); 921 if (items != null) { 922 result = items; 923 } 924 } catch (FileUploadException e) { 925 LOG.error(Messages.get().getBundle().key(Messages.LOG_PARSE_MULIPART_REQ_FAILED_0), e); 926 } 927 return result; 928 } 929 930 /** 931 * Creates a "standard" request parameter map from the values of a 932 * <code>multipart/form-data</code> request.<p> 933 * 934 * @param encoding the encoding to use when creating the values 935 * @param multiPartFileItems the list of parsed multi part file items 936 * 937 * @return a map containing all non-file request parameters 938 * 939 * @see #readMultipartFileItems(HttpServletRequest) 940 */ 941 public static Map<String, String[]> readParameterMapFromMultiPart( 942 String encoding, 943 List<DiskFileItem> multiPartFileItems) { 944 945 Map<String, String[]> parameterMap = new HashMap<String, String[]>(); 946 Iterator<DiskFileItem> i = multiPartFileItems.iterator(); 947 while (i.hasNext()) { 948 DiskFileItem item = i.next(); 949 String name = item.getFieldName(); 950 String value = null; 951 if ((name != null) && (item.getName() == null)) { 952 try { 953 // only put to map if current item is no file and not null 954 try { 955 value = item.getString(Charset.forName(encoding)); 956 } catch (UnsupportedEncodingException e) { 957 LOG.error(Messages.get().getBundle().key(Messages.LOG_ENC_MULTIPART_REQ_ERROR_0), e); 958 value = item.getString(); 959 } 960 } catch (IOException e) { 961 LOG.error(e.getLocalizedMessage(), e); 962 continue; 963 } 964 if (parameterMap.containsKey(name)) { 965 966 // append value to parameter values array 967 String[] oldValues = parameterMap.get(name); 968 String[] newValues = new String[oldValues.length + 1]; 969 System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); 970 newValues[oldValues.length] = value; 971 parameterMap.put(name, newValues); 972 973 } else { 974 parameterMap.put(name, new String[] {value}); 975 } 976 } 977 } 978 return parameterMap; 979 } 980 981 /** 982 * Redirects the response to the target link using a "301 - Moved Permanently" header.<p> 983 * 984 * This implementation will work only on JSP pages in OpenCms that use the default JSP loader implementation.<p> 985 * 986 * @param jsp the OpenCms JSP context 987 * @param target the target link 988 */ 989 public static void redirectPermanently(CmsJspActionElement jsp, String target) { 990 991 target = OpenCms.getLinkManager().substituteLink(jsp.getCmsObject(), target); 992 jsp.getResponse().setHeader(HEADER_CONNECTION, "close"); 993 try { 994 HttpServletResponse response = jsp.getResponse(); 995 CmsFlexResponse flexRes = CmsRequestUtil.getFlexResponse(response); 996 if (flexRes != null) { 997 flexRes.sendRedirect(target, true); 998 } else { 999 response.setHeader("Location", target); 1000 response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); 1001 } 1002 } catch (IOException e) { 1003 LOG.error(Messages.get().getBundle().key(Messages.ERR_IOERROR_0), e); 1004 } 1005 } 1006 1007 /** 1008 * Redirects the response to the target link.<p> 1009 * 1010 * Use this method instead of {@link jakarta.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)} 1011 * to avoid relative links with secure sites (and issues with apache).<p> 1012 * 1013 * @param jsp the OpenCms JSP context 1014 * @param target the target link 1015 * 1016 * @throws IOException if something goes wrong during redirection 1017 */ 1018 public static void redirectRequestSecure(CmsJspActionElement jsp, String target) throws IOException { 1019 1020 jsp.getResponse().sendRedirect(OpenCms.getLinkManager().substituteLink(jsp.getCmsObject(), target, null, true)); 1021 } 1022 1023 /** 1024 * Removes an object from the session of the given http request.<p> 1025 * 1026 * A session will be initialized if the request does not currently have a session. 1027 * As a result, the request will always have a session after this method has been called.<p> 1028 * 1029 * @param request the request to get the session from 1030 * @param key the key of the object to be removed from the session 1031 */ 1032 public static void removeSessionValue(HttpServletRequest request, String key) { 1033 1034 HttpSession session = request.getSession(true); 1035 session.removeAttribute(key); 1036 } 1037 1038 /** 1039 * Sets the value of a specific cookie.<p> 1040 * If no cookie exists with the value, a new cookie will be created. 1041 * 1042 * @param jsp the CmsJspActionElement to use 1043 * @param name the name of the cookie 1044 * @param value the value of the cookie 1045 */ 1046 public static void setCookieValue(CmsJspActionElement jsp, String name, String value) { 1047 1048 Cookie[] cookies = jsp.getRequest().getCookies(); 1049 for (int i = 0; (cookies != null) && (i < cookies.length); i++) { 1050 if (name.equalsIgnoreCase(cookies[i].getName())) { 1051 cookies[i].setValue(value); 1052 return; 1053 } 1054 } 1055 Cookie cookie = new Cookie(name, value); 1056 jsp.getResponse().addCookie(cookie); 1057 } 1058 1059 /** 1060 * Sets headers to the given response to prevent client side caching.<p> 1061 * 1062 * The following headers are set:<p> 1063 * <code> 1064 * Cache-Control: max-age=0<br> 1065 * Cache-Control: must-revalidate<br> 1066 * Pragma: no-cache 1067 * </code> 1068 * 1069 * @param res the request where to set the no-cache headers 1070 */ 1071 public static void setNoCacheHeaders(HttpServletResponse res) { 1072 1073 res.setHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, CmsRequestUtil.HEADER_VALUE_MAX_AGE + "0"); 1074 res.addHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, CmsRequestUtil.HEADER_VALUE_MUST_REVALIDATE); 1075 res.addHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, CmsRequestUtil.HEADER_VALUE_NO_CACHE); 1076 res.addHeader(CmsRequestUtil.HEADER_CACHE_CONTROL, CmsRequestUtil.HEADER_VALUE_NO_STORE); 1077 res.setHeader(CmsRequestUtil.HEADER_PRAGMA, CmsRequestUtil.HEADER_VALUE_NO_CACHE); 1078 } 1079 1080 /** 1081 * Adds an object to the session of the given HTTP request.<p> 1082 * 1083 * A session will be initialized if the request does not currently have a session. 1084 * As a result, the request will always have a session after this method has been called.<p> 1085 * 1086 * @param request the request to get the session from 1087 * @param key the key of the object to be stored in the session 1088 * @param value the object to be stored in the session 1089 */ 1090 public static void setSessionValue(HttpServletRequest request, String key, Object value) { 1091 1092 HttpSession session = request.getSession(true); 1093 session.setAttribute(key, value); 1094 } 1095}