From 063284837c8c366e5502b1b0264b8eb807b61732 Mon Sep 17 00:00:00 2001 From: Joe Robinson Date: Wed, 27 Oct 2010 14:21:09 +0100 Subject: Basic upload functionality to predifined location, with basic file browser --- src/com/lc8n/blauploader/FileBrowser.java | 198 ++ src/com/lc8n/blauploader/FileUpload.java | 76 + src/com/lc8n/blauploader/ProgressInputStream.java | 97 + .../apache/commons/net/DatagramSocketClient.java | 275 +++ .../apache/commons/net/DatagramSocketFactory.java | 67 + .../commons/net/DefaultDatagramSocketFactory.java | 75 + .../apache/commons/net/DefaultSocketFactory.java | 165 ++ .../commons/net/MalformedServerReplyException.java | 55 + .../apache/commons/net/PrintCommandListener.java | 54 + .../apache/commons/net/ProtocolCommandEvent.java | 146 ++ .../commons/net/ProtocolCommandListener.java | 59 + .../apache/commons/net/ProtocolCommandSupport.java | 134 ++ src/org/apache/commons/net/SocketClient.java | 586 +++++ src/org/apache/commons/net/ftp/Configurable.java | 35 + src/org/apache/commons/net/ftp/FTP.java | 1513 ++++++++++++ src/org/apache/commons/net/ftp/FTPClient.java | 2447 ++++++++++++++++++++ .../apache/commons/net/ftp/FTPClientConfig.java | 580 +++++ src/org/apache/commons/net/ftp/FTPCommand.java | 131 ++ .../net/ftp/FTPConnectionClosedException.java | 55 + src/org/apache/commons/net/ftp/FTPFile.java | 392 ++++ .../apache/commons/net/ftp/FTPFileEntryParser.java | 152 ++ .../commons/net/ftp/FTPFileEntryParserImpl.java | 85 + .../apache/commons/net/ftp/FTPListParseEngine.java | 290 +++ src/org/apache/commons/net/ftp/FTPReply.java | 240 ++ src/org/apache/commons/net/ftp/FTPSClient.java | 533 +++++ src/org/apache/commons/net/ftp/FTPSCommand.java | 50 + .../apache/commons/net/ftp/FTPSSocketFactory.java | 82 + .../apache/commons/net/ftp/FTPSTrustManager.java | 54 + .../net/ftp/parser/CompositeFileEntryParser.java | 72 + .../parser/ConfigurableFTPFileEntryParserImpl.java | 116 + .../parser/DefaultFTPFileEntryParserFactory.java | 247 ++ .../ftp/parser/EnterpriseUnixFTPEntryParser.java | 166 ++ .../net/ftp/parser/FTPFileEntryParserFactory.java | 68 + .../commons/net/ftp/parser/FTPTimestampParser.java | 52 + .../net/ftp/parser/FTPTimestampParserImpl.java | 301 +++ .../commons/net/ftp/parser/MVSFTPEntryParser.java | 495 ++++ .../commons/net/ftp/parser/NTFTPEntryParser.java | 147 ++ .../net/ftp/parser/NetwareFTPEntryParser.java | 176 ++ .../commons/net/ftp/parser/OS2FTPEntryParser.java | 147 ++ .../net/ftp/parser/OS400FTPEntryParser.java | 158 ++ .../ftp/parser/ParserInitializationException.java | 65 + .../ftp/parser/RegexFTPFileEntryParserImpl.java | 155 ++ .../commons/net/ftp/parser/UnixFTPEntryParser.java | 295 +++ .../commons/net/ftp/parser/VMSFTPEntryParser.java | 288 +++ .../ftp/parser/VMSVersioningFTPEntryParser.java | 183 ++ .../apache/commons/net/io/CopyStreamAdapter.java | 122 + src/org/apache/commons/net/io/CopyStreamEvent.java | 98 + .../apache/commons/net/io/CopyStreamException.java | 71 + .../apache/commons/net/io/CopyStreamListener.java | 72 + .../commons/net/io/DotTerminatedMessageReader.java | 280 +++ .../commons/net/io/DotTerminatedMessageWriter.java | 215 ++ .../commons/net/io/FromNetASCIIInputStream.java | 205 ++ .../commons/net/io/FromNetASCIIOutputStream.java | 174 ++ .../apache/commons/net/io/SocketInputStream.java | 69 + .../apache/commons/net/io/SocketOutputStream.java | 89 + .../commons/net/io/ToNetASCIIInputStream.java | 181 ++ .../commons/net/io/ToNetASCIIOutputStream.java | 119 + src/org/apache/commons/net/io/Util.java | 334 +++ src/org/apache/commons/net/util/ListenerList.java | 63 + src/org/apache/commons/net/util/SubnetUtils.java | 211 ++ 60 files changed, 14060 insertions(+) create mode 100644 src/com/lc8n/blauploader/FileBrowser.java create mode 100644 src/com/lc8n/blauploader/FileUpload.java create mode 100644 src/com/lc8n/blauploader/ProgressInputStream.java create mode 100644 src/org/apache/commons/net/DatagramSocketClient.java create mode 100644 src/org/apache/commons/net/DatagramSocketFactory.java create mode 100644 src/org/apache/commons/net/DefaultDatagramSocketFactory.java create mode 100644 src/org/apache/commons/net/DefaultSocketFactory.java create mode 100644 src/org/apache/commons/net/MalformedServerReplyException.java create mode 100644 src/org/apache/commons/net/PrintCommandListener.java create mode 100644 src/org/apache/commons/net/ProtocolCommandEvent.java create mode 100644 src/org/apache/commons/net/ProtocolCommandListener.java create mode 100644 src/org/apache/commons/net/ProtocolCommandSupport.java create mode 100644 src/org/apache/commons/net/SocketClient.java create mode 100644 src/org/apache/commons/net/ftp/Configurable.java create mode 100644 src/org/apache/commons/net/ftp/FTP.java create mode 100644 src/org/apache/commons/net/ftp/FTPClient.java create mode 100644 src/org/apache/commons/net/ftp/FTPClientConfig.java create mode 100644 src/org/apache/commons/net/ftp/FTPCommand.java create mode 100644 src/org/apache/commons/net/ftp/FTPConnectionClosedException.java create mode 100644 src/org/apache/commons/net/ftp/FTPFile.java create mode 100644 src/org/apache/commons/net/ftp/FTPFileEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java create mode 100644 src/org/apache/commons/net/ftp/FTPListParseEngine.java create mode 100644 src/org/apache/commons/net/ftp/FTPReply.java create mode 100644 src/org/apache/commons/net/ftp/FTPSClient.java create mode 100644 src/org/apache/commons/net/ftp/FTPSCommand.java create mode 100644 src/org/apache/commons/net/ftp/FTPSSocketFactory.java create mode 100644 src/org/apache/commons/net/ftp/FTPSTrustManager.java create mode 100644 src/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java create mode 100644 src/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java create mode 100644 src/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java create mode 100644 src/org/apache/commons/net/ftp/parser/FTPTimestampParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java create mode 100644 src/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/ParserInitializationException.java create mode 100644 src/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java create mode 100644 src/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java create mode 100644 src/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java create mode 100644 src/org/apache/commons/net/io/CopyStreamAdapter.java create mode 100644 src/org/apache/commons/net/io/CopyStreamEvent.java create mode 100644 src/org/apache/commons/net/io/CopyStreamException.java create mode 100644 src/org/apache/commons/net/io/CopyStreamListener.java create mode 100644 src/org/apache/commons/net/io/DotTerminatedMessageReader.java create mode 100644 src/org/apache/commons/net/io/DotTerminatedMessageWriter.java create mode 100644 src/org/apache/commons/net/io/FromNetASCIIInputStream.java create mode 100644 src/org/apache/commons/net/io/FromNetASCIIOutputStream.java create mode 100644 src/org/apache/commons/net/io/SocketInputStream.java create mode 100644 src/org/apache/commons/net/io/SocketOutputStream.java create mode 100644 src/org/apache/commons/net/io/ToNetASCIIInputStream.java create mode 100644 src/org/apache/commons/net/io/ToNetASCIIOutputStream.java create mode 100644 src/org/apache/commons/net/io/Util.java create mode 100644 src/org/apache/commons/net/util/ListenerList.java create mode 100644 src/org/apache/commons/net/util/SubnetUtils.java (limited to 'src') diff --git a/src/com/lc8n/blauploader/FileBrowser.java b/src/com/lc8n/blauploader/FileBrowser.java new file mode 100644 index 0000000..6546327 --- /dev/null +++ b/src/com/lc8n/blauploader/FileBrowser.java @@ -0,0 +1,198 @@ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + Copyright 2010 Joe Robinson +*/ + +package com.lc8n.blauploader; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +public class FileBrowser extends ListActivity { + /** Called when the activity is first created. */ + + private List directoryEntries = new ArrayList(); + private File currentDirectory = new File("/"); + private float fileSize = 0; + private Handler pbHandle = null; + private ProgressDialog progressDialog; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + directoryEntries.clear(); + + File[] files = currentDirectory.listFiles(); + directoryEntries.add("Directory:"+currentDirectory.getAbsolutePath()); + directoryEntries.add("Up One Level"); + if (files != null) + { + for (File file: files) + { + directoryEntries.add(file.getPath()); +// System.out.println(file.getPath()); + } + } + ArrayAdapter directoryList = new ArrayAdapter(this, R.layout.filerow, this.directoryEntries); + + this.setListAdapter(directoryList); + + progressDialog = new ProgressDialog(this); + + + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setMessage("Blauploading..."); + progressDialog.setCancelable(true); + progressDialog.setProgress(0); + + +// +// + + + + pbHandle = new Handler(){ + + @Override + public void handleMessage(Message msg) { + + /* get the value from the Message */ + + long progress = msg.getData().getLong("progress_update"); + System.out.println(progress+"/"+fileSize); + if(progress>(fileSize-10240)) + { + progressDialog.dismiss(); + } + float percent = (progress/fileSize)*100; + Integer intProgress = Math.round(percent); + if(intProgress==100) + { + progressDialog.dismiss(); + } + else + { + progressDialog.show(); + progressDialog.setProgress(intProgress); + } + } + }; + + } + + public void browseTo(File dir) + { + directoryEntries.clear(); + + File[] files = dir.listFiles(); + directoryEntries.add("Current Directory:"+currentDirectory.getAbsolutePath()); + directoryEntries.add("Up One Level"); + +// System.out.println(files.length); + if(files != null) + { + for (File file: files) + { + directoryEntries.add(file.getPath()); + System.out.println(file.getPath()); + } + } + ArrayAdapter directoryList = new ArrayAdapter(this, R.layout.filerow, this.directoryEntries); + + this.setListAdapter(directoryList); + } + + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + + String fileString = directoryEntries.get(position); + + + if(fileString.contains("Directory:")) + { + browseTo(currentDirectory); + } + else if (fileString.equals("Up One Level")) + { + System.out.println(currentDirectory.getParentFile().getPath()); + if (currentDirectory.getParent() != null) + { + currentDirectory = currentDirectory.getParentFile(); + browseTo(currentDirectory); + } + } + else + { + + + File chosenFile = new File(fileString); + if (chosenFile.isDirectory()) + { + currentDirectory = chosenFile; + browseTo(currentDirectory); + } + else + { + + //upload it + try { + + fileSize = chosenFile.length(); + + + + + + + FileUpload fu = new FileUpload(chosenFile,pbHandle); + Thread thread = new Thread(fu); + + thread.start(); + + + + + } catch (Exception e) { + // TODO Auto-generated catch block + System.err.println(e.getMessage()); + e.printStackTrace(); + } + + } + + } + + + + } + + + +} \ No newline at end of file diff --git a/src/com/lc8n/blauploader/FileUpload.java b/src/com/lc8n/blauploader/FileUpload.java new file mode 100644 index 0000000..b81ec2d --- /dev/null +++ b/src/com/lc8n/blauploader/FileUpload.java @@ -0,0 +1,76 @@ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + Copyright 2010 Joe Robinson +*/ + +package com.lc8n.blauploader; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.SocketException; + +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; + +import android.os.Handler; + +public class FileUpload implements Runnable{ + + private File file; + private Handler pbHandle; + public FileUpload(File file, Handler pbHandle) + { + this.file = file; + this.pbHandle = pbHandle; + } + + public void uploadFile(File file, Handler pbHandle) throws SocketException, IOException + { + + + FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis); + ProgressInputStream pis = new ProgressInputStream(bis,pbHandle); + String fileName = file.getName(); + FTPClient ftpClient = new FTPClient(); + + ftpClient.connect("tghost.co.uk"); + ftpClient.login("blaupload", "lemons"); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + ftpClient.enterLocalPassiveMode(); + ftpClient.changeWorkingDirectory("/"); + ftpClient.storeFile(fileName, pis); + bis.close(); + pis.close(); + ftpClient.logout(); + ftpClient.disconnect(); + + } + public void run() { + try { + uploadFile(file, pbHandle); + } catch (Exception e) { + + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + +} diff --git a/src/com/lc8n/blauploader/ProgressInputStream.java b/src/com/lc8n/blauploader/ProgressInputStream.java new file mode 100644 index 0000000..bda5980 --- /dev/null +++ b/src/com/lc8n/blauploader/ProgressInputStream.java @@ -0,0 +1,97 @@ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + Copyright 2010 Joe Robinson +*/ + +package com.lc8n.blauploader; + +import java.io.IOException; +import java.io.InputStream; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; + +public class ProgressInputStream extends InputStream { + + /* Key to retrieve progress value from message bundle passed to handler */ + public static final String PROGRESS_UPDATE = "progress_update"; + + private static final int TEN_KILOBYTES = 1024 * 10; + + private InputStream inputStream; + private Handler handler; + + private long progress; + private long lastUpdate; + + private boolean closed; + + public ProgressInputStream(InputStream inputStream, Handler handler) { + this.inputStream = inputStream; + this.handler = handler; + + this.progress = 0; + this.lastUpdate = 0; + + this.closed = false; + } + + @Override + public int read() throws IOException { + int count = inputStream.read(); + return incrementCounterAndUpdateDisplay(count); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int count = inputStream.read(b, off, len); + return incrementCounterAndUpdateDisplay(count); + } + + @Override + public void close() throws IOException { + super.close(); + if (closed) + throw new IOException("already closed"); + closed = true; + } + + private int incrementCounterAndUpdateDisplay(int count) { + if (count > 0) + progress += count; + lastUpdate = maybeUpdateDisplay(progress, lastUpdate); + return count; + } + + private long maybeUpdateDisplay(long progress, long lastUpdate) { + if (progress - lastUpdate > TEN_KILOBYTES) { + lastUpdate = progress; + sendLong(PROGRESS_UPDATE, progress); + } + return lastUpdate; + } + + public void sendLong(String key, long value) { + Bundle data = new Bundle(); + data.putLong(key, value); + + Message message = Message.obtain(); + message.setData(data); + handler.sendMessage(message); + } +} \ No newline at end of file diff --git a/src/org/apache/commons/net/DatagramSocketClient.java b/src/org/apache/commons/net/DatagramSocketClient.java new file mode 100644 index 0000000..9199704 --- /dev/null +++ b/src/org/apache/commons/net/DatagramSocketClient.java @@ -0,0 +1,275 @@ +/* + * 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; + +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; + +/*** + * The DatagramSocketClient provides the basic operations that are required + * of client objects accessing datagram sockets. It is meant to be + * subclassed to avoid having to rewrite the same code over and over again + * to open a socket, close a socket, set timeouts, etc. Of special note + * is the {@link #setDatagramSocketFactory setDatagramSocketFactory } + * method, which allows you to control the type of DatagramSocket the + * DatagramSocketClient creates for network communications. This is + * especially useful for adding things like proxy support as well as better + * support for applets. For + * example, you could create a + * {@link org.apache.commons.net.DatagramSocketFactory} + * that + * requests browser security capabilities before creating a socket. + * All classes derived from DatagramSocketClient should use the + * {@link #_socketFactory_ _socketFactory_ } member variable to + * create DatagramSocket instances rather than instantiating + * them by directly invoking a constructor. By honoring this contract + * you guarantee that a user will always be able to provide his own + * Socket implementations by substituting his own SocketFactory. + *

+ *

+ * @author Daniel F. Savarese + * @see DatagramSocketFactory + ***/ + +public abstract class DatagramSocketClient +{ + /*** + * The default DatagramSocketFactory shared by all DatagramSocketClient + * instances. + ***/ + private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY = + new DefaultDatagramSocketFactory(); + + /*** The timeout to use after opening a socket. ***/ + protected int _timeout_; + + /*** The datagram socket used for the connection. ***/ + protected DatagramSocket _socket_; + + /*** + * A status variable indicating if the client's socket is currently open. + ***/ + protected boolean _isOpen_; + + /*** The datagram socket's DatagramSocketFactory. ***/ + protected DatagramSocketFactory _socketFactory_; + + /*** + * Default constructor for DatagramSocketClient. Initializes + * _socket_ to null, _timeout_ to 0, and _isOpen_ to false. + ***/ + public DatagramSocketClient() + { + _socket_ = null; + _timeout_ = 0; + _isOpen_ = false; + _socketFactory_ = __DEFAULT_SOCKET_FACTORY; + } + + + /*** + * Opens a DatagramSocket on the local host at the first available port. + * Also sets the timeout on the socket to the default timeout set + * by {@link #setDefaultTimeout setDefaultTimeout() }. + *

+ * _isOpen_ is set to true after calling this method and _socket_ + * is set to the newly opened socket. + *

+ * @exception SocketException If the socket could not be opened or the + * timeout could not be set. + ***/ + public void open() throws SocketException + { + _socket_ = _socketFactory_.createDatagramSocket(); + _socket_.setSoTimeout(_timeout_); + _isOpen_ = true; + } + + + /*** + * Opens a DatagramSocket on the local host at a specified port. + * Also sets the timeout on the socket to the default timeout set + * by {@link #setDefaultTimeout setDefaultTimeout() }. + *

+ * _isOpen_ is set to true after calling this method and _socket_ + * is set to the newly opened socket. + *

+ * @param port The port to use for the socket. + * @exception SocketException If the socket could not be opened or the + * timeout could not be set. + ***/ + public void open(int port) throws SocketException + { + _socket_ = _socketFactory_.createDatagramSocket(port); + _socket_.setSoTimeout(_timeout_); + _isOpen_ = true; + } + + + /*** + * Opens a DatagramSocket at the specified address on the local host + * at a specified port. + * Also sets the timeout on the socket to the default timeout set + * by {@link #setDefaultTimeout setDefaultTimeout() }. + *

+ * _isOpen_ is set to true after calling this method and _socket_ + * is set to the newly opened socket. + *

+ * @param port The port to use for the socket. + * @param laddr The local address to use. + * @exception SocketException If the socket could not be opened or the + * timeout could not be set. + ***/ + public void open(int port, InetAddress laddr) throws SocketException + { + _socket_ = _socketFactory_.createDatagramSocket(port, laddr); + _socket_.setSoTimeout(_timeout_); + _isOpen_ = true; + } + + + + /*** + * Closes the DatagramSocket used for the connection. + * You should call this method after you've finished using the class + * instance and also before you call {@link #open open() } + * again. _isOpen_ is set to false and _socket_ is set to null. + * If you call this method when the client socket is not open, + * a NullPointerException is thrown. + ***/ + public void close() + { + _socket_.close(); + _socket_ = null; + _isOpen_ = false; + } + + + /*** + * Returns true if the client has a currently open socket. + *

+ * @return True if the client has a curerntly open socket, false otherwise. + ***/ + public boolean isOpen() + { + return _isOpen_; + } + + + /*** + * Set the default timeout in milliseconds to use when opening a socket. + * After a call to open, the timeout for the socket is set using this value. + * This method should be used prior to a call to {@link #open open()} + * and should not be confused with {@link #setSoTimeout setSoTimeout()} + * which operates on the currently open socket. _timeout_ contains + * the new timeout value. + *

+ * @param timeout The timeout in milliseconds to use for the datagram socket + * connection. + ***/ + public void setDefaultTimeout(int timeout) + { + _timeout_ = timeout; + } + + + /*** + * Returns the default timeout in milliseconds that is used when + * opening a socket. + *

+ * @return The default timeout in milliseconds that is used when + * opening a socket. + ***/ + public int getDefaultTimeout() + { + return _timeout_; + } + + + /*** + * Set the timeout in milliseconds of a currently open connection. + * Only call this method after a connection has been opened + * by {@link #open open()}. + *

+ * @param timeout The timeout in milliseconds to use for the currently + * open datagram socket connection. + ***/ + public void setSoTimeout(int timeout) throws SocketException + { + _socket_.setSoTimeout(timeout); + } + + + /*** + * Returns the timeout in milliseconds of the currently opened socket. + * If you call this method when the client socket is not open, + * a NullPointerException is thrown. + *

+ * @return The timeout in milliseconds of the currently opened socket. + ***/ + public int getSoTimeout() throws SocketException + { + return _socket_.getSoTimeout(); + } + + + /*** + * Returns the port number of the open socket on the local host used + * for the connection. If you call this method when the client socket + * is not open, a NullPointerException is thrown. + *

+ * @return The port number of the open socket on the local host used + * for the connection. + ***/ + public int getLocalPort() + { + return _socket_.getLocalPort(); + } + + + /*** + * Returns the local address to which the client's socket is bound. + * If you call this method when the client socket is not open, a + * NullPointerException is thrown. + *

+ * @return The local address to which the client's socket is bound. + ***/ + public InetAddress getLocalAddress() + { + return _socket_.getLocalAddress(); + } + + + /*** + * Sets the DatagramSocketFactory used by the DatagramSocketClient + * to open DatagramSockets. If the factory value is null, then a default + * factory is used (only do this to reset the factory after having + * previously altered it). + *

+ * @param factory The new DatagramSocketFactory the DatagramSocketClient + * should use. + ***/ + public void setDatagramSocketFactory(DatagramSocketFactory factory) + { + if (factory == null) + _socketFactory_ = __DEFAULT_SOCKET_FACTORY; + else + _socketFactory_ = factory; + } +} diff --git a/src/org/apache/commons/net/DatagramSocketFactory.java b/src/org/apache/commons/net/DatagramSocketFactory.java new file mode 100644 index 0000000..c152eb2 --- /dev/null +++ b/src/org/apache/commons/net/DatagramSocketFactory.java @@ -0,0 +1,67 @@ +/* + * 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; + +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; + +/*** + * The DatagramSocketFactory interface provides a means for the + * programmer to control the creation of datagram sockets and + * provide his own DatagramSocket implementations for use by all + * classes derived from + * {@link org.apache.commons.net.DatagramSocketClient} + * . + * This allows you to provide your own DatagramSocket implementations and + * to perform security checks or browser capability requests before + * creating a DatagramSocket. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public interface DatagramSocketFactory +{ + + /*** + * Creates a DatagramSocket on the local host at the first available port. + *

+ * @exception SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket() throws SocketException; + + /*** + * Creates a DatagramSocket on the local host at a specified port. + *

+ * @param port The port to use for the socket. + * @exception SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket(int port) throws SocketException; + + /*** + * Creates a DatagramSocket at the specified address on the local host + * at a specified port. + *

+ * @param port The port to use for the socket. + * @param laddr The local address to use. + * @exception SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket(int port, InetAddress laddr) + throws SocketException; +} diff --git a/src/org/apache/commons/net/DefaultDatagramSocketFactory.java b/src/org/apache/commons/net/DefaultDatagramSocketFactory.java new file mode 100644 index 0000000..3983fcb --- /dev/null +++ b/src/org/apache/commons/net/DefaultDatagramSocketFactory.java @@ -0,0 +1,75 @@ +/* + * 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; + +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; + +/*** + * DefaultDatagramSocketFactory implements the DatagramSocketFactory + * interface by simply wrapping the java.net.DatagramSocket + * constructors. It is the default DatagramSocketFactory used by + * {@link org.apache.commons.net.DatagramSocketClient} + * implementations. + *

+ *

+ * @author Daniel F. Savarese + * @see DatagramSocketFactory + * @see DatagramSocketClient + * @see DatagramSocketClient#setDatagramSocketFactory + ***/ + +public class DefaultDatagramSocketFactory implements DatagramSocketFactory +{ + + /*** + * Creates a DatagramSocket on the local host at the first available port. + *

+ * @exception SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket() throws SocketException + { + return new DatagramSocket(); + } + + /*** + * Creates a DatagramSocket on the local host at a specified port. + *

+ * @param port The port to use for the socket. + * @exception SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket(int port) throws SocketException + { + return new DatagramSocket(port); + } + + /*** + * Creates a DatagramSocket at the specified address on the local host + * at a specified port. + *

+ * @param port The port to use for the socket. + * @param laddr The local address to use. + * @exception SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket(int port, InetAddress laddr) + throws SocketException + { + return new DatagramSocket(port, laddr); + } +} diff --git a/src/org/apache/commons/net/DefaultSocketFactory.java b/src/org/apache/commons/net/DefaultSocketFactory.java new file mode 100644 index 0000000..e809f84 --- /dev/null +++ b/src/org/apache/commons/net/DefaultSocketFactory.java @@ -0,0 +1,165 @@ +/* + * 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; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import javax.net.SocketFactory; + +/*** + * DefaultSocketFactory implements the SocketFactory interface by + * simply wrapping the java.net.Socket and java.net.ServerSocket + * constructors. It is the default SocketFactory used by + * {@link org.apache.commons.net.SocketClient} + * implementations. + *

+ *

+ * @author Daniel F. Savarese + * @see SocketFactory + * @see SocketClient + * @see SocketClient#setSocketFactory + ***/ + +public class DefaultSocketFactory extends SocketFactory +{ + + /*** + * Creates a Socket connected to the given host and port. + *

+ * @param host The hostname to connect to. + * @param port The port to connect to. + * @return A Socket connected to the given host and port. + * @exception UnknownHostException If the hostname cannot be resolved. + * @exception IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(String host, int port) + throws UnknownHostException, IOException + { + return new Socket(host, port); + } + + /*** + * Creates a Socket connected to the given host and port. + *

+ * @param address The address of the host to connect to. + * @param port The port to connect to. + * @return A Socket connected to the given host and port. + * @exception IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(InetAddress address, int port) + throws IOException + { + return new Socket(address, port); + } + + /*** + * Creates a Socket connected to the given host and port and + * originating from the specified local address and port. + *

+ * @param host The hostname to connect to. + * @param port The port to connect to. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @return A Socket connected to the given host and port. + * @exception UnknownHostException If the hostname cannot be resolved. + * @exception IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(String host, int port, + InetAddress localAddr, int localPort) + throws UnknownHostException, IOException + { + return new Socket(host, port, localAddr, localPort); + } + + /*** + * Creates a Socket connected to the given host and port and + * originating from the specified local address and port. + *

+ * @param address The address of the host to connect to. + * @param port The port to connect to. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @return A Socket connected to the given host and port. + * @exception IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(InetAddress address, int port, + InetAddress localAddr, int localPort) + throws IOException + { + return new Socket(address, port, localAddr, localPort); + } + + /*** + * Creates a ServerSocket bound to a specified port. A port + * of 0 will create the ServerSocket on a system-determined free port. + *

+ * @param port The port on which to listen, or 0 to use any free port. + * @return A ServerSocket that will listen on a specified port. + * @exception IOException If an I/O error occurs while creating + * the ServerSocket. + ***/ + public ServerSocket createServerSocket(int port) throws IOException + { + return new ServerSocket(port); + } + + /*** + * Creates a ServerSocket bound to a specified port with a given + * maximum queue length for incoming connections. A port of 0 will + * create the ServerSocket on a system-determined free port. + *

+ * @param port The port on which to listen, or 0 to use any free port. + * @param backlog The maximum length of the queue for incoming connections. + * @return A ServerSocket that will listen on a specified port. + * @exception IOException If an I/O error occurs while creating + * the ServerSocket. + ***/ + public ServerSocket createServerSocket(int port, int backlog) + throws IOException + { + return new ServerSocket(port, backlog); + } + + /*** + * Creates a ServerSocket bound to a specified port on a given local + * address with a given maximum queue length for incoming connections. + * A port of 0 will + * create the ServerSocket on a system-determined free port. + *

+ * @param port The port on which to listen, or 0 to use any free port. + * @param backlog The maximum length of the queue for incoming connections. + * @param bindAddr The local address to which the ServerSocket should bind. + * @return A ServerSocket that will listen on a specified port. + * @exception IOException If an I/O error occurs while creating + * the ServerSocket. + ***/ + public ServerSocket createServerSocket(int port, int backlog, + InetAddress bindAddr) + throws IOException + { + return new ServerSocket(port, backlog, bindAddr); + } +} diff --git a/src/org/apache/commons/net/MalformedServerReplyException.java b/src/org/apache/commons/net/MalformedServerReplyException.java new file mode 100644 index 0000000..3cad5f0 --- /dev/null +++ b/src/org/apache/commons/net/MalformedServerReplyException.java @@ -0,0 +1,55 @@ +/* + * 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; + +import java.io.IOException; + +/*** + * This exception is used to indicate that the reply from a server + * could not be interpreted. Most of the NetComponents classes attempt + * to be as lenient as possible when receiving server replies. Many + * server implementations deviate from IETF protocol specifications, making + * it necessary to be as flexible as possible. However, there will be + * certain situations where it is not possible to continue an operation + * because the server reply could not be interpreted in a meaningful manner. + * In these cases, a MalformedServerReplyException should be thrown. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public class MalformedServerReplyException extends IOException +{ + + /*** Constructs a MalformedServerReplyException with no message ***/ + public MalformedServerReplyException() + { + super(); + } + + /*** + * Constructs a MalformedServerReplyException with a specified message. + *

+ * @param message The message explaining the reason for the exception. + ***/ + public MalformedServerReplyException(String message) + { + super(message); + } + +} diff --git a/src/org/apache/commons/net/PrintCommandListener.java b/src/org/apache/commons/net/PrintCommandListener.java new file mode 100644 index 0000000..d8e7a68 --- /dev/null +++ b/src/org/apache/commons/net/PrintCommandListener.java @@ -0,0 +1,54 @@ +/* + * 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; + +import java.io.PrintWriter; +import org.apache.commons.net.ProtocolCommandEvent; +import org.apache.commons.net.ProtocolCommandListener; + +/*** + * This is a support class for some of the example programs. It is + * a sample implementation of the ProtocolCommandListener interface + * which just prints out to a specified stream all command/reply traffic. + *

+ * + * @since 2.0 + ***/ + +public class PrintCommandListener implements ProtocolCommandListener +{ + private PrintWriter __writer; + + public PrintCommandListener(PrintWriter writer) + { + __writer = writer; + } + + public void protocolCommandSent(ProtocolCommandEvent event) + { + __writer.print(event.getMessage()); + __writer.flush(); + } + + public void protocolReplyReceived(ProtocolCommandEvent event) + { + __writer.print(event.getMessage()); + __writer.flush(); + } +} + diff --git a/src/org/apache/commons/net/ProtocolCommandEvent.java b/src/org/apache/commons/net/ProtocolCommandEvent.java new file mode 100644 index 0000000..8977c03 --- /dev/null +++ b/src/org/apache/commons/net/ProtocolCommandEvent.java @@ -0,0 +1,146 @@ +/* + * 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; +import java.util.EventObject; + +/*** + * There exists a large class of IETF protocols that work by sending an + * ASCII text command and arguments to a server, and then receiving an + * ASCII text reply. For debugging and other purposes, it is extremely + * useful to log or keep track of the contents of the protocol messages. + * The ProtocolCommandEvent class coupled with the + * {@link org.apache.commons.net.ProtocolCommandListener} + * interface facilitate this process. + *

+ *

+ * @see ProtocolCommandListener + * @see ProtocolCommandSupport + * @author Daniel F. Savarese + ***/ + +public class ProtocolCommandEvent extends EventObject +{ + private int __replyCode; + private boolean __isCommand; + private String __message, __command; + + /*** + * Creates a ProtocolCommandEvent signalling a command was sent to + * the server. ProtocolCommandEvents created with this constructor + * should only be sent after a command has been sent, but before the + * reply has been received. + *

+ * @param source The source of the event. + * @param command The string representation of the command type sent, not + * including the arguments (e.g., "STAT" or "GET"). + * @param message The entire command string verbatim as sent to the server, + * including all arguments. + ***/ + public ProtocolCommandEvent(Object source, String command, String message) + { + super(source); + __replyCode = 0; + __message = message; + __isCommand = true; + __command = command; + } + + + /*** + * Creates a ProtocolCommandEvent signalling a reply to a command was + * received. ProtocolCommandEvents created with this constructor + * should only be sent after a complete command reply has been received + * fromt a server. + *

+ * @param source The source of the event. + * @param replyCode The integer code indicating the natureof the reply. + * This will be the protocol integer value for protocols + * that use integer reply codes, or the reply class constant + * corresponding to the reply for protocols like POP3 that use + * strings like OK rather than integer codes (i.e., POP3Repy.OK). + * @param message The entire reply as received from the server. + ***/ + public ProtocolCommandEvent(Object source, int replyCode, String message) + { + super(source); + __replyCode = replyCode; + __message = message; + __isCommand = false; + __command = null; + } + + /*** + * Returns the string representation of the command type sent (e.g., "STAT" + * or "GET"). If the ProtocolCommandEvent is a reply event, then null + * is returned. + *

+ * @return The string representation of the command type sent, or null + * if this is a reply event. + ***/ + public String getCommand() + { + return __command; + } + + + /*** + * Returns the reply code of the received server reply. Undefined if + * this is not a reply event. + *

+ * @return The reply code of the received server reply. Undefined if + * not a reply event. + ***/ + public int getReplyCode() + { + return __replyCode; + } + + /*** + * Returns true if the ProtocolCommandEvent was generated as a result + * of sending a command. + *

+ * @return true If the ProtocolCommandEvent was generated as a result + * of sending a command. False otherwise. + ***/ + public boolean isCommand() + { + return __isCommand; + } + + /*** + * Returns true if the ProtocolCommandEvent was generated as a result + * of receiving a reply. + *

+ * @return true If the ProtocolCommandEvent was generated as a result + * of receiving a reply. False otherwise. + ***/ + public boolean isReply() + { + return !isCommand(); + } + + /*** + * Returns the entire message sent to or received from the server. + *

+ * @return The entire message sent to or received from the server. + ***/ + public String getMessage() + { + return __message; + } +} diff --git a/src/org/apache/commons/net/ProtocolCommandListener.java b/src/org/apache/commons/net/ProtocolCommandListener.java new file mode 100644 index 0000000..8089926 --- /dev/null +++ b/src/org/apache/commons/net/ProtocolCommandListener.java @@ -0,0 +1,59 @@ +/* + * 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; +import java.util.EventListener; + +/*** + * There exists a large class of IETF protocols that work by sending an + * ASCII text command and arguments to a server, and then receiving an + * ASCII text reply. For debugging and other purposes, it is extremely + * useful to log or keep track of the contents of the protocol messages. + * The ProtocolCommandListener interface coupled with the + * {@link ProtocolCommandEvent} class facilitate this process. + *

+ * To receive ProtocolCommandEvents, you merely implement the + * ProtocolCommandListener interface and register the class as a listener + * with a ProtocolCommandEvent source such as + * {@link org.apache.commons.net.ftp.FTPClient}. + *

+ *

+ * @see ProtocolCommandEvent + * @see ProtocolCommandSupport + * @author Daniel F. Savarese + ***/ + +public interface ProtocolCommandListener extends EventListener +{ + + /*** + * This method is invoked by a ProtocolCommandEvent source after + * sending a protocol command to a server. + *

+ * @param event The ProtocolCommandEvent fired. + ***/ + public void protocolCommandSent(ProtocolCommandEvent event); + + /*** + * This method is invoked by a ProtocolCommandEvent source after + * receiving a reply from a server. + *

+ * @param event The ProtocolCommandEvent fired. + ***/ + public void protocolReplyReceived(ProtocolCommandEvent event); + +} diff --git a/src/org/apache/commons/net/ProtocolCommandSupport.java b/src/org/apache/commons/net/ProtocolCommandSupport.java new file mode 100644 index 0000000..1a51fb6 --- /dev/null +++ b/src/org/apache/commons/net/ProtocolCommandSupport.java @@ -0,0 +1,134 @@ +/* + * 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; + +import java.io.Serializable; +import java.util.EventListener; + +import org.apache.commons.net.util.ListenerList; + +/*** + * ProtocolCommandSupport is a convenience class for managing a list of + * ProtocolCommandListeners and firing ProtocolCommandEvents. You can + * simply delegate ProtocolCommandEvent firing and listener + * registering/unregistering tasks to this class. + *

+ *

+ * @see ProtocolCommandEvent + * @see ProtocolCommandListener + * @author Daniel F. Savarese + ***/ + +public class ProtocolCommandSupport implements Serializable +{ + private Object __source; + private ListenerList __listeners; + + /*** + * Creates a ProtocolCommandSupport instant using the indicated source + * as the source of fired ProtocolCommandEvents. + *

+ * @param source The source to use for all generated ProtocolCommandEvents. + ***/ + public ProtocolCommandSupport(Object source) + { + __listeners = new ListenerList(); + __source = source; + } + + + /*** + * Fires a ProtocolCommandEvent signalling the sending of a command to all + * registered listeners, invoking their + * {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() } + * methods. + *

+ * @param command The string representation of the command type sent, not + * including the arguments (e.g., "STAT" or "GET"). + * @param message The entire command string verbatim as sent to the server, + * including all arguments. + ***/ + public void fireCommandSent(String command, String message) + { + ProtocolCommandEvent event; + + event = new ProtocolCommandEvent(__source, command, message); + + for (EventListener listener : __listeners) + { + ((ProtocolCommandListener)listener).protocolCommandSent(event); + } + } + + /*** + * Fires a ProtocolCommandEvent signalling the reception of a command reply + * to all registered listeners, invoking their + * {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() } + * methods. + *

+ * @param replyCode The integer code indicating the natureof the reply. + * This will be the protocol integer value for protocols + * that use integer reply codes, or the reply class constant + * corresponding to the reply for protocols like POP3 that use + * strings like OK rather than integer codes (i.e., POP3Repy.OK). + * @param message The entire reply as received from the server. + ***/ + public void fireReplyReceived(int replyCode, String message) + { + ProtocolCommandEvent event; + event = new ProtocolCommandEvent(__source, replyCode, message); + + for (EventListener listener : __listeners) + { + ((ProtocolCommandListener)listener).protocolReplyReceived(event); + } + } + + /*** + * Adds a ProtocolCommandListener. + *

+ * @param listener The ProtocolCommandListener to add. + ***/ + public void addProtocolCommandListener(ProtocolCommandListener listener) + { + __listeners.addListener(listener); + } + + /*** + * Removes a ProtocolCommandListener. + *

+ * @param listener The ProtocolCommandListener to remove. + ***/ + public void removeProtocolCommandListener(ProtocolCommandListener listener) + { + __listeners.removeListener(listener); + } + + + /*** + * Returns the number of ProtocolCommandListeners currently registered. + *

+ * @return The number of ProtocolCommandListeners currently registered. + ***/ + public int getListenerCount() + { + return __listeners.getListenerCount(); + } + +} + diff --git a/src/org/apache/commons/net/SocketClient.java b/src/org/apache/commons/net/SocketClient.java new file mode 100644 index 0000000..a5a8ead --- /dev/null +++ b/src/org/apache/commons/net/SocketClient.java @@ -0,0 +1,586 @@ +/* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; + +import javax.net.ServerSocketFactory; +import javax.net.SocketFactory; + + +/** + * The SocketClient provides the basic operations that are required of + * client objects accessing sockets. It is meant to be + * subclassed to avoid having to rewrite the same code over and over again + * to open a socket, close a socket, set timeouts, etc. Of special note + * is the {@link #setSocketFactory setSocketFactory } + * method, which allows you to control the type of Socket the SocketClient + * creates for initiating network connections. This is especially useful + * for adding SSL or proxy support as well as better support for applets. For + * example, you could create a + * {@link org.apache.commons.net.SocketFactory} that + * requests browser security capabilities before creating a socket. + * All classes derived from SocketClient should use the + * {@link #_socketFactory_ _socketFactory_ } member variable to + * create Socket and ServerSocket instances rather than instanting + * them by directly invoking a constructor. By honoring this contract + * you guarantee that a user will always be able to provide his own + * Socket implementations by substituting his own SocketFactory. + * @author Daniel F. Savarese + * @see SocketFactory + */ +public abstract class SocketClient +{ + /** + * The end of line character sequence used by most IETF protocols. That + * is a carriage return followed by a newline: "\r\n" + */ + public static final String NETASCII_EOL = "\r\n"; + + /** The default SocketFactory shared by all SocketClient instances. */ + private static final SocketFactory __DEFAULT_SOCKET_FACTORY = + SocketFactory.getDefault(); + + private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY = + ServerSocketFactory.getDefault(); + + /** The timeout to use after opening a socket. */ + protected int _timeout_; + + /** The socket used for the connection. */ + protected Socket _socket_; + + /** The default port the client should connect to. */ + protected int _defaultPort_; + + /** The socket's InputStream. */ + protected InputStream _input_; + + /** The socket's OutputStream. */ + protected OutputStream _output_; + + /** The socket's SocketFactory. */ + protected SocketFactory _socketFactory_; + + /** The socket's ServerSocket Factory. */ + protected ServerSocketFactory _serverSocketFactory_; + + /** The socket's connect timeout (0 = infinite timeout) */ + private static final int DEFAULT_CONNECT_TIMEOUT = 0; + protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT; + + /** + * Default constructor for SocketClient. Initializes + * _socket_ to null, _timeout_ to 0, _defaultPort to 0, + * _isConnected_ to false, and _socketFactory_ to a shared instance of + * {@link org.apache.commons.net.DefaultSocketFactory}. + */ + public SocketClient() + { + _socket_ = null; + _input_ = null; + _output_ = null; + _timeout_ = 0; + _defaultPort_ = 0; + _socketFactory_ = __DEFAULT_SOCKET_FACTORY; + _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY; + } + + + /** + * Because there are so many connect() methods, the _connectAction_() + * method is provided as a means of performing some action immediately + * after establishing a connection, rather than reimplementing all + * of the connect() methods. The last action performed by every + * connect() method after opening a socket is to call this method. + *

+ * This method sets the timeout on the just opened socket to the default + * timeout set by {@link #setDefaultTimeout setDefaultTimeout() }, + * sets _input_ and _output_ to the socket's InputStream and OutputStream + * respectively, and sets _isConnected_ to true. + *

+ * Subclasses overriding this method should start by calling + * super._connectAction_() first to ensure the + * initialization of the aforementioned protected variables. + */ + protected void _connectAction_() throws IOException + { + _socket_.setSoTimeout(_timeout_); + _input_ = _socket_.getInputStream(); + _output_ = _socket_.getOutputStream(); + } + + + /** + * Opens a Socket connected to a remote host at the specified port and + * originating from the current host at a system assigned port. + * Before returning, {@link #_connectAction_ _connectAction_() } + * is called to perform connection initialization actions. + *

+ * @param host The remote host. + * @param port The port to connect to on the remote host. + * @exception SocketException If the socket timeout could not be set. + * @exception IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + */ + public void connect(InetAddress host, int port) + throws SocketException, IOException + { + _socket_ = _socketFactory_.createSocket(); + _socket_.connect(new InetSocketAddress(host, port), connectTimeout); + + _connectAction_(); + } + + /** + * Opens a Socket connected to a remote host at the specified port and + * originating from the current host at a system assigned port. + * Before returning, {@link #_connectAction_ _connectAction_() } + * is called to perform connection initialization actions. + *

+ * @param hostname The name of the remote host. + * @param port The port to connect to on the remote host. + * @exception SocketException If the socket timeout could not be set. + * @exception IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @exception UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname, int port) + throws SocketException, IOException + { + _socket_= _socketFactory_.createSocket(); + _socket_.connect(new InetSocketAddress(hostname, port), connectTimeout); + + _connectAction_(); + } + + + /** + * Opens a Socket connected to a remote host at the specified port and + * originating from the specified local address and port. + * Before returning, {@link #_connectAction_ _connectAction_() } + * is called to perform connection initialization actions. + *

+ * @param host The remote host. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @exception SocketException If the socket timeout could not be set. + * @exception IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + */ + public void connect(InetAddress host, int port, + InetAddress localAddr, int localPort) + throws SocketException, IOException + { + _socket_ = _socketFactory_.createSocket(); + _socket_.bind(new InetSocketAddress(localAddr, localPort)); + _socket_.connect(new InetSocketAddress(host, port), connectTimeout); + + _connectAction_(); + } + + + /** + * Opens a Socket connected to a remote host at the specified port and + * originating from the specified local address and port. + * Before returning, {@link #_connectAction_ _connectAction_() } + * is called to perform connection initialization actions. + *

+ * @param hostname The name of the remote host. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @exception SocketException If the socket timeout could not be set. + * @exception IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @exception UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname, int port, + InetAddress localAddr, int localPort) + throws SocketException, IOException + { + _socket_ = + _socketFactory_.createSocket(hostname, port, localAddr, localPort); + _connectAction_(); + } + + + /** + * Opens a Socket connected to a remote host at the current default port + * and originating from the current host at a system assigned port. + * Before returning, {@link #_connectAction_ _connectAction_() } + * is called to perform connection initialization actions. + *

+ * @param host The remote host. + * @exception SocketException If the socket timeout could not be set. + * @exception IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + */ + public void connect(InetAddress host) throws SocketException, IOException + { + connect(host, _defaultPort_); + } + + + /** + * Opens a Socket connected to a remote host at the current default + * port and originating from the current host at a system assigned port. + * Before returning, {@link #_connectAction_ _connectAction_() } + * is called to perform connection initialization actions. + *

+ * @param hostname The name of the remote host. + * @exception SocketException If the socket timeout could not be set. + * @exception IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @exception UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname) throws SocketException, IOException + { + connect(hostname, _defaultPort_); + } + + + /** + * Disconnects the socket connection. + * You should call this method after you've finished using the class + * instance and also before you call + * {@link #connect connect() } + * again. _isConnected_ is set to false, _socket_ is set to null, + * _input_ is set to null, and _output_ is set to null. + *

+ * @exception IOException If there is an error closing the socket. + */ + public void disconnect() throws IOException + { + if (_socket_ != null) _socket_.close(); + if (_input_ != null) _input_.close(); + if (_output_ != null) _output_.close(); + if (_socket_ != null) _socket_ = null; + _input_ = null; + _output_ = null; + } + + + /** + * Returns true if the client is currently connected to a server. + *

+ * @return True if the client is currently connected to a server, + * false otherwise. + */ + public boolean isConnected() + { + if (_socket_ == null) + return false; + + return _socket_.isConnected(); + } + + + /** + * Sets the default port the SocketClient should connect to when a port + * is not specified. The {@link #_defaultPort_ _defaultPort_ } + * variable stores this value. If never set, the default port is equal + * to zero. + *

+ * @param port The default port to set. + */ + public void setDefaultPort(int port) + { + _defaultPort_ = port; + } + + /** + * Returns the current value of the default port (stored in + * {@link #_defaultPort_ _defaultPort_ }). + *

+ * @return The current value of the default port. + */ + public int getDefaultPort() + { + return _defaultPort_; + } + + + /** + * Set the default timeout in milliseconds to use when opening a socket. + * This value is only used previous to a call to + * {@link #connect connect()} + * and should not be confused with {@link #setSoTimeout setSoTimeout()} + * which operates on an the currently opened socket. _timeout_ contains + * the new timeout value. + *

+ * @param timeout The timeout in milliseconds to use for the socket + * connection. + */ + public void setDefaultTimeout(int timeout) + { + _timeout_ = timeout; + } + + + /** + * Returns the default timeout in milliseconds that is used when + * opening a socket. + *

+ * @return The default timeout in milliseconds that is used when + * opening a socket. + */ + public int getDefaultTimeout() + { + return _timeout_; + } + + + /** + * Set the timeout in milliseconds of a currently open connection. + * Only call this method after a connection has been opened + * by {@link #connect connect()}. + *

+ * @param timeout The timeout in milliseconds to use for the currently + * open socket connection. + * @exception SocketException If the operation fails. + */ + public void setSoTimeout(int timeout) throws SocketException + { + _socket_.setSoTimeout(timeout); + } + + + /** + * Set the underlying socket send buffer size. + *

+ * @param size The size of the buffer in bytes. + * @throws SocketException + * @since 2.0 + */ + public void setSendBufferSize(int size) throws SocketException { + _socket_.setSendBufferSize(size); + } + + + /** + * Sets the underlying socket receive buffer size. + *

+ * @param size The size of the buffer in bytes. + * @throws SocketException + * @since 2.0 + */ + public void setReceiveBufferSize(int size) throws SocketException { + _socket_.setReceiveBufferSize(size); + } + + + /** + * Returns the timeout in milliseconds of the currently opened socket. + *

+ * @return The timeout in milliseconds of the currently opened socket. + * @exception SocketException If the operation fails. + */ + public int getSoTimeout() throws SocketException + { + return _socket_.getSoTimeout(); + } + + /** + * Enables or disables the Nagle's algorithm (TCP_NODELAY) on the + * currently opened socket. + *

+ * @param on True if Nagle's algorithm is to be enabled, false if not. + * @exception SocketException If the operation fails. + */ + public void setTcpNoDelay(boolean on) throws SocketException + { + _socket_.setTcpNoDelay(on); + } + + + /** + * Returns true if Nagle's algorithm is enabled on the currently opened + * socket. + *

+ * @return True if Nagle's algorithm is enabled on the currently opened + * socket, false otherwise. + * @exception SocketException If the operation fails. + */ + public boolean getTcpNoDelay() throws SocketException + { + return _socket_.getTcpNoDelay(); + } + + + /** + * Sets the SO_LINGER timeout on the currently opened socket. + *

+ * @param on True if linger is to be enabled, false if not. + * @param val The linger timeout (in hundredths of a second?) + * @exception SocketException If the operation fails. + */ + public void setSoLinger(boolean on, int val) throws SocketException + { + _socket_.setSoLinger(on, val); + } + + + /** + * Returns the current SO_LINGER timeout of the currently opened socket. + *

+ * @return The current SO_LINGER timeout. If SO_LINGER is disabled returns + * -1. + * @exception SocketException If the operation fails. + */ + public int getSoLinger() throws SocketException + { + return _socket_.getSoLinger(); + } + + + /** + * Returns the port number of the open socket on the local host used + * for the connection. + *

+ * @return The port number of the open socket on the local host used + * for the connection. + */ + public int getLocalPort() + { + return _socket_.getLocalPort(); + } + + + /** + * Returns the local address to which the client's socket is bound. + *

+ * @return The local address to which the client's socket is bound. + */ + public InetAddress getLocalAddress() + { + return _socket_.getLocalAddress(); + } + + /** + * Returns the port number of the remote host to which the client is + * connected. + *

+ * @return The port number of the remote host to which the client is + * connected. + */ + public int getRemotePort() + { + return _socket_.getPort(); + } + + + /** + * @return The remote address to which the client is connected. + */ + public InetAddress getRemoteAddress() + { + return _socket_.getInetAddress(); + } + + + /** + * Verifies that the remote end of the given socket is connected to the + * the same host that the SocketClient is currently connected to. This + * is useful for doing a quick security check when a client needs to + * accept a connection from a server, such as an FTP data connection or + * a BSD R command standard error stream. + *

+ * @return True if the remote hosts are the same, false if not. + */ + public boolean verifyRemote(Socket socket) + { + InetAddress host1, host2; + + host1 = socket.getInetAddress(); + host2 = getRemoteAddress(); + + return host1.equals(host2); + } + + + /** + * Sets the SocketFactory used by the SocketClient to open socket + * connections. If the factory value is null, then a default + * factory is used (only do this to reset the factory after having + * previously altered it). + *

+ * @param factory The new SocketFactory the SocketClient should use. + */ + public void setSocketFactory(SocketFactory factory) + { + if (factory == null) + _socketFactory_ = __DEFAULT_SOCKET_FACTORY; + else + _socketFactory_ = factory; + } + + /** + * Sets the ServerSocketFactory used by the SocketClient to open ServerSocket + * connections. If the factory value is null, then a default + * factory is used (only do this to reset the factory after having + * previously altered it). + *

+ * @param factory The new ServerSocketFactory the SocketClient should use. + * @since 2.0 + */ + public void setServerSocketFactory(ServerSocketFactory factory) { + if (factory == null) + _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY; + else + _serverSocketFactory_ = factory; + } + + /** + * Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's + * connect() method. + * @param connectTimeout The connection timeout to use (in ms) + * @since 2.0 + */ + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + /** + * Get the underlying socket connection timeout. + * @return + * @since 2.0 + */ + public int getConnectTimeout() { + return connectTimeout; + } + + + +} + + diff --git a/src/org/apache/commons/net/ftp/Configurable.java b/src/org/apache/commons/net/ftp/Configurable.java new file mode 100644 index 0000000..5e8e749 --- /dev/null +++ b/src/org/apache/commons/net/ftp/Configurable.java @@ -0,0 +1,35 @@ +/* + * 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; + + +/** + * This interface adds the aspect of configurability by means of + * a supplied FTPClientConfig object to other classes in the + * system, especially listing parsers. + */ +public interface Configurable { + + /** + * @param config the object containing the configuration data + * @throws IllegalArgumentException if the elements of the + * config are somehow inadequate to configure the + * Configurable object. + */ + public void configure(FTPClientConfig config); +} diff --git a/src/org/apache/commons/net/ftp/FTP.java b/src/org/apache/commons/net/ftp/FTP.java new file mode 100644 index 0000000..6e2eff8 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTP.java @@ -0,0 +1,1513 @@ +/* + * 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.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; + +import org.apache.commons.net.MalformedServerReplyException; +import org.apache.commons.net.ProtocolCommandListener; +import org.apache.commons.net.ProtocolCommandSupport; +import org.apache.commons.net.SocketClient; + +/*** + * FTP provides the basic the functionality necessary to implement your + * own FTP client. It extends org.apache.commons.net.SocketClient since + * extending TelnetClient was causing unwanted behavior (like connections + * that did not time out properly). + *

+ * To derive the full benefits of the FTP class requires some knowledge + * of the FTP protocol defined in RFC 959. However, there is no reason + * why you should have to use the FTP class. The + * {@link org.apache.commons.net.ftp.FTPClient} class, + * derived from FTP, + * implements all the functionality required of an FTP client. The + * FTP class is made public to provide access to various FTP constants + * and to make it easier for adventurous programmers (or those with + * special needs) to interact with the FTP protocol and implement their + * own clients. A set of methods with names corresponding to the FTP + * command names are provided to facilitate this interaction. + *

+ * You should keep in mind that the FTP server may choose to prematurely + * close a connection if the client has been idle for longer than a + * given time period (usually 900 seconds). The FTP class will detect a + * premature FTP server connection closing when it receives a + * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } + * response to a command. + * When that occurs, the FTP class method encountering that reply will throw + * an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} + * . FTPConectionClosedException + * is a subclass of IOException and therefore need not be + * caught separately, but if you are going to catch it separately, its + * catch block must appear before the more general IOException + * catch block. When you encounter an + * {@link org.apache.commons.net.ftp.FTPConnectionClosedException} + * , you must disconnect the connection with + * {@link #disconnect disconnect() } to properly clean up the + * system resources used by FTP. Before disconnecting, you may check the + * last reply code and text with + * {@link #getReplyCode getReplyCode }, + * {@link #getReplyString getReplyString }, + * and {@link #getReplyStrings getReplyStrings}. + * You may avoid server disconnections while the client is idle by + * periodicaly sending NOOP commands to the server. + *

+ * Rather than list it separately for each method, we mention here that + * every method communicating with the server and throwing an IOException + * can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} + * , which is a subclass + * of IOException. A MalformedServerReplyException will be thrown when + * the reply received from the server deviates enough from the protocol + * specification that it cannot be interpreted in a useful manner despite + * attempts to be as lenient as possible. + *

+ *

+ * @author Daniel F. Savarese + * @author Rory Winston + * @author Joseph Hindsley + * @see FTPClient + * @see FTPConnectionClosedException + * @see org.apache.commons.net.MalformedServerReplyException + * @version $Id: FTP.java 658520 2008-05-21 01:14:11Z sebb $ + ***/ + +public class FTP extends SocketClient +{ + /*** The default FTP data port (20). ***/ + public static final int DEFAULT_DATA_PORT = 20; + /*** The default FTP control port (21). ***/ + public static final int DEFAULT_PORT = 21; + + /*** + * A constant used to indicate the file(s) being transfered should + * be treated as ASCII. This is the default file type. All constants + * ending in FILE_TYPE are used to indicate file types. + ***/ + public static final int ASCII_FILE_TYPE = 0; + + /*** + * A constant used to indicate the file(s) being transfered should + * be treated as EBCDIC. Note however that there are several different + * EBCDIC formats. All constants ending in FILE_TYPE + * are used to indicate file types. + ***/ + public static final int EBCDIC_FILE_TYPE = 1; + + + /*** + * A constant used to indicate the file(s) being transfered should + * be treated as a binary image, i.e., no translations should be + * performed. All constants ending in FILE_TYPE are used to + * indicate file types. + ***/ + public static final int BINARY_FILE_TYPE = 2; + + /*** + * A constant used to indicate the file(s) being transfered should + * be treated as a local type. All constants ending in + * FILE_TYPE are used to indicate file types. + ***/ + public static final int LOCAL_FILE_TYPE = 3; + + /*** + * A constant used for text files to indicate a non-print text format. + * This is the default format. + * All constants ending in TEXT_FORMAT are used to indicate + * text formatting for text transfers (both ASCII and EBCDIC). + ***/ + public static final int NON_PRINT_TEXT_FORMAT = 4; + + /*** + * A constant used to indicate a text file contains format vertical format + * control characters. + * All constants ending in TEXT_FORMAT are used to indicate + * text formatting for text transfers (both ASCII and EBCDIC). + ***/ + public static final int TELNET_TEXT_FORMAT = 5; + + /*** + * A constant used to indicate a text file contains ASA vertical format + * control characters. + * All constants ending in TEXT_FORMAT are used to indicate + * text formatting for text transfers (both ASCII and EBCDIC). + ***/ + public static final int CARRIAGE_CONTROL_TEXT_FORMAT = 6; + + /*** + * A constant used to indicate a file is to be treated as a continuous + * sequence of bytes. This is the default structure. All constants ending + * in _STRUCTURE are used to indicate file structure for + * file transfers. + ***/ + public static final int FILE_STRUCTURE = 7; + + /*** + * A constant used to indicate a file is to be treated as a sequence + * of records. All constants ending in _STRUCTURE + * are used to indicate file structure for file transfers. + ***/ + public static final int RECORD_STRUCTURE = 8; + + /*** + * A constant used to indicate a file is to be treated as a set of + * independent indexed pages. All constants ending in + * _STRUCTURE are used to indicate file structure for file + * transfers. + ***/ + public static final int PAGE_STRUCTURE = 9; + + /*** + * A constant used to indicate a file is to be transfered as a stream + * of bytes. This is the default transfer mode. All constants ending + * in TRANSFER_MODE are used to indicate file transfer + * modes. + ***/ + public static final int STREAM_TRANSFER_MODE = 10; + + /*** + * A constant used to indicate a file is to be transfered as a series + * of blocks. All constants ending in TRANSFER_MODE are used + * to indicate file transfer modes. + ***/ + public static final int BLOCK_TRANSFER_MODE = 11; + + /*** + * A constant used to indicate a file is to be transfered as FTP + * compressed data. All constants ending in TRANSFER_MODE + * are used to indicate file transfer modes. + ***/ + public static final int COMPRESSED_TRANSFER_MODE = 12; + + // 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. + /** + * The default character encoding used for communicating over an + * FTP control connection. The default encoding is an + * ASCII-compatible encoding. Some FTP servers expect other + * encodings. You can change the encoding used by an FTP instance + * with {@link #setControlEncoding setControlEncoding}. + */ + public static final String DEFAULT_CONTROL_ENCODING = "ISO-8859-1"; + private static final String __modes = "AEILNTCFRPSBC"; + + private StringBuilder __commandBuffer = new StringBuilder(); + + protected int _replyCode; + protected ArrayList _replyLines; + protected boolean _newReplyString; + protected String _replyString; + protected String _controlEncoding; + + /** + * This is used to signal whether a block of multiline responses beginning + * with xxx must be terminated by the same numeric code xxx + * See section 4.2 of RFX 959 for details. + */ + protected boolean strictMultilineParsing = false; + + /** + * Wraps SocketClient._input_ to facilitate the writing of text + * to the FTP control connection. Do not access the control + * connection via SocketClient._input_. This member starts + * with a null value, is initialized in {@link #_connectAction_}, + * and set to null in {@link #disconnect}. + */ + protected BufferedReader _controlInput_; + + /** + * Wraps SocketClient._output_ to facilitate the reading of text + * from the FTP control connection. Do not access the control + * connection via SocketClient._output_. This member starts + * with a null value, is initialized in {@link #_connectAction_}, + * and set to null in {@link #disconnect}. + */ + protected BufferedWriter _controlOutput_; + + /*** + * A ProtocolCommandSupport object used to manage the registering of + * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + ***/ + protected ProtocolCommandSupport _commandSupport_; + + /*** + * The default FTP constructor. Sets the default port to + * DEFAULT_PORT and initializes internal data structures + * for saving FTP reply information. + ***/ + public FTP() + { + super(); + setDefaultPort(DEFAULT_PORT); + _replyLines = new ArrayList(); + _newReplyString = false; + _replyString = null; + _commandSupport_ = new ProtocolCommandSupport(this); + _controlEncoding = DEFAULT_CONTROL_ENCODING; + } + + // The RFC-compliant multiline termination check + private boolean __strictCheck(String line, String code) { + return (!(line.startsWith(code) && line.charAt(3) == ' ')); + } + + // The strict check is too strong a condition because of non-conforming ftp + // servers like ftp.funet.fi which sent 226 as the last line of a + // 426 multi-line reply in response to ls /. We relax the condition to + // test that the line starts with a digit rather than starting with + // the code. + private boolean __lenientCheck(String line) { + return (!(line.length() >= 4 && line.charAt(3) != '-' && + Character.isDigit(line.charAt(0)))); + } + + private void __getReply() throws IOException + { + int length; + + _newReplyString = true; + _replyLines.clear(); + + String line = _controlInput_.readLine(); + + if (line == null) + throw new FTPConnectionClosedException( + "Connection closed without indication."); + + // In case we run into an anomaly we don't want fatal index exceptions + // to be thrown. + length = line.length(); + if (length < 3) + throw new MalformedServerReplyException( + "Truncated server reply: " + line); + + String code = null; + try + { + code = line.substring(0, 3); + _replyCode = Integer.parseInt(code); + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse response code.\nServer Reply: " + line); + } + + _replyLines.add(line); + + // Get extra lines if message continues. + if (length > 3 && line.charAt(3) == '-') + { + do + { + line = _controlInput_.readLine(); + + if (line == null) + throw new FTPConnectionClosedException( + "Connection closed without indication."); + + _replyLines.add(line); + + // The length() check handles problems that could arise from readLine() + // returning too soon after encountering a naked CR or some other + // anomaly. + } + while ( isStrictMultilineParsing() ? __strictCheck(line, code) : __lenientCheck(line)); + } + + if (_commandSupport_.getListenerCount() > 0) { + _commandSupport_.fireReplyReceived(_replyCode, getReplyString()); + } + + if (_replyCode == FTPReply.SERVICE_NOT_AVAILABLE) { + throw new FTPConnectionClosedException("FTP response 421 received. Server closed connection."); + } + } + + /** + * Initiates control connections and gets initial reply. + * Initializes {@link #_controlInput_} and {@link #_controlOutput_}. + */ + @Override + protected void _connectAction_() throws IOException + { + super._connectAction_(); + _controlInput_ = + new BufferedReader(new InputStreamReader(_socket_.getInputStream(), + getControlEncoding())); + _controlOutput_ = + new BufferedWriter(new OutputStreamWriter(_socket_.getOutputStream(), + getControlEncoding())); + __getReply(); + // If we received code 120, we have to fetch completion reply. + if (FTPReply.isPositivePreliminary(_replyCode)) + __getReply(); + } + + + /** + * Sets the character encoding used by the FTP control connection. + * Some FTP servers require that commands be issued in a non-ASCII + * encoding like UTF-8 so that filenames with multi-byte character + * representations (e.g, Big 8) can be specified. + * + * @param encoding The new character encoding for the control connection. + */ + public void setControlEncoding(String encoding) { + _controlEncoding = encoding; + } + + + /** + * @return The character encoding used to communicate over the + * control connection. + */ + public String getControlEncoding() { + return _controlEncoding; + } + + + /*** + * Adds a ProtocolCommandListener. Delegates this task to + * {@link #_commandSupport_ _commandSupport_ }. + *

+ * @param listener The ProtocolCommandListener to add. + ***/ + public void addProtocolCommandListener(ProtocolCommandListener listener) + { + _commandSupport_.addProtocolCommandListener(listener); + } + + /*** + * Removes a ProtocolCommandListener. Delegates this task to + * {@link #_commandSupport_ _commandSupport_ }. + *

+ * @param listener The ProtocolCommandListener to remove. + ***/ + public void removeProtocolCommandListener(ProtocolCommandListener listener) + { + _commandSupport_.removeProtocolCommandListener(listener); + } + + + /*** + * Closes the control connection to the FTP server and sets to null + * some internal data so that the memory may be reclaimed by the + * garbage collector. The reply text and code information from the + * last command is voided so that the memory it used may be reclaimed. + * Also sets {@link #_controlInput_} and {@link #_controlOutput_} to null. + *

+ * @exception IOException If an error occurs while disconnecting. + ***/ + @Override + public void disconnect() throws IOException + { + super.disconnect(); + _controlInput_ = null; + _controlOutput_ = null; + _newReplyString = false; + _replyString = null; + } + + + /*** + * Sends an FTP command to the server, waits for a reply and returns the + * numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling + * {@link #getReplyString getReplyString } or + * {@link #getReplyStrings getReplyStrings }. + *

+ * @param command The text representation of the FTP command to send. + * @param args The arguments to the FTP command. If this parameter is + * set to null, then the command is sent with no argument. + * @return The integer value of the FTP reply code returned by the server + * in response to the command. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int sendCommand(String command, String args) throws IOException + { + String message; + + __commandBuffer.setLength(0); + __commandBuffer.append(command); + + if (args != null) + { + __commandBuffer.append(' '); + __commandBuffer.append(args); + } + __commandBuffer.append(SocketClient.NETASCII_EOL); + + try{ + _controlOutput_.write(message = __commandBuffer.toString()); + _controlOutput_.flush(); + } + catch (SocketException e) + { + if (!isConnected() || !socketIsConnected(_socket_)) + { + throw new FTPConnectionClosedException("Connection unexpectedly closed."); + } + else + { + throw e; + } + } + + + if (_commandSupport_.getListenerCount() > 0) + _commandSupport_.fireCommandSent(command, message); + + __getReply(); + return _replyCode; + } + + /** + * Checks if the socket is connected + * + * @param socket + * @return true if connected + */ + private boolean socketIsConnected(Socket socket) + { + if (socket == null) + { + return false; + } + + return socket.isConnected(); + + } + + /*** + * Sends an FTP command to the server, waits for a reply and returns the + * numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling + * {@link #getReplyString getReplyString } or + * {@link #getReplyStrings getReplyStrings }. + *

+ * @param command The FTPCommand constant corresponding to the FTP command + * to send. + * @param args The arguments to the FTP command. If this parameter is + * set to null, then the command is sent with no argument. + * @return The integer value of the FTP reply code returned by the server + * in response to the command. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int sendCommand(int command, String args) throws IOException + { + return sendCommand(FTPCommand._commands[command], args); + } + + + /*** + * Sends an FTP command with no arguments to the server, waits for a + * reply and returns the numerical response code. After invocation, for + * more detailed information, the actual reply text can be accessed by + * calling {@link #getReplyString getReplyString } or + * {@link #getReplyStrings getReplyStrings }. + *

+ * @param command The text representation of the FTP command to send. + * @return The integer value of the FTP reply code returned by the server + * in response to the command. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int sendCommand(String command) throws IOException + { + return sendCommand(command, null); + } + + + /*** + * Sends an FTP command with no arguments to the server, waits for a + * reply and returns the numerical response code. After invocation, for + * more detailed information, the actual reply text can be accessed by + * calling {@link #getReplyString getReplyString } or + * {@link #getReplyStrings getReplyStrings }. + *

+ * @param command The FTPCommand constant corresponding to the FTP command + * to send. + * @return The integer value of the FTP reply code returned by the server + * in response to the command. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int sendCommand(int command) throws IOException + { + return sendCommand(command, null); + } + + + /*** + * Returns the integer value of the reply code of the last FTP reply. + * You will usually only use this method after you connect to the + * FTP server to check that the connection was successful since + * connect is of type void. + *

+ * @return The integer value of the reply code of the last FTP reply. + ***/ + public int getReplyCode() + { + return _replyCode; + } + + /*** + * Fetches a reply from the FTP server and returns the integer reply + * code. After calling this method, the actual reply text can be accessed + * from either calling {@link #getReplyString getReplyString } or + * {@link #getReplyStrings getReplyStrings }. Only use this + * method if you are implementing your own FTP client or if you need to + * fetch a secondary response from the FTP server. + *

+ * @return The integer value of the reply code of the fetched FTP reply. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while receiving the + * server reply. + ***/ + public int getReply() throws IOException + { + __getReply(); + return _replyCode; + } + + + /*** + * Returns the lines of text from the last FTP server response as an array + * of strings, one entry per line. The end of line markers of each are + * stripped from each line. + *

+ * @return The lines of text from the last FTP response as an array. + ***/ + public String[] getReplyStrings() + { + String[] lines; + lines = new String[_replyLines.size()]; + _replyLines.addAll(Arrays.asList(lines)); + return lines; + } + + /*** + * Returns the entire text of the last FTP server response exactly + * as it was received, including all end of line markers in NETASCII + * format. + *

+ * @return The entire text from the last FTP response as a String. + ***/ + public String getReplyString() + { + StringBuilder buffer; + + if (!_newReplyString) { + return _replyString; + } + + buffer = new StringBuilder(256); + + for (String line : _replyLines) { + buffer.append(line); + buffer.append(SocketClient.NETASCII_EOL); + } + + _newReplyString = false; + + return (_replyString = buffer.toString()); + } + + + /*** + * A convenience method to send the FTP USER command to the server, + * receive the reply, and return the reply code. + *

+ * @param username The username to login under. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int user(String username) throws IOException + { + return sendCommand(FTPCommand.USER, username); + } + + /** + * A convenience method to send the FTP PASS command to the server, + * receive the reply, and return the reply code. + * @param password The plain text password of the username being logged into. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + */ + public int pass(String password) throws IOException + { + return sendCommand(FTPCommand.PASS, password); + } + + /*** + * A convenience method to send the FTP ACCT command to the server, + * receive the reply, and return the reply code. + *

+ * @param account The account name to access. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int acct(String account) throws IOException + { + return sendCommand(FTPCommand.ACCT, account); + } + + + /*** + * A convenience method to send the FTP ABOR command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int abor() throws IOException + { + return sendCommand(FTPCommand.ABOR); + } + + /*** + * A convenience method to send the FTP CWD command to the server, + * receive the reply, and return the reply code. + *

+ * @param directory The new working directory. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int cwd(String directory) throws IOException + { + return sendCommand(FTPCommand.CWD, directory); + } + + /*** + * A convenience method to send the FTP CDUP command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int cdup() throws IOException + { + return sendCommand(FTPCommand.CDUP); + } + + /*** + * A convenience method to send the FTP QUIT command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int quit() throws IOException + { + return sendCommand(FTPCommand.QUIT); + } + + /*** + * A convenience method to send the FTP REIN command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rein() throws IOException + { + return sendCommand(FTPCommand.REIN); + } + + /*** + * A convenience method to send the FTP SMNT command to the server, + * receive the reply, and return the reply code. + *

+ * @param dir The directory name. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int smnt(String dir) throws IOException + { + return sendCommand(FTPCommand.SMNT, dir); + } + + /*** + * A convenience method to send the FTP PORT command to the server, + * receive the reply, and return the reply code. + *

+ * @param host The host owning the port. + * @param port The new port. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int port(InetAddress host, int port) throws IOException + { + int num; + StringBuffer info = new StringBuffer(24); + + info.append(host.getHostAddress().replace('.', ',')); + num = port >>> 8; + info.append(','); + info.append(num); + info.append(','); + num = port & 0xff; + info.append(num); + + return sendCommand(FTPCommand.PORT, info.toString()); + } + + /*** + * A convenience method to send the FTP PASV command to the server, + * receive the reply, and return the reply code. Remember, it's up + * to you to interpret the reply string containing the host/port + * information. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int pasv() throws IOException + { + return sendCommand(FTPCommand.PASV); + } + + /** + * A convenience method to send the FTP TYPE command for text files + * to the server, receive the reply, and return the reply code. + * @param fileType The type of the file (one of the FILE_TYPE + * constants). + * @param formatOrByteSize The format of the file (one of the + * _FORMAT constants. In the case of + * LOCAL_FILE_TYPE, the byte size. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + */ + public int type(int fileType, int formatOrByteSize) throws IOException + { + StringBuffer arg = new StringBuffer(); + + arg.append(__modes.charAt(fileType)); + arg.append(' '); + if (fileType == LOCAL_FILE_TYPE) + arg.append(formatOrByteSize); + else + arg.append(__modes.charAt(formatOrByteSize)); + + return sendCommand(FTPCommand.TYPE, arg.toString()); + } + + + /** + * A convenience method to send the FTP TYPE command to the server, + * receive the reply, and return the reply code. + *

+ * @param fileType The type of the file (one of the FILE_TYPE + * constants). + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + */ + public int type(int fileType) throws IOException + { + return sendCommand(FTPCommand.TYPE, + __modes.substring(fileType, fileType + 1)); + } + + /*** + * A convenience method to send the FTP STRU command to the server, + * receive the reply, and return the reply code. + *

+ * @param structure The structure of the file (one of the + * _STRUCTURE constants). + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stru(int structure) throws IOException + { + return sendCommand(FTPCommand.STRU, + __modes.substring(structure, structure + 1)); + } + + /*** + * A convenience method to send the FTP MODE command to the server, + * receive the reply, and return the reply code. + *

+ * @param mode The transfer mode to use (one of the + * TRANSFER_MODE constants). + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int mode(int mode) throws IOException + { + return sendCommand(FTPCommand.MODE, + __modes.substring(mode, mode + 1)); + } + + /*** + * A convenience method to send the FTP RETR command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @param pathname The pathname of the file to retrieve. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int retr(String pathname) throws IOException + { + return sendCommand(FTPCommand.RETR, pathname); + } + + /*** + * A convenience method to send the FTP STOR command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @param pathname The pathname to use for the file when stored at + * the remote end of the transfer. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stor(String pathname) throws IOException + { + return sendCommand(FTPCommand.STOR, pathname); + } + + /*** + * A convenience method to send the FTP STOU command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stou() throws IOException + { + return sendCommand(FTPCommand.STOU); + } + + /*** + * A convenience method to send the FTP STOU command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + * @param pathname The base pathname to use for the file when stored at + * the remote end of the transfer. Some FTP servers + * require this. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + */ + public int stou(String pathname) throws IOException + { + return sendCommand(FTPCommand.STOU, pathname); + } + + /*** + * A convenience method to send the FTP APPE command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @param pathname The pathname to use for the file when stored at + * the remote end of the transfer. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int appe(String pathname) throws IOException + { + return sendCommand(FTPCommand.APPE, pathname); + } + + /*** + * A convenience method to send the FTP ALLO command to the server, + * receive the reply, and return the reply code. + *

+ * @param bytes The number of bytes to allocate. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int allo(int bytes) throws IOException + { + return sendCommand(FTPCommand.ALLO, Integer.toString(bytes)); + } + + /*** + * A convenience method to send the FTP ALLO command to the server, + * receive the reply, and return the reply code. + *

+ * @param bytes The number of bytes to allocate. + * @param recordSize The size of a record. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int allo(int bytes, int recordSize) throws IOException + { + return sendCommand(FTPCommand.ALLO, Integer.toString(bytes) + " R " + + Integer.toString(recordSize)); + } + + /*** + * A convenience method to send the FTP REST command to the server, + * receive the reply, and return the reply code. + *

+ * @param marker The marker at which to restart a transfer. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rest(String marker) throws IOException + { + return sendCommand(FTPCommand.REST, marker); + } + + + /** + * @since 2.0 + **/ + public int mdtm(String file) throws IOException + { + return sendCommand(FTPCommand.MDTM, file); + } + + /*** + * A convenience method to send the FTP RNFR command to the server, + * receive the reply, and return the reply code. + *

+ * @param pathname The pathname to rename from. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rnfr(String pathname) throws IOException + { + return sendCommand(FTPCommand.RNFR, pathname); + } + + /*** + * A convenience method to send the FTP RNTO command to the server, + * receive the reply, and return the reply code. + *

+ * @param pathname The pathname to rename to + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rnto(String pathname) throws IOException + { + return sendCommand(FTPCommand.RNTO, pathname); + } + + /*** + * A convenience method to send the FTP DELE command to the server, + * receive the reply, and return the reply code. + *

+ * @param pathname The pathname to delete. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int dele(String pathname) throws IOException + { + return sendCommand(FTPCommand.DELE, pathname); + } + + /*** + * A convenience method to send the FTP RMD command to the server, + * receive the reply, and return the reply code. + *

+ * @param pathname The pathname of the directory to remove. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rmd(String pathname) throws IOException + { + return sendCommand(FTPCommand.RMD, pathname); + } + + /*** + * A convenience method to send the FTP MKD command to the server, + * receive the reply, and return the reply code. + *

+ * @param pathname The pathname of the new directory to create. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int mkd(String pathname) throws IOException + { + return sendCommand(FTPCommand.MKD, pathname); + } + + /*** + * A convenience method to send the FTP PWD command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int pwd() throws IOException + { + return sendCommand(FTPCommand.PWD); + } + + /*** + * A convenience method to send the FTP LIST command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int list() throws IOException + { + return sendCommand(FTPCommand.LIST); + } + + /*** + * A convenience method to send the FTP LIST command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @param pathname The pathname to list. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int list(String pathname) throws IOException + { + return sendCommand(FTPCommand.LIST, pathname); + } + + /*** + * A convenience method to send the FTP NLST command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int nlst() throws IOException + { + return sendCommand(FTPCommand.NLST); + } + + /*** + * A convenience method to send the FTP NLST command to the server, + * receive the reply, and return the reply code. Remember, it is up + * to you to manage the data connection. If you don't need this low + * level of access, use {@link org.apache.commons.net.ftp.FTPClient} + * , which will handle all low level details for you. + *

+ * @param pathname The pathname to list. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int nlst(String pathname) throws IOException + { + return sendCommand(FTPCommand.NLST, pathname); + } + + /*** + * A convenience method to send the FTP SITE command to the server, + * receive the reply, and return the reply code. + *

+ * @param parameters The site parameters to send. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int site(String parameters) throws IOException + { + return sendCommand(FTPCommand.SITE, parameters); + } + + /*** + * A convenience method to send the FTP SYST command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int syst() throws IOException + { + return sendCommand(FTPCommand.SYST); + } + + /*** + * A convenience method to send the FTP STAT command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stat() throws IOException + { + return sendCommand(FTPCommand.STAT); + } + + /*** + * A convenience method to send the FTP STAT command to the server, + * receive the reply, and return the reply code. + *

+ * @param pathname A pathname to list. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stat(String pathname) throws IOException + { + return sendCommand(FTPCommand.STAT, pathname); + } + + /*** + * A convenience method to send the FTP HELP command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int help() throws IOException + { + return sendCommand(FTPCommand.HELP); + } + + /*** + * A convenience method to send the FTP HELP command to the server, + * receive the reply, and return the reply code. + *

+ * @param command The command name on which to request help. + * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int help(String command) throws IOException + { + return sendCommand(FTPCommand.HELP, command); + } + + /*** + * A convenience method to send the FTP NOOP command to the server, + * receive the reply, and return the reply code. + *

+ * @return The reply code received from the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int noop() throws IOException + { + return sendCommand(FTPCommand.NOOP); + } + + /** + * Return whether strict multiline parsing is enabled, as per RFX 959, section 4.2. + * @return True if strict, false if lenient + * @since 2.0 + */ + public boolean isStrictMultilineParsing() { + return strictMultilineParsing; + } + + /** + * Set strict multiline parsing. + * @param strictMultilineParsing + * @since 2.0 + */ + public void setStrictMultilineParsing(boolean strictMultilineParsing) { + this.strictMultilineParsing = strictMultilineParsing; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/src/org/apache/commons/net/ftp/FTPClient.java b/src/org/apache/commons/net/ftp/FTPClient.java new file mode 100644 index 0000000..9a2c459 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPClient.java @@ -0,0 +1,2447 @@ +/* + * 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.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; + +import org.apache.commons.net.MalformedServerReplyException; +import org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory; +import org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory; +import org.apache.commons.net.ftp.parser.ParserInitializationException; +import org.apache.commons.net.io.CopyStreamEvent; +import org.apache.commons.net.io.CopyStreamException; +import org.apache.commons.net.io.FromNetASCIIInputStream; +import org.apache.commons.net.io.ToNetASCIIOutputStream; +import org.apache.commons.net.io.Util; + +/*** + * FTPClient encapsulates all the functionality necessary to store and + * retrieve files from an FTP server. This class takes care of all + * low level details of interacting with an FTP server and provides + * a convenient higher level interface. As with all classes derived + * from {@link org.apache.commons.net.SocketClient}, + * you must first connect to the server with + * {@link org.apache.commons.net.SocketClient#connect connect } + * before doing anything, and finally + * {@link org.apache.commons.net.SocketClient#disconnect disconnect } + * after you're completely finished interacting with the server. + * Then you need to check the FTP reply code to see if the connection + * was successful. For example: + *

+ *    boolean error = false;
+ *    try {
+ *      int reply;
+ *      ftp.connect("ftp.foobar.com");
+ *      System.out.println("Connected to " + server + ".");
+ *      System.out.print(ftp.getReplyString());
+ *
+ *      // After connection attempt, you should check the reply code to verify
+ *      // success.
+ *      reply = ftp.getReplyCode();
+ *
+ *      if(!FTPReply.isPositiveCompletion(reply)) {
+ *        ftp.disconnect();
+ *        System.err.println("FTP server refused connection.");
+ *        System.exit(1);
+ *      }
+ *      ... // transfer files
+ *      ftp.logout();
+ *    } catch(IOException e) {
+ *      error = true;
+ *      e.printStackTrace();
+ *    } finally {
+ *      if(ftp.isConnected()) {
+ *        try {
+ *          ftp.disconnect();
+ *        } catch(IOException ioe) {
+ *          // do nothing
+ *        }
+ *      }
+ *      System.exit(error ? 1 : 0);
+ *    }
+ * 
+ *

+ * Immediately after connecting is the only real time you need to check the + * reply code (because connect is of type void). The convention for all the + * FTP command methods in FTPClient is such that they either return a + * boolean value or some other value. + * The boolean methods return true on a successful completion reply from + * the FTP server and false on a reply resulting in an error condition or + * failure. The methods returning a value other than boolean return a value + * containing the higher level data produced by the FTP command, or null if a + * reply resulted in an error condition or failure. If you want to access + * the exact FTP reply code causing a success or failure, you must call + * {@link org.apache.commons.net.ftp.FTP#getReplyCode getReplyCode } after + * a success or failure. + *

+ * The default settings for FTPClient are for it to use + * FTP.ASCII_FILE_TYPE , + * FTP.NON_PRINT_TEXT_FORMAT , + * FTP.STREAM_TRANSFER_MODE , and + * FTP.FILE_STRUCTURE . The only file types directly supported + * are FTP.ASCII_FILE_TYPE and + * FTP.BINARY_FILE_TYPE . Because there are at least 4 + * different EBCDIC encodings, we have opted not to provide direct support + * for EBCDIC. To transfer EBCDIC and other unsupported file types you + * must create your own filter InputStreams and OutputStreams and wrap + * them around the streams returned or required by the FTPClient methods. + * FTPClient uses the {@link ToNetASCIIOutputStream NetASCII} + * filter streams to provide transparent handling of ASCII files. We will + * consider incorporating EBCDIC support if there is enough demand. + *

+ * FTP.NON_PRINT_TEXT_FORMAT , + * FTP.STREAM_TRANSFER_MODE , and + * FTP.FILE_STRUCTURE are the only supported formats, + * transfer modes, and file structures. + *

+ * Because the handling of sockets on different platforms can differ + * significantly, the FTPClient automatically issues a new PORT command + * prior to every transfer requiring that the server connect to the client's + * data port. This ensures identical problem-free behavior on Windows, Unix, + * and Macintosh platforms. Additionally, it relieves programmers from + * having to issue the PORT command themselves and dealing with platform + * dependent issues. + *

+ * Additionally, for security purposes, all data connections to the + * client are verified to ensure that they originated from the intended + * party (host and port). If a data connection is initiated by an unexpected + * party, the command will close the socket and throw an IOException. You + * may disable this behavior with + * {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}. + *

+ * You should keep in mind that the FTP server may choose to prematurely + * close a connection if the client has been idle for longer than a + * given time period (usually 900 seconds). The FTPClient class will detect a + * premature FTP server connection closing when it receives a + * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } + * response to a command. + * When that occurs, the FTP class method encountering that reply will throw + * an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} + * . + * FTPConnectionClosedException + * is a subclass of IOException and therefore need not be + * caught separately, but if you are going to catch it separately, its + * catch block must appear before the more general IOException + * catch block. When you encounter an + * {@link org.apache.commons.net.ftp.FTPConnectionClosedException} + * , you must disconnect the connection with + * {@link #disconnect disconnect() } to properly clean up the + * system resources used by FTPClient. Before disconnecting, you may check the + * last reply code and text with + * {@link org.apache.commons.net.ftp.FTP#getReplyCode getReplyCode }, + * {@link org.apache.commons.net.ftp.FTP#getReplyString getReplyString }, + * and + * {@link org.apache.commons.net.ftp.FTP#getReplyStrings getReplyStrings}. + * You may avoid server disconnections while the client is idle by + * periodicaly sending NOOP commands to the server. + *

+ * Rather than list it separately for each method, we mention here that + * every method communicating with the server and throwing an IOException + * can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} + * , which is a subclass + * of IOException. A MalformedServerReplyException will be thrown when + * the reply received from the server deviates enough from the protocol + * specification that it cannot be interpreted in a useful manner despite + * attempts to be as lenient as possible. + *

+ * Listing API Examples + * Both paged and unpaged examples of directory listings are available, + * as follows: + *

+ * Unpaged (whole list) access, using a parser accessible by auto-detect: + *

+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = listFiles(directory);
+ * 
+ *

+ * Paged access, using a parser not accessible by auto-detect. The class + * defined in the first parameter of initateListParsing should be derived + * from org.apache.commons.net.FTPFileEntryParser: + *

+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPListParseEngine engine =
+ *       f.initiateListParsing("com.whatever.YourOwnParser", directory);
+ *
+ *    while (engine.hasNext()) {
+ *       FTPFile[] files = engine.getNext(25);  // "page size" you want
+ *       //do whatever you want with these files, display them, etc.
+ *       //expensive FTPFile objects not created until needed.
+ *    }
+ * 
+ *

+ * Paged access, using a parser accessible by auto-detect: + *

+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPListParseEngine engine = f.initiateListParsing(directory);
+ *
+ *    while (engine.hasNext()) {
+ *       FTPFile[] files = engine.getNext(25);  // "page size" you want
+ *       //do whatever you want with these files, display them, etc.
+ *       //expensive FTPFile objects not created until needed.
+ *    }
+ * 
+ *

+ * For examples of using FTPClient on servers whose directory listings + *

    + *
  • use languages other than English
  • + *
  • use date formats other than the American English "standard" MM d yyyy
  • + *
  • are in different timezones and you need accurate timestamps for dependency checking + * as in Ant
  • + *
see {@link FTPClientConfig FTPClientConfig}. + *

+ * @author Daniel F. Savarese + * @author Rory Winston + * @see FTP + * @see FTPConnectionClosedException + * @see FTPFileEntryParser + * @see FTPFileEntryParserFactory + * @see DefaultFTPFileEntryParserFactory + * @see FTPClientConfig + * + * @see org.apache.commons.net.MalformedServerReplyException + **/ +public class FTPClient extends FTP +implements Configurable +{ + /*** + * A constant indicating the FTP session is expecting all transfers + * to occur between the client (local) and server and that the server + * should connect to the client's data port to initiate a data transfer. + * This is the default data connection mode when and FTPClient instance + * is created. + ***/ + public static final int ACTIVE_LOCAL_DATA_CONNECTION_MODE = 0; + /*** + * A constant indicating the FTP session is expecting all transfers + * to occur between two remote servers and that the server + * the client is connected to should connect to the other server's + * data port to initiate a data transfer. + ***/ + public static final int ACTIVE_REMOTE_DATA_CONNECTION_MODE = 1; + /*** + * A constant indicating the FTP session is expecting all transfers + * to occur between the client (local) and server and that the server + * is in passive mode, requiring the client to connect to the + * server's data port to initiate a transfer. + ***/ + public static final int PASSIVE_LOCAL_DATA_CONNECTION_MODE = 2; + /*** + * A constant indicating the FTP session is expecting all transfers + * to occur between two remote servers and that the server + * the client is connected to is in passive mode, requiring the other + * server to connect to the first server's data port to initiate a data + * transfer. + ***/ + public static final int PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3; + + private int __dataConnectionMode, __dataTimeout; + private int __passivePort; + private String __passiveHost; + private int __fileType, __fileFormat, __fileStructure, __fileTransferMode; + private boolean __remoteVerificationEnabled; + private long __restartOffset; + private FTPFileEntryParserFactory __parserFactory; + private int __bufferSize; + private boolean __listHiddenFiles; + + // __systemName is a cached value that should not be referenced directly + // except when assigned in getSystemName and __initDefaults. + private String __systemName; + + // __entryParser is a cached value that should not be referenced directly + // except when assigned in listFiles(String, String) and __initDefaults. + private FTPFileEntryParser __entryParser; + + private FTPClientConfig __configuration; + + /** Pattern for PASV mode responses */ + private static String __parms = "\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}"; + private static java.util.regex.Pattern __parms_pat; + static { + __parms_pat = java.util.regex.Pattern.compile(__parms); + } + + /*** + * Default FTPClient constructor. Creates a new FTPClient instance + * with the data connection mode set to + * ACTIVE_LOCAL_DATA_CONNECTION_MODE , the file type + * set to FTP.ASCII_FILE_TYPE , the + * file format set to FTP.NON_PRINT_TEXT_FORMAT , + * the file structure set to FTP.FILE_STRUCTURE , and + * the transfer mode set to FTP.STREAM_TRANSFER_MODE . + ***/ + public FTPClient() + { + __initDefaults(); + __dataTimeout = -1; + __remoteVerificationEnabled = true; + __parserFactory = new DefaultFTPFileEntryParserFactory(); + __configuration = null; + __listHiddenFiles = false; + } + + + private void __initDefaults() + { + __dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; + __passiveHost = null; + __passivePort = -1; + __fileType = FTP.ASCII_FILE_TYPE; + __fileStructure = FTP.FILE_STRUCTURE; + __fileFormat = FTP.NON_PRINT_TEXT_FORMAT; + __fileTransferMode = FTP.STREAM_TRANSFER_MODE; + __restartOffset = 0; + __systemName = null; + __entryParser = null; + __bufferSize = Util.DEFAULT_COPY_BUFFER_SIZE; + } + + private String __parsePathname(String reply) + { + int begin, end; + + begin = reply.indexOf('"') + 1; + end = reply.indexOf('"', begin); + + return reply.substring(begin, end); + } + + + private void __parsePassiveModeReply(String reply) + throws MalformedServerReplyException + { + java.util.regex.Matcher m = __parms_pat.matcher(reply); + if (!m.find()) { + throw new MalformedServerReplyException( + "Could not parse passive host information.\nServer Reply: " + reply); + } + reply = m.group(); + String parts[] = m.group().split(","); + + __passiveHost = parts[0] + '.' + parts[1] + '.' + parts[2] + '.' + parts[3]; + + try + { + int oct1 = Integer.parseInt(parts[4]); + int oct2 = Integer.parseInt(parts[5]); + __passivePort = (oct1 << 8) | oct2; + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse passive host information.\nServer Reply: " + reply); + } + + } + + private boolean __storeFile(int command, String remote, InputStream local) + throws IOException + { + OutputStream output; + Socket socket; + + if ((socket = _openDataConnection_(command, remote)) == null) + return false; + + output = new BufferedOutputStream(socket.getOutputStream(), + getBufferSize() + ); + if (__fileType == ASCII_FILE_TYPE) + output = new ToNetASCIIOutputStream(output); + // Treat everything else as binary for now + try + { + Util.copyStream(local, output, getBufferSize(), + CopyStreamEvent.UNKNOWN_STREAM_SIZE, null, + false); + } + catch (IOException e) + { + try + { + socket.close(); + } + catch (IOException f) + {} + throw e; + } + output.close(); + socket.close(); + return completePendingCommand(); + } + + private OutputStream __storeFileStream(int command, String remote) + throws IOException + { + OutputStream output; + Socket socket; + + if ((socket = _openDataConnection_(command, remote)) == null) + return null; + + output = socket.getOutputStream(); + if (__fileType == ASCII_FILE_TYPE) { + // We buffer ascii transfers because the buffering has to + // be interposed between ToNetASCIIOutputSream and the underlying + // socket output stream. We don't buffer binary transfers + // because we don't want to impose a buffering policy on the + // programmer if possible. Programmers can decide on their + // own if they want to wrap the SocketOutputStream we return + // for file types other than ASCII. + output = new BufferedOutputStream(output, + getBufferSize()); + output = new ToNetASCIIOutputStream(output); + + } + return new org.apache.commons.net.io.SocketOutputStream(socket, output); + } + + + /** + * Establishes a data connection with the FTP server, returning + * a Socket for the connection if successful. If a restart + * offset has been set with {@link #setRestartOffset(long)}, + * a REST command is issued to the server with the offset as + * an argument before establishing the data connection. Active + * mode connections also cause a local PORT command to be issued. + *

+ * @param command The text representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is + * set to null, then the command is sent with no argument. + * @return A Socket corresponding to the established data connection. + * Null is returned if an FTP protocol error is reported at + * any point during the establishment and initialization of + * the connection. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + */ + protected Socket _openDataConnection_(int command, String arg) + throws IOException + { + Socket socket; + + if (__dataConnectionMode != ACTIVE_LOCAL_DATA_CONNECTION_MODE && + __dataConnectionMode != PASSIVE_LOCAL_DATA_CONNECTION_MODE) + return null; + + if (__dataConnectionMode == ACTIVE_LOCAL_DATA_CONNECTION_MODE) + { + ServerSocket server; + server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress()); + + if (!FTPReply.isPositiveCompletion(port(getLocalAddress(), + server.getLocalPort()))) + { + server.close(); + return null; + } + + if ((__restartOffset > 0) && !restart(__restartOffset)) + { + server.close(); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) + { + server.close(); + return null; + } + + // For now, let's just use the data timeout value for waiting for + // the data connection. It may be desirable to let this be a + // separately configurable value. In any case, we really want + // to allow preventing the accept from blocking indefinitely. + if (__dataTimeout >= 0) + server.setSoTimeout(__dataTimeout); + try { + socket = server.accept(); + } finally { + server.close(); + } + } + else + { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE + + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) + return null; + + __parsePassiveModeReply(_replyLines.get(_replyLines.size() - 1)); + + socket = _socketFactory_.createSocket(__passiveHost, __passivePort); + if ((__restartOffset > 0) && !restart(__restartOffset)) + { + socket.close(); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) + { + socket.close(); + return null; + } + } + + if (__remoteVerificationEnabled && !verifyRemote(socket)) + { + InetAddress host1, host2; + + host1 = socket.getInetAddress(); + host2 = getRemoteAddress(); + + socket.close(); + + throw new IOException( + "Host attempting data connection " + host1.getHostAddress() + + " is not same as server " + host2.getHostAddress()); + } + + if (__dataTimeout >= 0) + socket.setSoTimeout(__dataTimeout); + + return socket; + } + + + @Override + protected void _connectAction_() throws IOException + { + super._connectAction_(); + __initDefaults(); + } + + + /*** + * Sets the timeout in milliseconds to use when reading from the + * data connection. This timeout will be set immediately after + * opening the data connection. + *

+ * @param timeout The default timeout in milliseconds that is used when + * opening a data connection socket. + ***/ + public void setDataTimeout(int timeout) + { + __dataTimeout = timeout; + } + + /** + * set the factory used for parser creation to the supplied factory object. + * + * @param parserFactory + * factory object used to create FTPFileEntryParsers + * + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + */ + public void setParserFactory(FTPFileEntryParserFactory parserFactory) { + __parserFactory = parserFactory; + } + + + /*** + * Closes the connection to the FTP server and restores + * connection parameters to the default values. + *

+ * @exception IOException If an error occurs while disconnecting. + ***/ + @Override + public void disconnect() throws IOException + { + super.disconnect(); + __initDefaults(); + } + + + /*** + * Enable or disable verification that the remote host taking part + * of a data connection is the same as the host to which the control + * connection is attached. The default is for verification to be + * enabled. You may set this value at any time, whether the + * FTPClient is currently connected or not. + *

+ * @param enable True to enable verification, false to disable verification. + ***/ + public void setRemoteVerificationEnabled(boolean enable) + { + __remoteVerificationEnabled = enable; + } + + /*** + * Return whether or not verification of the remote host participating + * in data connections is enabled. The default behavior is for + * verification to be enabled. + *

+ * @return True if verification is enabled, false if not. + ***/ + public boolean isRemoteVerificationEnabled() + { + return __remoteVerificationEnabled; + } + + /*** + * Login to the FTP server using the provided username and password. + *

+ * @param username The username to login under. + * @param password The password to use. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean login(String username, String password) throws IOException + { + user(username); + + if (FTPReply.isPositiveCompletion(_replyCode)) + return true; + + // If we get here, we either have an error code, or an intermmediate + // reply requesting password. + if (!FTPReply.isPositiveIntermediate(_replyCode)) + return false; + + return FTPReply.isPositiveCompletion(pass(password)); + } + + + /*** + * Login to the FTP server using the provided username, password, + * and account. If no account is required by the server, only + * the username and password, the account information is not used. + *

+ * @param username The username to login under. + * @param password The password to use. + * @param account The account to use. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean login(String username, String password, String account) + throws IOException + { + user(username); + + if (FTPReply.isPositiveCompletion(_replyCode)) + return true; + + // If we get here, we either have an error code, or an intermmediate + // reply requesting password. + if (!FTPReply.isPositiveIntermediate(_replyCode)) + return false; + + pass(password); + + if (FTPReply.isPositiveCompletion(_replyCode)) + return true; + + if (!FTPReply.isPositiveIntermediate(_replyCode)) + return false; + + return FTPReply.isPositiveCompletion(acct(account)); + } + + /*** + * Logout of the FTP server by sending the QUIT command. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean logout() throws IOException + { + return FTPReply.isPositiveCompletion(quit()); + } + + + /*** + * Change the current working directory of the FTP session. + *

+ * @param pathname The new current working directory. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean changeWorkingDirectory(String pathname) throws IOException + { + return FTPReply.isPositiveCompletion(cwd(pathname)); + } + + + /*** + * Change to the parent directory of the current working directory. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean changeToParentDirectory() throws IOException + { + return FTPReply.isPositiveCompletion(cdup()); + } + + + /*** + * Issue the FTP SMNT command. + *

+ * @param pathname The pathname to mount. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean structureMount(String pathname) throws IOException + { + return FTPReply.isPositiveCompletion(smnt(pathname)); + } + + /*** + * Reinitialize the FTP session. Not all FTP servers support this + * command, which issues the FTP REIN command. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + boolean reinitialize() throws IOException + { + rein(); + + if (FTPReply.isPositiveCompletion(_replyCode) || + (FTPReply.isPositivePreliminary(_replyCode) && + FTPReply.isPositiveCompletion(getReply()))) + { + + __initDefaults(); + + return true; + } + + return false; + } + + + /*** + * Set the current data connection mode to + * ACTIVE_LOCAL_DATA_CONNECTION_MODE. No communication + * with the FTP server is conducted, but this causes all future data + * transfers to require the FTP server to connect to the client's + * data port. Additionally, to accommodate differences between socket + * implementations on different platforms, this method causes the + * client to issue a PORT command before every data transfer. + ***/ + public void enterLocalActiveMode() + { + __dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; + __passiveHost = null; + __passivePort = -1; + } + + + /*** + * Set the current data connection mode to + * PASSIVE_LOCAL_DATA_CONNECTION_MODE . Use this + * method only for data transfers between the client and server. + * This method causes a PASV command to be issued to the server + * before the opening of every data connection, telling the server to + * open a data port to which the client will connect to conduct + * data transfers. The FTPClient will stay in + * PASSIVE_LOCAL_DATA_CONNECTION_MODE until the + * mode is changed by calling some other method such as + * {@link #enterLocalActiveMode enterLocalActiveMode() } + ***/ + public void enterLocalPassiveMode() + { + __dataConnectionMode = PASSIVE_LOCAL_DATA_CONNECTION_MODE; + // These will be set when just before a data connection is opened + // in _openDataConnection_() + __passiveHost = null; + __passivePort = -1; + } + + + /*** + * Set the current data connection mode to + * ACTIVE_REMOTE_DATA_CONNECTION . Use this method only + * for server to server data transfers. This method issues a PORT + * command to the server, indicating the other server and port to which + * it should connect for data transfers. You must call this method + * before EVERY server to server transfer attempt. The FTPClient will + * NOT automatically continue to issue PORT commands. You also + * must remember to call + * {@link #enterLocalActiveMode enterLocalActiveMode() } if you + * wish to return to the normal data connection mode. + *

+ * @param host The passive mode server accepting connections for data + * transfers. + * @param port The passive mode server's data port. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean enterRemoteActiveMode(InetAddress host, int port) + throws IOException + { + if (FTPReply.isPositiveCompletion(port(host, port))) + { + __dataConnectionMode = ACTIVE_REMOTE_DATA_CONNECTION_MODE; + __passiveHost = null; + __passivePort = -1; + return true; + } + return false; + } + + /*** + * Set the current data connection mode to + * PASSIVE_REMOTE_DATA_CONNECTION_MODE . Use this + * method only for server to server data transfers. + * This method issues a PASV command to the server, telling it to + * open a data port to which the active server will connect to conduct + * data transfers. You must call this method + * before EVERY server to server transfer attempt. The FTPClient will + * NOT automatically continue to issue PASV commands. You also + * must remember to call + * {@link #enterLocalActiveMode enterLocalActiveMode() } if you + * wish to return to the normal data connection mode. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean enterRemotePassiveMode() throws IOException + { + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) + return false; + + __dataConnectionMode = PASSIVE_REMOTE_DATA_CONNECTION_MODE; + __parsePassiveModeReply(_replyLines.get(0)); + + return true; + } + + /*** + * Returns the hostname or IP address (in the form of a string) returned + * by the server when entering passive mode. If not in passive mode, + * returns null. This method only returns a valid value AFTER a + * data connection has been opened after a call to + * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + * This is because FTPClient sends a PASV command to the server only + * just before opening a data connection, and not when you call + * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + *

+ * @return The passive host name if in passive mode, otherwise null. + ***/ + public String getPassiveHost() + { + return __passiveHost; + } + + /*** + * If in passive mode, returns the data port of the passive host. + * This method only returns a valid value AFTER a + * data connection has been opened after a call to + * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + * This is because FTPClient sends a PASV command to the server only + * just before opening a data connection, and not when you call + * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + *

+ * @return The data port of the passive server. If not in passive + * mode, undefined. + ***/ + public int getPassivePort() + { + return __passivePort; + } + + + /*** + * Returns the current data connection mode (one of the + * _DATA_CONNECTION_MODE constants. + *

+ * @return The current data connection mode (one of the + * _DATA_CONNECTION_MODE constants. + ***/ + public int getDataConnectionMode() + { + return __dataConnectionMode; + } + + + /*** + * Sets the file type to be transferred. This should be one of + * FTP.ASCII_FILE_TYPE , FTP.BINARY_FILE_TYPE, + * etc. The file type only needs to be set when you want to change the + * type. After changing it, the new type stays in effect until you change + * it again. The default file type is FTP.ASCII_FILE_TYPE + * if this method is never called. + *

+ * @param fileType The _FILE_TYPE constant indcating the + * type of file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean setFileType(int fileType) throws IOException + { + if (FTPReply.isPositiveCompletion(type(fileType))) + { + __fileType = fileType; + __fileFormat = FTP.NON_PRINT_TEXT_FORMAT; + return true; + } + return false; + } + + + /*** + * Sets the file type to be transferred and the format. The type should be + * one of FTP.ASCII_FILE_TYPE , + * FTP.BINARY_FILE_TYPE , etc. The file type only needs to + * be set when you want to change the type. After changing it, the new + * type stays in effect until you change it again. The default file type + * is FTP.ASCII_FILE_TYPE if this method is never called. + * The format should be one of the FTP class TEXT_FORMAT + * constants, or if the type is FTP.LOCAL_FILE_TYPE , the + * format should be the byte size for that type. The default format + * is FTP.NON_PRINT_TEXT_FORMAT if this method is never + * called. + *

+ * @param fileType The _FILE_TYPE constant indcating the + * type of file. + * @param formatOrByteSize The format of the file (one of the + * _FORMAT constants. In the case of + * LOCAL_FILE_TYPE, the byte size. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean setFileType(int fileType, int formatOrByteSize) + throws IOException + { + if (FTPReply.isPositiveCompletion(type(fileType, formatOrByteSize))) + { + __fileType = fileType; + __fileFormat = formatOrByteSize; + return true; + } + return false; + } + + + /*** + * Sets the file structure. The default structure is + * FTP.FILE_STRUCTURE if this method is never called. + *

+ * @param structure The structure of the file (one of the FTP class + * _STRUCTURE constants). + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean setFileStructure(int structure) throws IOException + { + if (FTPReply.isPositiveCompletion(stru(structure))) + { + __fileStructure = structure; + return true; + } + return false; + } + + + /*** + * Sets the transfer mode. The default transfer mode + * FTP.STREAM_TRANSFER_MODE if this method is never called. + *

+ * @param mode The new transfer mode to use (one of the FTP class + * _TRANSFER_MODE constants). + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean setFileTransferMode(int mode) throws IOException + { + if (FTPReply.isPositiveCompletion(mode(mode))) + { + __fileTransferMode = mode; + return true; + } + return false; + } + + + /*** + * Initiate a server to server file transfer. This method tells the + * server to which the client is connected to retrieve a given file from + * the other server. + *

+ * @param filename The name of the file to retrieve. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean remoteRetrieve(String filename) throws IOException + { + if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || + __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) + return FTPReply.isPositivePreliminary(retr(filename)); + return false; + } + + + /*** + * Initiate a server to server file transfer. This method tells the + * server to which the client is connected to store a file on + * the other server using the given filename. The other server must + * have had a remoteRetrieve issued to it by another + * FTPClient. + *

+ * @param filename The name to call the file that is to be stored. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean remoteStore(String filename) throws IOException + { + if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || + __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) + return FTPReply.isPositivePreliminary(stor(filename)); + return false; + } + + + /*** + * Initiate a server to server file transfer. This method tells the + * server to which the client is connected to store a file on + * the other server using a unique filename based on the given filename. + * The other server must have had a remoteRetrieve issued + * to it by another FTPClient. + *

+ * @param filename The name on which to base the filename of the file + * that is to be stored. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean remoteStoreUnique(String filename) throws IOException + { + if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || + __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) + return FTPReply.isPositivePreliminary(stou(filename)); + return false; + } + + + /*** + * Initiate a server to server file transfer. This method tells the + * server to which the client is connected to store a file on + * the other server using a unique filename. + * The other server must have had a remoteRetrieve issued + * to it by another FTPClient. Many FTP servers require that a base + * filename be given from which the unique filename can be derived. For + * those servers use the other version of remoteStoreUnique + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean remoteStoreUnique() throws IOException + { + if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || + __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) + return FTPReply.isPositivePreliminary(stou()); + return false; + } + + // For server to server transfers + /*** + * Initiate a server to server file transfer. This method tells the + * server to which the client is connected to append to a given file on + * the other server. The other server must have had a + * remoteRetrieve issued to it by another FTPClient. + *

+ * @param filename The name of the file to be appended to, or if the + * file does not exist, the name to call the file being stored. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean remoteAppend(String filename) throws IOException + { + if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || + __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) + return FTPReply.isPositivePreliminary(stor(filename)); + return false; + } + + /*** + * There are a few FTPClient methods that do not complete the + * entire sequence of FTP commands to complete a transaction. These + * commands require some action by the programmer after the reception + * of a positive intermediate command. After the programmer's code + * completes its actions, it must call this method to receive + * the completion reply from the server and verify the success of the + * entire transaction. + *

+ * For example, + *

+     * InputStream input;
+     * OutputStream output;
+     * input  = new FileInputStream("foobaz.txt");
+     * output = ftp.storeFileStream("foobar.txt")
+     * if(!FTPReply.isPositiveIntermediate(ftp.getReplyCode())) {
+     *     input.close();
+     *     output.close();
+     *     ftp.logout();
+     *     ftp.disconnect();
+     *     System.err.println("File transfer failed.");
+     *     System.exit(1);
+     * }
+     * Util.copyStream(input, output);
+     * input.close();
+     * output.close();
+     * // Must call completePendingCommand() to finish command.
+     * if(!ftp.completePendingCommand()) {
+     *     ftp.logout();
+     *     ftp.disconnect();
+     *     System.err.println("File transfer failed.");
+     *     System.exit(1);
+     * }
+     * 
+ *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean completePendingCommand() throws IOException + { + return FTPReply.isPositiveCompletion(getReply()); + } + + + /*** + * Retrieves a named file from the server and writes it to the given + * OutputStream. This method does NOT close the given OutputStream. + * If the current file type is ASCII, line separators in the file are + * converted to the local representation. + *

+ * @param remote The name of the remote file. + * @param local The local OutputStream to which to write the file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception CopyStreamException If an I/O error occurs while actually + * transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException + * causing the error. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean retrieveFile(String remote, OutputStream local) + throws IOException + { + InputStream input; + Socket socket; + + if ((socket = _openDataConnection_(FTPCommand.RETR, remote)) == null) + return false; + + input = new BufferedInputStream(socket.getInputStream(), + getBufferSize()); + if (__fileType == ASCII_FILE_TYPE) + input = new FromNetASCIIInputStream(input); + // Treat everything else as binary for now + try + { + Util.copyStream(input, local, getBufferSize(), + CopyStreamEvent.UNKNOWN_STREAM_SIZE, null, + false); + } + catch (IOException e) + { + try + { + socket.close(); + } + catch (IOException f) + {} + throw e; + } + socket.close(); + return completePendingCommand(); + } + + /*** + * Returns an InputStream from which a named file from the server + * can be read. If the current file type is ASCII, the returned + * InputStream will convert line separators in the file to + * the local representation. You must close the InputStream when you + * finish reading from it. The InputStream itself will take care of + * closing the parent data connection socket upon being closed. To + * finalize the file transfer you must call + * {@link #completePendingCommand completePendingCommand } and + * check its return value to verify success. + *

+ * @param remote The name of the remote file. + * @return An InputStream from which the remote file can be read. If + * the data connection cannot be opened (e.g., the file does not + * exist), null is returned (in which case you may check the reply + * code to determine the exact reason for failure). + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public InputStream retrieveFileStream(String remote) throws IOException + { + InputStream input; + Socket socket; + + if ((socket = _openDataConnection_(FTPCommand.RETR, remote)) == null) + return null; + + input = socket.getInputStream(); + if (__fileType == ASCII_FILE_TYPE) { + // We buffer ascii transfers because the buffering has to + // be interposed between FromNetASCIIOutputSream and the underlying + // socket input stream. We don't buffer binary transfers + // because we don't want to impose a buffering policy on the + // programmer if possible. Programmers can decide on their + // own if they want to wrap the SocketInputStream we return + // for file types other than ASCII. + input = new BufferedInputStream(input, + getBufferSize()); + input = new FromNetASCIIInputStream(input); + } + return new org.apache.commons.net.io.SocketInputStream(socket, input); + } + + + /*** + * Stores a file on the server using the given name and taking input + * from the given InputStream. This method does NOT close the given + * InputStream. If the current file type is ASCII, line separators in + * the file are transparently converted to the NETASCII format (i.e., + * you should not attempt to create a special InputStream to do this). + *

+ * @param remote The name to give the remote file. + * @param local The local InputStream from which to read the file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception CopyStreamException If an I/O error occurs while actually + * transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException + * causing the error. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean storeFile(String remote, InputStream local) + throws IOException + { + return __storeFile(FTPCommand.STOR, remote, local); + } + + + /*** + * Returns an OutputStream through which data can be written to store + * a file on the server using the given name. If the current file type + * is ASCII, the returned OutputStream will convert line separators in + * the file to the NETASCII format (i.e., you should not attempt to + * create a special OutputStream to do this). You must close the + * OutputStream when you finish writing to it. The OutputStream itself + * will take care of closing the parent data connection socket upon being + * closed. To finalize the file transfer you must call + * {@link #completePendingCommand completePendingCommand } and + * check its return value to verify success. + *

+ * @param remote The name to give the remote file. + * @return An OutputStream through which the remote file can be written. If + * the data connection cannot be opened (e.g., the file does not + * exist), null is returned (in which case you may check the reply + * code to determine the exact reason for failure). + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public OutputStream storeFileStream(String remote) throws IOException + { + return __storeFileStream(FTPCommand.STOR, remote); + } + + /*** + * Appends to a file on the server with the given name, taking input + * from the given InputStream. This method does NOT close the given + * InputStream. If the current file type is ASCII, line separators in + * the file are transparently converted to the NETASCII format (i.e., + * you should not attempt to create a special InputStream to do this). + *

+ * @param remote The name of the remote file. + * @param local The local InputStream from which to read the data to + * be appended to the remote file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception CopyStreamException If an I/O error occurs while actually + * transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException + * causing the error. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean appendFile(String remote, InputStream local) + throws IOException + { + return __storeFile(FTPCommand.APPE, remote, local); + } + + /*** + * Returns an OutputStream through which data can be written to append + * to a file on the server with the given name. If the current file type + * is ASCII, the returned OutputStream will convert line separators in + * the file to the NETASCII format (i.e., you should not attempt to + * create a special OutputStream to do this). You must close the + * OutputStream when you finish writing to it. The OutputStream itself + * will take care of closing the parent data connection socket upon being + * closed. To finalize the file transfer you must call + * {@link #completePendingCommand completePendingCommand } and + * check its return value to verify success. + *

+ * @param remote The name of the remote file. + * @return An OutputStream through which the remote file can be appended. + * If the data connection cannot be opened (e.g., the file does not + * exist), null is returned (in which case you may check the reply + * code to determine the exact reason for failure). + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public OutputStream appendFileStream(String remote) throws IOException + { + return __storeFileStream(FTPCommand.APPE, remote); + } + + /*** + * Stores a file on the server using a unique name derived from the + * given name and taking input + * from the given InputStream. This method does NOT close the given + * InputStream. If the current file type is ASCII, line separators in + * the file are transparently converted to the NETASCII format (i.e., + * you should not attempt to create a special InputStream to do this). + *

+ * @param remote The name on which to base the unique name given to + * the remote file. + * @param local The local InputStream from which to read the file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception CopyStreamException If an I/O error occurs while actually + * transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException + * causing the error. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean storeUniqueFile(String remote, InputStream local) + throws IOException + { + return __storeFile(FTPCommand.STOU, remote, local); + } + + + /*** + * Returns an OutputStream through which data can be written to store + * a file on the server using a unique name derived from the given name. + * If the current file type + * is ASCII, the returned OutputStream will convert line separators in + * the file to the NETASCII format (i.e., you should not attempt to + * create a special OutputStream to do this). You must close the + * OutputStream when you finish writing to it. The OutputStream itself + * will take care of closing the parent data connection socket upon being + * closed. To finalize the file transfer you must call + * {@link #completePendingCommand completePendingCommand } and + * check its return value to verify success. + *

+ * @param remote The name on which to base the unique name given to + * the remote file. + * @return An OutputStream through which the remote file can be written. If + * the data connection cannot be opened (e.g., the file does not + * exist), null is returned (in which case you may check the reply + * code to determine the exact reason for failure). + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public OutputStream storeUniqueFileStream(String remote) throws IOException + { + return __storeFileStream(FTPCommand.STOU, remote); + } + + /** + * Stores a file on the server using a unique name assigned by the + * server and taking input from the given InputStream. This method does + * NOT close the given + * InputStream. If the current file type is ASCII, line separators in + * the file are transparently converted to the NETASCII format (i.e., + * you should not attempt to create a special InputStream to do this). + *

+ * @param local The local InputStream from which to read the file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception CopyStreamException If an I/O error occurs while actually + * transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException + * causing the error. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + */ + public boolean storeUniqueFile(InputStream local) throws IOException + { + return __storeFile(FTPCommand.STOU, null, local); + } + + /** + * Returns an OutputStream through which data can be written to store + * a file on the server using a unique name assigned by the server. + * If the current file type + * is ASCII, the returned OutputStream will convert line separators in + * the file to the NETASCII format (i.e., you should not attempt to + * create a special OutputStream to do this). You must close the + * OutputStream when you finish writing to it. The OutputStream itself + * will take care of closing the parent data connection socket upon being + * closed. To finalize the file transfer you must call + * {@link #completePendingCommand completePendingCommand } and + * check its return value to verify success. + *

+ * @return An OutputStream through which the remote file can be written. If + * the data connection cannot be opened (e.g., the file does not + * exist), null is returned (in which case you may check the reply + * code to determine the exact reason for failure). + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + */ + public OutputStream storeUniqueFileStream() throws IOException + { + return __storeFileStream(FTPCommand.STOU, null); + } + + /*** + * Reserve a number of bytes on the server for the next file transfer. + *

+ * @param bytes The number of bytes which the server should allocate. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean allocate(int bytes) throws IOException + { + return FTPReply.isPositiveCompletion(allo(bytes)); + } + + + /** + * Reserve space on the server for the next file transfer. + *

+ * @param bytes The number of bytes which the server should allocate. + * @param recordSize The size of a file record. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + */ + public boolean allocate(int bytes, int recordSize) throws IOException + { + return FTPReply.isPositiveCompletion(allo(bytes, recordSize)); + } + + + /*** + * Restart a STREAM_TRANSFER_MODE file transfer starting + * from the given offset. This will only work on FTP servers supporting + * the REST comand for the stream transfer mode. However, most FTP + * servers support this. Any subsequent file transfer will start + * reading or writing the remote file from the indicated offset. + *

+ * @param offset The offset into the remote file at which to start the + * next file transfer. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + private boolean restart(long offset) throws IOException + { + __restartOffset = 0; + return FTPReply.isPositiveIntermediate(rest(Long.toString(offset))); + } + + /*** + * Sets the restart offset. The restart command is sent to the server + * only before sending the file transfer command. When this is done, + * the restart marker is reset to zero. + *

+ * @param offset The offset into the remote file at which to start the + * next file transfer. This must be a value greater than or + * equal to zero. + ***/ + public void setRestartOffset(long offset) + { + if (offset >= 0) + __restartOffset = offset; + } + + /*** + * Fetches the restart offset. + *

+ * @return offset The offset into the remote file at which to start the + * next file transfer. + ***/ + public long getRestartOffset() + { + return __restartOffset; + } + + + + /*** + * Renames a remote file. + *

+ * @param from The name of the remote file to rename. + * @param to The new name of the remote file. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean rename(String from, String to) throws IOException + { + if (!FTPReply.isPositiveIntermediate(rnfr(from))) + return false; + + return FTPReply.isPositiveCompletion(rnto(to)); + } + + + /*** + * Abort a transfer in progress. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean abort() throws IOException + { + return FTPReply.isPositiveCompletion(abor()); + } + + /*** + * Deletes a file on the FTP server. + *

+ * @param pathname The pathname of the file to be deleted. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean deleteFile(String pathname) throws IOException + { + return FTPReply.isPositiveCompletion(dele(pathname)); + } + + + /*** + * Removes a directory on the FTP server (if empty). + *

+ * @param pathname The pathname of the directory to remove. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean removeDirectory(String pathname) throws IOException + { + return FTPReply.isPositiveCompletion(rmd(pathname)); + } + + + /*** + * Creates a new subdirectory on the FTP server in the current directory + * (if a relative pathname is given) or where specified (if an absolute + * pathname is given). + *

+ * @param pathname The pathname of the directory to create. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean makeDirectory(String pathname) throws IOException + { + return FTPReply.isPositiveCompletion(mkd(pathname)); + } + + + /*** + * Returns the pathname of the current working directory. + *

+ * @return The pathname of the current working directory. If it cannot + * be obtained, returns null. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String printWorkingDirectory() throws IOException + { + if (pwd() != FTPReply.PATHNAME_CREATED) + return null; + + return __parsePathname(_replyLines.get( _replyLines.size() - 1)); + } + + + /** + * Send a site specific command. + * @param arguments The site specific command and arguments. + * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + */ + public boolean sendSiteCommand(String arguments) throws IOException + { + return FTPReply.isPositiveCompletion(site(arguments)); + } + + + /*** + * Fetches the system type name from the server and returns the string. + * This value is cached for the duration of the connection after the + * first call to this method. In other words, only the first time + * that you invoke this method will it issue a SYST command to the + * FTP server. FTPClient will remember the value and return the + * cached value until a call to disconnect. + *

+ * @return The system type name obtained from the server. null if the + * information could not be obtained. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String getSystemName() throws IOException + { + //if (syst() == FTPReply.NAME_SYSTEM_TYPE) + // Technically, we should expect a NAME_SYSTEM_TYPE response, but + // in practice FTP servers deviate, so we soften the condition to + // a positive completion. + if (__systemName == null && FTPReply.isPositiveCompletion(syst())) + __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + + return __systemName; + } + + + /*** + * Fetches the system help information from the server and returns the + * full string. + *

+ * @return The system help string obtained from the server. null if the + * information could not be obtained. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String listHelp() throws IOException + { + if (FTPReply.isPositiveCompletion(help())) + return getReplyString(); + return null; + } + + + /** + * Fetches the help information for a given command from the server and + * returns the full string. + * @param command The command on which to ask for help. + * @return The command help string obtained from the server. null if the + * information could not be obtained. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + */ + public String listHelp(String command) throws IOException + { + if (FTPReply.isPositiveCompletion(help(command))) + return getReplyString(); + return null; + } + + + /*** + * Sends a NOOP command to the FTP server. This is useful for preventing + * server timeouts. + *

+ * @return True if successfully completed, false if not. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public boolean sendNoOp() throws IOException + { + return FTPReply.isPositiveCompletion(noop()); + } + + + /*** + * Obtain a list of filenames in a directory (or just the name of a given + * file, which is not particularly useful). This information is obtained + * through the NLST command. If the given pathname is a directory and + * contains no files, a zero length array is returned only + * if the FTP server returned a positive completion code, otherwise + * null is returned (the FTP server returned a 550 error No files found.). + * If the directory is not empty, an array of filenames in the directory is + * returned. If the pathname corresponds + * to a file, only that file will be listed. The server may or may not + * expand glob expressions. + *

+ * @param pathname The file or directory to list. + * @return The list of filenames contained in the given path. null if + * the list could not be obtained. If there are no filenames in + * the directory, a zero-length array is returned. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String[] listNames(String pathname) throws IOException + { + String line; + Socket socket; + BufferedReader reader; + ArrayList results; + + if ((socket = _openDataConnection_(FTPCommand.NLST, pathname)) == null) + return null; + + reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding())); + + results = new ArrayList(); + while ((line = reader.readLine()) != null) + results.add(line); + + reader.close(); + socket.close(); + + if (completePendingCommand()) + { + String[] names = new String[ results.size() ]; + return results.toArray(names); + } + + return null; + } + + + /*** + * Obtain a list of filenames in the current working directory + * This information is obtained through the NLST command. If the current + * directory contains no files, a zero length array is returned only + * if the FTP server returned a positive completion code, otherwise, + * null is returned (the FTP server returned a 550 error No files found.). + * If the directory is not empty, an array of filenames in the directory is + * returned. + *

+ * @return The list of filenames contained in the current working + * directory. null if the list could not be obtained. + * If there are no filenames in the directory, a zero-length array + * is returned. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String[] listNames() throws IOException + { + return listNames(null); + } + + + + /** + * Using the default system autodetect mechanism, obtain a + * list of file information for the current working directory + * or for just a single file. + *

+ * This information is obtained through the LIST command. The contents of + * the returned array is determined by the FTPFileEntryParser + * used. + *

+ * @param pathname The file or directory to list. Since the server may + * or may not expand glob expressions, using them here + * is not recommended and may well cause this method to + * fail. + * + * @return The list of file information contained in the given path in + * the format determined by the autodetection mechanism + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection + * as a result of the client being idle or some other + * reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException + * or independently as itself. + * @exception IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @exception ParserInitializationException + * Thrown if the parserKey parameter cannot be + * resolved by the selected parser factory. + * In the DefaultFTPEntryParserFactory, this will + * happen when parserKey is neither + * the fully qualified class name of a class + * implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser + * nor a string containing one of the recognized keys + * mapping to such a parser or if class loader + * security issues prevent its being loaded. + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles(String pathname) + throws IOException + { + String key = null; + FTPListParseEngine engine = + initiateListParsing(key, pathname); + return engine.getFiles(); + + } + /** + * Using the default system autodetect mechanism, obtain a + * list of file information for the current working directory. + *

+ * This information is obtained through the LIST command. The contents of + * the returned array is determined by the FTPFileEntryParser + * used. + *

+ * @return The list of file information contained in the current directory + * in the format determined by the autodetection mechanism. + *

+ * NOTE: This array may contain null members if any of the + * individual file listings failed to parse. The caller should + * check each entry for null before referencing it. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection + * as a result of the client being idle or some other + * reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException + * or independently as itself. + * @exception IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @exception ParserInitializationException + * Thrown if the parserKey parameter cannot be + * resolved by the selected parser factory. + * In the DefaultFTPEntryParserFactory, this will + * happen when parserKey is neither + * the fully qualified class name of a class + * implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser + * nor a string containing one of the recognized keys + * mapping to such a parser or if class loader + * security issues prevent its being loaded. + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles() + throws IOException + { + return listFiles((String) null); + } + + /** + * Using the default autodetect mechanism, initialize an FTPListParseEngine + * object containing a raw file information for the current working + * directory on the server + * This information is obtained through the LIST command. This object + * is then capable of being iterated to return a sequence of FTPFile + * objects with information filled in by the + * FTPFileEntryParser used. + *

+ * This method differs from using the listFiles() methods in that + * expensive FTPFile objects are not created until needed which may be + * an advantage on large lists. + * + * @return A FTPListParseEngine object that holds the raw information and + * is capable of providing parsed FTPFile objects, one for each file + * containing information contained in the given path in the format + * determined by the parser parameter. Null will be + * returned if a data connection cannot be opened. If the current working + * directory contains no files, an empty array will be the return. + * + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @exception ParserInitializationException + * Thrown if the autodetect mechanism cannot + * resolve the type of system we are connected with. + * @see FTPListParseEngine + */ + public FTPListParseEngine initiateListParsing() + throws IOException + { + return initiateListParsing((String) null); + } + + /** + * Using the default autodetect mechanism, initialize an FTPListParseEngine + * object containing a raw file information for the supplied directory. + * This information is obtained through the LIST command. This object + * is then capable of being iterated to return a sequence of FTPFile + * objects with information filled in by the + * FTPFileEntryParser used. + *

+ * The server may or may not expand glob expressions. You should avoid + * using glob expressions because the return format for glob listings + * differs from server to server and will likely cause this method to fail. + *

+ * This method differs from using the listFiles() methods in that + * expensive FTPFile objects are not created until needed which may be + * an advantage on large lists. + *

+ *

+     *    FTPClient f=FTPClient();
+     *    f.connect(server);
+     *    f.login(username, password);
+     *    FTPListParseEngine engine = f.initiateListParsing(directory);
+     *
+     *    while (engine.hasNext()) {
+     *       FTPFile[] files = engine.getNext(25);  // "page size" you want
+     *       //do whatever you want with these files, display them, etc.
+     *       //expensive FTPFile objects not created until needed.
+     *    }
+     * 
+ * + * @return A FTPListParseEngine object that holds the raw information and + * is capable of providing parsed FTPFile objects, one for each file + * containing information contained in the given path in the format + * determined by the parser parameter. Null will be + * returned if a data connection cannot be opened. If the current working + * directory contains no files, an empty array will be the return. + * + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @exception ParserInitializationException + * Thrown if the autodetect mechanism cannot + * resolve the type of system we are connected with. + * @see FTPListParseEngine + */ + public FTPListParseEngine initiateListParsing( + String pathname) + throws IOException + { + String key = null; + return initiateListParsing(key, pathname); + } + + /** + * Using the supplied parser key, initialize an FTPListParseEngine + * object containing a raw file information for the supplied directory. + * This information is obtained through the LIST command. This object + * is then capable of being iterated to return a sequence of FTPFile + * objects with information filled in by the + * FTPFileEntryParser used. + *

+ * The server may or may not expand glob expressions. You should avoid + * using glob expressions because the return format for glob listings + * differs from server to server and will likely cause this method to fail. + *

+ * This method differs from using the listFiles() methods in that + * expensive FTPFile objects are not created until needed which may be + * an advantage on large lists. + * + * @param parserKey A string representing a designated code or fully-qualified + * class name of an FTPFileEntryParser that should be + * used to parse each server file listing. + * + * @return A FTPListParseEngine object that holds the raw information and + * is capable of providing parsed FTPFile objects, one for each file + * containing information contained in the given path in the format + * determined by the parser parameter. Null will be + * returned if a data connection cannot be opened. If the current working + * directory contains no files, an empty array will be the return. + * + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @exception ParserInitializationException + * Thrown if the parserKey parameter cannot be + * resolved by the selected parser factory. + * In the DefaultFTPEntryParserFactory, this will + * happen when parserKey is neither + * the fully qualified class name of a class + * implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser + * nor a string containing one of the recognized keys + * mapping to such a parser or if class loader + * security issues prevent its being loaded. + * @see FTPListParseEngine + */ + public FTPListParseEngine initiateListParsing( + String parserKey, String pathname) + throws IOException + { + // We cache the value to avoid creation of a new object every + // time a file listing is generated. + if(__entryParser == null) { + if (null != parserKey) { + // if a parser key was supplied in the parameters, + // use that to create the paraser + __entryParser = + __parserFactory.createFileEntryParser(parserKey); + + } else { + // if no parserKey was supplied, check for a configuration + // in the params, and if non-null, use that. + if (null != __configuration) { + __entryParser = + __parserFactory.createFileEntryParser(__configuration); + } else { + // if a parserKey hasn't been supplied, and a configuration + // hasn't been supplied, then autodetect by calling + // the SYST command and use that to choose the parser. + __entryParser = + __parserFactory.createFileEntryParser(getSystemName()); + } + } + } + + return initiateListParsing(__entryParser, pathname); + + } + + + /** + * private method through which all listFiles() and + * initiateListParsing methods pass once a parser is determined. + * + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @see FTPListParseEngine + */ + private FTPListParseEngine initiateListParsing( + FTPFileEntryParser parser, String pathname) + throws IOException + { + Socket socket; + + FTPListParseEngine engine = new FTPListParseEngine(parser); + + if ((socket = _openDataConnection_(FTPCommand.LIST, getListArguments(pathname))) == null) + { + return engine; + } + + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } + finally { + socket.close(); + } + + completePendingCommand(); + return engine; + } + + /** + * @since 2.0 + */ + protected String getListArguments(String pathname) { + if (getListHiddenFiles()) + { + StringBuffer sb = new StringBuffer(pathname.length() + 3); + sb.append("-a "); + sb.append(pathname); + return sb.toString(); + } + + return pathname; + } + + + /*** + * Issue the FTP STAT command to the server. + *

+ * @return The status information returned by the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String getStatus() throws IOException + { + if (FTPReply.isPositiveCompletion(stat())) + return getReplyString(); + return null; + } + + + /*** + * Issue the FTP STAT command to the server for a given pathname. This + * should produce a listing of the file or directory. + *

+ * @return The status information returned by the server. + * @exception FTPConnectionClosedException + * If the FTP server prematurely closes the connection as a result + * of the client being idle or some other reason causing the server + * to send FTP reply code 421. This exception may be caught either + * as an IOException or independently as itself. + * @exception IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + ***/ + public String getStatus(String pathname) throws IOException + { + if (FTPReply.isPositiveCompletion(stat(pathname))) + return getReplyString(); + return null; + } + + + /** + * Issue the FTP MDTM command (not supported by all servers to retrieve the last + * modification time of a file. The modification string should be in the + * ISO 3077 form "YYYYMMDDhhmmss(.xxx)?". The timestamp represented should also be in + * GMT, but not all FTP servers honour this. + * + * @param pathname The file path to query. + * @return A string representing the last file modification time in YYYYMMDDhhmmss format. + * @throws IOException if an I/O error occurs. + * @since 2.0 + */ + public String getModificationTime(String pathname) throws IOException { + if (FTPReply.isPositiveCompletion(mdtm(pathname))) + return getReplyString(); + return null; + } + + + /** + * Set the internal buffer size. + * + * @param bufSize The size of the buffer + */ + public void setBufferSize(int bufSize) { + __bufferSize = bufSize; + } + + /** + * Retrieve the current internal buffer size. + * @return The current buffer size. + */ + public int getBufferSize() { + return __bufferSize; + } + + + /** + * Implementation of the {@link Configurable Configurable} interface. + * In the case of this class, configuring merely makes the config object available for the + * factory methods that construct parsers. + * @param config {@link FTPClientConfig FTPClientConfig} object used to + * provide non-standard configurations to the parser. + * @since 1.4 + */ + public void configure(FTPClientConfig config) { + this.__configuration = config; + } + + /** + * You can set this to true if you would like to get hidden files when {@link #listFiles} too. + * A LIST -a will be issued to the ftp server. + * It depends on your ftp server if you need to call this method, also dont expect to get rid + * of hidden files if you call this method with "false". + * + * @param listHiddenFiles true if hidden files should be listed + * @since 2.0 + */ + public void setListHiddenFiles(boolean listHiddenFiles) { + this.__listHiddenFiles = listHiddenFiles; + } + + /** + * @see #setListHiddenFiles(boolean) + * @return the current state + * @since 2.0 + */ + public boolean getListHiddenFiles() { + return this.__listHiddenFiles; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/src/org/apache/commons/net/ftp/FTPClientConfig.java b/src/org/apache/commons/net/ftp/FTPClientConfig.java new file mode 100644 index 0000000..450eddc --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPClientConfig.java @@ -0,0 +1,580 @@ +/* + * 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.text.DateFormatSymbols; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.TreeMap; + +/** + *

+ * This class implements an alternate means of configuring the + * {@link org.apache.commons.net.ftp.FTPClient FTPClient} object and + * also subordinate objects which it uses. Any class implementing the + * {@link org.apache.commons.net.ftp.Configurable Configurable } + * interface can be configured by this object. + *

+ * In particular this class was designed primarily to support configuration + * of FTP servers which express file timestamps in formats and languages + * other than those for the US locale, which although it is the most common + * is not universal. Unfortunately, nothing in the FTP spec allows this to + * be determined in an automated way, so manual configuration such as this + * is necessary. + *

+ * This functionality was designed to allow existing clients to work exactly + * as before without requiring use of this component. This component should + * only need to be explicitly invoked by the user of this package for problem + * cases that previous implementations could not solve. + *

+ *

Examples of use of FTPClientConfig

+ * Use cases: + * You are trying to access a server that + *
    + *
  • lists files with timestamps that use month names in languages other + * than English
  • + *
  • lists files with timestamps that use date formats other + * than the American English "standard" MM dd yyyy
  • + *
  • is in different timezone and you need accurate timestamps for + * dependency checking as in Ant
  • + *
+ *

+ * Unpaged (whole list) access on a UNIX server that uses French month names + * but uses the "standard" MMM d yyyy date formatting + *

+ *    FTPClient f=FTPClient();
+ *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
+ *    conf.setServerLanguageCode("fr");
+ *    f.configure(conf);
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = listFiles(directory);
+ * 
+ *

+ *

+ * Paged access on a UNIX server that uses Danish month names + * and "European" date formatting in Denmark's time zone, when you + * are in some other time zone. + *

+ *    FTPClient f=FTPClient();
+ *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
+ *    conf.setServerLanguageCode("da");
+ *    conf.setDefaultDateFormat("d MMM yyyy");
+ *    conf.setRecentDateFormat("d MMM HH:mm");
+ *    conf.setTimeZoneId("Europe/Copenhagen");
+ *    f.configure(conf);
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPListParseEngine engine =
+ *       f.initiateListParsing("com.whatever.YourOwnParser", directory);
+ *
+ *    while (engine.hasNext()) {
+ *       FTPFile[] files = engine.getNext(25);  // "page size" you want
+ *       //do whatever you want with these files, display them, etc.
+ *       //expensive FTPFile objects not created until needed.
+ *    }
+ * 
+ *

+ *

+ * Unpaged (whole list) access on a VMS server that uses month names + * in a language not {@link #getSupportedLanguageCodes() supported} by the system. + * but uses the "standard" MMM d yyyy date formatting + *

+ *    FTPClient f=FTPClient();
+ *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_VMS);
+ *    conf.setShortMonthNames(
+ *        "jan|feb|mar|apr|ma\u00ED|j\u00FAn|j\u00FAl|\u00e1g\u00FA|sep|okt|n\u00F3v|des");
+ *    f.configure(conf);
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = listFiles(directory);
+ * 
+ *

+ *

+ * Unpaged (whole list) access on a Windows-NT server in a different time zone. + * (Note, since the NT Format uses numeric date formatting, language issues + * are irrelevant here). + *

+ *    FTPClient f=FTPClient();
+ *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
+ *    conf.setTimeZoneId("America/Denver");
+ *    f.configure(conf);
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = listFiles(directory);
+ * 
+ *

+ * Unpaged (whole list) access on a Windows-NT server in a different time zone + * but which has been configured to use a unix-style listing format. + *
+ *    FTPClient f=FTPClient();
+ *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
+ *    conf.setTimeZoneId("America/Denver");
+ *    f.configure(conf);
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = listFiles(directory);
+ * 
+ *

+ * @since 1.4 + * @see org.apache.commons.net.ftp.Configurable + * @see org.apache.commons.net.ftp.FTPClient + * @see org.apache.commons.net.ftp.parser.FTPTimestampParserImpl#configure(FTPClientConfig) + * @see org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl + */ +public class FTPClientConfig +{ + + /** + * Identifier by which a unix-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_UNIX = "UNIX"; + + /** + * Identifier by which a vms-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_VMS = "VMS"; + + /** + * Identifier by which a WindowsNT-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_NT = "WINDOWS"; + + /** + * Identifier by which an OS/2-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_OS2 = "OS/2"; + + /** + * Identifier by which an OS/400-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_OS400 = "OS/400"; + + /** + * Identifier by which an AS/400-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_AS400 = "AS/400"; + + /** + * Identifier by which an MVS-based ftp server is known throughout + * the commons-net ftp system. + */ + public static final String SYST_MVS = "MVS"; + + /** + * Some servers return an "UNKNOWN Type: L8" message + * in response to the SYST command. We set these to be a Unix-type system. + * This may happen if the ftpd in question was compiled without system + * information. + * + * NET-230 - Updated to be UPPERCASE so that the check done in + * createFileEntryParser will succeed. + * + * @since 1.5 + */ + public static final String SYST_L8 = "TYPE: L8"; + + /** + * Identifier by which an Netware-based ftp server is known throughout + * the commons-net ftp system. + * + * @since 1.5 + */ + public static final String SYST_NETWARE = "NETWARE"; + + private final String serverSystemKey; + private String defaultDateFormatStr = null; + private String recentDateFormatStr = null; + private boolean lenientFutureDates = false; + private String serverLanguageCode = null; + private String shortMonthNames = null; + private String serverTimeZoneId = null; + + + /** + * The main constructor for an FTPClientConfig object + * @param systemKey key representing system type of the server being + * connected to. See {@link #getServerSystemKey() serverSystemKey} + */ + public FTPClientConfig(String systemKey) { + this.serverSystemKey = systemKey; + } + + /** + * Convenience constructor mainly for use in testing. + * Constructs a UNIX configuration. + */ + public FTPClientConfig() { + this(SYST_UNIX); + } + + /** + * Constructor which allows setting of all member fields + * @param systemKey key representing system type of the server being + * connected to. See + * {@link #getServerSystemKey() serverSystemKey} + * @param defaultDateFormatStr See + * {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} + * @param recentDateFormatStr See + * {@link #setRecentDateFormatStr(String) recentDateFormatStr} + * @param serverLanguageCode See + * {@link #setServerLanguageCode(String) serverLanguageCode} + * @param shortMonthNames See + * {@link #setShortMonthNames(String) shortMonthNames} + * @param serverTimeZoneId See + * {@link #setServerTimeZoneId(String) serverTimeZoneId} + */ + public FTPClientConfig(String systemKey, + String defaultDateFormatStr, + String recentDateFormatStr, + String serverLanguageCode, + String shortMonthNames, + String serverTimeZoneId) + { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.recentDateFormatStr = recentDateFormatStr; + this.serverLanguageCode = serverLanguageCode; + this.shortMonthNames = shortMonthNames; + this.serverTimeZoneId = serverTimeZoneId; + } + + private static Map LANGUAGE_CODE_MAP = new TreeMap(); + static { + + // if there are other commonly used month name encodings which + // correspond to particular locales, please add them here. + + + + // many locales code short names for months as all three letters + // these we handle simply. + LANGUAGE_CODE_MAP.put("en", Locale.ENGLISH); + LANGUAGE_CODE_MAP.put("de",Locale.GERMAN); + LANGUAGE_CODE_MAP.put("it",Locale.ITALIAN); + LANGUAGE_CODE_MAP.put("es", new Locale("es", "", "")); // spanish + LANGUAGE_CODE_MAP.put("pt", new Locale("pt", "", "")); // portuguese + LANGUAGE_CODE_MAP.put("da", new Locale("da", "", "")); // danish + LANGUAGE_CODE_MAP.put("sv", new Locale("sv", "", "")); // swedish + LANGUAGE_CODE_MAP.put("no", new Locale("no", "", "")); // norwegian + LANGUAGE_CODE_MAP.put("nl", new Locale("nl", "", "")); // dutch + LANGUAGE_CODE_MAP.put("ro", new Locale("ro", "", "")); // romanian + LANGUAGE_CODE_MAP.put("sq", new Locale("sq", "", "")); // albanian + LANGUAGE_CODE_MAP.put("sh", new Locale("sh", "", "")); // serbo-croatian + LANGUAGE_CODE_MAP.put("sk", new Locale("sk", "", "")); // slovak + LANGUAGE_CODE_MAP.put("sl", new Locale("sl", "", "")); // slovenian + + + // some don't + LANGUAGE_CODE_MAP.put("fr", + "jan|f\u00e9v|mar|avr|mai|jun|jui|ao\u00fb|sep|oct|nov|d\u00e9c"); //french + + } + + /** + * Getter for the serverSystemKey property. This property + * specifies the general type of server to which the client connects. + * Should be either one of the FTPClientConfig.SYST_* codes + * or else the fully qualified class name of a parser implementing both + * the FTPFileEntryParser and Configurable + * interfaces. + * @return Returns the serverSystemKey property. + */ + public String getServerSystemKey() { + return serverSystemKey; + } + + /** + * getter for the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} + * property. + * @return Returns the defaultDateFormatStr property. + */ + public String getDefaultDateFormatStr() { + return defaultDateFormatStr; + } + + /** + * getter for the {@link #setRecentDateFormatStr(String) recentDateFormatStr} property. + * @return Returns the recentDateFormatStr property. + */ + + public String getRecentDateFormatStr() { + return recentDateFormatStr; + } + + /** + * getter for the {@link #setServerTimeZoneId(String) serverTimeZoneId} property. + * @return Returns the serverTimeZoneId property. + */ + public String getServerTimeZoneId() { + return serverTimeZoneId; + } + + /** + *

+ * getter for the {@link #setShortMonthNames(String) shortMonthNames} + * property. + *

+ * @return Returns the shortMonthNames. + */ + public String getShortMonthNames() { + return shortMonthNames; + } + + /** + *

+ * getter for the {@link #setServerLanguageCode(String) serverLanguageCode} property. + *

+ * @return Returns the serverLanguageCode property. + */ + public String getServerLanguageCode() { + return serverLanguageCode; + } + + /** + *

+ * getter for the {@link #setLenientFutureDates(boolean) lenientFutureDates} property. + *

+ * @return Returns the lenientFutureDates. + * @since 1.5 + */ + public boolean isLenientFutureDates() { + return lenientFutureDates; + } + /** + *

+ * setter for the defaultDateFormatStr property. This property + * specifies the main date format that will be used by a parser configured + * by this configuration to parse file timestamps. If this is not + * specified, such a parser will use as a default value, the most commonly + * used format which will be in as used in en_US locales. + *

+ * This should be in the format described for + * java.text.SimpleDateFormat. + * property. + *

+ * @param defaultDateFormatStr The defaultDateFormatStr to set. + */ + public void setDefaultDateFormatStr(String defaultDateFormatStr) { + this.defaultDateFormatStr = defaultDateFormatStr; + } + + /** + *

+ * setter for the recentDateFormatStr property. This property + * specifies a secondary date format that will be used by a parser + * configured by this configuration to parse file timestamps, typically + * those less than a year old. If this is not specified, such a parser + * will not attempt to parse using an alternate format. + *

+ * This is used primarily in unix-based systems. + *

+ * This should be in the format described for + * java.text.SimpleDateFormat. + *

+ * @param recentDateFormatStr The recentDateFormatStr to set. + */ + public void setRecentDateFormatStr(String recentDateFormatStr) { + this.recentDateFormatStr = recentDateFormatStr; + } + + /** + *

+ * setter for the lenientFutureDates property. This boolean property + * (default: false) only has meaning when a + * {@link #setRecentDateFormatStr(String) recentDateFormatStr} property + * has been set. In that case, if this property is set true, then the + * parser, when it encounters a listing parseable with the recent date + * format, will only consider a date to belong to the previous year if + * it is more than one day in the future. This will allow all + * out-of-synch situations (whether based on "slop" - i.e. servers simply + * out of synch with one another or because of time zone differences - + * but in the latter case it is highly recommended to use the + * {@link #setServerTimeZoneId(String) serverTimeZoneId} property + * instead) to resolve correctly. + *

+ * This is used primarily in unix-based systems. + *

+ * @param lenientFutureDates set true to compensate for out-of-synch + * conditions. + */ + public void setLenientFutureDates(boolean lenientFutureDates) { + this.lenientFutureDates = lenientFutureDates; + } + /** + *

+ * setter for the serverTimeZoneId property. This property + * allows a time zone to be specified corresponding to that known to be + * used by an FTP server in file listings. This might be particularly + * useful to clients such as Ant that try to use these timestamps for + * dependency checking. + *

+ * This should be one of the identifiers used by + * java.util.TimeZone to refer to time zones, for example, + * America/Chicago or Asia/Rangoon. + *

+ * @param serverTimeZoneId The serverTimeZoneId to set. + */ + public void setServerTimeZoneId(String serverTimeZoneId) { + this.serverTimeZoneId = serverTimeZoneId; + } + + /** + *

+ * setter for the shortMonthNames property. + * This property allows the user to specify a set of month names + * used by the server that is different from those that may be + * specified using the {@link #setServerLanguageCode(String) serverLanguageCode} + * property. + *

+ * This should be a string containing twelve strings each composed of + * three characters, delimited by pipe (|) characters. Currently, + * only 8-bit ASCII characters are known to be supported. For example, + * a set of month names used by a hypothetical Icelandic FTP server might + * conceivably be specified as + * "jan|feb|mar|apr|maí|jún|júl|ágú|sep|okt|nóv|des". + *

+ * @param shortMonthNames The value to set to the shortMonthNames property. + */ + public void setShortMonthNames(String shortMonthNames) { + this.shortMonthNames = shortMonthNames; + } + + /** + *

+ * setter for the serverLanguageCode property. This property allows + * user to specify a + * + * two-letter ISO-639 language code that will be used to + * configure the set of month names used by the file timestamp parser. + * If neither this nor the {@link #setShortMonthNames(String) shortMonthNames} + * is specified, parsing will assume English month names, which may or + * may not be significant, depending on whether the date format(s) + * specified via {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} + * and/or {@link #setRecentDateFormatStr(String) recentDateFormatStr} are using + * numeric or alphabetic month names. + *

+ *

If the code supplied is not supported here, en_US + * month names will be used. We are supporting here those language + * codes which, when a java.util.Locale is constucted + * using it, and a java.text.SimpleDateFormat is + * constructed using that Locale, the array returned by the + * SimpleDateFormat's getShortMonths() method consists + * solely of three 8-bit ASCII character strings. Additionally, + * languages which do not meet this requirement are included if a + * common alternative set of short month names is known to be used. + * This means that users who can tell us of additional such encodings + * may get them added to the list of supported languages by contacting + * the jakarta-commons-net team. + *

+ *

+ * Please note that this attribute will NOT be used to determine a + * locale-based date format for the language. + * Experience has shown that many if not most FTP servers outside the + * United States employ the standard en_US date format + * orderings of MMM d yyyy and MMM d HH:mm + * and attempting to deduce this automatically here would cause more + * problems than it would solve. The date format must be changed + * via the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} and/or + * {@link #setRecentDateFormatStr(String) recentDateFormatStr} parameters. + *

+ * @param serverLanguageCode The value to set to the serverLanguageCode property. + */ + public void setServerLanguageCode(String serverLanguageCode) { + this.serverLanguageCode = serverLanguageCode; + } + + /** + * Looks up the supplied language code in the internally maintained table of + * language codes. Returns a DateFormatSymbols object configured with + * short month names corresponding to the code. If there is no corresponding + * entry in the table, the object returned will be that for + * Locale.US + * @param languageCode See {@link #setServerLanguageCode(String) serverLanguageCode} + * @return a DateFormatSymbols object configured with short month names + * corresponding to the supplied code, or with month names for + * Locale.US if there is no corresponding entry in the internal + * table. + */ + public static DateFormatSymbols lookupDateFormatSymbols(String languageCode) + { + Object lang = LANGUAGE_CODE_MAP.get(languageCode); + if (lang != null) { + if (lang instanceof Locale) { + return new DateFormatSymbols((Locale) lang); + } else if (lang instanceof String){ + return getDateFormatSymbols((String) lang); + } + } + return new DateFormatSymbols(Locale.US); + } + + /** + * Returns a DateFormatSymbols object configured with short month names + * as in the supplied string + * @param shortmonths This should be as described in + * {@link #setShortMonthNames(String) shortMonthNames} + * @return a DateFormatSymbols object configured with short month names + * as in the supplied string + */ + public static DateFormatSymbols getDateFormatSymbols(String shortmonths) + { + String[] months = splitShortMonthString(shortmonths); + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + dfs.setShortMonths(months); + return dfs; + } + + private static String[] splitShortMonthString(String shortmonths) { + StringTokenizer st = new StringTokenizer(shortmonths, "|"); + int monthcnt = st.countTokens(); + if (12 != monthcnt) { + throw new IllegalArgumentException( + "expecting a pipe-delimited string containing 12 tokens"); + } + String[] months = new String[13]; + int pos = 0; + while(st.hasMoreTokens()) { + months[pos++] = st.nextToken(); + } + months[pos]=""; + return months; + } + + /** + * Returns a Collection of all the language codes currently supported + * by this class. See {@link #setServerLanguageCode(String) serverLanguageCode} + * for a functional descrption of language codes within this system. + * + * @return a Collection of all the language codes currently supported + * by this class + */ + public static Collection getSupportedLanguageCodes() { + return LANGUAGE_CODE_MAP.keySet(); + } + + +} diff --git a/src/org/apache/commons/net/ftp/FTPCommand.java b/src/org/apache/commons/net/ftp/FTPCommand.java new file mode 100644 index 0000000..d016dff --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPCommand.java @@ -0,0 +1,131 @@ +/* + * 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; + +/*** + * FTPCommand stores a set of constants for FTP command codes. To interpret + * the meaning of the codes, familiarity with RFC 959 is assumed. + * The mnemonic constant names are transcriptions from the code descriptions + * of RFC 959. For those who think in terms of the actual FTP commands, + * a set of constants such as {@link #USER USER } are provided + * where the constant name is the same as the FTP command. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class FTPCommand +{ + + + public static final int USER = 0; + public static final int PASS = 1; + public static final int ACCT = 2; + public static final int CWD = 3; + public static final int CDUP = 4; + public static final int SMNT = 5; + public static final int REIN = 6; + public static final int QUIT = 7; + public static final int PORT = 8; + public static final int PASV = 9; + public static final int TYPE = 10; + public static final int STRU = 11; + public static final int MODE = 12; + public static final int RETR = 13; + public static final int STOR = 14; + public static final int STOU = 15; + public static final int APPE = 16; + public static final int ALLO = 17; + public static final int REST = 18; + public static final int RNFR = 19; + public static final int RNTO = 20; + public static final int ABOR = 21; + public static final int DELE = 22; + public static final int RMD = 23; + public static final int MKD = 24; + public static final int PWD = 25; + public static final int LIST = 26; + public static final int NLST = 27; + public static final int SITE = 28; + public static final int SYST = 29; + public static final int STAT = 30; + public static final int HELP = 31; + public static final int NOOP = 32; + /** @since 2.0 */ + public static final int MDTM = 33; + + public static final int USERNAME = USER; + public static final int PASSWORD = PASS; + public static final int ACCOUNT = ACCT; + public static final int CHANGE_WORKING_DIRECTORY = CWD; + public static final int CHANGE_TO_PARENT_DIRECTORY = CDUP; + public static final int STRUCTURE_MOUNT = SMNT; + public static final int REINITIALIZE = REIN; + public static final int LOGOUT = QUIT; + public static final int DATA_PORT = PORT; + public static final int PASSIVE = PASV; + public static final int REPRESENTATION_TYPE = TYPE; + public static final int FILE_STRUCTURE = STRU; + public static final int TRANSFER_MODE = MODE; + public static final int RETRIEVE = RETR; + public static final int STORE = STOR; + public static final int STORE_UNIQUE = STOU; + public static final int APPEND = APPE; + public static final int ALLOCATE = ALLO; + public static final int RESTART = REST; + public static final int RENAME_FROM = RNFR; + public static final int RENAME_TO = RNTO; + public static final int ABORT = ABOR; + public static final int DELETE = DELE; + public static final int REMOVE_DIRECTORY = RMD; + public static final int MAKE_DIRECTORY = MKD; + public static final int PRINT_WORKING_DIRECTORY = PWD; + // public static final int LIST = LIST; + public static final int NAME_LIST = NLST; + public static final int SITE_PARAMETERS = SITE; + public static final int SYSTEM = SYST; + public static final int STATUS = STAT; + //public static final int HELP = HELP; + //public static final int NOOP = NOOP; + /** @since 2.0 */ + public static final int MOD_TIME = MDTM; + + // Cannot be instantiated + private FTPCommand() + {} + + static final String[] _commands = { + "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT", "PORT", + "PASV", "TYPE", "STRU", "MODE", "RETR", "STOR", "STOU", "APPE", "ALLO", + "REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", "MKD", "PWD", "LIST", + "NLST", "SITE", "SYST", "STAT", "HELP", "NOOP" + }; + + /** + * Retrieve the FTP protocol command string corresponding to a specified + * command code. + *

+ * @param command The command code. + * @return The FTP protcol command string corresponding to a specified + * command code. + */ + public static final String getCommand(int command) + { + return _commands[command]; + } +} diff --git a/src/org/apache/commons/net/ftp/FTPConnectionClosedException.java b/src/org/apache/commons/net/ftp/FTPConnectionClosedException.java new file mode 100644 index 0000000..3eccbf4 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPConnectionClosedException.java @@ -0,0 +1,55 @@ +/* + * 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; + +/*** + * FTPConnectionClosedException is used to indicate the premature or + * unexpected closing of an FTP connection resulting from a + * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } + * response (FTP reply code 421) to a + * failed FTP command. This exception is derived from IOException and + * therefore may be caught either as an IOException or specifically as an + * FTPConnectionClosedException. + *

+ *

+ * @author Daniel F. Savarese + * @see FTP + * @see FTPClient + ***/ + +public class FTPConnectionClosedException extends IOException +{ + + /*** Constructs a FTPConnectionClosedException with no message ***/ + public FTPConnectionClosedException() + { + super(); + } + + /*** + * Constructs a FTPConnectionClosedException with a specified message. + *

+ * @param message The message explaining the reason for the exception. + ***/ + public FTPConnectionClosedException(String message) + { + super(message); + } + +} diff --git a/src/org/apache/commons/net/ftp/FTPFile.java b/src/org/apache/commons/net/ftp/FTPFile.java new file mode 100644 index 0000000..dd67904 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPFile.java @@ -0,0 +1,392 @@ +/* + * 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.Serializable; +import java.util.Calendar; + +/*** + * The FTPFile class is used to represent information about files stored + * on an FTP server. Because there is no standard representation for + * file information on FTP servers, it may not always be possible to + * extract all the information that can be represented by FTPFile, or + * it may even be possible to extract more information. In cases where + * more information can be extracted, you will want to subclass FTPFile + * and implement your own {@link org.apache.commons.net.ftp.FTPFileListParser} + * to extract the information. + * However, most FTP servers return file information in a format that + * can be completely parsed by + * {@link org.apache.commons.net.ftp.DefaultFTPFileListParser} + * and stored in FTPFile. + *

+ *

+ * @author Daniel F. Savarese + * @see FTPFileListParser + * @see DefaultFTPFileListParser + * @see FTPClient#listFiles + ***/ + +public class FTPFile implements Serializable +{ + /** A constant indicating an FTPFile is a file. ***/ + public static final int FILE_TYPE = 0; + /** A constant indicating an FTPFile is a directory. ***/ + public static final int DIRECTORY_TYPE = 1; + /** A constant indicating an FTPFile is a symbolic link. ***/ + public static final int SYMBOLIC_LINK_TYPE = 2; + /** A constant indicating an FTPFile is of unknown type. ***/ + public static final int UNKNOWN_TYPE = 3; + + /** A constant indicating user access permissions. ***/ + public static final int USER_ACCESS = 0; + /** A constant indicating group access permissions. ***/ + public static final int GROUP_ACCESS = 1; + /** A constant indicating world access permissions. ***/ + public static final int WORLD_ACCESS = 2; + + /** A constant indicating file/directory read permission. ***/ + public static final int READ_PERMISSION = 0; + /** A constant indicating file/directory write permission. ***/ + public static final int WRITE_PERMISSION = 1; + /** + * A constant indicating file execute permission or directory listing + * permission. + ***/ + public static final int EXECUTE_PERMISSION = 2; + + int _type, _hardLinkCount; + long _size; + String _rawListing, _user, _group, _name, _link; + Calendar _date; + boolean[] _permissions[]; + + /*** Creates an empty FTPFile. ***/ + public FTPFile() + { + _permissions = new boolean[3][3]; + _rawListing = null; + _type = UNKNOWN_TYPE; + _hardLinkCount = 0; + _size = 0; + _user = null; + _group = null; + _date = null; + _name = null; + } + + + /*** + * Set the original FTP server raw listing from which the FTPFile was + * created. + *

+ * @param rawListing The raw FTP server listing. + ***/ + public void setRawListing(String rawListing) + { + _rawListing = rawListing; + } + + /*** + * Get the original FTP server raw listing used to initialize the FTPFile. + *

+ * @return The original FTP server raw listing used to initialize the + * FTPFile. + ***/ + public String getRawListing() + { + return _rawListing; + } + + + /*** + * Determine if the file is a directory. + *

+ * @return True if the file is of type DIRECTORY_TYPE, false if + * not. + ***/ + public boolean isDirectory() + { + return (_type == DIRECTORY_TYPE); + } + + /*** + * Determine if the file is a regular file. + *

+ * @return True if the file is of type FILE_TYPE, false if + * not. + ***/ + public boolean isFile() + { + return (_type == FILE_TYPE); + } + + /*** + * Determine if the file is a symbolic link. + *

+ * @return True if the file is of type UNKNOWN_TYPE, false if + * not. + ***/ + public boolean isSymbolicLink() + { + return (_type == SYMBOLIC_LINK_TYPE); + } + + /*** + * Determine if the type of the file is unknown. + *

+ * @return True if the file is of type UNKNOWN_TYPE, false if + * not. + ***/ + public boolean isUnknown() + { + return (_type == UNKNOWN_TYPE); + } + + + /*** + * Set the type of the file (DIRECTORY_TYPE, + * FILE_TYPE, etc.). + *

+ * @param type The integer code representing the type of the file. + ***/ + public void setType(int type) + { + _type = type; + } + + + /*** + * Return the type of the file (one of the _TYPE constants), + * e.g., if it is a directory, a regular file, or a symbolic link. + *

+ * @return The type of the file. + ***/ + public int getType() + { + return _type; + } + + + /*** + * Set the name of the file. + *

+ * @param name The name of the file. + ***/ + public void setName(String name) + { + _name = name; + } + + /*** + * Return the name of the file. + *

+ * @return The name of the file. + ***/ + public String getName() + { + return _name; + } + + + /** + * Set the file size in bytes. + * @param size The file size in bytes. + */ + public void setSize(long size) + { + _size = size; + } + + + /*** + * Return the file size in bytes. + *

+ * @return The file size in bytes. + ***/ + public long getSize() + { + return _size; + } + + + /*** + * Set the number of hard links to this file. This is not to be + * confused with symbolic links. + *

+ * @param links The number of hard links to this file. + ***/ + public void setHardLinkCount(int links) + { + _hardLinkCount = links; + } + + + /*** + * Return the number of hard links to this file. This is not to be + * confused with symbolic links. + *

+ * @return The number of hard links to this file. + ***/ + public int getHardLinkCount() + { + return _hardLinkCount; + } + + + /*** + * Set the name of the group owning the file. This may be + * a string representation of the group number. + *

+ * @param group The name of the group owning the file. + ***/ + public void setGroup(String group) + { + _group = group; + } + + + /*** + * Returns the name of the group owning the file. Sometimes this will be + * a string representation of the group number. + *

+ * @return The name of the group owning the file. + ***/ + public String getGroup() + { + return _group; + } + + + /*** + * Set the name of the user owning the file. This may be + * a string representation of the user number; + *

+ * @param user The name of the user owning the file. + ***/ + public void setUser(String user) + { + _user = user; + } + + /*** + * Returns the name of the user owning the file. Sometimes this will be + * a string representation of the user number. + *

+ * @return The name of the user owning the file. + ***/ + public String getUser() + { + return _user; + } + + + /*** + * If the FTPFile is a symbolic link, use this method to set the name of the + * file being pointed to by the symbolic link. + *

+ * @param link The file pointed to by the symbolic link. + ***/ + public void setLink(String link) + { + _link = link; + } + + + /*** + * If the FTPFile is a symbolic link, this method returns the name of the + * file being pointed to by the symbolic link. Otherwise it returns null. + *

+ * @return The file pointed to by the symbolic link (null if the FTPFile + * is not a symbolic link). + ***/ + public String getLink() + { + return _link; + } + + + /*** + * Set the file timestamp. This usually the last modification time. + * The parameter is not cloned, so do not alter its value after calling + * this method. + *

+ * @param date A Calendar instance representing the file timestamp. + ***/ + public void setTimestamp(Calendar date) + { + _date = date; + } + + + /*** + * Returns the file timestamp. This usually the last modification time. + *

+ * @return A Calendar instance representing the file timestamp. + ***/ + public Calendar getTimestamp() + { + return _date; + } + + + /*** + * Set if the given access group (one of the _ACCESS + * constants) has the given access permission (one of the + * _PERMISSION constants) to the file. + *

+ * @param access The access group (one of the _ACCESS + * constants) + * @param permission The access permission (one of the + * _PERMISSION constants) + * @param value True if permission is allowed, false if not. + ***/ + public void setPermission(int access, int permission, boolean value) + { + _permissions[access][permission] = value; + } + + + /*** + * Determines if the given access group (one of the _ACCESS + * constants) has the given access permission (one of the + * _PERMISSION constants) to the file. + *

+ * @param access The access group (one of the _ACCESS + * constants) + * @param permission The access permission (one of the + * _PERMISSION constants) + ***/ + public boolean hasPermission(int access, int permission) + { + return _permissions[access][permission]; + } + + + /*** + * Returns a string representation of the FTPFile information. This + * will be the raw FTP server listing that was used to initialize the + * FTPFile instance. + *

+ * @return A string representation of the FTPFile information. + ***/ + @Override + public String toString() + { + return _rawListing; + } + +} diff --git a/src/org/apache/commons/net/ftp/FTPFileEntryParser.java b/src/org/apache/commons/net/ftp/FTPFileEntryParser.java new file mode 100644 index 0000000..8e6d09c --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPFileEntryParser.java @@ -0,0 +1,152 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.util.List; + +/** + * FTPFileEntryParser defines the interface for parsing a single FTP file + * listing and converting that information into an + * {@link org.apache.commons.net.ftp.FTPFile} instance. + * Sometimes you will want to parse unusual listing formats, in which + * case you would create your own implementation of FTPFileEntryParser and + * if necessary, subclass FTPFile. + *

+ * Here are some examples showing how to use one of the classes that + * implement this interface. + *

+ * The first example shows how to get an iterable list of files in which the + * more expensive FTPFile objects are not created until needed. This + * is suitable for paged displays. It requires that a parser object be created + * beforehand: parser is an object (in the package + * org.apache.commons.net.ftp.parser) + * implementing this inteface. + * + *

+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFileList list = f.createFileList(directory, parser);
+ *    FTPFileIterator iter = list.iterator();
+ *
+ *    while (iter.hasNext()) {
+ *       FTPFile[] files = iter.getNext(25);  // "page size" you want
+ *       //do whatever you want with these files, display them, etc.
+ *       //expensive FTPFile objects not created until needed.
+ *    }
+ * 
+ * + * The second example uses the revised FTPClient.listFiles() + * API to pull the whole list from the subfolder subfolder in + * one call, attempting to automatically detect the parser type. This + * method, without a parserKey parameter, indicates that autodection should + * be used. + * + *
+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = f.listFiles("subfolder");
+ * 
+ * + * The third example uses the revised FTPClient.listFiles()> + * API to pull the whole list from the current working directory in one call, + * but specifying by classname the parser to be used. For this particular + * parser class, this approach is necessary since there is no way to + * autodetect this server type. + * + *
+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = f.listFiles(
+ *      "org.apache.commons.net.ftp.parser.EnterpriseUnixFTPFileEntryParser",
+ *      ".");
+ * 
+ * + * The fourth example uses the revised FTPClient.listFiles() + * API to pull a single file listing in an arbitrary directory in one call, + * specifying by KEY the parser to be used, in this case, VMS. + * + *
+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = f.listFiles("VMS", "subfolder/foo.java");
+ * 
+ * + * @author Steve Cohen + * @version $Id: FTPFileEntryParser.java 636854 2008-03-13 19:55:01Z sebb $ + * @see org.apache.commons.net.ftp.FTPFile + * @see org.apache.commons.net.ftp.FTPClient#createFileList + */ +public interface FTPFileEntryParser +{ + /** + * Parses a line of an FTP server file listing and converts it into a usable + * format in the form of an FTPFile instance. If the + * file listing line doesn't describe a file, null should be + * returned, otherwise a FTPFile instance representing the + * files in the directory is returned. + *

+ * @param listEntry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + FTPFile parseFTPEntry(String listEntry); + + /** + * Reads the next entry using the supplied BufferedReader object up to + * whatever delemits one entry from the next. Implementors must define + * this for the particular ftp system being parsed. In many but not all + * cases, this can be defined simply by calling BufferedReader.readLine(). + * + * @param reader The BufferedReader object from which entries are to be + * read. + * + * @return A string representing the next ftp entry or null if none found. + * @exception IOException thrown on any IO Error reading from the reader. + */ + String readNextEntry(BufferedReader reader) throws IOException; + + + /** + * This method is a hook for those implementors (such as + * VMSVersioningFTPEntryParser, and possibly others) which need to + * perform some action upon the FTPFileList after it has been created + * from the server stream, but before any clients see the list. + * + * The default implementation can be a no-op. + * + * @param original Original list after it has been created from the server stream + * + * @return Original list as processed by this method. + */ + List preParse(List original); + + +} + + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/src/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java b/src/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java new file mode 100644 index 0000000..22214fe --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java @@ -0,0 +1,85 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +/** + * This abstract class implements both the older FTPFileListParser and + * newer FTPFileEntryParser interfaces with default functionality. + * All the classes in the parser subpackage inherit from this. + * + */ +public abstract class FTPFileEntryParserImpl + implements FTPFileEntryParser +{ + /** + * The constructor for a FTPFileEntryParserImpl object. + */ + public FTPFileEntryParserImpl() + { + } + + /** + * Reads the next entry using the supplied BufferedReader object up to + * whatever delemits one entry from the next. This default implementation + * simply calls BufferedReader.readLine(). + * + * @param reader The BufferedReader object from which entries are to be + * read. + * + * @return A string representing the next ftp entry or null if none found. + * @exception java.io.IOException thrown on any IO Error reading from the reader. + */ + public String readNextEntry(BufferedReader reader) throws IOException + { + return reader.readLine(); + } + /** + * This method is a hook for those implementors (such as + * VMSVersioningFTPEntryParser, and possibly others) which need to + * perform some action upon the FTPFileList after it has been created + * from the server stream, but before any clients see the list. + * + * This default implementation removes entries that do not parse as files. + * + * @param original Original list after it has been created from the server stream + * + * @return original unmodified. + */ + public List preParse(List original) { + Iterator it = original.iterator(); + while (it.hasNext()){ + String entry = it.next(); + if (null == parseFTPEntry(entry)) { + it.remove(); + } + } + return original; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/src/org/apache/commons/net/ftp/FTPListParseEngine.java b/src/org/apache/commons/net/ftp/FTPListParseEngine.java new file mode 100644 index 0000000..468bf39 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPListParseEngine.java @@ -0,0 +1,290 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + + +/** + * This class handles the entire process of parsing a listing of + * file entries from the server. + *

+ * This object defines a two-part parsing mechanism. + *

+ * The first part is comprised of reading the raw input into an internal + * list of strings. Every item in this list corresponds to an actual + * file. All extraneous matter emitted by the server will have been + * removed by the end of this phase. This is accomplished in conjunction + * with the FTPFileEntryParser associated with this engine, by calling + * its methods readNextEntry() - which handles the issue of + * what delimits one entry from another, usually but not always a line + * feed and preParse() - which handles removal of + * extraneous matter such as the preliminary lines of a listing, removal + * of duplicates on versioning systems, etc. + *

+ * The second part is composed of the actual parsing, again in conjunction + * with the particular parser used by this engine. This is controlled + * by an iterator over the internal list of strings. This may be done + * either in block mode, by calling the getNext() and + * getPrevious() methods to provide "paged" output of less + * than the whole list at one time, or by calling the + * getFiles() method to return the entire list. + *

+ * Examples: + *

+ * Paged access: + *

+ *    FTPClient f=FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPListParseEngine engine = f.initiateListParsing(directory);
+ *
+ *    while (engine.hasNext()) {
+ *       FTPFile[] files = engine.getNext(25);  // "page size" you want
+ *       //do whatever you want with these files, display them, etc.
+ *       //expensive FTPFile objects not created until needed.
+ *    }
+ * 
+ *

+ * For unpaged access, simply use FTPClient.listFiles(). That method + * uses this class transparently. + * @version $Id: FTPListParseEngine.java 658518 2008-05-21 01:04:30Z sebb $ + */ +public class FTPListParseEngine { + private List entries = new LinkedList(); + private ListIterator _internalIterator = entries.listIterator(); + + FTPFileEntryParser parser = null; + + public FTPListParseEngine(FTPFileEntryParser parser) { + this.parser = parser; + } + + /** + * handle the iniitial reading and preparsing of the list returned by + * the server. After this method has completed, this object will contain + * a list of unparsed entries (Strings) each referring to a unique file + * on the server. + * + * @param stream input stream provided by the server socket. + * + * @exception IOException + * thrown on any failure to read from the sever. + */ + public void readServerList(InputStream stream, String encoding) + throws IOException + { + this.entries = new LinkedList(); + readStream(stream, encoding); + this.parser.preParse(this.entries); + resetIterator(); + } + + /** + * handle the iniitial reading and preparsing of the list returned by + * the server. After this method has completed, this object will contain + * a list of unparsed entries (Strings) each referring to a unique file + * on the server. + * + * @param stream input stream provided by the server socket. + * + * @exception IOException + * thrown on any failure to read from the sever. + * + * @deprecated The version of this method which takes an encoding should be used. + */ + public void readServerList(InputStream stream) + throws IOException + { + readServerList(stream, null); + } + + + + /** + * Internal method for reading the input into the entries list. + * After this method has completed, entries will contain a + * collection of entries (as defined by + * FTPFileEntryParser.readNextEntry()), but this may contain + * various non-entry preliminary lines from the server output, duplicates, + * and other data that will not be part of the final listing. + * + * @param stream The socket stream on which the input will be read. + * @param encoding The encoding to use. + * + * @exception IOException + * thrown on any failure to read the stream + */ + private void readStream(InputStream stream, String encoding) throws IOException + { + BufferedReader reader; + if (encoding == null) + { + reader = new BufferedReader(new InputStreamReader(stream)); + } + else + { + reader = new BufferedReader(new InputStreamReader(stream, encoding)); + } + + String line = this.parser.readNextEntry(reader); + + while (line != null) + { + this.entries.add(line); + line = this.parser.readNextEntry(reader); + } + reader.close(); + } + + /** + * Returns an array of at most quantityRequested FTPFile + * objects starting at this object's internal iterator's current position. + * If fewer than quantityRequested such + * elements are available, the returned array will have a length equal + * to the number of entries at and after after the current position. + * If no such entries are found, this array will have a length of 0. + * + * After this method is called this object's internal iterator is advanced + * by a number of positions equal to the size of the array returned. + * + * @param quantityRequested + * the maximum number of entries we want to get. + * + * @return an array of at most quantityRequested FTPFile + * objects starting at the current position of this iterator within its + * list and at least the number of elements which exist in the list at + * and after its current position. + *

+ * NOTE: This array may contain null members if any of the + * individual file listings failed to parse. The caller should + * check each entry for null before referencing it. + */ + public FTPFile[] getNext(int quantityRequested) { + List tmpResults = new LinkedList(); + int count = quantityRequested; + while (count > 0 && this._internalIterator.hasNext()) { + String entry = this._internalIterator.next(); + FTPFile temp = this.parser.parseFTPEntry(entry); + tmpResults.add(temp); + count--; + } + return tmpResults.toArray(new FTPFile[0]); + + } + + /** + * Returns an array of at most quantityRequested FTPFile + * objects starting at this object's internal iterator's current position, + * and working back toward the beginning. + * + * If fewer than quantityRequested such + * elements are available, the returned array will have a length equal + * to the number of entries at and after after the current position. + * If no such entries are found, this array will have a length of 0. + * + * After this method is called this object's internal iterator is moved + * back by a number of positions equal to the size of the array returned. + * + * @param quantityRequested + * the maximum number of entries we want to get. + * + * @return an array of at most quantityRequested FTPFile + * objects starting at the current position of this iterator within its + * list and at least the number of elements which exist in the list at + * and after its current position. This array will be in the same order + * as the underlying list (not reversed). + *

+ * NOTE: This array may contain null members if any of the + * individual file listings failed to parse. The caller should + * check each entry for null before referencing it. + */ + public FTPFile[] getPrevious(int quantityRequested) { + List tmpResults = new LinkedList(); + int count = quantityRequested; + while (count > 0 && this._internalIterator.hasPrevious()) { + String entry = this._internalIterator.previous(); + FTPFile temp = this.parser.parseFTPEntry(entry); + tmpResults.add(0,temp); + count--; + } + return tmpResults.toArray(new FTPFile[0]); + } + + /** + * Returns an array of FTPFile objects containing the whole list of + * files returned by the server as read by this object's parser. + * + * @return an array of FTPFile objects containing the whole list of + * files returned by the server as read by this object's parser. + *

+ * NOTE: This array may contain null members if any of the + * individual file listings failed to parse. The caller should + * check each entry for null before referencing it. + * @exception IOException + */ + public FTPFile[] getFiles() + throws IOException + { + List tmpResults = new LinkedList(); + Iterator iter = this.entries.iterator(); + while (iter.hasNext()) { + String entry = iter.next(); + FTPFile temp = this.parser.parseFTPEntry(entry); + tmpResults.add(temp); + } + return tmpResults.toArray(new FTPFile[0]); + + } + + /** + * convenience method to allow clients to know whether this object's + * internal iterator's current position is at the end of the list. + * + * @return true if internal iterator is not at end of list, false + * otherwise. + */ + public boolean hasNext() { + return _internalIterator.hasNext(); + } + + /** + * convenience method to allow clients to know whether this object's + * internal iterator's current position is at the beginning of the list. + * + * @return true if internal iterator is not at beginning of list, false + * otherwise. + */ + public boolean hasPrevious() { + return _internalIterator.hasPrevious(); + } + + /** + * resets this object's internal iterator to the beginning of the list. + */ + public void resetIterator() { + this._internalIterator = this.entries.listIterator(); + } +} diff --git a/src/org/apache/commons/net/ftp/FTPReply.java b/src/org/apache/commons/net/ftp/FTPReply.java new file mode 100644 index 0000000..6386568 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPReply.java @@ -0,0 +1,240 @@ +/* + * 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; + +/*** + * FTPReply stores a set of constants for FTP reply codes. To interpret + * the meaning of the codes, familiarity with RFC 959 is assumed. + * The mnemonic constant names are transcriptions from the code descriptions + * of RFC 959. For those who think in terms of the actual reply code values, + * a set of CODE_NUM constants are provided where NUM is the numerical value + * of the code. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class FTPReply +{ + + public static final int CODE_110 = 110; + public static final int CODE_120 = 120; + public static final int CODE_125 = 125; + public static final int CODE_150 = 150; + public static final int CODE_200 = 200; + public static final int CODE_202 = 202; + public static final int CODE_211 = 211; + public static final int CODE_212 = 212; + public static final int CODE_213 = 213; + public static final int CODE_214 = 214; + public static final int CODE_215 = 215; + public static final int CODE_220 = 220; + public static final int CODE_221 = 221; + public static final int CODE_225 = 225; + public static final int CODE_226 = 226; + public static final int CODE_227 = 227; + public static final int CODE_230 = 230; + public static final int CODE_250 = 250; + public static final int CODE_257 = 257; + public static final int CODE_331 = 331; + public static final int CODE_332 = 332; + public static final int CODE_350 = 350; + public static final int CODE_421 = 421; + public static final int CODE_425 = 425; + public static final int CODE_426 = 426; + public static final int CODE_450 = 450; + public static final int CODE_451 = 451; + public static final int CODE_452 = 452; + public static final int CODE_500 = 500; + public static final int CODE_501 = 501; + public static final int CODE_502 = 502; + public static final int CODE_503 = 503; + public static final int CODE_504 = 504; + public static final int CODE_521 = 521; + public static final int CODE_530 = 530; + public static final int CODE_532 = 532; + public static final int CODE_550 = 550; + public static final int CODE_551 = 551; + public static final int CODE_552 = 552; + public static final int CODE_553 = 553; + + public static final int RESTART_MARKER = CODE_110; + public static final int SERVICE_NOT_READY = CODE_120; + public static final int DATA_CONNECTION_ALREADY_OPEN = CODE_125; + public static final int FILE_STATUS_OK = CODE_150; + public static final int COMMAND_OK = CODE_200; + public static final int COMMAND_IS_SUPERFLUOUS = CODE_202; + public static final int SYSTEM_STATUS = CODE_211; + public static final int DIRECTORY_STATUS = CODE_212; + public static final int FILE_STATUS = CODE_213; + public static final int HELP_MESSAGE = CODE_214; + public static final int NAME_SYSTEM_TYPE = CODE_215; + public static final int SERVICE_READY = CODE_220; + public static final int SERVICE_CLOSING_CONTROL_CONNECTION = CODE_221; + public static final int DATA_CONNECTION_OPEN = CODE_225; + public static final int CLOSING_DATA_CONNECTION = CODE_226; + public static final int ENTERING_PASSIVE_MODE = CODE_227; + public static final int USER_LOGGED_IN = CODE_230; + public static final int FILE_ACTION_OK = CODE_250; + public static final int PATHNAME_CREATED = CODE_257; + public static final int NEED_PASSWORD = CODE_331; + public static final int NEED_ACCOUNT = CODE_332; + public static final int FILE_ACTION_PENDING = CODE_350; + public static final int SERVICE_NOT_AVAILABLE = CODE_421; + public static final int CANNOT_OPEN_DATA_CONNECTION = CODE_425; + public static final int TRANSFER_ABORTED = CODE_426; + public static final int FILE_ACTION_NOT_TAKEN = CODE_450; + public static final int ACTION_ABORTED = CODE_451; + public static final int INSUFFICIENT_STORAGE = CODE_452; + public static final int UNRECOGNIZED_COMMAND = CODE_500; + public static final int SYNTAX_ERROR_IN_ARGUMENTS = CODE_501; + public static final int COMMAND_NOT_IMPLEMENTED = CODE_502; + public static final int BAD_COMMAND_SEQUENCE = CODE_503; + public static final int COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER = CODE_504; + public static final int NOT_LOGGED_IN = CODE_530; + public static final int NEED_ACCOUNT_FOR_STORING_FILES = CODE_532; + public static final int FILE_UNAVAILABLE = CODE_550; + public static final int PAGE_TYPE_UNKNOWN = CODE_551; + public static final int STORAGE_ALLOCATION_EXCEEDED = CODE_552; + public static final int FILE_NAME_NOT_ALLOWED = CODE_553; + + // FTPS Reply Codes + /** @since 2.0 */ + public static final int CODE_234 = 234; + /** @since 2.0 */ + public static final int CODE_235 = 235; + /** @since 2.0 */ + public static final int CODE_334 = 334; + /** @since 2.0 */ + public static final int CODE_335 = 335; + /** @since 2.0 */ + public static final int CODE_431 = 431; + /** @since 2.0 */ + public static final int CODE_533 = 533; + /** @since 2.0 */ + public static final int CODE_534 = 534; + /** @since 2.0 */ + public static final int CODE_535 = 535; + /** @since 2.0 */ + public static final int CODE_536 = 536; + + /** @since 2.0 */ + public static final int SECURITY_DATA_EXCHANGE_COMPLETE = CODE_234; + /** @since 2.0 */ + public static final int SECURITY_DATA_EXCHANGE_SUCCESSFULLY = CODE_235; + /** @since 2.0 */ + public static final int SECURITY_MECHANISM_IS_OK = CODE_334; + /** @since 2.0 */ + public static final int SECURITY_DATA_IS_ACCEPTABLE = CODE_335; + /** @since 2.0 */ + public static final int UNAVAILABLE_RESOURCE = CODE_431; + /** @since 2.0 */ + public static final int DENIED_FOR_POLICY_REASONS = CODE_533; + /** @since 2.0 */ + public static final int REQUEST_DENIED = CODE_534; + /** @since 2.0 */ + public static final int FAILED_SECURITY_CHECK = CODE_535; + /** @since 2.0 */ + public static final int REQUESTED_PROT_LEVEL_NOT_SUPPORTED = CODE_536; + + + // Cannot be instantiated + private FTPReply() + {} + + /*** + * Determine if a reply code is a positive preliminary response. All + * codes beginning with a 1 are positive preliminary responses. + * Postitive preliminary responses are used to indicate tentative success. + * No further commands can be issued to the FTP server after a positive + * preliminary response until a follow up response is received from the + * server. + *

+ * @param reply The reply code to test. + * @return True if a reply code is a postive preliminary response, false + * if not. + ***/ + public static boolean isPositivePreliminary(int reply) + { + return (reply >= 100 && reply < 200); + } + + /*** + * Determine if a reply code is a positive completion response. All + * codes beginning with a 2 are positive completion responses. + * The FTP server will send a positive completion response on the final + * successful completion of a command. + *

+ * @param reply The reply code to test. + * @return True if a reply code is a postive completion response, false + * if not. + ***/ + public static boolean isPositiveCompletion(int reply) + { + return (reply >= 200 && reply < 300); + } + + /*** + * Determine if a reply code is a positive intermediate response. All + * codes beginning with a 3 are positive intermediate responses. + * The FTP server will send a positive intermediate response on the + * successful completion of one part of a multi-part sequence of + * commands. For example, after a successful USER command, a positive + * intermediate response will be sent to indicate that the server is + * ready for the PASS command. + *

+ * @param reply The reply code to test. + * @return True if a reply code is a postive intermediate response, false + * if not. + ***/ + public static boolean isPositiveIntermediate(int reply) + { + return (reply >= 300 && reply < 400); + } + + /*** + * Determine if a reply code is a negative transient response. All + * codes beginning with a 4 are negative transient responses. + * The FTP server will send a negative transient response on the + * failure of a command that can be reattempted with success. + *

+ * @param reply The reply code to test. + * @return True if a reply code is a negative transient response, false + * if not. + ***/ + public static boolean isNegativeTransient(int reply) + { + return (reply >= 400 && reply < 500); + } + + /*** + * Determine if a reply code is a negative permanent response. All + * codes beginning with a 5 are negative permanent responses. + * The FTP server will send a negative permanent response on the + * failure of a command that cannot be reattempted with success. + *

+ * @param reply The reply code to test. + * @return True if a reply code is a negative permanent response, false + * if not. + ***/ + public static boolean isNegativePermanent(int reply) + { + return (reply >= 500 && reply < 600); + } + +} diff --git a/src/org/apache/commons/net/ftp/FTPSClient.java b/src/org/apache/commons/net/ftp/FTPSClient.java new file mode 100644 index 0000000..f809c41 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPSClient.java @@ -0,0 +1,533 @@ +/* + * 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.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +/** + * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to + * see wire-level SSL details. + * + * @version $Id: FTPSClient.java 658520 2008-05-21 01:14:11Z sebb $ + * @since 2.0 + */ +public class FTPSClient extends FTPClient { + + /** keystore algorithm name. */ + public static String KEYSTORE_ALGORITHM; + /** truststore algorithm name. */ + public static String TRUSTSTORE_ALGORITHM; + /** provider name. */ + public static String PROVIDER; + /** truststore type. */ + public static String STORE_TYPE; + + /** The value that I can set in PROT command */ + private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"}; + /** Default PROT Command */ + private static final String DEFAULT_PROT = "C"; + /** Default protocol name */ + private static final String DEFAULT_PROTOCOL = "TLS"; + + /** The security mode. (True - Implicit Mode / False - Explicit Mode) */ + private boolean isImplicit; + /** The use SSL/TLS protocol. */ + private String protocol = DEFAULT_PROTOCOL; + /** The AUTH Command value */ + private String auth = DEFAULT_PROTOCOL; + /** The context object. */ + private SSLContext context; + /** The socket object. */ + private Socket planeSocket; + /** The established socket flag. */ + private boolean isCreation = true; + /** The use client mode flag. */ + private boolean isClientMode = true; + /** The need client auth flag. */ + private boolean isNeedClientAuth = false; + /** The want client auth flag. */ + private boolean isWantClientAuth = false; + /** The cipher suites */ + private String[] suites = null; + /** The protocol versions */ + private String[] protocols = null; + + /** The FTPS {@link TrustManager} implementation. */ + private TrustManager trustManager = new FTPSTrustManager(); + + /** The {@link KeyManager} */ + private KeyManager keyManager; + + /** + * Constructor for FTPSClient. + * @throws NoSuchAlgorithmException A requested cryptographic algorithm + * is not available in the environment. + */ + public FTPSClient() throws NoSuchAlgorithmException { + this.protocol = DEFAULT_PROTOCOL; + this.isImplicit = false; + } + + /** + * Constructor for FTPSClient. + * @param isImplicit The secutiry mode(Implicit/Explicit). + * @throws NoSuchAlgorithmException A requested cryptographic algorithm + * is not available in the environment. + */ + public FTPSClient(boolean isImplicit) throws NoSuchAlgorithmException { + this.protocol = DEFAULT_PROTOCOL; + this.isImplicit = isImplicit; + } + + /** + * Constructor for FTPSClient. + * @param protocol the protocol + * @throws NoSuchAlgorithmException A requested cryptographic algorithm + * is not available in the environment. + */ + public FTPSClient(String protocol) throws NoSuchAlgorithmException { + this.protocol = protocol; + this.isImplicit = false; + } + + /** + * Constructor for FTPSClient. + * @param protocol the protocol + * @param isImplicit The secutiry mode(Implicit/Explicit). + * @throws NoSuchAlgorithmException A requested cryptographic algorithm + * is not available in the environment. + */ + public FTPSClient(String protocol, boolean isImplicit) + throws NoSuchAlgorithmException { + this.protocol = protocol; + this.isImplicit = isImplicit; + } + + + /** + * Set AUTH command use value. + * This processing is done before connected processing. + * @param auth AUTH command use value. + */ + public void setAuthValue(String auth) { + this.auth = auth; + } + + /** + * Return AUTH command use value. + * @return AUTH command use value. + */ + public String getAuthValue() { + return this.auth; + } + + + /** + * Because there are so many connect() methods, + * the _connectAction_() method is provided as a means of performing + * some action immediately after establishing a connection, + * rather than reimplementing all of the connect() methods. + * @throws IOException If it throw by _connectAction_. + * @see org.apache.commons.net.SocketClient#_connectAction_() + */ + @Override + protected void _connectAction_() throws IOException { + // Implicit mode. + if (isImplicit) sslNegotiation(); + super._connectAction_(); + // Explicit mode. + if (!isImplicit) { + execAUTH(); + sslNegotiation(); + } + } + + /** + * AUTH command. + * @throws SSLException If it server reply code not equal "234" and "334". + * @throws IOException If an I/O error occurs while either sending + * the command. + */ + private void execAUTH() throws SSLException, IOException { + int replyCode = sendCommand( + FTPSCommand._commands[FTPSCommand.AUTH], auth); + if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) { + // replyCode = 334 + // I carry out an ADAT command. + } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) { + throw new SSLException(getReplyString()); + } + } + + /** + * Performs a lazy init of the SSL context + * @throws IOException + */ + private void initSslContext() throws IOException { + if(context == null) { + try { + context = SSLContext.getInstance(protocol); + + context.init(new KeyManager[] { getKeyManager() } , new TrustManager[] { getTrustManager() } , null); + } catch (KeyManagementException e) { + IOException ioe = new IOException("Could not initialize SSL context"); + ioe.initCause(e); + throw ioe; + } catch (NoSuchAlgorithmException e) { + IOException ioe = new IOException("Could not initialize SSL context"); + ioe.initCause(e); + throw ioe; + } + } + } + + /** + * SSL/TLS negotiation. Acquires an SSL socket of a control + * connection and carries out handshake processing. + * @throws IOException A handicap breaks out by sever negotiation. + */ + private void sslNegotiation() throws IOException { + // Evacuation not ssl socket. + planeSocket = _socket_; + + initSslContext(); + + SSLSocketFactory ssf = context.getSocketFactory(); + String ip = _socket_.getInetAddress().getHostAddress(); + int port = _socket_.getPort(); + SSLSocket socket = + (SSLSocket) ssf.createSocket(_socket_, ip, port, true); + socket.setEnableSessionCreation(isCreation); + socket.setUseClientMode(isClientMode); + // server mode + if (!isClientMode) { + socket.setNeedClientAuth(isNeedClientAuth); + socket.setWantClientAuth(isWantClientAuth); + } + if (protocols != null) socket.setEnabledProtocols(protocols); + if (suites != null) socket.setEnabledCipherSuites(suites); + + socket.startHandshake(); + + _socket_ = socket; + _controlInput_ = new BufferedReader(new InputStreamReader( + socket .getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter( + socket.getOutputStream(), getControlEncoding())); + } + + /** + * Get the {@link KeyManager} instance. + * @return The {@link KeyManager} instance + */ + private KeyManager getKeyManager() { + return keyManager; + } + + /** + * Set a {@link KeyManager} to use + * + * @param keyManager The KeyManager implementation to set. + */ + public void setKeyManager(KeyManager keyManager) { + this.keyManager = keyManager; + } + + /** + * Controls whether new a SSL session may be established by this socket. + * @param isCreation The established socket flag. + */ + public void setEnabledSessionCreation(boolean isCreation) { + this.isCreation = isCreation; + } + + /** + * Returns true if new SSL sessions may be established by this socket. + * When a socket does not have a ssl socket, This return False. + * @return true - Indicates that sessions may be created; + * this is the default. + * false - indicates that an existing session must be resumed. + */ + public boolean getEnableSessionCreation() { + if (_socket_ instanceof SSLSocket) + return ((SSLSocket)_socket_).getEnableSessionCreation(); + return false; + } + + /** + * Configures the socket to require client authentication. + * @param isNeedClientAuth The need client auth flag. + */ + public void setNeedClientAuth(boolean isNeedClientAuth) { + this.isNeedClientAuth = isNeedClientAuth; + } + + /** + * Returns true if the socket will require client authentication. + * When a socket does not have a ssl socket, This return False. + * @return true - If the server mode socket should request + * that the client authenticate itself. + */ + public boolean getNeedClientAuth() { + if (_socket_ instanceof SSLSocket) + return ((SSLSocket)_socket_).getNeedClientAuth(); + return false; + } + + /** + * Configures the socket to request client authentication, + * but only if such a request is appropriate to the cipher + * suite negotiated. + * @param isWantClientAuth The want client auth flag. + */ + public void setWantClientAuth(boolean isWantClientAuth) { + this.isWantClientAuth = isWantClientAuth; + } + + /** + * Returns true if the socket will request client authentication. + * When a socket does not have a ssl socket, This return False. + * @return true - If the server mode socket should request + * that the client authenticate itself. + */ + public boolean getWantClientAuth() { + if (_socket_ instanceof SSLSocket) + return ((SSLSocket)_socket_).getWantClientAuth(); + return false; + } + + /** + * Configures the socket to use client (or server) mode in its first + * handshake. + * @param isClientMode The use client mode flag. + */ + public void setUseClientMode(boolean isClientMode) { + this.isClientMode = isClientMode; + } + + /** + * Returns true if the socket is set to use client mode + * in its first handshake. + * When a socket does not have a ssl socket, This return False. + * @return true - If the socket should start its first handshake + * in "client" mode. + */ + public boolean getUseClientMode() { + if (_socket_ instanceof SSLSocket) + return ((SSLSocket)_socket_).getUseClientMode(); + return false; + } + + /** + * Controls which particular cipher suites are enabled for use on this + * connection. I perform setting before a server negotiation. + * @param cipherSuites The cipher suites. + */ + public void setEnabledCipherSuites(String[] cipherSuites) { + suites = new String[cipherSuites.length]; + System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length); + } + + /** + * Returns the names of the cipher suites which could be enabled + * for use on this connection. + * When a socket does not have a ssl socket, This return null. + * @return An array of cipher suite names. + */ + public String[] getEnabledCipherSuites() { + if (_socket_ instanceof SSLSocket) + return ((SSLSocket)_socket_).getEnabledCipherSuites(); + return null; + } + + /** + * Controls which particular protocol versions are enabled for use on this + * connection. I perform setting before a server negotiation. + * @param protocolVersions The protocol versions. + */ + public void setEnabledProtocols(String[] protocolVersions) { + protocols = new String[protocolVersions.length]; + System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length); + } + + /** + * Returns the names of the protocol versions which are currently + * enabled for use on this connection. + * When a socket does not have a ssl socket, This return null. + * @return An array of protocols. + */ + public String[] getEnabledProtocols() { + if (_socket_ instanceof SSLSocket) + return ((SSLSocket)_socket_).getEnabledProtocols(); + return null; + } + + /** + * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. + * @param pbsz Protection Buffer Size. + * @throws SSLException If it server reply code not equal "200". + * @throws IOException If an I/O error occurs while either sending + * the command. + */ + public void execPBSZ(long pbsz) throws SSLException, IOException { + if (pbsz < 0 || 4294967295L < pbsz) + throw new IllegalArgumentException(); + if (FTPReply.COMMAND_OK != sendCommand( + FTPSCommand._commands[FTPSCommand.PBSZ],String.valueOf(pbsz))) + throw new SSLException(getReplyString()); + } + + /** + * PROT command.
+ * C - Clear
+ * S - Safe(SSL protocol only)
+ * E - Confidential(SSL protocol only)
+ * P - Private + * @param prot Data Channel Protection Level. + * @throws SSLException If it server reply code not equal "200". + * @throws IOException If an I/O error occurs while either sending + * the command. + */ + public void execPROT(String prot) throws SSLException, IOException { + if (prot == null) prot = DEFAULT_PROT; + if (!checkPROTValue(prot)) throw new IllegalArgumentException(); + if (FTPReply.COMMAND_OK != sendCommand( + FTPSCommand._commands[FTPSCommand.PROT], prot)) + throw new SSLException(getReplyString()); + if (DEFAULT_PROT.equals(prot)) { + setSocketFactory(null); + setServerSocketFactory(null); + } else { + setSocketFactory(new FTPSSocketFactory(context)); + + initSslContext(); + + SSLServerSocketFactory ssf = context.getServerSocketFactory(); + + setServerSocketFactory(ssf); + } + } + + /** + * I check the value that I can set in PROT Command value. + * @param prot Data Channel Protection Level. + * @return True - A set point is right / False - A set point is not right + */ + private boolean checkPROTValue(String prot) { + for (int p = 0; p < PROT_COMMAND_VALUE.length; p++) { + if (PROT_COMMAND_VALUE[p].equals(prot)) return true; + } + return false; + } + + /** + * I carry out an ftp command. + * When a CCC command was carried out, I steep socket and SocketFactory + * in a state of not ssl. + * @parm command ftp command. + * @return server reply. + * @throws IOException If an I/O error occurs while either sending + * the command. + * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String) + */ + @Override + public int sendCommand(String command, String args) throws IOException { + int repCode = super.sendCommand(command, args); + if (FTPSCommand._commands[FTPSCommand.CCC].equals(command)) { + if (FTPReply.COMMAND_OK == repCode) { + // TODO Check this - is this necessary at all? + _socket_ = planeSocket; + setSocketFactory(null); + } else { + throw new SSLException(getReplyString()); + } + } + return repCode; + } + + /** + * Returns a socket of the data connection. + * Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * @pram command The text representation of the FTP command to send. + * @param arg The arguments to the FTP command. + * If this parameter is set to null, then the command is sent with + * no argument. + * @return A Socket corresponding to the established data connection. + * Null is returned if an FTP protocol error is reported at any point + * during the establishment and initialization of the connection. + * @throws IOException If there is any problem with the connection. + * @see org.apache.commons.net.ftp.FTPClient#_openDataConnection_(java.lang.String, int) + */ + @Override + protected Socket _openDataConnection_(int command, String arg) + throws IOException { + Socket socket = super._openDataConnection_(command, arg); + if (socket != null && socket instanceof SSLSocket) { + SSLSocket sslSocket = (SSLSocket)socket; + sslSocket.setUseClientMode(isClientMode); + sslSocket.setEnableSessionCreation(isCreation); + // server mode + if (!isClientMode) { + sslSocket.setNeedClientAuth(isNeedClientAuth); + sslSocket.setWantClientAuth(isWantClientAuth); + } + if (suites != null) + sslSocket.setEnabledCipherSuites(suites); + if (protocols != null) + sslSocket.setEnabledProtocols(protocols); + sslSocket.startHandshake(); + } + return socket; + } + + /** + * Get the currently configured {@link TrustManager}. + * + * @return A TrustManager instance. + */ + public TrustManager getTrustManager() { + return trustManager; + } + + /** + * Override the default {@link TrustManager} to use. + * + * @param trustManager The TrustManager implementation to set. + */ + public void setTrustManager(TrustManager trustManager) { + this.trustManager = trustManager; + } + + + +} diff --git a/src/org/apache/commons/net/ftp/FTPSCommand.java b/src/org/apache/commons/net/ftp/FTPSCommand.java new file mode 100644 index 0000000..6fd2efa --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPSCommand.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * I acquire a command added in FTPS. + * @since 2.0 + */ +public final class FTPSCommand { + public static final int AUTH = 0; + public static final int ADAT = 1; + public static final int PBSZ = 2; + public static final int PROT = 3; + public static final int CCC = 4; + + public static final int AUTHENTICATION_SECURITY_MECHANISM = AUTH; + public static final int AUTHENTICATION_SECURITY_DATA = ADAT; + public static final int PROTECTION_BUFFER_SIZE = PBSZ; + public static final int DATA_CHANNEL_PROTECTION_LEVEL = PROT; + public static final int CLEAR_COMMAND_CHANNEL = CCC; + + static final String[] _commands = {"AUTH","ADAT","PBSZ","PROT","CCC"}; + + /** + * Retrieve the FTPS command string corresponding to a specified + * command code. + *

+ * @param command The command code. + * @return The FTPS command string corresponding to a specified + * command code. + */ + public static final String getCommand(int command) { + return _commands[command]; + } +} diff --git a/src/org/apache/commons/net/ftp/FTPSSocketFactory.java b/src/org/apache/commons/net/ftp/FTPSSocketFactory.java new file mode 100644 index 0000000..b7e1186 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPSSocketFactory.java @@ -0,0 +1,82 @@ +/* + * 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.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; + + +/** + * + * Implementation of org.apache.commons.net.SocketFactory + * + * @since 2.0 + */ +public class FTPSSocketFactory extends SocketFactory { + + private SSLContext context; + + public FTPSSocketFactory(SSLContext context) { + this.context = context; + } + + @Override + public Socket createSocket(String address, int port) throws UnknownHostException, IOException { + return this.context.getSocketFactory().createSocket(address, port); + } + + @Override + public Socket createSocket(InetAddress address, int port) throws IOException { + return this.context.getSocketFactory().createSocket(address, port); + } + + @Override + public Socket createSocket(String address, int port, InetAddress localAddress, int localPort) throws UnknownHostException, IOException { + return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); + } + + public ServerSocket createServerSocket(int port) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port)); + } + + public ServerSocket createServerSocket(int port, int backlog) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + } + + public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + } + + public ServerSocket init(ServerSocket socket) throws IOException { + ((SSLServerSocket) socket).setUseClientMode(true); + return socket; + } +} diff --git a/src/org/apache/commons/net/ftp/FTPSTrustManager.java b/src/org/apache/commons/net/ftp/FTPSTrustManager.java new file mode 100644 index 0000000..d1a2dd7 --- /dev/null +++ b/src/org/apache/commons/net/ftp/FTPSTrustManager.java @@ -0,0 +1,54 @@ +/* + * 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.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * Custom {@link TrustManager} implementation. + * + * @version $Id: FTPSTrustManager.java 658520 2008-05-21 01:14:11Z sebb $ + * @since 2.0 + */ +public class FTPSTrustManager implements X509TrustManager +{ + /** + * No-op + */ + public void checkClientTrusted(X509Certificate[] certificates, String authType) + { + return; + } + + public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException + { + for (int i = 0; i < certificates.length; ++i) + { + certificates[i].checkValidity(); + } + } + + public X509Certificate[] getAcceptedIssuers() + { + return null; + } +} diff --git a/src/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java b/src/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java new file mode 100644 index 0000000..4977b3f --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java @@ -0,0 +1,72 @@ +/* + * 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.parser; + +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPFileEntryParser; +import org.apache.commons.net.ftp.FTPFileEntryParserImpl; + +/** + * This implementation allows to pack some FileEntryParsers together + * and handle the case where to returned dirstyle isnt clearly defined. + * The matching parser will be cached. + * If the cached parser wont match due to the server changed the dirstyle, + * a new matching parser will be searched. + * + * @author Mario Ivankovits + */ +public class CompositeFileEntryParser extends FTPFileEntryParserImpl +{ + private final FTPFileEntryParser[] ftpFileEntryParsers; + private FTPFileEntryParser cachedFtpFileEntryParser; + + public CompositeFileEntryParser(FTPFileEntryParser[] ftpFileEntryParsers) + { + super(); + + this.cachedFtpFileEntryParser = null; + this.ftpFileEntryParsers = ftpFileEntryParsers; + } + + public FTPFile parseFTPEntry(String listEntry) + { + if (cachedFtpFileEntryParser != null) + { + FTPFile matched = cachedFtpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) + { + return matched; + } + } + else + { + for (int iterParser=0; iterParser < ftpFileEntryParsers.length; iterParser++) + { + FTPFileEntryParser ftpFileEntryParser = ftpFileEntryParsers[iterParser]; + + FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) + { + cachedFtpFileEntryParser = ftpFileEntryParser; + return matched; + } + } + } + return null; + } +} diff --git a/src/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java b/src/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java new file mode 100644 index 0000000..a3e832f --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java @@ -0,0 +1,116 @@ +/* + * 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.parser; + +import java.text.ParseException; +import java.util.Calendar; + +import org.apache.commons.net.ftp.Configurable; +import org.apache.commons.net.ftp.FTPClientConfig; + + +/** + *

+ * This abstract class implements the common timestamp parsing + * algorithm for all the concrete parsers. Classes derived from + * this one will parse file listings via a supplied regular expression + * that pulls out the date portion as a separate string which is + * passed to the underlying {@link FTPTimestampParser delegate} to + * handle parsing of the file timestamp. + *

+ * This class also implements the {@link Configurable Configurable} + * interface to allow the parser to be configured from the outside. + *

+ * @since 1.4 + */ +/** + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates - Comments + */ +public abstract class ConfigurableFTPFileEntryParserImpl +extends RegexFTPFileEntryParserImpl +implements Configurable +{ + + private FTPTimestampParser timestampParser; + + /** + * Only constructor for this absract class. + * @param regex Regular expression used main parsing of the + * file listing. + */ + public ConfigurableFTPFileEntryParserImpl(String regex) + { + super(regex); + this.timestampParser = new FTPTimestampParserImpl(); + } + + /** + * This method is called by the concrete parsers to delegate + * timestamp parsing to the timestamp parser. + *

+ * @param timestampStr the timestamp string pulled from the + * file listing by the regular expression parser, to be submitted + * to the timestampParser for extracting the timestamp. + * @return a java.util.Calendar containing results of the + * timestamp parse. + */ + public Calendar parseTimestamp(String timestampStr) throws ParseException { + return this.timestampParser.parseTimestamp(timestampStr); + } + + + /** + * Implementation of the {@link Configurable Configurable} + * interface. Configures this parser by delegating to the + * underlying Configurable FTPTimestampParser implementation, ' + * passing it the supplied {@link FTPClientConfig FTPClientConfig} + * if that is non-null or a default configuration defined by + * each concrete subclass. + *

+ * @param config the configuration to be used to configure this parser. + * If it is null, a default configuration defined by + * each concrete subclass is used instead. + */ + public void configure(FTPClientConfig config) + { + if (this.timestampParser instanceof Configurable) { + FTPClientConfig defaultCfg = getDefaultConfiguration(); + if (config != null) { + if (null == config.getDefaultDateFormatStr()) { + config.setDefaultDateFormatStr(defaultCfg.getDefaultDateFormatStr()); + } + if (null == config.getRecentDateFormatStr()) { + config.setRecentDateFormatStr(defaultCfg.getRecentDateFormatStr()); + } + ((Configurable)this.timestampParser).configure(config); + } else { + ((Configurable)this.timestampParser).configure(defaultCfg); + } + } + } + + /** + * Each concrete subclass must define this member to create + * a default configuration to be used when that subclass is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for the subclass. + */ + protected abstract FTPClientConfig getDefaultConfiguration(); +} diff --git a/src/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java b/src/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java new file mode 100644 index 0000000..c071a8e --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java @@ -0,0 +1,247 @@ +/* + * 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.parser; + +import org.apache.commons.net.ftp.Configurable; +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFileEntryParser; + + +/** + * This is the default implementation of the + * FTPFileEntryParserFactory interface. This is the + * implementation that will be used by + * org.apache.commons.net.ftp.FTPClient.listFiles() + * if no other implementation has been specified. + * + * @see org.apache.commons.net.ftp.FTPClient#listFiles + * @see org.apache.commons.net.ftp.FTPClient#setParserFactory + */ +public class DefaultFTPFileEntryParserFactory + implements FTPFileEntryParserFactory +{ + private FTPClientConfig config = null; + + /** + * This default implementation of the FTPFileEntryParserFactory + * interface works according to the following logic: + * First it attempts to interpret the supplied key as a fully + * qualified classname of a class implementing the + * FTPFileEntryParser interface. If that succeeds, a parser + * object of this class is instantiated and is returned; + * otherwise it attempts to interpret the key as an identirier + * commonly used by the FTP SYST command to identify systems. + *

+ * If key is not recognized as a fully qualified + * classname known to the system, this method will then attempt + * to see whether it contains a string identifying one of + * the known parsers. This comparison is case-insensitive. + * The intent here is where possible, to select as keys strings + * which are returned by the SYST command on the systems which + * the corresponding parser successfully parses. This enables + * this factory to be used in the auto-detection system. + *

+ * + * @param key should be a fully qualified classname corresponding to + * a class implementing the FTPFileEntryParser interface
+ * OR
+ * a string containing (case-insensitively) one of the + * following keywords: + *

    + *
  • {@link FTPClientConfig#SYST_UNIX UNIX}
  • + *
  • {@link FTPClientConfig#SYST_NT WINDOWS}
  • + *
  • {@link FTPClientConfig#SYST_OS2 OS/2}
  • + *
  • {@link FTPClientConfig#SYST_OS400 OS/400}
  • + *
  • {@link FTPClientConfig#SYST_VMS VMS}
  • + *
  • {@link FTPClientConfig#SYST_MVS MVS}
  • + *
  • {@link FTPClientConfig#SYST_NETWARE}
  • + *
+ * @return the FTPFileEntryParser corresponding to the supplied key. + * @throws ParserInitializationException thrown if for any reason the factory cannot resolve + * the supplied key into an FTPFileEntryParser. + * @see FTPFileEntryParser + */ + public FTPFileEntryParser createFileEntryParser(String key) + { + if (key == null) + throw new ParserInitializationException("Parser key cannot be null"); + + Class parserClass = null; + FTPFileEntryParser parser = null; + try + { + parserClass = Class.forName(key); + parser = (FTPFileEntryParser) parserClass.newInstance(); + } + catch (ClassNotFoundException e) + { + try + { + String ukey = null; + if (null != key) + { + ukey = key.toUpperCase(java.util.Locale.ENGLISH); + } + if ((ukey.indexOf(FTPClientConfig.SYST_UNIX) >= 0) + || (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0)) + { + parser = createUnixFTPEntryParser(); + } + else if (ukey.indexOf(FTPClientConfig.SYST_VMS) >= 0) + { + parser = createVMSVersioningFTPEntryParser(); + } + else if (ukey.indexOf(FTPClientConfig.SYST_NT) >= 0) + { + parser = createNTFTPEntryParser(); + } + else if (ukey.indexOf(FTPClientConfig.SYST_OS2) >= 0) + { + parser = createOS2FTPEntryParser(); + } + else if (ukey.indexOf(FTPClientConfig.SYST_OS400) >= 0 || + ukey.indexOf(FTPClientConfig.SYST_AS400) >= 0) + { + parser = createOS400FTPEntryParser(); + } + else if (ukey.indexOf(FTPClientConfig.SYST_MVS) >= 0) + { + parser = createMVSEntryParser(); + } + else if (ukey.indexOf(FTPClientConfig.SYST_NETWARE) >= 0) + { + parser = createNetwareFTPEntryParser(); + } + else + { + throw new ParserInitializationException("Unknown parser type: " + key); + } + } + catch (NoClassDefFoundError nf) { + throw new ParserInitializationException("Error initializing parser", nf); + } + + } + catch (NoClassDefFoundError e) + { + throw new ParserInitializationException("Error initializing parser", e); + } + catch (ClassCastException e) + { + throw new ParserInitializationException(parserClass.getName() + + " does not implement the interface " + + "org.apache.commons.net.ftp.FTPFileEntryParser.", e); + } + catch (Throwable e) + { + throw new ParserInitializationException("Error initializing parser", e); + } + + if (parser instanceof Configurable) { + ((Configurable)parser).configure(this.config); + } + return parser; + } + + /** + *

Implementation extracts a key from the supplied + * {@link FTPClientConfig FTPClientConfig} + * parameter and creates an object implementing the + * interface FTPFileEntryParser and uses the supplied configuration + * to configure it. + *

+ * Note that this method will generally not be called in scenarios + * that call for autodetection of parser type but rather, for situations + * where the user knows that the server uses a non-default configuration + * and knows what that configuration is. + *

+ * @param config A {@link FTPClientConfig FTPClientConfig} + * used to configure the parser created + * + * @return the @link FTPFileEntryParser FTPFileEntryParser} so created. + * @exception ParserInitializationException + * Thrown on any exception in instantiation + * @since 1.4 + */ + public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) + throws ParserInitializationException + { + this.config = config; + String key = config.getServerSystemKey(); + return createFileEntryParser(key); + } + + + public FTPFileEntryParser createUnixFTPEntryParser() + { + return new UnixFTPEntryParser(); + } + + public FTPFileEntryParser createVMSVersioningFTPEntryParser() + { + return new VMSVersioningFTPEntryParser(); + } + + public FTPFileEntryParser createNetwareFTPEntryParser() { + return new NetwareFTPEntryParser(); + } + + public FTPFileEntryParser createNTFTPEntryParser() + { + if (config != null && FTPClientConfig.SYST_NT.equals( + config.getServerSystemKey())) + { + return new NTFTPEntryParser(); + } else { + return new CompositeFileEntryParser(new FTPFileEntryParser[] + { + new NTFTPEntryParser(), + new UnixFTPEntryParser() + }); + } + } + + public FTPFileEntryParser createOS2FTPEntryParser() + { + return new OS2FTPEntryParser(); + } + + public FTPFileEntryParser createOS400FTPEntryParser() + { + if (config != null && + FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) + { + return new OS400FTPEntryParser(); + } else { + return new CompositeFileEntryParser(new FTPFileEntryParser[] + { + new OS400FTPEntryParser(), + new UnixFTPEntryParser() + }); + } + } + + public FTPFileEntryParser createMVSEntryParser() + { + return new MVSFTPEntryParser(); + } + + + +} + diff --git a/src/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java new file mode 100644 index 0000000..c7e6da7 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java @@ -0,0 +1,166 @@ +/* + * 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.parser; +import java.util.Calendar; + +import org.apache.commons.net.ftp.FTPFile; + +/** + * Parser for the Connect Enterprise Unix FTP Server From Sterling Commerce. + * Here is a sample of the sort of output line this parser processes: + * "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST" + *

+ * Note: EnterpriseUnixFTPEntryParser can only be instantiated through the + * DefaultFTPParserFactory by classname. It will not be chosen + * by the autodetection scheme. + * + * @version $Id: EnterpriseUnixFTPEntryParser.java 658518 2008-05-21 01:04:30Z sebb $ + * @author Winston Ojeda + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + */ +public class EnterpriseUnixFTPEntryParser extends RegexFTPFileEntryParserImpl +{ + + /** + * months abbreviations looked for by this parser. Also used + * to determine which month has been matched by the parser. + */ + private static final String MONTHS = + "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"; + + /** + * this is the regular expression used by this parser. + */ + private static final String REGEX = + "(([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])" + + "([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z]))" + + "(\\S*)\\s*" + + "(\\S+)\\s*" + + "(\\S*)\\s*" + + "(\\d*)\\s*" + + "(\\d*)\\s*" + + MONTHS + + "\\s*" + + "((?:[012]\\d*)|(?:3[01]))\\s*" + + "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s" + + "(\\S*)(\\s*.*)"; + + /** + * The sole constructor for a EnterpriseUnixFTPEntryParser object. + * + */ + public EnterpriseUnixFTPEntryParser() + { + super(REGEX); + } + + /** + * Parses a line of a unix FTP server file listing and converts it into a + * usable format in the form of an FTPFile instance. If + * the file listing line doesn't describe a file, null is + * returned, otherwise a FTPFile instance representing the + * files in the directory is returned. + * + * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) + { + + FTPFile file = new FTPFile(); + file.setRawListing(entry); + + if (matches(entry)) + { + String usr = group(14); + String grp = group(15); + String filesize = group(16); + String mo = group(17); + String da = group(18); + String yr = group(20); + String hr = group(21); + String min = group(22); + String name = group(23); + + file.setType(FTPFile.FILE_TYPE); + file.setUser(usr); + file.setGroup(grp); + try + { + file.setSize(Long.parseLong(filesize)); + } + catch (NumberFormatException e) + { + // intentionally do nothing + } + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.SECOND, + 0); + cal.set(Calendar.MINUTE, + 0); + cal.set(Calendar.HOUR_OF_DAY, + 0); + try + { + + int pos = MONTHS.indexOf(mo); + int month = pos / 4; + if (yr != null) + { + // it's a year + cal.set(Calendar.YEAR, + Integer.parseInt(yr)); + } + else + { + // it must be hour/minute or we wouldn't have matched + int year = cal.get(Calendar.YEAR); + + // if the month we're reading is greater than now, it must + // be last year + if (cal.get(Calendar.MONTH) < month) + { + year--; + } + cal.set(Calendar.YEAR, + year); + cal.set(Calendar.HOUR_OF_DAY, + Integer.parseInt(hr)); + cal.set(Calendar.MINUTE, + Integer.parseInt(min)); + } + cal.set(Calendar.MONTH, + month); + cal.set(Calendar.DATE, + Integer.parseInt(da)); + file.setTimestamp(cal); + } + catch (NumberFormatException e) + { + // do nothing, date will be uninitialized + } + file.setName(name); + + return file; + } + return null; + } +} diff --git a/src/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java b/src/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java new file mode 100644 index 0000000..e80ec0d --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java @@ -0,0 +1,68 @@ +/* + * 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.parser; +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFileEntryParser; + +/** + * The interface describes a factory for creating FTPFileEntryParsers. + * @since 1.2 + */ +public interface FTPFileEntryParserFactory +{ + /** + * Implementation should be a method that decodes the + * supplied key and creates an object implementing the + * interface FTPFileEntryParser. + * + * @param key A string that somehow identifies an + * FTPFileEntryParser to be created. + * + * @return the FTPFileEntryParser created. + * @exception ParserInitializationException + * Thrown on any exception in instantiation + */ + public FTPFileEntryParser createFileEntryParser(String key) + throws ParserInitializationException; + + /** + *

+ * Implementation should be a method that extracts + * a key from the supplied {@link FTPClientConfig FTPClientConfig} + * parameter and creates an object implementing the + * interface FTPFileEntryParser and uses the supplied configuration + * to configure it. + *

+ * Note that this method will generally not be called in scenarios + * that call for autodetection of parser type but rather, for situations + * where the user knows that the server uses a non-default configuration + * and knows what that configuration is. + *

+ * + * @param config A {@link FTPClientConfig FTPClientConfig} + * used to configure the parser created + * + * @return the @link FTPFileEntryParser FTPFileEntryParser} so created. + * @exception ParserInitializationException + * Thrown on any exception in instantiation + * @since 1.4 + */ + public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) + throws ParserInitializationException; + +} diff --git a/src/org/apache/commons/net/ftp/parser/FTPTimestampParser.java b/src/org/apache/commons/net/ftp/parser/FTPTimestampParser.java new file mode 100644 index 0000000..40b46cb --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/FTPTimestampParser.java @@ -0,0 +1,52 @@ +/* + * 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.parser; + +import java.text.ParseException; +import java.util.Calendar; + +/** + * This interface specifies the concept of parsing an FTP server's + * timestamp. + * @since 1.4 + */ +public interface FTPTimestampParser { + + /** + * the default default date format. + */ + public static final String DEFAULT_SDF = UnixFTPEntryParser.DEFAULT_DATE_FORMAT; + /** + * the default recent date format. + */ + public static final String DEFAULT_RECENT_SDF = UnixFTPEntryParser.DEFAULT_RECENT_DATE_FORMAT; + + /** + * Parses the supplied datestamp parameter. This parameter typically would + * have been pulled from a longer FTP listing via the regular expression + * mechanism + * @param timestampStr - the timestamp portion of the FTP directory listing + * to be parsed + * @return a java.util.Calendar object initialized to the date + * parsed by the parser + * @throws ParseException if none of the parser mechanisms belonging to + * the implementor can parse the input. + */ + public Calendar parseTimestamp(String timestampStr) throws ParseException; + +} diff --git a/src/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java b/src/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java new file mode 100644 index 0000000..02a0cc8 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java @@ -0,0 +1,301 @@ +/* + * 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.parser; + +import java.text.DateFormatSymbols; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.commons.net.ftp.Configurable; +import org.apache.commons.net.ftp.FTPClientConfig; + +/** + * Default implementation of the {@link FTPTimestampParser FTPTimestampParser} + * interface also implements the {@link org.apache.commons.net.ftp.Configurable Configurable} + * interface to allow the parsing to be configured from the outside. + * + * @see ConfigurableFTPFileEntryParserImpl + * @since 1.4 + */ +public class FTPTimestampParserImpl implements + FTPTimestampParser, Configurable +{ + + + private SimpleDateFormat defaultDateFormat; + private SimpleDateFormat recentDateFormat; + private boolean lenientFutureDates = false; + + + /** + * The only constructor for this class. + */ + public FTPTimestampParserImpl() { + setDefaultDateFormat(DEFAULT_SDF); + setRecentDateFormat(DEFAULT_RECENT_SDF); + } + + /** + * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} + * in the {@link FTPTimestampParser FTPTimestampParser} interface + * according to this algorithm: + * + * If the recentDateFormat member has been defined, try to parse the + * supplied string with that. If that parse fails, or if the recentDateFormat + * member has not been defined, attempt to parse with the defaultDateFormat + * member. If that fails, throw a ParseException. + * + * This method allows a {@link Calendar} instance to be passed in which represents the + * current (system) time. + * + * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String) + * + * @param timestampStr The timestamp to be parsed + */ + public Calendar parseTimestamp(String timestampStr) throws ParseException { + Calendar now = Calendar.getInstance(); + return parseTimestamp(timestampStr, now); + } + + /** + * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} + * in the {@link FTPTimestampParser FTPTimestampParser} interface + * according to this algorithm: + * + * If the recentDateFormat member has been defined, try to parse the + * supplied string with that. If that parse fails, or if the recentDateFormat + * member has not been defined, attempt to parse with the defaultDateFormat + * member. If that fails, throw a ParseException. + * + * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String) + * @param timestampStr The timestamp to be parsed + * @param serverTime The current time for the server + * @since 1.5 + */ + public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException { + Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it + now.setTimeZone(this.getServerTimeZone()); + Calendar working = (Calendar) now.clone(); + working.setTimeZone(getServerTimeZone()); + ParsePosition pp = new ParsePosition(0); + + Date parsed = null; + if (recentDateFormat != null) { + if (lenientFutureDates) { + // add a day to "now" so that "slop" doesn't cause a date + // slightly in the future to roll back a full year. (Bug 35181) + now.add(Calendar.DATE, 1); + } + parsed = recentDateFormat.parse(timestampStr, pp); + } + if (parsed != null && pp.getIndex() == timestampStr.length()) + { + working.setTime(parsed); + working.set(Calendar.YEAR, now.get(Calendar.YEAR)); + + if (working.after(now)) { + working.add(Calendar.YEAR, -1); + } + } else { + // Temporarily add the current year to the short date time + // to cope with short-date leap year strings. + // e.g. Java's DateFormatter will assume that "Feb 29 12:00" refers to + // Feb 29 1970 (an invalid date) rather than a potentially valid leap year date. + // This is pretty bad hack to work around the deficiencies of the JDK date/time classes. + if (recentDateFormat != null) { + pp = new ParsePosition(0); + int year = now.get(Calendar.YEAR); + String timeStampStrPlusYear = timestampStr + " " + year; + SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", + recentDateFormat.getDateFormatSymbols()); + hackFormatter.setLenient(false); + hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); + parsed = hackFormatter.parse(timeStampStrPlusYear, pp); + } + if (parsed != null && pp.getIndex() == timestampStr.length() + 5) { + working.setTime(parsed); + } + else { + pp = new ParsePosition(0); + parsed = defaultDateFormat.parse(timestampStr, pp); + // note, length checks are mandatory for us since + // SimpleDateFormat methods will succeed if less than + // full string is matched. They will also accept, + // despite "leniency" setting, a two-digit number as + // a valid year (e.g. 22:04 will parse as 22 A.D.) + // so could mistakenly confuse an hour with a year, + // if we don't insist on full length parsing. + if (parsed != null && pp.getIndex() == timestampStr.length()) { + working.setTime(parsed); + } else { + throw new ParseException( + "Timestamp could not be parsed with older or recent DateFormat", + pp.getIndex()); + } + } + } + return working; + } + + /** + * @return Returns the defaultDateFormat. + */ + public SimpleDateFormat getDefaultDateFormat() { + return defaultDateFormat; + } + /** + * @return Returns the defaultDateFormat pattern string. + */ + public String getDefaultDateFormatString() { + return defaultDateFormat.toPattern(); + } + /** + * @param defaultDateFormat The defaultDateFormat to be set. + */ + private void setDefaultDateFormat(String format) { + if (format != null) { + this.defaultDateFormat = new SimpleDateFormat(format); + this.defaultDateFormat.setLenient(false); + } + } + /** + * @return Returns the recentDateFormat. + */ + public SimpleDateFormat getRecentDateFormat() { + return recentDateFormat; + } + /** + * @return Returns the recentDateFormat. + */ + public String getRecentDateFormatString() { + return recentDateFormat.toPattern(); + } + /** + * @param recentDateFormat The recentDateFormat to set. + */ + private void setRecentDateFormat(String format) { + if (format != null) { + this.recentDateFormat = new SimpleDateFormat(format); + this.recentDateFormat.setLenient(false); + } + } + + /** + * @return returns an array of 12 strings representing the short + * month names used by this parse. + */ + public String[] getShortMonths() { + return defaultDateFormat.getDateFormatSymbols().getShortMonths(); + } + + + /** + * @return Returns the serverTimeZone used by this parser. + */ + public TimeZone getServerTimeZone() { + return this.defaultDateFormat.getTimeZone(); + } + /** + * sets a TimeZone represented by the supplied ID string into all + * of the parsers used by this server. + * @param serverTimeZone Time Id java.util.TimeZone id used by + * the ftp server. If null the client's local time zone is assumed. + */ + private void setServerTimeZone(String serverTimeZoneId) { + TimeZone serverTimeZone = TimeZone.getDefault(); + if (serverTimeZoneId != null) { + serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId); + } + this.defaultDateFormat.setTimeZone(serverTimeZone); + if (this.recentDateFormat != null) { + this.recentDateFormat.setTimeZone(serverTimeZone); + } + } + + /** + * Implementation of the {@link Configurable Configurable} + * interface. Configures this FTPTimestampParser according + * to the following logic: + *

+ * Set up the {@link FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat} + * and optionally the {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat} + * to values supplied in the config based on month names configured as follows: + *

    + *
  • If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString} + * has been supplied in the config, use that to parse parse timestamps.
  • + *
  • Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode} + * has been supplied in the config, use the month names represented + * by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language} + * to parse timestamps.
  • + *
  • otherwise use default English month names
  • + *

+ * Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId} + * has been supplied via the config, set that into all date formats that have + * been configured. + *

+ */ + public void configure(FTPClientConfig config) { + DateFormatSymbols dfs = null; + + String languageCode = config.getServerLanguageCode(); + String shortmonths = config.getShortMonthNames(); + if (shortmonths != null) { + dfs = FTPClientConfig.getDateFormatSymbols(shortmonths); + } else if (languageCode != null) { + dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode); + } else { + dfs = FTPClientConfig.lookupDateFormatSymbols("en"); + } + + + String recentFormatString = config.getRecentDateFormatStr(); + if (recentFormatString == null) { + this.recentDateFormat = null; + } else { + this.recentDateFormat = new SimpleDateFormat(recentFormatString, dfs); + this.recentDateFormat.setLenient(false); + } + + String defaultFormatString = config.getDefaultDateFormatStr(); + if (defaultFormatString == null) { + throw new IllegalArgumentException("defaultFormatString cannot be null"); + } + this.defaultDateFormat = new SimpleDateFormat(defaultFormatString, dfs); + this.defaultDateFormat.setLenient(false); + + setServerTimeZone(config.getServerTimeZoneId()); + + this.lenientFutureDates = config.isLenientFutureDates(); + } + /** + * @return Returns the lenientFutureDates. + */ + boolean isLenientFutureDates() { + return lenientFutureDates; + } + /** + * @param lenientFutureDates The lenientFutureDates to set. + */ + void setLenientFutureDates(boolean lenientFutureDates) { + this.lenientFutureDates = lenientFutureDates; + } +} diff --git a/src/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java new file mode 100644 index 0000000..cd87e6e --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java @@ -0,0 +1,495 @@ +/* + * 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.parser; + +import java.text.ParseException; +import java.util.List; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation of FTPFileEntryParser and FTPFileListParser for IBM zOS/MVS + * Systems. + * + * @author Henrik Sorensen + * @author Jeff Nadler + * @author William Noto + * + * @version $Id: MVSFTPEntryParser.java 658520 2008-05-21 01:14:11Z sebb $ + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for + * usage instructions) + */ +public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { + + static final int UNKNOWN_LIST_TYPE = -1; + static final int FILE_LIST_TYPE = 0; + static final int MEMBER_LIST_TYPE = 1; + static final int UNIX_LIST_TYPE = 2; + static final int JES_LEVEL_1_LIST_TYPE = 3; + static final int JES_LEVEL_2_LIST_TYPE = 4; + + private int isType = UNKNOWN_LIST_TYPE; + + /** + * Fallback parser for Unix-style listings + */ + private UnixFTPEntryParser unixFTPEntryParser; + + /** + * Dates are ignored for file lists, but are used for member lists where + * possible + */ + static final String DEFAULT_DATE_FORMAT = "yyyy/MM/dd HH:mm"; // 2001/09/18 + // 13:52 + + /** + * Matches these entries: Volume Unit Referred Ext Used Recfm Lrecl BlkSz + * Dsorg Dsname B10142 3390 2006/03/20 2 31 F 80 80 PS MDI.OKL.WORK + * + */ + static final String FILE_LIST_REGEX = "\\S+\\s+" + // volume + // ignored + "\\S+\\s+" + // unit - ignored + "\\S+\\s+" + // access date - ignored + "\\S+\\s+" + // extents -ignored + "\\S+\\s+" + // used - ignored + "[FV]\\S*\\s+" + // recfm - must start with F or V + "\\S+\\s+" + // logical record length -ignored + "\\S+\\s+" + // block size - ignored + "(PS|PO|PO-E)\\s+" + // Dataset organisation. Many exist + // but only support: PS, PO, PO-E + "(\\S+)\\s*"; // Dataset Name (file name) + + /** + * Matches these entries: Name VV.MM Created Changed Size Init Mod Id + * TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001 + */ + static final String MEMBER_LIST_REGEX = "(\\S+)\\s+" + // name + "\\S+\\s+" + // version, modification (ignored) + "\\S+\\s+" + // create date (ignored) + "(\\S+)\\s+" + // modification date + "(\\S+)\\s+" + // modification time + "\\S+\\s+" + // size in lines (ignored) + "\\S+\\s+" + // size in lines at creation(ignored) + "\\S+\\s+" + // lines modified (ignored) + "\\S+\\s*"; // id of user who modified (ignored) + + /** + * Matches these entries, note: no header: IBMUSER1 JOB01906 OUTPUT 3 Spool + * Files 012345678901234567890123456789012345678901234 1 2 3 4 + */ + static final String JES_LEVEL_1_LIST_REGEX = "(\\S+)\\s+" + // job + // name + // ignored + "(\\S+)\\s+" + // job number + "(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) + "(\\S+)\\s+" + // number of spool files + "(\\S+)\\s+" + // Text "Spool" ignored + "(\\S+)\\s*" // Text "Files" ignored + ; + + /** + * JES INTERFACE LEVEL 2 parser Matches these entries: JOBNAME JOBID OWNER + * STATUS CLASS IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files + * IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files + * 012345678901234567890123456789012345678901234 1 2 3 4 + * 012345678901234567890123456789012345678901234567890 + */ + + static final String JES_LEVEL_2_LIST_REGEX = "(\\S+)\\s+" + // job + // name + // ignored + "(\\S+)\\s+" + // job number + "(\\S+)\\s+" + // owner ignored + "(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) ignored + "(\\S+)\\s+" + // job class ignored + "(\\S+).*" // rest ignored + ; + + /* + * --------------------------------------------------------------------- + * Very brief and incomplete description of the zOS/MVS-filesystem. (Note: + * "zOS" is the operating system on the mainframe, and is the new name for + * MVS) + * + * The filesystem on the mainframe does not have hierarchal structure as for + * example the unix filesystem. For a more comprehensive description, please + * refer to the IBM manuals + * + * @LINK: + * http://publibfp.boulder.ibm.com/cgi-bin/bookmgr/BOOKS/dgt2d440/CONTENTS + * + * + * Dataset names ============= + * + * A dataset name consist of a number of qualifiers separated by '.', each + * qualifier can be at most 8 characters, and the total length of a dataset + * can be max 44 characters including the dots. + * + * + * Dataset organisation ==================== + * + * A dataset represents a piece of storage allocated on one or more disks. + * The structure of the storage is described with the field dataset + * organinsation (DSORG). There are a number of dataset organisations, but + * only two are usable for FTP transfer. + * + * DSORG: PS: sequential, or flat file PO: partitioned dataset PO-E: + * extended partitioned dataset + * + * The PS file is just a flat file, as you would find it on the unix file + * system. + * + * The PO and PO-E files, can be compared to a single level directory + * structure. A PO file consist of a number of dataset members, or files if + * you will. It is possible to CD into the file, and to retrieve the + * individual members. + * + * + * Dataset record format ===================== + * + * The physical layout of the dataset is described on the dataset itself. + * There are a number of record formats (RECFM), but just a few is relavant + * for the FTP transfer. + * + * Any one beginning with either F or V can safely used by FTP transfer. All + * others should only be used with great care, so this version will just + * ignore the other record formats. F means a fixed number of records per + * allocated storage, and V means a variable number of records. + * + * + * Other notes =========== + * + * The file system supports automatically backup and retrieval of datasets. + * If a file is backed up, the ftp LIST command will return: ARCIVE Not + * Direct Access Device KJ.IOP998.ERROR.PL.UNITTEST + * + * + * Implementation notes ==================== + * + * Only datasets that have dsorg PS, PO or PO-E and have recfm beginning + * with F or V, is fully parsed. + * + * The following fields in FTPFile is used: FTPFile.Rawlisting: Always set. + * FTPFile.Type: DIRECTORY_TYPE or FILE_TYPE or UNKNOWN FTPFile.Name: name + * FTPFile.Timestamp: change time or null + * + * + * + * Additional information ====================== + * + * The MVS ftp server supports a number of features via the FTP interface. + * The features are controlled with the FTP command quote site filetype= + * SEQ is the default and used for normal file transfer JES is used to + * interact with the Job Entry Subsystem (JES) similar to a job scheduler + * DB2 is used to interact with a DB2 subsystem + * + * This parser supports SEQ and JES. + * + * + * + * + * + * + */ + + /** + * The sole constructor for a MVSFTPEntryParser object. + * + */ + public MVSFTPEntryParser() { + super(""); // note the regex is set in preParse. + super.configure(null); // configure parser with default configurations + } + + /** + * Parses a line of an z/OS - MVS FTP server file listing and converts it + * into a usable format in the form of an FTPFile instance. + * If the file listing line doesn't describe a file, then + * null is returned. Otherwise a FTPFile + * instance representing the file is returned. + * + * @param entry + * A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) { + boolean isParsed = false; + FTPFile f = new FTPFile(); + + if (isType == FILE_LIST_TYPE) + isParsed = parseFileList(f, entry); + else if (isType == MEMBER_LIST_TYPE) { + isParsed = parseMemberList(f, entry); + if (!isParsed) + isParsed = parseSimpleEntry(f, entry); + } else if (isType == UNIX_LIST_TYPE) { + isParsed = parseUnixList(f, entry); + } else if (isType == JES_LEVEL_1_LIST_TYPE) { + isParsed = parseJeslevel1List(f, entry); + } else if (isType == JES_LEVEL_2_LIST_TYPE) { + isParsed = parseJeslevel2List(f, entry); + } + + if (!isParsed) + f = null; + + return f; + } + + /** + * Parse entries representing a dataset list. Only datasets with DSORG PS or + * PO or PO-E and with RECFM F* or V* will be parsed. + * + * Format of ZOS/MVS file list: 1 2 3 4 5 6 7 8 9 10 Volume Unit Referred + * Ext Used Recfm Lrecl BlkSz Dsorg Dsname B10142 3390 2006/03/20 2 31 F 80 + * 80 PS MDI.OKL.WORK ARCIVE Not Direct Access Device + * KJ.IOP998.ERROR.PL.UNITTEST B1N231 3390 2006/03/20 1 15 VB 256 27998 PO + * PLU B1N231 3390 2006/03/20 1 15 VB 256 27998 PO-E PLB + * + * ----------------------------------- Group within Regex [1] Volume [2] + * Unit [3] Referred [4] Ext: number of extents [5] Used [6] Recfm: Record + * format [7] Lrecl: Logical record length [8] BlkSz: Block size [9] Dsorg: + * Dataset organisation. Many exists but only support: PS, PO, PO-E [10] + * Dsname: Dataset name + * + * Note: When volume is ARCIVE, it means the dataset is stored somewhere in + * a tape archive. These entries is currently not supported by this parser. + * A null value is returned. + * + * @param file + * will be updated with Name, Type, Timestamp if parsed. + * @param entry zosDirectoryEntry + * @return true: entry was parsed, false: entry was not parsed. + */ + private boolean parseFileList(FTPFile file, String entry) { + if (matches(entry)) { + file.setRawListing(entry); + String name = group(2); + String dsorg = group(1); + file.setName(name); + + // DSORG + if ("PS".equals(dsorg)) { + file.setType(FTPFile.FILE_TYPE); + } + else if ("PO".equals(dsorg) || "PO-E".equals(dsorg)) { + // regex already ruled out anything other than PO or PO-E + file.setType(FTPFile.DIRECTORY_TYPE); + } + else { + return false; + } + + return true; + } + + return false; + } + + /** + * Parse entries within a partitioned dataset. + * + * Format of a memberlist within a PDS: 1 2 3 4 5 6 7 8 9 Name VV.MM Created + * Changed Size Init Mod Id TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 + * 0 KIL001 TBTOOL 01.12 2002/09/12 2004/11/26 19:54 51 28 0 KIL001 + * + * ------------------------------------------- [1] Name [2] VV.MM: Version . + * modification [3] Created: yyyy / MM / dd [4,5] Changed: yyyy / MM / dd + * HH:mm [6] Size: number of lines [7] Init: number of lines when first + * created [8] Mod: number of modified lines a last save [9] Id: User id for + * last update + * + * + * @param file + * will be updated with Name, Type and Timestamp if parsed. + * @param entry zosDirectoryEntry + * @return true: entry was parsed, false: entry was not parsed. + */ + private boolean parseMemberList(FTPFile file, String entry) { + if (matches(entry)) { + file.setRawListing(entry); + String name = group(1); + String datestr = group(2) + " " + group(3); + file.setName(name); + file.setType(FTPFile.FILE_TYPE); + try { + file.setTimestamp(super.parseTimestamp(datestr)); + } catch (ParseException e) { + e.printStackTrace(); + // just ignore parsing errors. + // TODO check this is ok + return false; // this is a parsing failure too. + } + return true; + } + + return false; + } + + /** + * Assigns the name to the first word of the entry. Only to be used from a + * safe context, for example from a memberlist, where the regex for some + * reason fails. Then just assign the name field of FTPFile. + * + * @param file + * @param entry + * @return + */ + private boolean parseSimpleEntry(FTPFile file, String entry) { + if (entry != null && entry.length() > 0) { + file.setRawListing(entry); + String name = entry.split(" ")[0]; + file.setName(name); + file.setType(FTPFile.FILE_TYPE); + return true; + } + return false; + } + + /** + * Parse the entry as a standard unix file. Using the UnixFTPEntryParser. + * + * @param file + * @param entry + * @return true: entry is parsed, false: entry could not be parsed. + */ + private boolean parseUnixList(FTPFile file, String entry) { + file = unixFTPEntryParser.parseFTPEntry(entry); + if (file == null) + return false; + return true; + } + + /** + * Matches these entries, note: no header: [1] [2] [3] [4] [5] IBMUSER1 + * JOB01906 OUTPUT 3 Spool Files + * 012345678901234567890123456789012345678901234 1 2 3 4 + * ------------------------------------------- Group in regex [1] Job name + * [2] Job number [3] Job status (INPUT,ACTIVE,OUTPUT) [4] Number of sysout + * files [5] The string "Spool Files" + * + * + * @param file + * will be updated with Name, Type and Timestamp if parsed. + * @param entry zosDirectoryEntry + * @return true: entry was parsed, false: entry was not parsed. + */ + private boolean parseJeslevel1List(FTPFile file, String entry) { + if (matches(entry)) { + if (group(3).equalsIgnoreCase("OUTPUT")) { + file.setRawListing(entry); + String name = group(2); /* Job Number, used by GET */ + file.setName(name); + file.setType(FTPFile.FILE_TYPE); + return true; + } + } + + return false; + } + + /** + * Matches these entries, note: no header: [1] [2] [3] [4] [5] JOBNAME JOBID + * OWNER STATUS CLASS IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool + * files IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files + * 012345678901234567890123456789012345678901234 1 2 3 4 + * ------------------------------------------- Group in regex [1] Job name + * [2] Job number [3] Owner [4] Job status (INPUT,ACTIVE,OUTPUT) [5] Job + * Class [6] The rest + * + * + * @param file + * will be updated with Name, Type and Timestamp if parsed. + * @param entry zosDirectoryEntry + * @return true: entry was parsed, false: entry was not parsed. + */ + private boolean parseJeslevel2List(FTPFile file, String entry) { + if (matches(entry)) { + if (group(4).equalsIgnoreCase("OUTPUT")) { + file.setRawListing(entry); + String name = group(2); /* Job Number, used by GET */ + file.setName(name); + file.setType(FTPFile.FILE_TYPE); + return true; + } + } + + return false; + } + + /** + * preParse is called as part of the interface. Per definition is is called + * before the parsing takes place. Three kind of lists is recognize: + * z/OS-MVS File lists z/OS-MVS Member lists unix file lists + * @since 2.0 + */ + @Override + public List preParse(List orig) { + // simply remove the header line. Composite logic will take care of the + // two different types of + // list in short order. + if (orig != null && orig.size() > 0) { + String header = orig.get(0); + if (header.indexOf("Volume") >= 0 && header.indexOf("Dsname") >= 0) { + setType(FILE_LIST_TYPE); + super.setRegex(FILE_LIST_REGEX); + } else if (header.indexOf("Name") >= 0 && header.indexOf("Id") >= 0) { + setType(MEMBER_LIST_TYPE); + super.setRegex(MEMBER_LIST_REGEX); + } else if (header.indexOf("total") == 0) { + setType(UNIX_LIST_TYPE); + unixFTPEntryParser = new UnixFTPEntryParser(); + } else if (header.indexOf("Spool Files") >= 30) { + setType(JES_LEVEL_1_LIST_TYPE); + super.setRegex(JES_LEVEL_1_LIST_REGEX); + } else if (header.indexOf("JOBNAME") == 0 + && header.indexOf("JOBID") > 8) {// header contains JOBNAME JOBID OWNER // STATUS CLASS + setType(JES_LEVEL_2_LIST_TYPE); + super.setRegex(JES_LEVEL_2_LIST_REGEX); + } else { + setType(UNKNOWN_LIST_TYPE); + } + + if (isType != JES_LEVEL_1_LIST_TYPE) { // remove header is necessary + orig.remove(0); + } + } + + return orig; + } + + /** + * Explicitly set the type of listing being processed. + * @param type The listing type. + */ + void setType(int type) { + isType = type; + } + + /* + * @return + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_MVS, + DEFAULT_DATE_FORMAT, null, null, null, null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java new file mode 100644 index 0000000..b6bc75e --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java @@ -0,0 +1,147 @@ +/* + * 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.parser; +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation of FTPFileEntryParser and FTPFileListParser for NT Systems. + * + * @author Winston Ojeda + * @author Steve Cohen + * @version $Id: NTFTPEntryParser.java 658518 2008-05-21 01:04:30Z sebb $ + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + */ +public class NTFTPEntryParser extends ConfigurableFTPFileEntryParserImpl +{ + + private static final String DEFAULT_DATE_FORMAT + = "MM-dd-yy hh:mma"; //11-09-01 12:30PM + + + /** + * this is the regular expression used by this parser. + */ + private static final String REGEX = + "(\\S+)\\s+(\\S+)\\s+" + + "(?:()|([0-9]+))\\s+" + + "(\\S.*)"; + + /** + * The sole constructor for an NTFTPEntryParser object. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public NTFTPEntryParser() + { + this(null); + } + + /** + * This constructor allows the creation of an NTFTPEntryParser object + * with something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public NTFTPEntryParser(FTPClientConfig config) + { + super(REGEX); + configure(config); + } + + /** + * Parses a line of an NT FTP server file listing and converts it into a + * usable format in the form of an FTPFile instance. If the + * file listing line doesn't describe a file, null is + * returned, otherwise a FTPFile instance representing the + * files in the directory is returned. + *

+ * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) + { + FTPFile f = new FTPFile(); + f.setRawListing(entry); + + if (matches(entry)) + { + String datestr = group(1)+" "+group(2); + String dirString = group(3); + String size = group(4); + String name = group(5); + try + { + f.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + if (null == name || name.equals(".") || name.equals("..")) + { + return (null); + } + f.setName(name); + + + if ("

".equals(dirString)) + { + f.setType(FTPFile.DIRECTORY_TYPE); + f.setSize(0); + } + else + { + f.setType(FTPFile.FILE_TYPE); + if (null != size) + { + f.setSize(Long.parseLong(size)); + } + } + return (f); + } + return null; + } + + /** + * Defines a default configuration to be used when this class is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for this parser. + */ + @Override + public FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig( + FTPClientConfig.SYST_NT, + DEFAULT_DATE_FORMAT, + null, null, null, null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java new file mode 100644 index 0000000..3cbea82 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java @@ -0,0 +1,176 @@ +/* + * 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.parser; + +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation of FTPFileEntryParser and FTPFileListParser for Netware Systems. Note that some of the proprietary + * extensions for Novell-specific operations are not supported. See + * http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html + * for more details. + * + * @author Rory Winston + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @version $Id: NetwareFTPEntryParser.java 658520 2008-05-21 01:14:11Z sebb $ + * @since 1.5 + */ +public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { + + /** + * Default date format is e.g. Feb 22 2006 + */ + private static final String DEFAULT_DATE_FORMAT = "MMM dd yyyy"; + + /** + * Default recent date format is e.g. Feb 22 17:32 + */ + private static final String DEFAULT_RECENT_DATE_FORMAT = "MMM dd HH:mm"; + + /** + * this is the regular expression used by this parser. + * Example: d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2 + */ + private static final String REGEX = "(d|-){1}\\s+" // Directory/file flag + + "\\[(.*)\\]\\s+" // Attributes + + "(\\S+)\\s+" + "(\\d+)\\s+" // Owner and size + + "(\\S+\\s+\\S+\\s+((\\d+:\\d+)|(\\d{4})))" // Long/short date format + + "\\s+(.*)"; // Filename (incl. spaces) + + /** + * The default constructor for a NetwareFTPEntryParser object. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public NetwareFTPEntryParser() { + this(null); + } + + /** + * This constructor allows the creation of an NetwareFTPEntryParser object + * with something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public NetwareFTPEntryParser(FTPClientConfig config) { + super(REGEX); + configure(config); + } + + /** + * Parses a line of an NetwareFTP server file listing and converts it into a + * usable format in the form of an FTPFile instance. If the + * file listing line doesn't describe a file, null is + * returned, otherwise a FTPFile instance representing the + * files in the directory is returned. + *

+ *

+ * Netware file permissions are in the following format: RWCEAFMS, and are explained as follows: + *

    + *
  • S - Supervisor; All rights. + *
  • R - Read; Right to open and read or execute. + *
  • W - Write; Right to open and modify. + *
  • C - Create; Right to create; when assigned to a file, allows a deleted file to be recovered. + *
  • E - Erase; Right to delete. + *
  • M - Modify; Right to rename a file and to change attributes. + *
  • F - File Scan; Right to see directory or file listings. + *
  • A - Access Control; Right to modify trustee assignments and the Inherited Rights Mask. + *
+ * + * See here + * for more details + * + * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) { + + FTPFile f = new FTPFile(); + if (matches(entry)) { + String dirString = group(1); + String attrib = group(2); + String user = group(3); + String size = group(4); + String datestr = group(5); + String name = group(9); + + try { + f.setTimestamp(super.parseTimestamp(datestr)); + } catch (ParseException e) { + // intentionally do nothing + } + + //is it a DIR or a file + if (dirString.trim().equals("d")) { + f.setType(FTPFile.DIRECTORY_TYPE); + } else // Should be "-" + { + f.setType(FTPFile.FILE_TYPE); + } + + f.setUser(user); + + //set the name + f.setName(name.trim()); + + //set the size + f.setSize(Long.parseLong(size.trim())); + + // Now set the permissions (or at least a subset thereof - full permissions would probably require + // subclassing FTPFile and adding extra metainformation there) + if (attrib.indexOf("R") != -1) { + f.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, + true); + } + if (attrib.indexOf("W") != -1) { + f.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, + true); + } + + return (f); + } + return null; + + } + + /** + * Defines a default configuration to be used when this class is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_NETWARE, + DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT, null, null, + null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java new file mode 100644 index 0000000..dc02ffb --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java @@ -0,0 +1,147 @@ +/* + * 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.parser; +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation of FTPFileEntryParser and FTPFileListParser for OS2 Systems. + * + * @author Winston Ojeda + * @author Steve Cohen + * @version $Id: OS2FTPEntryParser.java 658518 2008-05-21 01:04:30Z sebb $ + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + */ +public class OS2FTPEntryParser extends ConfigurableFTPFileEntryParserImpl + +{ + + private static final String DEFAULT_DATE_FORMAT + = "MM-dd-yy HH:mm"; //11-09-01 12:30 + /** + * this is the regular expression used by this parser. + */ + private static final String REGEX = + "\\s*([0-9]+)\\s*" + + "(\\s+|[A-Z]+)\\s*" + + "(DIR|\\s+)\\s*" + + "(\\S+)\\s+(\\S+)\\s+" /* date stuff */ + + "(\\S.*)"; + + /** + * The default constructor for a OS2FTPEntryParser object. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public OS2FTPEntryParser() + { + this(null); + } + + /** + * This constructor allows the creation of an OS2FTPEntryParser object + * with something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public OS2FTPEntryParser(FTPClientConfig config) + { + super(REGEX); + configure(config); + } + + /** + * Parses a line of an OS2 FTP server file listing and converts it into a + * usable format in the form of an FTPFile instance. If the + * file listing line doesn't describe a file, null is + * returned, otherwise a FTPFile instance representing the + * files in the directory is returned. + *

+ * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) + { + + FTPFile f = new FTPFile(); + if (matches(entry)) + { + String size = group(1); + String attrib = group(2); + String dirString = group(3); + String datestr = group(4)+" "+group(5); + String name = group(6); + try + { + f.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + + //is it a DIR or a file + if (dirString.trim().equals("DIR") || attrib.trim().equals("DIR")) + { + f.setType(FTPFile.DIRECTORY_TYPE); + } + else + { + f.setType(FTPFile.FILE_TYPE); + } + + + //set the name + f.setName(name.trim()); + + //set the size + f.setSize(Long.parseLong(size.trim())); + + return (f); + } + return null; + + } + + /** + * Defines a default configuration to be used when this class is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig( + FTPClientConfig.SYST_OS2, + DEFAULT_DATE_FORMAT, + null, null, null, null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java new file mode 100644 index 0000000..66c370b --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java @@ -0,0 +1,158 @@ +/* + * 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.parser; + +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * @version $Id: OS400FTPEntryParser.java 658518 2008-05-21 01:04:30Z sebb $ + */ + +public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl +{ + private static final String DEFAULT_DATE_FORMAT + = "yy/MM/dd HH:mm:ss"; //01/11/09 12:30:24 + + + + private static final String REGEX = + "(\\S+)\\s+" // user + + "(\\d+)\\s+" // size + + "(\\S+)\\s+(\\S+)\\s+" // date stuff + + "(\\*\\S+)\\s+" // *STMF/*DIR + + "(\\S+/?)\\s*"; // filename + + + /** + * The default constructor for a OS400FTPEntryParser object. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public OS400FTPEntryParser() + { + this(null); + } + + /** + * This constructor allows the creation of an OS400FTPEntryParser object + * with something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public OS400FTPEntryParser(FTPClientConfig config) + { + super(REGEX); + configure(config); + } + + + public FTPFile parseFTPEntry(String entry) + { + + FTPFile file = new FTPFile(); + file.setRawListing(entry); + int type; + + if (matches(entry)) + { + String usr = group(1); + String filesize = group(2); + String datestr = group(3)+" "+group(4); + String typeStr = group(5); + String name = group(6); + + try + { + file.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + + if (typeStr.equalsIgnoreCase("*STMF")) + { + type = FTPFile.FILE_TYPE; + } + else if (typeStr.equalsIgnoreCase("*DIR")) + { + type = FTPFile.DIRECTORY_TYPE; + } + else + { + type = FTPFile.UNKNOWN_TYPE; + } + + file.setType(type); + + file.setUser(usr); + + try + { + file.setSize(Long.parseLong(filesize)); + } + catch (NumberFormatException e) + { + // intentionally do nothing + } + + if (name.endsWith("/")) + { + name = name.substring(0, name.length() - 1); + } + int pos = name.lastIndexOf('/'); + if (pos > -1) + { + name = name.substring(pos + 1); + } + + file.setName(name); + + return file; + } + return null; + } + + /** + * Defines a default configuration to be used when this class is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig( + FTPClientConfig.SYST_OS400, + DEFAULT_DATE_FORMAT, + null, null, null, null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/ParserInitializationException.java b/src/org/apache/commons/net/ftp/parser/ParserInitializationException.java new file mode 100644 index 0000000..8af9261 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/ParserInitializationException.java @@ -0,0 +1,65 @@ +/* + * 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.parser; + +/** + * This class encapsulates all errors that may be thrown by + * the process of an FTPFileEntryParserFactory creating and + * instantiating an FTPFileEntryParser. + */ +public class ParserInitializationException extends RuntimeException { + + /** + * Root exception that caused this to be thrown + */ + private final Throwable rootCause; + + /** + * Constucts a ParserInitializationException with just a message + * + * @param message Exception message + */ + public ParserInitializationException(String message) { + super(message); + this.rootCause = null; + } + + /** + * Constucts a ParserInitializationException with a message + * and a root cause. + * + * @param message Exception message + * @param rootCause root cause throwable that caused + * this to be thrown + */ + public ParserInitializationException(String message, Throwable rootCause) { + super(message); + this.rootCause = rootCause; + } + + /** + * returns the root cause of this exception or null + * if no root cause was specified. + * + * @return the root cause of this exception being thrown + */ + public Throwable getRootCause() { + return this.rootCause; + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java b/src/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java new file mode 100644 index 0000000..5277444 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java @@ -0,0 +1,155 @@ +/* + * 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.parser; + +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.net.ftp.FTPFileEntryParserImpl; + +/** + * This abstract class implements both the older FTPFileListParser and + * newer FTPFileEntryParser interfaces with default functionality. + * All the classes in the parser subpackage inherit from this. + * + * This is the base for all regular based FTPFileEntryParser + * + * @author Steve Cohen + */ +public abstract class RegexFTPFileEntryParserImpl extends + FTPFileEntryParserImpl { + /** + * internal pattern the matcher tries to match, representing a file + * entry + */ + private Pattern pattern = null; + + /** + * internal match result used by the parser + */ + private MatchResult result = null; + + /** + * Internal PatternMatcher object used by the parser. It has protected + * scope in case subclasses want to make use of it for their own purposes. + */ + protected Matcher _matcher_ = null; + + /** + * The constructor for a RegexFTPFileEntryParserImpl object. + * + * @param regex The regular expression with which this object is + * initialized. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen in + * normal conditions. It it is seen, this is a sign that a subclass has + * been created with a bad regular expression. Since the parser must be + * created before use, this means that any bad parser subclasses created + * from this will bomb very quickly, leading to easy detection. + */ + + public RegexFTPFileEntryParserImpl(String regex) { + super(); + setRegex(regex); + } + + /** + * Convenience method delegates to the internal MatchResult's matches() + * method. + * + * @param s the String to be matched + * @return true if s matches this object's regular expression. + */ + + public boolean matches(String s) { + this.result = null; + _matcher_ = pattern.matcher(s); + if (_matcher_.matches()) { + this.result = _matcher_.toMatchResult(); + } + return null != this.result; + } + + /** + * Convenience method + * + * @return the number of groups() in the internal MatchResult. + */ + + public int getGroupCnt() { + if (this.result == null) { + return 0; + } + return this.result.groupCount(); + } + + /** + * Convenience method delegates to the internal MatchResult's group() + * method. + * + * @param matchnum match group number to be retrieved + * + * @return the content of the matchnum'th group of the internal + * match or null if this method is called without a match having + * been made. + */ + public String group(int matchnum) { + if (this.result == null) { + return null; + } + return this.result.group(matchnum); + } + + /** + * For debugging purposes - returns a string shows each match group by + * number. + * + * @return a string shows each match group by number. + */ + + public String getGroupsAsString() { + StringBuffer b = new StringBuffer(); + for (int i = 1; i <= this.result.groupCount(); i++) { + b.append(i).append(") ").append(this.result.group(i)).append( + System.getProperty("line.separator")); + } + return b.toString(); + } + + /** + * Alter the current regular expression being utilised for entry parsing + * and create a new {@link Pattern} instance. + * @param regex The new regular expression + * @return + * @since 2.0 + */ + public boolean setRegex(String regex) { + try { + pattern = Pattern.compile(regex); + } catch (PatternSyntaxException pse) { + throw new IllegalArgumentException("Unparseable regex supplied: " + + regex); + } + return (pattern != null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java new file mode 100644 index 0000000..2a2a909 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java @@ -0,0 +1,295 @@ +/* + * 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.parser; +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation FTPFileEntryParser and FTPFileListParser for standard + * Unix Systems. + * + * This class is based on the logic of Daniel Savarese's + * DefaultFTPListParser, but adapted to use regular expressions and to fit the + * new FTPFileEntryParser interface. + * @version $Id: UnixFTPEntryParser.java 658518 2008-05-21 01:04:30Z sebb $ + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + */ +public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl +{ + + static final String DEFAULT_DATE_FORMAT + = "MMM d yyyy"; //Nov 9 2001 + + static final String DEFAULT_RECENT_DATE_FORMAT + = "MMM d HH:mm"; //Nov 9 20:06 + + static final String NUMERIC_DATE_FORMAT + = "yyyy-MM-dd HH:mm"; //2001-11-09 20:06 + + /** + * Some Linux distributions are now shipping an FTP server which formats + * file listing dates in an all-numeric format: + * "yyyy-MM-dd HH:mm. + * This is a very welcome development, and hopefully it will soon become + * the standard. However, since it is so new, for now, and possibly + * forever, we merely accomodate it, but do not make it the default. + *

+ * For now end users may specify this format only via + * UnixFTPEntryParser(FTPClientConfig). + * Steve Cohen - 2005-04-17 + */ + public static final FTPClientConfig NUMERIC_DATE_CONFIG = + new FTPClientConfig( + FTPClientConfig.SYST_UNIX, + NUMERIC_DATE_FORMAT, + null, null, null, null); + + /** + * this is the regular expression used by this parser. + * + * Permissions: + * r the file is readable + * w the file is writable + * x the file is executable + * - the indicated permission is not granted + * L mandatory locking occurs during access (the set-group-ID bit is + * on and the group execution bit is off) + * s the set-user-ID or set-group-ID bit is on, and the corresponding + * user or group execution bit is also on + * S undefined bit-state (the set-user-ID bit is on and the user + * execution bit is off) + * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and + * execution is on + * T the 1000 bit is turned on, and execution is off (undefined bit- + * state) + * e z/OS external link bit + */ + private static final String REGEX = + "([bcdelfmpSs-])" + +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" + + "(\\d+)\\s+" + + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces) + + "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces) + + "(\\d+(?:,\\s*\\d+)?)\\s+" + + /* + numeric or standard format date + */ + + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+" + + /* + year (for non-recent standard format) + or time (for numeric or recent standard format + */ + + "(\\d+(?::\\d+)?)\\s+" + + + "(\\S*)(\\s*.*)"; + + + /** + * The default constructor for a UnixFTPEntryParser object. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public UnixFTPEntryParser() + { + this(null); + } + + /** + * This constructor allows the creation of a UnixFTPEntryParser object with + * something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public UnixFTPEntryParser(FTPClientConfig config) + { + super(REGEX); + configure(config); + } + + + /** + * Parses a line of a unix (standard) FTP server file listing and converts + * it into a usable format in the form of an FTPFile + * instance. If the file listing line doesn't describe a file, + * null is returned, otherwise a FTPFile + * instance representing the files in the directory is returned. + *

+ * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) { + FTPFile file = new FTPFile(); + file.setRawListing(entry); + int type; + boolean isDevice = false; + + if (matches(entry)) + { + String typeStr = group(1); + String hardLinkCount = group(15); + String usr = group(16); + String grp = group(17); + String filesize = group(18); + String datestr = group(19) + " " + group(20); + String name = group(21); + String endtoken = group(22); + + try + { + file.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + + // bcdlfmpSs- + switch (typeStr.charAt(0)) + { + case 'd': + type = FTPFile.DIRECTORY_TYPE; + break; + case 'e': + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'l': + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'b': + case 'c': + isDevice = true; + // break; - fall through + case 'f': + case '-': + type = FTPFile.FILE_TYPE; + break; + default: + type = FTPFile.UNKNOWN_TYPE; + } + + file.setType(type); + + int g = 4; + for (int access = 0; access < 3; access++, g += 4) + { + // Use != '-' to avoid having to check for suid and sticky bits + file.setPermission(access, FTPFile.READ_PERMISSION, + (!group(g).equals("-"))); + file.setPermission(access, FTPFile.WRITE_PERMISSION, + (!group(g + 1).equals("-"))); + + String execPerm = group(g + 2); + if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))) + { + file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true); + } + else + { + file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false); + } + } + + if (!isDevice) + { + try + { + file.setHardLinkCount(Integer.parseInt(hardLinkCount)); + } + catch (NumberFormatException e) + { + // intentionally do nothing + } + } + + file.setUser(usr); + file.setGroup(grp); + + try + { + file.setSize(Long.parseLong(filesize)); + } + catch (NumberFormatException e) + { + // intentionally do nothing + } + + if (null == endtoken) + { + file.setName(name); + } + else + { + // oddball cases like symbolic links, file names + // with spaces in them. + name += endtoken; + if (type == FTPFile.SYMBOLIC_LINK_TYPE) + { + + int end = name.indexOf(" -> "); + // Give up if no link indicator is present + if (end == -1) + { + file.setName(name); + } + else + { + file.setName(name.substring(0, end)); + file.setLink(name.substring(end + 4)); + } + + } + else + { + file.setName(name); + } + } + return file; + } + return null; + } + + /** + * Defines a default configuration to be used when this class is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig( + FTPClientConfig.SYST_UNIX, + DEFAULT_DATE_FORMAT, + DEFAULT_RECENT_DATE_FORMAT, + null, null, null); + } + +} diff --git a/src/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java new file mode 100644 index 0000000..1e55ede --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java @@ -0,0 +1,288 @@ +/* + * 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.parser; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.StringTokenizer; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPListParseEngine; + +/** + * Implementation FTPFileEntryParser and FTPFileListParser for VMS Systems. + * This is a sample of VMS LIST output + * + * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + * "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + *

+ * Note: VMSFTPEntryParser can only be instantiated through the + * DefaultFTPParserFactory by classname. It will not be chosen + * by the autodetection scheme. + * + *

+ * + * @author Winston Ojeda + * @author Steve Cohen + * @author Stephane ESTE-GRACIAS + * @version $Id: VMSFTPEntryParser.java 658518 2008-05-21 01:04:30Z sebb $ + * + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + */ +public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl +{ + + private static final String DEFAULT_DATE_FORMAT + = "d-MMM-yyyy HH:mm:ss"; //9-NOV-2001 12:30:24 + + /** + * this is the regular expression used by this parser. + */ + private static final String REGEX = + "(.*;[0-9]+)\\s*" //1 file and version + + "(\\d+)/\\d+\\s*" //2 size/allocated + +"(\\S+)\\s+(\\S+)\\s+" //3+4 date and time + + "\\[(([0-9$A-Za-z_]+)|([0-9$A-Za-z_]+),([0-9$a-zA-Z_]+))\\]?\\s*" //5(6,7,8) owner + + "\\([a-zA-Z]*,([a-zA-Z]*),([a-zA-Z]*),([a-zA-Z]*)\\)"; //9,10,11 Permissions (O,G,W) + // TODO - perhaps restrict permissions to [RWED]* ? + + + + /** + * Constructor for a VMSFTPEntryParser object. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public VMSFTPEntryParser() + { + this(null); + } + + /** + * This constructor allows the creation of a VMSFTPEntryParser object with + * something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public VMSFTPEntryParser(FTPClientConfig config) + { + super(REGEX); + configure(config); + } + + + + /*** + * Parses an FTP server file listing and converts it into a usable format + * in the form of an array of FTPFile instances. If the + * file list contains no files, null should be + * returned, otherwise an array of FTPFile instances + * representing the files in the directory is returned. + *

+ * @param listStream The InputStream from which the file list should be + * read. + * @return The list of file information contained in the given path. null + * if the list could not be obtained or if there are no files in + * the directory. + * @exception IOException If an I/O error occurs reading the listStream. + ***/ + public FTPFile[] parseFileList(InputStream listStream) throws IOException { + FTPListParseEngine engine = new FTPListParseEngine(this); + engine.readServerList(listStream); + return engine.getFiles(); + } + + + + /** + * Parses a line of a VMS FTP server file listing and converts it into a + * usable format in the form of an FTPFile instance. If the + * file listing line doesn't describe a file, null is + * returned, otherwise a FTPFile instance representing the + * files in the directory is returned. + *

+ * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry + */ + public FTPFile parseFTPEntry(String entry) + { + //one block in VMS equals 512 bytes + long longBlock = 512; + + if (matches(entry)) + { + FTPFile f = new FTPFile(); + f.setRawListing(entry); + String name = group(1); + String size = group(2); + String datestr = group(3)+" "+group(4); + String owner = group(5); + String permissions[] = new String[3]; + permissions[0]= group(9); + permissions[1]= group(10); + permissions[2]= group(11); + try + { + f.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + + String grp; + String user; + StringTokenizer t = new StringTokenizer(owner, ","); + switch (t.countTokens()) { + case 1: + grp = null; + user = t.nextToken(); + break; + case 2: + grp = t.nextToken(); + user = t.nextToken(); + break; + default: + grp = null; + user = null; + } + + if (name.lastIndexOf(".DIR") != -1) + { + f.setType(FTPFile.DIRECTORY_TYPE); + } + else + { + f.setType(FTPFile.FILE_TYPE); + } + //set FTPFile name + //Check also for versions to be returned or not + if (isVersioning()) + { + f.setName(name); + } + else + { + name = name.substring(0, name.lastIndexOf(";")); + f.setName(name); + } + //size is retreived in blocks and needs to be put in bytes + //for us humans and added to the FTPFile array + long sizeInBytes = Long.parseLong(size) * longBlock; + f.setSize(sizeInBytes); + + f.setGroup(grp); + f.setUser(user); + //set group and owner + + //Set file permission. + //VMS has (SYSTEM,OWNER,GROUP,WORLD) users that can contain + //R (read) W (write) E (execute) D (delete) + + //iterate for OWNER GROUP WORLD permissions + for (int access = 0; access < 3; access++) + { + String permission = permissions[access]; + + f.setPermission(access, FTPFile.READ_PERMISSION, permission.indexOf('R')>=0); + f.setPermission(access, FTPFile.WRITE_PERMISSION, permission.indexOf('W')>=0); + f.setPermission(access, FTPFile.EXECUTE_PERMISSION, permission.indexOf('E')>=0); + } + + return f; + } + return null; + } + + + /** + * Reads the next entry using the supplied BufferedReader object up to + * whatever delemits one entry from the next. This parser cannot use + * the default implementation of simply calling BufferedReader.readLine(), + * because one entry may span multiple lines. + * + * @param reader The BufferedReader object from which entries are to be + * read. + * + * @return A string representing the next ftp entry or null if none found. + * @exception IOException thrown on any IO Error reading from the reader. + */ + @Override + public String readNextEntry(BufferedReader reader) throws IOException + { + String line = reader.readLine(); + StringBuffer entry = new StringBuffer(); + while (line != null) + { + if (line.startsWith("Directory") || line.startsWith("Total")) { + line = reader.readLine(); + continue; + } + + entry.append(line); + if (line.trim().endsWith(")")) + { + break; + } + line = reader.readLine(); + } + return (entry.length() == 0 ? null : entry.toString()); + } + + protected boolean isVersioning() { + return false; + } + + /** + * Defines a default configuration to be used when this class is + * instantiated without a {@link FTPClientConfig FTPClientConfig} + * parameter being specified. + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig( + FTPClientConfig.SYST_VMS, + DEFAULT_DATE_FORMAT, + null, null, null, null); + } + + +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/src/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java b/src/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java new file mode 100644 index 0000000..cb25709 --- /dev/null +++ b/src/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java @@ -0,0 +1,183 @@ +/* + * 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.parser; + +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.net.ftp.FTPClientConfig; + +/** + * Special implementation VMSFTPEntryParser with versioning turned on. + * This parser removes all duplicates and only leaves the version with the highest + * version number for each filename. + * + * This is a sample of VMS LIST output + * + * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + * "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + *

+ * + * @author Winston Ojeda + * @author Stephane ESTE-GRACIAS + * @version $Id: VMSVersioningFTPEntryParser.java 636854 2008-03-13 19:55:01Z sebb $ + * + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + */ +public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser +{ + + private Matcher _preparse_matcher_; + private Pattern _preparse_pattern_; + private static final String PRE_PARSE_REGEX = + "(.*);([0-9]+)\\s*.*"; + + /** + * Constructor for a VMSFTPEntryParser object. Sets the versioning member + * to the supplied value. + * + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + */ + public VMSVersioningFTPEntryParser() + { + this(null); + } + + /** + * This constructor allows the creation of a VMSVersioningFTPEntryParser + * object with something other than the default configuration. + * + * @param config The {@link FTPClientConfig configuration} object used to + * configure this parser. + * @exception IllegalArgumentException + * Thrown if the regular expression is unparseable. Should not be seen + * under normal conditions. It it is seen, this is a sign that + * REGEX is not a valid regular expression. + * @since 1.4 + */ + public VMSVersioningFTPEntryParser(FTPClientConfig config) + { + super(); + configure(config); + try + { + //_preparse_matcher_ = new Perl5Matcher(); + _preparse_pattern_ = Pattern.compile(PRE_PARSE_REGEX); + } + catch (PatternSyntaxException pse) + { + throw new IllegalArgumentException ( + "Unparseable regex supplied: " + PRE_PARSE_REGEX); + } + + } + + + + private static class NameVersion { + String name; + int versionNumber; + NameVersion(String name, String vers) { + this.name = name; + this.versionNumber = Integer.parseInt(vers); + } + } + + /** + * Implement hook provided for those implementers (such as + * VMSVersioningFTPEntryParser, and possibly others) which return + * multiple files with the same name to remove the duplicates .. + * + * @param original Original list + * + * @return Original list purged of duplicates + */ + @Override + public List preParse(List original) { + original = super.preParse(original); + HashMap existingEntries = new HashMap(); + ListIterator iter = original.listIterator(); + while (iter.hasNext()) { + String entry = iter.next().trim(); + MatchResult result = null; + _preparse_matcher_ = _preparse_pattern_.matcher(entry); + if (_preparse_matcher_.matches()) { + result = _preparse_matcher_.toMatchResult(); + String name = result.group(1); + String version = result.group(2); + NameVersion nv = new NameVersion(name, version); + NameVersion existing = existingEntries.get(name); + if (null != existing) { + if (nv.versionNumber < existing.versionNumber) { + iter.remove(); // removal removes from original list. + continue; + } + } + existingEntries.put(name, nv); + } + + } + // we've now removed all entries less than with less than the largest + // version number for each name that were listed after the largest. + // we now must remove those with smaller than the largest version number + // for each name that were found before the largest + while (iter.hasPrevious()) { + String entry = iter.previous().trim(); + MatchResult result = null; + _preparse_matcher_ = _preparse_pattern_.matcher(entry); + if (_preparse_matcher_.matches()) { + result = _preparse_matcher_.toMatchResult(); + String name = result.group(1); + String version = result.group(2); + NameVersion nv = new NameVersion(name, version); + NameVersion existing = existingEntries.get(name); + if (null != existing) { + if (nv.versionNumber < existing.versionNumber) { + iter.remove(); // removal removes from original list. + } + } + } + + } + return original; + } + + + @Override + protected boolean isVersioning() { + return true; + } + +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/src/org/apache/commons/net/io/CopyStreamAdapter.java b/src/org/apache/commons/net/io/CopyStreamAdapter.java new file mode 100644 index 0000000..0679d23 --- /dev/null +++ b/src/org/apache/commons/net/io/CopyStreamAdapter.java @@ -0,0 +1,122 @@ +/* + * 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.io; + +import java.util.EventListener; + +import org.apache.commons.net.util.ListenerList; + +/** + * The CopyStreamAdapter will relay CopyStreamEvents to a list of listeners + * when either of its bytesTransferred() methods are called. Its purpose + * is to facilitate the notification of the progress of a copy operation + * performed by one of the static copyStream() methods in + * org.apache.commons.io.Util to multiple listeners. The static + * copyStream() methods invoke the + * bytesTransfered(long, int) of a CopyStreamListener for performance + * reasons and also because multiple listeners cannot be registered given + * that the methods are static. + *

+ *

+ * @see CopyStreamEvent + * @see CopyStreamListener + * @see Util + * @author Daniel F. Savarese + * @version $Id: CopyStreamAdapter.java 489397 2006-12-21 16:28:51Z rwinston $ + */ +public class CopyStreamAdapter implements CopyStreamListener +{ + private ListenerList internalListeners; + + /** + * Creates a new copyStreamAdapter. + */ + public CopyStreamAdapter() + { + internalListeners = new ListenerList(); + } + + /** + * This method is invoked by a CopyStreamEvent source after copying + * a block of bytes from a stream. The CopyStreamEvent will contain + * the total number of bytes transferred so far and the number of bytes + * transferred in the last write. The CopyStreamAdapater will relay + * the event to all of its registered listeners, listing itself as the + * source of the event. + * @param event The CopyStreamEvent fired by the copying of a block of + * bytes. + */ + public void bytesTransferred(CopyStreamEvent event) + { + bytesTransferred(event.getTotalBytesTransferred(), + event.getBytesTransferred(), + event.getStreamSize()); + } + + /** + * This method is not part of the JavaBeans model and is used by the + * static methods in the org.apache.commons.io.Util class for efficiency. + * It is invoked after a block of bytes to inform the listener of the + * transfer. The CopyStreamAdapater will create a CopyStreamEvent + * from the arguments and relay the event to all of its registered + * listeners, listing itself as the source of the event. + * @param totalBytesTransferred The total number of bytes transferred + * so far by the copy operation. + * @param bytesTransferred The number of bytes copied by the most recent + * write. + * @param streamSize The number of bytes in the stream being copied. + * This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if + * the size is unknown. + */ + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, long streamSize) + { + CopyStreamEvent event; + + event = new CopyStreamEvent(this, + totalBytesTransferred, + bytesTransferred, + streamSize); + + for (EventListener listener : internalListeners) + { + ((CopyStreamListener) (listener)).bytesTransferred(event); + } + } + + /** + * Registers a CopyStreamListener to receive CopyStreamEvents. + * Although this method is not declared to be synchronized, it is + * implemented in a thread safe manner. + * @param listener The CopyStreamlistener to register. + */ + public void addCopyStreamListener(CopyStreamListener listener) + { + internalListeners.addListener(listener); + } + + /** + * Unregisters a CopyStreamListener. Although this method is not + * synchronized, it is implemented in a thread safe manner. + * @param listener The CopyStreamlistener to unregister. + */ + public void removeCopyStreamListener(CopyStreamListener listener) + { + internalListeners.removeListener(listener); + } +} diff --git a/src/org/apache/commons/net/io/CopyStreamEvent.java b/src/org/apache/commons/net/io/CopyStreamEvent.java new file mode 100644 index 0000000..d7d0ec3 --- /dev/null +++ b/src/org/apache/commons/net/io/CopyStreamEvent.java @@ -0,0 +1,98 @@ +/* + * 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.io; + +import java.util.EventObject; + +/** + * A CopyStreamEvent is triggered after every write performed by a + * stream copying operation. The event stores the number of bytes + * transferred by the write triggering the event as well as the total + * number of bytes transferred so far by the copy operation. + *

+ *

+ * @see CopyStreamListener + * @see CopyStreamAdapter + * @see Util + * @author Daniel F. Savarese + * @version $Id: CopyStreamEvent.java 489397 2006-12-21 16:28:51Z rwinston $ + */ +public class CopyStreamEvent extends EventObject +{ + /** + * Constant used to indicate the stream size is unknown. + */ + public static final long UNKNOWN_STREAM_SIZE = -1; + + private int bytesTransferred; + private long totalBytesTransferred; + private long streamSize; + + /** + * Creates a new CopyStreamEvent instance. + * @param source The source of the event. + * @param totalBytesTransferred The total number of bytes transferred so + * far during a copy operation. + * @param bytesTransferred The number of bytes transferred during the + * write that triggered the CopyStreamEvent. + * @param streamSize The number of bytes in the stream being copied. + * This may be set to UNKNOWN_STREAM_SIZE if the + * size is unknown. + */ + public CopyStreamEvent(Object source, long totalBytesTransferred, + int bytesTransferred, long streamSize) + { + super(source); + this.bytesTransferred = bytesTransferred; + this.totalBytesTransferred = totalBytesTransferred; + this.streamSize = streamSize; + } + + /** + * Returns the number of bytes transferred by the write that triggered + * the event. + * @return The number of bytes transferred by the write that triggered + * the vent. + */ + public int getBytesTransferred() + { + return bytesTransferred; + } + + /** + * Returns the total number of bytes transferred so far by the copy + * operation. + * @return The total number of bytes transferred so far by the copy + * operation. + */ + public long getTotalBytesTransferred() + { + return totalBytesTransferred; + } + + /** + * Returns the size of the stream being copied. + * This may be set to UNKNOWN_STREAM_SIZE if the + * size is unknown. + * @return The size of the stream being copied. + */ + public long getStreamSize() + { + return streamSize; + } +} diff --git a/src/org/apache/commons/net/io/CopyStreamException.java b/src/org/apache/commons/net/io/CopyStreamException.java new file mode 100644 index 0000000..8d34145 --- /dev/null +++ b/src/org/apache/commons/net/io/CopyStreamException.java @@ -0,0 +1,71 @@ +/* + * 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.io; + +import java.io.IOException; + +/** + * The CopyStreamException class is thrown by the org.apache.commons.io.Util + * copyStream() methods. It stores the number of bytes confirmed to + * have been transferred before an I/O error as well as the IOException + * responsible for the failure of a copy operation. + * @see Util + * @author Daniel F. Savarese + * @version $Id: CopyStreamException.java 489397 2006-12-21 16:28:51Z rwinston $ + */ +public class CopyStreamException extends IOException +{ + private long totalBytesTransferred; + private IOException ioException; + + /** + * Creates a new CopyStreamException instance. + * @param message A message describing the error. + * @param bytesTransferred The total number of bytes transferred before + * an exception was thrown in a copy operation. + * @param exception The IOException thrown during a copy operation. + */ + public CopyStreamException(String message, + long bytesTransferred, + IOException exception) + { + super(message); + totalBytesTransferred = bytesTransferred; + ioException = exception; + } + + /** + * Returns the total number of bytes confirmed to have + * been transferred by a failed copy operation. + * @return The total number of bytes confirmed to have + * been transferred by a failed copy operation. + */ + public long getTotalBytesTransferred() + { + return totalBytesTransferred; + } + + /** + * Returns the IOException responsible for the failure of a copy operation. + * @return The IOException responsible for the failure of a copy operation. + */ + public IOException getIOException() + { + return ioException; + } +} diff --git a/src/org/apache/commons/net/io/CopyStreamListener.java b/src/org/apache/commons/net/io/CopyStreamListener.java new file mode 100644 index 0000000..9e97fb9 --- /dev/null +++ b/src/org/apache/commons/net/io/CopyStreamListener.java @@ -0,0 +1,72 @@ +/* + * 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.io; + +import java.util.EventListener; + +/** + * The CopyStreamListener class can accept CopyStreamEvents to keep track + * of the progress of a stream copying operation. However, it is currently + * not used that way within NetComponents for performance reasons. Rather + * the bytesTransferred(long, int) method is called directly rather than + * passing an event to bytesTransferred(CopyStreamEvent), saving the creation + * of a CopyStreamEvent instance. Also, the only place where + * CopyStreamListener is currently used within NetComponents is in the + * static methods of the uninstantiable org.apache.commons.io.Util class, which + * would preclude the use of addCopyStreamListener and + * removeCopyStreamListener methods. However, future additions may use the + * JavaBean event model, which is why the hooks have been included from the + * beginning. + *

+ *

+ * @see CopyStreamEvent + * @see CopyStreamAdapter + * @see Util + * @author Daniel F. Savarese + * @version $Id: CopyStreamListener.java 489397 2006-12-21 16:28:51Z rwinston $ + */ +public interface CopyStreamListener extends EventListener +{ + /** + * This method is invoked by a CopyStreamEvent source after copying + * a block of bytes from a stream. The CopyStreamEvent will contain + * the total number of bytes transferred so far and the number of bytes + * transferred in the last write. + * @param event The CopyStreamEvent fired by the copying of a block of + * bytes. + */ + public void bytesTransferred(CopyStreamEvent event); + + + /** + * This method is not part of the JavaBeans model and is used by the + * static methods in the org.apache.commons.io.Util class for efficiency. + * It is invoked after a block of bytes to inform the listener of the + * transfer. + * @param totalBytesTransferred The total number of bytes transferred + * so far by the copy operation. + * @param bytesTransferred The number of bytes copied by the most recent + * write. + * @param streamSize The number of bytes in the stream being copied. + * This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if + * the size is unknown. + */ + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, + long streamSize); +} diff --git a/src/org/apache/commons/net/io/DotTerminatedMessageReader.java b/src/org/apache/commons/net/io/DotTerminatedMessageReader.java new file mode 100644 index 0000000..c0f344d --- /dev/null +++ b/src/org/apache/commons/net/io/DotTerminatedMessageReader.java @@ -0,0 +1,280 @@ +/* + * 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.io; + +import java.io.IOException; +import java.io.PushbackReader; +import java.io.Reader; + +/** + * DotTerminatedMessageReader is a class used to read messages from a + * server that are terminated by a single dot followed by a + * <CR><LF> + * sequence and with double dots appearing at the begining of lines which + * do not signal end of message yet start with a dot. Various Internet + * protocols such as NNTP and POP3 produce messages of this type. + *

+ * This class handles stripping of the duplicate period at the beginning + * of lines starting with a period, converts NETASCII newlines to the + * local line separator format, truncates the end of message indicator, + * and ensures you cannot read past the end of the message. + * @author Daniel F. Savarese + * @version $Id: DotTerminatedMessageReader.java 636825 2008-03-13 18:34:52Z sebb $ + */ +public final class DotTerminatedMessageReader extends Reader +{ + private static final String LS; + private static final char[] LS_CHARS; + + static + { + LS = System.getProperty("line.separator"); + LS_CHARS = LS.toCharArray(); + } + + private boolean atBeginning; + private boolean eof; + private int pos; + private char[] internalBuffer; + private PushbackReader internalReader; + + /** + * Creates a DotTerminatedMessageReader that wraps an existing Reader + * input source. + * @param reader The Reader input source containing the message. + */ + public DotTerminatedMessageReader(Reader reader) + { + super(reader); + internalBuffer = new char[LS_CHARS.length + 3]; + pos = internalBuffer.length; + // Assumes input is at start of message + atBeginning = true; + eof = false; + internalReader = new PushbackReader(reader); + } + + /** + * Reads and returns the next character in the message. If the end of the + * message has been reached, returns -1. Note that a call to this method + * may result in multiple reads from the underlying input stream to decode + * the message properly (removing doubled dots and so on). All of + * this is transparent to the programmer and is only mentioned for + * completeness. + * @return The next character in the message. Returns -1 if the end of the + * message has been reached. + * @exception IOException If an error occurs while reading the underlying + * stream. + */ + @Override + public int read() throws IOException + { + int ch; + + synchronized (lock) + { + if (pos < internalBuffer.length) + { + return internalBuffer[pos++]; + } + + if (eof) + { + return -1; + } + + if ((ch = internalReader.read()) == -1) + { + eof = true; + return -1; + } + + if (atBeginning) + { + atBeginning = false; + if (ch == '.') + { + ch = internalReader.read(); + + if (ch != '.') + { + // read newline + eof = true; + internalReader.read(); + return -1; + } + else + { + return '.'; + } + } + } + + if (ch == '\r') + { + ch = internalReader.read(); + + if (ch == '\n') + { + ch = internalReader.read(); + + if (ch == '.') + { + ch = internalReader.read(); + + if (ch != '.') + { + // read newline and indicate end of file + internalReader.read(); + eof = true; + } + else + { + internalBuffer[--pos] = (char) ch; + } + } + else + { + internalReader.unread(ch); + } + + pos -= LS_CHARS.length; + System.arraycopy(LS_CHARS, 0, internalBuffer, pos, + LS_CHARS.length); + ch = internalBuffer[pos++]; + } + else + { + internalBuffer[--pos] = (char) ch; + return '\r'; + } + } + + return ch; + } + } + + /** + * Reads the next characters from the message into an array and + * returns the number of characters read. Returns -1 if the end of the + * message has been reached. + * @param buffer The character array in which to store the characters. + * @return The number of characters read. Returns -1 if the + * end of the message has been reached. + * @exception IOException If an error occurs in reading the underlying + * stream. + */ + @Override + public int read(char[] buffer) throws IOException + { + return read(buffer, 0, buffer.length); + } + + /** + * Reads the next characters from the message into an array and + * returns the number of characters read. Returns -1 if the end of the + * message has been reached. The characters are stored in the array + * starting from the given offset and up to the length specified. + * @param buffer The character array in which to store the characters. + * @param offset The offset into the array at which to start storing + * characters. + * @param length The number of characters to read. + * @return The number of characters read. Returns -1 if the + * end of the message has been reached. + * @exception IOException If an error occurs in reading the underlying + * stream. + */ + @Override + public int read(char[] buffer, int offset, int length) throws IOException + { + int ch, off; + synchronized (lock) + { + if (length < 1) + { + return 0; + } + if ((ch = read()) == -1) + { + return -1; + } + off = offset; + + do + { + buffer[offset++] = (char) ch; + } + while (--length > 0 && (ch = read()) != -1); + + return (offset - off); + } + } + + /** + * Determines if the message is ready to be read. + * @return True if the message is ready to be read, false if not. + * @exception IOException If an error occurs while checking the underlying + * stream. + */ + @Override + public boolean ready() throws IOException + { + synchronized (lock) + { + return (pos < internalBuffer.length || internalReader.ready()); + } + } + + /** + * Closes the message for reading. This doesn't actually close the + * underlying stream. The underlying stream may still be used for + * communicating with the server and therefore is not closed. + *

+ * If the end of the message has not yet been reached, this method + * will read the remainder of the message until it reaches the end, + * so that the underlying stream may continue to be used properly + * for communicating with the server. If you do not fully read + * a message, you MUST close it, otherwise your program will likely + * hang or behave improperly. + * @exception IOException If an error occurs while reading the + * underlying stream. + */ + @Override + public void close() throws IOException + { + synchronized (lock) + { + if (internalReader == null) + { + return; + } + + if (!eof) + { + while (read() != -1) + { + ; + } + } + eof = true; + atBeginning = false; + pos = internalBuffer.length; + internalReader = null; + } + } +} diff --git a/src/org/apache/commons/net/io/DotTerminatedMessageWriter.java b/src/org/apache/commons/net/io/DotTerminatedMessageWriter.java new file mode 100644 index 0000000..853e42f --- /dev/null +++ b/src/org/apache/commons/net/io/DotTerminatedMessageWriter.java @@ -0,0 +1,215 @@ +/* + * 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.io; + +import java.io.IOException; +import java.io.Writer; + +/*** + * DotTerminatedMessageWriter is a class used to write messages to a + * server that are terminated by a single dot followed by a + * <CR><LF> + * sequence and with double dots appearing at the begining of lines which + * do not signal end of message yet start with a dot. Various Internet + * protocols such as NNTP and POP3 produce messages of this type. + *

+ * This class handles the doubling of line-starting periods, + * converts single linefeeds to NETASCII newlines, and on closing + * will send the final message terminator dot and NETASCII newline + * sequence. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class DotTerminatedMessageWriter extends Writer +{ + private static final int __NOTHING_SPECIAL_STATE = 0; + private static final int __LAST_WAS_CR_STATE = 1; + private static final int __LAST_WAS_NL_STATE = 2; + + private int __state; + private Writer __output; + + + /*** + * Creates a DotTerminatedMessageWriter that wraps an existing Writer + * output destination. + *

+ * @param output The Writer output destination to write the message. + ***/ + public DotTerminatedMessageWriter(Writer output) + { + super(output); + __output = output; + __state = __NOTHING_SPECIAL_STATE; + } + + + /*** + * Writes a character to the output. Note that a call to this method + * may result in multiple writes to the underling Writer in order to + * convert naked linefeeds to NETASCII line separators and to double + * line-leading periods. This is transparent to the programmer and + * is only mentioned for completeness. + *

+ * @param ch The character to write. + * @exception IOException If an error occurs while writing to the + * underlying output. + ***/ + @Override + public void write(int ch) throws IOException + { + synchronized (lock) + { + switch (ch) + { + case '\r': + __state = __LAST_WAS_CR_STATE; + __output.write('\r'); + return ; + case '\n': + if (__state != __LAST_WAS_CR_STATE) + __output.write('\r'); + __output.write('\n'); + __state = __LAST_WAS_NL_STATE; + return ; + case '.': + // Double the dot at the beginning of a line + if (__state == __LAST_WAS_NL_STATE) + __output.write('.'); + // Fall through + default: + __state = __NOTHING_SPECIAL_STATE; + __output.write(ch); + return ; + } + } + } + + + /*** + * Writes a number of characters from a character array to the output + * starting from a given offset. + *

+ * @param buffer The character array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of characters to write. + * @exception IOException If an error occurs while writing to the underlying + * output. + ***/ + @Override + public void write(char[] buffer, int offset, int length) throws IOException + { + synchronized (lock) + { + while (length-- > 0) + write(buffer[offset++]); + } + } + + + /*** + * Writes a character array to the output. + *

+ * @param buffer The character array to write. + * @exception IOException If an error occurs while writing to the underlying + * output. + ***/ + @Override + public void write(char[] buffer) throws IOException + { + write(buffer, 0, buffer.length); + } + + + /*** + * Writes a String to the output. + *

+ * @param string The String to write. + * @exception IOException If an error occurs while writing to the underlying + * output. + ***/ + @Override + public void write(String string) throws IOException + { + write(string.toCharArray()); + } + + + /*** + * Writes part of a String to the output starting from a given offset. + *

+ * @param string The String to write. + * @param offset The offset into the String at which to start copying data. + * @param length The number of characters to write. + * @exception IOException If an error occurs while writing to the underlying + * output. + ***/ + @Override + public void write(String string, int offset, int length) throws IOException + { + write(string.toCharArray(), offset, length); + } + + + /*** + * Flushes the underlying output, writing all buffered output. + *

+ * @exception IOException If an error occurs while writing to the underlying + * output. + ***/ + @Override + public void flush() throws IOException + { + synchronized (lock) + { + __output.flush(); + } + } + + + /*** + * Flushes the underlying output, writing all buffered output, but doesn't + * actually close the underlying stream. The underlying stream may still + * be used for communicating with the server and therefore is not closed. + *

+ * @exception IOException If an error occurs while writing to the underlying + * output or closing the Writer. + ***/ + @Override + public void close() throws IOException + { + synchronized (lock) + { + if (__output == null) + return ; + + if (__state == __LAST_WAS_CR_STATE) + __output.write('\n'); + else if (__state != __LAST_WAS_NL_STATE) + __output.write("\r\n"); + + __output.write(".\r\n"); + + __output.flush(); + __output = null; + } + } + +} diff --git a/src/org/apache/commons/net/io/FromNetASCIIInputStream.java b/src/org/apache/commons/net/io/FromNetASCIIInputStream.java new file mode 100644 index 0000000..76588ab --- /dev/null +++ b/src/org/apache/commons/net/io/FromNetASCIIInputStream.java @@ -0,0 +1,205 @@ +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +/*** + * This class wraps an input stream, replacing all occurrences + * of <CR><LF> (carriage return followed by a linefeed), + * which is the NETASCII standard for representing a newline, with the + * local line separator representation. You would use this class to + * implement ASCII file transfers requiring conversion from NETASCII. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class FromNetASCIIInputStream extends PushbackInputStream +{ + static final boolean _noConversionRequired; + static final String _lineSeparator; + static final byte[] _lineSeparatorBytes; + + static { + _lineSeparator = System.getProperty("line.separator"); + _noConversionRequired = _lineSeparator.equals("\r\n"); + _lineSeparatorBytes = _lineSeparator.getBytes(); + } + + private int __length = 0; + + /*** + * Returns true if the NetASCII line separator differs from the system + * line separator, false if they are the same. This method is useful + * to determine whether or not you need to instantiate a + * FromNetASCIIInputStream object. + *

+ * @return True if the NETASCII line separator differs from the local + * system line separator, false if they are the same. + ***/ + public static final boolean isConversionRequired() + { + return !_noConversionRequired; + } + + /*** + * Creates a FromNetASCIIInputStream instance that wraps an existing + * InputStream. + ***/ + public FromNetASCIIInputStream(InputStream input) + { + super(input, _lineSeparatorBytes.length + 1); + } + + + private int __read() throws IOException + { + int ch; + + ch = super.read(); + + if (ch == '\r') + { + ch = super.read(); + if (ch == '\n') + { + unread(_lineSeparatorBytes); + ch = super.read(); + // This is a kluge for read(byte[], ...) to read the right amount + --__length; + } + else + { + if (ch != -1) + unread(ch); + return '\r'; + } + } + + return ch; + } + + + /*** + * Reads and returns the next byte in the stream. If the end of the + * message has been reached, returns -1. Note that a call to this method + * may result in multiple reads from the underlying input stream in order + * to convert NETASCII line separators to the local line separator format. + * This is transparent to the programmer and is only mentioned for + * completeness. + *

+ * @return The next character in the stream. Returns -1 if the end of the + * stream has been reached. + * @exception IOException If an error occurs while reading the underlying + * stream. + ***/ + @Override + public int read() throws IOException + { + if (_noConversionRequired) + return super.read(); + + return __read(); + } + + + /*** + * Reads the next number of bytes from the stream into an array and + * returns the number of bytes read. Returns -1 if the end of the + * stream has been reached. + *

+ * @param buffer The byte array in which to store the data. + * @return The number of bytes read. Returns -1 if the + * end of the message has been reached. + * @exception IOException If an error occurs in reading the underlying + * stream. + ***/ + @Override + public int read(byte buffer[]) throws IOException + { + return read(buffer, 0, buffer.length); + } + + + /*** + * Reads the next number of bytes from the stream into an array and returns + * the number of bytes read. Returns -1 if the end of the + * message has been reached. The characters are stored in the array + * starting from the given offset and up to the length specified. + *

+ * @param buffer The byte array in which to store the data. + * @param offset The offset into the array at which to start storing data. + * @param length The number of bytes to read. + * @return The number of bytes read. Returns -1 if the + * end of the stream has been reached. + * @exception IOException If an error occurs while reading the underlying + * stream. + ***/ + @Override + public int read(byte buffer[], int offset, int length) throws IOException + { + int ch, off; + + if (length < 1) + return 0; + + ch = available(); + + __length = (length > ch ? ch : length); + + // If nothing is available, block to read only one character + if (__length < 1) + __length = 1; + + if (_noConversionRequired) + return super.read(buffer, offset, __length); + + if ((ch = __read()) == -1) + return -1; + + off = offset; + + do + { + buffer[offset++] = (byte)ch; + } + while (--__length > 0 && (ch = __read()) != -1); + + + return (offset - off); + } + + + // PushbackInputStream in JDK 1.1.3 returns the wrong thing + /*** + * Returns the number of bytes that can be read without blocking EXCEPT + * when newline conversions have to be made somewhere within the + * available block of bytes. In other words, you really should not + * rely on the value returned by this method if you are trying to avoid + * blocking. + ***/ + @Override + public int available() throws IOException + { + return (buf.length - pos) + in.available(); + } + +} diff --git a/src/org/apache/commons/net/io/FromNetASCIIOutputStream.java b/src/org/apache/commons/net/io/FromNetASCIIOutputStream.java new file mode 100644 index 0000000..c025a1b --- /dev/null +++ b/src/org/apache/commons/net/io/FromNetASCIIOutputStream.java @@ -0,0 +1,174 @@ +/* + * 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.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/*** + * This class wraps an output stream, replacing all occurrences + * of <CR><LF> (carriage return followed by a linefeed), + * which is the NETASCII standard for representing a newline, with the + * local line separator representation. You would use this class to + * implement ASCII file transfers requiring conversion from NETASCII. + *

+ * Because of the translation process, a call to flush() will + * not flush the last byte written if that byte was a carriage + * return. A call to {@link #close close() }, however, will + * flush the carriage return. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class FromNetASCIIOutputStream extends FilterOutputStream +{ + private boolean __lastWasCR; + + /*** + * Creates a FromNetASCIIOutputStream instance that wraps an existing + * OutputStream. + *

+ * @param output The OutputStream to wrap. + ***/ + public FromNetASCIIOutputStream(OutputStream output) + { + super(output); + __lastWasCR = false; + } + + + private void __write(int ch) throws IOException + { + switch (ch) + { + case '\r': + __lastWasCR = true; + // Don't write anything. We need to see if next one is linefeed + break; + case '\n': + if (__lastWasCR) + { + out.write(FromNetASCIIInputStream._lineSeparatorBytes); + __lastWasCR = false; + break; + } + __lastWasCR = false; + out.write('\n'); + break; + default: + if (__lastWasCR) + { + out.write('\r'); + __lastWasCR = false; + } + out.write(ch); + break; + } + } + + + /*** + * Writes a byte to the stream. Note that a call to this method + * might not actually write a byte to the underlying stream until a + * subsequent character is written, from which it can be determined if + * a NETASCII line separator was encountered. + * This is transparent to the programmer and is only mentioned for + * completeness. + *

+ * @param ch The byte to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public synchronized void write(int ch) + throws IOException + { + if (FromNetASCIIInputStream._noConversionRequired) + { + out.write(ch); + return ; + } + + __write(ch); + } + + + /*** + * Writes a byte array to the stream. + *

+ * @param buffer The byte array to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public synchronized void write(byte buffer[]) + throws IOException + { + write(buffer, 0, buffer.length); + } + + + /*** + * Writes a number of bytes from a byte array to the stream starting from + * a given offset. + *

+ * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public synchronized void write(byte buffer[], int offset, int length) + throws IOException + { + if (FromNetASCIIInputStream._noConversionRequired) + { + // FilterOutputStream method is very slow. + //super.write(buffer, offset, length); + out.write(buffer, offset, length); + return ; + } + + while (length-- > 0) + __write(buffer[offset++]); + } + + + /*** + * Closes the stream, writing all pending data. + *

+ * @exception IOException If an error occurs while closing the stream. + ***/ + @Override + public synchronized void close() + throws IOException + { + if (FromNetASCIIInputStream._noConversionRequired) + { + super.close(); + return ; + } + + if (__lastWasCR) + out.write('\r'); + super.close(); + } +} diff --git a/src/org/apache/commons/net/io/SocketInputStream.java b/src/org/apache/commons/net/io/SocketInputStream.java new file mode 100644 index 0000000..673f434 --- /dev/null +++ b/src/org/apache/commons/net/io/SocketInputStream.java @@ -0,0 +1,69 @@ +/* + * 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.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; + +/*** + * This class wraps an input stream, storing a reference to its originating + * socket. When the stream is closed, it will also close the socket + * immediately afterward. This class is useful for situations where you + * are dealing with a stream originating from a socket, but do not have + * a reference to the socket, and want to make sure it closes when the + * stream closes. + *

+ *

+ * @author Daniel F. Savarese + * @see SocketOutputStream + ***/ + +public class SocketInputStream extends FilterInputStream +{ + private Socket __socket; + + /*** + * Creates a SocketInputStream instance wrapping an input stream and + * storing a reference to a socket that should be closed on closing + * the stream. + *

+ * @param socket The socket to close on closing the stream. + * @param stream The input stream to wrap. + ***/ + public SocketInputStream(Socket socket, InputStream stream) + { + super(stream); + __socket = socket; + } + + /*** + * Closes the stream and immediately afterward closes the referenced + * socket. + *

+ * @exception IOException If there is an error in closing the stream + * or socket. + ***/ + @Override + public void close() throws IOException + { + super.close(); + __socket.close(); + } +} diff --git a/src/org/apache/commons/net/io/SocketOutputStream.java b/src/org/apache/commons/net/io/SocketOutputStream.java new file mode 100644 index 0000000..abd7f5d --- /dev/null +++ b/src/org/apache/commons/net/io/SocketOutputStream.java @@ -0,0 +1,89 @@ +/* + * 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.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; + +/*** + * This class wraps an output stream, storing a reference to its originating + * socket. When the stream is closed, it will also close the socket + * immediately afterward. This class is useful for situations where you + * are dealing with a stream originating from a socket, but do not have + * a reference to the socket, and want to make sure it closes when the + * stream closes. + *

+ *

+ * @author Daniel F. Savarese + * @see SocketInputStream + ***/ + +public class SocketOutputStream extends FilterOutputStream +{ + private Socket __socket; + + /*** + * Creates a SocketOutputStream instance wrapping an output stream and + * storing a reference to a socket that should be closed on closing + * the stream. + *

+ * @param socket The socket to close on closing the stream. + * @param stream The input stream to wrap. + ***/ + public SocketOutputStream(Socket socket, OutputStream stream) + { + super(stream); + __socket = socket; + } + + + /*** + * Writes a number of bytes from a byte array to the stream starting from + * a given offset. This method bypasses the equivalent method in + * FilterOutputStream because the FilterOutputStream implementation is + * very inefficient. + *

+ * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public void write(byte buffer[], int offset, int length) throws IOException + { + out.write(buffer, offset, length); + } + + + /*** + * Closes the stream and immediately afterward closes the referenced + * socket. + *

+ * @exception IOException If there is an error in closing the stream + * or socket. + ***/ + @Override + public void close() throws IOException + { + super.close(); + __socket.close(); + } +} diff --git a/src/org/apache/commons/net/io/ToNetASCIIInputStream.java b/src/org/apache/commons/net/io/ToNetASCIIInputStream.java new file mode 100644 index 0000000..55e4735 --- /dev/null +++ b/src/org/apache/commons/net/io/ToNetASCIIInputStream.java @@ -0,0 +1,181 @@ +/* + * 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.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/*** + * This class wraps an input stream, replacing all singly occurring + * <LF> (linefeed) characters with <CR><LF> (carriage return + * followed by linefeed), which is the NETASCII standard for representing + * a newline. + * You would use this class to implement ASCII file transfers requiring + * conversion to NETASCII. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class ToNetASCIIInputStream extends FilterInputStream +{ + private static final int __NOTHING_SPECIAL = 0; + private static final int __LAST_WAS_CR = 1; + private static final int __LAST_WAS_NL = 2; + private int __status; + + /*** + * Creates a ToNetASCIIInputStream instance that wraps an existing + * InputStream. + *

+ * @param input The InputStream to . + ***/ + public ToNetASCIIInputStream(InputStream input) + { + super(input); + __status = __NOTHING_SPECIAL; + } + + + /*** + * Reads and returns the next byte in the stream. If the end of the + * message has been reached, returns -1. + *

+ * @return The next character in the stream. Returns -1 if the end of the + * stream has been reached. + * @exception IOException If an error occurs while reading the underlying + * stream. + ***/ + @Override + public int read() throws IOException + { + int ch; + + if (__status == __LAST_WAS_NL) + { + __status = __NOTHING_SPECIAL; + return '\n'; + } + + ch = in.read(); + + switch (ch) + { + case '\r': + __status = __LAST_WAS_CR; + return '\r'; + case '\n': + if (__status != __LAST_WAS_CR) + { + __status = __LAST_WAS_NL; + return '\r'; + } + // else fall through + default: + __status = __NOTHING_SPECIAL; + return ch; + } + // statement not reached + //return ch; + } + + + /*** + * Reads the next number of bytes from the stream into an array and + * returns the number of bytes read. Returns -1 if the end of the + * stream has been reached. + *

+ * @param buffer The byte array in which to store the data. + * @return The number of bytes read. Returns -1 if the + * end of the message has been reached. + * @exception IOException If an error occurs in reading the underlying + * stream. + ***/ + @Override + public int read(byte buffer[]) throws IOException + { + return read(buffer, 0, buffer.length); + } + + + /*** + * Reads the next number of bytes from the stream into an array and returns + * the number of bytes read. Returns -1 if the end of the + * message has been reached. The characters are stored in the array + * starting from the given offset and up to the length specified. + *

+ * @param buffer The byte array in which to store the data. + * @param offset The offset into the array at which to start storing data. + * @param length The number of bytes to read. + * @return The number of bytes read. Returns -1 if the + * end of the stream has been reached. + * @exception IOException If an error occurs while reading the underlying + * stream. + ***/ + @Override + public int read(byte buffer[], int offset, int length) throws IOException + { + int ch, off; + + if (length < 1) + return 0; + + ch = available(); + + if (length > ch) + length = ch; + + // If nothing is available, block to read only one character + if (length < 1) + length = 1; + + if ((ch = read()) == -1) + return -1; + + off = offset; + + do + { + buffer[offset++] = (byte)ch; + } + while (--length > 0 && (ch = read()) != -1); + + return (offset - off); + } + + /*** Returns false. Mark is not supported. ***/ + @Override + public boolean markSupported() + { + return false; + } + + @Override + public int available() throws IOException + { + int result; + + result = in.available(); + + if (__status == __LAST_WAS_NL) + return (result + 1); + + return result; + } +} diff --git a/src/org/apache/commons/net/io/ToNetASCIIOutputStream.java b/src/org/apache/commons/net/io/ToNetASCIIOutputStream.java new file mode 100644 index 0000000..aeacc98 --- /dev/null +++ b/src/org/apache/commons/net/io/ToNetASCIIOutputStream.java @@ -0,0 +1,119 @@ +/* + * 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.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/*** + * This class wraps an output stream, replacing all singly occurring + * <LF> (linefeed) characters with <CR><LF> (carriage return + * followed by linefeed), which is the NETASCII standard for representing + * a newline. + * You would use this class to implement ASCII file transfers requiring + * conversion to NETASCII. + *

+ *

+ * @author Daniel F. Savarese + ***/ + +public final class ToNetASCIIOutputStream extends FilterOutputStream +{ + private boolean __lastWasCR; + + /*** + * Creates a ToNetASCIIOutputStream instance that wraps an existing + * OutputStream. + *

+ * @param output The OutputStream to wrap. + ***/ + public ToNetASCIIOutputStream(OutputStream output) + { + super(output); + __lastWasCR = false; + } + + + /*** + * Writes a byte to the stream. Note that a call to this method + * may result in multiple writes to the underlying input stream in order + * to convert naked newlines to NETASCII line separators. + * This is transparent to the programmer and is only mentioned for + * completeness. + *

+ * @param ch The byte to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public synchronized void write(int ch) + throws IOException + { + switch (ch) + { + case '\r': + __lastWasCR = true; + out.write('\r'); + return ; + case '\n': + if (!__lastWasCR) + out.write('\r'); + // Fall through + default: + __lastWasCR = false; + out.write(ch); + return ; + } + } + + + /*** + * Writes a byte array to the stream. + *

+ * @param buffer The byte array to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public synchronized void write(byte buffer[]) + throws IOException + { + write(buffer, 0, buffer.length); + } + + + /*** + * Writes a number of bytes from a byte array to the stream starting from + * a given offset. + *

+ * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @exception IOException If an error occurs while writing to the underlying + * stream. + ***/ + @Override + public synchronized void write(byte buffer[], int offset, int length) + throws IOException + { + while (length-- > 0) + write(buffer[offset++]); + } + +} diff --git a/src/org/apache/commons/net/io/Util.java b/src/org/apache/commons/net/io/Util.java new file mode 100644 index 0000000..4e85a93 --- /dev/null +++ b/src/org/apache/commons/net/io/Util.java @@ -0,0 +1,334 @@ +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; + +/*** + * The Util class cannot be instantiated and stores short static convenience + * methods that are often quite useful. + *

+ *

+ * @see CopyStreamException + * @see CopyStreamListener + * @see CopyStreamAdapter + * @author Daniel F. Savarese + ***/ + +public final class Util +{ + /*** + * The default buffer size used by {@link #copyStream copyStream } + * and {@link #copyReader copyReader }. It's value is 1024. + ***/ + public static final int DEFAULT_COPY_BUFFER_SIZE = 1024; + + // Cannot be instantiated + private Util() + { } + + + /*** + * Copies the contents of an InputStream to an OutputStream using a + * copy buffer of a given size and notifies the provided + * CopyStreamListener of the progress of the copy operation by calling + * its bytesTransferred(long, int) method after each write to the + * destination. If you wish to notify more than one listener you should + * use a CopyStreamAdapter as the listener and register the additional + * listeners with the CopyStreamAdapter. + *

+ * The contents of the InputStream are + * read until the end of the stream is reached, but neither the + * source nor the destination are closed. You must do this yourself + * outside of the method call. The number of bytes read/written is + * returned. + *

+ * @param source The source InputStream. + * @param dest The destination OutputStream. + * @param bufferSize The number of bytes to buffer during the copy. + * @param streamSize The number of bytes in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * @param listener The CopyStreamListener to notify of progress. If + * this parameter is null, notification is not attempted. + * @param flush Whether to flush the output stream after every + * write. This is necessary for interactive sessions that rely on + * buffered streams. If you don't flush, the data will stay in + * the stream buffer. + * @exception CopyStreamException If an error occurs while reading from the + * source or writing to the destination. The CopyStreamException + * will contain the number of bytes confirmed to have been + * transferred before an + * IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with + * the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + ***/ + public static final long copyStream(InputStream source, OutputStream dest, + int bufferSize, long streamSize, + CopyStreamListener listener, + boolean flush) + throws CopyStreamException + { + int bytes; + long total; + byte[] buffer; + + buffer = new byte[bufferSize]; + total = 0; + + try + { + while ((bytes = source.read(buffer)) != -1) + { + // Technically, some read(byte[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + + if (bytes == 0) + { + bytes = source.read(); + if (bytes < 0) + break; + dest.write(bytes); + if(flush) + dest.flush(); + ++total; + if (listener != null) + listener.bytesTransferred(total, 1, streamSize); + continue; + } + + dest.write(buffer, 0, bytes); + if(flush) + dest.flush(); + total += bytes; + if (listener != null) + listener.bytesTransferred(total, bytes, streamSize); + } + } + catch (IOException e) + { + throw new CopyStreamException("IOException caught while copying.", + total, e); + } + + return total; + } + + + /*** + * Copies the contents of an InputStream to an OutputStream using a + * copy buffer of a given size and notifies the provided + * CopyStreamListener of the progress of the copy operation by calling + * its bytesTransferred(long, int) method after each write to the + * destination. If you wish to notify more than one listener you should + * use a CopyStreamAdapter as the listener and register the additional + * listeners with the CopyStreamAdapter. + *

+ * The contents of the InputStream are + * read until the end of the stream is reached, but neither the + * source nor the destination are closed. You must do this yourself + * outside of the method call. The number of bytes read/written is + * returned. + *

+ * @param source The source InputStream. + * @param dest The destination OutputStream. + * @param bufferSize The number of bytes to buffer during the copy. + * @param streamSize The number of bytes in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * @param listener The CopyStreamListener to notify of progress. If + * this parameter is null, notification is not attempted. + * @exception CopyStreamException If an error occurs while reading from the + * source or writing to the destination. The CopyStreamException + * will contain the number of bytes confirmed to have been + * transferred before an + * IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with + * the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + ***/ + public static final long copyStream(InputStream source, OutputStream dest, + int bufferSize, long streamSize, + CopyStreamListener listener) + throws CopyStreamException + { + return copyStream(source, dest, bufferSize, streamSize, listener, + true); + } + + + /*** + * Copies the contents of an InputStream to an OutputStream using a + * copy buffer of a given size. The contents of the InputStream are + * read until the end of the stream is reached, but neither the + * source nor the destination are closed. You must do this yourself + * outside of the method call. The number of bytes read/written is + * returned. + *

+ * @param source The source InputStream. + * @param dest The destination OutputStream. + * @return The number of bytes read/written in the copy operation. + * @exception CopyStreamException If an error occurs while reading from the + * source or writing to the destination. The CopyStreamException + * will contain the number of bytes confirmed to have been + * transferred before an + * IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with + * the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + ***/ + public static final long copyStream(InputStream source, OutputStream dest, + int bufferSize) + throws CopyStreamException + { + return copyStream(source, dest, bufferSize, + CopyStreamEvent.UNKNOWN_STREAM_SIZE, null); + } + + + /*** + * Same as copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); + ***/ + public static final long copyStream(InputStream source, OutputStream dest) + throws CopyStreamException + { + return copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); + } + + + /*** + * Copies the contents of a Reader to a Writer using a + * copy buffer of a given size and notifies the provided + * CopyStreamListener of the progress of the copy operation by calling + * its bytesTransferred(long, int) method after each write to the + * destination. If you wish to notify more than one listener you should + * use a CopyStreamAdapter as the listener and register the additional + * listeners with the CopyStreamAdapter. + *

+ * The contents of the Reader are + * read until its end is reached, but neither the source nor the + * destination are closed. You must do this yourself outside of the + * method call. The number of characters read/written is returned. + *

+ * @param source The source Reader. + * @param dest The destination writer. + * @param bufferSize The number of characters to buffer during the copy. + * @param streamSize The number of characters in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * @param listener The CopyStreamListener to notify of progress. If + * this parameter is null, notification is not attempted. + * @return The number of characters read/written in the copy operation. + * @exception CopyStreamException If an error occurs while reading from the + * source or writing to the destination. The CopyStreamException + * will contain the number of bytes confirmed to have been + * transferred before an + * IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with + * the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + ***/ + public static final long copyReader(Reader source, Writer dest, + int bufferSize, long streamSize, + CopyStreamListener listener) + throws CopyStreamException + { + int chars; + long total; + char[] buffer; + + buffer = new char[bufferSize]; + total = 0; + + try + { + while ((chars = source.read(buffer)) != -1) + { + // Technically, some read(char[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + if (chars == 0) + { + chars = source.read(); + if (chars < 0) + break; + dest.write(chars); + dest.flush(); + ++total; + if (listener != null) + listener.bytesTransferred(total, chars, streamSize); + continue; + } + + dest.write(buffer, 0, chars); + dest.flush(); + total += chars; + if (listener != null) + listener.bytesTransferred(total, chars, streamSize); + } + } + catch (IOException e) + { + throw new CopyStreamException("IOException caught while copying.", + total, e); + } + + return total; + } + + + /*** + * Copies the contents of a Reader to a Writer using a + * copy buffer of a given size. The contents of the Reader are + * read until its end is reached, but neither the source nor the + * destination are closed. You must do this yourself outside of the + * method call. The number of characters read/written is returned. + *

+ * @param source The source Reader. + * @param dest The destination writer. + * @param bufferSize The number of characters to buffer during the copy. + * @return The number of characters read/written in the copy operation. + * @exception CopyStreamException If an error occurs while reading from the + * source or writing to the destination. The CopyStreamException + * will contain the number of bytes confirmed to have been + * transferred before an + * IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with + * the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + ***/ + public static final long copyReader(Reader source, Writer dest, + int bufferSize) + throws CopyStreamException + { + return copyReader(source, dest, bufferSize, + CopyStreamEvent.UNKNOWN_STREAM_SIZE, null); + } + + + /*** + * Same as copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); + ***/ + public static final long copyReader(Reader source, Writer dest) + throws CopyStreamException + { + return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); + } + +} diff --git a/src/org/apache/commons/net/util/ListenerList.java b/src/org/apache/commons/net/util/ListenerList.java new file mode 100644 index 0000000..796fb78 --- /dev/null +++ b/src/org/apache/commons/net/util/ListenerList.java @@ -0,0 +1,63 @@ +/* + * 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.util; + +import java.io.Serializable; +import java.util.EventListener; +import java.util.Iterator; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author Daniel F. Savarese + */ + +public class ListenerList implements Serializable, Iterable +{ + private final CopyOnWriteArrayList __listeners; + + public ListenerList() + { + __listeners = new CopyOnWriteArrayList(); + } + + public void addListener(EventListener listener) + { + __listeners.add(listener); + } + + public void removeListener(EventListener listener) + { + __listeners.remove(listener); + } + + public int getListenerCount() + { + return __listeners.size(); + } + + /** + * Return an {@link Iterator} for the {@link EventListener} instances + * + * @since 2.0 + * TODO Check that this is a good defensive strategy + */ + public Iterator iterator() { + return __listeners.iterator(); + } + +} diff --git a/src/org/apache/commons/net/util/SubnetUtils.java b/src/org/apache/commons/net/util/SubnetUtils.java new file mode 100644 index 0000000..90cc6e8 --- /dev/null +++ b/src/org/apache/commons/net/util/SubnetUtils.java @@ -0,0 +1,211 @@ +/* + * 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.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A class that performs some subnet calculations given a network address and a subnet mask. + * @see http://www.faqs.org/rfcs/rfc1519.html + * @author + * @since 2.0 + */ +public class SubnetUtils { + + private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; + private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})"; + private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS); + private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT); + private static final int NBITS = 32; + + private int netmask = 0; + private int address = 0; + private int network = 0; + private int broadcast = 0; + + /** + * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16" + * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" + */ + public SubnetUtils(String cidrNotation) { + calculate(cidrNotation); + } + + /** + * Constructor that takes two dotted decimal addresses. + * @param address An IP address, e.g. "192.168.0.1" + * @param mask A dotted decimal netmask e.g. "255.255.0.0" + */ + public SubnetUtils(String address, String mask) { + calculate(toCidrNotation(address, mask)); + } + + /** + * Convenience container for subnet summary information. + * + */ + public final class SubnetInfo { + private SubnetInfo() {} + + private int netmask() { return netmask; } + private int network() { return network; } + private int address() { return address; } + private int broadcast() { return broadcast; } + private int low() { return network() + 1; } + private int high() { return broadcast() - 1; } + + public boolean isInRange(String address) { return isInRange(toInteger(address)); } + private boolean isInRange(int address) { return ((address-low()) <= (high()-low())); } + + public String getBroadcastAddress() { return format(toArray(broadcast())); } + public String getNetworkAddress() { return format(toArray(network())); } + public String getNetmask() { return format(toArray(netmask())); } + public String getAddress() { return format(toArray(address())); } + public String getLowAddress() { return format(toArray(low())); } + public String getHighAddress() { return format(toArray(high())); } + public int getAddressCount() { return (broadcast() - low()); } + + public int asInteger(String address) { return toInteger(address); } + + public String getCidrSignature() { + return toCidrNotation( + format(toArray(address())), + format(toArray(netmask())) + ); + } + + public String[] getAllAddresses() { + String[] addresses = new String[getAddressCount()]; + for (int add = low(), j=0; add <= high(); ++add, ++j) { + addresses[j] = format(toArray(add)); + } + return addresses; + } + } + + /** + * Return a {@link SubnetInfo} instance that contains subnet-specific statistics + * @return + */ + public final SubnetInfo getInfo() { return new SubnetInfo(); } + + /* + * Initialize the internal fields from the supplied CIDR mask + */ + private void calculate(String mask) { + Matcher matcher = cidrPattern.matcher(mask); + + if (matcher.matches()) { + address = matchAddress(matcher); + + /* Create a binary netmask from the number of bits specification /x */ + int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS-1); + for (int j = 0; j < cidrPart; ++j) { + netmask |= (1 << 31-j); + } + + /* Calculate base network address */ + network = (address & netmask); + + /* Calculate broadcast address */ + broadcast = network | ~(netmask); + } + else + throw new IllegalArgumentException("Could not parse [" + mask + "]"); + } + + /* + * Convert a dotted decimal format address to a packed integer format + */ + private int toInteger(String address) { + Matcher matcher = addressPattern.matcher(address); + if (matcher.matches()) { + return matchAddress(matcher); + } + else + throw new IllegalArgumentException("Could not parse [" + address + "]"); + } + + /* + * Convenience method to extract the components of a dotted decimal address and + * pack into an integer using a regex match + */ + private int matchAddress(Matcher matcher) { + int addr = 0; + for (int i = 1; i <= 4; ++i) { + int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255)); + addr |= ((n & 0xff) << 8*(4-i)); + } + return addr; + } + + /* + * Convert a packed integer address into a 4-element array + */ + private int[] toArray(int val) { + int ret[] = new int[4]; + for (int j = 3; j >= 0; --j) + ret[j] |= ((val >>> 8*(3-j)) & (0xff)); + return ret; + } + + /* + * Convert a 4-element array into dotted decimal format + */ + private String format(int[] octets) { + StringBuilder str = new StringBuilder(); + for (int i =0; i < octets.length; ++i){ + str.append(octets[i]); + if (i != octets.length - 1) { + str.append("."); + } + } + return str.toString(); + } + + /* + * Convenience function to check integer boundaries + */ + private int rangeCheck(int value, int begin, int end) { + if (value >= begin && value <= end) + return value; + + throw new IllegalArgumentException("Value out of range: [" + value + "]"); + } + + /* + * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy + * see Hacker's Delight section 5.1 + */ + int pop(int x) { + x = x - ((x >>> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); + x = (x + (x >>> 4)) & 0x0F0F0F0F; + x = x + (x >>> 8); + x = x + (x >>> 16); + return x & 0x0000003F; + } + + /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format + * by counting the 1-bit population in the mask address. (It may be better to count + * NBITS-#trailing zeroes for this case) + */ + private String toCidrNotation(String addr, String mask) { + return addr + "/" + pop(toInteger(mask)); + } +} -- cgit v1.2.3