View Javadoc

1   /*
2    * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3    * Copyright (C) 2004-2009 Frederico Caldeira Knabben
4    * 
5    * == BEGIN LICENSE ==
6    * 
7    * Licensed under the terms of any of the following licenses at your
8    * choice:
9    * 
10   *  - GNU General Public License Version 2 or later (the "GPL")
11   *    http://www.gnu.org/licenses/gpl.html
12   * 
13   *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14   *    http://www.gnu.org/licenses/lgpl.html
15   * 
16   *  - Mozilla Public License Version 1.1 or later (the "MPL")
17   *    http://www.mozilla.org/MPL/MPL-1.1.html
18   * 
19   * == END LICENSE ==
20   */
21  package net.fckeditor.connector;
22  
23  import java.io.IOException;
24  import java.util.List;
25  
26  import javax.servlet.ServletContext;
27  import javax.servlet.http.HttpServletRequest;
28  
29  import net.fckeditor.connector.exception.FolderAlreadyExistsException;
30  import net.fckeditor.connector.exception.InvalidCurrentFolderException;
31  import net.fckeditor.connector.exception.InvalidNewFolderNameException;
32  import net.fckeditor.connector.exception.ReadException;
33  import net.fckeditor.connector.exception.WriteException;
34  import net.fckeditor.handlers.Command;
35  import net.fckeditor.handlers.ConnectorHandler;
36  import net.fckeditor.handlers.PropertiesLoader;
37  import net.fckeditor.handlers.RequestCycleHandler;
38  import net.fckeditor.handlers.ResourceType;
39  import net.fckeditor.requestcycle.Context;
40  import net.fckeditor.requestcycle.ThreadLocalData;
41  import net.fckeditor.response.GetResponse;
42  import net.fckeditor.response.UploadResponse;
43  import net.fckeditor.tool.Utils;
44  import net.fckeditor.tool.UtilsFile;
45  import net.fckeditor.tool.UtilsResponse;
46  
47  import org.apache.commons.fileupload.FileItem;
48  import org.apache.commons.fileupload.FileItemFactory;
49  import org.apache.commons.fileupload.FileUploadException;
50  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
51  import org.apache.commons.fileupload.servlet.ServletFileUpload;
52  import org.apache.commons.io.FilenameUtils;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  /**
57   * File Browser request dispatcher. This class is the validating and managing
58   * instance between the {@link ConnectorServlet connector servlet} and the
59   * {@link Connector connector}. It receives the requests, parses the parameters,
60   * validates/sanitizes them and mandates them to the connector. After the
61   * connector has processed the request, this dispatcher passes the response back
62   * to the connector servlet. More over, it intercepts all
63   * {@link net.fckeditor.connector.exception specified exceptions} from a
64   * connector and emits appropriate (localized) messages to the user. The
65   * exceptions won't be logged, they simply indicate the connector state.
66   * 
67   * @version $Id: Dispatcher.java 3764 2009-06-23 16:05:05Z mosipov $
68   */
69  public class Dispatcher {
70  	private final Logger logger = LoggerFactory.getLogger(Dispatcher.class);
71  	private Connector connector;
72  
73  	/**
74  	 * Initializes this dispatcher. It initializes the connector internally.
75  	 * Called at connector servlet {@link ConnectorServlet#init()
76  	 * initialization}.
77  	 * 
78  	 * @param servletContext
79  	 *            reference to the {@link ServletContext} in which the caller is
80  	 *            running
81  	 * @throws Exception
82  	 *             if the dispatcher initialization fails due to some reason
83  	 */
84  	Dispatcher(final ServletContext servletContext) throws Exception {
85  		connector = ConnectorHandler.getConnector();
86  		connector.init(servletContext);
87  	}
88  
89  	/**
90  	 * Called by the connector servlet to handle a {@code GET} request. In
91  	 * particular, it handles the {@link Command#GET_FOLDERS GetFolders},
92  	 * {@link Command#GET_FOLDERS_AND_FILES GetFoldersAndFiles} and
93  	 * {@link Command#CREATE_FOLDER CreateFolder} commands.
94  	 * 
95  	 * @param request
96  	 *            the current request instance
97  	 * @return the get response instance associated with this request
98  	 */
99  	GetResponse doGet(final HttpServletRequest request) {
100 		logger.debug("Entering Dispatcher#doGet");
101 		
102 		Context context = ThreadLocalData.getContext();
103 		context.logBaseParameters();
104 		
105 		GetResponse getResponse = null;
106 		// check parameters
107 		if (!Command.isValidForGet(context.getCommandStr()))
108 			getResponse = GetResponse.getInvalidCommandError();
109 		else if (!ResourceType.isValidType(context.getTypeStr()))
110 			getResponse = GetResponse.getInvalidResourceTypeError();
111 		else if (!UtilsFile.isValidPath(context.getCurrentFolderStr()))
112 			getResponse = GetResponse.getInvalidCurrentFolderError();
113 		else {
114 			
115 			// in contrast to doPost the referrer has to send an explicit type
116 			ResourceType type = context.getResourceType();
117 			Command command = context.getCommand();
118 			
119 			// check permissions for user action
120 			if ((command.equals(Command.GET_FOLDERS) || command
121 					.equals(Command.GET_FOLDERS_AND_FILES))
122 					&& !RequestCycleHandler.isEnabledForFileBrowsing(request))
123 				getResponse = GetResponse.getGetResourcesDisabledError();
124 			else if (command.equals(Command.CREATE_FOLDER)
125 					&& !RequestCycleHandler.isCreateFolderEnabled(request))
126 				getResponse = GetResponse.getCreateFolderDisabledError();
127 			else {
128 				// make the connector calls, catch its exceptions and generate
129 				// the proper response object
130 				try {
131 					if (command.equals(Command.CREATE_FOLDER)) {
132 						String newFolderNameStr = request
133 								.getParameter("NewFolderName");
134 						logger.debug("Parameter NewFolderName: {}",
135 								newFolderNameStr);				
136 						String sanitizedNewFolderNameStr = UtilsFile
137 								.sanitizeFolderName(newFolderNameStr);
138 						if (Utils.isEmpty(sanitizedNewFolderNameStr))
139 							getResponse = GetResponse
140 									.getInvalidNewFolderNameError();
141 						else {
142 							logger.debug(
143 									"Parameter NewFolderName (sanitized): {}",
144 									sanitizedNewFolderNameStr);
145 							connector.createFolder(type, context
146 									.getCurrentFolderStr(),
147 									sanitizedNewFolderNameStr);
148 							getResponse = GetResponse.getOK();
149 						}
150 					} else if (command.equals(Command.GET_FOLDERS)
151 							|| command
152 									.equals(Command.GET_FOLDERS_AND_FILES)) {
153 						String url = UtilsResponse.getUrl(RequestCycleHandler
154 								.getUserFilesPath(request), type, context
155 								.getCurrentFolderStr());
156 						getResponse = getFoldersAndOrFiles(command, type, context
157 								.getCurrentFolderStr(), url);
158 					}
159 				} catch (InvalidCurrentFolderException e) {
160 					getResponse = GetResponse.getInvalidCurrentFolderError();
161 				} catch (InvalidNewFolderNameException e) {
162 					getResponse = GetResponse.getInvalidNewFolderNameError();
163 				} catch (FolderAlreadyExistsException e) {
164 					getResponse = GetResponse.getFolderAlreadyExistsError();
165 				} catch (WriteException e) {
166 					getResponse = GetResponse.getCreateFolderWriteError();
167 				} catch (ReadException e) {
168 					getResponse = GetResponse.getGetResourcesReadError();
169 				}
170 			}
171 		}
172 		
173 		logger.debug("Exiting Dispatcher#doGet");
174 		return getResponse;
175 	}
176 	
177 	/**
178 	 * Returns get response for the {@code GetFolders*} commands. This is simply
179 	 * a helper method.
180 	 * 
181 	 * @param command
182 	 *            the current command, should be only GetFolders or
183 	 *            GetFoldersAndFiles
184 	 * @param the
185 	 *            current resource type
186 	 * @param currentFolder
187 	 *            the current folder
188 	 * @param constructedUrl
189 	 *            the final URL
190 	 * @return the get response instance associated with this request
191 	 * @throws InvalidCurrentFolderException
192 	 *             if the current folder name is invalid or does not exist
193 	 *             within the underlying backend
194 	 * @throws ReadException
195 	 *             if the file attributes and/or folder names could not be read
196 	 *             due to some reason
197 	 */
198 	private GetResponse getFoldersAndOrFiles(final Command command,
199 			final ResourceType type, final String currentFolder,
200 			final String constructedUrl) throws InvalidCurrentFolderException,
201 			ReadException {
202 		GetResponse getResponse = new GetResponse(command, type,
203 				currentFolder, constructedUrl);
204 		getResponse.setFolders(connector.getFolders(type, currentFolder));
205 		if (command.equals(Command.GET_FOLDERS_AND_FILES))
206 			getResponse.setFiles(connector.getFiles(type, currentFolder));
207 		return getResponse;
208 	}
209 
210 	/**
211 	 * Called by the connector servlet to handle a {@code POST} request. In
212 	 * particular, it handles the {@link Command#FILE_UPLOAD FileUpload} and
213 	 * {@link Command#QUICK_UPLOAD QuickUpload} commands.
214 	 * 
215 	 * @param request
216 	 *            the current request instance
217 	 * @return the upload response instance associated with this request
218 	 */
219 	UploadResponse doPost(final HttpServletRequest request) {
220 		logger.debug("Entering Dispatcher#doPost");
221 		
222 		Context context = ThreadLocalData.getContext();
223 		context.logBaseParameters();
224 		
225 		UploadResponse uploadResponse = null;
226 		// check permissions for user actions
227 		if (!RequestCycleHandler.isEnabledForFileUpload(request))
228 			uploadResponse = UploadResponse.getFileUploadDisabledError();
229 		// check parameters  
230 		else if (!Command.isValidForPost(context.getCommandStr()))
231 			uploadResponse = UploadResponse.getInvalidCommandError();
232 		else if (!ResourceType.isValidType(context.getTypeStr()))
233 			uploadResponse = UploadResponse.getInvalidResourceTypeError();
234 		else if (!UtilsFile.isValidPath(context.getCurrentFolderStr()))
235 			uploadResponse = UploadResponse.getInvalidCurrentFolderError();
236 		else {
237 
238 			// call the Connector#fileUpload
239 			ResourceType type = context.getDefaultResourceType();
240 			FileItemFactory factory = new DiskFileItemFactory();
241 			ServletFileUpload upload = new ServletFileUpload(factory);
242 			try {
243 				List<FileItem> items = upload.parseRequest(request);
244 				// We upload just one file at the same time
245 				FileItem uplFile = items.get(0);
246 				// Some browsers transfer the entire source path not just the
247 				// filename
248 				String fileName = FilenameUtils.getName(uplFile.getName());
249 				logger.debug("Parameter NewFile: {}", fileName);
250 				// check the extension
251 				if (type.isNotAllowedExtension(FilenameUtils
252 						.getExtension(fileName)))
253 					uploadResponse = UploadResponse.getInvalidFileTypeError();
254 				// Secure image check (can't be done if QuickUpload)
255 				else if (type.equals(ResourceType.IMAGE)
256 						&& PropertiesLoader.isSecureImageUploads()
257 						&& !UtilsFile.isImage(uplFile.getInputStream())) {
258 					uploadResponse = UploadResponse.getInvalidFileTypeError();
259 				} else {
260 					String sanitizedFileName = UtilsFile
261 							.sanitizeFileName(fileName);
262 					logger.debug("Parameter NewFile (sanitized): {}",
263 							sanitizedFileName);
264 					String newFileName = connector.fileUpload(type, context
265 							.getCurrentFolderStr(), sanitizedFileName, uplFile
266 							.getInputStream());
267 					String fileUrl = UtilsResponse.fileUrl(RequestCycleHandler
268 							.getUserFilesPath(request), type, context
269 							.getCurrentFolderStr(), newFileName);
270 
271 					if (sanitizedFileName.equals(newFileName))
272 						uploadResponse = UploadResponse.getOK(fileUrl);
273 					else {
274 						uploadResponse = UploadResponse.getFileRenamedWarning(fileUrl, newFileName);
275 						logger.debug("Parameter NewFile (renamed): {}",
276 								newFileName);
277 					}
278 				}
279 				
280 				uplFile.delete();
281 			} catch (InvalidCurrentFolderException e) {
282 				uploadResponse = UploadResponse.getInvalidCurrentFolderError();
283 			} catch (WriteException e) {
284 				uploadResponse = UploadResponse.getFileUploadWriteError();
285 			} catch (IOException e) {
286 				uploadResponse = UploadResponse.getFileUploadWriteError();
287 			} catch (FileUploadException e) {
288 				uploadResponse = UploadResponse.getFileUploadWriteError();
289 			}
290 		}
291 		
292 		logger.debug("Exiting Dispatcher#doPost");
293 		return uploadResponse;
294 	}
295 	
296 }