/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.xpn.xwiki.web; import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Vector; import javax.script.ScriptContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.velocity.VelocityContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.bridge.event.ActionExecutedEvent; import org.xwiki.bridge.event.ActionExecutingEvent; import org.xwiki.component.util.DefaultParameterizedType; import org.xwiki.container.Container; import org.xwiki.container.servlet.ServletContainerException; import org.xwiki.container.servlet.ServletContainerInitializer; import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.csrf.CSRFToken; import org.xwiki.job.event.status.JobProgressManager; import org.xwiki.job.internal.DefaultJobProgress; import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.model.EntityType; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceProvider; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.EntityReferenceValueProvider; import org.xwiki.model.reference.SpaceReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.observation.ObservationManager; import org.xwiki.observation.WrappedThreadEventListener; import org.xwiki.rendering.internal.transformation.MutableRenderingContext; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.rendering.transformation.RenderingContext; import org.xwiki.resource.NotFoundResourceHandlerException; import org.xwiki.resource.ResourceReference; import org.xwiki.resource.ResourceReferenceHandler; import org.xwiki.resource.ResourceReferenceManager; import org.xwiki.resource.ResourceType; import org.xwiki.resource.entity.EntityResourceReference; import org.xwiki.resource.internal.DefaultResourceReferenceHandlerChain; import org.xwiki.script.ScriptContextManager; import org.xwiki.template.TemplateManager; import org.xwiki.velocity.VelocityManager; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.monitor.api.MonitorPlugin; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin; /** * <p> * Root class for most XWiki actions. It provides a common framework that allows actions to execute just the specific * action code, handling the extra activities, such as preparing the context and retrieving the document corresponding * to the URL. * </p> * <p> * It defines two methods, {@link #action(XWikiContext)} and {@link #render(XWikiContext)}, that should be overridden by * specific actions. {@link #action(XWikiContext)} should contain the processing part of the action. * {@link #render(XWikiContext)} should return the name of a template that should be rendered, or manually write to the * {@link XWikiResponse response} stream. * </p> * <p> * Serving a request goes through the following phases: * </p> * <ul> * <li>Wrapping the request and response object in XWiki specific wrappers</li> * <li>Prepare the request {@link XWikiContext XWiki-specific context}</li> * <li>Initialize/retrieve the XWiki object corresponding to the requested wiki</li> * <li>Handle file uploads</li> * <li>Prepare the velocity context</li> * <li>Prepare the document objects corresponding to the requested URL</li> * <li>Send action pre-notifications to listeners</li> * <li>Run the overridden {@link #action(XWikiContext)}</li> * <li>If {@link #action(XWikiContext)} returns true, run the overridden {@link #render(XWikiContext)}</li> * <li>If {@link #render(XWikiContext)} returned a string (template name), render the template with that name</li> * <li>Send action post-notifications to listeners</li> * </ul> * <p> * During this process, also handle specific errors, like when a document does not exist, or the user does not have the * right to perform the current action. * </p> */ public abstract class XWikiAction extends Action { public static final String ACTION_PROGRESS = "actionprogress"; private static final Logger LOGGER = LoggerFactory.getLogger(XWikiAction.class); /** * Actions that need to be resolved on the main wiki instead of the current non-existing wiki. This is used to be * able to render the skin even on a wiki that doesn't exist. */ private static final List<String> ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST = Arrays.asList("skin", "ssx", "jsx", "download"); /** * Indicate if the action is blocked until XWiki is initialized. */ protected boolean waitForXWikiInitialization = true; /** * Indicate if the XWiki.RedirectClass is handled by the action (see handleRedirectObject()). */ protected boolean handleRedirectObject = false; private ContextualLocalizationManager localization; private JobProgressManager progress; private ScriptContextManager scriptContextManager; protected ContextualLocalizationManager getLocalization() { if (this.localization == null) { this.localization = Utils.getComponent(ContextualLocalizationManager.class); } return this.localization; } protected String localizePlainOrKey(String key, Object... parameters) { return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key); } protected JobProgressManager getProgress() { if (this.progress == null) { this.progress = Utils.getComponent(JobProgressManager.class); } return this.progress; } /** * @return the current unmodified {@link ScriptContext} instance * @since 8.3M1 */ protected ScriptContext getCurrentScriptContext() { if (this.scriptContextManager == null) { this.scriptContextManager = Utils.getComponent(ScriptContextManager.class); } return this.scriptContextManager.getCurrentScriptContext(); } /** * Handle server requests. * * @param mapping The ActionMapping used to select this instance * @param form The optional ActionForm bean for this request (if any) * @param req The HTTP request we are processing * @param resp The HTTP response we are creating * @throws IOException if an input/output error occurs * @throws ServletException if a servlet exception occurs */ @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse resp) throws Exception { ActionForward actionForward; XWikiContext context = null; try { // Initialize the XWiki Context which is the main object used to pass information across // classes/methods. It's also wrapping the request, response, and all container objects // in general. context = initializeXWikiContext(mapping, form, req, resp); // From this line forward all information can be found in the XWiki Context. actionForward = execute(context); } finally { if (context != null) { cleanupComponents(); } } return actionForward; } public ActionForward execute(XWikiContext context) throws Exception { MonitorPlugin monitor = null; FileUploadPlugin fileupload = null; DefaultJobProgress actionProgress = null; ObservationManager om = Utils.getComponent(ObservationManager.class); Execution execution = Utils.getComponent(Execution.class); String docName = ""; boolean debug = StringUtils.equals(context.getRequest().get("debug"), "true"); try { String action = context.getAction(); // Start progress if (debug && om != null && execution != null) { actionProgress = new DefaultJobProgress(context.getURL().toExternalForm()); om.addListener(new WrappedThreadEventListener(actionProgress)); // Register the action progress in the context ExecutionContext econtext = execution.getContext(); if (econtext != null) { econtext.setProperty(XWikiAction.ACTION_PROGRESS, actionProgress); } } getProgress().pushLevelProgress(2, this); getProgress().startStep(this, "Get XWiki instance"); // Initialize context.getWiki() with the main wiki XWiki xwiki; // Verify that the requested wiki exists try { xwiki = XWiki.getXWiki(this.waitForXWikiInitialization, context); // If XWiki is still initializing display initialization template if (xwiki == null) { // Display initialization template renderInit(context); // Initialization template has been displayed, stop here. return null; } } catch (XWikiException e) { // If the wiki asked by the user doesn't exist, then we first attempt to use any existing global // redirects. If there are none, then we display the specific error template. if (e.getCode() == XWikiException.ERROR_XWIKI_DOES_NOT_EXIST) { xwiki = XWiki.getMainXWiki(context); // Initialize the url factory XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context); context.setURLFactory(urlf); // Initialize the velocity context and its bindings so that it may be used in the velocity templates // that we // are parsing below. VelocityManager velocityManager = Utils.getComponent(VelocityManager.class); VelocityContext vcontext = velocityManager.getVelocityContext(); if (!sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) { // Starting XWiki 5.0M2, 'xwiki.virtual.redirect' was removed. Warn users still using it. if (!StringUtils.isEmpty(context.getWiki().Param("xwiki.virtual.redirect"))) { LOGGER.warn(String.format("%s %s", "'xwiki.virtual.redirect' is no longer supported.", "Please update your configuration and/or see XWIKI-8914 for more details.")); } // Display the error template only for actions that are not ignored if (!ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST.contains(action)) { // Add localization resources to the context xwiki.prepareResources(context); // Set the main home page in the main space of the main wiki as the current requested entity // since we cannot set the non existing one as it would generate errors obviously... EntityReferenceValueProvider valueProvider = Utils.getComponent(EntityReferenceValueProvider.class); xwiki.setPhonyDocument(new DocumentReference(valueProvider.getDefaultValue(EntityType.WIKI), valueProvider.getDefaultValue(EntityType.SPACE), valueProvider.getDefaultValue(EntityType.DOCUMENT)), context, vcontext); // Parse the error template Utils.parseTemplate(context.getWiki().Param("xwiki.wiki_exception", "wikidoesnotexist"), context); // Error template was displayed, stop here. return null; } // At this point, we allow regular execution of the ignored action because even if the wiki // does not exist, we still need to allow UI resources to be retrieved (from the filesystem // and the main wiki) or our error template will not be rendered properly. // Proceed with serving the main wiki } else { // Global redirect was executed, stop here. return null; } } else { LOGGER.error("Uncaught exception during XWiki initialisation:", e); throw e; } } // Send global redirection (if any) if (sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) { return null; } XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context); context.setURLFactory(urlf); // Handle ability to enter space URLs and convert them to page URLs (Nested Documents) if (redirectSpaceURLs(action, urlf, xwiki, context)) { return null; } String sajax = context.getRequest().get("ajax"); boolean ajax = false; if (sajax != null && !sajax.trim().equals("") && !sajax.equals("0")) { ajax = true; } context.put("ajax", ajax); // Any error before this will be treated using a redirection to an error page if (monitor != null) { monitor.startTimer("request"); } getProgress().startStep(this, "Execute request"); VelocityManager velocityManager = Utils.getComponent(VelocityManager.class); VelocityContext vcontext = velocityManager.getVelocityContext(); getProgress().pushLevelProgress(7, this); boolean eventSent = false; try { getProgress().startStep(this, "Prepare documents and put them in the context"); // Prepare documents and put them in the context if (!xwiki.prepareDocuments(context.getRequest(), context, vcontext)) { return null; } // Start monitoring timer monitor = (MonitorPlugin) xwiki.getPlugin("monitor", context); if (monitor != null) { monitor.startRequest("", context.getAction(), context.getURL()); monitor.startTimer("multipart"); } getProgress().startStep(this, "Parses multipart"); // Parses multipart so that params in multipart are available for all actions fileupload = Utils.handleMultipart(context.getRequest().getHttpServletRequest(), context); if (monitor != null) { monitor.endTimer("multipart"); } if (monitor != null) { monitor.setWikiPage(context.getDoc().getFullName()); } getProgress().startStep(this, "Send [" + context.getAction() + "] action start event"); // For the moment we're sending the XWiki context as the data, but this will be // changed in the future, when the whole platform will be written using components // and there won't be a need for the context. try { ActionExecutingEvent event = new ActionExecutingEvent(context.getAction()); om.notify(event, context.getDoc(), context); eventSent = true; if (event.isCanceled()) { // Action has been canceled // TODO: do something special ? return null; } } catch (Throwable ex) { LOGGER.error("Cannot send action notifications for document [" + context.getDoc() + " using action [" + context.getAction() + "]", ex); } if (monitor != null) { monitor.endTimer("prenotify"); } // Call the Actions getProgress().startStep(this, "Search and execute entity resource handler"); // Call the new Entity Resource Reference Handler. ResourceReferenceHandler entityResourceReferenceHandler = Utils.getComponent( new DefaultParameterizedType(null, ResourceReferenceHandler.class, ResourceType.class), "bin"); ResourceReference resourceReference = Utils.getComponent(ResourceReferenceManager.class).getResourceReference(); try { entityResourceReferenceHandler.handle(resourceReference, new DefaultResourceReferenceHandlerChain(Collections.<ResourceReferenceHandler>emptyList())); // Don't let the old actions kick in! return null; } catch (NotFoundResourceHandlerException e) { // No Entity Resource Action has been found. Don't do anything and let it go through // so that the old Action system kicks in... } catch (Throwable e) { // Some real failure, log it since it's a problem but still allow the old Action system a chance // to do something... LOGGER.error("Failed to handle Action for Resource [{}]", resourceReference, e); } getProgress().startStep(this, "Execute action render"); // Handle the XWiki.RedirectClass object that can be attached to the current document boolean hasRedirect = false; if (handleRedirectObject) { hasRedirect = handleRedirectObject(context); } // Then call the old Actions for backward compatibility (and because a lot of them have not been // migrated to new Actions yet). String renderResult = null; XWikiDocument doc = context.getDoc(); docName = doc.getFullName(); if (!hasRedirect && action(context)) { renderResult = render(context); } if (renderResult != null) { if (doc.isNew() && "view".equals(context.getAction()) && !"recyclebin".equals(context.getRequest().get("viewer")) && !"children".equals(context.getRequest().get("viewer")) && !"siblings".equals(context.getRequest().get("viewer"))) { String page = Utils.getPage(context.getRequest(), "docdoesnotexist"); getProgress().startStep(this, "Execute template [" + page + "]"); Utils.parseTemplate(page, context); } else { String page = Utils.getPage(context.getRequest(), renderResult); getProgress().startStep(this, "Execute template [" + page + "]"); Utils.parseTemplate(page, !page.equals("direct"), context); } } return null; } catch (Throwable e) { if (e instanceof IOException) { e = new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION, "Exception while sending response", e); } if (!(e instanceof XWikiException)) { e = new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_UNKNOWN, "Uncaught exception", e); } try { XWikiException xex = (XWikiException) e; if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) { // Connection aborted from the client side, there's not much we can do on the server side. We // simply ignore it. LOGGER.debug("Connection aborted", e); // We don't write any other message to the response, as the connection is broken, anyway. return null; } else if (xex.getCode() == XWikiException.ERROR_XWIKI_ACCESS_DENIED) { Utils.parseTemplate(context.getWiki().Param("xwiki.access_exception", "accessdenied"), context); return null; } else if (xex.getCode() == XWikiException.ERROR_XWIKI_USER_INACTIVE) { Utils.parseTemplate(context.getWiki().Param("xwiki.user_exception", "userinactive"), context); return null; } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND) { context.put("message", "attachmentdoesnotexist"); Utils.parseTemplate( context.getWiki().Param("xwiki.attachment_exception", "attachmentdoesnotexist"), context); return null; } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_URL_EXCEPTION) { vcontext.put("message", localizePlainOrKey("platform.core.invalidUrl")); xwiki.setPhonyDocument(xwiki.getDefaultSpace(context) + "." + xwiki.getDefaultPage(context), context, vcontext); context.getResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST); Utils.parseTemplate(context.getWiki().Param("xwiki.invalid_url_exception", "error"), context); return null; } vcontext.put("exp", e); if (LOGGER.isWarnEnabled()) { // Don't log "Broken Pipe" exceptions since they're not real errors and we don't want to pollute // the logs with unnecessary stack traces. It just means the client side has cancelled the // connection. if (ExceptionUtils.getRootCauseMessage(e).equals("IOException: Broken pipe")) { return null; } LOGGER.warn("Uncaught exception: " + e.getMessage(), e); } // If the request is an AJAX request, we don't return a whole HTML page, but just the exception // inline. String exceptionTemplate = ajax ? "exceptioninline" : "exception"; Utils.parseTemplate(Utils.getPage(context.getRequest(), exceptionTemplate), context); return null; } catch (XWikiException ex) { if (ex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) { LOGGER.error("Connection aborted"); } } catch (Exception e2) { // I hope this never happens LOGGER.error("Uncaught exceptions (inner): ", e); LOGGER.error("Uncaught exceptions (outer): ", e2); } return null; } finally { // Let's make sure we have flushed content and closed try { context.getResponse().getWriter().flush(); } catch (Throwable e) { // This might happen if the connection was closed, for example. // If we can't flush, then there's nothing more we can send to the client. } if (monitor != null) { monitor.endTimer("request"); monitor.startTimer("notify"); } if (eventSent) { // For the moment we're sending the XWiki context as the data, but this will be // changed in the future, when the whole platform will be written using components // and there won't be a need for the context. try { om.notify(new ActionExecutedEvent(context.getAction()), context.getDoc(), context); } catch (Throwable ex) { LOGGER.error("Cannot send action notifications for document [" + docName + " using action [" + context.getAction() + "]", ex); } } if (monitor != null) { monitor.endTimer("notify"); } getProgress().startStep(this, "Cleanup database connections"); // Make sure we cleanup database connections // There could be cases where we have some xwiki.getStore().cleanUp(context); getProgress().popLevelProgress(this); } } finally { // End request if (monitor != null) { monitor.endRequest(); } // Stop progress if (actionProgress != null) { getProgress().popLevelProgress(this); om.removeListener(actionProgress.getName()); } if (fileupload != null) { fileupload.cleanFileList(context); } } } private void renderInit(XWikiContext xcontext) throws Exception { RenderingContext renderingContext = Utils.getComponent(RenderingContext.class); MutableRenderingContext mutableRenderingContext = renderingContext instanceof MutableRenderingContext ? (MutableRenderingContext) renderingContext : null; if (mutableRenderingContext != null) { mutableRenderingContext.push(renderingContext.getTransformation(), renderingContext.getXDOM(), renderingContext.getDefaultSyntax(), "init.vm", renderingContext.isRestricted(), Syntax.XHTML_1_0); } xcontext.getResponse().setStatus(503); xcontext.getResponse().setContentType("text/html; charset=UTF-8"); try { Utils.getComponent(TemplateManager.class).render("init.vm", xcontext.getResponse().getWriter()); } finally { if (mutableRenderingContext != null) { mutableRenderingContext.pop(); } } xcontext.getResponse().flushBuffer(); xcontext.setFinished(true); } protected XWikiContext initializeXWikiContext(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse resp) throws XWikiException, ServletException { String action = mapping.getName(); XWikiRequest request = new XWikiServletRequest(req); XWikiResponse response = new XWikiServletResponse(resp); XWikiContext context = Utils.prepareContext(action, request, response, new XWikiServletContext(this.servlet.getServletContext())); // This code is already called by struts. // However struts will also set all the parameters of the form data // directly from the request objects. // However because of bug https://jira.xwiki.org/browse/XWIKI-2422 // We need to perform encoding of windows-1252 chars in ISO mode // So we need to make sure this code is called // TODO: completely get rid of struts so that we control this part of the code and can reduce drastically the // number of calls if (form != null) { form.reset(mapping, request); } // Add the form to the context context.setForm((XWikiForm) form); // Initialize the Container component which is the new way of transporting the Context in the new // component architecture. initializeContainerComponent(context); return context; } protected void initializeContainerComponent(XWikiContext context) throws ServletException { // Initialize the Container fields (request, response, session). // Note that this is a bridge between the old core and the component architecture. // In the new component architecture we use ThreadLocal to transport the request, // response and session to components which require them. // In the future this Servlet will be replaced by the XWikiPlexusServlet Servlet. ServletContainerInitializer containerInitializer = Utils.getComponent(ServletContainerInitializer.class); try { containerInitializer.initializeRequest(context.getRequest().getHttpServletRequest(), context); containerInitializer.initializeResponse(context.getResponse()); containerInitializer.initializeSession(context.getRequest().getHttpServletRequest()); } catch (ServletContainerException e) { throw new ServletException("Failed to initialize Request/Response or Session", e); } } protected void cleanupComponents() { Container container = Utils.getComponent(Container.class); Execution execution = Utils.getComponent(Execution.class); // We must ensure we clean the ThreadLocal variables located in the Container and Execution // components as otherwise we will have a potential memory leak. container.removeRequest(); container.removeResponse(); container.removeSession(); execution.removeContext(); } public String getRealPath(String path) { return this.servlet.getServletContext().getRealPath(path); } // hook public boolean action(XWikiContext context) throws XWikiException { return true; } // hook public String render(XWikiContext context) throws XWikiException { return null; } /** * Redirect the user to an other location if the document holds an XWiki.RedirectClass instance (used when a * document is moved). * * @param context the XWiki context * @return either or not a redirection have been sent * @throws XWikiException if error occurs * @since 8.0RC1 * @since 7.4.2 */ protected boolean handleRedirectObject(XWikiContext context) throws XWikiException { WikiReference wikiReference = context.getWikiReference(); // Look if the document has a redirect object XWikiDocument doc = context.getDoc(); BaseObject redirectObj = doc.getXObject(new DocumentReference("RedirectClass", new SpaceReference("XWiki", wikiReference))); if (redirectObj == null) { return false; } // Get the location String location = redirectObj.getStringValue("location"); if (StringUtils.isBlank(location)) { return false; } // Resolve the location to get a reference DocumentReferenceResolver<String> resolver = Utils.getComponent(DocumentReferenceResolver.TYPE_STRING); EntityReference locationReference = resolver.resolve(location, wikiReference); // Get the type of the current target ResourceReference resourceReference = Utils.getComponent(ResourceReferenceManager.class).getResourceReference(); EntityResourceReference entityResource = (EntityResourceReference) resourceReference; EntityReference entityReference = entityResource.getEntityReference(); // If the entity is inside a document, compute the new entity with the new document part. if (entityReference.getType().ordinal() > EntityType.DOCUMENT.ordinal()) { EntityReference parentDocument = entityReference.extractReference(EntityType.DOCUMENT); locationReference = entityReference.replaceParent(parentDocument, locationReference); } // Get the URL corresponding to the location // Note: the anchor part is lost in the process, because it is not sent to the server // (see: http://stackoverflow.com/a/4276491) String url = context.getWiki().getURL(locationReference, context.getAction(), context.getRequest().getQueryString(), null, context); // Send the redirection try { context.getResponse().sendRedirect(url); } catch (IOException e) { throw new XWikiException("Failed to redirect.", e); } return true; } protected void handleRevision(XWikiContext context) throws XWikiException { String rev = context.getRequest().getParameter("rev"); if (rev != null) { context.put("rev", rev); XWikiDocument doc = (XWikiDocument) context.get("doc"); XWikiDocument tdoc = (XWikiDocument) context.get("tdoc"); XWikiDocument rdoc = (!doc.getLocale().equals(tdoc.getLocale())) ? doc : context.getWiki().getDocument(doc, rev, context); XWikiDocument rtdoc = (doc.getLocale().equals(tdoc.getLocale())) ? rdoc : context.getWiki().getDocument(tdoc, rev, context); context.put("tdoc", rtdoc); context.put("cdoc", rdoc); context.put("doc", rdoc); } } /** * Send redirection based on a regexp pattern (if any) set at the main wiki level. To enable this feature you must * add xwiki.preferences.redirect=1 to your xwiki.cfg. * * @param response the servlet response * @param url url of the request * @param context the XWiki context * @return true if a redirection has been sent */ protected boolean sendGlobalRedirect(XWikiResponse response, String url, XWikiContext context) throws Exception { if ("1".equals(context.getWiki().Param("xwiki.preferences.redirect"))) { // Note: This implementation is not performant at all and will slow down the wiki as the number // of redirects increases. A better implementation would use a cache of redirects and would use // the notification mechanism to update the cache when the XWiki.XWikiPreferences document is // modified. XWikiDocument globalPreferences = context.getWiki().getDocument("xwiki:XWiki.XWikiPreferences", context); Vector<BaseObject> redirects = globalPreferences.getObjects("XWiki.GlobalRedirect"); if (redirects != null) { for (BaseObject redir : redirects) { if (redir != null) { String p = redir.getStringValue("pattern"); if (p != null && url.matches(p)) { String dest = redir.getStringValue("destination"); response.sendRedirect(url.replaceAll(p, dest)); return true; } } } } } return false; } protected void sendRedirect(XWikiResponse response, String url) throws XWikiException { try { if (url != null) { response.sendRedirect(response.encodeRedirectURL(url)); } } catch (IOException e) { Object[] args = { url }; throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_REDIRECT_EXCEPTION, "Exception while sending redirect to page {0}", e, args); } } /** * Gets the translated version of a document, in the specified language. If the translation does not exist, a new * document translation is created. If the requested language does not correspond to a translation (is not defined * or is the same as the main document), then the main document is returned. * * @param doc the main (default, untranslated) document to translate * @param language the requested document language * @param context the current request context * @return the translated document, or the original untranslated document if the requested language is not a * translation * @throws XWikiException if the translation cannot be retrieved from the database */ protected XWikiDocument getTranslatedDocument(XWikiDocument doc, String language, XWikiContext context) throws XWikiException { XWikiDocument tdoc; if (StringUtils.isBlank(language) || language.equals("default") || language.equals(doc.getDefaultLanguage())) { tdoc = doc; } else { tdoc = doc.getTranslatedDocument(language, context); if (tdoc == doc) { tdoc = new XWikiDocument(doc.getDocumentReference()); tdoc.setLanguage(language); tdoc.setStore(doc.getStore()); } tdoc.setTranslation(1); } return tdoc; } /** * Perform CSRF check and redirect to the resubmission page if needed. Throws an exception if the access should be * denied, returns false if the check failed and the user will be redirected to a resubmission page. * * @param context current xwiki context containing the request * @return true if the check succeeded, false if resubmission is needed * @throws XWikiException if the check fails */ protected boolean csrfTokenCheck(XWikiContext context) throws XWikiException { CSRFToken csrf = Utils.getComponent(CSRFToken.class); try { String token = context.getRequest().getParameter("form_token"); if (!csrf.isTokenValid(token)) { sendRedirect(context.getResponse(), csrf.getResubmissionURL()); return false; } } catch (XWikiException exception) { // too bad throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED, "Access denied, secret token verification failed", exception); } return true; } /** * In order to let users enter URLs to Spaces we do the following when receiving {@code /A/B} (where A and B are * spaces): * <ul> * <li>check that the action is "view" (we only support this for the view action since otherwise this would break * apps written before this concept was introduced in XWiki 7.2M1)</li> * <li>if A.B exists then continue</li> * <li>if A.B doesn't exist then forward to A.B.WebHome</li> * </ul> * In order to disable this redirect you should provide the {@code spaceRedirect=false} Query String parameter and * value. * * @since 7.2M1 */ private boolean redirectSpaceURLs(String action, XWikiURLFactory urlf, XWiki xwiki, XWikiContext context) throws Exception { if ("view".equals(action) && !"false".equalsIgnoreCase(context.getRequest().getParameter("spaceRedirect"))) { DocumentReference reference = xwiki.getDocumentReference(context.getRequest(), context); if (!xwiki.exists(reference, context)) { String defaultDocumentName = Utils.getComponent(EntityReferenceProvider.class) .getDefaultReference(EntityType.DOCUMENT).getName(); // Avoid an infinite loop by ensuring we're not on a WebHome already if (!reference.getName().equals(defaultDocumentName)) { // Consider the reference as a Space Reference and Construct a new reference to the home of that // Space. Then generate the URL for it and forward to it SpaceReference spaceReference = new SpaceReference(reference.getName(), reference.getParent()); EntityReferenceSerializer<String> localSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "local"); // Extract the anchor String anchor = new URL(context.getRequest().getRequestURL().toString()).getRef(); URL forwardURL = urlf.createURL(localSerializer.serialize(spaceReference), defaultDocumentName, action, context.getRequest().getQueryString(), anchor, spaceReference.getWikiReference().getName(), context); // Since createURL() contain the webapp context and since RequestDispatcher should not contain it, // we need to remove it! String webappContext = xwiki.getWebAppPath(context); String relativeURL = urlf.getURL(forwardURL, context); relativeURL = '/' + StringUtils.substringAfter(relativeURL, webappContext); context.getRequest().getRequestDispatcher(relativeURL).forward(context.getRequest(), context.getResponse()); return true; } } } return false; } }