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