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