1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.web

File XWikiServletURLFactory.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
54% of files have more coverage

Code metrics

70
210
37
1
774
457
102
0.49
5.68
37
2.76

Classes

Class Line # Actions
XWikiServletURLFactory 48 210 0% 102 66
0.791798179.2%
 

Contributing tests

This file is covered by 36 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 com.xpn.xwiki.web;
21   
22    import java.net.MalformedURLException;
23    import java.net.URI;
24    import java.net.URISyntaxException;
25    import java.net.URL;
26    import java.net.URLEncoder;
27    import java.util.Collections;
28    import java.util.List;
29   
30    import org.apache.commons.lang3.StringUtils;
31    import org.apache.commons.lang3.exception.ExceptionUtils;
32    import org.slf4j.Logger;
33    import org.slf4j.LoggerFactory;
34    import org.xwiki.model.EntityType;
35    import org.xwiki.model.reference.DocumentReference;
36    import org.xwiki.model.reference.EntityReference;
37    import org.xwiki.model.reference.EntityReferenceResolver;
38    import org.xwiki.model.reference.EntityReferenceSerializer;
39    import org.xwiki.resource.internal.entity.EntityResourceActionLister;
40   
41    import com.xpn.xwiki.XWiki;
42    import com.xpn.xwiki.XWikiContext;
43    import com.xpn.xwiki.XWikiException;
44    import com.xpn.xwiki.doc.DeletedAttachment;
45    import com.xpn.xwiki.doc.XWikiAttachment;
46    import com.xpn.xwiki.doc.XWikiDocument;
47   
 
48    public class XWikiServletURLFactory extends XWikiDefaultURLFactory
49    {
50    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiServletURLFactory.class);
51   
52    private EntityReferenceResolver<String> relativeEntityReferenceResolver =
53    Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "relative");
54   
55    private EntityResourceActionLister actionLister = Utils.getComponent(EntityResourceActionLister.class);
56   
57    /**
58    * This is the URL which was requested by the user possibly with the host modified if x-forwarded-host header is set
59    * or if xwiki.home parameter is set and we are viewing the main page.
60    */
61    protected URL serverURL;
62   
63    protected String contextPath;
64   
 
65  11643 toggle public XWikiServletURLFactory()
66    {
67    }
68   
69    // Used by tests
 
70  36 toggle public XWikiServletURLFactory(URL serverURL, String contextPath, String actionPath)
71    {
72  36 this.serverURL = serverURL;
73  36 this.contextPath = contextPath;
74    }
75   
76    /**
77    * Creates a new URL factory that uses the server URL and context path specified by the given XWiki context. This
78    * constructor should be used only in tests. Make sure {@link XWikiContext#setURL(URL)} is called before this
79    * constructor.
80    *
81    * @param context
82    */
 
83  8 toggle public XWikiServletURLFactory(XWikiContext context)
84    {
85  8 init(context);
86    }
87   
 
88  11652 toggle @Override
89    public void init(XWikiContext context)
90    {
91  11656 this.contextPath = context.getWiki().getWebAppPath(context);
92  11643 try {
93  11652 this.serverURL = new URL(getProtocol(context) + "://" + getHost(context));
94    } catch (MalformedURLException e) {
95    // This can't happen.
96    }
97    }
98   
99    /**
100    * Returns the part of the URL identifying the web application. In a normal install, that is <tt>xwiki/</tt>.
101    *
102    * @return The configured context path.
103    */
 
104  0 toggle public String getContextPath()
105    {
106  0 return this.contextPath;
107    }
108   
109    /**
110    * @param context the XWiki context used to access the request object
111    * @return the value of the {@code xwiki.url.protocol} configuration parameter, if defined, otherwise the protocol
112    * used to make the request to the proxy server if we are behind one, otherwise the protocol of the URL used
113    * to make the current request
114    */
 
115  11621 toggle private String getProtocol(XWikiContext context)
116    {
117    // Tests usually set the context URL but don't set the request object.
118  11641 String protocol = context.getURL().getProtocol();
119  11644 if (context.getRequest() != null) {
120  11637 protocol = context.getRequest().getScheme();
121  11640 if ("http".equalsIgnoreCase(protocol) && context.getRequest().isSecure()) {
122    // This can happen in reverse proxy mode, if the proxy server receives HTTPS requests and forwards them
123    // as HTTP to the internal web server running XWiki.
124  4 protocol = "https";
125    }
126    }
127    // Detected protocol can be overwritten by configuration.
128  11635 return context.getWiki().Param("xwiki.url.protocol", protocol);
129    }
130   
131    /**
132    * @param context the XWiki context used to access the request object
133    * @return the proxy host, if we are behind one, otherwise the host of the URL used to make the current request
134    */
 
135  11647 toggle private String getHost(XWikiContext context)
136    {
137  11658 URL url = context.getURL();
138   
139    // Check reverse-proxy mode (e.g. Apache's mod_proxy_http).
140  11656 String proxyHost = StringUtils.substringBefore(context.getRequest().getHeader("x-forwarded-host"), ",");
141  11652 if (!StringUtils.isEmpty(proxyHost)) {
142  6 return proxyHost;
143    }
144    // If the reverse proxy does not support the x-forwarded-host parameter
145    // we allow the user to force the the host name by using the xwiki.home param.
146  11636 final URL homeParam = getXWikiHomeParameter(context);
147  11650 if (homeParam != null && context.isMainWiki()) {
148  2 url = homeParam;
149    }
150   
151  11628 return url.getHost() + (url.getPort() < 0 ? "" : (":" + url.getPort()));
152    }
153   
154    /** @return a URL made from the xwiki.cfg parameter xwiki.home or null if undefined or unparsable. */
 
155  11684 toggle private static URL getXWikiHomeParameter(final XWikiContext context)
156    {
157  11689 final String surl = getXWikiHomeParameterAsString(context);
158  11716 if (!StringUtils.isEmpty(surl)) {
159  6 try {
160  6 return normalizeURL(surl, context);
161    } catch (MalformedURLException e) {
162  0 LOGGER.warn("Could not create URL from xwiki.cfg xwiki.home parameter: " + surl
163    + " Ignoring parameter.");
164    }
165    }
166  11707 return null;
167    }
168   
169    /** @return the xwiki.home parameter or null if undefined. */
 
170  11683 toggle private static String getXWikiHomeParameterAsString(final XWikiContext context)
171    {
172  11693 return context.getWiki().Param("xwiki.home", null);
173    }
174   
 
175  41929 toggle @Override
176    public URL getServerURL(XWikiContext context) throws MalformedURLException
177    {
178  41925 return getServerURL(context.getWikiId(), context);
179    }
180   
181    /**
182    * Get the url of the server EG: http://www.xwiki.org/ This function sometimes will return a URL with a trailing /
183    * and other times not. This is because the xwiki.home param is recommended to have a trailing / but this.serverURL
184    * never does.
185    *
186    * @param wikiId the identifier of the wiki, if null it is assumed to be the same as the wiki which we are currently
187    * displaying.
188    * @param context the XWikiContext used to determine the current wiki and the value if the xwiki.home parameter if
189    * needed as well as access the xwiki server document if in virtual mode.
190    * @return a URL containing the protocol, host, and port (if applicable) of the server to use for the given
191    * database.
192    */
 
193  139890 toggle public URL getServerURL(String wikiId, XWikiContext context) throws MalformedURLException
194    {
195  139889 if (wikiId == null || wikiId.equals(context.getOriginalWikiId())) {
196    // This is the case if we are getting a URL for a page which is in
197    // the same wiki as the page which is now being displayed.
198  130283 return this.serverURL;
199    }
200   
201  9602 if (context.isMainWiki(wikiId)) {
202    // Not in the same wiki so we are in a subwiki and we want a URL which points to the main wiki.
203    // if xwiki.home is set then lets return that.
204  62 final URL homeParam = getXWikiHomeParameter(context);
205  62 if (homeParam != null) {
206  2 return homeParam;
207    }
208    }
209   
210  9600 URL url = context.getWiki().getServerURL(wikiId, context);
211  9600 return url == null ? this.serverURL : url;
212    }
213   
 
214  0 toggle @Override
215    public URL createURL(String spaces, String name, String action, boolean redirect, XWikiContext context)
216    {
217  0 return createURL(spaces, name, action, context);
218    }
219   
 
220  93239 toggle @Override
221    public URL createURL(String spaces, String name, String action, String querystring, String anchor, String xwikidb,
222    XWikiContext context)
223    {
224    // Action and Query String transformers
225  93233 if (("view".equals(action)) && (context.getLinksAction() != null)) {
226  0 action = context.getLinksAction();
227    }
228  93235 if (context.getLinksQueryString() != null) {
229  0 if (querystring == null) {
230  0 querystring = context.getLinksQueryString();
231    } else {
232  0 querystring = querystring + "&" + context.getLinksQueryString();
233    }
234    }
235   
236  93237 StringBuilder path = new StringBuilder(this.contextPath);
237  93237 addServletPath(path, xwikidb, context);
238   
239    // Parse the spaces list into Space References
240  93235 EntityReference spaceReference = this.relativeEntityReferenceResolver.resolve(spaces, EntityType.SPACE);
241   
242    // For how to encode the various parts of the URL, see http://stackoverflow.com/a/29948396/153102
243  93237 addAction(path, spaceReference, action, context);
244  93238 addSpaces(path, spaceReference, action, context);
245  93238 addName(path, name, action, context);
246   
247  93239 if (!StringUtils.isEmpty(querystring)) {
248  37117 path.append("?");
249  37117 path.append(StringUtils.removeEnd(StringUtils.removeEnd(querystring, "&"), "&amp;"));
250    }
251   
252  93236 if (!StringUtils.isEmpty(anchor)) {
253  24 path.append("#");
254  24 path.append(encodeWithinQuery(anchor, context));
255    }
256   
257  93238 URL result;
258  93237 try {
259  93234 result = normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
260    } catch (MalformedURLException e) {
261    // This should not happen
262  0 result = null;
263    }
264   
265  93237 return result;
266    }
267   
 
268  111222 toggle private void addServletPath(StringBuilder path, String xwikidb, XWikiContext context)
269    {
270  111224 if (xwikidb == null) {
271  2 xwikidb = context.getWikiId();
272    }
273   
274  111220 path.append(context.getWiki().getServletPath(xwikidb, context));
275    }
276   
 
277  111215 toggle private void addAction(StringBuilder path, EntityReference spaceReference, String action, XWikiContext context)
278    {
279  111216 boolean showViewAction = context.getWiki().showViewAction(context);
280   
281    // - Always output the action if it's not "view" or if showViewAction is true
282    // - Output "view/<first space name>" when the first space name is an action name and the action is View
283    // (and showViewAction = false)
284  111221 if ((!"view".equals(action) || (showViewAction))
285    || (!showViewAction && spaceReference != null && "view".equals(action)
286    && this.actionLister.listActions().contains(
287    spaceReference.extractFirstReference(EntityType.SPACE).getName())))
288    {
289  111218 path.append(action).append("/");
290    }
291    }
292   
293    /**
294    * Add the spaces to the path.
295    */
 
296  97965 toggle private void addSpaces(StringBuilder path, EntityReference spaceReference, String action, XWikiContext context)
297    {
298  97963 for (EntityReference reference : spaceReference.getReversedReferenceChain()) {
299  107986 appendSpacePathSegment(path, reference, context);
300    }
301    }
302   
 
303  107978 toggle private void appendSpacePathSegment(StringBuilder path, EntityReference spaceReference, XWikiContext context)
304    {
305  107981 path.append(encodeWithinPath(spaceReference.getName(), context)).append('/');
306    }
307   
308    /**
309    * Add the page name to the path.
310    */
 
311  97960 toggle private void addName(StringBuilder path, String name, String action, XWikiContext context)
312    {
313  97956 XWiki xwiki = context.getWiki();
314  97968 if ((xwiki.useDefaultAction(context))
315    || (!name.equals(xwiki.getDefaultPage(context)) || (!"view".equals(action)))) {
316  70056 path.append(encodeWithinPath(name, context));
317    }
318    }
319   
 
320  398 toggle protected void addFileName(StringBuilder path, String fileName, XWikiContext context)
321    {
322  398 addFileName(path, fileName, true, context);
323    }
324   
 
325  36967 toggle protected void addFileName(StringBuilder path, String fileName, boolean encode, XWikiContext context)
326    {
327  36964 path.append("/");
328  36965 if (encode) {
329    // Encode the given file name as a single path segment.
330  398 path.append(encodeWithinPath(fileName, context).replace("+", "%20"));
331    } else {
332  36568 try {
333    // The given file name is actually a file path and so we need to encode each path segment separately.
334  36568 path.append(new URI(null, null, fileName, null));
335    } catch (URISyntaxException e) {
336  0 LOGGER.debug("Failed to encode the file path [{}]. Root cause: [{}]", fileName,
337    ExceptionUtils.getRootCauseMessage(e));
338    // Use the raw file path as a fall-back.
339  0 path.append(fileName);
340    }
341    }
342    }
343   
344    /**
345    * Encode a URL path following the URL specification so that space is encoded as {@code %20} in the path
346    * (and not as {@code +} wnich is not correct). Note that for all other characters we encode them even though some
347    * don't need to be encoded. For example we encode the single quote even though it's not necessary
348    * (see <a href="http://tinyurl.com/j6bjgaq">this explanation</a>). The reason is that otherwise it becomes
349    * dangerous to use a returned URL in the HREF attribute in HTML. Imagine the following {@code <a href='$url'...}
350    * and {@code #set ($url = $doc.getURL(...))}. Now let's assume that {@code $url}'s value is
351    * {@code http://localhost:8080/xwiki/bin/view/A'/B}. This would generate a HTML of
352    * {@code <a href='http://localhost:8080/xwiki/bin/view/A'/B'} which would generated a wrong link to
353    * {@code http://localhost:8080/xwiki/bin/view/A}... Thus if we were only encoding the characters that require
354    * encoding, we would need HMTL writers to encode the received URL and right now we don't do that anywhere in our
355    * code. Thus in order to not introduce any problem and keep it safe we just handle the {@code +} character
356    * specially and encode the rest.
357    *
358    * @param name the path to encode
359    * @param context see {@link XWikiContext}
360    * @return the URL-encoded path segment
361    */
 
362  178461 toggle private String encodeWithinPath(String name, XWikiContext context)
363    {
364    // Note: Ideally the following would have been the correct way of writing this method but it causes the issues
365    // mentioned in the javadoc of this method
366    // String encodedName;
367    // try {
368    // encodedName = URIUtil.encodeWithinPath(name, "UTF-8");
369    // } catch (URIException e) {
370    // throw new RuntimeException("Missing charset [UTF-8]", e);
371    // }
372    // return encodedName;
373   
374  178463 String encodedName;
375  178458 try {
376  178459 encodedName = URLEncoder.encode(name, "UTF-8");
377    } catch (Exception e) {
378    // Should not happen (UTF-8 is always available)
379  0 throw new RuntimeException("Missing charset [UTF-8]", e);
380    }
381   
382    // The previous call will convert " " into "+" (and "+" into "%2B") so we need to convert "+" into "%20"
383  178458 encodedName = encodedName.replaceAll("\\+", "%20");
384   
385  178463 return encodedName;
386    }
387   
388    /**
389    * Same rationale as {@link #encodeWithinPath(String, XWikiContext)}. Note that we also encode spaces as {@code %20}
390    * even though we could also have encoded them as {@code +}. We do this for consistency (it allows to have the same
391    * implementation for both URL paths and query string).
392    *
393    * @param name the query string part to encode
394    * @param context see {@link XWikiContext}
395    * @return the URL-encoded query string part
396    */
 
397  24 toggle private String encodeWithinQuery(String name, XWikiContext context)
398    {
399    // Note: Ideally the following would have been the correct way of writing this method but it causes the issues
400    // mentioned in the javadoc of this method
401    // String encodedName;
402    // try {
403    // encodedName = URIUtil.encodeWithinQuery(name, "UTF-8");
404    // } catch (URIException e) {
405    // throw new RuntimeException("Missing charset [UTF-8]", e);
406    // }
407    // return encodedName;
408   
409  24 return encodeWithinPath(name, context);
410    }
411   
 
412  692 toggle @Override
413    public URL createExternalURL(String spaces, String name, String action, String querystring, String anchor,
414    String xwikidb, XWikiContext context)
415    {
416  692 return this.createURL(spaces, name, action, querystring, anchor, xwikidb, context);
417    }
418   
 
419  1267 toggle @Override
420    public URL createSkinURL(String filename, String skin, XWikiContext context)
421    {
422  1266 StringBuilder path = new StringBuilder(this.contextPath);
423  1267 path.append("skins/");
424  1266 path.append(skin);
425  1267 addFileName(path, filename, false, context);
426  1266 try {
427  1267 return normalizeURL(new URL(getServerURL(context), path.toString()), context);
428    } catch (MalformedURLException e) {
429    // This should not happen
430  0 return null;
431    }
432    }
433   
 
434  4328 toggle @Override
435    public URL createSkinURL(String filename, String spaces, String name, String xwikidb, XWikiContext context)
436    {
437  4326 StringBuilder path = new StringBuilder(this.contextPath);
438  4326 addServletPath(path, xwikidb, context);
439   
440    // Parse the spaces list into Space References
441  4328 EntityReference spaceReference = this.relativeEntityReferenceResolver.resolve(spaces, EntityType.SPACE);
442   
443  4328 addAction(path, null, "skin", context);
444  4331 addSpaces(path, spaceReference, "skin", context);
445  4328 addName(path, name, "skin", context);
446  4331 addFileName(path, filename, false, context);
447  4331 try {
448  4330 return normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
449    } catch (MalformedURLException e) {
450    // This should not happen
451  0 return null;
452    }
453    }
454   
 
455  30973 toggle @Override
456    public URL createResourceURL(String filename, boolean forceSkinAction, XWikiContext context)
457    {
458  30973 StringBuilder path = new StringBuilder(this.contextPath);
459  30973 if (forceSkinAction) {
460  13257 addServletPath(path, context.getWikiId(), context);
461  13254 addAction(path, null, "skin", context);
462    }
463  30971 path.append("resources");
464  30971 addFileName(path, filename, false, context);
465  30971 try {
466  30972 return normalizeURL(new URL(getServerURL(context), path.toString()), context);
467    } catch (MalformedURLException e) {
468    // This should not happen
469  0 return null;
470    }
471    }
472   
 
473  0 toggle public URL createTemplateURL(String fileName, XWikiContext context)
474    {
475  0 StringBuilder path = new StringBuilder(this.contextPath);
476  0 path.append("templates");
477  0 addFileName(path, fileName, false, context);
478  0 try {
479  0 return normalizeURL(new URL(getServerURL(context), path.toString()), context);
480    } catch (MalformedURLException e) {
481    // This should not happen
482  0 return null;
483    }
484    }
485   
 
486  394 toggle @Override
487    public URL createAttachmentURL(String filename, String spaces, String name, String action, String querystring,
488    String xwikidb, XWikiContext context)
489    {
490  394 if ((context != null) && "viewrev".equals(context.getAction()) && context.get("rev") != null
491    && isContextDoc(xwikidb, spaces, name, context)) {
492  1 try {
493  1 String docRevision = context.get("rev").toString();
494  1 XWikiAttachment attachment =
495    findAttachmentForDocRevision(context.getDoc(), docRevision, filename, context);
496  1 if (attachment == null) {
497  1 action = "viewattachrev";
498    } else {
499  0 long arbId = findDeletedAttachmentForDocRevision(context.getDoc(), docRevision, filename, context);
500  0 return createAttachmentRevisionURL(filename, spaces, name, attachment.getVersion(), arbId,
501    querystring, xwikidb, context);
502    }
503    } catch (XWikiException e) {
504  0 if (LOGGER.isErrorEnabled()) {
505  0 LOGGER.error("Exception while trying to get attachment version !", e);
506    }
507    }
508    }
509   
510  394 StringBuilder path = new StringBuilder(this.contextPath);
511  394 addServletPath(path, xwikidb, context);
512   
513    // Parse the spaces list into Space References
514  394 EntityReference spaceReference = this.relativeEntityReferenceResolver.resolve(spaces, EntityType.SPACE);
515   
516  394 addAction(path, spaceReference, action, context);
517  394 addSpaces(path, spaceReference, action, context);
518  394 addName(path, name, action, context);
519  394 addFileName(path, filename, context);
520   
521  394 if (!StringUtils.isEmpty(querystring)) {
522  75 path.append("?");
523  75 path.append(StringUtils.removeEnd(StringUtils.removeEnd(querystring, "&"), "&amp;"));
524    }
525   
526  394 try {
527  394 return normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
528    } catch (Exception e) {
529  0 return null;
530    }
531    }
532   
533    /**
534    * Check if a document is the original context document. This is needed when generating attachment revision URLs,
535    * since only attachments of the context document should also be versioned.
536    *
537    * @param wiki the wiki name of the document to check
538    * @param spaces the space names of the document to check
539    * @param name the document name of the document to check
540    * @param context the current request context
541    * @return {@code true} if the provided document is the same as the current context document, {@code false}
542    * otherwise
543    */
 
544  2 toggle protected boolean isContextDoc(String wiki, String spaces, String name, XWikiContext context)
545    {
546  2 if (context == null || context.getDoc() == null) {
547  0 return false;
548    }
549   
550    // Use the local serializer so that we don't serialize the wiki part since all we want to do is compare the
551    // passed spaces represented as a String with the current doc's spaces.
552  2 EntityReferenceSerializer<String> serializer =
553    Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "local");
554  2 DocumentReference currentDocumentReference = context.getDoc().getDocumentReference();
555  2 return serializer.serialize(currentDocumentReference.getLastSpaceReference()).equals(spaces)
556    && currentDocumentReference.getName().equals(name)
557    && (wiki == null || currentDocumentReference.getWikiReference().getName().equals(wiki));
558    }
559   
 
560  4 toggle @Override
561    public URL createAttachmentRevisionURL(String filename, String spaces, String name, String revision,
562    String querystring, String xwikidb, XWikiContext context)
563    {
564  4 return createAttachmentRevisionURL(filename, spaces, name, revision, -1, querystring, xwikidb, context);
565    }
566   
 
567  4 toggle public URL createAttachmentRevisionURL(String filename, String spaces, String name, String revision,
568    long recycleId, String querystring, String xwikidb, XWikiContext context)
569    {
570  4 String action = "downloadrev";
571  4 StringBuilder path = new StringBuilder(this.contextPath);
572  4 addServletPath(path, xwikidb, context);
573   
574    // Parse the spaces list into Space References
575  4 EntityReference spaceReference = this.relativeEntityReferenceResolver.resolve(spaces, EntityType.SPACE);
576   
577  4 addAction(path, spaceReference, action, context);
578  4 addSpaces(path, spaceReference, action, context);
579  4 addName(path, name, action, context);
580  4 addFileName(path, filename, context);
581   
582  4 String qstring = "rev=" + revision;
583  4 if (recycleId >= 0) {
584  0 qstring += "&rid=" + recycleId;
585    }
586  4 if (!StringUtils.isEmpty(querystring)) {
587  0 qstring += "&" + querystring;
588    }
589  4 path.append("?");
590  4 path.append(StringUtils.removeEnd(StringUtils.removeEnd(qstring, "&"), "&amp;"));
591   
592  4 try {
593  4 return normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
594    } catch (MalformedURLException e) {
595    // This should not happen
596  0 return null;
597    }
598    }
599   
600    /**
601    * Converts a URL to a relative URL if it's a XWiki URL (keeping only the path + query string + anchor) and leave
602    * the URL unchanged if it's an external URL.
603    * <p>
604    * An URL is considered to be external if its server component doesn't match the server of the current request URL.
605    * This means that URLs are made relative with respect to the current request URL rather than the current wiki set
606    * on the XWiki context. Let's take an example:
607    *
608    * <pre>
609    * {@code
610    * request URL: http://playground.xwiki.org/xwiki/bin/view/Sandbox/TestURL
611    * current wiki: code (code.xwiki.org)
612    * URL (1): http://code.xwiki.org/xwiki/bin/view/Main/WebHome
613    * URL (2): http://playground.xwiki.org/xwiki/bin/view/Spage/Page
614    *
615    * The result will be:
616    * (1) http://code.xwiki.org/xwiki/bin/view/Main/WebHome
617    * (2) /xwiki/bin/view/Spage/Page
618    * }
619    * </pre>
620    *
621    * @param url the URL to convert
622    * @return the converted URL as a string
623    * @see com.xpn.xwiki.web.XWikiDefaultURLFactory#getURL(java.net.URL, com.xpn.xwiki.XWikiContext)
624    */
 
625  137148 toggle @Override
626    public String getURL(URL url, XWikiContext context)
627    {
628  137146 String relativeURL = "";
629   
630  137144 try {
631  137140 if (url != null) {
632  137143 String surl = url.toString();
633   
634  137141 if (!surl.startsWith(this.serverURL.toString())) {
635    // External URL: leave it as is.
636  4 relativeURL = surl;
637    } else {
638    // Internal XWiki URL: convert to relative.
639  137141 StringBuilder relativeURLBuilder = new StringBuilder(url.getPath());
640  137136 String querystring = url.getQuery();
641  137135 if (!StringUtils.isEmpty(querystring)) {
642  44060 relativeURLBuilder.append("?").append(
643    StringUtils.removeEnd(StringUtils.removeEnd(querystring, "&"), "&amp;"));
644    }
645   
646  137134 String anchor = url.getRef();
647  137132 if (!StringUtils.isEmpty(anchor)) {
648  702 relativeURLBuilder.append("#").append(anchor);
649    }
650   
651  137138 relativeURL = relativeURLBuilder.toString();
652    }
653    }
654    } catch (Exception e) {
655  0 LOGGER.error("Failed to create URL", e);
656    }
657   
658  137140 return StringUtils.defaultIfEmpty(relativeURL, "/");
659    }
660   
 
661  8606 toggle @Override
662    public URL getRequestURL(XWikiContext context)
663    {
664  8606 final URL url = super.getRequestURL(context);
665   
666  8604 try {
667  8603 final URL servurl = getServerURL(context);
668    // if use apache mod_proxy we needed to know external host address
669  8605 return normalizeURL(new URL(servurl.getProtocol(), servurl.getHost(), servurl.getPort(), url.getFile()),
670    context);
671    } catch (MalformedURLException e) {
672    // This should not happen
673  0 LOGGER.error("Failed to create request URL", e);
674    }
675   
676  0 return url;
677    }
678   
 
679  1 toggle public XWikiAttachment findAttachmentForDocRevision(XWikiDocument doc, String docRevision, String filename,
680    XWikiContext context) throws XWikiException
681    {
682  1 XWikiAttachment attachment = null;
683  1 XWikiDocument rdoc = context.getWiki().getDocument(doc, docRevision, context);
684  1 if (filename != null) {
685  1 attachment = rdoc.getAttachment(filename);
686    }
687   
688  1 return attachment;
689    }
690   
 
691  0 toggle public long findDeletedAttachmentForDocRevision(XWikiDocument doc, String docRevision, String filename,
692    XWikiContext context) throws XWikiException
693    {
694  0 XWikiAttachment attachment = null;
695  0 XWikiDocument rdoc = context.getWiki().getDocument(doc, docRevision, context);
696  0 if (context.getWiki().hasAttachmentRecycleBin(context) && filename != null) {
697  0 attachment = rdoc.getAttachment(filename);
698  0 if (attachment != null) {
699  0 List<DeletedAttachment> deleted =
700    context.getWiki().getAttachmentRecycleBinStore()
701    .getAllDeletedAttachments(attachment, context, true);
702  0 Collections.reverse(deleted);
703  0 for (DeletedAttachment entry : deleted) {
704  0 if (entry.getDate().after(rdoc.getDate())) {
705  0 return entry.getId();
706    }
707    }
708    }
709    }
710   
711  0 return -1;
712    }
713   
714    /**
715    * Encodes the passed URL and offers the possibility for Servlet Filter to perform URL rewriting (this is used for
716    * example by Tuckey's URLRewriteFilter for rewriting outbound URLs, see
717    * http://platform.xwiki.org/xwiki/bin/view/Main/ShortURLs).
718    * <p>
719    * However Servlet Container will also add a ";jsessionid=xxx" content to the URL while encoding the URL and we
720    * strip it since we don't want to have that in our URLs as it can cause issues with:
721    * <ul>
722    * <li>security</li>
723    * <li>SEO</li>
724    * <li>clients not expecting jsessionid in URL, for example RSS feed readers which will think that articles are
725    * different as they'll get different URLs everytime they call the XWiki server</li>
726    * </ul>
727    * See why jsessionid are considered harmful <a
728    * href="https://randomcoder.org/articles/jsessionid-considered-harmful">here</a> and <a
729    * href="http://java.dzone.com/articles/java-jsessionid-harmful">here</a>
730    *
731    * @param url the URL to encode and normalize
732    * @param context the XWiki Context used to get access to the Response for encoding the URL
733    * @return the normalized URL
734    * @throws MalformedURLException if the passed URL is invalid
735    */
 
736  138806 toggle protected static URL normalizeURL(URL url, XWikiContext context) throws MalformedURLException
737    {
738  138799 return normalizeURL(url.toExternalForm(), context);
739    }
740   
741    /**
742    * Encodes the passed URL and offers the possibility for Servlet Filter to perform URL rewriting (this is used for
743    * example by Tuckey's URLRewriteFilter for rewriting outbound URLs, see
744    * http://platform.xwiki.org/xwiki/bin/view/Main/ShortURLs).
745    * <p>
746    * However Servlet Container will also add a ";jsessionid=xxx" content to the URL while encoding the URL and we
747    * strip it since we don't want to have that in our URLs as it can cause issues with:
748    * <ul>
749    * <li>security</li>
750    * <li>SEO</li>
751    * <li>clients not expecting jsessionid in URL, for example RSS feed readers which will think that articles are
752    * different as they'll get different URLs everytime they call the XWiki server</li>
753    * </ul>
754    * See why jsessionid are considered harmful <a
755    * href="https://randomcoder.org/articles/jsessionid-considered-harmful">here</a> and <a
756    * href="http://java.dzone.com/articles/java-jsessionid-harmful">here</a>
757    *
758    * @param url the URL to encode and normalize
759    * @param context the XWiki Context used to get access to the Response for encoding the URL
760    * @return the normalized URL
761    * @throws MalformedURLException if the passed URL is invalid
762    */
 
763  138810 toggle protected static URL normalizeURL(String url, XWikiContext context) throws MalformedURLException
764    {
765    // For robust session tracking, all URLs emitted by a servlet should be encoded. Otherwise, URL rewriting
766    // cannot be used with browsers which do not support cookies.
767  138807 String encodedURLAsString = context.getResponse().encodeURL(url);
768   
769    // Remove a potential jsessionid in the URL
770  138807 encodedURLAsString = encodedURLAsString.replaceAll(";jsessionid=.*?(?=\\?|$)", "");
771   
772  138808 return new URL(encodedURLAsString);
773    }
774    }