View Javadoc

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