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>Send an initial reply of 150</li> 34 * <li>Read all available bytes from the data connection and store/append to the named file in the server file system</li> 35 * <li>If file write/store fails, then reply with 553 and terminate</li> 36 * <li>Send a final reply with 226</li> 37 * </ol> 38 * 39 * @author Chris Mair 40 * @version $Revision: 78 $ - $Date: 2008-07-02 20:47:17 -0400 (Wed, 02 Jul 2008) $ 41 */ 42public abstract class AbstractStoreFileCommandHandler extends AbstractFakeCommandHandler { 43 44 protected void handle(Command command, Session session) { 45 verifyLoggedIn(session); 46 this.replyCodeForFileSystemException = ReplyCodes.WRITE_FILE_ERROR; 47 48 String filename = getOutputFile(command); 49 String path = getRealPath(session, filename); 50 verifyFileSystemCondition(!getFileSystem().isDirectory(path), path, "filesystem.isDirectory"); 51 String parentPath = getFileSystem().getParent(path); 52 verifyFileSystemCondition(getFileSystem().isDirectory(parentPath), parentPath, "filesystem.isNotADirectory"); 53 54 // User must have write permission to the file, if an existing file, or else to the directory if a new file 55 String pathMustBeWritable = getFileSystem().exists(path) ? path : parentPath; 56 verifyWritePermission(session, pathMustBeWritable); 57 58 // User must have execute permission to the parent directory 59 verifyExecutePermission(session, parentPath); 60 61 sendReply(session, ReplyCodes.TRANSFER_DATA_INITIAL_OK); 62 63 session.openDataConnection(); 64 byte[] contents = session.readData(); 65 session.closeDataConnection(); 66 67 FileEntry file = (FileEntry) getFileSystem().getEntry(path); 68 if (file == null) { 69 file = new FileEntry(path); 70 getFileSystem().add(file); 71 } 72 file.setPermissions(getUserAccount(session).getDefaultPermissionsForNewFile()); 73 74 if (contents != null && contents.length > 0) { 75 OutputStream out = file.createOutputStream(appendToOutputFile()); 76 try { 77 out.write(contents); 78 } 79 catch (IOException e) { 80 LOG.error("Error writing to file [" + file.getPath() + "]", e); 81 throw new FileSystemException(file.getPath(), null, e); 82 } 83 finally { 84 try { 85 out.close(); 86 } catch (IOException e) { 87 LOG.error("Error closing OutputStream for file [" + file.getPath() + "]", e); 88 } 89 } 90 } 91 sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK, getMessageKey(), list(filename)); 92 } 93 94 /** 95 * Return the path (absolute or relative) for the output file. The default behavior is to return 96 * the required first parameter for the specified Command. Subclasses may override the default behavior. 97 */ 98 protected String getOutputFile(Command command) { 99 return command.getRequiredParameter(0); 100 } 101 102 /** 103 * @return true if this command should append the transferred contents to the output file; false means 104 * overwrite an existing file. This default implentation returns false. 105 */ 106 protected boolean appendToOutputFile() { 107 return false; 108 } 109 110 /** 111 * @return the message key for the reply message sent with the final (226) reply 112 */ 113 protected abstract String getMessageKey(); 114 115}