Skip to content

Commit

Permalink
Implemented MODE Z for ftp client
Browse files Browse the repository at this point in the history
  • Loading branch information
Georg Voss committed Feb 23, 2024
1 parent a5fc4e8 commit 5446cc5
Show file tree
Hide file tree
Showing 4 changed files with 480 additions and 4 deletions.
9 changes: 8 additions & 1 deletion src/main/java/org/apache/commons/net/ftp/FTP.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ public class FTP extends SocketClient {
*/
public static final int COMPRESSED_TRANSFER_MODE = 12;

/**
* A constant used to indicate a file is to be transferred as FTP compressed
* data with MODE Z (zlib). All constants ending in <code>TRANSFER_MODE</code> are used to indicate
* file transfer modes.
*/
public static final int COMPRESSED_MODE_Z_TRANSFER_MODE = 13;

// We have to ensure that the protocol communication is in ASCII,
// but we use ISO-8859-1 just in case 8-bit characters cross
// the wire.
Expand All @@ -163,7 +170,7 @@ public class FTP extends SocketClient {
/** Length of the FTP reply code (3 alphanumerics) */
public static final int REPLY_CODE_LEN = 3;

private static final String modes = "AEILNTCFRPSBC";
private static final String modes = "AEILNTCFRPSBCZ";
protected int _replyCode;
protected ArrayList<String> _replyLines;
protected boolean _newReplyString;
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/org/apache/commons/net/ftp/FTPClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,6 @@ static String parsePathname(final String reply) {
private int fileFormat;
@SuppressWarnings("unused") // field is written, but currently not read
private int fileStructure;
@SuppressWarnings("unused") // field is written, but currently not read
private int fileTransferMode;

private boolean remoteVerificationEnabled;
Expand Down Expand Up @@ -713,7 +712,8 @@ protected Socket _openDataConnection_(final String command, final String arg) th
if (soTimeoutMillis >= 0) {
server.setSoTimeout(soTimeoutMillis);
}
socket = server.accept();

socket = wrapSocketIfModeZisEnabled(server.accept());

// Ensure the timeout is set before any commands are issued on the new socket
if (soTimeoutMillis >= 0) {
Expand Down Expand Up @@ -749,7 +749,8 @@ protected Socket _openDataConnection_(final String command, final String arg) th
_parsePassiveModeReply(_replyLines.get(0));
}

socket = _socketFactory_.createSocket();
socket = wrapSocketIfModeZisEnabled(_socketFactory_.createSocket());

if (receiveDataSocketBufferSize > 0) {
socket.setReceiveBufferSize(receiveDataSocketBufferSize);
}
Expand Down Expand Up @@ -793,6 +794,14 @@ protected Socket _openDataConnection_(final String command, final String arg) th
return socket;
}

private Socket wrapSocketIfModeZisEnabled(final Socket plainSocket) {
if (fileTransferMode == COMPRESSED_MODE_Z_TRANSFER_MODE) {
return ModeZSocket.wrap(plainSocket);
} else {
return plainSocket;
}
}

protected void _parseExtendedPassiveModeReply(String reply) throws MalformedServerReplyException {
reply = reply.substring(reply.indexOf('(') + 1, reply.indexOf(')')).trim();

Expand Down
252 changes: 252 additions & 0 deletions src/main/java/org/apache/commons/net/ftp/ModeZSocket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* 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.commons.net.ftp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

/**
* Wrapper class for FTP data channel sockets when using MODE Z. All methods
* except of {@link #getInputStream()} and {@link #getOutputStream()} are
* calling the delegate methods directly.
*/
public class ModeZSocket extends Socket {

private final Socket delegate;

private ModeZSocket(final Socket delegate) {
this.delegate = delegate;
}

static Socket wrap(final Socket plain) {
return new ModeZSocket(plain);
}

@Override
public void connect(final SocketAddress endpoint) throws IOException {
delegate.connect(endpoint);
}

@Override
public void connect(final SocketAddress endpoint, final int timeout) throws IOException {
delegate.connect(endpoint, timeout);
}

@Override
public void bind(final SocketAddress bindpoint) throws IOException {
delegate.bind(bindpoint);
}

@Override
public InetAddress getInetAddress() {
return delegate.getInetAddress();
}

@Override
public InetAddress getLocalAddress() {
return delegate.getLocalAddress();
}

@Override
public int getPort() {
return delegate.getPort();
}

@Override
public int getLocalPort() {
return delegate.getLocalPort();
}

@Override
public SocketAddress getRemoteSocketAddress() {
return delegate.getRemoteSocketAddress();
}

@Override
public SocketAddress getLocalSocketAddress() {
return delegate.getLocalSocketAddress();
}

@Override
public SocketChannel getChannel() {
return delegate.getChannel();
}

@Override
public InputStream getInputStream() throws IOException {
return new InflaterInputStream(delegate.getInputStream());
}

@Override
public OutputStream getOutputStream() throws IOException {
return new DeflaterOutputStream(delegate.getOutputStream());
}

@Override
public void setTcpNoDelay(final boolean on) throws SocketException {
delegate.setTcpNoDelay(on);
}

@Override
public boolean getTcpNoDelay() throws SocketException {
return delegate.getTcpNoDelay();
}

@Override
public void setSoLinger(final boolean on, final int linger) throws SocketException {
delegate.setSoLinger(on, linger);
}

@Override
public int getSoLinger() throws SocketException {
return delegate.getSoLinger();
}

@Override
public void sendUrgentData(final int data) throws IOException {
delegate.sendUrgentData(data);
}

@Override
public void setOOBInline(final boolean on) throws SocketException {
delegate.setOOBInline(on);
}

@Override
public boolean getOOBInline() throws SocketException {
return delegate.getOOBInline();
}

@Override
public synchronized void setSoTimeout(final int timeout) throws SocketException {
delegate.setSoTimeout(timeout);
}

@Override
public synchronized int getSoTimeout() throws SocketException {
return delegate.getSoTimeout();
}

@Override
public synchronized void setSendBufferSize(final int size) throws SocketException {
delegate.setSendBufferSize(size);
}

@Override
public synchronized int getSendBufferSize() throws SocketException {
return delegate.getSendBufferSize();
}

@Override
public synchronized void setReceiveBufferSize(final int size) throws SocketException {
delegate.setReceiveBufferSize(size);
}

@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return delegate.getReceiveBufferSize();
}

@Override
public void setKeepAlive(final boolean on) throws SocketException {
delegate.setKeepAlive(on);
}

@Override
public boolean getKeepAlive() throws SocketException {
return delegate.getKeepAlive();
}

@Override
public void setTrafficClass(final int tc) throws SocketException {
delegate.setTrafficClass(tc);
}

@Override
public int getTrafficClass() throws SocketException {
return delegate.getTrafficClass();
}

@Override
public void setReuseAddress(final boolean on) throws SocketException {
delegate.setReuseAddress(on);
}

@Override
public boolean getReuseAddress() throws SocketException {
return delegate.getReuseAddress();
}

@Override
public synchronized void close() throws IOException {
delegate.close();
}

@Override
public void shutdownInput() throws IOException {
delegate.shutdownInput();
}

@Override
public void shutdownOutput() throws IOException {
delegate.shutdownOutput();
}

@Override
public String toString() {
return delegate.toString();
}

@Override
public boolean isConnected() {
return delegate.isConnected();
}

@Override
public boolean isBound() {
return delegate.isBound();
}

@Override
public boolean isClosed() {
return delegate.isClosed();
}

@Override
public boolean isInputShutdown() {
return delegate.isInputShutdown();
}

@Override
public boolean isOutputShutdown() {
return delegate.isOutputShutdown();
}

@Override
public void setPerformancePreferences(final int connectionTime, final int latency, final int bandwidth) {
delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
}
}

0 comments on commit 5446cc5

Please sign in to comment.