1/* 2 * Copyright 2008 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package org.mockftpserver.fake.command; 17 18import org.mockftpserver.core.command.Command; 19import org.mockftpserver.core.command.ReplyCodes; 20import org.mockftpserver.core.session.Session; 21import org.mockftpserver.fake.filesystem.FileEntry; 22import org.mockftpserver.fake.filesystem.FileSystemException; 23 24import java.io.IOException; 25import java.io.OutputStream; 26 27/** 28 * Abstract superclass for CommandHandlers that that store a file (STOR, STOU, APPE). Handler logic: 29 * <ol> 30 * <li>If the user has not logged in, then reply with 530 and terminate</li> 31 * <li>If the pathname parameter is required but missing, then reply with 501 and terminate</li> 32 * <li>If the required pathname parameter does not specify a valid filename, then reply with 553 and terminate</li> 33 * <li>If the current user does not have write access to the named file, if it already exists, or else to its 34 * parent directory, then reply with 553 and terminate</li> 35 * <li>If the current user does not have execute access to the parent directory, then reply with 553 and terminate</li> 36 * <li>Send an initial reply of 150</li> 37 * <li>Read all available bytes from the data connection and store/append to the named file in the server file system</li> 38 * <li>If file write/store fails, then reply with 553 and terminate</li> 39 * <li>Send a final reply with 226</li> 40 * </ol> 41 * 42 * @author Chris Mair 43 * @version $Revision$ - $Date$ 44 */ 45public abstract class AbstractStoreFileCommandHandler extends AbstractFakeCommandHandler { 46 47 protected void handle(Command command, Session session) { 48 verifyLoggedIn(session); 49 this.replyCodeForFileSystemException = ReplyCodes.WRITE_FILE_ERROR; 50 51 String filename = getOutputFile(command); 52 String path = getRealPath(session, filename); 53 verifyFileSystemCondition(!getFileSystem().isDirectory(path), path, "filesystem.isDirectory"); 54 String parentPath = getFileSystem().getParent(path); 55 verifyFileSystemCondition(getFileSystem().isDirectory(parentPath), parentPath, "filesystem.isNotADirectory"); 56 57 // User must have write permission to the file, if an existing file, or else to the directory if a new file 58 String pathMustBeWritable = getFileSystem().exists(path) ? path : parentPath; 59 verifyWritePermission(session, pathMustBeWritable); 60 61 // User must have execute permission to the parent directory 62 verifyExecutePermission(session, parentPath); 63 64 sendReply(session, ReplyCodes.TRANSFER_DATA_INITIAL_OK); 65 66 session.openDataConnection(); 67 byte[] contents = session.readData(); 68 session.closeDataConnection(); 69 70 FileEntry file = (FileEntry) getFileSystem().getEntry(path); 71 if (file == null) { 72 file = new FileEntry(path); 73 getFileSystem().add(file); 74 } 75 file.setPermissions(getUserAccount(session).getDefaultPermissionsForNewFile()); 76 77 if (contents != null && contents.length > 0) { 78 OutputStream out = file.createOutputStream(appendToOutputFile()); 79 try { 80 out.write(contents); 81 } 82 catch (IOException e) { 83 LOG.error("Error writing to file [" + file.getPath() + "]", e); 84 throw new FileSystemException(file.getPath(), null, e); 85 } 86 finally { 87 try { 88 out.close(); 89 } catch (IOException e) { 90 LOG.error("Error closing OutputStream for file [" + file.getPath() + "]", e); 91 } 92 } 93 } 94 sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK, getMessageKey(), list(filename)); 95 } 96 97 /** 98 * Return the path (absolute or relative) for the output file. The default behavior is to return 99 * the required first parameter for the specified Command. Subclasses may override the default behavior. 100 * 101 * @param command - the Command 102 * @return the output file name 103 */ 104 protected String getOutputFile(Command command) { 105 return command.getRequiredParameter(0); 106 } 107 108 /** 109 * @return true if this command should append the transferred contents to the output file; false means 110 * overwrite an existing file. This default implentation returns false. 111 */ 112 protected boolean appendToOutputFile() { 113 return false; 114 } 115 116 /** 117 * @return the message key for the reply message sent with the final (226) reply 118 */ 119 protected abstract String getMessageKey(); 120 121}