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

File UploadAction.java

 

Coverage histogram

../../../../img/srcFileCovDistChart6.png
72% of files have more coverage

Code metrics

46
112
4
1
304
202
36
0.32
28
4
9

Classes

Class Line # Actions
UploadAction 53 112 0% 36 72
0.555555655.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    import java.io.InputStream;
24    import java.util.ArrayList;
25    import java.util.LinkedHashMap;
26    import java.util.List;
27    import java.util.Locale;
28    import java.util.Map;
29    import java.util.Map.Entry;
30   
31    import javax.script.ScriptContext;
32    import javax.servlet.http.HttpServletResponse;
33   
34    import org.apache.commons.lang3.StringUtils;
35    import org.apache.commons.lang3.exception.ExceptionUtils;
36    import org.slf4j.Logger;
37    import org.slf4j.LoggerFactory;
38    import org.xwiki.localization.LocaleUtils;
39    import org.xwiki.model.reference.DocumentReference;
40   
41    import com.xpn.xwiki.XWikiContext;
42    import com.xpn.xwiki.XWikiException;
43    import com.xpn.xwiki.doc.XWikiAttachment;
44    import com.xpn.xwiki.doc.XWikiDocument;
45    import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin;
46   
47    /**
48    * Action that handles uploading document attachments. It saves all the uploaded files whose fieldname start with
49    * {@code filepath}.
50    *
51    * @version $Id: bb64a474518aa547423c400f4f2ede93122dd1b9 $
52    */
 
53    public class UploadAction extends XWikiAction
54    {
55    /** Logging helper object. */
56    private static final Logger LOGGER = LoggerFactory.getLogger(UploadAction.class);
57   
58    /** The prefix of the accepted file input field name. */
59    private static final String FILE_FIELD_NAME = "filepath";
60   
61    /** The prefix of the corresponding filename input field name. */
62    private static final String FILENAME_FIELD_NAME = "filename";
63   
 
64  19 toggle @Override
65    public boolean action(XWikiContext context) throws XWikiException
66    {
67  19 XWikiResponse response = context.getResponse();
68  19 Object exception = context.get("exception");
69  19 boolean ajax = ((Boolean) context.get("ajax")).booleanValue();
70    // check Exception File upload is large
71  19 if (exception != null) {
72  0 if (exception instanceof XWikiException) {
73  0 XWikiException exp = (XWikiException) exception;
74  0 if (exp.getCode() == XWikiException.ERROR_XWIKI_APP_FILE_EXCEPTION_MAXSIZE) {
75  0 response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
76  0 getCurrentScriptContext().setAttribute("message", "core.action.upload.failure.maxSize",
77    ScriptContext.ENGINE_SCOPE);
78  0 context.put("message", "fileuploadislarge");
79   
80  0 return true;
81    }
82    }
83    }
84   
85    // CSRF prevention
86  19 if (!csrfTokenCheck(context)) {
87  0 return false;
88    }
89   
90    // We need to clone the document before we modify it because the cached storage gives the same instance to other
91    // requests (until the cache is invalidated).
92  19 XWikiDocument doc = context.getDoc().clone();
93   
94    // It is possible to submit an attachment to a new document (the WYSIWYG content editor does it for instance).
95    // Let's make sure the new document is created with the right (default) language.
96  19 if (doc.isNew()) {
97  0 doc.setLocale(Locale.ROOT);
98  0 if (doc.getDefaultLocale() == Locale.ROOT) {
99  0 doc.setDefaultLocale(
100    LocaleUtils.toLocale(context.getWiki().getLanguagePreference(context), Locale.ROOT));
101    }
102    }
103   
104    // The document is saved for each attachment in the group.
105  19 FileUploadPlugin fileupload = (FileUploadPlugin) context.get("fileuploadplugin");
106  19 if (fileupload == null) {
107  0 getCurrentScriptContext().setAttribute("message", "core.action.upload.failure.noFiles",
108    ScriptContext.ENGINE_SCOPE);
109   
110  0 return true;
111    }
112  19 Map<String, String> fileNames = new LinkedHashMap<String, String>();
113  19 List<String> wrongFileNames = new ArrayList<String>();
114  19 Map<String, String> failedFiles = new LinkedHashMap<String, String>();
115  19 for (String fieldName : fileupload.getFileItemNames(context)) {
116  55 try {
117  55 if (fieldName.startsWith(FILE_FIELD_NAME)) {
118  19 String fileName = getFileName(fieldName, fileupload, context);
119  19 if (fileName != null) {
120  19 fileNames.put(fileName, fieldName);
121    }
122    }
123    } catch (Exception ex) {
124  0 wrongFileNames.add(fileupload.getFileName(fieldName, context));
125    }
126    }
127   
128  19 for (Entry<String, String> file : fileNames.entrySet()) {
129  19 try {
130  19 uploadAttachment(file.getValue(), file.getKey(), fileupload, doc, context);
131    } catch (Exception ex) {
132  0 LOGGER.warn("Saving uploaded file failed", ex);
133  0 failedFiles.put(file.getKey(), ExceptionUtils.getRootCauseMessage(ex));
134    }
135    }
136   
137  19 LOGGER.debug("Found files to upload: " + fileNames);
138  19 LOGGER.debug("Failed attachments: " + failedFiles);
139  19 LOGGER.debug("Wrong attachment names: " + wrongFileNames);
140  19 if (ajax) {
141  0 try {
142  0 response.getOutputStream().println("ok");
143    } catch (IOException ex) {
144  0 LOGGER.error("Unhandled exception writing output:", ex);
145    }
146  0 return false;
147    }
148    // Forward to the attachment page
149  19 if (failedFiles.size() > 0 || !wrongFileNames.isEmpty()) {
150  0 getCurrentScriptContext().setAttribute("message", "core.action.upload.failure", ScriptContext.ENGINE_SCOPE);
151  0 getCurrentScriptContext().setAttribute("failedFiles", failedFiles, ScriptContext.ENGINE_SCOPE);
152  0 getCurrentScriptContext().setAttribute("wrongFileNames", wrongFileNames, ScriptContext.ENGINE_SCOPE);
153   
154  0 return true;
155    }
156  19 String redirect = fileupload.getFileItemAsString("xredirect", context);
157  19 if (StringUtils.isEmpty(redirect)) {
158  5 redirect = context.getDoc().getURL("attach", true, context);
159    }
160  19 sendRedirect(response, redirect);
161  19 return false;
162    }
163   
164    /**
165    * Attach a file to the current document.
166    *
167    * @param fieldName the target file field
168    * @param filename
169    * @param fileupload the {@link FileUploadPlugin} holding the form data
170    * @param doc the target document
171    * @param context the current request context
172    * @return {@code true} if the file was successfully attached, {@code false} otherwise.
173    * @throws XWikiException if the form data cannot be accessed, or if the database operation failed
174    */
 
175  19 toggle public boolean uploadAttachment(String fieldName, String filename, FileUploadPlugin fileupload, XWikiDocument doc,
176    XWikiContext context) throws XWikiException
177    {
178  19 XWikiResponse response = context.getResponse();
179  19 DocumentReference usernameReference = context.getUserReference();
180   
181  19 XWikiAttachment attachment;
182  19 try {
183  19 InputStream contentInputStream = fileupload.getFileItemInputStream(fieldName, context);
184  19 attachment = doc.setAttachment(filename, contentInputStream, context);
185    } catch (IOException e) {
186  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP,
187    XWikiException.ERROR_XWIKI_APP_UPLOAD_FILE_EXCEPTION, "Exception while reading uploaded parsed file",
188    e);
189    }
190   
191    // Set the document author
192  19 doc.setAuthorReference(usernameReference);
193  19 if (doc.isNew()) {
194  0 doc.setCreatorReference(usernameReference);
195    }
196   
197    // Calculate and store mime type
198  19 attachment.resetMimeType(context);
199   
200    // Remember character encoding
201  19 attachment.setCharset(context.getRequest().getCharacterEncoding());
202   
203    // Add a comment to the attachment history.
204  19 String attachmentComment = StringUtils.defaultString(context.getRequest().getParameter("comment"));
205  19 attachment.setComment(attachmentComment);
206   
207    // Add a comment to the document history. Include the attachment name, revision and comment.
208  19 String documentComment;
209  19 ArrayList<String> params = new ArrayList<>();
210  19 params.add(filename);
211  19 String nextRev = attachment.getNextVersion();
212  19 if (StringUtils.isBlank(attachmentComment)) {
213  19 params.add(nextRev);
214    } else {
215  0 params.add(String.format("%s (%s)", nextRev, attachmentComment));
216    }
217  19 if (attachment.isImage(context)) {
218  3 documentComment = localizePlainOrKey("core.comment.uploadImageComment", params.toArray());
219    } else {
220  16 documentComment = localizePlainOrKey("core.comment.uploadAttachmentComment", params.toArray());
221    }
222   
223    // Save the document.
224  19 try {
225  19 context.getWiki().saveDocument(doc, documentComment, context);
226    } catch (XWikiException e) {
227    // check Exception is ERROR_XWIKI_APP_JAVA_HEAP_SPACE when saving Attachment
228  0 if (e.getCode() == XWikiException.ERROR_XWIKI_APP_JAVA_HEAP_SPACE) {
229  0 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
230  0 context.put("message", "javaheapspace");
231  0 return true;
232    }
233  0 throw e;
234    }
235  19 return false;
236    }
237   
238    /**
239    * Extract the corresponding attachment name for a given file field. It can either be specified in a separate form
240    * input field, or it is extracted from the original filename.
241    *
242    * @param fieldName the target file field
243    * @param fileupload the {@link FileUploadPlugin} holding the form data
244    * @param context the current request context
245    * @return a valid attachment name
246    * @throws XWikiException if the form data cannot be accessed, or if the specified filename is invalid
247    */
 
248  19 toggle protected String getFileName(String fieldName, FileUploadPlugin fileupload, XWikiContext context)
249    throws XWikiException
250    {
251  19 String filenameField = FILENAME_FIELD_NAME + fieldName.substring(FILE_FIELD_NAME.length());
252  19 String filename = null;
253   
254    // Try to use the name provided by the user
255  19 filename = fileupload.getFileItemAsString(filenameField, context);
256  19 if (!StringUtils.isBlank(filename)) {
257    // TODO These should be supported, the URL should just contain escapes.
258  0 if (filename.indexOf("/") != -1 || filename.indexOf("\\") != -1 || filename.indexOf(";") != -1) {
259  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_INVALID_CHARS,
260    "Invalid filename: " + filename);
261    }
262    }
263   
264  19 if (StringUtils.isBlank(filename)) {
265    // Try to get the actual filename on the client
266  19 String fname = fileupload.getFileName(fieldName, context);
267  19 if (StringUtils.indexOf(fname, "/") >= 0) {
268  0 fname = StringUtils.substringAfterLast(fname, "/");
269    }
270  19 if (StringUtils.indexOf(fname, "\\") >= 0) {
271  0 fname = StringUtils.substringAfterLast(fname, "\\");
272    }
273  19 filename = fname;
274    }
275    // Sometimes spaces are replaced with '+' by the browser.
276  19 filename = filename.replaceAll("\\+", " ");
277   
278  19 if (StringUtils.isBlank(filename)) {
279    // The file field was left empty, ignore this
280  0 return null;
281    }
282   
283  19 return filename;
284    }
285   
 
286  0 toggle @Override
287    public String render(XWikiContext context) throws XWikiException
288    {
289  0 boolean ajax = ((Boolean) context.get("ajax")).booleanValue();
290  0 if (ajax) {
291  0 try {
292  0 context.getResponse().getOutputStream()
293    .println("error: " + localizePlainOrKey((String) context.get("message")));
294    } catch (IOException ex) {
295  0 LOGGER.error("Unhandled exception writing output:", ex);
296    }
297  0 return null;
298    }
299   
300  0 getCurrentScriptContext().setAttribute("viewer", "uploadfailure", ScriptContext.ENGINE_SCOPE);
301   
302  0 return "view";
303    }
304    }