001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.http;
018
019import java.nio.charset.Charset;
020import java.nio.charset.StandardCharsets;
021import java.nio.charset.UnsupportedCharsetException;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.List;
025
026import jakarta.servlet.http.HttpServletRequest;
027
028import org.apache.wicket.Application;
029import org.apache.wicket.request.cycle.RequestCycle;
030import org.apache.wicket.request.mapper.parameter.INamedParameters;
031import org.apache.wicket.request.mapper.parameter.PageParameters;
032import org.apache.wicket.util.encoding.UrlDecoder;
033import org.apache.wicket.util.string.Strings;
034
035/**
036 * Wicket Http specific utilities class.
037 */
038public final class RequestUtils
039{
040        /**
041         * Decode the provided queryString as a series of key/ value pairs and set them in the provided
042         * value map.
043         * 
044         * @param queryString
045         *            string to decode, uses '&' to separate parameters and '=' to separate key from
046         *            value
047         * @param params
048         *            parameters map to write the found key/ value pairs to
049         */
050        public static void decodeParameters(String queryString, PageParameters params)
051        {
052                decodeParameters(queryString, params, getCurrentCharset());
053        }
054
055        /**
056         * Decode the provided queryString as a series of key/ value pairs and set them in the provided
057         * value map.
058         *
059         * @param queryString
060         *            string to decode, uses '&' to separate parameters and '=' to separate key from
061         *            value
062         * @param params
063         *            parameters map to write the found key/ value pairs to
064         * @param currentCharset
065         *            charset resolved via current requestCycle
066         */
067        static void decodeParameters(String queryString, PageParameters params, Charset currentCharset)
068        {
069        
070        if (Strings.indexOf(queryString, '?') == 0)
071        {
072            queryString = queryString.substring(1);
073        }
074        
075                for (String paramTuple : Strings.split(queryString, '&'))
076                {
077                        final String[] bits = Strings.split(paramTuple, '=');
078
079                        if (bits.length == 2)
080                        {
081                                params.add(UrlDecoder.QUERY_INSTANCE.decode(bits[0], currentCharset),
082                                        UrlDecoder.QUERY_INSTANCE.decode(bits[1], currentCharset), INamedParameters.Type.QUERY_STRING);
083                        }
084                        else
085                        {
086                                params.add(UrlDecoder.QUERY_INSTANCE.decode(bits[0], currentCharset), "", INamedParameters.Type.QUERY_STRING);
087                        }
088                }
089        }
090
091        /**
092         * Remove occurrences of ".." from the path
093         * 
094         * @param path
095         * @return path string with double dots removed
096         */
097        public static String removeDoubleDots(String path)
098        {
099                String[] segments = Strings.split(path, '/');
100                List<String> newcomponents = new ArrayList<>(Arrays.asList(segments));
101
102                for (int i = 0; i < newcomponents.size(); i++)
103                {
104                        if (i < newcomponents.size() - 1)
105                        {
106                                // Verify for a ".." component at next iteration
107                                if ((newcomponents.get(i)).length() > 0 && newcomponents.get(i + 1).equals(".."))
108                                {
109                                        newcomponents.remove(i);
110                                        newcomponents.remove(i);
111                                        i = i - 2;
112                                        if (i < -1)
113                                        {
114                                                i = -1;
115                                        }
116                                }
117                        }
118                }
119                String newpath = Strings.join("/", newcomponents);
120                if (path.endsWith("/"))
121                {
122                        return newpath + "/";
123                }
124                return newpath;
125        }
126
127        /**
128         * Hidden utility class constructor.
129         */
130        private RequestUtils()
131        {
132        }
133
134
135        /**
136         * Calculates absolute path to url relative to another absolute url.
137         * 
138         * @param requestPath
139         *            absolute path.
140         * @param relativePagePath
141         *            path, relative to requestPath
142         * @return absolute path for given url
143         */
144        public static String toAbsolutePath(final String requestPath, String relativePagePath)
145        {
146                final StringBuilder result;
147                if (requestPath.endsWith("/"))
148                {
149                        result = new StringBuilder(requestPath);
150                }
151                else
152                {
153                        // Remove everything after last slash (but not slash itself)
154                        result = new StringBuilder(requestPath.substring(0, requestPath.lastIndexOf('/') + 1));
155                }
156
157                if (relativePagePath.startsWith("./"))
158                {
159                        relativePagePath = relativePagePath.substring(2);
160                }
161
162                if (relativePagePath.startsWith("../"))
163                {
164                        StringBuilder tempRelative = new StringBuilder(relativePagePath);
165
166                        // Go up through hierarchy until we find most common directory for both pathes.
167                        while (tempRelative.indexOf("../") == 0)
168                        {
169                                // Delete ../ from relative path
170                                tempRelative.delete(0, 3);
171
172                                // Delete last slash from result
173                                result.setLength(result.length() - 1);
174
175                                // Delete everyting up to last slash
176                                result.delete(result.lastIndexOf("/") + 1, result.length());
177                        }
178                        result.append(tempRelative);
179                }
180                else
181                {
182                        // Pages are in the same directory
183                        result.append(relativePagePath);
184                }
185                return result.toString();
186        }
187
188        private static Charset getDefaultCharset()
189        {
190                String charsetName = null;
191                if (Application.exists())
192                {
193                        charsetName = Application.get().getRequestCycleSettings().getResponseRequestEncoding();
194                }
195                
196                if (Strings.isEmpty(charsetName))
197                {
198                        return StandardCharsets.UTF_8;
199                }
200                
201                return Charset.forName(charsetName);
202        }
203
204        private static Charset getCurrentCharset()
205        {
206                return RequestCycle.get().getRequest().getCharset();
207        }
208
209        /**
210         * @param request
211         *            the http servlet request to extract the charset from
212         * @return the request's charset or a default it request is {@code null} or has an unsupported
213         *         character encoding
214         * 
215         * @see org.apache.wicket.settings.RequestCycleSettings#getResponseRequestEncoding()
216         */
217        public static Charset getCharset(HttpServletRequest request)
218        {
219                Charset charset = null;
220                if (request != null)
221                {
222                        String charsetName = request.getCharacterEncoding();
223                        if (charsetName != null)
224                        {
225                                try
226                                {
227                                        charset = Charset.forName(charsetName);
228                                }
229                                catch (UnsupportedCharsetException useDefault)
230                                {
231                                }
232                        }
233                }
234                if (charset == null)
235                {
236                        charset = getDefaultCharset();
237                }
238                return charset;
239        }
240}