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}