http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
deleted file mode 100644
index 08213ee..0000000
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
+++ /dev/null
@@ -1,2580 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.sshd.server.subsystem.sftp;
-
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.StreamCorruptedException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.CopyOption;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.NotDirectoryException;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.security.Principal;
-import java.util.AbstractMap.SimpleImmutableEntry;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.function.IntUnaryOperator;
-
-import org.apache.sshd.common.FactoryManager;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.OptionalFeature;
-import org.apache.sshd.common.PropertyResolver;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.config.VersionProperties;
-import org.apache.sshd.common.digest.BuiltinDigests;
-import org.apache.sshd.common.digest.Digest;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.SftpException;
-import org.apache.sshd.common.subsystem.sftp.SftpHelper;
-import org.apache.sshd.common.subsystem.sftp.extensions.AclSupportedParser;
-import 
org.apache.sshd.common.subsystem.sftp.extensions.SpaceAvailableExtensionInfo;
-import 
org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
-import 
org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
-import 
org.apache.sshd.common.subsystem.sftp.extensions.openssh.HardLinkExtensionParser;
-import org.apache.sshd.common.util.EventListenerUtils;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.NumberUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.SelectorUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.io.FileInfoExtractor;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-import org.apache.sshd.server.session.ServerSession;
-
-/**
- * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
- */
-public abstract class AbstractSftpSubsystemHelper
-            extends AbstractLoggingBean
-            implements SftpEventListenerManager, SftpSubsystemEnvironment {
-    /**
-     * Whether to automatically follow symbolic links when resolving paths
-     * @see #DEFAULT_AUTO_FOLLOW_LINKS
-     */
-    public static final String AUTO_FOLLOW_LINKS = "sftp-auto-follow-links";
-
-    /**
-     * Default value of {@value #AUTO_FOLLOW_LINKS}
-     */
-    public static final boolean DEFAULT_AUTO_FOLLOW_LINKS = true;
-
-    /**
-     * Allows controlling reports of which client extensions are supported
-     * (and reported via &quot;support&quot; and &quot;support2&quot; server
-     * extensions) as a comma-separate list of names. <B>Note:</B> requires
-     * overriding the {@link #executeExtendedCommand(Buffer, int, String)}
-     * command accordingly. If empty string is set then no server extensions
-     * are reported
-     *
-     * @see #DEFAULT_SUPPORTED_CLIENT_EXTENSIONS
-     */
-    public static final String CLIENT_EXTENSIONS_PROP = 
"sftp-client-extensions";
-
-    /**
-     * The default reported supported client extensions
-     */
-    public static final Map<String, OptionalFeature> 
DEFAULT_SUPPORTED_CLIENT_EXTENSIONS =
-            // TODO text-seek - see 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-13.txt
-            // TODO home-directory - see 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt
-            GenericUtils.<String, OptionalFeature>mapBuilder()
-                .put(SftpConstants.EXT_VERSION_SELECT, OptionalFeature.TRUE)
-                .put(SftpConstants.EXT_COPY_FILE, OptionalFeature.TRUE)
-                .put(SftpConstants.EXT_MD5_HASH, BuiltinDigests.md5)
-                .put(SftpConstants.EXT_MD5_HASH_HANDLE, BuiltinDigests.md5)
-                .put(SftpConstants.EXT_CHECK_FILE_HANDLE, 
OptionalFeature.any(BuiltinDigests.VALUES))
-                .put(SftpConstants.EXT_CHECK_FILE_NAME, 
OptionalFeature.any(BuiltinDigests.VALUES))
-                .put(SftpConstants.EXT_COPY_DATA, OptionalFeature.TRUE)
-                .put(SftpConstants.EXT_SPACE_AVAILABLE, OptionalFeature.TRUE)
-                .immutable();
-
-    /**
-     * Comma-separated list of which {@code OpenSSH} extensions are reported 
and
-     * what version is reported for each - format: {@code name=version}. If 
empty
-     * value set, then no such extensions are reported. Otherwise, the
-     * {@link #DEFAULT_OPEN_SSH_EXTENSIONS} are used
-     */
-    public static final String OPENSSH_EXTENSIONS_PROP = 
"sftp-openssh-extensions";
-    public static final List<OpenSSHExtension> DEFAULT_OPEN_SSH_EXTENSIONS =
-            Collections.unmodifiableList(
-                    Arrays.asList(
-                            new OpenSSHExtension(FsyncExtensionParser.NAME, 
"1"),
-                            new OpenSSHExtension(HardLinkExtensionParser.NAME, 
"1")
-                    ));
-
-    public static final List<String> DEFAULT_OPEN_SSH_EXTENSIONS_NAMES =
-            
Collections.unmodifiableList(NamedResource.getNameList(DEFAULT_OPEN_SSH_EXTENSIONS));
-
-    /**
-     * Comma separate list of {@code SSH_ACL_CAP_xxx} names - where name can 
be without
-     * the prefix. If not defined then {@link #DEFAULT_ACL_SUPPORTED_MASK} is 
used
-     */
-    public static final String ACL_SUPPORTED_MASK_PROP = 
"sftp-acl-supported-mask";
-    public static final Set<Integer> DEFAULT_ACL_SUPPORTED_MASK =
-            Collections.unmodifiableSet(
-                    new HashSet<>(Arrays.asList(
-                            SftpConstants.SSH_ACL_CAP_ALLOW,
-                            SftpConstants.SSH_ACL_CAP_DENY,
-                            SftpConstants.SSH_ACL_CAP_AUDIT,
-                            SftpConstants.SSH_ACL_CAP_ALARM)));
-
-    /**
-     * Property that can be used to set the reported NL value.
-     * If not set, then {@link IoUtils#EOL} is used
-     */
-    public static final String NEWLINE_VALUE = "sftp-newline";
-
-    /**
-     * Force the use of a max. packet length for {@link #doRead(Buffer, int)} 
protection
-     * against malicious packets
-     *
-     * @see #DEFAULT_MAX_READDATA_PACKET_LENGTH
-     */
-    public static final String MAX_READDATA_PACKET_LENGTH_PROP = 
"sftp-max-readdata-packet-length";
-    public static final int DEFAULT_MAX_READDATA_PACKET_LENGTH = 63 * 1024;
-
-    private final UnsupportedAttributePolicy unsupportedAttributePolicy;
-    private final Collection<SftpEventListener> sftpEventListeners = new 
CopyOnWriteArraySet<>();
-    private final SftpEventListener sftpEventListenerProxy;
-    private final SftpFileSystemAccessor fileSystemAccessor;
-    private final SftpErrorStatusDataHandler errorStatusDataHandler;
-
-    protected AbstractSftpSubsystemHelper(
-            UnsupportedAttributePolicy policy, SftpFileSystemAccessor 
accessor, SftpErrorStatusDataHandler handler) {
-        unsupportedAttributePolicy = Objects.requireNonNull(policy, "No 
unsupported attribute policy provided");
-        fileSystemAccessor = Objects.requireNonNull(accessor, "No file system 
accessor");
-        sftpEventListenerProxy = 
EventListenerUtils.proxyWrapper(SftpEventListener.class, 
getClass().getClassLoader(), sftpEventListeners);
-        errorStatusDataHandler = Objects.requireNonNull(handler, "No error 
status data handler");
-    }
-
-    @Override
-    public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
-        return unsupportedAttributePolicy;
-    }
-
-    @Override
-    public SftpFileSystemAccessor getFileSystemAccessor() {
-        return fileSystemAccessor;
-    }
-
-    @Override
-    public SftpEventListener getSftpEventListenerProxy() {
-        return sftpEventListenerProxy;
-    }
-
-    @Override
-    public boolean addSftpEventListener(SftpEventListener listener) {
-        return 
sftpEventListeners.add(SftpEventListener.validateListener(listener));
-    }
-
-    @Override
-    public boolean removeSftpEventListener(SftpEventListener listener) {
-        if (listener == null) {
-            return false;
-        }
-
-        return 
sftpEventListeners.remove(SftpEventListener.validateListener(listener));
-    }
-
-    public SftpErrorStatusDataHandler getErrorStatusDataHandler() {
-        return errorStatusDataHandler;
-    }
-
-    protected abstract void process(Buffer buffer) throws IOException;
-
-    /**
-     * @param buffer   The {@link Buffer} holding the request
-     * @param id       The request id
-     * @param proposed The proposed value
-     * @return A {@link Boolean} indicating whether to accept/reject the 
proposal.
-     * If {@code null} then rejection response has been sent, otherwise and
-     * appropriate response is generated
-     * @throws IOException If failed send an independent rejection response
-     */
-    protected Boolean validateProposedVersion(Buffer buffer, int id, String 
proposed) throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("validateProposedVersion({})[id={}] 
SSH_FXP_EXTENDED(version-select) (version={})",
-                      getServerSession(), id, proposed);
-        }
-
-        if (GenericUtils.length(proposed) != 1) {
-            return Boolean.FALSE;
-        }
-
-        char digit = proposed.charAt(0);
-        if ((digit < '0') || (digit > '9')) {
-            return Boolean.FALSE;
-        }
-
-        int value = digit - '0';
-        String all = checkVersionCompatibility(buffer, id, value, 
SftpConstants.SSH_FX_FAILURE);
-        if (GenericUtils.isEmpty(all)) {    // validation failed
-            return null;
-        } else {
-            return Boolean.TRUE;
-        }
-    }
-
-    /**
-     * Checks if a proposed version is within supported range. <B>Note:</B>
-     * if the user forced a specific value via the {@link 
SftpSubsystemEnvironment#SFTP_VERSION}
-     * property, then it is used to validate the proposed value
-     *
-     * @param buffer        The {@link Buffer} containing the request
-     * @param id            The SSH message ID to be used to send the failure 
message
-     *                      if required
-     * @param proposed      The proposed version value
-     * @param failureOpcode The failure opcode to send if validation fails
-     * @return A {@link String} of comma separated values representing all
-     * the supported version - {@code null} if validation failed and an
-     * appropriate status message was sent
-     * @throws IOException If failed to send the failure status message
-     */
-    protected String checkVersionCompatibility(Buffer buffer, int id, int 
proposed, int failureOpcode) throws IOException {
-        int low = SftpSubsystemEnvironment.LOWER_SFTP_IMPL;
-        int hig = SftpSubsystemEnvironment.HIGHER_SFTP_IMPL;
-        String available = SftpSubsystemEnvironment.ALL_SFTP_IMPL;
-        // check if user wants to use a specific version
-        ServerSession session = getServerSession();
-        Integer sftpVersion = 
session.getInteger(SftpSubsystemEnvironment.SFTP_VERSION);
-        if (sftpVersion != null) {
-            int forcedValue = sftpVersion;
-            if ((forcedValue < SftpSubsystemEnvironment.LOWER_SFTP_IMPL) || 
(forcedValue > SftpSubsystemEnvironment.HIGHER_SFTP_IMPL)) {
-                throw new IllegalStateException("Forced SFTP version (" + 
sftpVersion + ") not within supported values: " + available);
-            }
-            hig = sftpVersion;
-            low = hig;
-            available = sftpVersion.toString();
-        }
-
-        if (log.isTraceEnabled()) {
-            log.trace("checkVersionCompatibility({})[id={}] - proposed={}, 
available={}",
-                      getServerSession(), id, proposed, available);
-        }
-
-        if ((proposed < low) || (proposed > hig)) {
-            sendStatus(BufferUtils.clear(buffer), id, failureOpcode, "Proposed 
version (" + proposed + ") not in supported range: " + available);
-            return null;
-        }
-
-        return available;
-    }
-
-    protected void doOpen(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        /*
-         * Be consistent with FileChannel#open - if no mode specified then 
READ is assumed
-         */
-        int access = 0;
-        int version = getVersion();
-        if (version >= SftpConstants.SFTP_V5) {
-            access = buffer.getInt();
-            if (access == 0) {
-                access = SftpConstants.ACE4_READ_DATA | 
SftpConstants.ACE4_READ_ATTRIBUTES;
-            }
-        }
-
-        int pflags = buffer.getInt();
-        if (pflags == 0) {
-            pflags = SftpConstants.SSH_FXF_READ;
-        }
-
-        if (version < SftpConstants.SFTP_V5) {
-            int flags = pflags;
-            pflags = 0;
-            switch (flags & (SftpConstants.SSH_FXF_READ | 
SftpConstants.SSH_FXF_WRITE)) {
-                case SftpConstants.SSH_FXF_READ:
-                    access |= SftpConstants.ACE4_READ_DATA | 
SftpConstants.ACE4_READ_ATTRIBUTES;
-                    break;
-                case SftpConstants.SSH_FXF_WRITE:
-                    access |= SftpConstants.ACE4_WRITE_DATA | 
SftpConstants.ACE4_WRITE_ATTRIBUTES;
-                    break;
-                default:
-                    access |= SftpConstants.ACE4_READ_DATA | 
SftpConstants.ACE4_READ_ATTRIBUTES;
-                    access |= SftpConstants.ACE4_WRITE_DATA | 
SftpConstants.ACE4_WRITE_ATTRIBUTES;
-                    break;
-            }
-            if ((flags & SftpConstants.SSH_FXF_APPEND) != 0) {
-                access |= SftpConstants.ACE4_APPEND_DATA;
-                pflags |= SftpConstants.SSH_FXF_APPEND_DATA | 
SftpConstants.SSH_FXF_APPEND_DATA_ATOMIC;
-            }
-            if ((flags & SftpConstants.SSH_FXF_CREAT) != 0) {
-                if ((flags & SftpConstants.SSH_FXF_EXCL) != 0) {
-                    pflags |= SftpConstants.SSH_FXF_CREATE_NEW;
-                } else if ((flags & SftpConstants.SSH_FXF_TRUNC) != 0) {
-                    pflags |= SftpConstants.SSH_FXF_CREATE_TRUNCATE;
-                } else {
-                    pflags |= SftpConstants.SSH_FXF_OPEN_OR_CREATE;
-                }
-            } else {
-                if ((flags & SftpConstants.SSH_FXF_TRUNC) != 0) {
-                    pflags |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
-                } else {
-                    pflags |= SftpConstants.SSH_FXF_OPEN_EXISTING;
-                }
-            }
-        }
-
-        Map<String, Object> attrs = readAttrs(buffer);
-        String handle;
-        try {
-            handle = doOpen(id, path, pflags, access, attrs);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_OPEN, path);
-            return;
-        }
-
-        sendHandle(BufferUtils.clear(buffer), id, handle);
-    }
-
-    /**
-     * @param id     Request id
-     * @param path   Path
-     * @param pflags Open mode flags - see {@code SSH_FXF_XXX} flags
-     * @param access Access mode flags - see {@code ACE4_XXX} flags
-     * @param attrs  Requested attributes
-     * @return The assigned (opaque) handle
-     * @throws IOException if failed to execute
-     */
-    protected abstract String doOpen(int id, String path, int pflags, int 
access, Map<String, Object> attrs) throws IOException;
-
-    protected void doClose(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        try {
-            doClose(id, handle);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_CLOSE, handle);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "", 
"");
-    }
-
-    protected abstract void doClose(int id, String handle) throws IOException;
-
-    protected void doRead(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        int requestedLength = buffer.getInt();
-        ServerSession serverSession = getServerSession();
-        int maxAllowed = 
serverSession.getIntProperty(MAX_READDATA_PACKET_LENGTH_PROP, 
DEFAULT_MAX_READDATA_PACKET_LENGTH);
-        int readLen = Math.min(requestedLength, maxAllowed);
-        if (log.isTraceEnabled()) {
-            log.trace("doRead({})[id={}]({})[offset={}] - req={}, max={}, 
effective={}",
-                    serverSession, id, handle, offset, requestedLength, 
maxAllowed, readLen);
-        }
-
-        try {
-            ValidateUtils.checkTrue(readLen >= 0, "Illegal requested read 
length: %d", readLen);
-
-            buffer.clear();
-            buffer.ensureCapacity(readLen + Long.SIZE /* the header */, 
IntUnaryOperator.identity());
-
-            buffer.putByte((byte) SftpConstants.SSH_FXP_DATA);
-            buffer.putInt(id);
-            int lenPos = buffer.wpos();
-            buffer.putInt(0);
-
-            int startPos = buffer.wpos();
-            int len = doRead(id, handle, offset, readLen, buffer.array(), 
startPos);
-            if (len < 0) {
-                throw new EOFException("Unable to read " + readLen + " bytes 
from offset=" + offset + " of " + handle);
-            }
-            buffer.wpos(startPos + len);
-            BufferUtils.updateLengthPlaceholder(buffer, lenPos, len);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_READ, handle, offset, requestedLength);
-            return;
-        }
-
-        send(buffer);
-    }
-
-    protected abstract int doRead(int id, String handle, long offset, int 
length, byte[] data, int doff) throws IOException;
-
-    protected void doWrite(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        int length = buffer.getInt();
-        try {
-            doWrite(id, handle, offset, length, buffer.array(), buffer.rpos(), 
buffer.available());
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_WRITE, handle, offset, length);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doWrite(int id, String handle, long offset, int 
length, byte[] data, int doff, int remaining) throws IOException;
-
-    protected void doLStat(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL;
-        int version = getVersion();
-        if (version >= SftpConstants.SFTP_V4) {
-            flags = buffer.getInt();
-        }
-
-        Map<String, ?> attrs;
-        try {
-            attrs = doLStat(id, path, flags);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_LSTAT, path, flags);
-            return;
-        }
-
-        sendAttrs(BufferUtils.clear(buffer), id, attrs);
-    }
-
-    protected Map<String, Object> doLStat(int id, String path, int flags) 
throws IOException {
-        Path p = resolveFile(path);
-        if (log.isDebugEnabled()) {
-            log.debug("doLStat({})[id={}] SSH_FXP_LSTAT (path={}[{}], 
flags=0x{})",
-                      getServerSession(), id, path, p, 
Integer.toHexString(flags));
-        }
-
-        /*
-         * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT
-         * follows symbolic links on the server, whereas SSH_FXP_LSTAT does 
not.
-         */
-        return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(false));
-    }
-
-    protected void doSetStat(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        Map<String, Object> attrs = readAttrs(buffer);
-        try {
-            doSetStat(id, path, attrs);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_SETSTAT, path);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doSetStat(int id, String path, Map<String, ?> attrs) throws 
IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("doSetStat({})[id={}] SSH_FXP_SETSTAT (path={}, 
attrs={})",
-                      getServerSession(), id, path, attrs);
-        }
-        Path p = resolveFile(path);
-        doSetAttributes(p, attrs);
-    }
-
-    protected void doFStat(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL;
-        int version = getVersion();
-        if (version >= SftpConstants.SFTP_V4) {
-            flags = buffer.getInt();
-        }
-
-        Map<String, ?> attrs;
-        try {
-            attrs = doFStat(id, handle, flags);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_FSTAT, handle, flags);
-            return;
-        }
-
-        sendAttrs(BufferUtils.clear(buffer), id, attrs);
-    }
-
-    protected abstract Map<String, Object> doFStat(int id, String handle, int 
flags) throws IOException;
-
-    protected void doFSetStat(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        Map<String, Object> attrs = readAttrs(buffer);
-        try {
-            doFSetStat(id, handle, attrs);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_FSETSTAT, handle, attrs);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doFSetStat(int id, String handle, Map<String, ?> 
attrs) throws IOException;
-
-    protected void doOpenDir(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        String handle;
-
-        try {
-            Path p = resolveNormalizedLocation(path);
-            if (log.isDebugEnabled()) {
-                log.debug("doOpenDir({})[id={}] SSH_FXP_OPENDIR (path={})[{}]",
-                          getServerSession(), id, path, p);
-            }
-
-            LinkOption[] options =
-                getPathResolutionLinkOption(SftpConstants.SSH_FXP_OPENDIR, "", 
p);
-            handle = doOpenDir(id, path, p, options);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_OPENDIR, path);
-            return;
-        }
-
-        sendHandle(BufferUtils.clear(buffer), id, handle);
-    }
-
-    protected abstract String doOpenDir(int id, String path, Path p, 
LinkOption... options) throws IOException;
-
-    protected abstract void doReadDir(Buffer buffer, int id) throws 
IOException;
-
-    protected void doLink(Buffer buffer, int id) throws IOException {
-        String targetPath = buffer.getString();
-        String linkPath = buffer.getString();
-        boolean symLink = buffer.getBoolean();
-
-        try {
-            if (log.isDebugEnabled()) {
-                log.debug("doLink({})[id={}] SSH_FXP_LINK linkpath={}, 
targetpath={}, symlink={}",
-                          getServerSession(), id, linkPath, targetPath, 
symLink);
-            }
-
-            doLink(id, targetPath, linkPath, symLink);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_LINK, targetPath, linkPath, symLink);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doLink(int id, String targetPath, String linkPath, boolean 
symLink) throws IOException {
-        createLink(id, targetPath, linkPath, symLink);
-    }
-
-    protected void doSymLink(Buffer buffer, int id) throws IOException {
-        String targetPath = buffer.getString();
-        String linkPath = buffer.getString();
-        try {
-            if (log.isDebugEnabled()) {
-                log.debug("doSymLink({})[id={}] SSH_FXP_SYMLINK linkpath={}, 
targetpath={}",
-                          getServerSession(), id, targetPath, linkPath);
-            }
-            doSymLink(id, targetPath, linkPath);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_SYMLINK, targetPath, linkPath);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doSymLink(int id, String targetPath, String linkPath) 
throws IOException {
-        createLink(id, targetPath, linkPath, true);
-    }
-
-    protected abstract void createLink(int id, String existingPath, String 
linkPath, boolean symLink) throws IOException;
-
-    // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL 
section 10
-    protected void doOpenSSHHardLink(Buffer buffer, int id) throws IOException 
{
-        String srcFile = buffer.getString();
-        String dstFile = buffer.getString();
-
-        try {
-            doOpenSSHHardLink(id, srcFile, dstFile);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_EXTENDED, HardLinkExtensionParser.NAME, srcFile, dstFile);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doOpenSSHHardLink(int id, String srcFile, String dstFile) 
throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("doOpenSSHHardLink({})[id={}] SSH_FXP_EXTENDED[{}] 
(src={}, dst={})",
-                      getServerSession(), id, HardLinkExtensionParser.NAME, 
srcFile, dstFile);
-        }
-
-        createLink(id, srcFile, dstFile, false);
-    }
-
-    protected void doSpaceAvailable(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        SpaceAvailableExtensionInfo info;
-        try {
-            info = doSpaceAvailable(id, path);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_SPACE_AVAILABLE, path);
-            return;
-        }
-
-        buffer.clear();
-        buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
-        buffer.putInt(id);
-        SpaceAvailableExtensionInfo.encode(buffer, info);
-        send(buffer);
-    }
-
-    protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String 
path) throws IOException {
-        Path nrm = resolveNormalizedLocation(path);
-        if (log.isDebugEnabled()) {
-            log.debug("doSpaceAvailable({})[id={}] path={}[{}]", 
getServerSession(), id, path, nrm);
-        }
-
-        FileStore store = Files.getFileStore(nrm);
-        if (log.isTraceEnabled()) {
-            log.trace("doSpaceAvailable({})[id={}] path={}[{}] - {}[{}]",
-                      getServerSession(), id, path, nrm, store.name(), 
store.type());
-        }
-
-        return new SpaceAvailableExtensionInfo(store);
-    }
-
-    protected void doTextSeek(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long line = buffer.getLong();
-        try {
-            // TODO : implement text-seek - see 
https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-6.3
-            doTextSeek(id, handle, line);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_TEXT_SEEK, handle, line);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doTextSeek(int id, String handle, long line) 
throws IOException;
-
-    // see https://github.com/openssh/openssh-portable/blob/master/PROTOCOL 
section 10
-    protected void doOpenSSHFsync(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        try {
-            doOpenSSHFsync(id, handle);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_EXTENDED, FsyncExtensionParser.NAME, handle);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doOpenSSHFsync(int id, String handle) throws 
IOException;
-
-    protected void doCheckFileHash(Buffer buffer, int id, String targetType) 
throws IOException {
-        String target = buffer.getString();
-        String algList = buffer.getString();
-        String[] algos = GenericUtils.split(algList, ',');
-        long startOffset = buffer.getLong();
-        long length = buffer.getLong();
-        int blockSize = buffer.getInt();
-        try {
-            buffer.clear();
-            buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
-            buffer.putInt(id);
-            buffer.putString(SftpConstants.EXT_CHECK_FILE);
-            doCheckFileHash(id, targetType, target, Arrays.asList(algos), 
startOffset, length, blockSize, buffer);
-        } catch (Exception e) {
-            sendStatus(BufferUtils.clear(buffer), id, e,
-                SftpConstants.SSH_FXP_EXTENDED, targetType, target, algList, 
startOffset, length, blockSize);
-            return;
-        }
-
-        send(buffer);
-    }
-
-    protected void doCheckFileHash(int id, Path file, NamedFactory<? extends 
Digest> factory,
-            long startOffset, long length, int blockSize, Buffer buffer)
-                    throws Exception {
-        ValidateUtils.checkTrue(startOffset >= 0L, "Invalid start offset: %d", 
startOffset);
-        ValidateUtils.checkTrue(length >= 0L, "Invalid length: %d", length);
-        ValidateUtils.checkTrue((blockSize == 0) || (blockSize >= 
SftpConstants.MIN_CHKFILE_BLOCKSIZE), "Invalid block size: %d", blockSize);
-        Objects.requireNonNull(factory, "No digest factory provided");
-        buffer.putString(factory.getName());
-
-        long effectiveLength = length;
-        long totalLength = Files.size(file);
-        if (effectiveLength == 0L) {
-            effectiveLength = totalLength - startOffset;
-        } else {
-            long maxRead = startOffset + length;
-            if (maxRead > totalLength) {
-                effectiveLength = totalLength - startOffset;
-            }
-        }
-        ValidateUtils.checkTrue(effectiveLength > 0L, "Non-positive effective 
hash data length: %d", effectiveLength);
-
-        byte[] digestBuf = (blockSize == 0)
-                ? new byte[Math.min((int) effectiveLength, 
IoUtils.DEFAULT_COPY_SIZE)]
-                : new byte[Math.min((int) effectiveLength, blockSize)];
-        ByteBuffer wb = ByteBuffer.wrap(digestBuf);
-        SftpFileSystemAccessor accessor = getFileSystemAccessor();
-        try (SeekableByteChannel channel = 
accessor.openFile(getServerSession(), this, file, "", Collections.emptySet())) {
-            channel.position(startOffset);
-
-            Digest digest = factory.create();
-            digest.init();
-
-            boolean traceEnabled = log.isTraceEnabled();
-            if (blockSize == 0) {
-                while (effectiveLength > 0L) {
-                    int remainLen = Math.min(digestBuf.length, (int) 
effectiveLength);
-                    ByteBuffer bb = wb;
-                    if (remainLen < digestBuf.length) {
-                        bb = ByteBuffer.wrap(digestBuf, 0, remainLen);
-                    }
-                    bb.clear(); // prepare for next read
-
-                    int readLen = channel.read(bb);
-                    if (readLen < 0) {
-                        break;
-                    }
-
-                    effectiveLength -= readLen;
-                    digest.update(digestBuf, 0, readLen);
-                }
-
-                byte[] hashValue = digest.digest();
-                if (traceEnabled) {
-                    log.trace("doCheckFileHash({})[{}] offset={}, length={} - 
algo={}, hash={}",
-                              getServerSession(), file, startOffset, length,
-                              digest.getAlgorithm(), BufferUtils.toHex(':', 
hashValue));
-                }
-                buffer.putBytes(hashValue);
-            } else {
-                for (int count = 0; effectiveLength > 0L; count++) {
-                    int remainLen = Math.min(digestBuf.length, (int) 
effectiveLength);
-                    ByteBuffer bb = wb;
-                    if (remainLen < digestBuf.length) {
-                        bb = ByteBuffer.wrap(digestBuf, 0, remainLen);
-                    }
-                    bb.clear(); // prepare for next read
-
-                    int readLen = channel.read(bb);
-                    if (readLen < 0) {
-                        break;
-                    }
-
-                    effectiveLength -= readLen;
-                    digest.update(digestBuf, 0, readLen);
-
-                    byte[] hashValue = digest.digest(); // NOTE: this also 
resets the hash for the next read
-                    if (traceEnabled) {
-                        log.trace("doCheckFileHash({})({})[{}] offset={}, 
length={} - algo={}, hash={}",
-                                  getServerSession(), file, count, 
startOffset, length,
-                                  digest.getAlgorithm(), 
BufferUtils.toHex(':', hashValue));
-                    }
-                    buffer.putBytes(hashValue);
-                }
-            }
-        }
-    }
-
-    protected void doMD5Hash(Buffer buffer, int id, String targetType) throws 
IOException {
-        String target = buffer.getString();
-        long startOffset = buffer.getLong();
-        long length = buffer.getLong();
-        byte[] quickCheckHash = buffer.getBytes();
-        byte[] hashValue;
-
-        try {
-            hashValue = doMD5Hash(id, targetType, target, startOffset, length, 
quickCheckHash);
-            if (log.isTraceEnabled()) {
-                log.trace("doMD5Hash({})({})[{}] offset={}, length={}, 
quick-hash={} - hash={}",
-                          getServerSession(), targetType, target, startOffset, 
length,
-                          BufferUtils.toHex(':', quickCheckHash),
-                          BufferUtils.toHex(':', hashValue));
-            }
-
-        } catch (Exception e) {
-            sendStatus(BufferUtils.clear(buffer), id, e,
-                SftpConstants.SSH_FXP_EXTENDED, targetType, target, 
startOffset, length, quickCheckHash);
-            return;
-        }
-
-        buffer.clear();
-        buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
-        buffer.putInt(id);
-        buffer.putString(targetType);
-        buffer.putBytes(hashValue);
-        send(buffer);
-    }
-
-    protected abstract byte[] doMD5Hash(
-            int id, String targetType, String target, long startOffset, long 
length, byte[] quickCheckHash)
-                    throws Exception;
-
-    protected byte[] doMD5Hash(int id, Path path, long startOffset, long 
length, byte[] quickCheckHash) throws Exception {
-        ValidateUtils.checkTrue(startOffset >= 0L, "Invalid start offset: %d", 
startOffset);
-        ValidateUtils.checkTrue(length > 0L, "Invalid length: %d", length);
-        if (!BuiltinDigests.md5.isSupported()) {
-            throw new 
UnsupportedOperationException(BuiltinDigests.md5.getAlgorithm() + " hash not 
supported");
-        }
-
-        Digest digest = BuiltinDigests.md5.create();
-        digest.init();
-
-        long effectiveLength = length;
-        byte[] digestBuf = new byte[(int) Math.min(effectiveLength, 
SftpConstants.MD5_QUICK_HASH_SIZE)];
-        ByteBuffer wb = ByteBuffer.wrap(digestBuf);
-        boolean hashMatches = false;
-        byte[] hashValue = null;
-        SftpFileSystemAccessor accessor = getFileSystemAccessor();
-        boolean traceEnabled = log.isTraceEnabled();
-        try (SeekableByteChannel channel = 
accessor.openFile(getServerSession(), this, path, null, 
EnumSet.of(StandardOpenOption.READ))) {
-            channel.position(startOffset);
-
-            /*
-             * To quote 
http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt
 section 9.1.1:
-             *
-             *      If this is a zero length string, the client does not have 
the
-             *      data, and is requesting the hash for reasons other than 
comparing
-             *      with a local file.  The server MAY return 
SSH_FX_OP_UNSUPPORTED in
-             *      this case.
-             */
-            if (NumberUtils.length(quickCheckHash) <= 0) {
-                // TODO consider limiting it - e.g., if the requested 
effective length is <= than some (configurable) threshold
-                hashMatches = true;
-            } else {
-                int readLen = channel.read(wb);
-                if (readLen < 0) {
-                    throw new EOFException("EOF while read initial buffer from 
" + path);
-                }
-                effectiveLength -= readLen;
-                digest.update(digestBuf, 0, readLen);
-
-                hashValue = digest.digest();
-                hashMatches = Arrays.equals(quickCheckHash, hashValue);
-                if (hashMatches) {
-                    /*
-                     * Need to re-initialize the digester due to the Javadoc:
-                     *
-                     *      "The digest method can be called once for a given 
number
-                     *       of updates. After digest has been called, the 
MessageDigest
-                     *       object is reset to its initialized state."
-                     */
-                    if (effectiveLength > 0L) {
-                        digest = BuiltinDigests.md5.create();
-                        digest.init();
-                        digest.update(digestBuf, 0, readLen);
-                        hashValue = null;   // start again
-                    }
-                } else {
-                    if (traceEnabled) {
-                        log.trace("doMD5Hash({})({}) offset={}, length={} - 
quick-hash mismatched expected={}, actual={}",
-                                  getServerSession(), path, startOffset, 
length,
-                                  BufferUtils.toHex(':', quickCheckHash),
-                                  BufferUtils.toHex(':', hashValue));
-                    }
-                }
-            }
-
-            if (hashMatches) {
-                while (effectiveLength > 0L) {
-                    int remainLen = Math.min(digestBuf.length, (int) 
effectiveLength);
-                    ByteBuffer bb = wb;
-                    if (remainLen < digestBuf.length) {
-                        bb = ByteBuffer.wrap(digestBuf, 0, remainLen);
-                    }
-                    bb.clear(); // prepare for next read
-
-                    int readLen = channel.read(bb);
-                    if (readLen < 0) {
-                        break;  // user may have specified more than we have 
available
-                    }
-                    effectiveLength -= readLen;
-                    digest.update(digestBuf, 0, readLen);
-                }
-
-                if (hashValue == null) {    // check if did any more 
iterations after the quick hash
-                    hashValue = digest.digest();
-                }
-            } else {
-                hashValue = GenericUtils.EMPTY_BYTE_ARRAY;
-            }
-        }
-
-        if (traceEnabled) {
-            log.trace("doMD5Hash({})({}) offset={}, length={} - matches={}, 
quick={} hash={}",
-                      getServerSession(), path, startOffset, length, 
hashMatches,
-                      BufferUtils.toHex(':', quickCheckHash),
-                      BufferUtils.toHex(':', hashValue));
-        }
-
-        return hashValue;
-    }
-
-    protected abstract void doCheckFileHash(
-            int id, String targetType, String target, Collection<String> algos,
-            long startOffset, long length, int blockSize, Buffer buffer)
-                    throws Exception;
-
-    protected void doReadLink(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        String l;
-        try {
-            if (log.isDebugEnabled()) {
-                log.debug("doReadLink({})[id={}] SSH_FXP_READLINK path={}",
-                          getServerSession(), id, path);
-            }
-            l = doReadLink(id, path);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_READLINK, path);
-            return;
-        }
-
-        sendLink(BufferUtils.clear(buffer), id, l);
-    }
-
-    protected String doReadLink(int id, String path) throws IOException {
-        Path f = resolveFile(path);
-        Path t = Files.readSymbolicLink(f);
-        if (log.isDebugEnabled()) {
-            log.debug("doReadLink({})[id={}] path={}[{}]: {}",
-                      getServerSession(), id, path, f, t);
-        }
-        return t.toString();
-    }
-
-    protected void doRename(Buffer buffer, int id) throws IOException {
-        String oldPath = buffer.getString();
-        String newPath = buffer.getString();
-        int flags = 0;
-        int version = getVersion();
-        if (version >= SftpConstants.SFTP_V5) {
-            flags = buffer.getInt();
-        }
-        try {
-            doRename(id, oldPath, newPath, flags);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_RENAME, oldPath, newPath, flags);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doRename(int id, String oldPath, String newPath, int flags) 
throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("doRename({})[id={}] SSH_FXP_RENAME (oldPath={}, 
newPath={}, flags=0x{})",
-                      getServerSession(), id, oldPath, newPath, 
Integer.toHexString(flags));
-        }
-
-        Collection<CopyOption> opts = Collections.emptyList();
-        if (flags != 0) {
-            opts = new ArrayList<>();
-            if ((flags & SftpConstants.SSH_FXP_RENAME_ATOMIC) == 
SftpConstants.SSH_FXP_RENAME_ATOMIC) {
-                opts.add(StandardCopyOption.ATOMIC_MOVE);
-            }
-            if ((flags & SftpConstants.SSH_FXP_RENAME_OVERWRITE) == 
SftpConstants.SSH_FXP_RENAME_OVERWRITE) {
-                opts.add(StandardCopyOption.REPLACE_EXISTING);
-            }
-        }
-
-        doRename(id, oldPath, newPath, opts);
-    }
-
-    protected void doRename(int id, String oldPath, String newPath, 
Collection<CopyOption> opts) throws IOException {
-        Path o = resolveFile(oldPath);
-        Path n = resolveFile(newPath);
-        SftpEventListener listener = getSftpEventListenerProxy();
-        ServerSession session = getServerSession();
-
-        listener.moving(session, o, n, opts);
-        try {
-            Files.move(o, n, GenericUtils.isEmpty(opts) ? 
IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
-        } catch (IOException | RuntimeException e) {
-            listener.moved(session, o, n, opts, e);
-            throw e;
-        }
-        listener.moved(session, o, n, opts, null);
-    }
-
-    // see 
https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7
-    protected void doCopyData(Buffer buffer, int id) throws IOException {
-        String readHandle = buffer.getString();
-        long readOffset = buffer.getLong();
-        long readLength = buffer.getLong();
-        String writeHandle = buffer.getString();
-        long writeOffset = buffer.getLong();
-        try {
-            doCopyData(id, readHandle, readOffset, readLength, writeHandle, 
writeOffset);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e,
-                SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_COPY_DATA,
-                readHandle, readOffset, readLength, writeHandle, writeOffset);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doCopyData(int id, String readHandle, long 
readOffset, long readLength, String writeHandle, long writeOffset) throws 
IOException;
-
-    // see 
https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-6
-    protected void doCopyFile(Buffer buffer, int id) throws IOException {
-        String srcFile = buffer.getString();
-        String dstFile = buffer.getString();
-        boolean overwriteDestination = buffer.getBoolean();
-
-        try {
-            doCopyFile(id, srcFile, dstFile, overwriteDestination);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e,
-                SftpConstants.SSH_FXP_EXTENDED, SftpConstants.EXT_COPY_FILE, 
srcFile, dstFile, overwriteDestination);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doCopyFile(int id, String srcFile, String dstFile, boolean 
overwriteDestination) throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("doCopyFile({})[id={}] SSH_FXP_EXTENDED[{}] (src={}, 
dst={}, overwrite=0x{})",
-                      getServerSession(), id, SftpConstants.EXT_COPY_FILE,
-                      srcFile, dstFile, overwriteDestination);
-        }
-
-        doCopyFile(id, srcFile, dstFile,
-                overwriteDestination
-                        ? 
Collections.singletonList(StandardCopyOption.REPLACE_EXISTING)
-                        : Collections.emptyList());
-    }
-
-    protected void doCopyFile(int id, String srcFile, String dstFile, 
Collection<CopyOption> opts) throws IOException {
-        Path src = resolveFile(srcFile);
-        Path dst = resolveFile(dstFile);
-        Files.copy(src, dst, GenericUtils.isEmpty(opts) ? 
IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
-    }
-
-    protected void doBlock(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        long length = buffer.getLong();
-        int mask = buffer.getInt();
-
-        try {
-            doBlock(id, handle, offset, length, mask);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_BLOCK, handle, offset, length, mask);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doBlock(int id, String handle, long offset, long 
length, int mask) throws IOException;
-
-    protected void doUnblock(Buffer buffer, int id) throws IOException {
-        String handle = buffer.getString();
-        long offset = buffer.getLong();
-        long length = buffer.getLong();
-        try {
-            doUnblock(id, handle, offset, length);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_UNBLOCK, handle, offset, length);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected abstract void doUnblock(int id, String handle, long offset, long 
length) throws IOException;
-
-    protected void doStat(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        int flags = SftpConstants.SSH_FILEXFER_ATTR_ALL;
-        int version = getVersion();
-        if (version >= SftpConstants.SFTP_V4) {
-            flags = buffer.getInt();
-        }
-
-        Map<String, Object> attrs;
-        try {
-            attrs = doStat(id, path, flags);
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_STAT, path, flags);
-            return;
-        }
-
-        sendAttrs(BufferUtils.clear(buffer), id, attrs);
-    }
-
-    protected Map<String, Object> doStat(int id, String path, int flags) 
throws IOException {
-        if (log.isDebugEnabled()) {
-            log.debug("doStat({})[id={}] SSH_FXP_STAT (path={}, flags=0x{})",
-                      getServerSession(), id, path, 
Integer.toHexString(flags));
-        }
-
-        /*
-         * SSH_FXP_STAT and SSH_FXP_LSTAT only differ in that SSH_FXP_STAT
-         * follows symbolic links on the server, whereas SSH_FXP_LSTAT does 
not.
-         */
-        Path p = resolveFile(path);
-        return resolveFileAttributes(p, flags, IoUtils.getLinkOptions(true));
-    }
-
-    protected void doRealPath(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        boolean debugEnabled = log.isDebugEnabled();
-        if (debugEnabled) {
-            log.debug("doRealPath({})[id={}] SSH_FXP_REALPATH (path={})", 
getServerSession(), id, path);
-        }
-        path = GenericUtils.trimToEmpty(path);
-        if (GenericUtils.isEmpty(path)) {
-            path = ".";
-        }
-
-        Map<String, ?> attrs = Collections.emptyMap();
-        Map.Entry<Path, Boolean> result;
-        try {
-            int version = getVersion();
-            if (version < SftpConstants.SFTP_V6) {
-                /*
-                 * See 
http://www.openssh.com/txt/draft-ietf-secsh-filexfer-02.txt:
-                 *
-                 *      The SSH_FXP_REALPATH request can be used to have the 
server
-                 *      canonicalize any given path name to an absolute path.
-                 *
-                 * See also SSHD-294
-                 */
-                Path p = resolveFile(path);
-                LinkOption[] options =
-                    
getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
-                result = doRealPathV345(id, path, p, options);
-            } else {
-                /*
-                 * See 
https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
-                 *
-                 *      This field is optional, and if it is not present in 
the packet, it
-                 *      is assumed to be SSH_FXP_REALPATH_NO_CHECK.
-                 */
-                int control = SftpConstants.SSH_FXP_REALPATH_NO_CHECK;
-                if (buffer.available() > 0) {
-                    control = buffer.getUByte();
-                    if (debugEnabled) {
-                        log.debug("doRealPath({}) - control=0x{} for path={}",
-                              getServerSession(), 
Integer.toHexString(control), path);
-                    }
-                }
-
-                Collection<String> extraPaths = new LinkedList<>();
-                while (buffer.available() > 0) {
-                    extraPaths.add(buffer.getString());
-                }
-
-                Path p = resolveFile(path);
-                LinkOption[] options =
-                    
getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
-                result = doRealPathV6(id, path, extraPaths, p, options);
-
-                p = result.getKey();
-                options = 
getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
-                Boolean status = result.getValue();
-                switch (control) {
-                    case SftpConstants.SSH_FXP_REALPATH_STAT_IF:
-                        if (status == null) {
-                            attrs = handleUnknownStatusFileAttributes(p, 
SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
-                        } else if (status) {
-                            try {
-                                attrs = getAttributes(p, options);
-                            } catch (IOException e) {
-                                if (debugEnabled) {
-                                    log.debug("doRealPath({}) - failed ({}) to 
retrieve attributes of {}: {}",
-                                              getServerSession(), 
e.getClass().getSimpleName(), p, e.getMessage());
-                                }
-                                if (log.isTraceEnabled()) {
-                                    log.trace("doRealPath(" + 
getServerSession() + ")[" + p + "] attributes retrieval failure details", e);
-                                }
-                            }
-                        } else {
-                            if (debugEnabled) {
-                                log.debug("doRealPath({}) - dummy attributes 
for non-existing file: {}", getServerSession(), p);
-                            }
-                        }
-                        break;
-                    case SftpConstants.SSH_FXP_REALPATH_STAT_ALWAYS:
-                        if (status == null) {
-                            attrs = handleUnknownStatusFileAttributes(p, 
SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
-                        } else if (status) {
-                            attrs = getAttributes(p, options);
-                        } else {
-                            throw new NoSuchFileException(p.toString(), 
p.toString(), "Real path N/A for target");
-                        }
-                        break;
-                    case SftpConstants.SSH_FXP_REALPATH_NO_CHECK:
-                        break;
-                    default:
-                        log.warn("doRealPath({}) unknown control value 0x{} 
for path={}",
-                             getServerSession(), Integer.toHexString(control), 
p);
-                }
-            }
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_REALPATH, path);
-            return;
-        }
-
-        sendPath(BufferUtils.clear(buffer), id, result.getKey(), attrs);
-    }
-
-    protected SimpleImmutableEntry<Path, Boolean> doRealPathV6(
-            int id, String path, Collection<String> extraPaths, Path p, 
LinkOption... options) throws IOException {
-        int numExtra = GenericUtils.size(extraPaths);
-        if (numExtra > 0) {
-            if (log.isDebugEnabled()) {
-                log.debug("doRealPathV6({})[id={}] path={}, extra={}",
-                          getServerSession(), id, path, extraPaths);
-            }
-            StringBuilder sb = new StringBuilder(GenericUtils.length(path) + 
numExtra * 8);
-            sb.append(path);
-
-            for (String p2 : extraPaths) {
-                p = p.resolve(p2);
-                options = 
getPathResolutionLinkOption(SftpConstants.SSH_FXP_REALPATH, "", p);
-                sb.append('/').append(p2);
-            }
-
-            path = sb.toString();
-        }
-
-        return validateRealPath(id, path, p, options);
-    }
-
-    protected SimpleImmutableEntry<Path, Boolean> doRealPathV345(int id, 
String path, Path p, LinkOption... options) throws IOException {
-        return validateRealPath(id, path, p, options);
-    }
-
-    /**
-     * @param id      The request identifier
-     * @param path    The original path
-     * @param f       The resolve {@link Path}
-     * @param options The {@link LinkOption}s to use to verify file existence 
and access
-     * @return A {@link SimpleImmutableEntry} whose key is the <U>absolute 
<B>normalized</B></U>
-     * {@link Path} and value is a {@link Boolean} indicating its status
-     * @throws IOException If failed to validate the file
-     * @see IoUtils#checkFileExists(Path, LinkOption...)
-     */
-    protected SimpleImmutableEntry<Path, Boolean> validateRealPath(int id, 
String path, Path f, LinkOption... options) throws IOException {
-        Path p = normalize(f);
-        Boolean status = IoUtils.checkFileExists(p, options);
-        return new SimpleImmutableEntry<>(p, status);
-    }
-
-    protected void doRemoveDirectory(Buffer buffer, int id) throws IOException 
{
-        String path = buffer.getString();
-        try {
-            doRemoveDirectory(id, path, IoUtils.getLinkOptions(false));
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_RMDIR, path);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doRemoveDirectory(int id, String path, LinkOption... 
options) throws IOException {
-        Path p = resolveFile(path);
-        if (log.isDebugEnabled()) {
-            log.debug("doRemoveDirectory({})[id={}] SSH_FXP_RMDIR 
(path={})[{}]",
-                      getServerSession(), id, path, p);
-        }
-        if (Files.isDirectory(p, options)) {
-            doRemove(id, p);
-        } else {
-            throw new NotDirectoryException(p.toString());
-        }
-    }
-
-    /**
-     * Called when need to delete a file / directory - also informs the {@link 
SftpEventListener}
-     *
-     * @param id Deletion request ID
-     * @param p {@link Path} to delete
-     * @throws IOException If failed to delete
-     */
-    protected void doRemove(int id, Path p) throws IOException {
-        SftpEventListener listener = getSftpEventListenerProxy();
-        ServerSession session = getServerSession();
-        listener.removing(session, p);
-        try {
-            Files.delete(p);
-        } catch (IOException | RuntimeException e) {
-            listener.removed(session, p, e);
-            throw e;
-        }
-        listener.removed(session, p, null);
-    }
-
-    protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        Map<String, ?> attrs = readAttrs(buffer);
-        try {
-            doMakeDirectory(id, path, attrs, IoUtils.getLinkOptions(false));
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_MKDIR, path, attrs);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doMakeDirectory(int id, String path, Map<String, ?> attrs, 
LinkOption... options) throws IOException {
-        Path p = resolveFile(path);
-        if (log.isDebugEnabled()) {
-            log.debug("doMakeDirectory({})[id={}] SSH_FXP_MKDIR (path={}[{}], 
attrs={})",
-                      getServerSession(), id, path, p, attrs);
-        }
-
-        Boolean status = IoUtils.checkFileExists(p, options);
-        if (status == null) {
-            throw new AccessDeniedException(p.toString(), p.toString(), 
"Cannot validate make-directory existence");
-        }
-
-        if (status) {
-            if (Files.isDirectory(p, options)) {
-                throw new FileAlreadyExistsException(p.toString(), 
p.toString(), "Target directory already exists");
-            } else {
-                throw new FileAlreadyExistsException(p.toString(), 
p.toString(), "Already exists as a file");
-            }
-        } else {
-            SftpEventListener listener = getSftpEventListenerProxy();
-            ServerSession session = getServerSession();
-            listener.creating(session, p, attrs);
-            try {
-                Files.createDirectory(p);
-                doSetAttributes(p, attrs);
-            } catch (IOException | RuntimeException e) {
-                listener.created(session, p, attrs, e);
-                throw e;
-            }
-            listener.created(session, p, attrs, null);
-        }
-    }
-
-    protected void doRemove(Buffer buffer, int id) throws IOException {
-        String path = buffer.getString();
-        try {
-            /*
-             * If 'filename' is a symbolic link, the link is removed,
-             * not the file it points to.
-             */
-            doRemove(id, path, IoUtils.getLinkOptions(false));
-        } catch (IOException | RuntimeException e) {
-            sendStatus(BufferUtils.clear(buffer), id, e, 
SftpConstants.SSH_FXP_REMOVE, path);
-            return;
-        }
-
-        sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
-    }
-
-    protected void doRemove(int id, String path, LinkOption... options) throws 
IOException {
-        Path p = resolveFile(path);
-        if (log.isDebugEnabled()) {
-            log.debug("doRemove({})[id={}] SSH_FXP_REMOVE (path={}[{}])",
-                      getServerSession(), id, path, p);
-        }
-
-        Boolean status = IoUtils.checkFileExists(p, options);
-        if (status == null) {
-            throw new AccessDeniedException(p.toString(), p.toString(), 
"Cannot determine existence of remove candidate");
-        }
-        if (!status) {
-            throw new NoSuchFileException(p.toString(), p.toString(), "Removal 
candidate not found");
-        } else if (Files.isDirectory(p, options)) {
-            throw new SftpException(SftpConstants.SSH_FX_FILE_IS_A_DIRECTORY, 
p.toString() + " is a folder");
-        } else {
-            doRemove(id, p);
-        }
-    }
-
-    protected void doExtended(Buffer buffer, int id) throws IOException {
-        executeExtendedCommand(buffer, id, buffer.getString());
-    }
-
-    /**
-     * @param buffer    The command {@link Buffer}
-     * @param id        The request id
-     * @param extension The extension name
-     * @throws IOException If failed to execute the extension
-     */
-    protected abstract void executeExtendedCommand(Buffer buffer, int id, 
String extension) throws IOException;
-
-    protected void appendExtensions(Buffer buffer, String supportedVersions) {
-        appendVersionsExtension(buffer, supportedVersions);
-        appendNewlineExtension(buffer, 
resolveNewlineValue(getServerSession()));
-        appendVendorIdExtension(buffer, 
VersionProperties.getVersionProperties());
-        appendOpenSSHExtensions(buffer);
-        appendAclSupportedExtension(buffer);
-
-        Map<String, OptionalFeature> extensions = 
getSupportedClientExtensions();
-        int numExtensions = GenericUtils.size(extensions);
-        List<String> extras = (numExtensions <= 0) ? Collections.emptyList() : 
new ArrayList<>(numExtensions);
-        if (numExtensions > 0) {
-            ServerSession session = getServerSession();
-            boolean debugEnabled = log.isDebugEnabled();
-            extensions.forEach((name, f) -> {
-                if (!f.isSupported()) {
-                    if (debugEnabled) {
-                        log.debug("appendExtensions({}) skip unsupported 
extension={}", session, name);
-                    }
-                    return;
-                }
-
-                extras.add(name);
-            });
-        }
-        appendSupportedExtension(buffer, extras);
-        appendSupported2Extension(buffer, extras);
-    }
-
-    protected int appendAclSupportedExtension(Buffer buffer) {
-        ServerSession session = getServerSession();
-        Collection<Integer> maskValues = 
resolveAclSupportedCapabilities(session);
-        int mask = 
AclSupportedParser.AclCapabilities.constructAclCapabilities(maskValues);
-        if (mask != 0) {
-            if (log.isTraceEnabled()) {
-                log.trace("appendAclSupportedExtension({}) capabilities={}",
-                          session, 
AclSupportedParser.AclCapabilities.decodeAclCapabilities(mask));
-            }
-
-            buffer.putString(SftpConstants.EXT_ACL_SUPPORTED);
-
-            // placeholder for length
-            int lenPos = buffer.wpos();
-            buffer.putInt(0);
-            buffer.putInt(mask);
-            BufferUtils.updateLengthPlaceholder(buffer, lenPos);
-        }
-
-        return mask;
-    }
-
-    protected Collection<Integer> 
resolveAclSupportedCapabilities(ServerSession session) {
-        String override = session.getString(ACL_SUPPORTED_MASK_PROP);
-        if (override == null) {
-            return DEFAULT_ACL_SUPPORTED_MASK;
-        }
-
-        // empty means not supported
-        if (log.isDebugEnabled()) {
-            log.debug("resolveAclSupportedCapabilities({}) override='{}'", 
session, override);
-        }
-
-        if (override.length() == 0) {
-            return Collections.emptySet();
-        }
-
-        String[] names = GenericUtils.split(override, ',');
-        Set<Integer> maskValues = new HashSet<>(names.length);
-        for (String n : names) {
-            Integer v = ValidateUtils.checkNotNull(
-                AclSupportedParser.AclCapabilities.getAclCapabilityValue(n), 
"Unknown ACL capability: %s", n);
-            maskValues.add(v);
-        }
-
-        return maskValues;
-    }
-
-    protected List<OpenSSHExtension> appendOpenSSHExtensions(Buffer buffer) {
-        List<OpenSSHExtension> extList = 
resolveOpenSSHExtensions(getServerSession());
-        if (GenericUtils.isEmpty(extList)) {
-            return extList;
-        }
-
-        for (OpenSSHExtension ext : extList) {
-            buffer.putString(ext.getName());
-            buffer.putString(ext.getVersion());
-        }
-
-        return extList;
-    }
-
-    protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession 
session) {
-        String value = session.getString(OPENSSH_EXTENSIONS_PROP);
-        if (value == null) {    // No override
-            return DEFAULT_OPEN_SSH_EXTENSIONS;
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("resolveOpenSSHExtensions({}) override='{}'", session, 
value);
-        }
-
-        String[] pairs = GenericUtils.split(value, ',');
-        int numExts = GenericUtils.length(pairs);
-        if (numExts <= 0) {     // User does not want to report ANY extensions
-            return Collections.emptyList();
-        }
-
-        List<OpenSSHExtension> extList = new ArrayList<>(numExts);
-        for (String nvp : pairs) {
-            nvp = GenericUtils.trimToEmpty(nvp);
-            if (GenericUtils.isEmpty(nvp)) {
-                continue;
-            }
-
-            int pos = nvp.indexOf('=');
-            ValidateUtils.checkTrue((pos > 0) && (pos < (nvp.length() - 1)), 
"Malformed OpenSSH extension spec: %s", nvp);
-            String name = GenericUtils.trimToEmpty(nvp.substring(0, pos));
-            String version = GenericUtils.trimToEmpty(nvp.substring(pos + 1));
-            extList.add(new OpenSSHExtension(name, 
ValidateUtils.checkNotNullAndNotEmpty(version, "No version specified for 
OpenSSH extension %s", name)));
-        }
-
-        return extList;
-    }
-
-    protected Map<String, OptionalFeature> getSupportedClientExtensions() {
-        ServerSession session = getServerSession();
-        String value = session.getString(CLIENT_EXTENSIONS_PROP);
-        if (value == null) {
-            return DEFAULT_SUPPORTED_CLIENT_EXTENSIONS;
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("getSupportedClientExtensions({}) override='{}'", 
session, value);
-        }
-
-        if (value.length() <= 0) {  // means don't report any extensions
-            return Collections.emptyMap();
-        }
-
-        if (value.indexOf(',') <= 0) {
-            return Collections.singletonMap(value, OptionalFeature.TRUE);
-        }
-
-        String[] comps = GenericUtils.split(value, ',');
-        Map<String, OptionalFeature> result = new 
LinkedHashMap<>(comps.length);
-        for (String c : comps) {
-            result.put(c, OptionalFeature.TRUE);
-        }
-
-        return result;
-    }
-
-    /**
-     * Appends the &quot;versions&quot; extension to the buffer. <B>Note:</B>
-     * if overriding this method make sure you either do not append anything
-     * or use the correct extension name
-     *
-     * @param buffer The {@link Buffer} to append to
-     * @param value  The recommended value - ignored if {@code null}/empty
-     * @see SftpConstants#EXT_VERSIONS
-     */
-    protected void appendVersionsExtension(Buffer buffer, String value) {
-        if (GenericUtils.isEmpty(value)) {
-            return;
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("appendVersionsExtension({}) value={}", 
getServerSession(), value);
-        }
-
-        buffer.putString(SftpConstants.EXT_VERSIONS);
-        buffer.putString(value);
-    }
-
-    /**
-     * Appends the &quot;newline&quot; extension to the buffer. <B>Note:</B>
-     * if overriding this method make sure you either do not append anything
-     * or use the correct extension name
-     *
-     * @param buffer The {@link Buffer} to append to
-     * @param value  The recommended value - ignored if {@code null}/empty
-     * @see SftpConstants#EXT_NEWLINE
-     */
-    protected void appendNewlineExtension(Buffer buffer, String value) {
-        if (GenericUtils.isEmpty(value)) {
-            return;
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("appendNewlineExtension({}) value={}",
-                      getServerSession(), BufferUtils.toHex(':', 
value.getBytes(StandardCharsets.UTF_8)));
-        }
-
-        buffer.putString(SftpConstants.EXT_NEWLINE);
-        buffer.putString(value);
-    }
-
-    protected String resolveNewlineValue(ServerSession session) {
-        String value = session.getString(NEWLINE_VALUE);
-        if (value == null) {
-            return IoUtils.EOL;
-        } else {
-            return value;   // empty means disabled
-        }
-    }
-
-    /**
-     * Appends the &quot;vendor-id&quot; extension to the buffer. <B>Note:</B>
-     * if overriding this method make sure you either do not append anything
-     * or use the correct extension name
-     *
-     * @param buffer            The {@link Buffer} to append to
-     * @param versionProperties The currently available version properties - 
ignored
-     * if {@code null}/empty. The code expects the following values:
-     * <UL>
-     *     <LI>{@code groupId} - as the vendor name</LI>
-     *     <LI>{@code artifactId} - as the product name</LI>
-     *     <LI>{@code version} - as the product version</LI>
-     * </UL>
-     * @see SftpConstants#EXT_VENDOR_ID
-     * @see <A 
HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt";>DRAFT
 09 - section 4.4</A>
-     */
-    protected void appendVendorIdExtension(Buffer buffer, Map<String, ?> 
versionProperties) {
-        if (GenericUtils.isEmpty(versionProperties)) {
-            return;
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("appendVendorIdExtension({}): {}", getServerSession(), 
versionProperties);
-        }
-        buffer.putString(SftpConstants.EXT_VENDOR_ID);
-
-        PropertyResolver resolver = 
PropertyResolverUtils.toPropertyResolver(Collections.unmodifiableMap(versionProperties));
-        // placeholder for length
-        int lenPos = buffer.wpos();
-        buffer.putInt(0);
-        buffer.putString(resolver.getStringProperty("groupId", 
getClass().getPackage().getName()));   // vendor-name
-        buffer.putString(resolver.getStringProperty("artifactId", 
getClass().getSimpleName()));       // product-name
-        buffer.putString(resolver.getStringProperty("version", 
FactoryManager.DEFAULT_VERSION));      // product-version
-        buffer.putLong(0L); // product-build-number
-        BufferUtils.updateLengthPlaceholder(buffer, lenPos);
-    }
-
-    /**
-     * Appends the &quot;supported&quot; extension to the buffer. <B>Note:</B>
-     * if overriding this method make sure you either do not append anything
-     * or use the correct extension name
-     *
-     * @param buffer The {@link Buffer} to append to
-     * @param extras The extra extensions that are available and can be 
reported
-     * - may be {@code null}/empty
-     */
-    protected void appendSupportedExtension(Buffer buffer, Collection<String> 
extras) {
-        buffer.putString(SftpConstants.EXT_SUPPORTED);
-
-        int lenPos = buffer.wpos();
-        buffer.putInt(0); // length placeholder
-        // supported-attribute-mask
-        buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_SIZE | 
SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS
-                | SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME | 
SftpConstants.SSH_FILEXFER_ATTR_CREATETIME
-                | SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME | 
SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP
-                | SftpConstants.SSH_FILEXFER_ATTR_BITS);
-        // TODO: supported-attribute-bits
-        buffer.putInt(0);
-        // supported-open-flags
-        buffer.putInt(SftpConstants.SSH_FXF_READ | SftpConstants.SSH_FXF_WRITE 
| SftpConstants.SSH_FXF_APPEND
-                | SftpConstants.SSH_FXF_CREAT | SftpConstants.SSH_FXF_TRUNC | 
SftpConstants.SSH_FXF_EXCL);
-        // TODO: supported-access-mask
-        buffer.putInt(0);
-        // max-read-size
-        buffer.putInt(0);
-        // supported extensions
-        buffer.putStringList(extras, false);
-
-        BufferUtils.updateLengthPlaceholder(buffer, lenPos);
-    }
-
-    /**
-     * Appends the &quot;supported2&quot; extension to the buffer. <B>Note:</B>
-     * if overriding this method make sure you either do not append anything
-     * or use the correct extension name
-     *
-     * @param buffer The {@link Buffer} to append to
-     * @param extras The extra extensions that are available and can be 
reported
-     * - may be {@code null}/empty
-     * @see SftpConstants#EXT_SUPPORTED
-     * @see <A 
HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-10";>DRAFT 
13 section 5.4</A>
-     */
-    protected void appendSupported2Extension(Buffer buffer, Collection<String> 
extras) {
-        buffer.putString(SftpConstants.EXT_SUPPORTED2);
-
-        int lenPos = buffer.wpos();
-        buffer.putInt(0); // length placeholder
-        // supported-attribute-mask
-        buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_SIZE | 
SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS
-                | SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME | 
SftpConstants.SSH_FILEXFER_ATTR_CREATETIME
-                | SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME | 
SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP
-                | SftpConstants.SSH_FILEXFER_ATTR_BITS);
-        // TODO: supported-attribute-bits
-        buffer.putInt(0);
-        // supported-open-flags
-        buffer.putInt(SftpConstants.SSH_FXF_ACCESS_DISPOSITION | 
SftpConstants.SSH_FXF_APPEND_DATA);
-        // TODO: supported-access-mask
-        buffer.putInt(0);
-        // max-read-size
-        buffer.putInt(0);
-        // supported-open-block-vector
-        buffer.putShort(0);
-        // supported-block-vector
-        buffer.putShort(0);
-        // attrib-extension-count + attributes name
-        buffer.putStringList(Collections.<String>emptyList(), true);
-        // extension-count + supported extensions
-        buffer.putStringList(extras, true);
-
-        BufferUtils.updateLengthPlaceholder(buffer, lenPos);
-    }
-
-    protected void sendHandle(Buffer buffer, int id, String handle) throws 
IOException {
-        buffer.putByte((byte) SftpConstants.SSH_FXP_HANDLE);
-        buffer.putInt(id);
-        buffer.putString(handle);
-        send(buffer);
-    }
-
-    protected void sendAttrs(Buffer buffer, int id, Map<String, ?> attributes) 
throws IOException {
-        buffer.putByte((byte) SftpConstants.SSH_FXP_ATTRS);
-        buffer.putInt(id);
-        writeAttrs(buffer, attributes);
-        send(buffer);
-    }
-
-    protected void sendLink(Buffer buffer, int id, String link) throws 
IOException {
-        //in case we are running on Windows
-        String unixPath = link.replace(File.separatorChar, '/');
-
-        buffer.putByte((byte) SftpConstants.SSH_FXP_NAME);
-        buffer.putInt(id);
-        buffer.putInt(1);   // one response
-        buffer.putString(unixPath);
-
-        /*
-         * As per the spec 
(https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.10):
-         *
-         *      The server will respond with a SSH_FXP_NAME packet containing 
only
-         *      one name and a dummy attributes value.
-         */
-        Map<String, Object> attrs = Collections.emptyMap();
-        int version = getVersion();
-        if (version == SftpConstants.SFTP_V3) {
-            buffer.putString(SftpHelper.getLongName(unixPath, attrs));
-        }
-
-        writeAttrs(buffer, attrs);
-        SftpHelper.indicateEndOfNamesList(buffer, getVersion(), 
getServerSession());
-        send(buffer);
-    }
-
-    protected void sendPath(Buffer buffer, int id, Path f, Map<String, ?> 
attrs) throws IOException {
-        buffer.putByte((byte) SftpConstants.SSH_FXP_NAME);
-        buffer.putInt(id);
-        buffer.putInt(1);   // one reply
-
-        String originalPath = f.toString();
-        //in case we are running on Windows
-        String unixPath = originalPath.replace(File.separatorChar, '/');
-        buffer.putString(unixPath);
-
-        int version = getVersion();
-        if (version == SftpConstants.SFTP_V3) {
-            buffer.putString(getLongName(f, getShortName(f), attrs));
-        }
-
-        writeAttrs(buffer, attrs);
-        SftpHelper.indicateEndOfNamesList(buffer, getVersion(), 
getServerSession());
-        send(buffer);
-    }
-
-    /**
-     * @param id      Request id
-     * @param handle  The (opaque) handle assigned to this directory
-     * @param dir     The {@link DirectoryHandle}
-     * @param buffer  The {@link Buffer} to write the results
-     * @param maxSize Max. buffer size
-     * @param options The {@link LinkOption}-s to use when querying the 
directory contents
-     * @return Number of written entries
-     * @throws IOException If failed to generate an entry
-     */
-    protected int doReadDir(
-            int id, String handle, DirectoryHandle dir, Buffer buffer, int 
maxSize, LinkOption... options) throws IOException {
-        int nb = 0;
-        Map<String, Path> entries = new TreeMap<>(Comparator.naturalOrder());
-        while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && 
(buffer.wpos() < maxSize)) {
-            if (dir.isSendDot()) {
-                writeDirEntry(id, dir, entries, buffer, nb, dir.getFile(), 
".", options);
-                dir.markDotSent();    // do not send it again
-            } else if (dir.isSendDotDot()) {
-                Path dirPath = dir.getFile();
-                writeDirEntry(id, dir, entries, buffer, nb, 
dirPath.getParent(), "..", options);
-                dir.markDotDotSent(); // do not send it again
-            } else {
-                Path f = dir.next();
-                writeDirEntry(id, dir, entries, buffer, nb, f, 
getShortName(f), options);
-            }
-
-            nb++;
-        }
-
-        SftpEventListener listener = getSftpEventListenerProxy();
-        listener.read(getServerSession(), handle, dir, entries);
-        return nb;
-    }
-
-    /**
-     * @param id        Request id
-     * @param dir       The {@link DirectoryHandle}
-     * @param entries   An in / out {@link Map} for updating the written entry 
-
-     *                  key = short name, value = entry {@link Path}
-     * @param buffer    The {@link Buffer} to write the results
-     * @param index     Zero-based index of the entry to be written
-     * @param f         The entry {@link Path}
-     * @param shortName The entry short name
-     * @param options   The {@link LinkOption}s to use for querying the 
entry-s attributes
-     * @throws IOException If failed to generate the entry data
-     */
-    protected void writeDirEntry(
-            int id, DirectoryHandle dir, Map<String, Path> entries, Buffer 
buffer, int index, Path f, String shortName, LinkOption... options)
-                    throws IOException {
-        Map<String, ?> attrs = resolveFileAttributes(f, 
SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
-        entries.put(shortName, f);
-
-        buffer.putString(shortName);
-        int version = getVersion();
-        if (version == SftpConstants.SFTP_V3) {
-            String longName = getLongName(f, shortName, options);
-            buffer.putString(longName);
-            if (log.isTraceEnabled()) {
-                log.trace("writeDirEntry(" + getServerSession() + ") id=" + id 
+ ")[" + index + "] - "
-                        + shortName + " [" + longName + "]: " + attrs);
-            }
-        } else {
-            if (log.isTraceEnabled()) {
-                log.trace("writeDirEntry(" + getServerSession() + "(id=" + id 
+ ")[" + index + "] - "
-                        + shortName + ": " + attrs);
-            }
-        }
-
-        writeAttrs(buffer, attrs);
-    }
-
-    protected String getLongName(Path f, String shortName, LinkOption... 
options) throws IOException {
-        return getLongName(f, shortName, true, options);
-    }
-
-    protected String getLongName(Path f, String shortName, boolean sendAttrs, 
LinkOption... options) throws IOException {
-        Map<String, Object> attributes;
-        if (sendAttrs) {
-            attributes = getAttributes(f, options);
-        } else {
-            attributes = Collections.emptyMap();
-        }
-        return getLongName(f, shortName, attributes);
-    }
-
-    protected String getLongName(Path f, String shortName, Map<String, ?> 
attributes) throws IOException {
-        return SftpHelper.getLongName(shortName, attributes);
-    }
-
-    protected String getShortName(Path f) throws IOException {
-        Path nrm = normalize(f);
-        int  count = nrm.getNameCount();
-        /*
-         * According to the javadoc:
-         *
-         *      The number of elements in the path, or 0 if this path only
-         *      represents a root component
-         */
-        if (OsUtils.isUNIX()) {
-            Path name = f.getFileName();
-            if (name == null) {
-                Path p = resolveFile(".");
-                name = p.getFileName();
-            }
-
-            if (name == null) {
-                if (count > 0) {
-                    name = nrm.getFileName();
-                }
-            }
-
-            if (name != null) {
-                return name.toString();
-            } else {
-                return nrm.toString();
-            }
-        } else {    // need special handling for Windows root drives
-            if (count > 0) {
-                Path name = nrm.getFileName();
-                return name.toString();
-            } else {
-                return nrm.toString().replace(File.separatorChar, '/');
-            }
-        }
-    }
-
-    protected NavigableMap<String, Object> resolveFileAttributes(Path file, 
int flags, LinkOption... options) throws IOException {
-        Boolean status = IoUtils.checkFileExists(file, options);
-        if (status == null) {
-            return handleUnknownStatusFileAttributes(file, flags, options);
-        } else if (!status) {
-            throw new NoSuchFileException(file.toString(), file.toString(), 
"Attributes N/A for target");
-        } else {
-            return getAttributes(file, flags, options);
-        }
-    }
-
-    protected void writeAttrs(Buffer buffer, Map<String, ?> attributes) throws 
IOException {
-        SftpHelper.writeAttrs(buffer, getVersion(), attributes);
-    }
-
-    protected NavigableMap<String, Object> getAttributes(Path file, 
LinkOption... options) throws IOException {
-        return getAttributes(file, SftpConstants.SSH_FILEXFER_ATTR_ALL, 
options);
-    }
-
-    protected NavigableMap<String, Object> 
handleUnknownStatusFileAttributes(Path file, int flags, LinkOption... options) 
throws IOException {
-        UnsupportedAttributePolicy unsupportedAttributePolicy = 
getUnsupportedAttributePolicy();
-        switch (unsupportedAttributePolicy) {
-            case Ignore:
-                break;
-            case ThrowException:
-                throw new AccessDeniedException(file.toString(), 
file.toString(), "Cannot determine existence for attributes of target");
-            case Warn:
-                log.warn("handleUnknownStatusFileAttributes(" + 
getServerSession() + ")[" + file + "] cannot determine existence");
-                break;
-            default:
-                log.warn("handleUnknownStatusFileAttributes(" + 
getServerSession() + ")[" + file + "] unknown policy: " + 
unsupportedAttributePolicy);
-        }
-
-        return getAttributes(file, flags, options);
-    }
-
-    /**
-     * @param file The {@link Path} location for the required attributes
-     * @param flags A mask of the original required attributes - ignored by the
-     * default implementation
-     * @param options The {@link LinkOption}s to use in order to access the 
file
-     * if necessary
-     * @return A {@link Map} of the retrieved attributes
-     * @throws IOException If failed to access the file
-     * @see #resolveMissingFileAttributes(Path, int, Map, LinkOption...)
-     */
-    protected NavigableMap<String, Object> getAttributes(Path file, int flags, 
LinkOption... options) throws IOException {
-        FileSystem fs = file.getFileSystem();
-        Collection<String> supportedViews = fs.supportedFileAttributeViews();
-        NavigableMap<String, Object> attrs = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        Collection<String> views;
-
-        if (GenericUtils.isEmpty(supportedViews)) {
-            views = Collections.emptyList();
-        } else if (supportedViews.contains("unix")) {
-            

<TRUNCATED>

Reply via email to