1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.url

File ExtendedURL.java

 

Coverage histogram

../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

24
75
15
1
328
182
30
0.4
5
15
2

Classes

Class Line # Actions
ExtendedURL 47 75 0% 30 8
0.929824693%
 

Contributing tests

This file is covered by 75 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.url;
21   
22    import java.io.UnsupportedEncodingException;
23    import java.net.URI;
24    import java.net.URISyntaxException;
25    import java.net.URL;
26    import java.net.URLDecoder;
27    import java.net.URLEncoder;
28    import java.util.ArrayList;
29    import java.util.Arrays;
30    import java.util.Collections;
31    import java.util.LinkedHashMap;
32    import java.util.List;
33    import java.util.Map;
34   
35    import org.apache.commons.lang3.StringUtils;
36    import org.apache.commons.lang3.builder.EqualsBuilder;
37    import org.apache.commons.lang3.builder.HashCodeBuilder;
38    import org.xwiki.resource.CreateResourceReferenceException;
39    import org.xwiki.velocity.tools.EscapeTool;
40   
41    /**
42    * Extend a {@link URL} by providing access to the URL path segments (URL-decoded).
43    *
44    * @version $Id: 127514ccdae948124d4a3615f04509083c508742 $
45    * @since 6.1M2
46    */
 
47    public class ExtendedURL implements Cloneable
48    {
49    /**
50    * URL path separator character.
51    */
52    private static final String URL_SEPARATOR = "/";
53   
54    private static final String UTF8 = "UTF-8";
55   
56    /**
57    * @see #getURI()
58    */
59    private URI uri;
60   
61    /**
62    * Keep the URL that we're wrapping since for some operations it's better to make them on the URL object rather
63    * than on the URI one. For example domain names are stricter in URI (no "_" character allowed while they are
64    * allowed in URLs).
65    *
66    * @see #getWrappedURL()
67    */
68    private URL wrappedURL;
69   
70    /**
71    * @see #getSegments()
72    */
73    private List<String> segments;
74   
75    private Map<String, List<String>> parameters;
76   
77    /**
78    * Used to serialize a {@link Map} as a URL query string.
79    */
80    private EscapeTool escapeTool = new EscapeTool();
81   
82    /**
83    * Populate the Extended URL with a list of path segments.
84    *
85    * @param segments the path segments of the URL
86    */
 
87  36 toggle public ExtendedURL(List<String> segments)
88    {
89  36 this(segments, Collections.<String, List<String>>emptyMap());
90    }
91   
92    /**
93    * Populate the Extended URL with a list of path segments.
94    *
95    * @param segments the path segments of the URL
96    * @param parameters the query string parameters of the URL
97    * @since 7.1M1
98    */
 
99  51500 toggle public ExtendedURL(List<String> segments, Map<String, List<String>> parameters)
100    {
101  51500 this.segments = new ArrayList<>(segments);
102  51503 this.parameters = parameters;
103    }
104   
105    /**
106    * @param url the URL being wrapped
107    * @param ignorePrefix the ignore prefix must start with "/" (eg "/xwiki"). It can be empty or null too in which
108    * case it's not used
109    * @throws org.xwiki.resource.CreateResourceReferenceException if the passed URL is invalid which can happen if it
110    * has incorrect encoding
111    */
 
112  35279 toggle public ExtendedURL(URL url, String ignorePrefix) throws CreateResourceReferenceException
113    {
114  35269 this.wrappedURL = url;
115   
116    // Convert the URL to a URI since URI performs correctly decoding.
117    // Note that this means that this method only accepts valid URLs (with proper encoding)
118  35193 URI internalURI;
119  35205 try {
120  35239 internalURI = url.toURI();
121    } catch (URISyntaxException e) {
122  1 throw new CreateResourceReferenceException(String.format("Invalid URL [%s]", url), e);
123    }
124  35255 this.uri = internalURI;
125   
126    // Extract the path after the ignore prefix
127  35242 String rawPath = getURI().getRawPath();
128  35244 if (!StringUtils.isEmpty(ignorePrefix)) {
129    // Allow the passed ignore prefix to not contain the leading "/"
130  35195 String normalizedIgnorePrefix = ignorePrefix;
131  35173 if (!ignorePrefix.startsWith(URL_SEPARATOR)) {
132  25 normalizedIgnorePrefix = URL_SEPARATOR + ignorePrefix;
133    }
134   
135  35212 if (!getURI().getPath().startsWith(normalizedIgnorePrefix)) {
136  1 throw new CreateResourceReferenceException(
137    String.format("URL Path [%s] doesn't start with [%s]", getURI().getPath(), ignorePrefix));
138    }
139  35200 rawPath = rawPath.substring(normalizedIgnorePrefix.length());
140    }
141   
142    // Remove leading "/" if any
143  35207 rawPath = StringUtils.removeStart(rawPath, URL_SEPARATOR);
144   
145  35234 this.segments = extractPathSegments(rawPath);
146  35288 this.parameters = extractParameters(internalURI);
147    }
148   
149    /**
150    * @return the path segments (each part of the URL separated by the path separator character)
151    */
 
152  141435 toggle public List<String> getSegments()
153    {
154  141448 return this.segments;
155    }
156   
157    /**
158    * @return the URL that this instance wraps, provided as a helper feature
159    */
 
160  20963 toggle public URL getWrappedURL()
161    {
162  20962 return this.wrappedURL;
163    }
164   
165    /**
166    * @return the URI corresponding to the passed URL that this instance wraps, provided as a helper feature
167    */
 
168  70375 toggle public URI getURI()
169    {
170  70439 return this.uri;
171    }
172   
173    /**
174    * @return the list of query string parameters passed in the original URL
175    * @since 7.1M1
176    */
 
177  74288 toggle public Map<String, List<String>> getParameters()
178    {
179  74287 return this.parameters;
180    }
181   
 
182  35236 toggle private Map<String, List<String>> extractParameters(URI uri)
183    {
184  35254 Map<String, List<String>> uriParameters;
185  35265 if (uri.getQuery() != null) {
186  17530 uriParameters = new LinkedHashMap<>();
187  17527 for (String nameValue : Arrays.asList(uri.getQuery().split("&"))) {
188  28545 String[] pair = nameValue.split("=", 2);
189    // Check if the parameter has a value or not.
190  28554 if (pair.length == 2) {
191  28367 addParameter(pair[0], pair[1], uriParameters);
192    } else {
193  187 addParameter(pair[0], null, uriParameters);
194    }
195    }
196    } else {
197  17719 uriParameters = Collections.emptyMap();
198    }
199  35239 return uriParameters;
200    }
201   
 
202  28544 toggle private void addParameter(String name, String value, Map<String, List<String>> parameters)
203    {
204  28547 List<String> list = parameters.get(name);
205  28545 if (list == null) {
206  28075 list = new ArrayList<>();
207    }
208  28547 if (value != null) {
209  28360 list.add(value);
210    }
211  28548 parameters.put(name, list);
212    }
213   
214    /**
215    * Extract segments between "/" characters in the passed path. Also remove any path parameters (i.e. content
216    * after ";" in a path segment; for ex ";jsessionid=...") since we don't want to have these params in the
217    * segments we return and act on (otherwise we would get them in document names for example).
218    * <p>
219    * Note that we only remove ";" characters when they are not URL-encoded. We want to allow the ";" character to be
220    * in document names for example.
221    *
222    * @param rawPath the path from which to extract the segments
223    * @return the extracted path segments
224    */
 
225  35235 toggle private List<String> extractPathSegments(String rawPath)
226    {
227  35247 List<String> urlSegments = new ArrayList<String>();
228   
229  35237 if (StringUtils.isEmpty(rawPath)) {
230  3 return urlSegments;
231    }
232   
233    // Note that we use -1 in the call below in order to get empty segments too. This is needed since in our URL
234    // scheme a tailing "/" can have a meaning (for example "bin/view/Page" can represent a Page while
235    // "bin/view/Space/" can represents a Space).
236  35222 for (String pathSegment : rawPath.split(URL_SEPARATOR, -1)) {
237   
238    // Remove path parameters
239  161561 String normalizedPathSegment = pathSegment.split(";", 2)[0];
240   
241    // Now let's decode it
242  161609 String decodedPathSegment;
243  161642 try {
244    // Note: we decode using UTF-8 since the URI javadoc says:
245    // "A sequence of escaped octets is decoded by replacing it with the sequence of characters that it
246    // represents in the UTF-8 character set. UTF-8 contains US-ASCII, hence decoding has the effect of
247    // de-quoting any quoted US-ASCII characters as well as that of decoding any encoded non-US-ASCII
248    // characters."
249  161651 decodedPathSegment = URLDecoder.decode(normalizedPathSegment, UTF8);
250    } catch (UnsupportedEncodingException e) {
251    // Not supporting UTF-8 as a valid encoding for some reasons. We consider XWiki cannot work
252    // without that encoding.
253  0 throw new RuntimeException(
254    String.format("Failed to URL decode [%s] using UTF-8.", normalizedPathSegment), e);
255    }
256   
257  161533 urlSegments.add(decodedPathSegment);
258    }
259   
260  35286 return urlSegments;
261    }
262   
 
263  0 toggle @Override
264    public int hashCode()
265    {
266  0 return new HashCodeBuilder(7, 7)
267    .append(getURI())
268    .append(getSegments())
269    .toHashCode();
270    }
271   
 
272  6 toggle @Override
273    public boolean equals(Object object)
274    {
275  6 if (object == null) {
276  0 return false;
277    }
278  6 if (object == this) {
279  1 return true;
280    }
281  5 if (object.getClass() != getClass()) {
282  0 return false;
283    }
284  5 ExtendedURL rhs = (ExtendedURL) object;
285  5 return new EqualsBuilder()
286    .append(getURI(), rhs.getURI())
287    .append(getSegments(), rhs.getSegments())
288    .isEquals();
289    }
290   
291    /**
292    * @return the serialized segments as a relative URL with URL-encoded path segments. Note that the returned String
293    * starts with a URL separator ("/")
294    */
 
295  25754 toggle public String serialize()
296    {
297  25754 StringBuilder builder = new StringBuilder();
298  25754 List<String> encodedSegments = new ArrayList<>();
299  25754 for (String path : getSegments()) {
300  162206 encodedSegments.add(encodeSegment(path));
301    }
302  25755 builder.append(URL_SEPARATOR);
303  25753 builder.append(StringUtils.join(encodedSegments, URL_SEPARATOR));
304  25754 Map<String, List<String>> uriParameters = getParameters();
305  25753 if (!uriParameters.isEmpty()) {
306  9045 builder.append('?');
307  9046 builder.append(this.escapeTool.url(uriParameters));
308    }
309  25754 return builder.toString();
310    }
311   
 
312  162206 toggle private String encodeSegment(String value)
313    {
314  162206 try {
315  162207 return URLEncoder.encode(value, UTF8);
316    } catch (UnsupportedEncodingException e) {
317    // Not supporting UTF-8 as a valid encoding for some reasons. We consider XWiki cannot work
318    // without that encoding.
319  0 throw new RuntimeException(String.format("Failed to URL encode [%s] using UTF-8.", value), e);
320    }
321    }
322   
 
323  10 toggle @Override
324    public String toString()
325    {
326  10 return serialize();
327    }
328    }