1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.web

File XWikiServletURLFactory.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
41% of files have more coverage

Code metrics

76
227
39
1
817
490
106
0.47
5.82
39
2.72

Classes

Class Line # Actions
XWikiServletURLFactory 51 227 0% 106 64
0.812865581.3%
 

Contributing tests

This file is covered by 42 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.HashMap;
29    import java.util.List;
30    import java.util.Map;
31   
32    import org.apache.commons.lang3.StringUtils;
33    import org.apache.commons.lang3.exception.ExceptionUtils;
34    import org.slf4j.Logger;
35    import org.slf4j.LoggerFactory;
36    import org.xwiki.container.servlet.HttpServletUtils;
37    import org.xwiki.model.EntityType;
38    import org.xwiki.model.reference.DocumentReference;
39    import org.xwiki.model.reference.EntityReference;
40    import org.xwiki.model.reference.EntityReferenceResolver;
41    import org.xwiki.model.reference.EntityReferenceSerializer;
42    import org.xwiki.resource.internal.entity.EntityResourceActionLister;
43   
44    import com.xpn.xwiki.XWiki;
45    import com.xpn.xwiki.XWikiContext;
46    import com.xpn.xwiki.XWikiException;
47    import com.xpn.xwiki.doc.DeletedAttachment;
48    import com.xpn.xwiki.doc.XWikiAttachment;
49    import com.xpn.xwiki.doc.XWikiDocument;
50   
 
51    public class XWikiServletURLFactory extends XWikiDefaultURLFactory
52    {
53    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiServletURLFactory.class);
54   
55    private EntityReferenceResolver<String> relativeEntityReferenceResolver;
56   
57    private EntityResourceActionLister actionLister;
58   
59    protected URL originalURL;
60   
61    protected String defaultURL;
62   
63    /**
64    * This is the URL which was requested by the user possibly with the host modified if x-forwarded-host header is set
65    * or if xwiki.home parameter is set and we are viewing the main page.
66    */
67    protected Map<String, URL> defaultURLs;
68   
69    protected String contextPath;
70   
 
71  43625 toggle public XWikiServletURLFactory()
72    {
73    }
74   
75    // Used by tests
 
76  45 toggle public XWikiServletURLFactory(URL defaultURL, String contextPath, String actionPath)
77    {
78  45 this.contextPath = contextPath;
79  45 this.originalURL = defaultURL;
80    }
81   
82    /**
83    * Creates a new URL factory that uses the server URL and context path specified by the given XWiki context. This
84    * constructor should be used only in tests. Make sure {@link XWikiContext#setURL(URL)} is called before this
85    * constructor.
86    *
87    * @param context
88    */
 
89  37 toggle public XWikiServletURLFactory(XWikiContext context)
90    {
91  37 init(context);
92    }
93   
 
94  64866 toggle @Override
95    public void init(XWikiContext context)
96    {
97  64888 this.defaultURLs = null;
98   
99  64888 this.contextPath = context.getWiki().getWebAppPath(context);
100   
101  64880 URL homepageConfigration = getXWikiHomeParameter(context);
102   
103    // Set the configured home URL for the main wiki
104  64888 setDefaultURL(null, homepageConfigration);
105   
106    // Remember initial request base URL for path for last resort
107  64853 if (homepageConfigration != null && context.isMainWiki()) {
108    // If the main wiki base URL is forced in the configuration use it
109  2 this.originalURL = homepageConfigration;
110    } else {
111    // Remember the request base URL for last resort
112  64865 this.originalURL = HttpServletUtils.getSourceBaseURL(context.getRequest());
113   
114    // If protocol is forced in the configuration witch to it
115  64890 String protocolConfiguration = context.getWiki().Param("xwiki.url.protocol");
116  64876 if (StringUtils.isNoneEmpty(protocolConfiguration)) {
117  0 try {
118  0 this.defaultURL =
119    new URL(protocolConfiguration, this.originalURL.getHost(), this.originalURL.getPort(), "")
120    .toString();
121    } catch (MalformedURLException e) {
122  0 LOGGER.warn("The configured protocol [{}] produce an invalid URL: {}", protocolConfiguration,
123    ExceptionUtils.getRootCauseMessage(e));
124    }
125    }
126    }
127   
128    // Only take into account initial request if it's meant to be
129  64869 XWikiRequest request = context.getRequest();
130  64861 if (!(request.getHttpServletRequest() instanceof XWikiServletRequestStub)
131    || !((XWikiServletRequestStub) request.getHttpServletRequest()).isDaemon()) {
132  43106 this.defaultURL = this.originalURL.toString();
133  43118 setDefaultURL(context.getOriginalWikiId(), this.originalURL);
134    }
135    }
136   
137    /**
138    * @param wikiId the wiki identifier to associate with this URL
139    * @param baseURL the input URL to take into account, null to disable it
140    * @since 10.3
141    */
 
142  107961 toggle public void setDefaultURL(String wikiId, URL baseURL)
143    {
144  108007 if (this.defaultURLs == null) {
145  64867 this.defaultURLs = new HashMap<>();
146    }
147   
148  107989 this.defaultURLs.put(wikiId, baseURL);
149    }
150   
 
151  2496659 toggle protected URL getDefaultURL(String wikiId, XWikiContext xcontext)
152    {
153  2496778 if (this.defaultURLs == null) {
154  23 return this.originalURL;
155    }
156   
157  2496790 URL url = this.defaultURLs.get(wikiId);
158   
159  2496623 if (url != null) {
160  2398479 return url;
161    }
162   
163    // Main wiki can be associated to null
164  98266 if (xcontext.isMainWiki(wikiId)) {
165  97905 return this.defaultURLs.get(null);
166    }
167   
168  386 return null;
169    }
170   
171    /**
172    * Returns the part of the URL identifying the web application. In a normal install, that is <tt>xwiki/</tt>.
173    *
174    * @return The configured context path.
175    */
 
176  0 toggle public String getContextPath()
177    {
178  0 return this.contextPath;
179    }
180   
181    /**
182    * @return a URL made from the xwiki.cfg parameter xwiki.home or null if undefined or unparsable.
183    */
 
184  64846 toggle private static URL getXWikiHomeParameter(final XWikiContext context)
185    {
186  64870 final String surl = getXWikiHomeParameterAsString(context);
187  64889 if (StringUtils.isNotEmpty(surl)) {
188  4 try {
189  4 return normalizeURL(surl, context);
190    } catch (MalformedURLException e) {
191  0 LOGGER.warn("Could not create URL from xwiki.cfg xwiki.home parameter: {}. Ignoring parameter.", surl);
192    }
193    }
194   
195  64886 return null;
196    }
197   
198    /**
199    * @return the xwiki.home parameter or null if undefined.
200    */
 
201  64854 toggle private static String getXWikiHomeParameterAsString(final XWikiContext context)
202    {
203  64866 return context.getWiki().Param("xwiki.home", null);
204    }
205   
 
206  463110 toggle @Override
207    public URL getServerURL(XWikiContext context) throws MalformedURLException
208    {
209  463122 return getServerURL(context.getWikiId(), context);
210    }
211   
212    /**
213    * Get the url of the server EG: http://www.xwiki.org/ This function sometimes will return a URL with a trailing /
214    * and other times not. This is because the xwiki.home param is recommended to have a trailing / but this.serverURL
215    * never does.
216    *
217    * @param wikiId the identifier of the wiki, if null it is assumed to be the same as the wiki which we are currently
218    * displaying.
219    * @param context the XWikiContext used to determine the current wiki and the value if the xwiki.home parameter if
220    * needed as well as access the xwiki server document if in virtual mode.
221    * @return a URL containing the protocol, host, and port (if applicable) of the server to use for the given
222    * database.
223    */
 
224  2496575 toggle public URL getServerURL(String wikiId, XWikiContext context) throws MalformedURLException
225    {
226  2496728 if (wikiId == null) {
227  3 wikiId = context.getWikiId();
228    }
229   
230  2496752 URL inputURL = getDefaultURL(wikiId, context);
231  2496591 if (inputURL != null) {
232    // This is the case if we are getting a URL for a page which is in
233    // the same wiki as the page which is now being displayed.
234  2398476 return inputURL;
235    }
236   
237  98126 URL url = context.getWiki().getServerURL(wikiId, context);
238  98298 if (url != null) {
239  4 return url;
240    }
241   
242    // Fallback on initial request base URL
243  98285 return this.originalURL;
244    }
245   
 
246  0 toggle @Override
247    public URL createURL(String spaces, String name, String action, boolean redirect, XWikiContext context)
248    {
249  0 return createURL(spaces, name, action, context);
250    }
251   
 
252  1968044 toggle @Override
253    public URL createURL(String spaces, String name, String action, String querystring, String anchor, String xwikidb,
254    XWikiContext context)
255    {
256    // Action and Query String transformers
257  1968371 if (("view".equals(action)) && (context.getLinksAction() != null)) {
258  0 action = context.getLinksAction();
259    }
260  1968385 if (context.getLinksQueryString() != null) {
261  16 if (querystring == null) {
262  10 querystring = context.getLinksQueryString();
263    } else {
264  6 querystring = querystring + "&" + context.getLinksQueryString();
265    }
266    }
267   
268  1968427 StringBuilder path = new StringBuilder(this.contextPath);
269  1968379 addServletPath(path, xwikidb, context);
270   
271    // Parse the spaces list into Space References
272  1968488 EntityReference spaceReference = getRelativeEntityReferenceResolver().resolve(spaces, EntityType.SPACE);
273   
274    // For how to encode the various parts of the URL, see http://stackoverflow.com/a/29948396/153102
275  1968454 addAction(path, spaceReference, action, context);
276  1968381 addSpaces(path, spaceReference);
277  1968387 addName(path, name, action, context);
278   
279  1968485 if (!StringUtils.isEmpty(querystring)) {
280  806432 path.append("?");
281  806401 path.append(StringUtils.removeEnd(StringUtils.removeEnd(querystring, "&"), "&amp;"));
282    }
283   
284  1968418 if (!StringUtils.isEmpty(anchor)) {
285  386 path.append("#");
286  386 path.append(encodeWithinQuery(anchor));
287    }
288   
289  1968436 URL result;
290  1968480 try {
291  1968473 result = normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
292    } catch (MalformedURLException e) {
293    // This should not happen
294  0 result = null;
295    }
296   
297  1968315 return result;
298    }
299   
 
300  2246257 toggle private void addServletPath(StringBuilder path, String xwikidb, XWikiContext context)
301    {
302  2246621 if (xwikidb == null) {
303  2 xwikidb = context.getWikiId();
304    }
305   
306  2246578 path.append(context.getWiki().getServletPath(xwikidb, context));
307    }
308   
 
309  2246522 toggle private void addAction(StringBuilder path, EntityReference spaceReference, String action, XWikiContext context)
310    {
311  2246606 boolean showViewAction = context.getWiki().showViewAction(context);
312   
313    // - Always output the action if it's not "view" or if showViewAction is true
314    // - Output "view/<first space name>" when the first space name is an action name and the action is View
315    // (and showViewAction = false)
316  2246695 if ((!"view".equals(action) || showViewAction)
317    || (spaceReference != null && "view".equals(action) && getActionLister().listActions()
318    .contains(spaceReference.extractFirstReference(EntityType.SPACE).getName()))) {
319  2246702 path.append(action).append("/");
320    }
321    }
322   
323    /**
324    * Add the spaces to the path.
325    */
 
326  2033601 toggle private void addSpaces(StringBuilder path, EntityReference spaceReference)
327    {
328  2033594 for (EntityReference reference : spaceReference.getReversedReferenceChain()) {
329  2383484 appendSpacePathSegment(path, reference);
330    }
331    }
332   
 
333  2383215 toggle private void appendSpacePathSegment(StringBuilder path, EntityReference spaceReference)
334    {
335  2383392 path.append(encodeWithinPath(spaceReference.getName())).append('/');
336    }
337   
338    /**
339    * Add the page name to the path.
340    */
 
341  2033610 toggle private void addName(StringBuilder path, String name, String action, XWikiContext context)
342    {
343  2033672 XWiki xwiki = context.getWiki();
344  2033694 if ((xwiki.useDefaultAction(context))
345    || (!name.equals(xwiki.getDefaultPage(context)) || (!"view".equals(action)))) {
346  1230532 path.append(encodeWithinPath(name));
347    }
348    }
349   
 
350  15179 toggle protected void addFileName(StringBuilder path, String fileName, XWikiContext context)
351    {
352  15179 addFileName(path, fileName, true, context);
353    }
354   
 
355  446263 toggle protected void addFileName(StringBuilder path, String fileName, boolean encode, XWikiContext context)
356    {
357  446276 path.append("/");
358  446269 if (encode) {
359    // Encode the given file name as a single path segment.
360  15179 path.append(encodeWithinPath(fileName).replace("+", "%20"));
361    } else {
362  431085 try {
363    // The given file name is actually a file path and so we need to encode each path segment separately.
364  431092 path.append(new URI(null, null, fileName, null));
365    } catch (URISyntaxException e) {
366  0 LOGGER.debug("Failed to encode the file path [{}]. Root cause: [{}]", fileName,
367    ExceptionUtils.getRootCauseMessage(e));
368    // Use the raw file path as a fall-back.
369  0 path.append(fileName);
370    }
371    }
372    }
373   
374    /**
375    * Encode a URL path following the URL specification so that space is encoded as {@code %20} in the path (and not as
376    * {@code +} wnich is not correct). Note that for all other characters we encode them even though some don't need to
377    * be encoded. For example we encode the single quote even though it's not necessary (see
378    * <a href="http://tinyurl.com/j6bjgaq">this explanation</a>). The reason is that otherwise it becomes dangerous to
379    * use a returned URL in the HREF attribute in HTML. Imagine the following {@code <a href='$url'...} and
380    * {@code #set ($url = $doc.getURL(...))}. Now let's assume that {@code $url}'s value is
381    * {@code http://localhost:8080/xwiki/bin/view/A'/B}. This would generate a HTML of
382    * {@code <a href='http://localhost:8080/xwiki/bin/view/A'/B'} which would generated a wrong link to
383    * {@code http://localhost:8080/xwiki/bin/view/A}... Thus if we were only encoding the characters that require
384    * encoding, we would need HMTL writers to encode the received URL and right now we don't do that anywhere in our
385    * code. Thus in order to not introduce any problem and keep it safe we just handle the {@code +} character
386    * specially and encode the rest.
387    *
388    * @param name the path to encode
389    * @return the URL-encoded path segment
390    */
 
391  3629149 toggle private String encodeWithinPath(String name)
392    {
393    // Note: Ideally the following would have been the correct way of writing this method but it causes the issues
394    // mentioned in the javadoc of this method
395    // String encodedName;
396    // try {
397    // encodedName = URIUtil.encodeWithinPath(name, "UTF-8");
398    // } catch (URIException e) {
399    // throw new RuntimeException("Missing charset [UTF-8]", e);
400    // }
401    // return encodedName;
402   
403  3629313 String encodedName;
404  3629404 try {
405  3629494 encodedName = URLEncoder.encode(name, "UTF-8");
406    } catch (Exception e) {
407    // Should not happen (UTF-8 is always available)
408  0 throw new RuntimeException("Missing charset [UTF-8]", e);
409    }
410   
411    // The previous call will convert " " into "+" (and "+" into "%2B") so we need to convert "+" into "%20"
412  3629516 encodedName = encodedName.replaceAll("\\+", "%20");
413   
414  3629538 return encodedName;
415    }
416   
417    /**
418    * Same rationale as {@link #encodeWithinPath(String, XWikiContext)}. Note that we also encode spaces as {@code %20}
419    * even though we could also have encoded them as {@code +}. We do this for consistency (it allows to have the same
420    * implementation for both URL paths and query string).
421    *
422    * @param name the query string part to encode
423    * @return the URL-encoded query string part
424    */
 
425  386 toggle private String encodeWithinQuery(String name)
426    {
427    // Note: Ideally the following would have been the correct way of writing this method but it causes the issues
428    // mentioned in the javadoc of this method
429    // String encodedName;
430    // try {
431    // encodedName = URIUtil.encodeWithinQuery(name, "UTF-8");
432    // } catch (URIException e) {
433    // throw new RuntimeException("Missing charset [UTF-8]", e);
434    // }
435    // return encodedName;
436   
437  386 return encodeWithinPath(name);
438    }
439   
 
440  1133 toggle @Override
441    public URL createExternalURL(String spaces, String name, String action, String querystring, String anchor,
442    String xwikidb, XWikiContext context)
443    {
444  1133 return this.createURL(spaces, name, action, querystring, anchor, xwikidb, context);
445    }
446   
 
447  1434 toggle @Override
448    public URL createSkinURL(String filename, String skin, XWikiContext context)
449    {
450  1434 StringBuilder path = new StringBuilder(this.contextPath);
451  1434 path.append("skins/");
452  1434 path.append(skin);
453  1434 addFileName(path, filename, false, context);
454  1434 try {
455  1434 return normalizeURL(new URL(getServerURL(context), path.toString()), context);
456    } catch (MalformedURLException e) {
457    // This should not happen
458  0 return null;
459    }
460    }
461   
 
462  50030 toggle @Override
463    public URL createSkinURL(String filename, String spaces, String name, String xwikidb, XWikiContext context)
464    {
465  50030 StringBuilder path = new StringBuilder(this.contextPath);
466  50030 addServletPath(path, xwikidb, context);
467   
468    // Parse the spaces list into Space References
469  50030 EntityReference spaceReference = getRelativeEntityReferenceResolver().resolve(spaces, EntityType.SPACE);
470   
471  50029 addAction(path, null, "skin", context);
472  50028 addSpaces(path, spaceReference);
473  50028 addName(path, name, "skin", context);
474  50028 addFileName(path, filename, false, context);
475  50028 try {
476  50028 return normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
477    } catch (MalformedURLException e) {
478    // This should not happen
479  0 return null;
480    }
481    }
482   
 
483  379380 toggle @Override
484    public URL createResourceURL(String filename, boolean forceSkinAction, XWikiContext context)
485    {
486  379398 StringBuilder path = new StringBuilder(this.contextPath);
487  379394 if (forceSkinAction) {
488  213008 addServletPath(path, context.getWikiId(), context);
489  213012 addAction(path, null, "skin", context);
490    }
491  379387 path.append("resources");
492  379397 addFileName(path, filename, false, context);
493  379393 try {
494  379399 return normalizeURL(new URL(getServerURL(context), path.toString()), context);
495    } catch (MalformedURLException e) {
496    // This should not happen
497  0 return null;
498    }
499    }
500   
 
501  0 toggle public URL createTemplateURL(String fileName, XWikiContext context)
502    {
503  0 StringBuilder path = new StringBuilder(this.contextPath);
504  0 path.append("templates");
505  0 addFileName(path, fileName, false, context);
506  0 try {
507  0 return normalizeURL(new URL(getServerURL(context), path.toString()), context);
508    } catch (MalformedURLException e) {
509    // This should not happen
510  0 return null;
511    }
512    }
513   
 
514  15175 toggle @Override
515    public URL createAttachmentURL(String filename, String spaces, String name, String action, String querystring,
516    String xwikidb, XWikiContext context)
517    {
518  15175 if ((context != null) && "viewrev".equals(context.getAction()) && context.get("rev") != null
519    && isContextDoc(xwikidb, spaces, name, context)) {
520  1 try {
521  1 String docRevision = context.get("rev").toString();
522  1 XWikiAttachment attachment =
523    findAttachmentForDocRevision(context.getDoc(), docRevision, filename, context);
524  1 if (attachment == null) {
525  1 action = "viewattachrev";
526    } else {
527  0 long arbId = findDeletedAttachmentForDocRevision(context.getDoc(), docRevision, filename, context);
528  0 return createAttachmentRevisionURL(filename, spaces, name, attachment.getVersion(), arbId,
529    querystring, xwikidb, context);
530    }
531    } catch (XWikiException e) {
532  0 if (LOGGER.isErrorEnabled()) {
533  0 LOGGER.error("Exception while trying to get attachment version !", e);
534    }
535    }
536    }
537   
538  15175 StringBuilder path = new StringBuilder(this.contextPath);
539  15175 addServletPath(path, xwikidb, context);
540   
541    // Parse the spaces list into Space References
542  15175 EntityReference spaceReference = getRelativeEntityReferenceResolver().resolve(spaces, EntityType.SPACE);
543   
544  15175 addAction(path, spaceReference, action, context);
545  15175 addSpaces(path, spaceReference);
546  15175 addName(path, name, action, context);
547  15175 addFileName(path, filename, context);
548   
549  15175 if (!StringUtils.isEmpty(querystring)) {
550  744 path.append("?");
551  744 path.append(StringUtils.removeEnd(StringUtils.removeEnd(querystring, "&"), "&amp;"));
552    }
553   
554  15175 try {
555  15175 return normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
556    } catch (Exception e) {
557  0 return null;
558    }
559    }
560   
561    /**
562    * Check if a document is the original context document. This is needed when generating attachment revision URLs,
563    * since only attachments of the context document should also be versioned.
564    *
565    * @param wiki the wiki name of the document to check
566    * @param spaces the space names of the document to check
567    * @param name the document name of the document to check
568    * @param context the current request context
569    * @return {@code true} if the provided document is the same as the current context document, {@code false}
570    * otherwise
571    */
 
572  4 toggle protected boolean isContextDoc(String wiki, String spaces, String name, XWikiContext context)
573    {
574  4 if (context == null || context.getDoc() == null) {
575  0 return false;
576    }
577   
578    // Use the local serializer so that we don't serialize the wiki part since all we want to do is compare the
579    // passed spaces represented as a String with the current doc's spaces.
580  4 EntityReferenceSerializer<String> serializer =
581    Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "local");
582  4 DocumentReference currentDocumentReference = context.getDoc().getDocumentReference();
583  4 return serializer.serialize(currentDocumentReference.getLastSpaceReference()).equals(spaces)
584    && currentDocumentReference.getName().equals(name)
585    && (wiki == null || currentDocumentReference.getWikiReference().getName().equals(wiki));
586    }
587   
 
588  4 toggle @Override
589    public URL createAttachmentRevisionURL(String filename, String spaces, String name, String revision,
590    String querystring, String xwikidb, XWikiContext context)
591    {
592  4 return createAttachmentRevisionURL(filename, spaces, name, revision, -1, querystring, xwikidb, context);
593    }
594   
 
595  4 toggle public URL createAttachmentRevisionURL(String filename, String spaces, String name, String revision, long recycleId,
596    String querystring, String xwikidb, XWikiContext context)
597    {
598  4 String action = "downloadrev";
599  4 StringBuilder path = new StringBuilder(this.contextPath);
600  4 addServletPath(path, xwikidb, context);
601   
602    // Parse the spaces list into Space References
603  4 EntityReference spaceReference = getRelativeEntityReferenceResolver().resolve(spaces, EntityType.SPACE);
604   
605  4 addAction(path, spaceReference, action, context);
606  4 addSpaces(path, spaceReference);
607  4 addName(path, name, action, context);
608  4 addFileName(path, filename, context);
609   
610  4 String qstring = "rev=" + revision;
611  4 if (recycleId >= 0) {
612  0 qstring += "&rid=" + recycleId;
613    }
614  4 if (!StringUtils.isEmpty(querystring)) {
615  0 qstring += "&" + querystring;
616    }
617  4 path.append("?");
618  4 path.append(StringUtils.removeEnd(StringUtils.removeEnd(qstring, "&"), "&amp;"));
619   
620  4 try {
621  4 return normalizeURL(new URL(getServerURL(xwikidb, context), path.toString()), context);
622    } catch (MalformedURLException e) {
623    // This should not happen
624  0 return null;
625    }
626    }
627   
628    /**
629    * Converts a URL to a relative URL if it's a XWiki URL (keeping only the path + query string + anchor) and leave
630    * the URL unchanged if it's an external URL.
631    * <p>
632    * An URL is considered to be external if its server component doesn't match the server of the current request URL.
633    * This means that URLs are made relative with respect to the current request URL rather than the current wiki set
634    * on the XWiki context. Let's take an example:
635    *
636    * <pre>
637    * {@code
638    * request URL: http://playground.xwiki.org/xwiki/bin/view/Sandbox/TestURL
639    * current wiki: code (code.xwiki.org)
640    * URL (1): http://code.xwiki.org/xwiki/bin/view/Main/WebHome
641    * URL (2): http://playground.xwiki.org/xwiki/bin/view/Spage/Page
642    *
643    * The result will be:
644    * (1) http://code.xwiki.org/xwiki/bin/view/Main/WebHome
645    * (2) /xwiki/bin/view/Spage/Page
646    * }
647    * </pre>
648    *
649    * @param url the URL to convert
650    * @return the converted URL as a string
651    * @see com.xpn.xwiki.web.XWikiDefaultURLFactory#getURL(java.net.URL, com.xpn.xwiki.XWikiContext)
652    */
 
653  2460340 toggle @Override
654    public String getURL(URL url, XWikiContext context)
655    {
656  2460477 String relativeURL = "";
657   
658  2460573 try {
659  2460732 if (url != null) {
660  2460745 String surl = url.toString();
661   
662  2460601 if (this.defaultURL == null || !surl.startsWith(this.defaultURL)) {
663    // External URL: leave it as is.
664  97854 relativeURL = surl;
665    } else {
666    // Internal XWiki URL: convert to relative.
667  2363048 StringBuilder relativeURLBuilder = new StringBuilder(url.getPath());
668  2363017 String querystring = url.getQuery();
669  2363025 if (!StringUtils.isEmpty(querystring)) {
670  813090 relativeURLBuilder.append("?")
671    .append(StringUtils.removeEnd(StringUtils.removeEnd(querystring, "&"), "&amp;"));
672    }
673   
674  2363029 String anchor = url.getRef();
675  2363019 if (!StringUtils.isEmpty(anchor)) {
676  11867 relativeURLBuilder.append("#").append(anchor);
677    }
678   
679  2363040 relativeURL = relativeURLBuilder.toString();
680    }
681    }
682    } catch (Exception e) {
683  0 LOGGER.error("Failed to create URL", e);
684    }
685   
686  2460558 return StringUtils.defaultIfEmpty(relativeURL, "/");
687    }
688   
 
689  69578 toggle @Override
690    public URL getRequestURL(XWikiContext context)
691    {
692  69579 final URL url = super.getRequestURL(context);
693   
694  69577 try {
695  69579 final URL servurl = getServerURL(context);
696    // if use apache mod_proxy we needed to know external host address
697  69581 return normalizeURL(new URL(servurl.getProtocol(), servurl.getHost(), servurl.getPort(), url.getFile()),
698    context);
699    } catch (MalformedURLException e) {
700    // This should not happen
701  0 LOGGER.error("Failed to create request URL", e);
702    }
703   
704  0 return url;
705    }
706   
 
707  1 toggle public XWikiAttachment findAttachmentForDocRevision(XWikiDocument doc, String docRevision, String filename,
708    XWikiContext context) throws XWikiException
709    {
710  1 XWikiAttachment attachment = null;
711  1 XWikiDocument rdoc = context.getWiki().getDocument(doc, docRevision, context);
712  1 if (filename != null) {
713  1 attachment = rdoc.getAttachment(filename);
714    }
715   
716  1 return attachment;
717    }
718   
 
719  0 toggle public long findDeletedAttachmentForDocRevision(XWikiDocument doc, String docRevision, String filename,
720    XWikiContext context) throws XWikiException
721    {
722  0 XWikiAttachment attachment = null;
723  0 XWikiDocument rdoc = context.getWiki().getDocument(doc, docRevision, context);
724  0 if (context.getWiki().hasAttachmentRecycleBin(context) && filename != null) {
725  0 attachment = rdoc.getAttachment(filename);
726  0 if (attachment != null) {
727  0 List<DeletedAttachment> deleted = context.getWiki().getAttachmentRecycleBinStore()
728    .getAllDeletedAttachments(attachment, context, true);
729  0 Collections.reverse(deleted);
730  0 for (DeletedAttachment entry : deleted) {
731  0 if (entry.getDate().after(rdoc.getDate())) {
732  0 return entry.getId();
733    }
734    }
735    }
736    }
737   
738  0 return -1;
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
755    * <a href="https://randomcoder.org/articles/jsessionid-considered-harmful">here</a> and
756    * <a 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  2483647 toggle protected static URL normalizeURL(URL url, XWikiContext context) throws MalformedURLException
764    {
765  2483920 return normalizeURL(url.toExternalForm(), context);
766    }
767   
768    /**
769    * Encodes the passed URL and offers the possibility for Servlet Filter to perform URL rewriting (this is used for
770    * example by Tuckey's URLRewriteFilter for rewriting outbound URLs, see
771    * http://platform.xwiki.org/xwiki/bin/view/Main/ShortURLs).
772    * <p>
773    * However Servlet Container will also add a ";jsessionid=xxx" content to the URL while encoding the URL and we
774    * strip it since we don't want to have that in our URLs as it can cause issues with:
775    * <ul>
776    * <li>security</li>
777    * <li>SEO</li>
778    * <li>clients not expecting jsessionid in URL, for example RSS feed readers which will think that articles are
779    * different as they'll get different URLs everytime they call the XWiki server</li>
780    * </ul>
781    * See why jsessionid are considered harmful
782    * <a href="https://randomcoder.org/articles/jsessionid-considered-harmful">here</a> and
783    * <a href="http://java.dzone.com/articles/java-jsessionid-harmful">here</a>
784    *
785    * @param url the URL to encode and normalize
786    * @param context the XWiki Context used to get access to the Response for encoding the URL
787    * @return the normalized URL
788    * @throws MalformedURLException if the passed URL is invalid
789    */
 
790  2483979 toggle protected static URL normalizeURL(String url, XWikiContext context) throws MalformedURLException
791    {
792    // For robust session tracking, all URLs emitted by a servlet should be encoded. Otherwise, URL rewriting
793    // cannot be used with browsers which do not support cookies.
794  2484068 String encodedURLAsString = context.getResponse().encodeURL(url);
795   
796    // Remove a potential jsessionid in the URL
797  2483893 encodedURLAsString = encodedURLAsString.replaceAll(";jsessionid=.*?(?=\\?|$)", "");
798   
799  2483890 return new URL(encodedURLAsString);
800    }
801   
 
802  2033663 toggle private EntityReferenceResolver<String> getRelativeEntityReferenceResolver()
803    {
804  2033665 if (this.relativeEntityReferenceResolver == null) {
805  35207 this.relativeEntityReferenceResolver = Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "relative");
806    }
807  2033680 return this.relativeEntityReferenceResolver;
808    }
809   
 
810  3 toggle private EntityResourceActionLister getActionLister()
811    {
812  3 if (this.actionLister == null) {
813  3 this.actionLister = Utils.getComponent(EntityResourceActionLister.class);
814    }
815  3 return this.actionLister;
816    }
817    }