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

File SaveAndContinueAction.java

 

Coverage histogram

../../../../img/srcFileCovDistChart5.png
76% of files have more coverage

Code metrics

38
79
8
1
278
161
31
0.39
9.88
8
3.88

Classes

Class Line # Actions
SaveAndContinueAction 39 79 0% 31 68
0.45645.6%
 

Contributing tests

No tests hitting this source file were found.

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.io.IOException;
23   
24    import javax.servlet.http.HttpServletResponse;
25   
26    import org.apache.commons.lang3.StringUtils;
27    import org.slf4j.Logger;
28    import org.slf4j.LoggerFactory;
29    import org.xwiki.csrf.CSRFToken;
30   
31    import com.xpn.xwiki.XWikiContext;
32    import com.xpn.xwiki.XWikiException;
33   
34    /**
35    * Action used for saving and returning to the edit page rather than viewing changes.
36    *
37    * @version $Id: 6faffaa6bc4bc22c7048498cddc8aed2b4c861b1 $
38    */
 
39    public class SaveAndContinueAction extends XWikiAction
40    {
41   
42    /** Key for storing the wrapped action in the context. */
43    private static final String WRAPPED_ACTION_CONTEXT_KEY = "SaveAndContinueAction.wrappedAction";
44   
45    /** Logger. */
46    private static final Logger LOGGER = LoggerFactory.getLogger(SaveAndContinueAction.class);
47   
48    /**
49    * Write an error response to an ajax request.
50    *
51    * @param httpStatusCode The status code to set on the response.
52    * @param message The message that should be displayed.
53    * @param context the context.
54    */
 
55  0 toggle private void writeAjaxErrorResponse(int httpStatusCode, String message, XWikiContext context)
56    {
57  0 try {
58  0 context.getResponse().setContentType("text/plain");
59  0 context.getResponse().setStatus(httpStatusCode);
60  0 context.getResponse().setCharacterEncoding(context.getWiki().getEncoding());
61  0 context.getResponse().getWriter().print(message);
62    } catch (IOException e) {
63  0 LOGGER.error("Failed to send error response to AJAX save and continue request.", e);
64    }
65    }
66   
67    /**
68    * Perform the internal action implied by the save and continue request. If the request is an ajax request,
69    * writeAjaxErrorResponse will be called. The return value will be that of the wrapped action.
70    *
71    * @param isAjaxRequest Indicate if this is an ajax request.
72    * @param back The back URL.
73    * @param context The xwiki context.
74    * @return {\code false} if the request is an ajax request, otherwise the return value of the wrapped action.
75    * @throws XWikiException
76    */
 
77  52 toggle private boolean doWrappedAction(boolean isAjaxRequest, String back, XWikiContext context) throws XWikiException
78    {
79   
80  52 boolean failure = false;
81   
82    // This will never be true if "back" comes from request.getHeader("referer")
83  52 if (back != null && back.contains("editor=class")) {
84  8 PropUpdateAction pua = new PropUpdateAction();
85   
86  8 if (pua.propUpdate(context)) {
87  0 if (isAjaxRequest) {
88  0 String errorMessage = localizePlainOrKey((String) context.get("message"));
89  0 writeAjaxErrorResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage, context);
90    } else {
91  0 context.put(WRAPPED_ACTION_CONTEXT_KEY, pua);
92    }
93   
94  0 failure = true;
95    }
96    } else {
97  44 SaveAction sa = new SaveAction();
98  44 if (sa.save(context)) {
99  0 if (isAjaxRequest) {
100  0 String errorMessage =
101    localizePlainOrKey("core.editors.saveandcontinue.theDocumentWasNotSaved");
102    // This should not happen. SaveAction.save(context) should normally throw an
103    // exception when failing during save and continue.
104  0 LOGGER.error("SaveAction.save(context) returned true while using save & continue");
105  0 writeAjaxErrorResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage, context);
106    } else {
107  0 context.put(WRAPPED_ACTION_CONTEXT_KEY, sa);
108    }
109   
110  0 failure = true;
111    } else {
112    // Lock back the document
113  44 context.getDoc().getTranslatedDocument(context).setLock(context.getUser(), context);
114    }
115    }
116   
117  52 return failure;
118    }
119   
120    /**
121    * @param isAjaxRequest Indicate if this is an ajax request.
122    * @param context The XWiki context.
123    * @throws XWikiException unless it is an ajax request.
124    */
 
125  0 toggle private void handleCSRFValidationFailure(boolean isAjaxRequest, XWikiContext context)
126    throws XWikiException
127    {
128  0 final String csrfCheckFailedMessage = localizePlainOrKey("core.editors.saveandcontinue.csrfCheckFailed");
129  0 if (isAjaxRequest) {
130  0 writeAjaxErrorResponse(HttpServletResponse.SC_FORBIDDEN,
131    csrfCheckFailedMessage,
132    context);
133    } else {
134  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP,
135    XWikiException.ERROR_XWIKI_ACCESS_TOKEN_INVALID,
136    csrfCheckFailedMessage);
137    }
138    }
139   
140    /**
141    * @param isAjaxRequest Indicate if this is an ajax request.
142    * @param exception The exception to handle.
143    * @param context The XWiki context.
144    * @throws XWikiException unless it is an ajax request.
145    */
 
146  0 toggle private void handleException(boolean isAjaxRequest, Exception exception, XWikiContext context)
147    throws XWikiException
148    {
149  0 if (isAjaxRequest) {
150  0 String errorMessage =
151    localizePlainOrKey("core.editors.saveandcontinue.exceptionWhileSaving", exception.getMessage());
152   
153  0 writeAjaxErrorResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage, context);
154   
155  0 String logMessage = "Caught exception during save and continue";
156  0 if (exception instanceof XWikiException) {
157  0 LOGGER.info(logMessage, exception);
158    } else {
159  0 LOGGER.error(logMessage, exception);
160    }
161    } else {
162  0 if (exception instanceof XWikiException) {
163  0 throw (XWikiException) exception;
164    } else {
165  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_UNKNOWN,
166    "Uncaught exception", exception);
167    }
168    }
169    }
170   
 
171  52 toggle @Override
172    public boolean action(XWikiContext context) throws XWikiException
173    {
174  52 CSRFToken csrf = Utils.getComponent(CSRFToken.class);
175  52 String token = context.getRequest().getParameter("form_token");
176   
177    // If the request is an ajax request, we will:
178    //
179    // 1) _not_ send a redirect response
180    //
181    // 2) if for any reason the document is not saved, call the method writeAjaxErrorResponse and return false
182    // (which normally indicates success).
183   
184  52 final boolean isAjaxRequest = Utils.isAjaxRequest(context);
185   
186  52 if (!csrf.isTokenValid(token)) {
187  0 handleCSRFValidationFailure(isAjaxRequest, context);
188  0 return false;
189    }
190   
191    // Try to find the URL of the edit page which we came from
192  52 String back = findBackURL(context);
193   
194  52 try {
195  52 if (doWrappedAction(isAjaxRequest, back, context)) {
196  0 return !isAjaxRequest;
197    }
198    } catch (Exception e) {
199  0 handleException(isAjaxRequest, e, context);
200  0 return !isAjaxRequest;
201    }
202   
203    // If this is an ajax request, no need to redirect.
204  52 if (isAjaxRequest) {
205  33 context.getResponse().setStatus(HttpServletResponse.SC_NO_CONTENT);
206  33 return false;
207    }
208   
209    // Forward back to the originating page
210  19 try {
211  19 context.getResponse().sendRedirect(back);
212    } catch (IOException ignored) {
213    // This exception is ignored because it will only be thrown if content has already been sent to the
214    // response. This should never happen but we have to catch the exception anyway.
215    }
216  19 return false;
217    }
218   
 
219  0 toggle @Override
220    public String render(XWikiContext context) throws XWikiException
221    {
222  0 XWikiAction wrappedAction = (XWikiAction) context.get(WRAPPED_ACTION_CONTEXT_KEY);
223   
224  0 if (wrappedAction != null) {
225  0 return wrappedAction.render(context);
226    }
227   
228  0 return "exception";
229    }
230   
231    /**
232    * Try to find the URL of the edit page which we came from.
233    *
234    * @param context current xwiki context
235    * @return URL of the edit page
236    */
 
237  52 toggle private String findBackURL(XWikiContext context)
238    {
239  52 XWikiRequest request = context.getRequest();
240  52 String back = request.getParameter("xcontinue");
241  52 if (StringUtils.isEmpty(back)) {
242  5 back = request.getParameter("xredirect");
243    }
244  52 if (StringUtils.isEmpty(back)) {
245  5 back = removeAllParametersFromQueryStringExceptEditor(request.getHeader("Referer"));
246    }
247  52 if (StringUtils.isEmpty(back)) {
248  0 back = context.getDoc().getURL("edit", context);
249    }
250  52 return back;
251    }
252   
253    /**
254    * @param url the URL to get a modified version of.
255    * @return A modified version of the input url where all parameters are stripped from the query string except
256    * "editor"
257    */
 
258  5 toggle private String removeAllParametersFromQueryStringExceptEditor(String url)
259    {
260  5 if (url == null) {
261  0 return "";
262    }
263   
264  5 String[] baseAndQuery = url.split("\\?");
265    // No query string: no change.
266  5 if (baseAndQuery.length < 2) {
267  0 return url;
268    }
269   
270  5 String[] queryBeforeAndAfterEditor = baseAndQuery[1].split("editor=");
271    // No editor=* in query string: return URI
272  5 if (queryBeforeAndAfterEditor.length < 2) {
273  1 return baseAndQuery[0];
274    }
275   
276  4 return baseAndQuery[0] + "?editor=" + queryBeforeAndAfterEditor[1].split("&")[0];
277    }
278    }