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

File UploadAction.java

 

Coverage histogram

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

Code metrics

44
107
4
1
292
194
35
0.33
26.75
4
8.75

Classes

Class Line # Actions
UploadAction 52 107 0% 35 72
0.535483953.5%
 

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