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