diff --git a/.gitignore b/.gitignore index 2f961d9e..6b0f8a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ .gradle /.idea .idea -/cache \ No newline at end of file +/cache +*.log \ No newline at end of file diff --git a/Aria/build.gradle b/Aria/build.gradle index 1e2366c1..befa95ba 100644 --- a/Aria/build.gradle +++ b/Aria/build.gradle @@ -23,5 +23,7 @@ dependencies { testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile project(':AriaAnnotations') +// compile project(':AriaFtpPlug') + } apply from: 'bintray-release.gradle' diff --git a/Aria/src/main/java/com/arialyy/aria/core/Aria.java b/Aria/src/main/java/com/arialyy/aria/core/Aria.java index 5dc70cfe..538f8504 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/Aria.java +++ b/Aria/src/main/java/com/arialyy/aria/core/Aria.java @@ -43,7 +43,7 @@ import com.arialyy.aria.core.upload.UploadTask; * * //下载 * Aria.download(this) - * .load(DOWNLOAD_URL) //下载地址,必填 + * .load(URL) //下载地址,必填 * //文件保存路径,必填 * .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") * .start(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java index 9ef1995a..f13e618d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -28,6 +28,7 @@ import android.support.v4.app.Fragment; import android.text.TextUtils; import android.util.Log; import android.widget.PopupWindow; +import com.arialyy.aria.core.common.QueueMod; import com.arialyy.aria.core.download.DownloadReceiver; import com.arialyy.aria.core.command.ICmd; import com.arialyy.aria.core.inf.IReceiver; @@ -37,10 +38,10 @@ import com.arialyy.aria.util.CommonUtil; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; @@ -59,7 +60,7 @@ import org.xml.sax.SAXException; public static final String DOWNLOAD_TEMP_DIR = "/Aria/temp/download/"; public static final String UPLOAD_TEMP_DIR = "/Aria/temp/upload/"; @SuppressLint("StaticFieldLeak") private static volatile AriaManager INSTANCE = null; - private Map mReceivers = new HashMap<>(); + private Map mReceivers = new ConcurrentHashMap<>(); public static Context APP; private List mCommands = new ArrayList<>(); private Configuration.DownloadConfig mDConfig; @@ -88,7 +89,7 @@ import org.xml.sax.SAXException; /** * 设置上传任务的执行队列类型 * - * @param mod {@link com.arialyy.aria.core.QueueMod} + * @param mod {@link QueueMod} */ public AriaManager setUploadQueueMod(QueueMod mod) { mUConfig.setQueueMod(mod.tag); @@ -98,7 +99,7 @@ import org.xml.sax.SAXException; /** * 设置下载任务的执行队列类型 * - * @param mod {@link com.arialyy.aria.core.QueueMod} + * @param mod {@link QueueMod} */ public AriaManager setDownloadQueueMod(QueueMod mod) { mDConfig.setQueueMod(mod.tag); diff --git a/Aria/src/main/java/com/arialyy/aria/core/ConfigHelper.java b/Aria/src/main/java/com/arialyy/aria/core/ConfigHelper.java index d0df8509..6243d5ca 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/ConfigHelper.java +++ b/Aria/src/main/java/com/arialyy/aria/core/ConfigHelper.java @@ -228,7 +228,7 @@ class ConfigHelper extends DefaultHandler { } if (num < 1) { Log.e(TAG, "下载线程数不能小于 1"); - num = 3; + num = 1; } if (isDownloadConfig) { mDownloadConfig.threadNum = num; diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/group/GroupStartCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/group/GroupStartCmd.java index 4fde6115..8f890987 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/group/GroupStartCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/group/GroupStartCmd.java @@ -17,7 +17,7 @@ package com.arialyy.aria.core.command.group; import android.text.TextUtils; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.QueueMod; +import com.arialyy.aria.core.common.QueueMod; import com.arialyy.aria.core.inf.AbsGroupTask; import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.IEntity; diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/normal/StartCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/normal/StartCmd.java index 25e9a8e7..c61b31da 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/normal/StartCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/normal/StartCmd.java @@ -18,7 +18,7 @@ package com.arialyy.aria.core.command.normal; import android.text.TextUtils; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.QueueMod; +import com.arialyy.aria.core.common.QueueMod; import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.AbsTaskEntity; diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java new file mode 100644 index 00000000..a9af7000 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.common; + +import android.content.Context; +import android.util.Log; +import android.util.SparseArray; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.inf.AbsNormalEntity; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.core.inf.IDownloadListener; +import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.upload.UploadTaskEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.util.Properties; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by AriaL on 2017/7/1. + * 文件下载器 + */ +public abstract class AbsFileer> + implements Runnable, IUtil { + private final String TAG = "Downloader"; + protected IEventListener mListener; + protected TASK_ENTITY mTaskEntity; + protected ENTITY mEntity; + protected File mConfigFile;//信息配置文件 + protected Context mContext; + protected File mTempFile; //下载的文件 + protected boolean isNewTask = true; + protected StateConstance mConstance; + private ExecutorService mFixedThreadPool; + private int mThreadNum, mRealThreadNum; + private SparseArray mTask = new SparseArray<>(); + + /** + * 小于1m的文件不启用多线程 + */ + private static final long SUB_LEN = 1024 * 1024; + private Timer mTimer; + + protected AbsFileer(IEventListener listener, TASK_ENTITY taskEntity) { + mListener = listener; + mTaskEntity = taskEntity; + mEntity = mTaskEntity.getEntity(); + mContext = AriaManager.APP; + mConstance = new StateConstance(); + } + + @Override public void setMaxSpeed(double maxSpeed) { + for (int i = 0; i < mThreadNum; i++) { + AbsThreadTask task = mTask.get(i); + if (task != null) { + task.setMaxSpeed(maxSpeed); + } + } + } + + public StateConstance getConstance() { + return mConstance; + } + + @Override public void run() { + startFlow(); + } + + /** + * 开始下载流程 + */ + private void startFlow() { + checkTask(); + if (mListener instanceof IDownloadListener) { + ((IDownloadListener) mListener).onPostPre(mEntity.getFileSize()); + } + mConstance.cleanState(); + mConstance.isRunning = true; + if (!mTaskEntity.isSupportBP) { + mThreadNum = 1; + mConstance.THREAD_NUM = mThreadNum; + handleNoSupportBP(); + } else { + mThreadNum = isNewTask ? (getNewTaskThreadNum()) : mRealThreadNum; + mConstance.THREAD_NUM = mThreadNum; + handleBreakpoint(); + } + startTimer(); + } + + /** + * 设置新任务的最大线程数 + */ + protected int getNewTaskThreadNum() { + return mEntity.getFileSize() <= SUB_LEN || mTaskEntity.requestType == AbsTaskEntity.FTP_DIR ? 1 + : AriaManager.getInstance(mContext).getDownloadConfig().getThreadNum(); + } + + /** + * 启动进度获取定时器 + */ + private void startTimer() { + mTimer = new Timer(true); + mTimer.schedule(new TimerTask() { + @Override public void run() { + if (mConstance.isComplete() || !mConstance.isRunning) { + closeTimer(); + } else if (mConstance.CURRENT_LOCATION >= 0) { + mListener.onProgress(mConstance.CURRENT_LOCATION); + } + } + }, 0, 1000); + } + + private void closeTimer() { + if (mTimer != null) { + mTimer.purge(); + mTimer.cancel(); + } + } + + @Override public long getFileSize() { + return mEntity.getFileSize(); + } + + /** + * 获取当前下载位置 + */ + @Override public long getCurrentLocation() { + return mConstance.CURRENT_LOCATION; + } + + @Override public boolean isRunning() { + return mConstance.isRunning; + } + + @Override public void cancel() { + closeTimer(); + mConstance.isCancel = true; + mConstance.isRunning = false; + if (mFixedThreadPool != null) { + mFixedThreadPool.shutdown(); + } + for (int i = 0; i < mThreadNum; i++) { + AbsThreadTask task = mTask.get(i); + if (task != null) { + task.cancel(); + } + } + if (mTaskEntity instanceof DownloadTaskEntity) { + CommonUtil.delDownloadTaskConfig(mTaskEntity.removeFile, (DownloadTaskEntity) mTaskEntity); + } else if (mTaskEntity instanceof UploadTaskEntity) { + CommonUtil.delUploadTaskConfig(mTaskEntity.removeFile, (UploadTaskEntity) mTaskEntity); + } + } + + @Override public void stop() { + closeTimer(); + if (mConstance.isComplete()) return; + mConstance.isStop = true; + mConstance.isRunning = false; + if (mFixedThreadPool != null) { + mFixedThreadPool.shutdown(); + } + for (int i = 0; i < mThreadNum; i++) { + AbsThreadTask task = mTask.get(i); + if (task != null) { + task.stop(); + } + } + } + + /** + * 直接调用的时候会自动启动线程执行 + */ + @Override public void start() { + new Thread(this).start(); + } + + @Override public void resume() { + start(); + } + + /** + * 返回该下载器的 + */ + public IEventListener getListener() { + return mListener; + } + + /** + * 检查任务是否是新任务,新任务条件: + * 1、文件不存在 + * 2、下载记录文件不存在 + * 3、下载记录文件缺失或不匹配 + * 4、数据库记录不存在 + * 5、不支持断点,则是新任务 + */ + protected abstract void checkTask(); + + /** + * 检查记录文件,如果是新任务返回{@code true},否则返回{@code false} + */ + protected boolean checkConfigFile() { + Properties pro = CommonUtil.loadConfig(mConfigFile); + if (pro.isEmpty()) { + return true; + } + Set keys = pro.keySet(); + int num = 0; + for (Object key : keys) { + if (String.valueOf(key).contains("_record_")) { + num++; + } + } + if (num == 0) { + return true; + } + mRealThreadNum = num; + for (int i = 0; i < mRealThreadNum; i++) { + if (pro.getProperty(mTempFile.getName() + "_record_" + i) == null) { + Object state = pro.getProperty(mTempFile.getName() + "_state_" + i); + if (state != null && Integer.parseInt(state + "") == 1) { + continue; + } + return true; + } + } + return false; + } + + /** + * 恢复记录地址 + * + * @return true 表示下载完成 + */ + private boolean resumeRecordLocation(int i, long startL, long endL) { + mConstance.CURRENT_LOCATION += endL - startL; + Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); + mConstance.COMPLETE_THREAD_NUM++; + mConstance.STOP_NUM++; + mConstance.CANCEL_NUM++; + if (mConstance.isComplete()) { + if (mConfigFile.exists()) { + mConfigFile.delete(); + } + mListener.onComplete(); + mConstance.isRunning = false; + return true; + } + return false; + } + + /** + * 启动断点任务时,创建单线程任务 + * + * @param i 线程id + * @param startL 该任务起始位置 + * @param endL 该任务结束位置 + * @param fileLength 该任务需要处理的文件长度 + */ + private AbsThreadTask createSingThreadTask(int i, long startL, long endL, long fileLength) { + SubThreadConfig config = new SubThreadConfig<>(); + config.FILE_SIZE = fileLength; + config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); + config.TEMP_FILE = mTempFile; + config.THREAD_ID = i; + config.START_LOCATION = startL; + config.END_LOCATION = endL; + config.CONFIG_FILE_PATH = mConfigFile.getPath(); + config.SUPPORT_BP = mTaskEntity.isSupportBP; + config.TASK_ENTITY = mTaskEntity; + return selectThreadTask(config); + } + + /** + * 处理断点 + */ + private void handleBreakpoint() { + long fileLength = mEntity.getFileSize(); + Properties pro = CommonUtil.loadConfig(mConfigFile); + int blockSize = (int) (fileLength / mThreadNum); + int[] recordL = new int[mThreadNum]; + for (int i = 0; i < mThreadNum; i++) { + recordL[i] = -1; + } + int rl = 0; + if (isNewTask) { + handleNewTask(); + } + for (int i = 0; i < mThreadNum; i++) { + long startL = i * blockSize, endL = (i + 1) * blockSize; + Object state = pro.getProperty(mTempFile.getName() + "_state_" + i); + if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成 + if (resumeRecordLocation(i, startL, endL)) return; + continue; + } + //分配下载位置 + Object record = pro.getProperty(mTempFile.getName() + "_record_" + i); + //如果有记录,则恢复下载 + if (!isNewTask && record != null && Long.parseLong(record + "") >= 0) { + Long r = Long.parseLong(record + ""); + mConstance.CURRENT_LOCATION += r - startL; + Log.d(TAG, "任务【" + mEntity.getFileName() + "】线程__" + i + "__恢复下载"); + startL = r; + recordL[rl] = i; + rl++; + } else { + recordL[rl] = i; + rl++; + } + if (i == (mThreadNum - 1)) { + //最后一个线程的结束位置即为文件的总长度 + endL = fileLength; + } + AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength); + if (task == null) return; + mTask.put(i, task); + } + startSingleTask(recordL); + } + + /** + * 启动单线程下载任务 + */ + private void startSingleTask(int[] recordL) { + if (mConstance.CURRENT_LOCATION > 0) { + mListener.onResume(mConstance.CURRENT_LOCATION); + } else { + mListener.onStart(mConstance.CURRENT_LOCATION); + } + mFixedThreadPool = Executors.newFixedThreadPool(recordL.length); + for (int l : recordL) { + if (l == -1) continue; + Runnable task = mTask.get(l); + if (task != null) { + mFixedThreadPool.execute(task); + } + } + } + + /** + * 处理新任务 + */ + protected abstract void handleNewTask(); + + /** + * 处理不支持断点的下载 + */ + private void handleNoSupportBP() { + SubThreadConfig config = new SubThreadConfig<>(); + config.FILE_SIZE = mEntity.getFileSize(); + config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); + config.TEMP_FILE = mTempFile; + config.THREAD_ID = 0; + config.START_LOCATION = 0; + config.END_LOCATION = config.FILE_SIZE; + config.CONFIG_FILE_PATH = mConfigFile.getPath(); + config.SUPPORT_BP = mTaskEntity.isSupportBP; + config.TASK_ENTITY = mTaskEntity; + AbsThreadTask task = selectThreadTask(config); + if (task == null) return; + mTask.put(0, task); + mFixedThreadPool = Executors.newFixedThreadPool(1); + mFixedThreadPool.execute(task); + mListener.onStart(0); + } + + /** + * 选择单任务线程的类型 + */ + protected abstract AbsThreadTask selectThreadTask(SubThreadConfig config); + + protected void failDownload(String errorMsg) { + closeTimer(); + Log.e(TAG, errorMsg); + mConstance.isRunning = false; + mListener.onFail(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java new file mode 100644 index 00000000..316d6cbc --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.common; + +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.inf.AbsEntity; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Properties; + +/** + * Created by lyy on 2017/1/18. + * 下载线程 + */ +public abstract class AbsThreadTask> + implements Runnable { + private final String TAG = "AbsThreadTask"; + protected long mChildCurrentLocation = 0, mSleepTime = 0; + protected int mBufSize; + protected String mConfigFPath; + protected IEventListener mListener; + protected StateConstance STATE; + protected SubThreadConfig mConfig; + protected ENTITY mEntity; + protected TASK_ENTITY mTaskEntity; + /** + * FTP 服务器编码 + */ + public static String SERVER_CHARSET = "ISO-8859-1"; + + protected AbsThreadTask(StateConstance constance, IEventListener listener, + SubThreadConfig info) { + AriaManager manager = AriaManager.getInstance(AriaManager.APP); + STATE = constance; + STATE.CONNECT_TIME_OUT = manager.getDownloadConfig().getConnectTimeOut(); + STATE.READ_TIME_OUT = manager.getDownloadConfig().getIOTimeOut(); + mListener = listener; + mConfig = info; + mTaskEntity = mConfig.TASK_ENTITY; + mEntity = mTaskEntity.getEntity(); + if (mConfig.SUPPORT_BP) { + mConfigFPath = info.CONFIG_FILE_PATH; + } + mBufSize = manager.getDownloadConfig().getBuffSize(); + setMaxSpeed(AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getMsxSpeed()); + } + + public void setMaxSpeed(double maxSpeed) { + if (-0.9999 < maxSpeed && maxSpeed < 0.00001) { + mSleepTime = 0; + } else { + BigDecimal db = new BigDecimal( + ((mBufSize / 1024) * (filterVersion() ? 1 : STATE.THREAD_NUM) / maxSpeed) * 1000); + mSleepTime = db.setScale(0, BigDecimal.ROUND_HALF_UP).longValue(); + } + } + + private boolean filterVersion() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + /** + * 停止下载 + */ + public void stop() { + synchronized (AriaManager.LOCK) { + try { + if (mConfig.SUPPORT_BP) { + STATE.STOP_NUM++; + Log.d(TAG, "任务【" + + mConfig.TEMP_FILE.getName() + + "】thread__" + + mConfig.THREAD_ID + + "__停止, stop location ==> " + + mChildCurrentLocation); + writeConfig(false, mChildCurrentLocation); + if (STATE.isStop()) { + Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已停止"); + STATE.isRunning = false; + mListener.onStop(STATE.CURRENT_LOCATION); + } + } else { + Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已停止"); + STATE.isRunning = false; + mListener.onStop(STATE.CURRENT_LOCATION); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 下载中 + */ + protected void progress(long len) { + synchronized (AriaManager.LOCK) { + mChildCurrentLocation += len; + STATE.CURRENT_LOCATION += len; + } + } + + /** + * 取消下载 + */ + public void cancel() { + synchronized (AriaManager.LOCK) { + if (mConfig.SUPPORT_BP) { + STATE.CANCEL_NUM++; + Log.d(TAG, + "任务【" + mConfig.TEMP_FILE.getName() + "】thread__" + mConfig.THREAD_ID + "__取消下载"); + if (STATE.isCancel()) { + File configFile = new File(mConfigFPath); + if (configFile.exists()) { + configFile.delete(); + } + if (mConfig.TEMP_FILE.exists() && !(mEntity instanceof UploadEntity)) { + mConfig.TEMP_FILE.delete(); + } + Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已取消"); + STATE.isRunning = false; + mListener.onCancel(); + } + } else { + Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已取消"); + STATE.isRunning = false; + mListener.onCancel(); + } + } + } + + /** + * 任务失败 + */ + protected void fail(long currentLocation, String msg, Exception ex) { + synchronized (AriaManager.LOCK) { + try { + STATE.FAIL_NUM++; + STATE.isRunning = false; + STATE.isStop = true; + if (ex != null) { + Log.e(TAG, msg + "\n" + CommonUtil.getPrintException(ex)); + } else { + Log.e(TAG, msg); + } + if (mConfig.SUPPORT_BP) { + writeConfig(false, currentLocation); + if (STATE.isFail()) { + Log.e(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】执行失败"); + mListener.onFail(); + } + } else { + Log.e(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】执行失败"); + mListener.onFail(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 将记录写入到配置文件 + */ + protected void writeConfig(boolean isComplete, long record) throws IOException { + synchronized (AriaManager.LOCK) { + String key = null, value = null; + if (0 < record && record < mConfig.END_LOCATION) { + key = mConfig.TEMP_FILE.getName() + "_record_" + mConfig.THREAD_ID; + value = String.valueOf(record); + } else if (record >= mConfig.END_LOCATION || isComplete) { + key = mConfig.TEMP_FILE.getName() + "_state_" + mConfig.THREAD_ID; + value = "1"; + } + if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) { + File configFile = new File(mConfigFPath); + Properties pro = CommonUtil.loadConfig(configFile); + pro.setProperty(key, value); + CommonUtil.saveConfig(configFile, pro); + } + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java similarity index 81% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadUtil.java rename to Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java index 5d2c7456..ae60e058 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.core.common; /** * Created by lyy on 2016/10/31. * 抽象的下载接口 */ -public interface IDownloadUtil { +public interface IUtil { /** * 获取文件大小 @@ -37,25 +37,30 @@ public interface IDownloadUtil { * * @return true, 正在下载 */ - boolean isDownloading(); + boolean isRunning(); /** * 取消下载 */ - void cancelDownload(); + void cancel(); /** * 停止下载 */ - void stopDownload(); + void stop(); /** * 开始下载 */ - void startDownload(); + void start(); /** * 从上次断点恢复下载 */ - void resumeDownload(); + void resume(); + + /** + * 设置最大下载速度 + */ + void setMaxSpeed(double maxSpeed); } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/ProxyHelper.java b/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java similarity index 96% rename from Aria/src/main/java/com/arialyy/aria/core/ProxyHelper.java rename to Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java index c1d1236f..c12ca3f2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/ProxyHelper.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core; +package com.arialyy.aria.core.common; +import com.arialyy.aria.core.AriaManager; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Set; diff --git a/Aria/src/main/java/com/arialyy/aria/core/QueueMod.java b/Aria/src/main/java/com/arialyy/aria/core/common/QueueMod.java similarity index 92% rename from Aria/src/main/java/com/arialyy/aria/core/QueueMod.java rename to Aria/src/main/java/com/arialyy/aria/core/common/QueueMod.java index db9e7b6c..44088f3a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/QueueMod.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/QueueMod.java @@ -1,4 +1,4 @@ -package com.arialyy.aria.core; +package com.arialyy.aria.core.common; /** * Created by Aria.Lao on 2017/6/21. @@ -19,7 +19,7 @@ public enum QueueMod { */ NOW("now"); - String tag; + public String tag; public String getTag() { return tag; diff --git a/Aria/src/main/java/com/arialyy/aria/core/RequestEnum.java b/Aria/src/main/java/com/arialyy/aria/core/common/RequestEnum.java similarity index 95% rename from Aria/src/main/java/com/arialyy/aria/core/RequestEnum.java rename to Aria/src/main/java/com/arialyy/aria/core/common/RequestEnum.java index 71b82ecd..5822bb70 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/RequestEnum.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/RequestEnum.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core; +package com.arialyy.aria.core.common; /** * Created by lyy on 2017/1/23. diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/StateConstance.java b/Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java similarity index 65% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/StateConstance.java rename to Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java index 323f65ff..101d3dbd 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/StateConstance.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java @@ -13,32 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.core.common; /** * Created by lyy on 2017/1/18. * 下载状态常量 */ -final class StateConstance { - int CANCEL_NUM = 0; - int STOP_NUM = 0; - int FAIL_NUM = 0; - int CONNECT_TIME_OUT; //连接超时时间 - int READ_TIME_OUT; //流读取的超时时间 - int COMPLETE_THREAD_NUM = 0; - int THREAD_NUM; - long CURRENT_LOCATION = 0; - boolean isDownloading = false; - boolean isCancel = false; - boolean isStop = false; +public class StateConstance { + public int CANCEL_NUM = 0; + public int STOP_NUM = 0; + public int FAIL_NUM = 0; + public int CONNECT_TIME_OUT; //连接超时时间 + public int READ_TIME_OUT; //流读取的超时时间 + public int COMPLETE_THREAD_NUM = 0; + public int THREAD_NUM; + public long CURRENT_LOCATION = 0; + public boolean isRunning = false; + public boolean isCancel = false; + public boolean isStop = false; - StateConstance() { + public StateConstance() { } - void cleanState() { + public void cleanState() { isCancel = false; isStop = false; - isDownloading = true; + isRunning = true; CURRENT_LOCATION = 0; CANCEL_NUM = 0; STOP_NUM = 0; @@ -48,28 +48,28 @@ final class StateConstance { /** * 所有子线程是否都已经停止下载 */ - boolean isStop() { + public boolean isStop() { return STOP_NUM == THREAD_NUM; } /** * 所有子线程是否都已经下载失败 */ - boolean isFail() { + public boolean isFail() { return FAIL_NUM == THREAD_NUM; } /** * 所有子线程是否都已经完成下载 */ - boolean isComplete() { + public boolean isComplete() { return COMPLETE_THREAD_NUM == THREAD_NUM; } /** * 所有子线程是否都已经取消下载 */ - boolean isCancel() { + public boolean isCancel() { return CANCEL_NUM == THREAD_NUM; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java b/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java new file mode 100644 index 00000000..d2992980 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java @@ -0,0 +1,25 @@ +package com.arialyy.aria.core.common; + +import com.arialyy.aria.core.inf.AbsTaskEntity; +import java.io.File; + +/** + * 子线程下载信息类 + */ +public class SubThreadConfig { + //线程Id + public int THREAD_ID; + //下载文件大小 + public long FILE_SIZE; + //子线程启动下载位置 + public long START_LOCATION; + //子线程结束下载位置 + public long END_LOCATION; + //下载文件或上传的文件路径 + public File TEMP_FILE; + //服务器地址 + public String URL; + public String CONFIG_FILE_PATH; + public TASK_ENTITY TASK_ENTITY; + public boolean SUPPORT_BP = true; +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/BaseListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java similarity index 93% rename from Aria/src/main/java/com/arialyy/aria/core/download/BaseListener.java rename to Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java index dbbed555..0b02c968 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/BaseListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java @@ -16,9 +16,11 @@ package com.arialyy.aria.core.download; import android.os.Handler; +import android.util.Log; import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.scheduler.ISchedulers; @@ -28,8 +30,8 @@ import java.lang.ref.WeakReference; /** * 下载监听类 */ -class BaseListener> - implements IEventListener { +class BaseDListener> + implements IDownloadListener { private WeakReference outHandler; private long mLastLen = 0; //上一次发送长度 private boolean isFirst = true; @@ -38,7 +40,7 @@ class BaseListener> private boolean isConvertSpeed = false; boolean isWait = false; - BaseListener(TASK task, Handler outHandler) { + BaseDListener(TASK task, Handler outHandler) { this.outHandler = new WeakReference<>(outHandler); this.mTask = new WeakReference<>(task).get(); this.mEntity = this.mTask.getEntity(); @@ -59,6 +61,10 @@ class BaseListener> sendInState2Target(ISchedulers.POST_PRE); } + @Override public void supportBreakpoint(boolean support) { + + } + @Override public void onStart(long startLocation) { saveData(IEntity.STATE_RUNNING, startLocation); sendInState2Target(ISchedulers.START); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java new file mode 100644 index 00000000..e6e5c684 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download; + +import android.text.TextUtils; +import com.arialyy.aria.core.inf.AbsDownloadTarget; +import com.arialyy.aria.core.inf.AbsTarget; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Aria.Lao on 2017/7/26. + */ +abstract class BaseGroupTarget + extends AbsDownloadTarget { + + List mUrls = new ArrayList<>(); + String mGroupName; + /** + * 子任务文件名 + */ + private List mSubTaskFileName = new ArrayList<>(); + /** + * 是否已经设置了文件路径 + */ + private boolean isSetDirPathed = false; + + /** + * 查询任务组实体,如果数据库不存在该实体,则新创建一个新的任务组实体 + */ + DownloadGroupEntity getDownloadGroupEntity() { + DownloadGroupEntity entity = + DbEntity.findFirst(DownloadGroupEntity.class, "groupName=?", mGroupName); + if (entity == null) { + entity = new DownloadGroupEntity(); + entity.setGroupName(mGroupName); + entity.setUrls(mUrls); + entity.insert(); + } + return entity; + } + + /** + * 设置任务组别名 + */ + public TARGET setGroupAlias(String alias) { + if (TextUtils.isEmpty(alias)) return (TARGET) this; + mEntity.setAlias(alias); + mEntity.update(); + return (TARGET) this; + } + + /** + * 设置任务组的文件夹路径,在Aria中,任务组的所有子任务都会下载到以任务组组名的文件夹中。 + * 如:groupDirPath = "/mnt/sdcard/download/group_test" + *
+   *   {@code
+   *      + mnt
+   *        + sdcard
+   *          + download
+   *            + group_test
+   *              - task1.apk
+   *              - task2.apk
+   *              - task3.apk
+   *              ....
+   *
+   *   }
+   * 
+ * + * @param groupDirPath 任务组保存文件夹路径 + */ + public TARGET setDownloadDirPath(String groupDirPath) { + if (TextUtils.isEmpty(groupDirPath)) { + throw new NullPointerException("任务组文件夹保存路径不能为null"); + } + + isSetDirPathed = true; + if (mEntity.getDirPath().equals(groupDirPath)) return (TARGET) this; + + File file = new File(groupDirPath); + if (file.exists() && file.isFile()) { + throw new IllegalArgumentException("路径不能为文件"); + } + if (!file.exists()) { + file.mkdirs(); + } + + mEntity.setDirPath(groupDirPath); + if (!TextUtils.isEmpty(mEntity.getDirPath())) { + reChangeDirPath(groupDirPath); + } else { + mEntity.setSubTasks(createSubTask()); + } + mEntity.update(); + return (TARGET) this; + } + + /** + * 改变任务组文件夹路径,修改文件夹路径会将子任务所有路径更换 + * + * @param newDirPath 新的文件夹路径 + */ + private void reChangeDirPath(String newDirPath) { + List subTask = mEntity.getSubTask(); + if (subTask != null && !subTask.isEmpty()) { + for (DownloadEntity entity : subTask) { + String oldPath = entity.getDownloadPath(); + String newPath = newDirPath + "/" + entity.getFileName(); + File file = new File(oldPath); + file.renameTo(new File(newPath)); + DbEntity.exeSql("UPDATE DownloadEntity SET downloadPath='" + + newPath + + "' WHERE downloadPath='" + + oldPath + + "'"); + DbEntity.exeSql( + "UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'"); + } + } else { + mEntity.setSubTasks(createSubTask()); + } + } + + /** + * 设置子任务文件名,该方法必须在{@link #setDownloadDirPath(String)}之后调用,否则不生效 + */ + public TARGET setSubTaskFileName(List subTaskFileName) { + if (subTaskFileName == null || subTaskFileName.isEmpty()) return (TARGET) this; + mSubTaskFileName.addAll(subTaskFileName); + if (mUrls.size() != subTaskFileName.size()) { + throw new IllegalArgumentException("下载链接数必须要和保存路径的数量一致"); + } + if (isSetDirPathed) { + List entities = mEntity.getSubTask(); + int i = 0; + for (DownloadEntity entity : entities) { + String newName = mSubTaskFileName.get(i); + updateSubFileName(entity, newName); + i++; + } + } + return (TARGET) this; + } + + /** + * 更新子任务文件名 + */ + private void updateSubFileName(DownloadEntity entity, String newName) { + if (!newName.equals(entity.getFileName())) { + String oldPath = mEntity.getDirPath() + "/" + entity.getFileName(); + String newPath = mEntity.getDirPath() + "/" + newName; + File oldFile = new File(oldPath); + if (oldFile.exists()) { + oldFile.renameTo(new File(newPath)); + } + CommonUtil.renameDownloadConfig(oldFile.getName(), newName); + DbEntity.exeSql( + "UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'"); + entity.setDownloadPath(newPath); + entity.setFileName(newName); + entity.update(); + } + } + + /** + * 创建子任务 + */ + private List createSubTask() { + List list = new ArrayList<>(); + for (int i = 0, len = mUrls.size(); i < len; i++) { + DownloadEntity entity = new DownloadEntity(); + entity.setUrl(mUrls.get(i)); + String fileName = mSubTaskFileName.isEmpty() ? createFileName(entity.getUrl()) + : mSubTaskFileName.get(i); + entity.setDownloadPath(mEntity.getDirPath() + "/" + fileName); + entity.setGroupName(mGroupName); + entity.setGroupChild(true); + entity.setFileName(fileName); + entity.insert(); + list.add(entity); + } + return list; + } + +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java index f710c50f..f97bf1b7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java @@ -27,10 +27,7 @@ import com.arialyy.aria.orm.Primary; * 下载实体 */ public class DownloadEntity extends AbsNormalEntity implements Parcelable { - private String downloadUrl = ""; //下载路径 @Primary private String downloadPath = ""; //保存路径 - private boolean isRedirect = false; //是否重定向 - private String redirectUrl = ""; //重定向链接 /** * 所属任务组 @@ -54,40 +51,12 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable { private String serverFileName = ""; @Override public String getKey() { - return downloadUrl; + return getUrl(); } public DownloadEntity() { } - @Override public String toString() { - return "DownloadEntity{" - + "downloadUrl='" - + downloadUrl - + '\'' - + ", downloadPath='" - + downloadPath - + '\'' - + ", isRedirect=" - + isRedirect - + ", redirectUrl='" - + redirectUrl - + '\'' - + ", groupName='" - + groupName - + '\'' - + ", md5Code='" - + md5Code - + '\'' - + ", disposition='" - + disposition - + '\'' - + ", serverFileName='" - + serverFileName - + '\'' - + '}'; - } - public String getMd5Code() { return md5Code; } @@ -120,13 +89,11 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable { this.groupName = groupName; } - public String getDownloadUrl() { - return downloadUrl; - } - - public DownloadEntity setDownloadUrl(String downloadUrl) { - this.downloadUrl = downloadUrl; - return this; + /** + * {@link #getUrl()} + */ + @Deprecated public String getDownloadUrl() { + return getUrl(); } public String getDownloadPath() { @@ -142,44 +109,42 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable { return (DownloadEntity) super.clone(); } - public boolean isRedirect() { - return isRedirect; - } - - public void setRedirect(boolean redirect) { - isRedirect = redirect; - } - - public String getRedirectUrl() { - return redirectUrl; - } - - public void setRedirectUrl(String redirectUrl) { - this.redirectUrl = redirectUrl; - } - @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeString(this.downloadUrl); dest.writeString(this.downloadPath); - dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0); - dest.writeString(this.redirectUrl); dest.writeString(this.groupName); dest.writeString(this.md5Code); dest.writeString(this.disposition); dest.writeString(this.serverFileName); } + @Override public String toString() { + return "DownloadEntity{" + + "downloadPath='" + + downloadPath + + '\'' + + ", groupName='" + + groupName + + '\'' + + ", md5Code='" + + md5Code + + '\'' + + ", disposition='" + + disposition + + '\'' + + ", serverFileName='" + + serverFileName + + '\'' + + '}'; + } + protected DownloadEntity(Parcel in) { super(in); - this.downloadUrl = in.readString(); this.downloadPath = in.readString(); - this.isRedirect = in.readByte() != 0; - this.redirectUrl = in.readString(); this.groupName = in.readString(); this.md5Code = in.readString(); this.disposition = in.readString(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java index da96b5ff..4d5354c2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java @@ -47,7 +47,7 @@ public class DownloadGroupEntity extends AbsGroupEntity { return subtask; } - void setSubTasks(List subTasks) { + public void setSubTasks(List subTasks) { this.subtask = subTasks; } @@ -63,7 +63,7 @@ public class DownloadGroupEntity extends AbsGroupEntity { return urls; } - void setUrls(List urls) { + public void setUrls(List urls) { this.urls = urls; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java index e590bd1a..650772b6 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java @@ -16,14 +16,13 @@ package com.arialyy.aria.core.download; import android.os.Handler; -import android.util.Log; import com.arialyy.aria.core.download.downloader.IDownloadGroupListener; /** * Created by Aria.Lao on 2017/7/20. * 任务组下载事件 */ -class DownloadGroupListener extends BaseListener +class DownloadGroupListener extends BaseDListener implements IDownloadGroupListener { private final String TAG = "DownloadGroupListener"; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java index 53f8b79c..33d0529b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java @@ -15,32 +15,18 @@ */ package com.arialyy.aria.core.download; -import android.text.TextUtils; -import com.arialyy.aria.core.inf.AbsDownloadTarget; -import com.arialyy.aria.core.inf.AbsUploadTarget; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; -import java.io.File; -import java.util.ArrayList; import java.util.List; /** * Created by AriaL on 2017/6/29. + * 下载任务组 */ public class DownloadGroupTarget - extends AbsDownloadTarget { - private List mUrls = new ArrayList<>(); + extends BaseGroupTarget { private final String TAG = "DownloadGroupTarget"; - /** - * 子任务文件名 - */ - private List mSubTaskFileName = new ArrayList<>(); - private String mGroupName; - /** - * 是否已经设置了文件路径 - */ - private boolean isSetDirPathed = false; DownloadGroupTarget(DownloadGroupEntity groupEntity, String targetName) { this.mTargetName = targetName; @@ -71,31 +57,6 @@ public class DownloadGroupTarget mEntity = mTaskEntity.entity; } - /** - * 查询任务组实体,如果数据库不存在该实体,则新创建一个新的任务组实体 - */ - private DownloadGroupEntity getDownloadGroupEntity() { - DownloadGroupEntity entity = - DbEntity.findFirst(DownloadGroupEntity.class, "groupName=?", mGroupName); - if (entity == null) { - entity = new DownloadGroupEntity(); - entity.setGroupName(mGroupName); - entity.setUrls(mUrls); - entity.insert(); - } - return entity; - } - - /** - * 设置任务组别名 - */ - public DownloadGroupTarget setGroupAlias(String alias) { - if (TextUtils.isEmpty(alias)) return this; - mEntity.setAlias(alias); - mEntity.update(); - return this; - } - /** * 如果你是使用{@link DownloadReceiver#load(DownloadGroupEntity)}进行下载操作,那么你需要设置任务组的下载地址 */ @@ -107,136 +68,4 @@ public class DownloadGroupTarget mEntity.update(); return this; } - - /** - * 设置任务组的文件夹路径,在Aria中,任务组的所有子任务都会下载到以任务组组名的文件夹中。 - * 如:groupDirPath = "/mnt/sdcard/download/group_test" - *
-   *   {@code
-   *      + mnt
-   *        + sdcard
-   *          + download
-   *            + group_test
-   *              - task1.apk
-   *              - task2.apk
-   *              - task3.apk
-   *              ....
-   *
-   *   }
-   * 
- * - * @param groupDirPath 任务组保存文件夹路径 - */ - public DownloadGroupTarget setDownloadDirPath(String groupDirPath) { - if (TextUtils.isEmpty(groupDirPath)) { - throw new NullPointerException("任务组文件夹保存路径不能为null"); - } - - isSetDirPathed = true; - if (mEntity.getDirPath().equals(groupDirPath)) return this; - - File file = new File(groupDirPath); - if (file.exists() && file.isFile()) { - throw new IllegalArgumentException("路径不能为文件"); - } - if (!file.exists()) { - file.mkdirs(); - } - - mEntity.setDirPath(groupDirPath); - if (!TextUtils.isEmpty(mEntity.getDirPath())) { - reChangeDirPath(groupDirPath); - } else { - mEntity.setSubTasks(createSubTask()); - } - mEntity.update(); - return this; - } - - /** - * 改变任务组文件夹路径,修改文件夹路径会将子任务所有路径更换 - * - * @param newDirPath 新的文件夹路径 - */ - private void reChangeDirPath(String newDirPath) { - List subTask = mEntity.getSubTask(); - if (subTask != null && !subTask.isEmpty()) { - for (DownloadEntity entity : subTask) { - String oldPath = entity.getDownloadPath(); - String newPath = newDirPath + "/" + entity.getFileName(); - File file = new File(oldPath); - file.renameTo(new File(newPath)); - DbEntity.exeSql("UPDATE DownloadEntity SET downloadPath='" - + newPath - + "' WHERE downloadPath='" - + oldPath - + "'"); - DbEntity.exeSql( - "UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'"); - } - } else { - mEntity.setSubTasks(createSubTask()); - } - } - - /** - * 设置子任务文件名,该方法必须在{@link #setDownloadDirPath(String)}之后调用,否则不生效 - */ - public DownloadGroupTarget setSubTaskFileName(List subTaskFileName) { - if (subTaskFileName == null || subTaskFileName.isEmpty()) return this; - mSubTaskFileName.addAll(subTaskFileName); - if (mUrls.size() != subTaskFileName.size()) { - throw new IllegalArgumentException("下载链接数必须要和保存路径的数量一致"); - } - if (isSetDirPathed) { - List entities = mEntity.getSubTask(); - int i = 0; - for (DownloadEntity entity : entities) { - String newName = mSubTaskFileName.get(i); - updateSubFileName(entity, newName); - i++; - } - } - return this; - } - - /** - * 更新子任务文件名 - */ - private void updateSubFileName(DownloadEntity entity, String newName) { - if (!newName.equals(entity.getFileName())) { - String oldPath = mEntity.getDirPath() + "/" + entity.getFileName(); - String newPath = mEntity.getDirPath() + "/" + newName; - File oldFile = new File(oldPath); - if (oldFile.exists()) { - oldFile.renameTo(new File(newPath)); - } - CommonUtil.renameDownloadConfig(oldFile.getName(), newName); - DbEntity.exeSql( - "UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'"); - entity.setDownloadPath(newPath); - entity.setFileName(newName); - entity.update(); - } - } - - /** - * 创建子任务 - */ - private List createSubTask() { - List list = new ArrayList<>(); - for (int i = 0, len = mUrls.size(); i < len; i++) { - DownloadEntity entity = new DownloadEntity(); - entity.setDownloadUrl(mUrls.get(i)); - String fileName = mSubTaskFileName.isEmpty() ? createFileName(entity.getDownloadUrl()) - : mSubTaskFileName.get(i); - entity.setDownloadPath(mEntity.getDirPath() + "/" + fileName); - entity.setGroupName(mGroupName); - entity.setGroupChild(true); - entity.setFileName(fileName); - entity.insert(); - list.add(entity); - } - return list; - } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java index 6c668ad2..a165c564 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java @@ -18,8 +18,10 @@ package com.arialyy.aria.core.download; import android.os.Handler; import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.download.downloader.DownloadGroupUtil; -import com.arialyy.aria.core.download.downloader.IDownloadUtil; +import com.arialyy.aria.core.download.downloader.FtpDirDownloadUtil; +import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.inf.AbsGroupTask; +import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.scheduler.ISchedulers; import com.arialyy.aria.util.CheckUtil; @@ -30,7 +32,7 @@ import com.arialyy.aria.util.CheckUtil; public class DownloadGroupTask extends AbsGroupTask { private final String TAG = "DownloadGroupTask"; private DownloadGroupListener mListener; - private IDownloadUtil mUtil; + private IUtil mUtil; private DownloadGroupTask(DownloadGroupTaskEntity taskEntity, Handler outHandler) { mTaskEntity = taskEntity; @@ -38,33 +40,40 @@ public class DownloadGroupTask extends AbsGroupTask +class DownloadListener extends BaseDListener implements IDownloadListener { DownloadListener(DownloadTask task, Handler outHandler) { super(task, outHandler); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java index ce518da8..c99ac623 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java @@ -24,7 +24,7 @@ import com.arialyy.aria.core.command.normal.NormalCmdFactory; import com.arialyy.aria.core.scheduler.DownloadGroupSchedulers; import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.ISchedulerListener; -import com.arialyy.aria.core.ProxyHelper; +import com.arialyy.aria.core.common.ProxyHelper; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; @@ -58,11 +58,11 @@ public class DownloadReceiver extends AbsReceiver { } /** - * 加载下载地址 + * 加载Http、https单任务下载地址 */ - public DownloadTarget load(@NonNull String downloadUrl) { - CheckUtil.checkDownloadUrl(downloadUrl); - return new DownloadTarget(downloadUrl, targetName); + public DownloadTarget load(@NonNull String url) { + CheckUtil.checkDownloadUrl(url); + return new DownloadTarget(url, targetName); } /** @@ -73,6 +73,14 @@ public class DownloadReceiver extends AbsReceiver { return new DownloadGroupTarget(urls, targetName); } + /** + * 加载ftp单任务下载地址 + */ + public FtpDownloadTarget loadFtp(@NonNull String url) { + CheckUtil.checkDownloadUrl(url); + return new FtpDownloadTarget(url, targetName); + } + /** * 使用任务组实体执行任务组的实体执行任务组的下载操作 * @@ -83,6 +91,14 @@ public class DownloadReceiver extends AbsReceiver { return new DownloadGroupTarget(groupEntity, targetName); } + /** + * 加载ftp文件夹下载地址 + */ + public FtpDirDownloadTarget loadFtpDir(@NonNull String dirUrl) { + CheckUtil.checkDownloadUrl(dirUrl); + return new FtpDirDownloadTarget(dirUrl, targetName); + } + /** * 将当前类注册到Aria */ @@ -147,7 +163,7 @@ public class DownloadReceiver extends AbsReceiver { */ public DownloadEntity getDownloadEntity(String downloadUrl) { CheckUtil.checkDownloadUrl(downloadUrl); - return DbEntity.findFirst(DownloadEntity.class, "downloadUrl=? and isGroupChild='false'", + return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl); } @@ -169,11 +185,20 @@ public class DownloadReceiver extends AbsReceiver { return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", hashCode); } + /** + * 通过任务组key,获取任务组实体 + * 如果是http,key为所有子任务下载地址拼接后取md5 + * 如果是ftp,key为ftp服务器的文件夹路径 + */ + public DownloadGroupTaskEntity getDownloadGroupTask(String key) { + return DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", key); + } + /** * 下载任务是否存在 */ @Override public boolean taskExists(String downloadUrl) { - return DownloadEntity.findFirst(DownloadEntity.class, "downloadUrl=?", downloadUrl) != null; + return DownloadEntity.findFirst(DownloadEntity.class, "url=?", downloadUrl) != null; } /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java index 206e24b4..3d41df0c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java @@ -30,12 +30,13 @@ import java.io.File; */ public class DownloadTarget extends AbsDownloadTarget { - + protected String url; DownloadTarget(DownloadEntity entity, String targetName) { - this(entity.getDownloadUrl(), targetName); + this(entity.getUrl(), targetName); } DownloadTarget(String url, String targetName) { + this.url = url; mTargetName = targetName; DownloadEntity entity = getEntity(url); mTaskEntity = DbEntity.findFirst(DownloadTaskEntity.class, "key=? and isGroupTask='false'", @@ -59,11 +60,11 @@ public class DownloadTarget */ private DownloadEntity getEntity(String downloadUrl) { DownloadEntity entity = - DownloadEntity.findFirst(DownloadEntity.class, "downloadUrl=? and isGroupChild='false'", + DownloadEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl); if (entity == null) { entity = new DownloadEntity(); - entity.setDownloadUrl(downloadUrl); + entity.setUrl(downloadUrl); entity.setGroupChild(false); entity.save(); } @@ -92,7 +93,7 @@ public class DownloadTarget * 下载任务是否存在 */ @Override public boolean taskExists() { - return DownloadTaskQueue.getInstance().getTask(mEntity.getDownloadUrl()) != null; + return DownloadTaskQueue.getInstance().getTask(mEntity.getUrl()) != null; } /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java index de0b2066..78738926 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java @@ -19,6 +19,7 @@ package com.arialyy.aria.core.download; import android.os.Handler; import android.util.Log; import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.download.downloader.SimpleDownloadUtil; import com.arialyy.aria.core.inf.AbsNormalTask; import com.arialyy.aria.core.inf.IEntity; @@ -33,7 +34,7 @@ public class DownloadTask extends AbsNormalTask { public static final String TAG = "DownloadTask"; private DownloadListener mListener; - private SimpleDownloadUtil mUtil; + private IUtil mUtil; private DownloadTask(DownloadTaskEntity taskEntity, Handler outHandler) { mEntity = taskEntity.getEntity(); @@ -62,11 +63,11 @@ public class DownloadTask extends AbsNormalTask { * @see DownloadTask#getKey() */ @Deprecated public String getDownloadUrl() { - return mEntity.getDownloadUrl(); + return mEntity.getUrl(); } @Override public String getKey() { - return getDownloadUrl(); + return mEntity.getUrl(); } /** @@ -75,7 +76,7 @@ public class DownloadTask extends AbsNormalTask { * @see DownloadTask#isRunning() */ @Deprecated public boolean isDownloading() { - return mUtil.isDownloading(); + return mUtil.isRunning(); } @Override public boolean isRunning() { @@ -107,10 +108,10 @@ public class DownloadTask extends AbsNormalTask { */ @Override public void start() { mListener.isWait = false; - if (mUtil.isDownloading()) { + if (mUtil.isRunning()) { Log.d(TAG, "任务正在下载"); } else { - mUtil.startDownload(); + mUtil.start(); } } @@ -123,8 +124,8 @@ public class DownloadTask extends AbsNormalTask { private void stop(boolean isWait) { mListener.isWait = isWait; - if (mUtil.isDownloading()) { - mUtil.stopDownload(); + if (mUtil.isRunning()) { + mUtil.stop(); } else { mEntity.setState(isWait ? IEntity.STATE_WAIT : IEntity.STATE_STOP); mEntity.update(); @@ -138,14 +139,12 @@ public class DownloadTask extends AbsNormalTask { * 取消下载 */ @Override public void cancel() { - if (!mEntity.isComplete()) { - if (!mUtil.isDownloading()) { - if (mOutHandler != null) { - mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget(); - } + if (!mUtil.isRunning()) { + if (mOutHandler != null) { + mOutHandler.obtainMessage(ISchedulers.CANCEL, this).sendToTarget(); } - mUtil.cancelDownload(); } + mUtil.cancel(); } public static class Builder { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskEntity.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskEntity.java index f5b9dbc0..d947d2e4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskEntity.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskEntity.java @@ -16,6 +16,7 @@ package com.arialyy.aria.core.download; import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.orm.Ignore; import com.arialyy.aria.orm.OneToOne; /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java new file mode 100644 index 00000000..2fcca589 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download; + +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.orm.DbEntity; + +/** + * Created by Aria.Lao on 2017/7/26. + * ftp文件夹下载 + */ +public class FtpDirDownloadTarget + extends BaseGroupTarget { + private final String TAG = "FtpDirDownloadTarget"; + private String serverIp, remotePath; + private int port; + + FtpDirDownloadTarget(String url, String targetName) { + init(url); + String[] pp = url.split("/")[2].split(":"); + mTargetName = targetName; + serverIp = pp[0]; + port = Integer.parseInt(pp[1]); + mTaskEntity.requestType = AbsTaskEntity.FTP_DIR; + mTaskEntity.serverIp = serverIp; + mTaskEntity.port = port; + remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length()); + if (TextUtils.isEmpty(remotePath)) { + throw new NullPointerException("ftp服务器地址不能为null"); + } + } + + private void init(String key) { + mGroupName = key; + mTaskEntity = DbEntity.findFirst(DownloadGroupTaskEntity.class, "key=?", key); + if (mTaskEntity == null) { + mTaskEntity = new DownloadGroupTaskEntity(); + mTaskEntity.key = key; + mTaskEntity.entity = getDownloadGroupEntity(); + mTaskEntity.insert(); + } + if (mTaskEntity.entity == null) { + mTaskEntity.entity = getDownloadGroupEntity(); + } + mEntity = mTaskEntity.entity; + } + + /** + * 设置字符编码 + */ + public FtpDirDownloadTarget charSet(String charSet) { + if (TextUtils.isEmpty(charSet)) return this; + mTaskEntity.charSet = charSet; + return this; + } + + /** + * ftp 用户登录信息 + * + * @param userName ftp用户名 + * @param password ftp用户密码 + */ + public FtpDirDownloadTarget login(String userName, String password) { + return login(userName, password, null); + } + + /** + * ftp 用户登录信息 + * + * @param userName ftp用户名 + * @param password ftp用户密码 + * @param account ftp账号 + */ + public FtpDirDownloadTarget login(String userName, String password, String account) { + if (TextUtils.isEmpty(userName)) { + Log.e(TAG, "用户名不能为null"); + return this; + } else if (TextUtils.isEmpty(password)) { + Log.e(TAG, "密码不能为null"); + return this; + } + mTaskEntity.userName = userName; + mTaskEntity.userPw = password; + mTaskEntity.account = account; + return this; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java new file mode 100644 index 00000000..b7dea7cc --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download; + +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; + +/** + * Created by lyy on 2016/12/5. + * https://github.com/AriaLyy/Aria + */ +public class FtpDownloadTarget extends DownloadTarget { + private final String TAG = "FtpDownloadTarget"; + private String serverIp, remotePath; + private int port; + + FtpDownloadTarget(String url, String targetName) { + super(url, targetName); + String[] pp = url.split("/")[2].split(":"); + this.serverIp = pp[0]; + this.port = Integer.parseInt(pp[1]); + mTaskEntity.requestType = AbsTaskEntity.FTP; + remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length()); + if (TextUtils.isEmpty(remotePath)) { + throw new NullPointerException("ftp服务器地址不能为null"); + } + int lastIndex = url.lastIndexOf("/"); + mTaskEntity.serverIp = serverIp; + mTaskEntity.port = port; + mEntity.setFileName(url.substring(lastIndex + 1, url.length())); + } + + /** + * 设置文件保存文件夹路径 + * 关于文件名: + * 1、如果保存路径是该文件的保存路径,如:/mnt/sdcard/file.zip,则使用路径中的文件名file.zip + * 2、如果保存路径是文件夹路径,如:/mnt/sdcard/,则使用FTP服务器该文件的文件名 + * + * @param downloadPath 路径必须为文件路径,不能为文件夹路径 + */ + @Override public FtpDownloadTarget setDownloadPath(@NonNull String downloadPath) { + if (TextUtils.isEmpty(downloadPath)) { + throw new IllegalArgumentException("文件保持路径不能为null"); + } + File file = new File(downloadPath); + if (file.isDirectory()) { + downloadPath += mEntity.getFileName(); + } + if (!downloadPath.equals(mEntity.getDownloadPath())) { + File oldFile = new File(mEntity.getDownloadPath()); + File newFile = new File(downloadPath); + if (TextUtils.isEmpty(mEntity.getDownloadPath()) || oldFile.renameTo(newFile)) { + mEntity.setDownloadPath(downloadPath); + mEntity.setFileName(newFile.getName()); + mTaskEntity.key = downloadPath; + mEntity.update(); + mTaskEntity.update(); + CommonUtil.renameDownloadConfig(oldFile.getName(), newFile.getName()); + } + } + return this; + } + + /** + * 设置字符编码 + */ + public FtpDownloadTarget charSet(String charSet) { + if (TextUtils.isEmpty(charSet)) return this; + mTaskEntity.charSet = charSet; + return this; + } + + /** + * ftp 用户登录信息 + * + * @param userName ftp用户名 + * @param password ftp用户密码 + */ + public FtpDownloadTarget login(String userName, String password) { + return login(userName, password, null); + } + + /** + * ftp 用户登录信息 + * + * @param userName ftp用户名 + * @param password ftp用户密码 + * @param account ftp账号 + */ + public FtpDownloadTarget login(String userName, String password, String account) { + if (TextUtils.isEmpty(userName)) { + Log.e(TAG, "用户名不能为null"); + return this; + } else if (TextUtils.isEmpty(password)) { + Log.e(TAG, "密码不能为null"); + return this; + } + mTaskEntity.userName = userName; + mTaskEntity.userPw = password; + mTaskEntity.account = account; + return this; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsFtpInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsFtpInfoThread.java new file mode 100644 index 00000000..b20909c7 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsFtpInfoThread.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.inf.AbsEntity; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.IOException; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; + +/** + * Created by Aria.Lao on 2017/7/25. + * 获取ftp文件夹信息 + */ +abstract class AbsFtpInfoThread> + implements Runnable { + + private final String TAG = "HttpFileInfoThread"; + protected ENTITY mEntity; + protected TASK_ENTITY mTaskEntity; + private int mConnectTimeOut; + private OnFileInfoCallback mCallback; + + AbsFtpInfoThread(TASK_ENTITY taskEntity, OnFileInfoCallback callback) { + mTaskEntity = taskEntity; + mEntity = taskEntity.getEntity(); + mConnectTimeOut = + AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getConnectTimeOut(); + mCallback = callback; + } + + @Override public void run() { + FTPClient client = null; + try { + String url = mTaskEntity.getEntity().getKey(); + String[] pp = url.split("/")[2].split(":"); + String serverIp = pp[0]; + int port = Integer.parseInt(pp[1]); + String remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length()); + client = new FTPClient(); + client.connect(serverIp, port); + if (!TextUtils.isEmpty(mTaskEntity.account)) { + client.login(mTaskEntity.userName, mTaskEntity.userPw); + } else { + client.login(mTaskEntity.userName, mTaskEntity.userPw, mTaskEntity.account); + } + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + client.disconnect(); + failDownload("无法连接到ftp服务器,错误码为:" + reply); + return; + } + client.setDataTimeout(mConnectTimeOut); + String charSet = "UTF-8"; + // 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码 + if (!TextUtils.isEmpty(mTaskEntity.charSet) || !FTPReply.isPositiveCompletion( + client.sendCommand("OPTS UTF8", "ON"))) { + charSet = mTaskEntity.charSet; + } + client.setControlEncoding(charSet); + client.enterLocalPassiveMode(); + client.setFileType(FTP.BINARY_FILE_TYPE); + FTPFile[] files = + client.listFiles(new String(remotePath.getBytes(charSet), AbsThreadTask.SERVER_CHARSET)); + long size = getFileSize(files, client, remotePath); + mEntity.setFileSize(size); + reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + client.disconnect(); + failDownload("获取文件信息错误,错误码为:" + reply); + return; + } + mTaskEntity.code = reply; + onPreComplete(); + mEntity.update(); + mTaskEntity.update(); + mCallback.onComplete(mEntity.getKey(), reply); + } catch (IOException e) { + failDownload(e.getMessage()); + } finally { + if (client != null) { + try { + client.disconnect(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + void start() { + new Thread(this).start(); + } + + protected void onPreComplete() { + + } + + /** + * 遍历FTP服务器上对应文件或文件夹大小 + * + * @throws IOException + */ + private long getFileSize(FTPFile[] files, FTPClient client, String dirName) throws IOException { + long size = 0; + String path = dirName + "/"; + for (FTPFile file : files) { + if (file.isFile()) { + size += file.getSize(); + handleFile(path + file.getName(), file); + } else { + size += getFileSize(client.listFiles( + CommonUtil.strCharSetConvert(path + file.getName(), mTaskEntity.charSet)), client, + path + file.getName()); + } + } + return size; + } + + /** + * 处理FTP文件信息 + * + * @param remotePath ftp服务器文件夹路径 + * @param ftpFile ftp服务器上对应的文件 + */ + void handleFile(String remotePath, FTPFile ftpFile) { + } + + private void failDownload(String errorMsg) { + Log.e(TAG, errorMsg); + if (mCallback != null) { + mCallback.onFail(mEntity.getKey(), errorMsg); + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsGroupUtil.java new file mode 100644 index 00000000..21a10b8b --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsGroupUtil.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.common.IUtil; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadGroupTaskEntity; +import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.inf.IDownloadListener; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by AriaL on 2017/6/30. + * 任务组核心逻辑 + */ +abstract class AbsGroupUtil implements IUtil { + private final String TAG = "DownloadGroupUtil"; + /** + * 任务组所有任务总大小 + */ + long mTotalSize = 0; + private long mCurrentLocation = 0; + private ExecutorService mExePool; + protected IDownloadGroupListener mListener; + DownloadGroupTaskEntity mTaskEntity; + private boolean isRunning = true; + private Timer mTimer; + /** + * 初始化完成的任务书数 + */ + int mInitNum = 0; + /** + * 初始化失败的任务数 + */ + int mInitFailNum = 0; + /** + * 保存所有没有下载完成的任务,key为下载地址 + */ + Map mExeMap = new HashMap<>(); + + /** + * 下载失败的映射表,key为下载地址 + */ + Map mFailMap = new HashMap<>(); + + /** + * 下载器映射表,key为下载地址 + */ + private Map mDownloaderMap = new HashMap<>(); + + /** + * 该任务组对应的所有任务 + */ + private Map mTasksMap = new HashMap<>(); + //已经完成的任务数 + private int mCompleteNum = 0; + //失败的任务数 + private int mFailNum = 0; + //实际的下载任务数 + int mActualTaskNum = 0; + + AbsGroupUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) { + mListener = listener; + mTaskEntity = taskEntity; + mExePool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + List tasks = + DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key); + if (tasks != null && !tasks.isEmpty()) { + for (DownloadTaskEntity te : tasks) { + mTasksMap.put(te.getEntity().getUrl(), te); + } + } + for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) { + File file = new File(entity.getDownloadPath()); + if (entity.getState() == IEntity.STATE_COMPLETE && file.exists()) { + mCompleteNum++; + mInitNum++; + mCurrentLocation += entity.getFileSize(); + } else { + mExeMap.put(entity.getUrl(), createChildDownloadTask(entity)); + mCurrentLocation += entity.getCurrentProgress(); + mActualTaskNum++; + } + mTotalSize += entity.getFileSize(); + } + } + + @Override public long getFileSize() { + return mTotalSize; + } + + @Override public long getCurrentLocation() { + return mCurrentLocation; + } + + @Override public boolean isRunning() { + return isRunning; + } + + @Override public void cancel() { + closeTimer(false); + mListener.onCancel(); + onCancel(); + if (!mExePool.isShutdown()) { + mExePool.shutdown(); + } + + Set keys = mDownloaderMap.keySet(); + for (String key : keys) { + Downloader dt = mDownloaderMap.get(key); + if (dt != null) { + dt.cancel(); + } + } + delDownloadInfo(); + mTaskEntity.deleteData(); + } + + public void onCancel() { + + } + + /** + * 删除所有子任务的下载信息 + */ + private void delDownloadInfo() { + List tasks = + DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key); + if (tasks == null || tasks.isEmpty()) return; + for (DownloadTaskEntity taskEntity : tasks) { + CommonUtil.delDownloadTaskConfig(taskEntity.removeFile, taskEntity); + } + } + + @Override public void stop() { + closeTimer(false); + mListener.onStop(mCurrentLocation); + onStop(); + if (!mExePool.isShutdown()) { + mExePool.shutdown(); + } + + Set keys = mDownloaderMap.keySet(); + for (String key : keys) { + Downloader dt = mDownloaderMap.get(key); + if (dt != null) { + dt.stop(); + } + } + } + + protected void onStop() { + + } + + @Override public void start() { + isRunning = true; + mFailNum = 0; + mListener.onPre(); + onStart(); + } + + protected void onStart() { + + } + + @Override public void resume() { + start(); + mListener.onResume(mCurrentLocation); + } + + @Override public void setMaxSpeed(double maxSpeed) { + + } + + private void closeTimer(boolean isRunning) { + this.isRunning = isRunning; + if (mTimer != null) { + mTimer.purge(); + mTimer.cancel(); + } + } + + /** + * 开始进度流程 + */ + void startRunningFlow() { + closeTimer(true); + mListener.onPostPre(mTotalSize); + mListener.onStart(mCurrentLocation); + mTimer = new Timer(true); + mTimer.schedule(new TimerTask() { + @Override public void run() { + if (!isRunning) { + closeTimer(false); + } else if (mCurrentLocation >= 0) { + mListener.onProgress(mCurrentLocation); + } + } + }, 0, 1000); + } + + /** + * 启动子任务下载器 + */ + void startChildDownload(DownloadTaskEntity taskEntity) { + ChildDownloadListener listener = new ChildDownloadListener(taskEntity); + Downloader dt = new Downloader(listener, taskEntity); + mDownloaderMap.put(taskEntity.getEntity().getUrl(), dt); + if (mExePool.isShutdown()) return; + mExePool.execute(dt); + } + + /** + * 创建子任务下载信息 + */ + DownloadTaskEntity createChildDownloadTask(DownloadEntity entity) { + DownloadTaskEntity taskEntity = mTasksMap.get(entity.getUrl()); + if (taskEntity != null) { + taskEntity.entity = entity; + //ftp登录的 + taskEntity.userName = mTaskEntity.userName; + taskEntity.userPw = mTaskEntity.userPw; + taskEntity.account = mTaskEntity.account; + return taskEntity; + } + taskEntity = new DownloadTaskEntity(); + taskEntity.entity = entity; + taskEntity.headers = mTaskEntity.headers; + taskEntity.requestEnum = mTaskEntity.requestEnum; + taskEntity.redirectUrlKey = mTaskEntity.redirectUrlKey; + taskEntity.removeFile = mTaskEntity.removeFile; + taskEntity.groupName = mTaskEntity.key; + taskEntity.isGroupTask = true; + taskEntity.requestType = mTaskEntity.requestType; + //ftp登录的 + taskEntity.userName = mTaskEntity.userName; + taskEntity.userPw = mTaskEntity.userPw; + taskEntity.account = mTaskEntity.account; + taskEntity.key = entity.getDownloadPath(); + taskEntity.save(); + return taskEntity; + } + + /** + * 子任务事件监听 + */ + private class ChildDownloadListener implements IDownloadListener { + + DownloadTaskEntity taskEntity; + DownloadEntity entity; + + long lastLen = 0; + + ChildDownloadListener(DownloadTaskEntity entity) { + this.taskEntity = entity; + this.entity = taskEntity.getEntity(); + lastLen = this.entity.getCurrentProgress(); + this.entity.setFailNum(0); + } + + @Override public void onPre() { + saveData(IEntity.STATE_PRE, -1); + } + + @Override public void onPostPre(long fileSize) { + entity.setFileSize(fileSize); + entity.setConvertFileSize(CommonUtil.formatFileSize(fileSize)); + saveData(IEntity.STATE_POST_PRE, -1); + } + + @Override public void onResume(long resumeLocation) { + saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING); + lastLen = resumeLocation; + } + + @Override public void onStart(long startLocation) { + saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING); + lastLen = startLocation; + } + + @Override public void onProgress(long currentLocation) { + long speed = currentLocation - lastLen; + mCurrentLocation += speed; + lastLen = currentLocation; + entity.setCurrentProgress(currentLocation); + handleSpeed(speed); + } + + @Override public void onStop(long stopLocation) { + saveData(IEntity.STATE_STOP, stopLocation); + handleSpeed(0); + mListener.onSubStop(entity); + } + + @Override public void onCancel() { + saveData(IEntity.STATE_CANCEL, -1); + handleSpeed(0); + mListener.onSubCancel(entity); + } + + @Override public void onComplete() { + saveData(IEntity.STATE_COMPLETE, entity.getFileSize()); + mCompleteNum++; + handleSpeed(0); + mListener.onSubComplete(entity); + //如果子任务完成的数量和总任务数一致,表示任务组任务已经完成 + if (mCompleteNum >= mTaskEntity.getEntity().getSubTask().size()) { + closeTimer(false); + mListener.onComplete(); + } else if (mCompleteNum + mFailNum >= mActualTaskNum) { + //如果子任务完成数量加上失败的数量和总任务数一致,则任务组停止下载 + closeTimer(false); + } + } + + @Override public void onFail() { + entity.setFailNum(entity.getFailNum() + 1); + saveData(IEntity.STATE_FAIL, lastLen); + handleSpeed(0); + reTry(); + } + + /** + * 失败后重试下载,如果失败次数超过5次,不再重试 + */ + private void reTry() { + synchronized (AriaManager.LOCK) { + if (entity.getFailNum() < 5 && isRunning) { + reStartTask(); + } else { + mFailNum++; + mListener.onSubFail(entity); + //如果失败的任务数大于实际的下载任务数,任务组停止下载 + if (mFailNum >= mActualTaskNum) { + closeTimer(false); + mListener.onStop(mCurrentLocation); + } + } + } + } + + private void reStartTask() { + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override public void run() { + Downloader dt = mDownloaderMap.get(entity.getUrl()); + dt.start(); + } + }, 3000); + } + + private void handleSpeed(long speed) { + entity.setSpeed(speed); + entity.setConvertSpeed(speed <= 0 ? "" : CommonUtil.formatFileSize(speed) + "/s"); + } + + private void saveData(int state, long location) { + entity.setState(state); + entity.setComplete(state == IEntity.STATE_COMPLETE); + if (entity.isComplete()) { + entity.setCompleteTime(System.currentTimeMillis()); + entity.setCurrentProgress(entity.getFileSize()); + } else if (location > 0) { + entity.setCurrentProgress(location); + } + entity.update(); + } + + @Override public void supportBreakpoint(boolean support) { + + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChildThreadConfigEntity.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChildThreadConfigEntity.java deleted file mode 100644 index 94b865ea..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChildThreadConfigEntity.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.arialyy.aria.core.download.downloader; - -import com.arialyy.aria.core.download.DownloadTaskEntity; -import java.io.File; - -/** - * 子线程下载信息类 - */ -final class ChildThreadConfigEntity { - //线程Id - int THREAD_ID; - //下载文件大小 - long FILE_SIZE; - //子线程启动下载位置 - long START_LOCATION; - //子线程结束下载位置 - long END_LOCATION; - //下载路径 - File TEMP_FILE; - String DOWNLOAD_URL; - String CONFIG_FILE_PATH; - DownloadTaskEntity DOWNLOAD_TASK_ENTITY; - boolean IS_SUPPORT_BREAK_POINT = true; -} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java index 028db87c..a4a2dfd4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java @@ -26,12 +26,14 @@ import java.util.Set; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; +import org.apache.commons.net.ftp.FTPClient; /** * Created by lyy on 2017/1/18. * 链接帮助类 */ class ConnectionHelp { + /** * 处理链接 * diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java index ab3cb6d8..5b3abfbf 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java @@ -16,19 +16,11 @@ package com.arialyy.aria.core.download.downloader; import android.util.SparseArray; -import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.download.DownloadGroupTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.orm.DbEntity; -import com.arialyy.aria.util.CommonUtil; -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -36,153 +28,37 @@ import java.util.concurrent.Executors; * Created by AriaL on 2017/6/30. * 任务组下载工具 */ -public class DownloadGroupUtil implements IDownloadUtil { +public class DownloadGroupUtil extends AbsGroupUtil implements IUtil { private final String TAG = "DownloadGroupUtil"; - /** - * 任务组所有任务总大小 - */ - private long mTotalSize = 0; - private long mCurrentLocation = 0; private ExecutorService mInfoPool; - private ExecutorService mExePool; - private IDownloadGroupListener mListener; - private DownloadGroupTaskEntity mTaskEntity; - private boolean isRunning = true; - private Timer mTimer; - /** - * 初始化完成的任务书数 - */ - private int mInitNum = 0; - /** - * 初始化失败的任务数 - */ - private int mInitFailNum = 0; - /** - * 保存所有没有下载完成的任务,key为下载地址 - */ - private Map mExeMap = new HashMap<>(); - - /** - * 下载失败的映射表,key为下载地址 - */ - private Map mFailMap = new HashMap<>(); - - /** - * 下载器映射表,key为下载地址 - */ - private Map mDownloaderMap = new HashMap<>(); /** * 文件信息回调组 */ - private SparseArray mFileInfoCallbacks = new SparseArray<>(); - /** - * 该任务组对应的所有任务 - */ - private Map mTasksMap = new HashMap<>(); - //已经完成的任务数 - private int mCompleteNum = 0; - //失败的任务数 - private int mFailNum = 0; - //实际的下载任务数 - private int mActualTaskNum = 0; + private SparseArray mFileInfoCallbacks = new SparseArray<>(); public DownloadGroupUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) { - mListener = listener; - mTaskEntity = taskEntity; + super(listener, taskEntity); mInfoPool = Executors.newCachedThreadPool(); - mExePool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - mActualTaskNum = mTaskEntity.entity.getSubTask().size(); - List tasks = - DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key); - if (tasks != null && !tasks.isEmpty()) { - for (DownloadTaskEntity te : tasks) { - mTasksMap.put(te.getEntity().getDownloadUrl(), te); - } - } - for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) { - File file = new File(entity.getDownloadPath()); - if (entity.getState() == IEntity.STATE_COMPLETE && file.exists()) { - mCompleteNum++; - mInitNum++; - mCurrentLocation += entity.getFileSize(); - } else { - mExeMap.put(entity.getDownloadUrl(), createChildDownloadTask(entity)); - mCurrentLocation += entity.getCurrentProgress(); - } - mTotalSize += entity.getFileSize(); - } - } - - @Override public long getFileSize() { - return mTotalSize; - } - - @Override public long getCurrentLocation() { - return mCurrentLocation; - } - - @Override public boolean isDownloading() { - return isRunning; } - @Override public void cancelDownload() { - closeTimer(false); - mListener.onCancel(); + @Override public void onCancel() { + super.onCancel(); if (!mInfoPool.isShutdown()) { mInfoPool.shutdown(); } - if (!mExePool.isShutdown()) { - mExePool.shutdown(); - } - - Set keys = mDownloaderMap.keySet(); - for (String key : keys) { - Downloader dt = mDownloaderMap.get(key); - if (dt != null) { - dt.cancelDownload(); - } - } - delDownloadInfo(); - mTaskEntity.deleteData(); } - /** - * 删除所有子任务的下载信息 - */ - private void delDownloadInfo() { - List tasks = - DbEntity.findDatas(DownloadTaskEntity.class, "groupName=?", mTaskEntity.key); - if (tasks == null || tasks.isEmpty()) return; - for (DownloadTaskEntity taskEntity : tasks) { - CommonUtil.delDownloadTaskConfig(taskEntity.removeFile, taskEntity); - } - } - - @Override public void stopDownload() { - closeTimer(false); - mListener.onStop(mCurrentLocation); + @Override protected void onStop() { + super.onStop(); if (!mInfoPool.isShutdown()) { mInfoPool.shutdown(); } - if (!mExePool.isShutdown()) { - mExePool.shutdown(); - } - - Set keys = mDownloaderMap.keySet(); - for (String key : keys) { - Downloader dt = mDownloaderMap.get(key); - if (dt != null) { - dt.stopDownload(); - } - } } - @Override public void startDownload() { - isRunning = true; - mFailNum = 0; + @Override protected void onStart() { + super.onStart(); Set keys = mExeMap.keySet(); - mListener.onPre(); int i = 0; for (String key : keys) { DownloadTaskEntity taskEntity = mExeMap.get(key); @@ -199,19 +75,14 @@ public class DownloadGroupUtil implements IDownloadUtil { if (i == mExeMap.size()) startRunningFlow(); } - @Override public void resumeDownload() { - startDownload(); - mListener.onResume(mCurrentLocation); - } - /** * 创建文件信息获取线程 */ - private FileInfoThread createFileInfoThread(DownloadTaskEntity taskEntity) { - FileInfoThread.OnFileInfoCallback callback = mFileInfoCallbacks.get(taskEntity.hashCode()); + private HttpFileInfoThread createFileInfoThread(DownloadTaskEntity taskEntity) { + OnFileInfoCallback callback = mFileInfoCallbacks.get(taskEntity.hashCode()); if (callback == null) { - callback = new FileInfoThread.OnFileInfoCallback() { + callback = new OnFileInfoCallback() { int failNum = 0; @Override public void onComplete(String url, int code) { @@ -248,195 +119,6 @@ public class DownloadGroupUtil implements IDownloadUtil { } }; } - return new FileInfoThread(taskEntity, callback); - } - - private void closeTimer(boolean isRunning) { - this.isRunning = isRunning; - if (mTimer != null) { - mTimer.purge(); - mTimer.cancel(); - } - } - - /** - * 开始进度流程 - */ - private void startRunningFlow() { - closeTimer(true); - mListener.onPostPre(mTotalSize); - mListener.onStart(mCurrentLocation); - mTimer = new Timer(true); - mTimer.schedule(new TimerTask() { - @Override public void run() { - if (!isRunning) { - closeTimer(false); - } else if (mCurrentLocation >= 0) { - mListener.onProgress(mCurrentLocation); - } - } - }, 0, 1000); - } - - /** - * 启动子任务下载器 - */ - private void startChildDownload(DownloadTaskEntity taskEntity) { - ChildDownloadListener listener = new ChildDownloadListener(taskEntity); - Downloader dt = new Downloader(listener, taskEntity); - mDownloaderMap.put(taskEntity.getEntity().getDownloadUrl(), dt); - if (mExePool.isShutdown()) return; - mExePool.execute(dt); - } - - /** - * 创建子任务下载信息 - */ - private DownloadTaskEntity createChildDownloadTask(DownloadEntity entity) { - DownloadTaskEntity taskEntity = mTasksMap.get(entity.getDownloadUrl()); - if (taskEntity != null) { - taskEntity.entity = entity; - return taskEntity; - } - taskEntity = new DownloadTaskEntity(); - taskEntity.entity = entity; - taskEntity.headers = mTaskEntity.headers; - taskEntity.requestEnum = mTaskEntity.requestEnum; - taskEntity.redirectUrlKey = mTaskEntity.redirectUrlKey; - taskEntity.removeFile = mTaskEntity.removeFile; - taskEntity.groupName = mTaskEntity.key; - taskEntity.isGroupTask = true; - taskEntity.key = entity.getDownloadPath(); - taskEntity.save(); - return taskEntity; - } - - /** - * 子任务事件监听 - */ - private class ChildDownloadListener implements IDownloadListener { - - DownloadTaskEntity taskEntity; - DownloadEntity entity; - - long lastLen = 0; - - ChildDownloadListener(DownloadTaskEntity entity) { - this.taskEntity = entity; - this.entity = taskEntity.getEntity(); - lastLen = this.entity.getCurrentProgress(); - this.entity.setFailNum(0); - } - - @Override public void onPre() { - saveData(IEntity.STATE_PRE, -1); - } - - @Override public void onPostPre(long fileSize) { - entity.setFileSize(fileSize); - entity.setConvertFileSize(CommonUtil.formatFileSize(fileSize)); - saveData(IEntity.STATE_POST_PRE, -1); - } - - @Override public void onResume(long resumeLocation) { - saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING); - lastLen = resumeLocation; - } - - @Override public void onStart(long startLocation) { - saveData(IEntity.STATE_POST_PRE, IEntity.STATE_RUNNING); - lastLen = startLocation; - } - - @Override public void onProgress(long currentLocation) { - long speed = currentLocation - lastLen; - mCurrentLocation += speed; - lastLen = currentLocation; - entity.setCurrentProgress(currentLocation); - handleSpeed(speed); - } - - @Override public void onStop(long stopLocation) { - saveData(IEntity.STATE_STOP, stopLocation); - handleSpeed(0); - mListener.onSubStop(entity); - } - - @Override public void onCancel() { - saveData(IEntity.STATE_CANCEL, -1); - handleSpeed(0); - mListener.onSubCancel(entity); - } - - @Override public void onComplete() { - saveData(IEntity.STATE_COMPLETE, entity.getFileSize()); - mCompleteNum++; - handleSpeed(0); - mListener.onSubComplete(entity); - //如果子任务完成的数量和总任务数一致,表示任务组任务已经完成 - if (mCompleteNum >= mTaskEntity.getEntity().getSubTask().size()) { - closeTimer(false); - mListener.onComplete(); - } else if (mCompleteNum + mFailNum >= mActualTaskNum) { - //如果子任务完成数量加上失败的数量和总任务数一致,则任务组停止下载 - closeTimer(false); - mListener.onComplete(); - } - } - - @Override public void onFail() { - entity.setFailNum(entity.getFailNum() + 1); - saveData(IEntity.STATE_FAIL, lastLen); - handleSpeed(0); - reTry(); - } - - /** - * 失败后重试下载,如果失败次数超过5次,不再重试 - */ - private void reTry() { - if (entity.getFailNum() < 5) { - reStartTask(); - } else { - mFailNum++; - mListener.onSubFail(entity); - //如果失败的任务数大于实际的下载任务数,任务组停止下载 - if (mFailNum >= mActualTaskNum) { - closeTimer(false); - mListener.onStop(mCurrentLocation); - } - } - } - - private void reStartTask() { - Timer timer = new Timer(); - timer.schedule(new TimerTask() { - @Override public void run() { - Downloader dt = mDownloaderMap.get(entity.getDownloadUrl()); - dt.startDownload(); - } - }, 3000); - } - - private void handleSpeed(long speed) { - entity.setSpeed(speed); - entity.setConvertSpeed(speed <= 0 ? "" : CommonUtil.formatFileSize(speed) + "/s"); - } - - private void saveData(int state, long location) { - entity.setState(state); - entity.setComplete(state == IEntity.STATE_COMPLETE); - if (entity.isComplete()) { - entity.setCompleteTime(System.currentTimeMillis()); - entity.setCurrentProgress(entity.getFileSize()); - } else if (location > 0) { - entity.setCurrentProgress(location); - } - entity.update(); - } - - @Override public void supportBreakpoint(boolean support) { - - } + return new HttpFileInfoThread(taskEntity, callback); } -} +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java index 172731e1..ddfb8c4f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java @@ -15,192 +15,31 @@ */ package com.arialyy.aria.core.download.downloader; -import android.content.Context; -import android.util.Log; -import android.util.SparseArray; import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.common.AbsFileer; +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.CommonUtil; import java.io.File; import java.io.IOException; -import java.util.Properties; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; /** * Created by AriaL on 2017/7/1. * 文件下载器 */ -class Downloader implements Runnable, IDownloadUtil { - private final String TAG = "Downloader"; - private IDownloadListener mListener; - private DownloadTaskEntity mTaskEntity; - private DownloadEntity mEntity; - private ExecutorService mFixedThreadPool; - private File mConfigFile;//下载信息配置文件 - private Context mContext; - private File mTempFile; //下载的文件 - private boolean isNewTask = true; - private int mThreadNum, mRealThreadNum; - private StateConstance mConstance; - private SparseArray mTask = new SparseArray<>(); - - /** - * 小于1m的文件不启用多线程 - */ - private static final long SUB_LEN = 1024 * 1024; - private Timer mTimer; +class Downloader extends AbsFileer { Downloader(IDownloadListener listener, DownloadTaskEntity taskEntity) { - mListener = listener; - mTaskEntity = taskEntity; - mEntity = mTaskEntity.getEntity(); - mContext = AriaManager.APP; - mConstance = new StateConstance(); - } - - void setMaxSpeed(double maxSpeed) { - for (int i = 0; i < mThreadNum; i++) { - SingleThreadTask task = (SingleThreadTask) mTask.get(i); - if (task != null) { - task.setMaxSpeed(maxSpeed); - } - } + super(listener, taskEntity); } - public StateConstance getConstance() { - return mConstance; - } - - @Override public void run() { - startFlow(); - } - - /** - * 开始下载流程 - */ - private void startFlow() { - checkTask(); - mListener.onPostPre(mEntity.getFileSize()); - mConstance.cleanState(); - mConstance.isDownloading = true; - if (!mTaskEntity.isSupportBP) { - mThreadNum = 1; - mConstance.THREAD_NUM = mThreadNum; - handleNoSupportBreakpointDownload(); - } else { - mThreadNum = isNewTask ? (mEntity.getFileSize() <= SUB_LEN ? 1 - : AriaManager.getInstance(mContext).getDownloadConfig().getThreadNum()) : mRealThreadNum; - mConstance.THREAD_NUM = mThreadNum; - mFixedThreadPool = Executors.newFixedThreadPool(mThreadNum); - handleBreakpoint(); - } - startTimer(); - } - - /** - * 启动进度获取定时器 - */ - private void startTimer() { - mTimer = new Timer(true); - mTimer.schedule(new TimerTask() { - @Override public void run() { - if (mConstance.isComplete() || !mConstance.isDownloading) { - closeTimer(); - } else if (mConstance.CURRENT_LOCATION >= 0) { - mListener.onProgress(mConstance.CURRENT_LOCATION); - } - } - }, 0, 1000); - } - - private void closeTimer() { - if (mTimer != null) { - mTimer.purge(); - mTimer.cancel(); - } - } - - @Override public long getFileSize() { - return mEntity.getFileSize(); - } - - /** - * 获取当前下载位置 - */ - @Override public long getCurrentLocation() { - return mConstance.CURRENT_LOCATION; - } - - @Override public boolean isDownloading() { - return mConstance.isDownloading; - } - - @Override public void cancelDownload() { - closeTimer(); - mConstance.isCancel = true; - mConstance.isDownloading = false; - if (mFixedThreadPool != null) { - mFixedThreadPool.shutdown(); - } - for (int i = 0; i < mThreadNum; i++) { - SingleThreadTask task = (SingleThreadTask) mTask.get(i); - if (task != null) { - task.cancel(); - } - } - CommonUtil.delDownloadTaskConfig(mTaskEntity.removeFile, mTaskEntity); - } - - @Override public void stopDownload() { - closeTimer(); - if (mConstance.isComplete()) return; - mConstance.isStop = true; - mConstance.isDownloading = false; - if (mFixedThreadPool != null) { - mFixedThreadPool.shutdown(); - } - for (int i = 0; i < mThreadNum; i++) { - SingleThreadTask task = (SingleThreadTask) mTask.get(i); - if (task != null) { - task.stop(); - } - } - } - - /** - * 直接调用的时候会自动启动线程执行 - */ - @Override public void startDownload() { - new Thread(this).start(); - } - - @Override public void resumeDownload() { - startDownload(); - } - - /** - * 返回该下载器的 - */ - public IDownloadListener getListener() { - return mListener; - } - - /** - * 检查任务是否是新任务,新任务条件: - * 1、文件不存在 - * 2、下载记录文件不存在 - * 3、下载记录文件缺失或不匹配 - * 4、数据库记录不存在 - * 5、不支持断点,则是新任务 - */ - private void checkTask() { + @Override protected void checkTask() { if (!mTaskEntity.isSupportBP) { isNewTask = true; return; @@ -215,7 +54,7 @@ class Downloader implements Runnable, IDownloadUtil { CommonUtil.createFile(mConfigFile.getPath()); } else if (!mTempFile.exists()) { isNewTask = true; - } else if (DbEntity.findFirst(DownloadEntity.class, "downloadUrl=?", mEntity.getDownloadUrl()) + } else if (DbEntity.findFirst(DownloadEntity.class, "url=?", mEntity.getDownloadUrl()) == null) { isNewTask = true; } else { @@ -223,155 +62,16 @@ class Downloader implements Runnable, IDownloadUtil { } } - /** - * 检查记录文件,如果是新任务返回{@code true},否则返回{@code false} - */ - private boolean checkConfigFile() { - Properties pro = CommonUtil.loadConfig(mConfigFile); - if (pro.isEmpty()) { - return true; - } - Set keys = pro.keySet(); - int num = 0; - for (Object key : keys) { - if (String.valueOf(key).contains("_record_")) { - num++; - } - } - if (num == 0) { - return true; - } - mRealThreadNum = num; - for (int i = 0; i < mRealThreadNum; i++) { - if (pro.getProperty(mTempFile.getName() + "_record_" + i) == null) { - Object state = pro.getProperty(mTempFile.getName() + "_state_" + i); - if (state != null && Integer.parseInt(state + "") == 1) { - continue; - } - return true; - } - } - return false; - } - - /** - * 恢复记录地址 - * - * @return true 表示下载完成 - */ - private boolean resumeRecordLocation(int i, long startL, long endL) { - mConstance.CURRENT_LOCATION += endL - startL; - Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); - mConstance.COMPLETE_THREAD_NUM++; - mConstance.STOP_NUM++; - mConstance.CANCEL_NUM++; - if (mConstance.isComplete()) { - if (mConfigFile.exists()) { - mConfigFile.delete(); - } - mListener.onComplete(); - mConstance.isDownloading = false; - return true; - } - return false; - } - - /** - * 创建单线程任务 - */ - private void addSingleTask(int i, long startL, long endL, long fileLength) { - ChildThreadConfigEntity entity = new ChildThreadConfigEntity(); - entity.FILE_SIZE = fileLength; - entity.DOWNLOAD_URL = - mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getDownloadUrl(); - entity.TEMP_FILE = mTempFile; - entity.THREAD_ID = i; - entity.START_LOCATION = startL; - entity.END_LOCATION = endL; - entity.CONFIG_FILE_PATH = mConfigFile.getPath(); - entity.IS_SUPPORT_BREAK_POINT = mTaskEntity.isSupportBP; - entity.DOWNLOAD_TASK_ENTITY = mTaskEntity; - SingleThreadTask task = new SingleThreadTask(mConstance, mListener, entity); - mTask.put(i, task); - } - - /** - * 启动单线程下载任务 - */ - private void startSingleTask(int[] recordL) { - if (mConstance.CURRENT_LOCATION > 0) { - mListener.onResume(mConstance.CURRENT_LOCATION); - } else { - mListener.onStart(mConstance.CURRENT_LOCATION); - } - mFixedThreadPool = Executors.newFixedThreadPool(recordL.length); - for (int l : recordL) { - if (l == -1) continue; - Runnable task = mTask.get(l); - if (task != null) { - mFixedThreadPool.execute(task); - } - } - } - - /** - * 处理断点 - */ - private void handleBreakpoint() { - long fileLength = mEntity.getFileSize(); - Properties pro = CommonUtil.loadConfig(mConfigFile); - int blockSize = (int) (fileLength / mThreadNum); - int[] recordL = new int[mThreadNum]; - for (int i = 0; i < mThreadNum; i++) { - recordL[i] = -1; - } - int rl = 0; - if (isNewTask) { - createNewFile(fileLength); - } - for (int i = 0; i < mThreadNum; i++) { - long startL = i * blockSize, endL = (i + 1) * blockSize; - Object state = pro.getProperty(mTempFile.getName() + "_state_" + i); - if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成 - if (resumeRecordLocation(i, startL, endL)) return; - continue; - } - //分配下载位置 - Object record = pro.getProperty(mTempFile.getName() + "_record_" + i); - //如果有记录,则恢复下载 - if (!isNewTask && record != null && Long.parseLong(record + "") >= 0) { - Long r = Long.parseLong(record + ""); - mConstance.CURRENT_LOCATION += r - startL; - Log.d(TAG, "任务【" + mEntity.getFileName() + "】线程__" + i + "__恢复下载"); - startL = r; - recordL[rl] = i; - rl++; - } else { - recordL[rl] = i; - rl++; - } - if (i == (mThreadNum - 1)) { - //最后一个线程的结束位置即为文件的总长度 - endL = fileLength; - } - addSingleTask(i, startL, endL, fileLength); - } - startSingleTask(recordL); - } - - /** - * 创建新的下载文件 - */ - private void createNewFile(long fileLength) { + @Override protected void handleNewTask() { CommonUtil.createFile(mTempFile.getPath()); BufferedRandomAccessFile file = null; try { file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192); //设置文件长度 - file.setLength(fileLength); + file.setLength(mEntity.getFileSize()); } catch (IOException e) { failDownload("下载失败【downloadUrl:" - + mEntity.getDownloadUrl() + + mEntity.getUrl() + "】\n【filePath:" + mEntity.getDownloadPath() + "】\n" @@ -387,33 +87,14 @@ class Downloader implements Runnable, IDownloadUtil { } } - /** - * 处理不支持断点的下载 - */ - private void handleNoSupportBreakpointDownload() { - ChildThreadConfigEntity entity = new ChildThreadConfigEntity(); - long len = mEntity.getFileSize(); - entity.FILE_SIZE = len; - entity.DOWNLOAD_URL = - mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getDownloadUrl(); - entity.TEMP_FILE = mTempFile; - entity.THREAD_ID = 0; - entity.START_LOCATION = 0; - entity.END_LOCATION = entity.FILE_SIZE; - entity.CONFIG_FILE_PATH = mConfigFile.getPath(); - entity.IS_SUPPORT_BREAK_POINT = mTaskEntity.isSupportBP; - entity.DOWNLOAD_TASK_ENTITY = mTaskEntity; - SingleThreadTask task = new SingleThreadTask(mConstance, mListener, entity); - mTask.put(0, task); - mFixedThreadPool.execute(task); - mListener.onPostPre(len); - mListener.onStart(0); - } - - private void failDownload(String errorMsg) { - closeTimer(); - Log.e(TAG, errorMsg); - mConstance.isDownloading = false; - mListener.onFail(); + @Override protected AbsThreadTask selectThreadTask(SubThreadConfig config) { + switch (mTaskEntity.requestType) { + case AbsTaskEntity.FTP: + case AbsTaskEntity.FTP_DIR: + return new FtpThreadTask(mConstance, (IDownloadListener) mListener, config); + case AbsTaskEntity.HTTP: + return new HttpThreadTask(mConstance, (IDownloadListener) mListener, config); + } + return null; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpClientHelp.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpClientHelp.java new file mode 100644 index 00000000..338d114b --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpClientHelp.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.AriaManager; +import java.io.IOException; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; + +/** + * Created by Aria.Lao on 2017/7/26. + */ +public class FtpClientHelp { + private final String TAG = "FtpClientHelp"; + private static volatile FtpClientHelp INSTANCE = null; + + private FTPClient client; + private String serverIp, user, pw, account; + private int port; + + private FtpClientHelp() { + } + + public static FtpClientHelp getInstnce() { + if (INSTANCE == null) { + synchronized (AriaManager.LOCK) { + INSTANCE = new FtpClientHelp(); + } + } + return INSTANCE; + } + + public FTPClient getClient() { + if (client == null || !client.isConnected()) { + createClient(); + } + return client; + } + + /** + * 登录到FTP服务器,当客户端为null或客户端没有连接到FTP服务器时才会执行登录操作 + */ + public FTPClient login(String serverIp, int port, String user, String pw, String account) { + this.serverIp = serverIp; + this.port = port; + this.user = user; + this.pw = pw; + this.account = account; + if (client == null || !client.isConnected()) { + createClient(); + } + return client; + } + + /** + * 登出 + */ + public void logout() { + try { + if (client != null && client.isConnected()) { + client.logout(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + FTPClient createClient() { + new Thread(new Runnable() { + @Override public void run() { + client = new FTPClient(); + try { + client.connect(serverIp, port); + if (!TextUtils.isEmpty(account)) { + client.login(user, pw); + } else { + client.login(user, pw, account); + } + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + client.disconnect(); + Log.e(TAG, "无法连接到ftp服务器,错误码为:" + reply); + } + } catch (IOException e) { + Log.d(TAG, e.getMessage()); + } finally { + synchronized (FtpClientHelp.this) { + FtpClientHelp.this.notify(); + } + } + } + }).start(); + synchronized (FtpClientHelp.this) { + try { + wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + return client; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirDownloadUtil.java new file mode 100644 index 00000000..9c5c7ce8 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirDownloadUtil.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadGroupTaskEntity; +import com.arialyy.aria.core.download.DownloadTaskEntity; +import java.util.Set; + +/** + * Created by Aria.Lao on 2017/7/27. + * ftp文件夹下载工具 + */ +public class FtpDirDownloadUtil extends AbsGroupUtil { + public FtpDirDownloadUtil(IDownloadGroupListener listener, DownloadGroupTaskEntity taskEntity) { + super(listener, taskEntity); + } + + @Override protected void onStart() { + super.onStart(); + if (mTaskEntity.getEntity().getFileSize() > 1) { + startDownload(); + } else { + new FtpDirInfoThread(mTaskEntity, new OnFileInfoCallback() { + @Override public void onComplete(String url, int code) { + if (code >= 200 && code < 300) { + mTotalSize = mTaskEntity.getEntity().getFileSize(); + for (DownloadEntity entity : mTaskEntity.entity.getSubTask()) { + mExeMap.put(entity.getUrl(), createChildDownloadTask(entity)); + } + mActualTaskNum = mTaskEntity.entity.getSubTask().size(); + startDownload(); + } + } + + @Override public void onFail(String url, String errorMsg) { + mListener.onFail(); + } + }).start(); + } + } + + private void startDownload() { + int i = 0; + Set keys = mExeMap.keySet(); + for (String key : keys) { + DownloadTaskEntity taskEntity = mExeMap.get(key); + if (taskEntity != null) { + startChildDownload(taskEntity); + i++; + } + } + if (i == mExeMap.size()) startRunningFlow(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirInfoThread.java new file mode 100644 index 00000000..963639de --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirInfoThread.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.download.DownloadGroupTaskEntity; +import com.arialyy.aria.util.CommonUtil; +import java.nio.charset.Charset; +import java.util.ArrayList; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Created by Aria.Lao on 2017/7/25. + * 获取ftp文件夹信息 + */ +class FtpDirInfoThread extends AbsFtpInfoThread { + private long mSize = 0; + + FtpDirInfoThread(DownloadGroupTaskEntity taskEntity, OnFileInfoCallback callback) { + super(taskEntity, callback); + } + + @Override void handleFile(String remotePath, FTPFile ftpFile) { + super.handleFile(remotePath, ftpFile); + mSize += ftpFile.getSize(); + addEntity(remotePath, ftpFile); + } + + @Override protected void onPreComplete() { + super.onPreComplete(); + mEntity.setFileSize(mSize); + } + + private void addEntity(String remotePath, FTPFile ftpFile) { + DownloadEntity entity = new DownloadEntity(); + entity.setUrl("ftp://" + mTaskEntity.serverIp + ":" + mTaskEntity.port + remotePath); + entity.setDownloadPath(mEntity.getDirPath() + "/" + remotePath); + int lastIndex = remotePath.lastIndexOf("/"); + String fileName = lastIndex < 0 ? CommonUtil.keyToHashKey(remotePath) + : remotePath.substring(lastIndex + 1, remotePath.length()); + entity.setFileName(new String(fileName.getBytes(), Charset.forName(mTaskEntity.charSet))); + entity.setGroupName(mEntity.getGroupName()); + entity.setGroupChild(true); + entity.setFileSize(ftpFile.getSize()); + entity.insert(); + if (mEntity.getUrls() == null) { + mEntity.setUrls(new ArrayList()); + } + if (mEntity.getSubTask() == null) { + mEntity.setSubTasks(new ArrayList()); + } + mEntity.getSubTask().add(entity); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDownloadUtil.java deleted file mode 100644 index 2ddf525e..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDownloadUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.arialyy.aria.core.download.downloader; - -/** - * Created by Aria.Lao on 2017/7/21. - */ -public class FtpDownloadUtil implements IDownloadUtil, Runnable{ - - - @Override public long getFileSize() { - return 0; - } - - @Override public long getCurrentLocation() { - return 0; - } - - @Override public boolean isDownloading() { - return false; - } - - @Override public void cancelDownload() { - - } - - @Override public void stopDownload() { - - } - - @Override public void startDownload() { - - } - - @Override public void resumeDownload() { - - } - - @Override public void run() { - - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java new file mode 100644 index 00000000..c83db279 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadTaskEntity; + +/** + * Created by Aria.Lao on 2017/7/25. + * 获取ftp文件信息 + */ +class FtpFileInfoThread extends AbsFtpInfoThread { + + FtpFileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) { + super(taskEntity, callback); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java new file mode 100644 index 00000000..1272e05b --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.StateConstance; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.inf.IDownloadListener; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; + +/** + * Created by Aria.Lao on 2017/7/24. + * Ftp下载任务 + */ +class FtpThreadTask extends AbsThreadTask { + private final String TAG = "FtpThreadTask"; + + FtpThreadTask(StateConstance constance, IDownloadListener listener, + SubThreadConfig downloadInfo) { + super(constance, listener, downloadInfo); + } + + @Override public void run() { + FTPClient client = null; + InputStream is = null; + BufferedRandomAccessFile file = null; + try { + Log.d(TAG, "任务【" + + mConfig.TEMP_FILE.getName() + + "】线程__" + + mConfig.THREAD_ID + + "__开始下载【开始位置 : " + + mConfig.START_LOCATION + + ",结束位置:" + + mConfig.END_LOCATION + + "】"); + String url = mEntity.getUrl(); + String[] pp = url.split("/")[2].split(":"); + String serverIp = pp[0]; + int port = Integer.parseInt(pp[1]); + String remotePath = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length()); + client = new FTPClient(); + client.connect(serverIp, port); + if (!TextUtils.isEmpty(mTaskEntity.account)) { + client.login(mTaskEntity.userName, mTaskEntity.userPw); + } else { + client.login(mTaskEntity.userName, mTaskEntity.userPw, mTaskEntity.account); + } + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + client.disconnect(); + fail(STATE.CURRENT_LOCATION, "无法连接到ftp服务器,错误码为:" + reply, null); + return; + } + String charSet = "UTF-8"; + // 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码 + if (!TextUtils.isEmpty(mTaskEntity.charSet) || !FTPReply.isPositiveCompletion( + client.sendCommand("OPTS UTF8", "ON"))) { + charSet = mTaskEntity.charSet; + } + client.setControlEncoding(charSet); + client.setDataTimeout(STATE.READ_TIME_OUT); + client.enterLocalPassiveMode(); + client.setFileType(FTP.BINARY_FILE_TYPE); + client.setRestartOffset(mConfig.START_LOCATION); + client.allocate(mBufSize); + is = client.retrieveFileStream( + new String(remotePath.getBytes(charSet), SERVER_CHARSET)); + //发送第二次指令时,还需要再做一次判断 + reply = client.getReplyCode(); + if (!FTPReply.isPositivePreliminary(reply)) { + client.disconnect(); + fail(mChildCurrentLocation, "获取文件信息错误,错误码为:" + reply, null); + return; + } + file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize); + file.seek(mConfig.START_LOCATION); + byte[] buffer = new byte[mBufSize]; + int len; + //当前子线程的下载位置 + mChildCurrentLocation = mConfig.START_LOCATION; + while ((len = is.read(buffer)) != -1) { + if (STATE.isCancel) break; + if (STATE.isStop) break; + if (mSleepTime > 0) Thread.sleep(mSleepTime); + if (mChildCurrentLocation + len >= mConfig.END_LOCATION) { + len = (int) (mConfig.END_LOCATION - mChildCurrentLocation); + file.write(buffer, 0, len); + progress(len); + break; + } else { + file.write(buffer, 0, len); + progress(len); + } + } + if (STATE.isCancel || STATE.isStop) return; + Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__下载完毕"); + writeConfig(true, 1); + STATE.COMPLETE_THREAD_NUM++; + if (STATE.isComplete()) { + File configFile = new File(mConfigFPath); + if (configFile.exists()) { + configFile.delete(); + } + STATE.isRunning = false; + mListener.onComplete(); + } + } catch (IOException e) { + fail(mChildCurrentLocation, "下载失败【" + mConfig.URL + "】", e); + } catch (Exception e) { + fail(mChildCurrentLocation, "获取流失败", e); + } finally { + try { + if (file != null) { + file.close(); + } + if (is != null) { + is.close(); + } + if (client != null && client.isConnected()) { + client.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java similarity index 81% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/FileInfoThread.java rename to Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java index 71fbbc27..275000eb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FileInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java @@ -20,6 +20,7 @@ import android.util.Log; import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.download.downloader.ConnectionHelp; import com.arialyy.aria.util.CommonUtil; import java.io.IOException; import java.net.HttpURLConnection; @@ -29,30 +30,14 @@ import java.net.URLDecoder; /** * 下载文件信息获取 */ -class FileInfoThread implements Runnable { - private final String TAG = "FileInfoThread"; +class HttpFileInfoThread implements Runnable { + private final String TAG = "HttpFileInfoThread"; private DownloadEntity mEntity; private DownloadTaskEntity mTaskEntity; private int mConnectTimeOut; private OnFileInfoCallback onFileInfoListener; - interface OnFileInfoCallback { - /** - * 处理完成 - * - * @param code 状态码 - */ - void onComplete(String url, int code); - - /** - * 请求失败 - * - * @param errorMsg 错误信息 - */ - void onFail(String url, String errorMsg); - } - - FileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) { + HttpFileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) { this.mTaskEntity = taskEntity; mEntity = taskEntity.getEntity(); mConnectTimeOut = @@ -63,7 +48,7 @@ class FileInfoThread implements Runnable { @Override public void run() { HttpURLConnection conn = null; try { - URL url = new URL(mEntity.getDownloadUrl()); + URL url = new URL(mEntity.getUrl()); conn = ConnectionHelp.handleConnection(url); conn = ConnectionHelp.setConnectParam(mTaskEntity, conn); conn.setRequestProperty("Range", "bytes=" + 0 + "-"); @@ -72,7 +57,7 @@ class FileInfoThread implements Runnable { handleConnect(conn); } catch (IOException e) { failDownload("下载失败【downloadUrl:" - + mEntity.getDownloadUrl() + + mEntity.getUrl() + "】\n【filePath:" + mEntity.getDownloadPath() + "】\n" @@ -87,7 +72,8 @@ class FileInfoThread implements Runnable { private void handleConnect(HttpURLConnection conn) throws IOException { long len = conn.getContentLength(); if (len < 0) { - len = Long.parseLong(conn.getHeaderField(mTaskEntity.contentLength)); + String temp = conn.getHeaderField(mTaskEntity.contentLength); + len = TextUtils.isEmpty(temp) ? -1 : Long.parseLong(temp); } int code = conn.getResponseCode(); boolean isComplete = false; @@ -117,7 +103,7 @@ class FileInfoThread implements Runnable { mTaskEntity.isSupportBP = false; isComplete = true; } else if (code == HttpURLConnection.HTTP_NOT_FOUND) { - failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,错误码:404"); + failDownload("任务【" + mEntity.getUrl() + "】下载失败,错误码:404"); } else if (code == HttpURLConnection.HTTP_MOVED_TEMP || code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_SEE_OTHER) { @@ -126,11 +112,11 @@ class FileInfoThread implements Runnable { mEntity.setRedirectUrl(mTaskEntity.redirectUrl); handle302Turn(conn); } else { - failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,错误码:" + code); + failDownload("任务【" + mEntity.getUrl() + "】下载失败,错误码:" + code); } if (isComplete) { if (onFileInfoListener != null) { - onFileInfoListener.onComplete(mEntity.getDownloadUrl(), code); + onFileInfoListener.onComplete(mEntity.getUrl(), code); } mEntity.update(); mTaskEntity.update(); @@ -145,7 +131,7 @@ class FileInfoThread implements Runnable { Log.d(TAG, "30x跳转,location【 " + mTaskEntity.redirectUrlKey + "】" + "新url为【" + newUrl + "】"); if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) { if (onFileInfoListener != null) { - onFileInfoListener.onFail(mEntity.getDownloadUrl(), "获取重定向链接失败"); + onFileInfoListener.onFail(mEntity.getUrl(), "获取重定向链接失败"); } return; } @@ -168,7 +154,7 @@ class FileInfoThread implements Runnable { */ private boolean checkLen(long len) { if (len < 0) { - failDownload("任务【" + mEntity.getDownloadUrl() + "】下载失败,文件长度小于0"); + failDownload("任务【" + mEntity.getUrl() + "】下载失败,文件长度小于0"); return false; } return true; @@ -177,7 +163,7 @@ class FileInfoThread implements Runnable { private void failDownload(String errorMsg) { Log.e(TAG, errorMsg); if (onFileInfoListener != null) { - onFileInfoListener.onFail(mEntity.getDownloadUrl(), errorMsg); + onFileInfoListener.onFail(mEntity.getUrl(), errorMsg); } } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java new file mode 100644 index 00000000..09d76523 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.download.downloader; + +import android.util.Log; +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.StateConstance; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.inf.IDownloadListener; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Created by lyy on 2017/1/18. + * 下载线程 + */ +final class HttpThreadTask extends AbsThreadTask { + private final String TAG = "HttpThreadTask"; + + HttpThreadTask(StateConstance constance, IDownloadListener listener, + SubThreadConfig downloadInfo) { + super(constance, listener, downloadInfo); + } + + @Override public void run() { + HttpURLConnection conn = null; + InputStream is = null; + BufferedRandomAccessFile file = null; + try { + URL url = new URL(mConfig.URL); + conn = ConnectionHelp.handleConnection(url); + if (mConfig.SUPPORT_BP) { + Log.d(TAG, "任务【" + + mConfig.TEMP_FILE.getName() + + "】线程__" + + mConfig.THREAD_ID + + "__开始下载【开始位置 : " + + mConfig.START_LOCATION + + ",结束位置:" + + mConfig.END_LOCATION + + "】"); + //在头里面请求下载开始位置和结束位置 + conn.setRequestProperty("Range", + "bytes=" + mConfig.START_LOCATION + "-" + (mConfig.END_LOCATION - 1)); + } else { + Log.w(TAG, "该下载不支持断点"); + } + conn = ConnectionHelp.setConnectParam(mConfig.TASK_ENTITY, conn); + conn.setConnectTimeout(STATE.CONNECT_TIME_OUT); + conn.setReadTimeout(STATE.READ_TIME_OUT); //设置读取流的等待时间,必须设置该参数 + is = conn.getInputStream(); + //创建可设置位置的文件 + file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize); + //设置每条线程写入文件的位置 + file.seek(mConfig.START_LOCATION); + byte[] buffer = new byte[mBufSize]; + int len; + //当前子线程的下载位置 + mChildCurrentLocation = mConfig.START_LOCATION; + while ((len = is.read(buffer)) != -1) { + if (STATE.isCancel) break; + if (STATE.isStop) break; + if (mSleepTime > 0) Thread.sleep(mSleepTime); + file.write(buffer, 0, len); + progress(len); + } + if (STATE.isCancel || STATE.isStop) return; + //支持断点的处理 + if (mConfig.SUPPORT_BP) { + Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__下载完毕"); + writeConfig(true, 1); + STATE.COMPLETE_THREAD_NUM++; + if (STATE.isComplete()) { + File configFile = new File(mConfigFPath); + if (configFile.exists()) { + configFile.delete(); + } + STATE.isRunning = false; + mListener.onComplete(); + } + } else { + Log.i(TAG, "下载任务完成"); + STATE.isRunning = false; + mListener.onComplete(); + } + } catch (MalformedURLException e) { + fail(mChildCurrentLocation, "下载链接异常", e); + } catch (IOException e) { + fail(mChildCurrentLocation, "下载失败【" + mConfig.URL + "】", e); + } catch (Exception e) { + fail(mChildCurrentLocation, "获取流失败", e); + } finally { + try { + if (file != null) { + file.close(); + } + if (is != null) { + is.close(); + } + if (conn != null) { + conn.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadGroupListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadGroupListener.java index 2b0f130f..e0b0695f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadGroupListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadGroupListener.java @@ -16,13 +16,14 @@ package com.arialyy.aria.core.download.downloader; import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IEventListener; /** * Created by Aria.Lao on 2017/7/20. * 下载任务组事件 */ -public interface IDownloadGroupListener extends IEventListener { +public interface IDownloadGroupListener extends IDownloadListener { /** * 子任务支持断点回调 diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/OnFileInfoCallback.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/OnFileInfoCallback.java new file mode 100644 index 00000000..9e2071e2 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/OnFileInfoCallback.java @@ -0,0 +1,17 @@ +package com.arialyy.aria.core.download.downloader; + +interface OnFileInfoCallback { + /** + * 处理完成 + * + * @param code 状态码 + */ + void onComplete(String url, int code); + + /** + * 请求失败 + * + * @param errorMsg 错误信息 + */ + void onFail(String url, String errorMsg); +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java index bd22f7aa..62236c33 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java @@ -16,89 +16,110 @@ package com.arialyy.aria.core.download.downloader; -import android.text.TextUtils; -import android.util.Log; +import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.download.DownloadTaskEntity; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.core.inf.IDownloadListener; /** * Created by lyy on 2015/8/25. - * 简单的下载工具 + * HTTP单任务下载工具 */ -public class SimpleDownloadUtil implements IDownloadUtil, Runnable { +public class SimpleDownloadUtil implements IUtil, Runnable { private static final String TAG = "SimpleDownloadUtil"; private IDownloadListener mListener; - private Downloader mDT; + private Downloader mDownloader; private DownloadTaskEntity mTaskEntity; public SimpleDownloadUtil(DownloadTaskEntity entity, IDownloadListener downloadListener) { mTaskEntity = entity; mListener = downloadListener; - mDT = new Downloader(downloadListener, entity); + mDownloader = new Downloader(downloadListener, entity); } @Override public long getFileSize() { - return mDT.getFileSize(); + return mDownloader.getFileSize(); } /** * 获取当前下载位置 */ @Override public long getCurrentLocation() { - return mDT.getCurrentLocation(); + return mDownloader.getCurrentLocation(); } - @Override public boolean isDownloading() { - return mDT.isDownloading(); + @Override public boolean isRunning() { + return mDownloader.isRunning(); } /** * 取消下载 */ - @Override public void cancelDownload() { - mDT.cancelDownload(); + @Override public void cancel() { + mDownloader.cancel(); } /** * 停止下载 */ - @Override public void stopDownload() { - mDT.stopDownload(); + @Override public void stop() { + mDownloader.stop(); } /** * 多线程断点续传下载文件,开始下载 */ - @Override public void startDownload() { - mListener.onPre(); + @Override public void start() { new Thread(this).start(); } - @Override public void resumeDownload() { - startDownload(); + @Override public void resume() { + start(); } public void setMaxSpeed(double maxSpeed) { - mDT.setMaxSpeed(maxSpeed); + mDownloader.setMaxSpeed(maxSpeed); } private void failDownload(String msg) { - Log.e(TAG, msg); mListener.onFail(); } @Override public void run() { - if (TextUtils.isEmpty(mTaskEntity.redirectUrl)) { - new Thread(new FileInfoThread(mTaskEntity, new FileInfoThread.OnFileInfoCallback() { - @Override public void onComplete(String url, int code) { - mDT.startDownload(); - } - - @Override public void onFail(String url, String errorMsg) { - failDownload(errorMsg); - } - })).start(); + mListener.onPre(); + if (mTaskEntity.getEntity().getFileSize() <= 1) { + new Thread(createInfoThread()).start(); } else { - mDT.startDownload(); + mDownloader.start(); + } + } + + /** + * 通过链接类型创建不同的获取文件信息的线程 + */ + private Runnable createInfoThread() { + switch (mTaskEntity.requestType) { + case AbsTaskEntity.FTP: + return new FtpFileInfoThread(mTaskEntity, new OnFileInfoCallback() { + @Override public void onComplete(String url, int code) { + mDownloader.start(); + } + + @Override public void onFail(String url, String errorMsg) { + failDownload(errorMsg); + } + }); + case AbsTaskEntity.HTTP: + return new HttpFileInfoThread(mTaskEntity, new OnFileInfoCallback() { + @Override public void onComplete(String url, int code) { + mDownloader.start(); + } + + @Override public void onFail(String url, String errorMsg) { + failDownload(errorMsg); + } + }); } + return null; } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SingleThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SingleThreadTask.java deleted file mode 100644 index ea7666c2..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SingleThreadTask.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) - * - * Licensed 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 com.arialyy.aria.core.download.downloader; - -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.util.BufferedRandomAccessFile; -import com.arialyy.aria.util.CommonUtil; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Properties; - -/** - * Created by lyy on 2017/1/18. - * 下载线程 - */ -final class SingleThreadTask implements Runnable { - private static final String TAG = "SingleThreadTask"; - private ChildThreadConfigEntity mConfigEntity; - private String mConfigFPath; - private long mChildCurrentLocation = 0; - private int mBufSize; - private IDownloadListener mListener; - private StateConstance CONSTANCE; - private long mSleepTime = 0; - - SingleThreadTask(StateConstance constance, IDownloadListener listener, - ChildThreadConfigEntity downloadInfo) { - AriaManager manager = AriaManager.getInstance(AriaManager.APP); - CONSTANCE = constance; - CONSTANCE.CONNECT_TIME_OUT = manager.getDownloadConfig().getConnectTimeOut(); - CONSTANCE.READ_TIME_OUT = manager.getDownloadConfig().getIOTimeOut(); - mListener = listener; - this.mConfigEntity = downloadInfo; - if (mConfigEntity.IS_SUPPORT_BREAK_POINT) { - mConfigFPath = downloadInfo.CONFIG_FILE_PATH; - } - mBufSize = manager.getDownloadConfig().getBuffSize(); - setMaxSpeed(AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getMsxSpeed()); - } - - void setMaxSpeed(double maxSpeed) { - if (-0.9999 < maxSpeed && maxSpeed < 0.00001) { - mSleepTime = 0; - } else { - BigDecimal db = new BigDecimal( - ((mBufSize / 1024) * (filterVersion() ? 1 : CONSTANCE.THREAD_NUM) / maxSpeed) * 1000); - mSleepTime = db.setScale(0, BigDecimal.ROUND_HALF_UP).longValue(); - } - } - - private boolean filterVersion() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - } - - @Override public void run() { - HttpURLConnection conn = null; - InputStream is = null; - BufferedRandomAccessFile file = null; - try { - URL url = new URL(mConfigEntity.DOWNLOAD_URL); - conn = ConnectionHelp.handleConnection(url); - if (mConfigEntity.IS_SUPPORT_BREAK_POINT) { - Log.d(TAG, "任务【" - + mConfigEntity.TEMP_FILE.getName() - + "】线程__" - + mConfigEntity.THREAD_ID - + "__开始下载【开始位置 : " - + mConfigEntity.START_LOCATION - + ",结束位置:" - + mConfigEntity.END_LOCATION - + "】"); - //在头里面请求下载开始位置和结束位置 - conn.setRequestProperty("Range", - "bytes=" + mConfigEntity.START_LOCATION + "-" + (mConfigEntity.END_LOCATION - 1)); - } else { - Log.w(TAG, "该下载不支持断点"); - } - conn = ConnectionHelp.setConnectParam(mConfigEntity.DOWNLOAD_TASK_ENTITY, conn); - conn.setConnectTimeout(CONSTANCE.CONNECT_TIME_OUT); - conn.setReadTimeout(CONSTANCE.READ_TIME_OUT); //设置读取流的等待时间,必须设置该参数 - is = conn.getInputStream(); - //创建可设置位置的文件 - file = new BufferedRandomAccessFile(mConfigEntity.TEMP_FILE, "rwd", mBufSize); - //设置每条线程写入文件的位置 - file.seek(mConfigEntity.START_LOCATION); - byte[] buffer = new byte[mBufSize]; - int len; - //当前子线程的下载位置 - mChildCurrentLocation = mConfigEntity.START_LOCATION; - while ((len = is.read(buffer)) != -1) { - if (CONSTANCE.isCancel) break; - if (CONSTANCE.isStop) break; - if (mSleepTime > 0) Thread.sleep(mSleepTime); - file.write(buffer, 0, len); - progress(len); - } - if (CONSTANCE.isCancel) return; - //停止状态不需要删除记录文件 - if (CONSTANCE.isStop) return; - //支持断点的处理 - if (mConfigEntity.IS_SUPPORT_BREAK_POINT) { - Log.i(TAG, "任务【" - + mConfigEntity.TEMP_FILE.getName() - + "】线程__" - + mConfigEntity.THREAD_ID - + "__下载完毕"); - writeConfig(true, 1); - CONSTANCE.COMPLETE_THREAD_NUM++; - if (CONSTANCE.isComplete()) { - File configFile = new File(mConfigFPath); - if (configFile.exists()) { - configFile.delete(); - } - CONSTANCE.isDownloading = false; - mListener.onComplete(); - } - } else { - Log.i(TAG, "下载任务完成"); - CONSTANCE.isDownloading = false; - mListener.onComplete(); - } - } catch (MalformedURLException e) { - failDownload(mChildCurrentLocation, "下载链接异常", e); - } catch (IOException e) { - failDownload(mChildCurrentLocation, "下载失败【" + mConfigEntity.DOWNLOAD_URL + "】", e); - } catch (Exception e) { - failDownload(mChildCurrentLocation, "获取流失败", e); - } finally { - try { - if (file != null) { - file.close(); - } - if (is != null) { - is.close(); - } - if (conn != null) { - conn.disconnect(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 停止下载 - */ - protected void stop() { - synchronized (AriaManager.LOCK) { - try { - if (mConfigEntity.IS_SUPPORT_BREAK_POINT) { - CONSTANCE.STOP_NUM++; - Log.d(TAG, "任务【" - + mConfigEntity.TEMP_FILE.getName() - + "】thread__" - + mConfigEntity.THREAD_ID - + "__停止, stop location ==> " - + mChildCurrentLocation); - writeConfig(false, mChildCurrentLocation); - if (CONSTANCE.isStop()) { - Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已停止"); - CONSTANCE.isDownloading = false; - mListener.onStop(CONSTANCE.CURRENT_LOCATION); - } - } else { - Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已停止"); - CONSTANCE.isDownloading = false; - mListener.onStop(CONSTANCE.CURRENT_LOCATION); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 下载中 - */ - private void progress(long len) { - synchronized (AriaManager.LOCK) { - mChildCurrentLocation += len; - CONSTANCE.CURRENT_LOCATION += len; - } - } - - /** - * 取消下载 - */ - protected void cancel() { - synchronized (AriaManager.LOCK) { - if (mConfigEntity.IS_SUPPORT_BREAK_POINT) { - CONSTANCE.CANCEL_NUM++; - Log.d(TAG, "任务【" - + mConfigEntity.TEMP_FILE.getName() - + "】thread__" - + mConfigEntity.THREAD_ID - + "__取消下载"); - if (CONSTANCE.isCancel()) { - File configFile = new File(mConfigFPath); - if (configFile.exists()) { - configFile.delete(); - } - if (mConfigEntity.TEMP_FILE.exists()) { - mConfigEntity.TEMP_FILE.delete(); - } - Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已取消"); - CONSTANCE.isDownloading = false; - mListener.onCancel(); - } - } else { - Log.d(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】已取消"); - CONSTANCE.isDownloading = false; - mListener.onCancel(); - } - } - } - - /** - * 下载失败 - */ - private void failDownload(long currentLocation, String msg, Exception ex) { - synchronized (AriaManager.LOCK) { - try { - CONSTANCE.FAIL_NUM++; - CONSTANCE.isDownloading = false; - CONSTANCE.isStop = true; - if (ex != null) { - Log.e(TAG, msg + "\n" + CommonUtil.getPrintException(ex)); - } - if (mConfigEntity.IS_SUPPORT_BREAK_POINT) { - writeConfig(false, currentLocation); - if (CONSTANCE.isFail()) { - Log.e(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】下载失败"); - mListener.onFail(); - } - } else { - Log.e(TAG, "任务【" + mConfigEntity.TEMP_FILE.getName() + "】下载失败"); - mListener.onFail(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 将记录写入到配置文件 - */ - private void writeConfig(boolean isComplete, long record) throws IOException { - synchronized (AriaManager.LOCK) { - String key = null, value = null; - if (0 < record && record < mConfigEntity.END_LOCATION) { - key = mConfigEntity.TEMP_FILE.getName() + "_record_" + mConfigEntity.THREAD_ID; - value = String.valueOf(record); - } else if (record >= mConfigEntity.END_LOCATION || isComplete) { - key = mConfigEntity.TEMP_FILE.getName() + "_state_" + mConfigEntity.THREAD_ID; - value = "1"; - } - if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) { - File configFile = new File(mConfigFPath); - Properties pro = CommonUtil.loadConfig(configFile); - pro.setProperty(key, value); - CommonUtil.saveConfig(configFile, pro); - } - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java index d1425f7f..5f03f29a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java @@ -25,6 +25,11 @@ import com.arialyy.aria.orm.Ignore; */ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { + /** + * 服务器地址 + */ + private String url = ""; + /** * 文件名 */ @@ -35,6 +40,16 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { */ private boolean isGroupChild = false; + private boolean isRedirect = false; //是否重定向 + private String redirectUrl = ""; //重定向链接 + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } public boolean isGroupChild() { return isGroupChild; @@ -52,6 +67,22 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { this.fileName = fileName; } + public boolean isRedirect() { + return isRedirect; + } + + public void setRedirect(boolean redirect) { + isRedirect = redirect; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + public AbsNormalEntity() { } @@ -61,13 +92,19 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); + dest.writeString(this.url); dest.writeString(this.fileName); dest.writeByte(this.isGroupChild ? (byte) 1 : (byte) 0); + dest.writeByte(this.isRedirect ? (byte) 1 : (byte) 0); + dest.writeString(this.redirectUrl); } protected AbsNormalEntity(Parcel in) { super(in); + this.url = in.readString(); this.fileName = in.readString(); this.isGroupChild = in.readByte() != 0; + this.isRedirect = in.readByte() != 0; + this.redirectUrl = in.readString(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java index 41d39541..42c91bbe 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java @@ -19,7 +19,7 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.RequestEnum; +import com.arialyy.aria.core.common.RequestEnum; import com.arialyy.aria.core.command.normal.NormalCmdFactory; import com.arialyy.aria.util.CommonUtil; import java.util.Map; @@ -157,7 +157,7 @@ public abstract class AbsTarget extends DbEntity { + /** + * HTTP下载 + */ + public static final int HTTP = 0x11; + /** + * FTP当文件下载 + */ + public static final int FTP = 0x12; + /** + * FTP文件夹下载,为避免登录过多,子任务由单线程进行处理 + */ + public static final int FTP_DIR = 0x13; /** * Task实体对应的key */ @Primary public String key = ""; + /** + * 账号和密码 + */ + @Ignore public String userName, userPw, account, serverIp; + @Ignore public int port; + + /** + * 请求类型 + * {@link AbsTaskEntity#HTTP}、{@link AbsTaskEntity#FTP} + */ + public int requestType = HTTP; + /** * http 请求头 */ public Map headers = new HashMap<>(); + /** + * 字符编码,默认为"utf-8" + */ + public String charSet = "utf-8"; + /** * 网络请求类型 */ diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java index 5422007c..ad4ddee3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java @@ -15,12 +15,66 @@ */ package com.arialyy.aria.core.inf; +import android.support.annotation.NonNull; +import com.arialyy.aria.core.queue.UploadTaskQueue; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.core.upload.UploadTaskEntity; +import com.arialyy.aria.util.CheckUtil; +import java.util.regex.Pattern; + /** * Created by AriaL on 2017/6/29. * 任务组超类 */ -public abstract class AbsUploadTarget +public abstract class AbsUploadTarget extends AbsTarget { + /** + * 设置上传路径 + * + * @param uploadUrl 上传路径 + */ + public TARGET setUploadUrl(@NonNull String uploadUrl) { + CheckUtil.checkDownloadUrl(uploadUrl); + if (mEntity.getUrl().equals(uploadUrl)) return (TARGET) this; + mEntity.setUrl(uploadUrl); + mEntity.update(); + return (TARGET) this; + } + + /** + * 从数据中读取上传实体,如果数据库查不到,则新创建一个上传实体 + * + * @param filePath 上传文件的文件路径 + */ + protected UploadEntity getUploadEntity(String filePath) { + UploadEntity entity = UploadEntity.findFirst(UploadEntity.class, "filePath=?", filePath); + if (entity == null) { + entity = new UploadEntity(); + String regex = "[/|\\\\|//]"; + Pattern p = Pattern.compile(regex); + String[] strs = p.split(filePath); + String fileName = strs[strs.length - 1]; + entity.setFileName(fileName); + entity.setFilePath(filePath); + entity.insert(); + } + return entity; + } + + /** + * 下载任务是否存在 + */ + @Override public boolean taskExists() { + return UploadTaskQueue.getInstance().getTask(mEntity.getFilePath()) != null; + } + /** + * 是否在下载 + */ + public boolean isUploading() { + UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity); + return task != null && task.isRunning(); + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadListener.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java similarity index 86% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadListener.java rename to Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java index 131c8779..d5cec1df 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/IDownloadListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java @@ -14,15 +14,18 @@ * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; - -import com.arialyy.aria.core.inf.IEventListener; +package com.arialyy.aria.core.inf; /** * 下载监听 */ public interface IDownloadListener extends IEventListener { + /** + * 预处理完成,准备下载---开始下载之间 + */ + void onPostPre(long fileSize); + /** * 支持断点回调 * diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java index c9842fac..edce5282 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java @@ -26,11 +26,6 @@ public interface IEventListener { */ void onPre(); - /** - * 预处理完成,准备下载---开始下载之间 - */ - void onPostPre(long fileSize); - /** * 开始 */ diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java index 6a12071b..527e8078 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java @@ -16,7 +16,7 @@ package com.arialyy.aria.core.inf; import android.support.annotation.NonNull; -import com.arialyy.aria.core.RequestEnum; +import com.arialyy.aria.core.common.RequestEnum; import java.util.Map; /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/IUploadListener.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java similarity index 88% rename from Aria/src/main/java/com/arialyy/aria/core/upload/IUploadListener.java rename to Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java index 2a8cab14..3387c49d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/IUploadListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload; +package com.arialyy.aria.core.inf; import com.arialyy.aria.core.inf.IEventListener; @@ -21,6 +21,6 @@ import com.arialyy.aria.core.inf.IEventListener; * Created by lyy on 2017/2/9. * 上传监听 */ -interface IUploadListener extends IEventListener { +public interface IUploadListener extends IEventListener { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java b/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java index 3eda8f38..b96f2ea1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java @@ -60,7 +60,7 @@ public class DownloadTaskQueue } @Override public String getKey(DownloadEntity entity) { - return entity.getDownloadUrl(); + return entity.getUrl(); } @Override public int getConfigMaxNum() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulerListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulerListener.java index 2be98fe3..b8128680 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulerListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulerListener.java @@ -25,45 +25,45 @@ public interface ISchedulerListener { * 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的UI,如按钮的状态。 * 在这个回调中,任务是获取不到文件大小,下载速度等参数 */ - public void onPre(TASK task); + void onPre(TASK task); /** * 任务预加载完成 */ - public void onTaskPre(TASK task); + void onTaskPre(TASK task); /** * 任务恢复下载 */ - public void onTaskResume(TASK task); + void onTaskResume(TASK task); /** * 任务开始 */ - public void onTaskStart(TASK task); + void onTaskStart(TASK task); /** * 任务停止 */ - public void onTaskStop(TASK task); + void onTaskStop(TASK task); /** * 任务取消 */ - public void onTaskCancel(TASK task); + void onTaskCancel(TASK task); /** * 任务下载失败 */ - public void onTaskFail(TASK task); + void onTaskFail(TASK task); /** * 任务完成 */ - public void onTaskComplete(TASK task); + void onTaskComplete(TASK task); /** * 任务执行中 */ - public void onTaskRunning(TASK task); + void onTaskRunning(TASK task); } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java b/Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java new file mode 100644 index 00000000..299726e4 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.upload; + +import android.os.Handler; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.inf.AbsEntity; +import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.inf.IUploadListener; +import com.arialyy.aria.core.scheduler.ISchedulers; +import com.arialyy.aria.util.CommonUtil; +import java.lang.ref.WeakReference; + +/** + * 下载监听类 + */ +class BaseUListener> + implements IUploadListener { + private WeakReference outHandler; + private long mLastLen = 0; //上一次发送长度 + private boolean isFirst = true; + protected ENTITY mEntity; + protected TASK mTask; + private boolean isConvertSpeed = false; + boolean isWait = false; + + BaseUListener(TASK task, Handler outHandler) { + this.outHandler = new WeakReference<>(outHandler); + this.mTask = new WeakReference<>(task).get(); + this.mEntity = this.mTask.getEntity(); + final AriaManager manager = AriaManager.getInstance(AriaManager.APP); + isConvertSpeed = manager.getDownloadConfig().isConvertSpeed(); + mLastLen = mEntity.getCurrentProgress(); + } + + @Override public void onPre() { + saveData(IEntity.STATE_PRE, -1); + sendInState2Target(ISchedulers.PRE); + } + + @Override public void onStart(long startLocation) { + saveData(IEntity.STATE_RUNNING, startLocation); + sendInState2Target(ISchedulers.START); + } + + @Override public void onResume(long resumeLocation) { + saveData(IEntity.STATE_RUNNING, resumeLocation); + sendInState2Target(ISchedulers.RESUME); + } + + @Override public void onProgress(long currentLocation) { + mEntity.setCurrentProgress(currentLocation); + long speed = currentLocation - mLastLen; + if (isFirst) { + speed = 0; + isFirst = false; + } + handleSpeed(speed); + sendInState2Target(ISchedulers.RUNNING); + mLastLen = currentLocation; + } + + @Override public void onStop(long stopLocation) { + saveData(isWait ? IEntity.STATE_WAIT : IEntity.STATE_STOP, stopLocation); + handleSpeed(0); + sendInState2Target(ISchedulers.STOP); + } + + @Override public void onCancel() { + saveData(IEntity.STATE_CANCEL, -1); + handleSpeed(0); + sendInState2Target(ISchedulers.CANCEL); + } + + @Override public void onComplete() { + saveData(IEntity.STATE_COMPLETE, mEntity.getFileSize()); + handleSpeed(0); + sendInState2Target(ISchedulers.COMPLETE); + } + + @Override public void onFail() { + mEntity.setFailNum(mEntity.getFailNum() + 1); + saveData(IEntity.STATE_FAIL, mEntity.getCurrentProgress()); + handleSpeed(0); + sendInState2Target(ISchedulers.FAIL); + } + + private void handleSpeed(long speed) { + if (isConvertSpeed) { + mEntity.setConvertSpeed(CommonUtil.formatFileSize(speed < 0 ? 0 : speed) + "/s"); + } else { + mEntity.setSpeed(speed < 0 ? 0 : speed); + } + } + + /** + * 将任务状态发送给下载器 + * + * @param state {@link ISchedulers#START} + */ + private void sendInState2Target(int state) { + if (outHandler.get() != null) { + outHandler.get().obtainMessage(state, mTask).sendToTarget(); + } + } + + private void saveData(int state, long location) { + mEntity.setComplete(state == IEntity.STATE_COMPLETE); + if (state == IEntity.STATE_CANCEL) { + mEntity.deleteData(); + } else if (state == IEntity.STATE_COMPLETE) { + mEntity.setState(state); + mEntity.setCompleteTime(System.currentTimeMillis()); + mEntity.setCurrentProgress(mEntity.getFileSize()); + mEntity.update(); + } else { + mEntity.setState(state); + if (location != -1) { + mEntity.setCurrentProgress(location); + } + mEntity.update(); + } + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java new file mode 100644 index 00000000..a0cea376 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.upload; + +import android.text.TextUtils; +import android.util.Log; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.core.inf.AbsUploadTarget; +import com.arialyy.aria.orm.DbEntity; +import java.io.File; + +/** + * Created by Aria.Lao on 2017/7/27. + * ftp单任务上传 + */ +public class FtpUploadTarget + extends AbsUploadTarget { + private final String TAG = "FtpUploadTarget"; + + FtpUploadTarget(String filePath, String targetName) { + this.mTargetName = targetName; + mTaskEntity = DbEntity.findFirst(UploadTaskEntity.class, "key=?", filePath); + if (mTaskEntity == null) { + mTaskEntity = new UploadTaskEntity(); + mTaskEntity.entity = getUploadEntity(filePath); + } + if (mTaskEntity.entity == null) { + mTaskEntity.entity = getUploadEntity(filePath); + } + mTaskEntity.requestType = AbsTaskEntity.FTP; + mEntity = mTaskEntity.entity; + File file = new File(filePath); + mEntity.setFileName(file.getName()); + mEntity.setFileSize(file.length()); + } + + /** + * ftp 用户登录信息 + * + * @param userName ftp用户名 + * @param password ftp用户密码 + */ + public FtpUploadTarget login(String userName, String password) { + return login(userName, password, null); + } + + /** + * ftp 用户登录信息 + * + * @param userName ftp用户名 + * @param password ftp用户密码 + * @param account ftp账号 + */ + public FtpUploadTarget login(String userName, String password, String account) { + if (TextUtils.isEmpty(userName)) { + Log.e(TAG, "用户名不能为null"); + return this; + } else if (TextUtils.isEmpty(password)) { + Log.e(TAG, "密码不能为null"); + return this; + } + mTaskEntity.userName = userName; + mTaskEntity.userPw = password; + mTaskEntity.account = account; + return this; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java index 05bfc5cf..c21b32b4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java @@ -59,7 +59,7 @@ public class UploadEntity extends AbsNormalEntity implements Parcelable { this.filePath = in.readString(); } - @Ignore public static final Creator CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { @Override public UploadEntity createFromParcel(Parcel source) { return new UploadEntity(source); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java index a4c432ae..bf43c7bd 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java @@ -15,6 +15,8 @@ */ package com.arialyy.aria.core.upload; +import com.arialyy.aria.core.inf.IUploadListener; + /** * Created by lyy on 2017/2/23. */ @@ -23,10 +25,6 @@ class UploadListener implements IUploadListener { } - @Override public void onPostPre(long fileSize) { - - } - @Override public void onStart(long startLocation) { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java index 9df0a8b9..8b4f4f6b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java @@ -17,7 +17,7 @@ package com.arialyy.aria.core.upload; import android.support.annotation.NonNull; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.ProxyHelper; +import com.arialyy.aria.core.common.ProxyHelper; import com.arialyy.aria.core.command.normal.NormalCmdFactory; import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.inf.AbsReceiver; @@ -35,24 +35,33 @@ import java.util.Set; * 上传功能接收器 */ public class UploadReceiver extends AbsReceiver { - private static final String TAG = "DownloadReceiver"; + private static final String TAG = "UploadReceiver"; public ISchedulerListener listener; /** - * 加载任务 + * 加载HTTP单文件上传任务 * - * @param filePath 文件地址 + * @param filePath 文件路径 */ public UploadTarget load(@NonNull String filePath) { CheckUtil.checkUploadPath(filePath); return new UploadTarget(filePath, targetName); } + /** + * 加载FTP单文件上传任务 + * + * @param filePath 文件路径 + */ + public FtpUploadTarget loadFtp(@NonNull String filePath) { + CheckUtil.checkUploadPath(filePath); + return new FtpUploadTarget(filePath, targetName); + } + /** * 通过上传路径获取上传实体 */ public UploadEntity getUploadEntity(String filePath) { - CheckUtil.checkUploadPath(filePath); return DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java index b6ef2f1d..094f3c36 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java @@ -20,10 +20,12 @@ import com.arialyy.aria.core.inf.AbsDownloadTarget; import com.arialyy.aria.core.inf.AbsUploadTarget; import com.arialyy.aria.core.queue.UploadTaskQueue; import com.arialyy.aria.orm.DbEntity; +import java.io.File; import java.util.regex.Pattern; /** * Created by lyy on 2017/2/28. + * http 当文件上传 */ public class UploadTarget extends AbsUploadTarget { @@ -38,21 +40,11 @@ public class UploadTarget extends AbsUploadTarget { + private final String TAG = "FtpThreadTask"; + private String dir, remotePath, charSet; + + FtpThreadTask(StateConstance constance, IEventListener listener, + SubThreadConfig info) { + super(constance, listener, info); + } + + @Override public void run() { + FTPClient client = null; + OutputStream os = null; + BufferedRandomAccessFile file = null; + try { + Log.d(TAG, "任务【" + + mConfig.TEMP_FILE.getName() + + "】线程__" + + mConfig.THREAD_ID + + "__开始上传【开始位置 : " + + mConfig.START_LOCATION + + ",结束位置:" + + mConfig.END_LOCATION + + "】"); + //当前子线程的下载位置 + mChildCurrentLocation = mConfig.START_LOCATION; + client = createClient(); + if (client == null) return; + client.makeDirectory(dir); + client.changeWorkingDirectory(dir); + client.setRestartOffset(mConfig.START_LOCATION); + file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize); + file.seek(mConfig.START_LOCATION); + if (!isRemoteComplete(client)) { + os = client.storeFileStream(new String(remotePath.getBytes(charSet), SERVER_CHARSET)); + //发送第二次指令时,还需要再做一次判断 + int reply = client.getReplyCode(); + if (!FTPReply.isPositivePreliminary(reply)) { + client.disconnect(); + fail(mChildCurrentLocation, "上传文件错误,错误码为:" + reply, null); + return; + } + upload(file, os); + } + if (STATE.isCancel || STATE.isStop) return; + Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__上传完毕"); + writeConfig(true, 1); + STATE.COMPLETE_THREAD_NUM++; + if (STATE.isComplete()) { + File configFile = new File(mConfigFPath); + if (configFile.exists()) { + configFile.delete(); + } + STATE.isRunning = false; + mListener.onComplete(); + } + } catch (IOException e) { + fail(mChildCurrentLocation, "上传失败【" + mConfig.URL + "】", e); + } catch (Exception e) { + fail(mChildCurrentLocation, "获取流失败", e); + } finally { + try { + if (file != null) { + file.close(); + } + if (os != null) { + os.close(); + } + if (client != null && client.isConnected()) { + client.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 远程文件是否已经玩飞车 + * + * @return true 任务已经完成 + */ + private boolean isRemoteComplete(FTPClient client) throws IOException { + FTPFile[] files = client.listFiles(new String(remotePath.getBytes(charSet), SERVER_CHARSET)); + return files.length != 0 && files[0].getSize() == mEntity.getFileSize(); + } + + /** + * 执行上传操作 + */ + private void upload(BufferedRandomAccessFile file, OutputStream os) + throws IOException, InterruptedException { + int len; + byte[] buffer = new byte[mBufSize]; + while ((len = file.read(buffer)) != -1) { + if (STATE.isCancel) break; + if (STATE.isStop) break; + if (mSleepTime > 0) Thread.sleep(mSleepTime); + if (mChildCurrentLocation + len >= mConfig.END_LOCATION) { + len = (int) (mConfig.END_LOCATION - mChildCurrentLocation); + os.write(buffer, 0, len); + progress(len); + break; + } else { + os.write(buffer, 0, len); + progress(len); + } + } + } + + /** + * 构建FTP客户端 + */ + private FTPClient createClient() throws IOException { + String url = mEntity.getUrl(); + String[] pp = url.split("/")[2].split(":"); + String serverIp = pp[0]; + int port = Integer.parseInt(pp[1]); + dir = url.substring(url.indexOf(pp[1]) + pp[1].length(), url.length()); + remotePath = dir + "/" + mEntity.getFileName(); + FTPClient client = new FTPClient(); + client.connect(serverIp, port); + if (!TextUtils.isEmpty(mTaskEntity.account)) { + client.login(mTaskEntity.userName, mTaskEntity.userPw); + } else { + client.login(mTaskEntity.userName, mTaskEntity.userPw, mTaskEntity.account); + } + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + client.disconnect(); + fail(STATE.CURRENT_LOCATION, "无法连接到ftp服务器,错误码为:" + reply, null); + return null; + } + charSet = "UTF-8"; + // 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码 + if (!TextUtils.isEmpty(mTaskEntity.charSet) || !FTPReply.isPositiveCompletion( + client.sendCommand("OPTS UTF8", "ON"))) { + charSet = mTaskEntity.charSet; + } + client.setControlEncoding(charSet); + client.setDataTimeout(STATE.READ_TIME_OUT); + client.enterLocalPassiveMode(); + client.setFileType(FTP.BINARY_FILE_TYPE); + client.allocate(mBufSize); + return client; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java similarity index 79% rename from Aria/src/main/java/com/arialyy/aria/core/upload/UploadUtil.java rename to Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java index c0ee07d7..c6f2db22 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java @@ -13,10 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload; +package com.arialyy.aria.core.upload.uploader; import android.util.Log; -import com.arialyy.aria.util.CheckUtil; +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.StateConstance; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.inf.IUploadListener; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.upload.UploadTaskEntity; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -32,55 +37,34 @@ import java.util.Set; import java.util.UUID; /** - * Created by lyy on 2017/2/9. - * 上传工具 + * Created by Aria.Lao on 2017/7/28. + * 不支持断点的HTTP上传任务 */ -final class UploadUtil implements Runnable { - private static final String TAG = "UploadUtil"; +class HttpThreadTask extends AbsThreadTask { + private final String TAG = "HttpThreadTask"; + private final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成 private final String PREFIX = "--", LINE_END = "\r\n"; - private UploadEntity mUploadEntity; - private UploadTaskEntity mTaskEntity; - private IUploadListener mListener; private HttpURLConnection mHttpConn; private long mCurrentLocation = 0; - private boolean isCancel = false; - private boolean isRunning = false; private OutputStream mOutputStream; - UploadUtil(UploadTaskEntity taskEntity, IUploadListener listener) { - mTaskEntity = taskEntity; - CheckUtil.checkTaskEntity(taskEntity); - mUploadEntity = taskEntity.getEntity(); - if (listener == null) { - throw new IllegalArgumentException("上传监听不能为空"); - } - mListener = listener; - } - - public void start() { - isCancel = false; - isRunning = false; - new Thread(this).start(); - } - - public void cancel() { - isCancel = true; - isRunning = false; + HttpThreadTask(StateConstance constance, IUploadListener listener, + SubThreadConfig uploadInfo) { + super(constance, listener, uploadInfo); } @Override public void run() { - File uploadFile = new File(mUploadEntity.getFilePath()); + File uploadFile = new File(mEntity.getFilePath()); if (!uploadFile.exists()) { - Log.e(TAG, "【" + mUploadEntity.getFilePath() + "】,文件不存在。"); + Log.e(TAG, "【" + mEntity.getFilePath() + "】,文件不存在。"); fail(); return; } - mListener.onPre(); URL url; try { - url = new URL(mTaskEntity.uploadUrl); + url = new URL(mEntity.getUrl()); mHttpConn = (HttpURLConnection) url.openConnection(); mHttpConn.setUseCaches(false); mHttpConn.setDoOutput(true); @@ -97,19 +81,14 @@ final class UploadUtil implements Runnable { for (String key : keys) { mHttpConn.setRequestProperty(key, mTaskEntity.headers.get(key)); } - mOutputStream = mHttpConn.getOutputStream(); - mListener.onPostPre(uploadFile.length()); - PrintWriter writer = - new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskEntity.charset), true); - + new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskEntity.charSet), true); //添加文件上传表单字段 keys = mTaskEntity.formFields.keySet(); for (String key : keys) { addFormField(writer, key, mTaskEntity.formFields.get(key)); } - mListener.onStart(0); uploadFile(writer, mTaskEntity.attachment, uploadFile); Log.d(TAG, finish(writer) + ""); } catch (IOException e) { @@ -118,13 +97,10 @@ final class UploadUtil implements Runnable { } } - boolean isRunning() { - return isRunning; - } - private void fail() { try { mListener.onFail(); + STATE.isRunning = false; if (mOutputStream != null) { mOutputStream.close(); } @@ -143,7 +119,7 @@ final class UploadUtil implements Runnable { .append("\"") .append(LINE_END); writer.append("Content-Type: text/plain; charset=") - .append(mTaskEntity.charset) + .append(mTaskEntity.charSet) .append(LINE_END); writer.append(LINE_END); writer.append(value).append(LINE_END); @@ -178,10 +154,9 @@ final class UploadUtil implements Runnable { while ((bytesRead = inputStream.read(buffer)) != -1) { mCurrentLocation += bytesRead; mOutputStream.write(buffer, 0, bytesRead); - if (isCancel) { + if (STATE.isCancel) { break; } - isRunning = true; mListener.onProgress(mCurrentLocation); } @@ -190,12 +165,13 @@ final class UploadUtil implements Runnable { inputStream.close(); writer.append(LINE_END); writer.flush(); - isRunning = false; - if (isCancel) { + if (STATE.isCancel) { mListener.onCancel(); + STATE.isRunning = false; return; } mListener.onComplete(); + STATE.isRunning = false; } /** @@ -221,7 +197,7 @@ final class UploadUtil implements Runnable { mHttpConn.disconnect(); } else { Log.w(TAG, "state_code = " + status); - mListener.onFail(); + fail(); } writer.flush(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java new file mode 100644 index 00000000..634f616c --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.upload.uploader; + +import com.arialyy.aria.core.common.IUtil; +import com.arialyy.aria.core.inf.IUploadListener; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.upload.UploadTaskEntity; +import com.arialyy.aria.util.CheckUtil; + +/** + * Created by lyy on 2017/2/9. + * 简单的文件上传工具 + */ +public class SimpleUploadUtil implements IUtil, Runnable { + private static final String TAG = "SimpleUploadUtil"; + + private UploadEntity mUploadEntity; + private UploadTaskEntity mTaskEntity; + private IUploadListener mListener; + private Uploader mUploader; + + public SimpleUploadUtil(UploadTaskEntity taskEntity, IUploadListener listener) { + mTaskEntity = taskEntity; + CheckUtil.checkTaskEntity(taskEntity); + mUploadEntity = taskEntity.getEntity(); + if (listener == null) { + throw new IllegalArgumentException("上传监听不能为空"); + } + mListener = listener; + mUploader = new Uploader(mListener, taskEntity); + } + + @Override public void run() { + mListener.onPre(); + mUploader.start(); + } + + @Override public long getFileSize() { + return mUploader.getFileSize(); + } + + @Override public long getCurrentLocation() { + return mUploader.getCurrentLocation(); + } + + @Override public boolean isRunning() { + return mUploader.isRunning(); + } + + @Override public void cancel() { + mUploader.cancel(); + } + + @Override public void stop() { + mUploader.stop(); + } + + @Override public void start() { + new Thread(this).start(); + } + + @Override public void resume() { + mUploader.cancel(); + } + + @Override public void setMaxSpeed(double maxSpeed) { + mUploader.setMaxSpeed(maxSpeed); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java new file mode 100644 index 00000000..8ef19e82 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.aria.core.upload.uploader; + +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.common.AbsFileer; +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.inf.AbsTaskEntity; +import com.arialyy.aria.core.inf.IUploadListener; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.upload.UploadTaskEntity; +import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; + +/** + * Created by Aria.Lao on 2017/7/27. + * 文件上传器 + */ +class Uploader extends AbsFileer { + + Uploader(IUploadListener listener, UploadTaskEntity taskEntity) { + super(listener, taskEntity); + mTempFile = new File(mEntity.getFilePath()); + } + + /** + * 检查任务是否是新任务,新任务条件: + * 1、文件不存在 + * 2、记录文件不存在 + * 3、记录文件缺失或不匹配 + * 4、数据库记录不存在 + * 5、不支持断点,则是新任务 + */ + protected void checkTask() { + mConfigFile = new File(mContext.getFilesDir().getPath() + + AriaManager.UPLOAD_TEMP_DIR + + mEntity.getFileName() + + ".properties"); + if (!mTaskEntity.isSupportBP) { + isNewTask = true; + return; + } + if (!mConfigFile.exists()) { //记录文件被删除,则重新下载 + isNewTask = true; + CommonUtil.createFile(mConfigFile.getPath()); + } else if (DbEntity.findFirst(UploadEntity.class, "filePath=?", mEntity.getFilePath()) + == null) { + isNewTask = true; + } else { + isNewTask = checkConfigFile(); + } + } + + @Override protected void handleNewTask() { + + } + + @Override protected int getNewTaskThreadNum() { + return 1; + } + + @Override protected AbsThreadTask selectThreadTask(SubThreadConfig config) { + switch (mTaskEntity.requestType) { + case AbsTaskEntity.FTP: + case AbsTaskEntity.FTP_DIR: + return new FtpThreadTask(mConstance, mListener, config); + case AbsTaskEntity.HTTP: + return new HttpThreadTask(mConstance, (IUploadListener) mListener, config); + } + return null; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java b/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java index 11b6f2d5..1738293c 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java +++ b/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java @@ -32,7 +32,7 @@ import java.util.Map; class DBConfig { static Map mapping = new HashMap<>(); static String DB_NAME; - static int VERSION = 11; + static int VERSION = 12; static { if (TextUtils.isEmpty(DB_NAME)) { diff --git a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java index 34c54227..745d2826 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java @@ -24,6 +24,7 @@ import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadTaskEntity; import com.arialyy.aria.exception.FileException; +import java.io.File; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -77,6 +78,14 @@ public class CheckUtil { if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("下载链接不能为null"); } + /** + * 检测下载链接是否为null + */ + public static void checkUploadUrl(String downloadUrl) { + if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("上传地址不能为null"); + } + + /** * 检测下载链接组是否为null */ @@ -100,6 +109,8 @@ public class CheckUtil { */ public static void checkUploadPath(String uploadPath) { if (TextUtils.isEmpty(uploadPath)) throw new IllegalArgumentException("上传地址不能为null"); + File file = new File(uploadPath); + if (!file.exists()) throw new IllegalArgumentException("上传文件不存在"); } /** @@ -125,7 +136,7 @@ public class CheckUtil { DownloadEntity entity1 = ((DownloadTaskEntity) entity).getEntity(); if (entity1 == null) { Log.e(TAG, "下载实体不能为空"); - } else if (checkType && TextUtils.isEmpty(entity1.getDownloadUrl())) { + } else if (checkType && TextUtils.isEmpty(entity1.getUrl())) { Log.e(TAG, "下载链接不能为空"); } else if (checkType && TextUtils.isEmpty(entity1.getDownloadPath())) { Log.e(TAG, "保存路径不能为空"); @@ -167,7 +178,7 @@ public class CheckUtil { private static void checkDownloadTaskEntity(DownloadEntity entity) { if (entity == null) { throw new NullPointerException("下载实体不能为空"); - } else if (TextUtils.isEmpty(entity.getDownloadUrl())) { + } else if (TextUtils.isEmpty(entity.getUrl())) { throw new IllegalArgumentException("下载链接不能为空"); } else if (TextUtils.isEmpty(entity.getFileName())) { throw new FileException("文件名不能为null"); diff --git a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java index 8d605872..3e6b6e54 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java @@ -37,6 +37,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; @@ -63,6 +64,18 @@ import java.lang.reflect.WildcardType; public class CommonUtil { private static final String TAG = "CommonUtil"; + /** + * 字符串编码转换 + */ + public static String strCharSetConvert(String oldStr, String charSet) { + try { + return new String(oldStr.getBytes(), charSet); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null; + } + /** * 实例化泛型的实际类型参数 * @@ -137,7 +150,7 @@ public class CommonUtil { /** * 删除上传任务的配置,包括 * - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经下载完成的文件 + * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 * {@code false}如果任务已经完成,只删除任务数据库记录 */ public static void delUploadTaskConfig(boolean removeFile, UploadTaskEntity tEntity) { @@ -147,15 +160,8 @@ public class CommonUtil { if (file.exists()) { file.delete(); } - } else { - if (!uEntity.isComplete()) { - if (file.exists()) { - file.delete(); - } - } } - File config = new File( - AriaManager.APP.getFilesDir().getPath() + "/temp/" + uEntity.getFileName() + ".properties"); + File config = new File(getFileConfig(false, uEntity.getFileName())); if (config.exists()) { config.delete(); } @@ -184,8 +190,7 @@ public class CommonUtil { } } - File config = new File( - AriaManager.APP.getFilesDir().getPath() + "/temp/" + dEntity.getFileName() + ".properties"); + File config = new File(getFileConfig(true, dEntity.getFileName())); if (config.exists()) { config.delete(); } @@ -599,6 +604,16 @@ public class CommonUtil { return err.toString(); } + /** + * 通过文件名获取下载配置文件 + * + * @param fileName 文件名 + */ + public static String getFileConfig(boolean isDownload, String fileName) { + return AriaManager.APP.getFilesDir().getPath() + (isDownload ? AriaManager.DOWNLOAD_TEMP_DIR + : AriaManager.UPLOAD_TEMP_DIR) + fileName + ".properties"; + } + /** * 重命名下载配置文件 * 如果旧的配置文件名不存在,则使用新的配置文件名新创建一个文件,否则将旧的配置文件重命名为新的位置文件名。 @@ -608,8 +623,7 @@ public class CommonUtil { * @param newName 新的下载文件名 */ public static void renameDownloadConfig(String oldName, String newName) { - renameConfig(AriaManager.APP.getFilesDir().getPath() + AriaManager.DOWNLOAD_TEMP_DIR, oldName, - newName); + renameConfig(true, oldName, newName); } /** @@ -621,14 +635,13 @@ public class CommonUtil { * @param newName 新的上传文件名 */ public static void renameUploadConfig(String oldName, String newName) { - renameConfig(AriaManager.APP.getFilesDir().getPath() + AriaManager.UPLOAD_TEMP_DIR, oldName, - newName); + renameConfig(false, oldName, newName); } - private static void renameConfig(String basePath, String oldName, String newName) { + private static void renameConfig(boolean isDownload, String oldName, String newName) { if (oldName.equals(newName)) return; - File oldFile = new File(basePath + oldName + ".properties"); - File newFile = new File(basePath + newName + ".properties"); + File oldFile = new File(getFileConfig(isDownload, oldName)); + File newFile = new File(getFileConfig(isDownload, oldName)); if (!oldFile.exists()) { createFile(newFile.getPath()); } else { diff --git a/Aria/src/main/java/org/apache/commons/net/DatagramSocketClient.java b/Aria/src/main/java/org/apache/commons/net/DatagramSocketClient.java new file mode 100644 index 00000000..a2980f0b --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/DatagramSocketClient.java @@ -0,0 +1,314 @@ +/* + * 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; +import java.nio.charset.Charset; + +/*** + * 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. + * + * + * @see DatagramSocketFactory + ***/ + +public abstract class DatagramSocketClient +{ + /*** + * The default DatagramSocketFactory shared by all DatagramSocketClient + * instances. + ***/ + private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY = + new DefaultDatagramSocketFactory(); + + /** + * Charset to use for byte IO. + */ + private Charset charset = Charset.defaultCharset(); + + /*** 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. + * + * @throws 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. + * @throws 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. + * @throws 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. + ***/ + public void close() + { + if (_socket_ != null) { + _socket_.close(); + } + _socket_ = null; + _isOpen_ = false; + } + + + /*** + * Returns true if the client has a currently open socket. + * + * @return True if the client has a currently 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. + * @throws SocketException if an error setting the timeout + ***/ + 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. + * @throws SocketException if an error getting the timeout + ***/ + 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; + } + } + + /** + * Gets the charset name. + * + * @return the charset name. + * @since 3.3 + * TODO Will be deprecated once the code requires Java 1.6 as a mininmum + */ + public String getCharsetName() { + return charset.name(); + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 3.3 + */ + public Charset getCharset() { + return charset; + } + + /** + * Sets the charset. + * + * @param charset the charset. + * @since 3.3 + */ + public void setCharset(Charset charset) { + this.charset = charset; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/DatagramSocketFactory.java b/Aria/src/main/java/org/apache/commons/net/DatagramSocketFactory.java new file mode 100644 index 00000000..c05a5321 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/DatagramSocketFactory.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; + +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 DatagramSocketClient} + * . + * This allows you to provide your own DatagramSocket implementations and + * to perform security checks or browser capability requests before + * creating a DatagramSocket. + * + * + ***/ + +public interface DatagramSocketFactory +{ + + /*** + * Creates a DatagramSocket on the local host at the first available port. + * @return the socket + * + * @throws 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. + * @return the socket + * @throws 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. + * @return the socket + * @throws SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket(int port, InetAddress laddr) + throws SocketException; +} diff --git a/Aria/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java b/Aria/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java new file mode 100644 index 00000000..0f7d4475 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java @@ -0,0 +1,79 @@ +/* + * 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 DatagramSocketClient} + * implementations. + * + * + * @see DatagramSocketFactory + * @see DatagramSocketClient + * @see DatagramSocketClient#setDatagramSocketFactory + ***/ + +public class DefaultDatagramSocketFactory implements DatagramSocketFactory +{ + + /*** + * Creates a DatagramSocket on the local host at the first available port. + * @return a new DatagramSocket + * @throws SocketException If the socket could not be created. + ***/ + @Override + 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. + * @return a new DatagramSocket + * @throws SocketException If the socket could not be created. + ***/ + @Override + 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. + * @return a new DatagramSocket + * @throws SocketException If the socket could not be created. + ***/ + @Override + public DatagramSocket createDatagramSocket(int port, InetAddress laddr) + throws SocketException + { + return new DatagramSocket(port, laddr); + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/DefaultSocketFactory.java b/Aria/src/main/java/org/apache/commons/net/DefaultSocketFactory.java new file mode 100644 index 00000000..3f1aad18 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/DefaultSocketFactory.java @@ -0,0 +1,230 @@ +/* + * 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.InetSocketAddress; +import java.net.Proxy; +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 SocketClient} + * implementations. + * + * + * @see SocketFactory + * @see SocketClient + * @see SocketClient#setSocketFactory + ***/ + +public class DefaultSocketFactory extends SocketFactory +{ + /** The proxy to use when creating new sockets. */ + private final Proxy connProxy; + + /** + * The default constructor. + */ + public DefaultSocketFactory() + { + this(null); + } + + /** + * A constructor for sockets with proxy support. + * + * @param proxy The Proxy to use when creating new Sockets. + * @since 3.2 + */ + public DefaultSocketFactory(Proxy proxy) + { + connProxy = proxy; + } + + /** + * Creates an unconnected Socket. + * + * @return A new unconnected Socket. + * @throws IOException If an I/O error occurs while creating the Socket. + * @since 3.2 + */ + @Override + public Socket createSocket() throws IOException + { + if (connProxy != null) + { + return new Socket(connProxy); + } + return new Socket(); + } + + /*** + * 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. + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(String host, int port) + throws UnknownHostException, IOException + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.connect(new InetSocketAddress(host, port)); + return s; + } + 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. + * @throws IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(InetAddress address, int port) + throws IOException + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.connect(new InetSocketAddress(address, port)); + return s; + } + 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. + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws 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 + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.bind(new InetSocketAddress(localAddr, localPort)); + s.connect(new InetSocketAddress(host, port)); + return s; + } + 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. + * @throws 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 + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.bind(new InetSocketAddress(localAddr, localPort)); + s.connect(new InetSocketAddress(address, port)); + return s; + } + 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. + * @throws 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. + * @throws 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. + * @throws 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/Aria/src/main/java/org/apache/commons/net/MalformedServerReplyException.java b/Aria/src/main/java/org/apache/commons/net/MalformedServerReplyException.java new file mode 100644 index 00000000..e5ade4e3 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/MalformedServerReplyException.java @@ -0,0 +1,56 @@ +/* + * 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. + * + * + ***/ + +public class MalformedServerReplyException extends IOException +{ + + private static final long serialVersionUID = 6006765264250543945L; + + /*** 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/Aria/src/main/java/org/apache/commons/net/PrintCommandListener.java b/Aria/src/main/java/org/apache/commons/net/PrintCommandListener.java new file mode 100644 index 00000000..8fff4ed1 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/PrintCommandListener.java @@ -0,0 +1,202 @@ +/* + * 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.PrintStream; +import java.io.PrintWriter; + +/*** + * 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 final PrintWriter __writer; + private final boolean __nologin; + private final char __eolMarker; + private final boolean __directionMarker; + + /** + * Create the default instance which prints everything. + * + * @param stream where to write the commands and responses + * e.g. System.out + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream) + { + this(new PrintWriter(stream)); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param stream where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream, boolean suppressLogin) { + this(new PrintWriter(stream), suppressLogin); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param stream where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker) { + this(new PrintWriter(stream), suppressLogin, eolMarker); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param stream where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output + * + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker, boolean showDirection) { + this(new PrintWriter(stream), suppressLogin, eolMarker, showDirection); + } + + /** + * Create the default instance which prints everything. + * + * @param writer where to write the commands and responses + */ + public PrintCommandListener(PrintWriter writer) + { + this(writer, false); + } + + /** + * Create an instance which optionally suppresses login command text. + * + * @param writer where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * + * @since 3.0 + */ + public PrintCommandListener(PrintWriter writer, boolean suppressLogin) + { + this(writer, suppressLogin, (char) 0); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param writer where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * + * @since 3.0 + */ + public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker) + { + this(writer, suppressLogin, eolMarker, false); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param writer where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output + * + * @since 3.0 + */ + public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker, boolean showDirection) + { + __writer = writer; + __nologin = suppressLogin; + __eolMarker = eolMarker; + __directionMarker = showDirection; + } + + @Override + public void protocolCommandSent(ProtocolCommandEvent event) + { + if (__directionMarker) { + __writer.print("> "); + } + if (__nologin) { + String cmd = event.getCommand(); + if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) { + __writer.print(cmd); + __writer.println(" *******"); // Don't bother with EOL marker for this! + } else { + final String IMAP_LOGIN = "LOGIN"; + if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP + String msg = event.getMessage(); + msg=msg.substring(0, msg.indexOf(IMAP_LOGIN)+IMAP_LOGIN.length()); + __writer.print(msg); + __writer.println(" *******"); // Don't bother with EOL marker for this! + } else { + __writer.print(getPrintableString(event.getMessage())); + } + } + } else { + __writer.print(getPrintableString(event.getMessage())); + } + __writer.flush(); + } + + private String getPrintableString(String msg){ + if (__eolMarker == 0) { + return msg; + } + int pos = msg.indexOf(SocketClient.NETASCII_EOL); + if (pos > 0) { + StringBuilder sb = new StringBuilder(); + sb.append(msg.substring(0,pos)); + sb.append(__eolMarker); + sb.append(msg.substring(pos)); + return sb.toString(); + } + return msg; + } + + @Override + public void protocolReplyReceived(ProtocolCommandEvent event) + { + if (__directionMarker) { + __writer.print("< "); + } + __writer.print(event.getMessage()); + __writer.flush(); + } +} + diff --git a/Aria/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java b/Aria/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java new file mode 100644 index 00000000..d921c615 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java @@ -0,0 +1,148 @@ +/* + * 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 + ***/ + +public class ProtocolCommandEvent extends EventObject +{ + private static final long serialVersionUID = 403743538418947240L; + + private final int __replyCode; + private final boolean __isCommand; + private final 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. + * Includes the line terminator. + * + * @return The entire message sent to or received from the server. + ***/ + public String getMessage() + { + return __message; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ProtocolCommandListener.java b/Aria/src/main/java/org/apache/commons/net/ProtocolCommandListener.java new file mode 100644 index 00000000..f6d4231d --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ProtocolCommandListener.java @@ -0,0 +1,58 @@ +/* + * 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 + ***/ + +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/Aria/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java b/Aria/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java new file mode 100644 index 00000000..95611f01 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java @@ -0,0 +1,135 @@ +/* + * 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 + ***/ + +public class ProtocolCommandSupport implements Serializable +{ + private static final long serialVersionUID = -8017692739988399978L; + + private final Object __source; + private final ListenerList __listeners; + + /*** + * Creates a ProtocolCommandSupport instance using the indicated source + * as the source of 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/Aria/src/main/java/org/apache/commons/net/SocketClient.java b/Aria/src/main/java/org/apache/commons/net/SocketClient.java new file mode 100644 index 00000000..1b78872b --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/SocketClient.java @@ -0,0 +1,887 @@ +/* + * 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.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.SocketException; +import java.nio.charset.Charset; + +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 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 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. + * @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(); + + /** The default {@link ServerSocketFactory} */ + private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY = + ServerSocketFactory.getDefault(); + + /** + * A ProtocolCommandSupport object used to manage the registering of + * ProtocolCommandListeners and the firing of ProtocolCommandEvents. + */ + private ProtocolCommandSupport __commandSupport; + + /** The timeout to use after opening a socket. */ + protected int _timeout_; + + /** The socket used for the connection. */ + protected Socket _socket_; + + /** The hostname used for the connection (null = no hostname supplied). */ + protected String _hostname_; + + /** 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; + + /** Hint for SO_RCVBUF size */ + private int receiveBufferSize = -1; + + /** Hint for SO_SNDBUF size */ + private int sendBufferSize = -1; + + /** The proxy to use when connecting. */ + private Proxy connProxy; + + /** + * Charset to use for byte IO. + */ + private Charset charset = Charset.defaultCharset(); + + /** + * Default constructor for SocketClient. Initializes + * _socket_ to null, _timeout_ to 0, _defaultPort to 0, + * _isConnected_ to false, charset to {@code Charset.defaultCharset()} + * and _socketFactory_ to a shared instance of + * {@link org.apache.commons.net.DefaultSocketFactory}. + */ + public SocketClient() + { + _socket_ = null; + _hostname_ = 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. + * @throws IOException (SocketException) if a problem occurs with the socket + */ + 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. + * @throws SocketException If the socket timeout could not be set. + * @throws 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 + { + _hostname_ = null; + _connect(host, port, null, -1); + } + + /** + * 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. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname, int port) + throws SocketException, IOException + { + _hostname_ = hostname; + _connect(InetAddress.getByName(hostname), port, null, -1); + } + + + /** + * 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. + * @throws SocketException If the socket timeout could not be set. + * @throws 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 + { + _hostname_ = null; + _connect(host, port, localAddr, localPort); + } + + // helper method to allow code to be shared with connect(String,...) methods + private void _connect(InetAddress host, int port, InetAddress localAddr, int localPort) + throws SocketException, IOException + { + _socket_ = _socketFactory_.createSocket(); + if (receiveBufferSize != -1) { + _socket_.setReceiveBufferSize(receiveBufferSize); + } + if (sendBufferSize != -1) { + _socket_.setSendBufferSize(sendBufferSize); + } + if (localAddr != null) { + _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. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname, int port, + InetAddress localAddr, int localPort) + throws SocketException, IOException + { + _hostname_ = hostname; + _connect(InetAddress.getByName(hostname), port, localAddr, localPort); + } + + + /** + * 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. + * @throws SocketException If the socket timeout could not be set. + * @throws 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 + { + _hostname_ = null; + 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. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.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. + *

+ * @throws IOException If there is an error closing the socket. + */ + public void disconnect() throws IOException + { + closeQuietly(_socket_); + closeQuietly(_input_); + closeQuietly(_output_); + _socket_ = null; + _hostname_ = null; + _input_ = null; + _output_ = null; + } + + private void closeQuietly(Socket socket) { + if (socket != null){ + try { + socket.close(); + } catch (IOException e) { + // Ignored + } + } + } + + private void closeQuietly(Closeable close){ + if (close != null){ + try { + close.close(); + } catch (IOException e) { + // Ignored + } + } + } + /** + * Returns true if the client is currently connected to a server. + *

+ * Delegates to {@link Socket#isConnected()} + * @return True if the client is currently connected to a server, + * false otherwise. + */ + public boolean isConnected() + { + if (_socket_ == null) { + return false; + } + + return _socket_.isConnected(); + } + + /** + * Make various checks on the socket to test if it is available for use. + * Note that the only sure test is to use it, but these checks may help + * in some cases. + * @see NET-350 + * @return {@code true} if the socket appears to be available for use + * @since 3.0 + */ + public boolean isAvailable(){ + if (isConnected()) { + try + { + if (_socket_.getInetAddress() == null) { + return false; + } + if (_socket_.getPort() == 0) { + return false; + } + if (_socket_.getRemoteSocketAddress() == null) { + return false; + } + if (_socket_.isClosed()) { + return false; + } + /* these aren't exact checks (a Socket can be half-open), + but since we usually require two-way data transfer, + we check these here too: */ + if (_socket_.isInputShutdown()) { + return false; + } + if (_socket_.isOutputShutdown()) { + return false; + } + /* ignore the result, catch exceptions: */ + _socket_.getInputStream(); + _socket_.getOutputStream(); + } + catch (IOException ioex) + { + return false; + } + return true; + } else { + return false; + } + } + + /** + * 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()}. + *

+ * To set the initial timeout, use {@link #setDefaultTimeout(int)} instead. + * + * @param timeout The timeout in milliseconds to use for the currently + * open socket connection. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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 never thrown, but subclasses might want to do so + * @since 2.0 + */ + public void setSendBufferSize(int size) throws SocketException { + sendBufferSize = size; + } + + /** + * Get the current sendBuffer size + * @return the size, or -1 if not initialised + * @since 3.0 + */ + protected int getSendBufferSize(){ + return sendBufferSize; + } + + /** + * Sets the underlying socket receive buffer size. + *

+ * @param size The size of the buffer in bytes. + * @throws SocketException never (but subclasses may wish to do so) + * @since 2.0 + */ + public void setReceiveBufferSize(int size) throws SocketException { + receiveBufferSize = size; + } + + /** + * Get the current receivedBuffer size + * @return the size, or -1 if not initialised + * @since 3.0 + */ + protected int getReceiveBufferSize(){ + return receiveBufferSize; + } + + /** + * Returns the timeout in milliseconds of the currently opened socket. + *

+ * @return The timeout in milliseconds of the currently opened socket. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + public boolean getTcpNoDelay() throws SocketException + { + return _socket_.getTcpNoDelay(); + } + + /** + * Sets the SO_KEEPALIVE flag on the currently opened socket. + * + * From the Javadocs, the default keepalive time is 2 hours (although this is + * implementation dependent). It looks as though the Windows WSA sockets implementation + * allows a specific keepalive value to be set, although this seems not to be the case on + * other systems. + * @param keepAlive If true, keepAlive is turned on + * @throws SocketException if there is a problem with the socket + * @throws NullPointerException if the socket is not currently open + * @since 2.2 + */ + public void setKeepAlive(boolean keepAlive) throws SocketException { + _socket_.setKeepAlive(keepAlive); + } + + /** + * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket. + * Delegates to {@link Socket#getKeepAlive()} + * @return True if SO_KEEPALIVE is enabled. + * @throws SocketException if there is a problem with the socket + * @throws NullPointerException if the socket is not currently open + * @since 2.2 + */ + public boolean getKeepAlive() throws SocketException { + return _socket_.getKeepAlive(); + } + + /** + * 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?) + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + public int getSoLinger() throws SocketException + { + return _socket_.getSoLinger(); + } + + + /** + * Returns the port number of the open socket on the local host used + * for the connection. + * Delegates to {@link Socket#getLocalPort()} + *

+ * @return The port number of the open socket on the local host used + * for the connection. + * @throws NullPointerException if the socket is not currently open + */ + public int getLocalPort() + { + return _socket_.getLocalPort(); + } + + + /** + * Returns the local address to which the client's socket is bound. + * Delegates to {@link Socket#getLocalAddress()} + *

+ * @return The local address to which the client's socket is bound. + * @throws NullPointerException if the socket is not currently open + */ + public InetAddress getLocalAddress() + { + return _socket_.getLocalAddress(); + } + + /** + * Returns the port number of the remote host to which the client is + * connected. + * Delegates to {@link Socket#getPort()} + *

+ * @return The port number of the remote host to which the client is + * connected. + * @throws NullPointerException if the socket is not currently open + */ + public int getRemotePort() + { + return _socket_.getPort(); + } + + + /** + * @return The remote address to which the client is connected. + * Delegates to {@link Socket#getInetAddress()} + * @throws NullPointerException if the socket is not currently open + */ + 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. + *

+ * @param socket the item to check against + * @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). + * Any proxy setting is discarded. + *

+ * @param factory The new SocketFactory the SocketClient should use. + */ + public void setSocketFactory(SocketFactory factory) + { + if (factory == null) { + _socketFactory_ = __DEFAULT_SOCKET_FACTORY; + } else { + _socketFactory_ = factory; + } + // re-setting the socket factory makes the proxy setting useless, + // so set the field to null so that getProxy() doesn't return a + // Proxy that we're actually not using. + connProxy = null; + } + + /** + * 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 timeout (in ms) + * @since 2.0 + */ + public int getConnectTimeout() { + return connectTimeout; + } + + /** + * Get the underlying {@link ServerSocketFactory} + * @return The server socket factory + * @since 2.2 + */ + public ServerSocketFactory getServerSocketFactory() { + return _serverSocketFactory_; + } + + + /** + * Adds a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to add. + * @since 3.0 + */ + public void addProtocolCommandListener(ProtocolCommandListener listener) { + getCommandSupport().addProtocolCommandListener(listener); + } + + /** + * Removes a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to remove. + * @since 3.0 + */ + public void removeProtocolCommandListener(ProtocolCommandListener listener) { + getCommandSupport().removeProtocolCommandListener(listener); + } + + /** + * If there are any listeners, send them the reply details. + * + * @param replyCode the code extracted from the reply + * @param reply the full reply text + * @since 3.0 + */ + protected void fireReplyReceived(int replyCode, String reply) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireReplyReceived(replyCode, reply); + } + } + + /** + * If there are any listeners, send them the command details. + * + * @param command the command name + * @param message the complete message, including command name + * @since 3.0 + */ + protected void fireCommandSent(String command, String message) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireCommandSent(command, message); + } + } + + /** + * Create the CommandSupport instance if required + */ + protected void createCommandSupport(){ + __commandSupport = new ProtocolCommandSupport(this); + } + + /** + * Subclasses can override this if they need to provide their own + * instance field for backwards compatibilty. + * + * @return the CommandSupport instance, may be {@code null} + * @since 3.0 + */ + protected ProtocolCommandSupport getCommandSupport() { + return __commandSupport; + } + + /** + * Sets the proxy for use with all the connections. + * The proxy is used for connections established after the + * call to this method. + * + * @param proxy the new proxy for connections. + * @since 3.2 + */ + public void setProxy(Proxy proxy) { + setSocketFactory(new DefaultSocketFactory(proxy)); + connProxy = proxy; + } + + /** + * Gets the proxy for use with all the connections. + * @return the current proxy for connections. + */ + public Proxy getProxy() { + return connProxy; + } + + /** + * Gets the charset name. + * + * @return the charset. + * @since 3.3 + * @deprecated Since the code now requires Java 1.6 as a mininmum + */ + @Deprecated + public String getCharsetName() { + return charset.name(); + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 3.3 + */ + public Charset getCharset() { + return charset; + } + + /** + * Sets the charset. + * + * @param charset the charset. + * @since 3.3 + */ + public void setCharset(Charset charset) { + this.charset = charset; + } + + /* + * N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility, + * so the abstract method is needed to pass the instance to the methods which were moved here. + */ +} + + diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/Configurable.java b/Aria/src/main/java/org/apache/commons/net/ftp/Configurable.java new file mode 100644 index 00000000..b04ded9a --- /dev/null +++ b/Aria/src/main/java/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/Aria/src/main/java/org/apache/commons/net/ftp/FTP.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTP.java new file mode 100644 index 00000000..8341f88b --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTP.java @@ -0,0 +1,1870 @@ +/* + * 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.io.Reader; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; + +import org.apache.commons.net.MalformedServerReplyException; +import org.apache.commons.net.ProtocolCommandSupport; +import org.apache.commons.net.SocketClient; +import org.apache.commons.net.io.CRLFLineReader; + +/*** + * 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. + * + * @see FTPClient + * @see FTPConnectionClosedException + * @see org.apache.commons.net.MalformedServerReplyException + * @version $Id: FTP.java 1782546 2017-02-11 00:05:41Z 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 transferred 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 transferred 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 transferred 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 transferred 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 transferred 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 transferred 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 transferred 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"; + + /** Length of the FTP reply code (3 alphanumerics) */ + public static final int REPLY_CODE_LEN = 3; + + private static final String __modes = "AEILNTCFRPSBC"; + + protected int _replyCode; + protected ArrayList _replyLines; + protected boolean _newReplyString; + protected String _replyString; + protected String _controlEncoding; + + /** + * A ProtocolCommandSupport object used to manage the registering of + * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + */ + protected ProtocolCommandSupport _commandSupport_; + + /** + * 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 RFC 959 for details. + */ + protected boolean strictMultilineParsing = false; + + /** + * If this is true, then non-multiline replies must have the format: + * 3 digit code + * If false, then the 3 digit code does not have to be followed by space + * See section 4.2 of RFC 959 for details. + */ + private boolean strictReplyParsing = true; + + /** + * Wraps SocketClient._input_ to facilitate the reading of text + * from 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 writing of text + * to 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_; + + /*** + * 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; + _controlEncoding = DEFAULT_CONTROL_ENCODING; + _commandSupport_ = new ProtocolCommandSupport(this); + } + + // The RFC-compliant multiline termination check + private boolean __strictCheck(String line, String code) { + return (!(line.startsWith(code) && line.charAt(REPLY_CODE_LEN) == ' ')); + } + + // 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() > REPLY_CODE_LEN&& line.charAt(REPLY_CODE_LEN) != '-' && + Character.isDigit(line.charAt(0)))); + } + + /** + * Get the reply, and pass it to command listeners + */ + private void __getReply() throws IOException + { + __getReply(true); + } + + /** + * Get the reply, but don't pass it to command listeners. + * Used for keep-alive processing only. + * @since 3.0 + * @throws IOException on error + */ + protected void __getReplyNoReport() throws IOException + { + __getReply(false); + } + + private void __getReply(boolean reportReply) 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 < REPLY_CODE_LEN) { + throw new MalformedServerReplyException( + "Truncated server reply: " + line); + } + + String code = null; + try + { + code = line.substring(0, REPLY_CODE_LEN); + _replyCode = Integer.parseInt(code); + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse response code.\nServer Reply: " + line); + } + + _replyLines.add(line); + + // Check the server reply type + if (length > REPLY_CODE_LEN) { + char sep = line.charAt(REPLY_CODE_LEN); + // Get extra lines if message continues. + if (sep == '-') { + 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)); + + } else if (isStrictReplyParsing()) { + if (length == REPLY_CODE_LEN + 1) { // expecting some text + throw new MalformedServerReplyException("Truncated server reply: '" + line +"'"); + } else if (sep != ' ') { + throw new MalformedServerReplyException("Invalid server reply: '" + line +"'"); + } + } + } else if (isStrictReplyParsing()) { + throw new MalformedServerReplyException("Truncated server reply: '" + line +"'"); + } + + if (reportReply) { + 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 + { + _connectAction_(null); + } + + + /** + * Initiates control connections and gets initial reply. + * Initializes {@link #_controlInput_} and {@link #_controlOutput_}. + * + * @param socketIsReader the reader to reuse (if non-null) + * @throws IOException on error + * @since 3.4 + */ + protected void _connectAction_(Reader socketIsReader) throws IOException { + super._connectAction_(); // sets up _input_ and _output_ + if(socketIsReader == null) { + _controlInput_ = + new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); + } else { + _controlInput_ = new CRLFLineReader(socketIsReader); + } + _controlOutput_ = + new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); + if (connectTimeout > 0) { // NET-385 + int original = _socket_.getSoTimeout(); + _socket_.setSoTimeout(connectTimeout); + try { + __getReply(); + // If we received code 120, we have to fetch completion reply. + if (FTPReply.isPositivePreliminary(_replyCode)) { + __getReply(); + } + } catch (SocketTimeoutException e) { + IOException ioe = new IOException("Timed out waiting for initial connect reply"); + ioe.initCause(e); + throw ioe; + } finally { + _socket_.setSoTimeout(original); + } + } else { + __getReply(); + // If we received code 120, we have to fetch completion reply. + if (FTPReply.isPositivePreliminary(_replyCode)) { + __getReply(); + } + } + } + + + /** + * Saves the character encoding to be 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. + *

+ * Please note that this has to be set before the connection is established. + * + * @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; + } + + + /*** + * 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. + * + * @throws 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. + * @throws 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. + * @throws 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 + { + if (_controlOutput_ == null) { + throw new IOException("Connection is not open"); + } + + final String message = __buildMessage(command, args); + + __send(message); + + fireCommandSent(command, message); + + __getReply(); + return _replyCode; + } + + private String __buildMessage(String command, String args) { + final StringBuilder __commandBuffer = new StringBuilder(); + + __commandBuffer.append(command); + + if (args != null) + { + __commandBuffer.append(' '); + __commandBuffer.append(args); + } + __commandBuffer.append(SocketClient.NETASCII_EOL); + return __commandBuffer.toString(); + } + + private void __send(String message) throws IOException, + FTPConnectionClosedException, SocketException { + try{ + _controlOutput_.write(message); + _controlOutput_.flush(); + } + catch (SocketException e) + { + if (!isConnected()) + { + throw new FTPConnectionClosedException("Connection unexpectedly closed."); + } + else + { + throw e; + } + } + } + + /** + * Send a noop and get the reply without reporting to the command listener. + * Intended for use with keep-alive. + * + * @throws IOException on error + * @since 3.0 + */ + protected void __noop() throws IOException { + String msg = __buildMessage(FTPCmd.NOOP.getCommand(), null); + __send(msg); + __getReplyNoReport(); // This may timeout + } + + /*** + * 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @deprecated (3.3) Use {@link #sendCommand(FTPCmd, String)} instead + ***/ + @Deprecated + public int sendCommand(int command, String args) throws IOException + { + return sendCommand(FTPCommand.getCommand(command), args); + } + + /** + * 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 FTPCmd enum 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.3 + */ + public int sendCommand(FTPCmd command) throws IOException{ + return sendCommand(command, 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 FTPCmd enum 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.3 + */ + public int sendCommand(FTPCmd command, String args) throws IOException{ + return sendCommand(command.getCommand(), 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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() + { + return _replyLines.toArray(new String[_replyLines.size()]); + } + + /*** + * 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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int abor() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int cdup() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int quit() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rein() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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; + StringBuilder info = new StringBuilder(24); + + info.append(host.getHostAddress().replace('.', ',')); + num = port >>> 8; + info.append(','); + info.append(num); + info.append(','); + num = port & 0xff; + info.append(num); + + return sendCommand(FTPCmd.PORT, info.toString()); + } + + /*** + * A convenience method to send the FTP EPRT command to the server, + * receive the reply, and return the reply code. + * + * Examples: + *

    + *
  • EPRT |1|132.235.1.2|6275|
  • + *
  • EPRT |2|1080::8:800:200C:417A|5282|
  • + *
+ * + * @see "http://www.faqs.org/rfcs/rfc2428.html" + * + * @param host The host owning the port. + * @param port The new port. + * @return The reply code received from the server. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + ***/ + public int eprt(InetAddress host, int port) throws IOException + { + int num; + StringBuilder info = new StringBuilder(); + String h; + + // If IPv6, trim the zone index + h = host.getHostAddress(); + num = h.indexOf("%"); + if (num > 0) { + h = h.substring(0, num); + } + + info.append("|"); + + if (host instanceof Inet4Address) { + info.append("1"); + } else if (host instanceof Inet6Address) { + info.append("2"); + } + info.append("|"); + info.append(h); + info.append("|"); + info.append(port); + info.append("|"); + + return sendCommand(FTPCmd.EPRT, 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int pasv() throws IOException + { + return sendCommand(FTPCmd.PASV); + } + + /*** + * A convenience method to send the FTP EPSV 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + ***/ + public int epsv() throws IOException + { + return sendCommand(FTPCmd.EPSV); + } + + /** + * 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. + * @throws 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. + * @throws 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 + { + StringBuilder arg = new StringBuilder(); + + arg.append(__modes.charAt(fileType)); + arg.append(' '); + if (fileType == LOCAL_FILE_TYPE) { + arg.append(formatOrByteSize); + } else { + arg.append(__modes.charAt(formatOrByteSize)); + } + + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stou() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.ALLO, Integer.toString(bytes)); + } + + /** + * A convenience method to send the FTP FEAT command to the server, receive the reply, + * and return the reply code. + * @return The reply code received by the server + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + */ + public int feat() throws IOException + { + return sendCommand(FTPCmd.FEAT); + } + + /*** + * 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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.REST, marker); + } + + + /** + * @param file name of file + * @return the status + * @throws IOException on error + * @since 2.0 + **/ + public int mdtm(String file) throws IOException + { + return sendCommand(FTPCmd.MDTM, file); + } + + + /** + * A convenience method to send the FTP MFMT command to the server, + * receive the reply, and return the reply code. + * + * @param pathname The pathname for which mtime is to be changed + * @param timeval Timestamp in YYYYMMDDhhmmss format + * @return The reply code received from the server. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + * @see http://tools.ietf.org/html/draft-somers-ftp-mfxx-04 + **/ + public int mfmt(String pathname, String timeval) throws IOException + { + return sendCommand(FTPCmd.MFMT, timeval + " " + pathname); + } + + + /*** + * 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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int pwd() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int list() throws IOException + { + return sendCommand(FTPCmd.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, + * may be {@code null} in which case the command is sent with no parameters + * @return The reply code received from the server. + * @throws 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. + * @throws 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(FTPCmd.LIST, pathname); + } + + /** + * A convenience method to send the FTP MLSD 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlsd() throws IOException + { + return sendCommand(FTPCmd.MLSD); + } + + /** + * A convenience method to send the FTP MLSD 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 path the path to report on + * @return The reply code received from the server, + * may be {@code null} in which case the command is sent with no parameters + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlsd(String path) throws IOException + { + return sendCommand(FTPCmd.MLSD, path); + } + + /** + * A convenience method to send the FTP MLST 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlst() throws IOException + { + return sendCommand(FTPCmd.MLST); + } + + /** + * A convenience method to send the FTP MLST 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 path the path to report on + * @return The reply code received from the server, + * may be {@code null} in which case the command is sent with no parameters + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlst(String path) throws IOException + { + return sendCommand(FTPCmd.MLST, path); + } + + /*** + * 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int nlst() throws IOException + { + return sendCommand(FTPCmd.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, + * may be {@code null} in which case the command is sent with no parameters + * @return The reply code received from the server. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int syst() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stat() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int help() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int noop() throws IOException + { + return sendCommand(FTPCmd.NOOP); + } + + /** + * Return whether strict multiline parsing is enabled, as per RFC 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 the setting + * @since 2.0 + */ + public void setStrictMultilineParsing(boolean strictMultilineParsing) { + this.strictMultilineParsing = strictMultilineParsing; + } + + /** + * Return whether strict non-multiline parsing is enabled, as per RFC 959, section 4.2. + *

+ * The default is true, which requires the 3 digit code be followed by space and some text. + *
+ * If false, only the 3 digit code is required (as was the case for versions up to 3.5) + *
+ * @return True if strict (default), false if additional checks are not made + * @since 3.6 + */ + public boolean isStrictReplyParsing() { + return strictReplyParsing; + } + + /** + * Set strict non-multiline parsing. + *

+ * If true, it requires the 3 digit code be followed by space and some text. + *
+ * If false, only the 3 digit code is required (as was the case for versions up to 3.5) + *

+ * This should not be required by a well-behaved FTP server + *
+ * @param strictReplyParsing the setting + * @since 3.6 + */ + public void setStrictReplyParsing(boolean strictReplyParsing) { + this.strictReplyParsing = strictReplyParsing; + } + + /** + * Provide command support to super-class + */ + @Override + protected ProtocolCommandSupport getCommandSupport() { + return _commandSupport_; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPClient.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPClient.java new file mode 100644 index 00000000..fa9f5fd6 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPClient.java @@ -0,0 +1,3980 @@ +/* + * 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.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Properties; +import java.util.Random; +import java.util.Set; + +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.MLSxEntryParser; +import org.apache.commons.net.io.CRLFLineReader; +import org.apache.commons.net.io.CopyStreamAdapter; +import org.apache.commons.net.io.CopyStreamEvent; +import org.apache.commons.net.io.CopyStreamListener; +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: + *

+ *    FTPClient ftp = new FTPClient();
+ *    FTPClientConfig config = new FTPClientConfig();
+ *    config.setXXX(YYY); // change required options
+ *    // for example config.setServerTimeZoneId("Pacific/Pitcairn")
+ *    ftp.configure(config );
+ *    boolean error = false;
+ *    try {
+ *      int reply;
+ *      String server = "ftp.example.com";
+ *      ftp.connect(server);
+ *      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 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 (or EPRT) 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 (or EPRT) 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 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 FTP#getReplyCode getReplyCode }, + * {@link FTP#getReplyString getReplyString }, + * and + * {@link FTP#getReplyStrings getReplyStrings}. + * You may avoid server disconnections while the client is idle by + * periodically 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 = new FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = f.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 = new 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 = new 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}. + *

+ * Control channel keep-alive feature: + *

+ * Please note: this does not apply to the methods where the user is responsible for writing or reading + * the data stream, i.e. {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} + * and the other xxxFileStream methods + *

+ * During file transfers, the data connection is busy, but the control connection is idle. + * FTP servers know that the control connection is in use, so won't close it through lack of activity, + * but it's a lot harder for network routers to know that the control and data connections are associated + * with each other. + * Some routers may treat the control connection as idle, and disconnect it if the transfer over the data + * connection takes longer than the allowable idle time for the router. + *
+ * One solution to this is to send a safe command (i.e. NOOP) over the control connection to reset the router's + * idle timer. This is enabled as follows: + *

+ *     ftpClient.setControlKeepAliveTimeout(300); // set timeout to 5 minutes
+ * 
+ * This will cause the file upload/download methods to send a NOOP approximately every 5 minutes. + * The following public methods support this: + *
    + *
  • {@link #retrieveFile(String, OutputStream)}
  • + *
  • {@link #appendFile(String, InputStream)}
  • + *
  • {@link #storeFile(String, InputStream)}
  • + *
  • {@link #storeUniqueFile(InputStream)}
  • + *
  • {@link #storeUniqueFileStream(String)}
  • + *
+ * This feature does not apply to the methods where the user is responsible for writing or reading + * the data stream, i.e. {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} + * and the other xxxFileStream methods. + * In such cases, the user is responsible for keeping the control connection alive if necessary. + *

+ * The implementation currently uses a {@link CopyStreamListener} which is passed to the + * {@link Util#copyStream(InputStream, OutputStream, int, long, CopyStreamListener, boolean)} + * method, so the timing is partially dependent on how long each block transfer takes. + * + * @see #FTP_SYSTEM_TYPE + * @see #SYSTEM_TYPE_PROPERTIES + * @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 +{ + /** + * The system property ({@value}) which can be used to override the system type.
+ * If defined, the value will be used to create any automatically created parsers. + * + * @since 3.0 + */ + public static final String FTP_SYSTEM_TYPE = "org.apache.commons.net.ftp.systemType"; + + /** + * The system property ({@value}) which can be used as the default system type.
+ * If defined, the value will be used if the SYST command fails. + * + * @since 3.1 + */ + public static final String FTP_SYSTEM_TYPE_DEFAULT = "org.apache.commons.net.ftp.systemType.default"; + + /** + * The name of an optional systemType properties file ({@value}), which is loaded + * using {@link Class#getResourceAsStream(String)}.
+ * The entries are the systemType (as determined by {@link FTPClient#getSystemType}) + * and the values are the replacement type or parserClass, which is passed to + * {@link FTPFileEntryParserFactory#createFileEntryParser(String)}.
+ * For example: + *

+     * Plan 9=Unix
+     * OS410=org.apache.commons.net.ftp.parser.OS400FTPEntryParser
+     * 
+ * + * @since 3.0 + */ + public static final String SYSTEM_TYPE_PROPERTIES = "/systemType.properties"; + + /** + * 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; + private int __dataTimeout; + private int __passivePort; + private String __passiveHost; + private final Random __random; + private int __activeMinPort; + private int __activeMaxPort; + private InetAddress __activeExternalHost; + private InetAddress __reportActiveExternalHost; // overrides __activeExternalHost in EPRT/PORT commands + /** The address to bind to on passive connections, if necessary. */ + private InetAddress __passiveLocalHost; + + private int __fileType; + @SuppressWarnings("unused") // fields are written, but currently not read + private int __fileFormat; + @SuppressWarnings("unused") // field is written, but currently not read + private int __fileStructure; + @SuppressWarnings("unused") // field is written, but currently not read + private int __fileTransferMode; + private boolean __remoteVerificationEnabled; + private long __restartOffset; + private FTPFileEntryParserFactory __parserFactory; + private int __bufferSize; // buffersize for buffered data streams + private int __sendDataSocketBufferSize; + private int __receiveDataSocketBufferSize; + private boolean __listHiddenFiles; + private boolean __useEPSVwithIPv4; // whether to attempt EPSV with an IPv4 connection + + // __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; + + // Key used to create the parser; necessary to ensure that the parser type is not ignored + private String __entryParserKey; + + private FTPClientConfig __configuration; + + // Listener used by store/retrieve methods to handle keepalive + private CopyStreamListener __copyStreamListener; + + // How long to wait before sending another control keep-alive message + private long __controlKeepAliveTimeout; + + // How long to wait (ms) for keepalive message replies before continuing + // Most FTP servers don't seem to support concurrent control and data connection usage + private int __controlKeepAliveReplyTimeout=1000; + + /** + * Enable or disable replacement of internal IP in passive mode. Default enabled + * using {code NatServerResolverImpl}. + */ + private HostnameResolver __passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); + + /** Pattern for PASV mode responses. Groups: (n,n,n,n),(n),(n) */ + private static final java.util.regex.Pattern __PARMS_PAT; + + static { + __PARMS_PAT = java.util.regex.Pattern.compile( + "(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})"); + } + + /** Controls the automatic server encoding detection (only UTF-8 supported). */ + private boolean __autodetectEncoding = false; + + /** Map of FEAT responses. If null, has not been initialised. */ + private HashMap> __featuresMap; + + private static class PropertiesSingleton { + + static final Properties PROPERTIES; + + static { + InputStream resourceAsStream = FTPClient.class.getResourceAsStream(SYSTEM_TYPE_PROPERTIES); + Properties p = null; + if (resourceAsStream != null) { + p = new Properties(); + try { + p.load(resourceAsStream); + } catch (IOException e) { + // Ignored + } finally { + try { + resourceAsStream.close(); + } catch (IOException e) { + // Ignored + } + } + } + PROPERTIES = p; + } + + } + private static Properties getOverrideProperties(){ + return PropertiesSingleton.PROPERTIES; + } + + /** + * 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 . + *

+ * The list parsing auto-detect feature can be configured to use lenient future + * dates (short dates may be up to one day in the future) as follows: + *

+     * FTPClient ftp = new FTPClient();
+     * FTPClientConfig config = new FTPClientConfig();
+     * config.setLenientFutureDates(true);
+     * ftp.configure(config );
+     * 
+ */ + public FTPClient() + { + __initDefaults(); + __dataTimeout = -1; + __remoteVerificationEnabled = true; + __parserFactory = new DefaultFTPFileEntryParserFactory(); + __configuration = null; + __listHiddenFiles = false; + __useEPSVwithIPv4 = false; + __random = new Random(); + __passiveLocalHost = null; + } + + + private void __initDefaults() + { + __dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; + __passiveHost = null; + __passivePort = -1; + __activeExternalHost = null; + __reportActiveExternalHost = null; + __activeMinPort = 0; + __activeMaxPort = 0; + __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; + __entryParserKey = ""; + __featuresMap = null; + } + + /** + * Parse the pathname from a CWD reply. + *

+ * According to RFC959 (http://www.ietf.org/rfc/rfc959.txt), + * it should be the same as for MKD i.e. + * {@code 257""[commentary]} + * where any double-quotes in {@code } are doubled. + * Unlike MKD, the commentary is optional. + *

+ * However, see NET-442 for an exception. + * + * @param reply + * @return the pathname, without enclosing quotes, + * or the full string after the reply code and space if the syntax is invalid + * (i.e. enclosing quotes are missing or embedded quotes are not doubled) + */ + // package protected for access by test cases + static String __parsePathname(String reply) + { + String param = reply.substring(REPLY_CODE_LEN + 1); + if (param.startsWith("\"")) { + StringBuilder sb = new StringBuilder(); + boolean quoteSeen = false; + // start after initial quote + for(int i=1; i < param.length(); i++) { + char ch = param.charAt(i); + if (ch=='"') { + if (quoteSeen) { + sb.append(ch); + quoteSeen=false; + } else { + // don't output yet, in case doubled + quoteSeen=true; + } + } else { + if (quoteSeen) { // found lone trailing quote within string + return sb.toString(); + } + sb.append(ch); // just another character + } + } + if (quoteSeen) { // found lone trailing quote at end of string + return sb.toString(); + } + } + // malformed reply, return all after reply code and space + return param; + } + + /** + * @since 3.1 + * @param reply the reply to parse + * @throws MalformedServerReplyException if the server reply does not match (n,n,n,n),(n),(n) + */ + protected 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); + } + + __passiveHost = m.group(1).replace(',', '.'); // Fix up to look like IP address + + try + { + int oct1 = Integer.parseInt(m.group(2)); + int oct2 = Integer.parseInt(m.group(3)); + __passivePort = (oct1 << 8) | oct2; + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse passive port information.\nServer Reply: " + reply); + } + + if (__passiveNatWorkaroundStrategy != null) { + try { + String passiveHost = __passiveNatWorkaroundStrategy.resolve(__passiveHost); + if (!__passiveHost.equals(passiveHost)) { + fireReplyReceived(0, + "[Replacing PASV mode reply address "+__passiveHost+" with "+passiveHost+"]\n"); + __passiveHost = passiveHost; + } + } catch (UnknownHostException e) { // Should not happen as we are passing in an IP address + throw new MalformedServerReplyException( + "Could not parse passive host information.\nServer Reply: " + reply); + } + } + } + + protected void _parseExtendedPassiveModeReply(String reply) + throws MalformedServerReplyException + { + reply = reply.substring(reply.indexOf('(') + 1, + reply.indexOf(')')).trim(); + + char delim1, delim2, delim3, delim4; + delim1 = reply.charAt(0); + delim2 = reply.charAt(1); + delim3 = reply.charAt(2); + delim4 = reply.charAt(reply.length()-1); + + if (!(delim1 == delim2) || !(delim2 == delim3) + || !(delim3 == delim4)) { + throw new MalformedServerReplyException( + "Could not parse extended passive host information.\nServer Reply: " + reply); + } + + int port; + try + { + port = Integer.parseInt(reply.substring(3, reply.length()-1)); + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse extended passive host information.\nServer Reply: " + reply); + } + + + // in EPSV mode, the passive host address is implicit + __passiveHost = getRemoteAddress().getHostAddress(); + __passivePort = port; + } + + private boolean __storeFile(FTPCmd command, String remote, InputStream local) + throws IOException + { + return _storeFile(command.getCommand(), remote, local); + } + + /** + * @since 3.1 + * @param command the command to send + * @param remote the remote file name + * @param local the local file name + * @return true if successful + * @throws IOException on error + */ + protected boolean _storeFile(String command, String remote, InputStream local) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return false; + } + + final OutputStream output; + + if (__fileType == ASCII_FILE_TYPE) { + output = new ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); + } else { + output = getBufferedOutputStream(socket.getOutputStream()); + } + + CSL csl = null; + if (__controlKeepAliveTimeout > 0) { + csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); + } + + // Treat everything else as binary for now + try + { + Util.copyStream(local, output, getBufferSize(), + CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), + false); + } + catch (IOException e) + { + Util.closeQuietly(socket); // ignore close errors here + if (csl != null) { + csl.cleanUp(); // fetch any outstanding keepalive replies + } + throw e; + } + + output.close(); // ensure the file is fully written + socket.close(); // done writing the file + if (csl != null) { + csl.cleanUp(); // fetch any outstanding keepalive replies + } + // Get the transfer response + boolean ok = completePendingCommand(); + return ok; + } + + private OutputStream __storeFileStream(FTPCmd command, String remote) + throws IOException + { + return _storeFileStream(command.getCommand(), remote); + } + + /** + * @param command the command to send + * @param remote the remote file name + * @return the output stream to write to + * @throws IOException on error + * @since 3.1 + */ + protected OutputStream _storeFileStream(String command, String remote) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return null; + } + + final OutputStream output; + 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 ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); + } else { + output = socket.getOutputStream(); + } + 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 int 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 an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @deprecated (3.3) Use {@link #_openDataConnection_(FTPCmd, String)} instead + */ + @Deprecated + protected Socket _openDataConnection_(int command, String arg) + throws IOException + { + return _openDataConnection_(FTPCommand.getCommand(command), arg); + } + + /** + * 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 int 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 an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.3 + */ + protected Socket _openDataConnection_(FTPCmd command, String arg) + throws IOException + { + return _openDataConnection_(command.getCommand(), arg); + } + + /** + * 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.1 + */ + protected Socket _openDataConnection_(String command, String arg) + throws IOException + { + if (__dataConnectionMode != ACTIVE_LOCAL_DATA_CONNECTION_MODE && + __dataConnectionMode != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { + return null; + } + + final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; + + Socket socket; + + if (__dataConnectionMode == ACTIVE_LOCAL_DATA_CONNECTION_MODE) + { + // if no activePortRange was set (correctly) -> getActivePort() = 0 + // -> new ServerSocket(0) -> bind to any free local port + ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress()); + + try { + // Try EPRT only if remote server is over IPv6, if not use PORT, + // because EPRT has no advantage over PORT on IPv4. + // It could even have the disadvantage, + // that EPRT will make the data connection fail, because + // today's intelligent NAT Firewalls are able to + // substitute IP addresses in the PORT command, + // but might not be able to recognize the EPRT command. + if (isInet6Address) { + if (!FTPReply.isPositiveCompletion(eprt(getReportHostAddress(), server.getLocalPort()))) { + return null; + } + } else { + if (!FTPReply.isPositiveCompletion(port(getReportHostAddress(), server.getLocalPort()))) { + return null; + } + } + + if ((__restartOffset > 0) && !restart(__restartOffset)) { + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { + 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); + } + socket = server.accept(); + + // Ensure the timeout is set before any commands are issued on the new socket + if (__dataTimeout >= 0) { + socket.setSoTimeout(__dataTimeout); + } + if (__receiveDataSocketBufferSize > 0) { + socket.setReceiveBufferSize(__receiveDataSocketBufferSize); + } + if (__sendDataSocketBufferSize > 0) { + socket.setSendBufferSize(__sendDataSocketBufferSize); + } + } finally { + server.close(); + } + } + else + { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE + + // Try EPSV command first on IPv6 - and IPv4 if enabled. + // When using IPv4 with NAT it has the advantage + // to work with more rare configurations. + // E.g. if FTP server has a static PASV address (external network) + // and the client is coming from another internal network. + // In that case the data connection after PASV command would fail, + // while EPSV would make the client succeed by taking just the port. + boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) + { + _parseExtendedPassiveModeReply(_replyLines.get(0)); + } + else + { + if (isInet6Address) { + return null; // Must use EPSV for IPV6 + } + // If EPSV failed on IPV4, revert to PASV + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { + return null; + } + _parsePassiveModeReply(_replyLines.get(0)); + } + + socket = _socketFactory_.createSocket(); + if (__receiveDataSocketBufferSize > 0) { + socket.setReceiveBufferSize(__receiveDataSocketBufferSize); + } + if (__sendDataSocketBufferSize > 0) { + socket.setSendBufferSize(__sendDataSocketBufferSize); + } + if (__passiveLocalHost != null) { + socket.bind(new InetSocketAddress(__passiveLocalHost, 0)); + } + + // 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) { + socket.setSoTimeout(__dataTimeout); + } + + socket.connect(new InetSocketAddress(__passiveHost, __passivePort), connectTimeout); + if ((__restartOffset > 0) && !restart(__restartOffset)) + { + socket.close(); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) + { + socket.close(); + return null; + } + } + + if (__remoteVerificationEnabled && !verifyRemote(socket)) + { + socket.close(); + + throw new IOException( + "Host attempting data connection " + socket.getInetAddress().getHostAddress() + + " is not same as server " + getRemoteAddress().getHostAddress()); + } + + return socket; + } + + + @Override + protected void _connectAction_() throws IOException + { + _connectAction_(null); + } + + + /** + * @param socketIsReader the reader to reuse (if non-null) + * @throws IOException on error + * @since 3.4 + */ + @Override + protected void _connectAction_(Reader socketIsReader) throws IOException + { + super._connectAction_(socketIsReader); // sets up _input_ and _output_ + __initDefaults(); + // must be after super._connectAction_(), because otherwise we get an + // Exception claiming we're not connected + if ( __autodetectEncoding ) + { + ArrayList oldReplyLines = new ArrayList (_replyLines); + int oldReplyCode = _replyCode; + if ( hasFeature("UTF8") || hasFeature("UTF-8")) // UTF8 appears to be the default + { + setControlEncoding("UTF-8"); + _controlInput_ = + new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); + _controlOutput_ = + new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); + } + // restore the original reply (server greeting) + _replyLines.clear(); + _replyLines.addAll(oldReplyLines); + _replyCode = oldReplyCode; + _newReplyString = true; + } + } + + + /** + * Sets the timeout in milliseconds to use when reading from the + * data connection. This timeout will be set immediately after + * opening the data connection, provided that the value is ≥ 0. + *

+ * Note: the timeout will also be applied when calling accept() + * whilst establishing an active local data connection. + * @param timeout The default timeout in milliseconds that is used when + * opening a data connection socket. The value 0 means an infinite timeout. + */ + 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 FTPFileEntryParserFactory + * @see DefaultFTPFileEntryParserFactory + */ + public void setParserFactory(FTPFileEntryParserFactory parserFactory) { + __parserFactory = parserFactory; + } + + + /** + * Closes the connection to the FTP server and restores + * connection parameters to the default values. + * + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.4 (made public) + */ + public 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 (or EPSV) 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() } + *

+ * N.B. currently calling any connect method will reset the mode to + * ACTIVE_LOCAL_DATA_CONNECTION_MODE. + */ + 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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; + } + + /** + * Get the client port for active mode. + * + * @return The client port for active mode. + */ + private int getActivePort() + { + if (__activeMinPort > 0 && __activeMaxPort >= __activeMinPort) + { + if (__activeMaxPort == __activeMinPort) { + return __activeMaxPort; + } + // Get a random port between the min and max port range + return __random.nextInt(__activeMaxPort - __activeMinPort + 1) + __activeMinPort; + } + else + { + // default port + return 0; + } + } + + /** + * Get the host address for active mode; allows the local address to be overridden. + * + * @return __activeExternalHost if non-null, else getLocalAddress() + * @see #setActiveExternalIPAddress(String) + */ + private InetAddress getHostAddress() + { + if (__activeExternalHost != null) + { + return __activeExternalHost; + } + else + { + // default local address + return getLocalAddress(); + } + } + + /** + * Get the reported host address for active mode EPRT/PORT commands; + * allows override of {@link #getHostAddress()}. + * + * Useful for FTP Client behind Firewall NAT. + * + * @return __reportActiveExternalHost if non-null, else getHostAddress(); + */ + private InetAddress getReportHostAddress() { + if (__reportActiveExternalHost != null) { + return __reportActiveExternalHost ; + } else { + return getHostAddress(); + } + } + + /** + * Set the client side port range in active mode. + * + * @param minPort The lowest available port (inclusive). + * @param maxPort The highest available port (inclusive). + * @since 2.2 + */ + public void setActivePortRange(int minPort, int maxPort) + { + this.__activeMinPort = minPort; + this.__activeMaxPort = maxPort; + } + + /** + * Set the external IP address in active mode. + * Useful when there are multiple network cards. + * + * @param ipAddress The external IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + * @since 2.2 + */ + public void setActiveExternalIPAddress(String ipAddress) throws UnknownHostException + { + this.__activeExternalHost = InetAddress.getByName(ipAddress); + } + + /** + * Set the local IP address to use in passive mode. + * Useful when there are multiple network cards. + * + * @param ipAddress The local IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + */ + public void setPassiveLocalIPAddress(String ipAddress) throws UnknownHostException + { + this.__passiveLocalHost = InetAddress.getByName(ipAddress); + } + + /** + * Set the local IP address to use in passive mode. + * Useful when there are multiple network cards. + * + * @param inetAddress The local IP address of this machine. + */ + public void setPassiveLocalIPAddress(InetAddress inetAddress) + { + this.__passiveLocalHost = inetAddress; + } + + /** + * Set the local IP address in passive mode. + * Useful when there are multiple network cards. + * + * @return The local IP address in passive mode. + */ + public InetAddress getPassiveLocalIPAddress() + { + return this.__passiveLocalHost; + } + + /** + * Set the external IP address to report in EPRT/PORT commands in active mode. + * Useful when there are multiple network cards. + * + * @param ipAddress The external IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + * @since 3.1 + * @see #getReportHostAddress() + */ + public void setReportActiveExternalIPAddress(String ipAddress) throws UnknownHostException + { + this.__reportActiveExternalHost = InetAddress.getByName(ipAddress); + } + + + /** + * 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. + *
+ * The server default is supposed to be ASCII (see RFC 959), however many + * ftp servers default to BINARY. To ensure correct operation with all servers, + * always specify the appropriate file type after connecting to the server. + *
+ *

+ * N.B. currently calling any connect method will reset the type to + * FTP.ASCII_FILE_TYPE. + * @param fileType The _FILE_TYPE constant indcating the + * type of file. + * @return True if successfully completed, false if not. + * @throws 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. + * @throws 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 server default is supposed to be ASCII (see RFC 959), however many + * ftp servers default to BINARY. To ensure correct operation with all servers, + * always specify the appropriate file type after connecting to the server. + *
+ * 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. + *

+ * N.B. currently calling any connect method will reset the type to + * FTP.ASCII_FILE_TYPE and the formatOrByteSize to FTP.NON_PRINT_TEXT_FORMAT. + * + * @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. + * @throws 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. + * @throws 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 + * or if a connect method is called. + * + * @param structure The structure of the file (one of the FTP class + * _STRUCTURE constants). + * @return True if successfully completed, false if not. + * @throws 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. + * @throws 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 + * or if a connect method is 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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(appe(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. + * @throws 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. + * @throws 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. + *

+ * Note: if you have used {@link #setRestartOffset(long)}, + * the file data will start from the selected offset. + * @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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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 + { + return _retrieveFile(FTPCmd.RETR.getCommand(), remote, local); + } + + /** + * @param command the command to get + * @param remote the remote file name + * @param local the local file name + * @return true if successful + * @throws IOException on error + * @since 3.1 + */ + protected boolean _retrieveFile(String command, String remote, OutputStream local) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return false; + } + + final InputStream input; + if (__fileType == ASCII_FILE_TYPE) { + input = new FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); + } else { + input = getBufferedInputStream(socket.getInputStream()); + } + + CSL csl = null; + if (__controlKeepAliveTimeout > 0) { + csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); + } + + // Treat everything else as binary for now + try + { + Util.copyStream(input, local, getBufferSize(), + CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), + false); + } finally { + Util.closeQuietly(input); + Util.closeQuietly(socket); + if (csl != null) { + csl.cleanUp(); // fetch any outstanding keepalive replies + } + } + + // Get the transfer response + boolean ok = completePendingCommand(); + return ok; + } + + /** + * 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. + * If this is not done, subsequent commands may behave unexpectedly. + *

+ * Note: if you have used {@link #setRestartOffset(long)}, + * the file data will start from the selected offset. + * + * @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). + * @throws 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. + * @throws 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 + { + return _retrieveFileStream(FTPCmd.RETR.getCommand(), remote); + } + + /** + * @param command the command to send + * @param remote the remote file name + * @return the stream from which to read the file + * @throws IOException on error + * @since 3.1 + */ + protected InputStream _retrieveFileStream(String command, String remote) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return null; + } + + final InputStream input; + 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 FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); + } else { + input = socket.getInputStream(); + } + 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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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)); + } + + /** + * Query the server for supported features. The server may reply with a list of server-supported exensions. + * For example, a typical client-server interaction might be (from RFC 2389): + *

+        C> feat
+        S> 211-Extensions supported:
+        S>  MLST size*;create;modify*;perm;media-type
+        S>  SIZE
+        S>  COMPRESSION
+        S>  MDTM
+        S> 211 END
+     * 
+ * @see http://www.faqs.org/rfcs/rfc2389.html + * @return True if successfully completed, false if not. + * @throws IOException on error + * @since 2.2 + */ + public boolean features() throws IOException { + return FTPReply.isPositiveCompletion(feat()); + } + + /** + * Query the server for a supported feature, and returns its values (if any). + * Caches the parsed response to avoid resending the command repeatedly. + * @param feature the feature to check + * + * @return if the feature is present, returns the feature values (empty array if none) + * Returns {@code null} if the feature is not found or the command failed. + * Check {@link #getReplyCode()} or {@link #getReplyString()} if so. + * @throws IOException on error + * @since 3.0 + */ + public String[] featureValues(String feature) throws IOException { + if (!initFeatureMap()) { + return null; + } + Set entries = __featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); + if (entries != null) { + return entries.toArray(new String[entries.size()]); + } + return null; + } + + /** + * Query the server for a supported feature, and returns the its value (if any). + * Caches the parsed response to avoid resending the command repeatedly. + * @param feature the feature to check + * + * @return if the feature is present, returns the feature value or the empty string + * if the feature exists but has no value. + * Returns {@code null} if the feature is not found or the command failed. + * Check {@link #getReplyCode()} or {@link #getReplyString()} if so. + * @throws IOException on error + * @since 3.0 + */ + public String featureValue(String feature) throws IOException { + String [] values = featureValues(feature); + if (values != null) { + return values[0]; + } + return null; + } + + /** + * Query the server for a supported feature. + * Caches the parsed response to avoid resending the command repeatedly. + * + * @param feature the name of the feature; it is converted to upper case. + * @return {@code true} if the feature is present, {@code false} if the feature is not present + * or the {@link #feat()} command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} + * if it is necessary to distinguish these cases. + * + * @throws IOException on error + * @since 3.0 + */ + public boolean hasFeature(String feature) throws IOException { + if (!initFeatureMap()) { + return false; + } + return __featuresMap.containsKey(feature.toUpperCase(Locale.ENGLISH)); + } + + /** + * Query the server for a supported feature with particular value, + * for example "AUTH SSL" or "AUTH TLS". + * Caches the parsed response to avoid resending the command repeatedly. + * + * @param feature the name of the feature; it is converted to upper case. + * @param value the value to find. + * + * @return {@code true} if the feature is present, {@code false} if the feature is not present + * or the {@link #feat()} command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} + * if it is necessary to distinguish these cases. + * + * @throws IOException on error + * @since 3.0 + */ + public boolean hasFeature(String feature, String value) throws IOException { + if (!initFeatureMap()) { + return false; + } + Set entries = __featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); + if (entries != null) { + return entries.contains(value); + } + return false; + } + + /* + * Create the feature map if not already created. + */ + private boolean initFeatureMap() throws IOException { + if (__featuresMap == null) { + // Don't create map here, because next line may throw exception + final int replyCode = feat(); + if (replyCode == FTPReply.NOT_LOGGED_IN) { // 503 + return false; // NET-518; don't create empy map + } + boolean success = FTPReply.isPositiveCompletion(replyCode); + // we init the map here, so we don't keep trying if we know the command will fail + __featuresMap = new HashMap>(); + if (!success) { + return false; + } + for (String l : getReplyStrings()) { + if (l.startsWith(" ")) { // it's a FEAT entry + String key; + String value=""; + int varsep = l.indexOf(' ', 1); + if (varsep > 0) { + key = l.substring(1, varsep); + value = l.substring(varsep+1); + } else { + key = l.substring(1); + } + key = key.toUpperCase(Locale.ENGLISH); + Set entries = __featuresMap.get(key); + if (entries == null) { + entries = new HashSet(); + __featuresMap.put(key, entries); + } + entries.add(value); + } + } + } + return true; + } + + /** + * 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. + * @throws 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. + * @throws 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)); + } + + + /** + * Issue a command and wait for the reply. + *

+ * Should only be used with commands that return replies on the + * command channel - do not use for LIST, NLST, MLSD etc. + * + * @param command The command to invoke + * @param params The parameters string, may be {@code null} + * @return True if successfully completed, false if not, in which case + * call {@link #getReplyCode()} or {@link #getReplyString()} + * to get the reason. + * + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.0 + */ + public boolean doCommand(String command, String params) throws IOException + { + return FTPReply.isPositiveCompletion(sendCommand(command, params)); + } + + /** + * Issue a command and wait for the reply, returning it as an array of strings. + *

+ * Should only be used with commands that return replies on the + * command channel - do not use for LIST, NLST, MLSD etc. + * + * @param command The command to invoke + * @param params The parameters string, may be {@code null} + * @return The array of replies, or {@code null} if the command failed, in which case + * call {@link #getReplyCode()} or {@link #getReplyString()} + * to get the reason. + * + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.0 + */ + public String[] doCommandAsStrings(String command, String params) throws IOException + { + boolean success = FTPReply.isPositiveCompletion(sendCommand(command, params)); + if (success){ + return getReplyStrings(); + } else { + return null; + } + } + + /** + * Get file details using the MLST command + * + * @param pathname the file or directory to list, may be {@code null} + * @return the file details, may be {@code null} + * @throws IOException on error + * @since 3.0 + */ + public FTPFile mlistFile(String pathname) throws IOException + { + boolean success = FTPReply.isPositiveCompletion(sendCommand(FTPCmd.MLST, pathname)); + if (success){ + String reply = getReplyStrings()[1]; + /* check the response makes sense. + * Must have space before fact(s) and between fact(s) and filename + * Fact(s) can be absent, so at least 3 chars are needed. + */ + if (reply.length() < 3 || reply.charAt(0) != ' ') { + throw new MalformedServerReplyException("Invalid server reply (MLST): '" + reply + "'"); + } + String entry = reply.substring(1); // skip leading space for parser + return MLSxEntryParser.parseEntry(entry); + } else { + return null; + } + } + + /** + * Generate a directory listing for the current directory using the MLSD command. + * + * @return the array of file entries + * @throws IOException on error + * @since 3.0 + */ + public FTPFile[] mlistDir() throws IOException + { + return mlistDir(null); + } + + /** + * Generate a directory listing using the MLSD command. + * + * @param pathname the directory name, may be {@code null} + * @return the array of file entries + * @throws IOException on error + * @since 3.0 + */ + public FTPFile[] mlistDir(String pathname) throws IOException + { + FTPListParseEngine engine = initiateMListParsing( pathname); + return engine.getFiles(); + } + + /** + * Generate a directory listing using the MLSD command. + * + * @param pathname the directory name, may be {@code null} + * @param filter the filter to apply to the responses + * @return the array of file entries + * @throws IOException on error + * @since 3.0 + */ + public FTPFile[] mlistDir(String pathname, FTPFileFilter filter) throws IOException + { + FTPListParseEngine engine = initiateMListParsing( pathname); + return engine.getFiles(filter); + } + + /** + * 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.1 (changed from private to protected) + */ + protected boolean restart(long offset) throws IOException + { + __restartOffset = 0; + return FTPReply.isPositiveIntermediate(rest(Long.toString(offset))); + } + + /** + * Sets the restart offset for file transfers. + *

+ * The restart command is not sent to the server immediately. + * It is sent when a data connection is created as part of a + * subsequent command. + * The restart marker is reset to zero after use. + *

+ *

+ * Note: This method should only be invoked immediately prior to + * the transfer to which it applies. + * + * @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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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 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. + *

+ * If the SYST command fails, and the system property + * {@link #FTP_SYSTEM_TYPE_DEFAULT} is defined, then this is used instead. + * @return The system type obtained from the server. Never null. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server (and the default + * system type property is not defined) + * @since 2.2 + */ + public String getSystemType() 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){ + if (FTPReply.isPositiveCompletion(syst())) { + // Assume that response is not empty here (cannot be null) + __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + } else { + // Check if the user has provided a default for when the SYST command fails + String systDefault = System.getProperty(FTP_SYSTEM_TYPE_DEFAULT); + if (systDefault != null) { + __systemName = systDefault; + } else { + throw new IOException("Unable to determine system type - response: " + getReplyString()); + } + } + } + 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * Warning: the server may treat a leading '-' as an + * option introducer. If so, try using an absolute path, + * or prefix the path with ./ (unix style servers). + * Some servers may support "--" as meaning end of options, + * in which case "-- -xyz" should work. + * @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. + * @throws 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. + * @throws 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 + { + Socket socket = _openDataConnection_(FTPCmd.NLST, getListArguments(pathname)); + + if (socket == null) { + return null; + } + + BufferedReader reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding())); + + ArrayList results = new ArrayList(); + String line; + 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. + * @throws 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. + * @throws 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. + *

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * + * @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. + * Also, some servers treat a leading '-' as being an option. + * To avoid this interpretation, use an absolute pathname + * or prefix the pathname with ./ (unix style servers). + * Some servers may support "--" as meaning end of options, + * in which case "-- -xyz" should work. + * + * @return The list of file information contained in the given path in + * the format determined by the autodetection mechanism + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles(String pathname) + throws IOException + { + FTPListParseEngine engine = initiateListParsing((String) null, 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. + *

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * + * @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. + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles() + throws IOException + { + return listFiles((String) null); + } + + /** + * Version of {@link #listFiles(String)} which allows a filter to be provided. + * For example: listFiles("site", FTPFileFilters.DIRECTORY); + * @param pathname the initial path, may be null + * @param filter the filter, non-null + * @return the list of FTPFile entries. + * @throws IOException on error + * @since 2.2 + */ + public FTPFile[] listFiles(String pathname, FTPFileFilter filter) + throws IOException + { + FTPListParseEngine engine = initiateListParsing((String) null, pathname); + return engine.getFiles(filter); + + } + + /** + * Using the default system autodetect mechanism, obtain a + * list of directories contained in the current working directory. + *

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

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * + * @return The list of directories contained in the current directory + * in the format determined by the autodetection mechanism. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @since 3.0 + */ + public FTPFile[] listDirectories() throws IOException { + return listDirectories((String) null); + } + + /** + * Using the default system autodetect mechanism, obtain a + * list of directories contained in the specified directory. + *

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

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * @param parent the starting directory + * + * @return The list of directories contained in the specified directory + * in the format determined by the autodetection mechanism. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @since 3.0 + */ + public FTPFile[] listDirectories(String parent) throws IOException { + return listFiles(parent, FTPFileFilters.DIRECTORIES); + } + + /** + * 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. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @throws org.apache.commons.net.ftp.parser.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.
+     *    }
+     * 
+ * @param pathname the starting directory + * + * @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. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @throws org.apache.commons.net.ftp.parser.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 + { + return initiateListParsing((String) null, 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. + * May be {@code null}, in which case the code checks first + * the system property {@link #FTP_SYSTEM_TYPE}, and if that is + * not defined the SYST command is used to provide the value. + * To allow for arbitrary system types, the return from the + * SYST command is used to look up an alias for the type in the + * {@link #SYSTEM_TYPE_PROPERTIES} properties file if it is available. + * @param pathname the starting directory + * + * @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. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @throws org.apache.commons.net.ftp.parser.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 + { + __createParser(parserKey); // create and cache parser + return initiateListParsing(__entryParser, pathname); + } + + // package access for test purposes + void __createParser(String parserKey) throws IOException { + // We cache the value to avoid creation of a new object every + // time a file listing is generated. + // Note: we don't check against a null parserKey (NET-544) + if(__entryParser == null || (parserKey != null && ! __entryParserKey.equals(parserKey))) { + if (null != parserKey) { + // if a parser key was supplied in the parameters, + // use that to create the parser + __entryParser = + __parserFactory.createFileEntryParser(parserKey); + __entryParserKey = parserKey; + + } else { + // if no parserKey was supplied, check for a configuration + // in the params, and if it has a non-empty system type, use that. + if (null != __configuration && __configuration.getServerSystemKey().length() > 0) { + __entryParser = + __parserFactory.createFileEntryParser(__configuration); + __entryParserKey = __configuration.getServerSystemKey(); + } else { + // if a parserKey hasn't been supplied, and a configuration + // hasn't been supplied, and the override property is not set + // then autodetect by calling + // the SYST command and use that to choose the parser. + String systemType = System.getProperty(FTP_SYSTEM_TYPE); + if (systemType == null) { + systemType = getSystemType(); // cannot be null + Properties override = getOverrideProperties(); + if (override != null) { + String newType = override.getProperty(systemType); + if (newType != null) { + systemType = newType; + } + } + } + if (null != __configuration) { // system type must have been empty above + __entryParser = __parserFactory.createFileEntryParser(new FTPClientConfig(systemType, __configuration)); + } else { + __entryParser = __parserFactory.createFileEntryParser(systemType); + } + __entryParserKey = systemType; + } + } + } + + + } + + /** + * private method through which all listFiles() and + * initiateListParsing methods pass once a parser is determined. + * + * @throws 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. + * @throws 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 = _openDataConnection_(FTPCmd.LIST, getListArguments(pathname)); + + FTPListParseEngine engine = new FTPListParseEngine(parser, __configuration); + if (socket == null) + { + return engine; + } + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } + finally { + Util.closeQuietly(socket); + } + + completePendingCommand(); + return engine; + } + + /** + * Initiate list parsing for MLSD listings. + * + * @param pathname + * @return the engine + * @throws IOException + */ + private FTPListParseEngine initiateMListParsing(String pathname) throws IOException + { + Socket socket = _openDataConnection_(FTPCmd.MLSD, pathname); + FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance(), __configuration); + if (socket == null) + { + return engine; + } + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } + finally { + Util.closeQuietly(socket); + completePendingCommand(); + } + return engine; + } + + /** + * @param pathname the initial pathname + * @return the adjusted string with "-a" added if necessary + * @since 2.0 + */ + protected String getListArguments(String pathname) { + if (getListHiddenFiles()) + { + if (pathname != null) + { + StringBuilder sb = new StringBuilder(pathname.length() + 3); + sb.append("-a "); + sb.append(pathname); + return sb.toString(); + } + else + { + return "-a"; + } + } + + return pathname; + } + + + /** + * Issue the FTP STAT command to the server. + * + * @return The status information returned by the server. + * @throws 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. + * @throws 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. + * @param pathname the filename + * + * @return The status information returned by the server. + * @throws 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. + * @throws 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 getReplyStrings()[0].substring(4); // skip the return code (e.g. 213) and the space + } + 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 FTPFile representing the last file modification time, may be {@code null}. + * The FTPFile timestamp will be null if a parse error occurs. + * @throws IOException if an I/O error occurs. + * @since 3.4 + */ + public FTPFile mdtmFile(String pathname) throws IOException { + if (FTPReply.isPositiveCompletion(mdtm(pathname))) { + String reply = getReplyStrings()[0].substring(4); // skip the return code (e.g. 213) and the space + FTPFile file = new FTPFile(); + file.setName(pathname); + file.setRawListing(reply); + file.setTimestamp(MLSxEntryParser.parseGMTdateTime(reply)); + return file; + } + return null; + } + + + /** + * Issue the FTP MFMT command (not supported by all servers) which sets the last + * modified time of a file. + * + * The timestamp should be in the form YYYYMMDDhhmmss. It should also + * be in GMT, but not all servers honour this. + * + * An FTP server would indicate its support of this feature by including "MFMT" + * in its response to the FEAT command, which may be retrieved by FTPClient.features() + * + * @param pathname The file path for which last modified time is to be changed. + * @param timeval The timestamp to set to, in YYYYMMDDhhmmss format. + * @return true if successfully set, false if not + * @throws IOException if an I/O error occurs. + * @since 2.2 + * @see http://tools.ietf.org/html/draft-somers-ftp-mfxx-04 + */ + public boolean setModificationTime(String pathname, String timeval) throws IOException { + return (FTPReply.isPositiveCompletion(mfmt(pathname, timeval))); + } + + + /** + * Set the internal buffer size for buffered data streams. + * + * @param bufSize The size of the buffer. Use a non-positive value to use the default. + */ + public void setBufferSize(int bufSize) { + __bufferSize = bufSize; + } + + /** + * Retrieve the current internal buffer size for buffered data streams. + * @return The current buffer size. + */ + public int getBufferSize() { + return __bufferSize; + } + + /** + * Sets the value to be used for the data socket SO_SNDBUF option. + * If the value is positive, the option will be set when the data socket has been created. + * + * @param bufSize The size of the buffer, zero or negative means the value is ignored. + * @since 3.3 + */ + public void setSendDataSocketBufferSize(int bufSize) { + __sendDataSocketBufferSize = bufSize; + } + + /** + * Retrieve the value to be used for the data socket SO_SNDBUF option. + * @return The current buffer size. + * @since 3.3 + */ + public int getSendDataSocketBufferSize() { + return __sendDataSocketBufferSize; + } + + /** + * Sets the value to be used for the data socket SO_RCVBUF option. + * If the value is positive, the option will be set when the data socket has been created. + * + * @param bufSize The size of the buffer, zero or negative means the value is ignored. + * @since 3.3 + */ + public void setReceieveDataSocketBufferSize(int bufSize) { + __receiveDataSocketBufferSize = bufSize; + } + + /** + * Retrieve the value to be used for the data socket SO_RCVBUF option. + * @return The current buffer size. + * @since 3.3 + */ + public int getReceiveDataSocketBufferSize() { + return __receiveDataSocketBufferSize; + } + + /** + * 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 + */ + @Override + 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; + } + + /** + * Whether should attempt to use EPSV with IPv4. + * Default (if not set) is false + * @return true if should attempt EPSV + * @since 2.2 + */ + public boolean isUseEPSVwithIPv4() { + return __useEPSVwithIPv4; + } + + + /** + * Set whether to use EPSV with IPv4. + * Might be worth enabling in some circumstances. + * + * For example, when using IPv4 with NAT it + * may work with some rare configurations. + * E.g. if FTP server has a static PASV address (external network) + * and the client is coming from another internal network. + * In that case the data connection after PASV command would fail, + * while EPSV would make the client succeed by taking just the port. + * + * @param selected value to set. + * @since 2.2 + */ + public void setUseEPSVwithIPv4(boolean selected) { + this.__useEPSVwithIPv4 = selected; + } + + /** + * Set the listener to be used when performing store/retrieve operations. + * The default value (if not set) is {@code null}. + * + * @param listener to be used, may be {@code null} to disable + * @since 3.0 + */ + public void setCopyStreamListener(CopyStreamListener listener){ + __copyStreamListener = listener; + } + + /** + * Obtain the currently active listener. + * + * @return the listener, may be {@code null} + * @since 3.0 + */ + public CopyStreamListener getCopyStreamListener(){ + return __copyStreamListener; + } + + /** + * Set the time to wait between sending control connection keepalive messages + * when processing file upload or download. + * + * @param controlIdle the wait (in secs) between keepalive messages. Zero (or less) disables. + * @since 3.0 + * @see #setControlKeepAliveReplyTimeout(int) + */ + public void setControlKeepAliveTimeout(long controlIdle){ + __controlKeepAliveTimeout = controlIdle * 1000; + } + + /** + * Get the time to wait between sending control connection keepalive messages. + * @return the number of seconds between keepalive messages. + * @since 3.0 + */ + public long getControlKeepAliveTimeout() { + return __controlKeepAliveTimeout / 1000; + } + + /** + * Set how long to wait for control keep-alive message replies. + * + * @param timeout number of milliseconds to wait (defaults to 1000) + * @since 3.0 + * @see #setControlKeepAliveTimeout(long) + */ + public void setControlKeepAliveReplyTimeout(int timeout) { + __controlKeepAliveReplyTimeout = timeout; + } + + /** + * Get how long to wait for control keep-alive message replies. + * @return wait time in msec + * @since 3.0 + */ + public int getControlKeepAliveReplyTimeout() { + return __controlKeepAliveReplyTimeout; + } + + /** + * Enable or disable passive mode NAT workaround. + * If enabled, a site-local PASV mode reply address will be replaced with the + * remote host address to which the PASV mode request was sent + * (unless that is also a site local address). + * This gets around the problem that some NAT boxes may change the + * reply. + * + * The default is true, i.e. site-local replies are replaced. + * @param enabled true to enable replacing internal IP's in passive + * mode. + * @deprecated (3.6) use {@link #setPassiveNatWorkaroundStrategy(HostnameResolver)} instead + */ + @Deprecated + public void setPassiveNatWorkaround(boolean enabled) { + if (enabled) { + this.__passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); + } else { + this.__passiveNatWorkaroundStrategy = null; + } + } + + /** + * Set the workaround strategy to replace the PASV mode reply addresses. + * This gets around the problem that some NAT boxes may change the reply. + * + * The default implementation is {@code NatServerResolverImpl}, i.e. site-local + * replies are replaced. + * @param resolver strategy to replace internal IP's in passive mode + * or null to disable the workaround (i.e. use PASV mode reply address.) + * @since 3.6 + */ + public void setPassiveNatWorkaroundStrategy(HostnameResolver resolver) { + this.__passiveNatWorkaroundStrategy = resolver; + } + + /** + * Strategy interface for updating host names received from FTP server + * for passive NAT workaround. + * + * @since 3.6 + */ + public static interface HostnameResolver { + String resolve(String hostname) throws UnknownHostException; + } + + /** + * Default strategy for passive NAT workaround (site-local + * replies are replaced.) + * @since 3.6 + */ + public static class NatServerResolverImpl implements HostnameResolver { + private FTPClient client; + + public NatServerResolverImpl(FTPClient client) { + this.client = client; + } + + @Override + public String resolve(String hostname) throws UnknownHostException { + String newHostname = hostname; + InetAddress host = InetAddress.getByName(newHostname); + // reply is a local address, but target is not - assume NAT box changed the PASV reply + if (host.isSiteLocalAddress()) { + InetAddress remote = this.client.getRemoteAddress(); + if (!remote.isSiteLocalAddress()){ + newHostname = remote.getHostAddress(); + } + } + return newHostname; + } + } + + private OutputStream getBufferedOutputStream(OutputStream outputStream) { + if (__bufferSize > 0) { + return new BufferedOutputStream(outputStream, __bufferSize); + } + return new BufferedOutputStream(outputStream); + } + + private InputStream getBufferedInputStream(InputStream inputStream) { + if (__bufferSize > 0) { + return new BufferedInputStream(inputStream, __bufferSize); + } + return new BufferedInputStream(inputStream); + } + + // @since 3.0 + private static class CSL implements CopyStreamListener { + + private final FTPClient parent; + private final long idle; + private final int currentSoTimeout; + + private long time = System.currentTimeMillis(); + private int notAcked; + + CSL(FTPClient parent, long idleTime, int maxWait) throws SocketException { + this.idle = idleTime; + this.parent = parent; + this.currentSoTimeout = parent.getSoTimeout(); + parent.setSoTimeout(maxWait); + } + + @Override + public void bytesTransferred(CopyStreamEvent event) { + bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, long streamSize) { + long now = System.currentTimeMillis(); + if (now - time > idle) { + try { + parent.__noop(); + } catch (SocketTimeoutException e) { + notAcked++; + } catch (IOException e) { + // Ignored + } + time = now; + } + } + + void cleanUp() throws IOException { + try { + while(notAcked-- > 0) { + parent.__getReplyNoReport(); + } + } finally { + parent.setSoTimeout(currentSoTimeout); + } + } + + } + + /** + * Merge two copystream listeners, either or both of which may be null. + * + * @param local the listener used by this class, may be null + * @return a merged listener or a single listener or null + * @since 3.0 + */ + private CopyStreamListener __mergeListeners(CopyStreamListener local) { + if (local == null) { + return __copyStreamListener; + } + if (__copyStreamListener == null) { + return local; + } + // Both are non-null + CopyStreamAdapter merged = new CopyStreamAdapter(); + merged.addCopyStreamListener(local); + merged.addCopyStreamListener(__copyStreamListener); + return merged; + } + + /** + * Enables or disables automatic server encoding detection (only UTF-8 supported). + *

+ * Does not affect existing connections; must be invoked before a connection is established. + * + * @param autodetect If true, automatic server encoding detection will be enabled. + */ + public void setAutodetectUTF8(boolean autodetect) + { + __autodetectEncoding = autodetect; + } + + /** + * Tells if automatic server encoding detection is enabled or disabled. + * @return true, if automatic server encoding detection is enabled. + */ + public boolean getAutodetectUTF8() + { + return __autodetectEncoding; + } + + // Method for use by unit test code only + FTPFileEntryParser getEntryParser() { + return __entryParser; + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** + * @return the name + * @throws IOException on error + * @deprecated use {@link #getSystemType()} instead + */ + @Deprecated + public String getSystemName() throws IOException + { + if (__systemName == null && FTPReply.isPositiveCompletion(syst())) { + __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + } + return __systemName; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ +/* kate: indent-width 4; replace-tabs on; */ diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java new file mode 100644 index 00000000..892beb9e --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java @@ -0,0 +1,709 @@ +/* + * 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 FTPClient FTPClient} object and + * also subordinate objects which it uses. Any class implementing the + * {@link 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 Configurable + * @see 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 for alternate UNIX parser; same as {@link #SYST_UNIX} but leading spaces are + * trimmed from file names. This is to maintain backwards compatibility with + * the original behaviour of the parser which ignored multiple spaces between the date + * and the start of the file name. + * @since 3.4 + */ + public static final String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM"; + + /** + * 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"; + + /** + * Identifier by which a Mac pre OS-X -based ftp server is known throughout + * the commons-net ftp system. + * + * @since 3.1 + */ + // Full string is "MACOS Peter's Server"; the substring below should be enough + public static final String SYST_MACOS_PETER = "MACOS PETER"; // NET-436 + + private final String serverSystemKey; + private String defaultDateFormatStr = null; + private String recentDateFormatStr = null; + private boolean lenientFutureDates = true; // NET-407 + private String serverLanguageCode = null; + private String shortMonthNames = null; + private String serverTimeZoneId = null; + private boolean saveUnparseableEntries = false; + + + /** + * The main constructor for an FTPClientConfig object + * @param systemKey key representing system type of the server being + * connected to. See {@link #getServerSystemKey() serverSystemKey} + * If set to the empty string, then FTPClient uses the system type returned by the server. + * However this is not recommended for general use; + * the correct system type should be set if it is known. + */ + 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 the format string 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} + * @since 3.6 + */ + public FTPClientConfig(String systemKey, + String defaultDateFormatStr, + String recentDateFormatStr) + { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.recentDateFormatStr = recentDateFormatStr; + } + + /** + * Constructor which allows setting of most 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; + } + + /** + * 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} + * @param lenientFutureDates See + * {@link #setLenientFutureDates(boolean) lenientFutureDates} + * @param saveUnparseableEntries See + * {@link #setUnparseableEntries(boolean) saveUnparseableEntries} + */ + public FTPClientConfig(String systemKey, + String defaultDateFormatStr, + String recentDateFormatStr, + String serverLanguageCode, + String shortMonthNames, + String serverTimeZoneId, + boolean lenientFutureDates, + boolean saveUnparseableEntries) + { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.lenientFutureDates = lenientFutureDates; + this.recentDateFormatStr = recentDateFormatStr; + this.saveUnparseableEntries = saveUnparseableEntries; + this.serverLanguageCode = serverLanguageCode; + this.shortMonthNames = shortMonthNames; + this.serverTimeZoneId = serverTimeZoneId; + } + + // Copy constructor, intended for use by FTPClient only + FTPClientConfig(String systemKey, FTPClientConfig config) { + this.serverSystemKey = systemKey; + this.defaultDateFormatStr = config.defaultDateFormatStr; + this.lenientFutureDates = config.lenientFutureDates; + this.recentDateFormatStr = config.recentDateFormatStr; + this.saveUnparseableEntries = config.saveUnparseableEntries; + this.serverLanguageCode = config.serverLanguageCode; + this.serverTimeZoneId = config.serverTimeZoneId; + this.shortMonthNames = config.shortMonthNames; + } + + /** + * Copy constructor + * @param config source + * @since 3.6 + */ + public FTPClientConfig(FTPClientConfig config) { + this.serverSystemKey = config.serverSystemKey; + this.defaultDateFormatStr = config.defaultDateFormatStr; + this.lenientFutureDates = config.lenientFutureDates; + this.recentDateFormatStr = config.recentDateFormatStr; + this.saveUnparseableEntries = config.saveUnparseableEntries; + this.serverLanguageCode = config.serverLanguageCode; + this.serverTimeZoneId = config.serverTimeZoneId; + this.shortMonthNames = config.shortMonthNames; + } + + private static final 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 Apache 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(); + } + + /** + * Allow list parsing methods to create basic FTPFile entries if parsing fails. + *

+ * In this case, the FTPFile will contain only the unparsed entry {@link FTPFile#getRawListing()} + * and {@link FTPFile#isValid()} will return {@code false} + * @param saveUnparseable if true, then create FTPFile entries if parsing fails + * @since 3.4 + */ + public void setUnparseableEntries(boolean saveUnparseable) { + this.saveUnparseableEntries = saveUnparseable; + } + + /** + * @return true if list parsing should return FTPFile entries even for unparseable response lines + *

+ * If true, the FTPFile for any unparseable entries will contain only the unparsed entry + * {@link FTPFile#getRawListing()} and {@link FTPFile#isValid()} will return {@code false} + * @since 3.4 + */ + public boolean getUnparseableEntries() { + return this.saveUnparseableEntries; + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPCmd.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPCmd.java new file mode 100644 index 00000000..bd020ed2 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPCmd.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; + +/** +* @since 3.3 + */ +public enum FTPCmd { + ABOR, + ACCT, + ALLO, + APPE, + CDUP, + CWD, + DELE, + EPRT, + EPSV, + FEAT, + HELP, + LIST, + MDTM, + MFMT, + MKD, + MLSD, + MLST, + MODE, + NLST, + NOOP, + PASS, + PASV, + PORT, + PWD, + QUIT, + REIN, + REST, + RETR, + RMD, + RNFR, + RNTO, + SITE, + SMNT, + STAT, + STOR, + STOU, + STRU, + SYST, + TYPE, + USER, + ; + + // Aliases + + public static final FTPCmd ABORT = ABOR; + public static final FTPCmd ACCOUNT = ACCT; + public static final FTPCmd ALLOCATE = ALLO; + public static final FTPCmd APPEND = APPE; + public static final FTPCmd CHANGE_TO_PARENT_DIRECTORY = CDUP; + public static final FTPCmd CHANGE_WORKING_DIRECTORY = CWD; + public static final FTPCmd DATA_PORT = PORT; + public static final FTPCmd DELETE = DELE; + public static final FTPCmd FEATURES = FEAT; + public static final FTPCmd FILE_STRUCTURE = STRU; + public static final FTPCmd GET_MOD_TIME = MDTM; + public static final FTPCmd LOGOUT = QUIT; + public static final FTPCmd MAKE_DIRECTORY = MKD; + public static final FTPCmd MOD_TIME = MDTM; + public static final FTPCmd NAME_LIST = NLST; + public static final FTPCmd PASSIVE = PASV; + public static final FTPCmd PASSWORD = PASS; + public static final FTPCmd PRINT_WORKING_DIRECTORY = PWD; + public static final FTPCmd REINITIALIZE = REIN; + public static final FTPCmd REMOVE_DIRECTORY = RMD; + public static final FTPCmd RENAME_FROM = RNFR; + public static final FTPCmd RENAME_TO = RNTO; + public static final FTPCmd REPRESENTATION_TYPE = TYPE; + public static final FTPCmd RESTART = REST; + public static final FTPCmd RETRIEVE = RETR; + public static final FTPCmd SET_MOD_TIME = MFMT; + public static final FTPCmd SITE_PARAMETERS = SITE; + public static final FTPCmd STATUS = STAT; + public static final FTPCmd STORE = STOR; + public static final FTPCmd STORE_UNIQUE = STOU; + public static final FTPCmd STRUCTURE_MOUNT = SMNT; + public static final FTPCmd SYSTEM = SYST; + public static final FTPCmd TRANSFER_MODE = MODE; + public static final FTPCmd USERNAME = USER; + + /** + * Retrieve the FTP protocol command string corresponding to a specified + * command code. + * + * @return The FTP protcol command string corresponding to a specified + * command code. + */ + public final String getCommand() + { + return this.name(); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPCommand.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPCommand.java new file mode 100644 index 00000000..c5739d82 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPCommand.java @@ -0,0 +1,171 @@ +/* + * 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. + * + * @deprecated use {@link FTPCmd} instead + */ +@Deprecated +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; + /** @since 2.2 */ + public static final int FEAT = 34; + /** @since 2.2 */ + public static final int MFMT = 35; + /** @since 2.2 */ + public static final int EPSV = 36; + /** @since 2.2 */ + public static final int EPRT = 37; + + /** + * Machine parseable list for a directory + * @since 3.0 + */ + public static final int MLSD = 38; + + /** + * Machine parseable list for a single file + * @since 3.0 + */ + public static final int MLST = 39; + + // Must agree with final entry above; used to check array size + private static final int LAST = MLST; + + 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; + + /** @since 2.2 */ + public static final int FEATURES = FEAT; + /** @since 2.2 */ + public static final int GET_MOD_TIME = MDTM; + /** @since 2.2 */ + public static final int SET_MOD_TIME = MFMT; + + // Cannot be instantiated + private FTPCommand() + {} + + private 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", "MDTM", "FEAT", "MFMT", + "EPSV", "EPRT", "MLSD", "MLST" }; + + + + // default access needed for Unit test + static void checkArray(){ + int expectedLength = LAST+1; + if (_commands.length != expectedLength) { + throw new RuntimeException("Incorrect _commands array. Should have length " + +expectedLength+" found "+_commands.length); + } + } + + /** + * 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/Aria/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java new file mode 100644 index 00000000..e6857bf6 --- /dev/null +++ b/Aria/src/main/java/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 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. + * + * @see FTP + * @see FTPClient + ***/ + +public class FTPConnectionClosedException extends IOException +{ + + private static final long serialVersionUID = 3500547241659379952L; + + /*** 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/Aria/src/main/java/org/apache/commons/net/ftp/FTPFile.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFile.java new file mode 100644 index 00000000..b208e276 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFile.java @@ -0,0 +1,536 @@ +/* + * 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; +import java.util.Date; +import java.util.Formatter; +import java.util.TimeZone; + +/*** + * The FTPFile class is used to represent information about files stored + * on an FTP server. + * + * @see FTPFileEntryParser + * @see FTPClient#listFiles + ***/ + +public class FTPFile implements Serializable +{ + private static final long serialVersionUID = 9010790363003271996L; + + /** 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; + + private int _type, _hardLinkCount; + private long _size; + private String _rawListing, _user, _group, _name, _link; + private Calendar _date; + // If this is null, then list entry parsing failed + private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION] + + /*** Creates an empty FTPFile. ***/ + public FTPFile() + { + _permissions = new boolean[3][3]; + _type = UNKNOWN_TYPE; + // init these to values that do not occur in listings + // so can distinguish which fields are unset + _hardLinkCount = 0; // 0 is invalid as a link count + _size = -1; // 0 is valid, so use -1 + _user = ""; + _group = ""; + _date = null; + _name = null; + } + + /** + * Constructor for use by {@link FTPListParseEngine} only. + * Used to create FTPFile entries for failed parses + * @param rawListing line that could not be parsed. + * @since 3.4 + */ + FTPFile(String rawListing) + { + _permissions = null; // flag that entry is invalid + _rawListing = rawListing; + _type = UNKNOWN_TYPE; + // init these to values that do not occur in listings + // so can distinguish which fields are unset + _hardLinkCount = 0; // 0 is invalid as a link count + _size = -1; // 0 is valid, so use -1 + _user = ""; + _group = ""; + _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); + } + + /** + * Used to indicate whether an entry is valid or not. + * If the entry is invalid, only the {@link #getRawListing()} method will be useful. + * Other methods may fail. + * + * Used in conjunction with list parsing that preseverves entries that failed to parse. + * @see FTPClientConfig#setUnparseableEntries(boolean) + * @return true if the entry is valid + * @since 3.4 + */ + public boolean isValid() { + return (_permissions != null); + } + + /*** + * 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. + * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range + ***/ + 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) + * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range + * @return true if {@link #isValid()} is {@code true &&} the associated permission is set; + * {@code false} otherwise. + ***/ + public boolean hasPermission(int access, int permission) + { + if (_permissions == null) { + return false; + } + return _permissions[access][permission]; + } + + /*** + * Returns a string representation of the FTPFile information. + * + * @return A string representation of the FTPFile information. + */ + @Override + public String toString() + { + return getRawListing(); + } + + /*** + * Returns a string representation of the FTPFile information. + * This currently mimics the Unix listing format. + * This method uses the timezone of the Calendar entry, which is + * the server time zone (if one was provided) otherwise it is + * the local time zone. + *

+ * Note: if the instance is not valid {@link #isValid()}, no useful + * information can be returned. In this case, use {@link #getRawListing()} + * instead. + * + * @return A string representation of the FTPFile information. + * @since 3.0 + */ + public String toFormattedString() + { + return toFormattedString(null); + } + + /** + * Returns a string representation of the FTPFile information. + * This currently mimics the Unix listing format. + * This method allows the Calendar time zone to be overridden. + *

+ * Note: if the instance is not valid {@link #isValid()}, no useful + * information can be returned. In this case, use {@link #getRawListing()} + * instead. + * @param timezone the timezone to use for displaying the time stamp + * If {@code null}, then use the Calendar entry timezone + * @return A string representation of the FTPFile information. + * @since 3.4 + */ + public String toFormattedString(final String timezone) + { + + if (!isValid()) { + return "[Invalid: could not parse file entry]"; + } + StringBuilder sb = new StringBuilder(); + Formatter fmt = new Formatter(sb); + sb.append(formatType()); + sb.append(permissionToString(USER_ACCESS)); + sb.append(permissionToString(GROUP_ACCESS)); + sb.append(permissionToString(WORLD_ACCESS)); + fmt.format(" %4d", Integer.valueOf(getHardLinkCount())); + fmt.format(" %-8s %-8s", getUser(), getGroup()); + fmt.format(" %8d", Long.valueOf(getSize())); + Calendar timestamp = getTimestamp(); + if (timestamp != null) { + if (timezone != null) { + TimeZone newZone = TimeZone.getTimeZone(timezone); + if (!newZone.equals(timestamp.getTimeZone())){ + Date original = timestamp.getTime(); + Calendar newStamp = Calendar.getInstance(newZone); + newStamp.setTime(original); + timestamp = newStamp; + } + } + fmt.format(" %1$tY-%1$tm-%1$td", timestamp); + // Only display time units if they are present + if (timestamp.isSet(Calendar.HOUR_OF_DAY)) { + fmt.format(" %1$tH", timestamp); + if (timestamp.isSet(Calendar.MINUTE)) { + fmt.format(":%1$tM", timestamp); + if (timestamp.isSet(Calendar.SECOND)) { + fmt.format(":%1$tS", timestamp); + if (timestamp.isSet(Calendar.MILLISECOND)) { + fmt.format(".%1$tL", timestamp); + } + } + } + fmt.format(" %1$tZ", timestamp); + } + } + sb.append(' '); + sb.append(getName()); + fmt.close(); + return sb.toString(); + } + + private char formatType(){ + switch(_type) { + case FILE_TYPE: + return '-'; + case DIRECTORY_TYPE: + return 'd'; + case SYMBOLIC_LINK_TYPE: + return 'l'; + default: + return '?'; + } + } + + private String permissionToString(int access ){ + StringBuilder sb = new StringBuilder(); + if (hasPermission(access, READ_PERMISSION)) { + sb.append('r'); + } else { + sb.append('-'); + } + if (hasPermission(access, WRITE_PERMISSION)) { + sb.append('w'); + } else { + sb.append('-'); + } + if (hasPermission(access, EXECUTE_PERMISSION)) { + sb.append('x'); + } else { + sb.append('-'); + } + return sb.toString(); + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java new file mode 100644 index 00000000..48675a57 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.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.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 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 uses the 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 second example uses the 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 third example uses the 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");
+ * 
+ * + * For an alternative approach, see the {@link FTPListParseEngine} class + * which provides iterative access. + * + * @version $Id: FTPFileEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ + * @see FTPFile + * @see FTPClient#listFiles() + */ +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. + * @throws 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/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java new file mode 100644 index 00000000..05c45c55 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java @@ -0,0 +1,79 @@ +/* + * 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; + +/** + * 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 delimits 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. + * @throws IOException thrown on any IO Error reading from the reader. + */ + @Override + 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 does nothing. + * + * @param original Original list after it has been created from the server stream + * + * @return original unmodified. + */ + @Override + public List preParse(List original) { + return original; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java new file mode 100644 index 00000000..16f9c130 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Perform filtering on FTPFile entries. + * @since 2.2 + */ +public interface FTPFileFilter { + /** + * Checks if an FTPFile entry should be included or not. + * + * @param file entry to be checked for inclusion. May be null. + * @return true if the file is to be included, false otherwise + */ + public boolean accept(FTPFile file); +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java new file mode 100644 index 00000000..29eba603 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * Implements some simple FTPFileFilter classes. + * @since 2.2 + */ +public class FTPFileFilters { + + /** + * Accepts all FTPFile entries, including null. + */ + public static final FTPFileFilter ALL = new FTPFileFilter() { + @Override + public boolean accept(FTPFile file) { + return true; + } + }; + + /** + * Accepts all non-null FTPFile entries. + */ + public static final FTPFileFilter NON_NULL = new FTPFileFilter() { + @Override + public boolean accept(FTPFile file) { + return file != null; + } + }; + + /** + * Accepts all (non-null) FTPFile directory entries. + */ + public static final FTPFileFilter DIRECTORIES = new FTPFileFilter() { + @Override + public boolean accept(FTPFile file) { + return file != null && file.isDirectory(); + } + }; + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java new file mode 100644 index 00000000..52df2c83 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java @@ -0,0 +1,202 @@ +/* + * 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.io.OutputStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.Inet6Address; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.net.util.Base64; + +/** + * Experimental attempt at FTP client that tunnels over an HTTP proxy connection. + * + * @since 2.2 + */ +public class FTPHTTPClient extends FTPClient { + private final String proxyHost; + private final int proxyPort; + private final String proxyUsername; + private final String proxyPassword; + + private static final byte[] CRLF={'\r', '\n'}; + private final Base64 base64 = new Base64(); + + private String tunnelHost; // Save the host when setting up a tunnel (needed for EPSV) + + public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUsername = proxyUser; + this.proxyPassword = proxyPass; + this.tunnelHost = null; + } + + public FTPHTTPClient(String proxyHost, int proxyPort) { + this(proxyHost, proxyPort, null, null); + } + + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if connection mode is not passive + * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead + */ + // Kept to maintain binary compatibility + // Not strictly necessary, but Clirr complains even though there is a super-impl + @Override + @Deprecated + protected Socket _openDataConnection_(int command, String arg) + throws IOException { + return super._openDataConnection_(command, arg); + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if connection mode is not passive + * @since 3.1 + */ + @Override + protected Socket _openDataConnection_(String command, String arg) + throws IOException { + //Force local passive mode, active mode not supported by through proxy + if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { + throw new IllegalStateException("Only passive connection mode supported"); + } + + final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; + String passiveHost = null; + + boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) { + _parseExtendedPassiveModeReply(_replyLines.get(0)); + passiveHost = this.tunnelHost; + } else { + if (isInet6Address) { + return null; // Must use EPSV for IPV6 + } + // If EPSV failed on IPV4, revert to PASV + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { + return null; + } + _parsePassiveModeReply(_replyLines.get(0)); + passiveHost = this.getPassiveHost(); + } + + Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort); + InputStream is = socket.getInputStream(); + OutputStream os = socket.getOutputStream(); + tunnelHandshake(passiveHost, this.getPassivePort(), is, os); + if ((getRestartOffset() > 0) && !restart(getRestartOffset())) { + socket.close(); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { + socket.close(); + return null; + } + + return socket; + } + + @Override + public void connect(String host, int port) throws SocketException, IOException { + + _socket_ = _socketFactory_.createSocket(proxyHost, proxyPort); + _input_ = _socket_.getInputStream(); + _output_ = _socket_.getOutputStream(); + Reader socketIsReader; + try { + socketIsReader = tunnelHandshake(host, port, _input_, _output_); + } + catch (Exception e) { + IOException ioe = new IOException("Could not connect to " + host+ " using port " + port); + ioe.initCause(e); + throw ioe; + } + super._connectAction_(socketIsReader); + } + + private BufferedReader tunnelHandshake(String host, int port, InputStream input, OutputStream output) throws IOException, + UnsupportedEncodingException { + final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1"; + final String hostString = "Host: " + host + ":" + port; + + this.tunnelHost = host; + output.write(connectString.getBytes("UTF-8")); // TODO what is the correct encoding? + output.write(CRLF); + output.write(hostString.getBytes("UTF-8")); + output.write(CRLF); + + if (proxyUsername != null && proxyPassword != null) { + final String auth = proxyUsername + ":" + proxyPassword; + final String header = "Proxy-Authorization: Basic " + + base64.encodeToString(auth.getBytes("UTF-8")); + output.write(header.getBytes("UTF-8")); + } + output.write(CRLF); + + List response = new ArrayList(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(input, getCharset())); + + for (String line = reader.readLine(); line != null + && line.length() > 0; line = reader.readLine()) { + response.add(line); + } + + int size = response.size(); + if (size == 0) { + throw new IOException("No response from proxy"); + } + + String code = null; + String resp = response.get(0); + if (resp.startsWith("HTTP/") && resp.length() >= 12) { + code = resp.substring(9, 12); + } else { + throw new IOException("Invalid response from proxy: " + resp); + } + + if (!"200".equals(code)) { + StringBuilder msg = new StringBuilder(); + msg.append("HTTPTunnelConnector: connection failed\r\n"); + msg.append("Response received from the proxy:\r\n"); + for (String line : response) { + msg.append(line); + msg.append("\r\n"); + } + throw new IOException(msg.toString()); + } + return reader; + } +} + + diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java new file mode 100644 index 00000000..29e1bae4 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java @@ -0,0 +1,329 @@ +/* + * 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.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.net.util.Charsets; + + +/** + * 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 1747119 2016-06-07 02:22:24Z ggregory $ + */ +public class FTPListParseEngine { + private List entries = new LinkedList(); + private ListIterator _internalIterator = entries.listIterator(); + + private final FTPFileEntryParser parser; + // Should invalid files (parse failures) be allowed? + private final boolean saveUnparseableEntries; + + public FTPListParseEngine(FTPFileEntryParser parser) { + this(parser, null); + } + + /** + * Intended for use by FTPClient only + * @since 3.4 + */ + FTPListParseEngine(FTPFileEntryParser parser, FTPClientConfig configuration) { + this.parser = parser; + if (configuration != null) { + this.saveUnparseableEntries = configuration.getUnparseableEntries(); + } else { + this.saveUnparseableEntries = false; + } + } + + /** + * handle the initial 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. + * @param encoding the encoding to be used for reading the stream + * + * @throws 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(); + } + + /** + * 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. + * + * @throws IOException + * thrown on any failure to read the stream + */ + private void readStream(InputStream stream, String encoding) throws IOException + { + BufferedReader reader = new BufferedReader( + new InputStreamReader(stream, Charsets.toCharset(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); + if (temp == null && saveUnparseableEntries) { + temp = new FTPFile(entry); + } + tmpResults.add(temp); + count--; + } + return tmpResults.toArray(new FTPFile[tmpResults.size()]); + + } + + /** + * 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); + if (temp == null && saveUnparseableEntries) { + temp = new FTPFile(entry); + } + tmpResults.add(0,temp); + count--; + } + return tmpResults.toArray(new FTPFile[tmpResults.size()]); + } + + /** + * 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. + * None of the entries will be null + * @throws IOException - not ever thrown, may be removed in a later release + */ + public FTPFile[] getFiles() + throws IOException // TODO remove; not actually thrown + { + return getFiles(FTPFileFilters.NON_NULL); + } + + /** + * Returns an array of FTPFile objects containing the whole list of + * files returned by the server as read by this object's parser. + * The files are filtered before being added to the array. + * + * @param filter FTPFileFilter, must not be null. + * + * @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, or use the + * a filter such as {@link FTPFileFilters#NON_NULL} which does not + * allow null entries. + * @since 2.2 + * @throws IOException - not ever thrown, may be removed in a later release + */ + public FTPFile[] getFiles(FTPFileFilter filter) + throws IOException // TODO remove; not actually thrown + { + List tmpResults = new ArrayList(); + Iterator iter = this.entries.iterator(); + while (iter.hasNext()) { + String entry = iter.next(); + FTPFile temp = this.parser.parseFTPEntry(entry); + if (temp == null && saveUnparseableEntries) { + temp = new FTPFile(entry); + } + if (filter.accept(temp)){ + tmpResults.add(temp); + } + } + return tmpResults.toArray(new FTPFile[tmpResults.size()]); + + } + + /** + * 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(); + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** + * Do not use. + * @param stream the stream from which to read + * @throws IOException on error + * @deprecated use {@link #readServerList(InputStream, String)} instead + */ + @Deprecated + public void readServerList(InputStream stream) + throws IOException + { + readServerList(stream, null); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPReply.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPReply.java new file mode 100644 index 00000000..37d5df50 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPReply.java @@ -0,0 +1,201 @@ +/* + * 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. + *

+ * TODO replace with an enum + ***/ + +public final class FTPReply +{ + + public static final int RESTART_MARKER = 110; + public static final int SERVICE_NOT_READY = 120; + public static final int DATA_CONNECTION_ALREADY_OPEN = 125; + public static final int FILE_STATUS_OK = 150; + public static final int COMMAND_OK = 200; + public static final int COMMAND_IS_SUPERFLUOUS = 202; + public static final int SYSTEM_STATUS = 211; + public static final int DIRECTORY_STATUS = 212; + public static final int FILE_STATUS = 213; + public static final int HELP_MESSAGE = 214; + public static final int NAME_SYSTEM_TYPE = 215; + public static final int SERVICE_READY = 220; + public static final int SERVICE_CLOSING_CONTROL_CONNECTION = 221; + public static final int DATA_CONNECTION_OPEN = 225; + public static final int CLOSING_DATA_CONNECTION = 226; + public static final int ENTERING_PASSIVE_MODE = 227; + /** @since 2.2 */ + public static final int ENTERING_EPSV_MODE = 229; + public static final int USER_LOGGED_IN = 230; + public static final int FILE_ACTION_OK = 250; + public static final int PATHNAME_CREATED = 257; + public static final int NEED_PASSWORD = 331; + public static final int NEED_ACCOUNT = 332; + public static final int FILE_ACTION_PENDING = 350; + public static final int SERVICE_NOT_AVAILABLE = 421; + public static final int CANNOT_OPEN_DATA_CONNECTION = 425; + public static final int TRANSFER_ABORTED = 426; + public static final int FILE_ACTION_NOT_TAKEN = 450; + public static final int ACTION_ABORTED = 451; + public static final int INSUFFICIENT_STORAGE = 452; + public static final int UNRECOGNIZED_COMMAND = 500; + public static final int SYNTAX_ERROR_IN_ARGUMENTS = 501; + public static final int COMMAND_NOT_IMPLEMENTED = 502; + public static final int BAD_COMMAND_SEQUENCE = 503; + public static final int COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER = 504; + public static final int NOT_LOGGED_IN = 530; + public static final int NEED_ACCOUNT_FOR_STORING_FILES = 532; + public static final int FILE_UNAVAILABLE = 550; + public static final int PAGE_TYPE_UNKNOWN = 551; + public static final int STORAGE_ALLOCATION_EXCEEDED = 552; + public static final int FILE_NAME_NOT_ALLOWED = 553; + + // FTPS Reply Codes + + /** @since 2.0 */ + public static final int SECURITY_DATA_EXCHANGE_COMPLETE = 234; + /** @since 2.0 */ + public static final int SECURITY_DATA_EXCHANGE_SUCCESSFULLY = 235; + /** @since 2.0 */ + public static final int SECURITY_MECHANISM_IS_OK = 334; + /** @since 2.0 */ + public static final int SECURITY_DATA_IS_ACCEPTABLE = 335; + /** @since 2.0 */ + public static final int UNAVAILABLE_RESOURCE = 431; + /** @since 2.2 */ + public static final int BAD_TLS_NEGOTIATION_OR_DATA_ENCRYPTION_REQUIRED = 522; + /** @since 2.0 */ + public static final int DENIED_FOR_POLICY_REASONS = 533; + /** @since 2.0 */ + public static final int REQUEST_DENIED = 534; + /** @since 2.0 */ + public static final int FAILED_SECURITY_CHECK = 535; + /** @since 2.0 */ + public static final int REQUESTED_PROT_LEVEL_NOT_SUPPORTED = 536; + + // IPv6 error codes + // Note this is also used as an FTPS error code reply + /** @since 2.2 */ + public static final int EXTENDED_PORT_FAILURE = 522; + + // 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); + } + + /** + * Determine if a reply code is a protected response. + * @param reply The reply code to test. + * @return True if a reply code is a protected response, false + * if not. + * @since 3.0 + */ + public static boolean isProtectedReplyCode(int reply) + { + // actually, only 3 protected reply codes are + // defined in RFC 2228: 631, 632 and 633. + return (reply >= 600 && reply < 700); + } + + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPSClient.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSClient.java new file mode 100644 index 00000000..51152697 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSClient.java @@ -0,0 +1,925 @@ +/* + * 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 javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import org.apache.commons.net.util.Base64; +import org.apache.commons.net.util.SSLContextUtils; +import org.apache.commons.net.util.SSLSocketUtils; +import org.apache.commons.net.util.TrustManagerUtils; + +/** + * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to + * see wire-level SSL details. + * + * Warning: the hostname is not verified against the certificate by default, use + * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} + * (on Java 1.7+) to enable verification. Verification is only performed on client mode connections. + * @version $Id: FTPSClient.java 1747829 2016-06-11 00:57:57Z sebb $ + * @since 2.0 + */ +public class FTPSClient extends FTPClient { + +// From http://www.iana.org/assignments/port-numbers + +// ftps-data 989/tcp ftp protocol, data, over TLS/SSL +// ftps-data 989/udp ftp protocol, data, over TLS/SSL +// ftps 990/tcp ftp protocol, control, over TLS/SSL +// ftps 990/udp ftp protocol, control, over TLS/SSL + + public static final int DEFAULT_FTPS_DATA_PORT = 989; + public static final int DEFAULT_FTPS_PORT = 990; + + /** The value that I can set in PROT command (C = Clear, P = Protected) */ + private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"}; + /** Default PROT Command */ + private static final String DEFAULT_PROT = "C"; + /** Default secure socket protocol name, i.e. TLS */ + private static final String DEFAULT_PROTOCOL = "TLS"; + + /** The AUTH (Authentication/Security Mechanism) command. */ + private static final String CMD_AUTH = "AUTH"; + /** The ADAT (Authentication/Security Data) command. */ + private static final String CMD_ADAT = "ADAT"; + /** The PROT (Data Channel Protection Level) command. */ + private static final String CMD_PROT = "PROT"; + /** The PBSZ (Protection Buffer Size) command. */ + private static final String CMD_PBSZ = "PBSZ"; + /** The MIC (Integrity Protected Command) command. */ + private static final String CMD_MIC = "MIC"; + /** The CONF (Confidentiality Protected Command) command. */ + private static final String CMD_CONF = "CONF"; + /** The ENC (Privacy Protected Command) command. */ + private static final String CMD_ENC = "ENC"; + /** The CCC (Clear Command Channel) command. */ + private static final String CMD_CCC = "CCC"; + + /** The security mode. (True - Implicit Mode / False - Explicit Mode) */ + private final boolean isImplicit; + /** The secure socket protocol to be used, e.g. SSL/TLS. */ + private final String protocol; + /** The AUTH Command value */ + private String auth = DEFAULT_PROTOCOL; + /** The context object. */ + private SSLContext context; + /** The socket object. */ + private Socket plainSocket; + /** Controls whether a new SSL session may be established by this socket. Default true. */ + 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, default validate only + * {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}. + */ + private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager(); + + /** The {@link KeyManager}, default null (i.e. use system default). */ + private KeyManager keyManager = null; + + /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ + private HostnameVerifier hostnameVerifier = null; + + /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ + private boolean tlsEndpointChecking; + + /** + * Constructor for FTPSClient, calls {@link #FTPSClient(String, boolean)}. + * + * Sets protocol to {@link #DEFAULT_PROTOCOL} - i.e. TLS - and security mode to explicit (isImplicit = false) + */ + public FTPSClient() { + this(DEFAULT_PROTOCOL, false); + } + + /** + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS + * Calls {@link #FTPSClient(String, boolean)} + * @param isImplicit The security mode (Implicit/Explicit). + */ + public FTPSClient(boolean isImplicit) { + this(DEFAULT_PROTOCOL, isImplicit); + } + + /** + * Constructor for FTPSClient, using explict mode, calls {@link #FTPSClient(String, boolean)}. + * + * @param protocol the protocol to use + */ + public FTPSClient(String protocol) { + this(protocol, false); + } + + /** + * Constructor for FTPSClient allowing specification of protocol + * and security mode. If isImplicit is true, the port is set to + * {@link #DEFAULT_FTPS_PORT} i.e. 990. + * The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} + * @param protocol the protocol + * @param isImplicit The security mode(Implicit/Explicit). + */ + public FTPSClient(String protocol, boolean isImplicit) { + super(); + this.protocol = protocol; + this.isImplicit = isImplicit; + if (isImplicit) { + setDefaultPort(DEFAULT_FTPS_PORT); + } + } + + /** + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS + * The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} + * @param isImplicit The security mode(Implicit/Explicit). + * @param context A pre-configured SSL Context + */ + public FTPSClient(boolean isImplicit, SSLContext context) { + this(DEFAULT_PROTOCOL, isImplicit); + this.context = context; + } + + /** + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS + * and isImplicit {@code false} + * Calls {@link #FTPSClient(boolean, SSLContext)} + * @param context A pre-configured SSL Context + */ + public FTPSClient(SSLContext context) { + this(false, context); + } + + + /** + * 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. + */ + protected void execAUTH() throws SSLException, IOException { + int replyCode = sendCommand(CMD_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) { + context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); + } + } + + /** + * SSL/TLS negotiation. Acquires an SSL socket of a control + * connection and carries out handshake processing. + * @throws IOException If server negotiation fails + */ + protected void sslNegotiation() throws IOException { + plainSocket = _socket_; + initSslContext(); + + SSLSocketFactory ssf = context.getSocketFactory(); + String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress(); + int port = _socket_.getPort(); + SSLSocket socket = + (SSLSocket) ssf.createSocket(_socket_, host, port, false); + socket.setEnableSessionCreation(isCreation); + socket.setUseClientMode(isClientMode); + + // client mode + if (isClientMode) { + if (tlsEndpointChecking) { + SSLSocketUtils.enableEndpointNameVerification(socket); + } + } else { // server mode + socket.setNeedClientAuth(isNeedClientAuth); + socket.setWantClientAuth(isWantClientAuth); + } + + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + if (suites != null) { + socket.setEnabledCipherSuites(suites); + } + socket.startHandshake(); + + // TODO the following setup appears to duplicate that in the super class methods + _socket_ = socket; + _controlInput_ = new BufferedReader(new InputStreamReader( + socket .getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter( + socket.getOutputStream(), getControlEncoding())); + + if (isClientMode) { + if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { + throw new SSLHandshakeException("Hostname doesn't match certificate"); + } + } + } + + /** + * 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. + * @see org.apache.commons.net.util.KeyManagerUtils + */ + public void setKeyManager(KeyManager keyManager) { + this.keyManager = keyManager; + } + + /** + * Controls whether a new 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 the underlying {@link Socket} instance is not SSL-enabled (i.e. an + * instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled, + * this returns 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns 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. Called before 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null. + * @return An array of cipher suite names, or null + */ + 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null. + * @return An array of protocols, or null + */ + 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 the server reply code does not equal "200". + * @throws IOException If an I/O error occurs while sending + * the command. + * @see #parsePBSZ(long) + */ + public void execPBSZ(long pbsz) throws SSLException, IOException { + if (pbsz < 0 || 4294967295L < pbsz) { // 32-bit unsigned number + throw new IllegalArgumentException(); + } + int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz)); + if (FTPReply.COMMAND_OK != status) { + throw new SSLException(getReplyString()); + } + } + + /** + * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. + * Issues the command and parses the response to return the negotiated value. + * + * @param pbsz Protection Buffer Size. + * @throws SSLException If the server reply code does not equal "200". + * @throws IOException If an I/O error occurs while sending + * the command. + * @return the negotiated value. + * @see #execPBSZ(long) + * @since 3.0 + */ + public long parsePBSZ(long pbsz) throws SSLException, IOException { + execPBSZ(pbsz); + long minvalue = pbsz; + String remainder = extractPrefixedData("PBSZ=", getReplyString()); + if (remainder != null) { + long replysz = Long.parseLong(remainder); + if (replysz < minvalue) { + minvalue = replysz; + } + } + return minvalue; + } + + /** + * PROT command. + *

    + *
  • C - Clear
  • + *
  • S - Safe(SSL protocol only)
  • + *
  • E - Confidential(SSL protocol only)
  • + *
  • P - Private
  • + *
+ * N.B. the method calls + * {@link #setSocketFactory(javax.net.SocketFactory)} and + * {@link #setServerSocketFactory(javax.net.ServerSocketFactory)} + * + * @param prot Data Channel Protection Level, if {@code null}, use {@link #DEFAULT_PROT}. + * @throws SSLException If the server reply code does not equal {@code 200}. + * @throws IOException If an I/O error occurs while 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(CMD_PROT, prot)) { + throw new SSLException(getReplyString()); + } + if (DEFAULT_PROT.equals(prot)) { + setSocketFactory(null); + setServerSocketFactory(null); + } else { + setSocketFactory(new FTPSSocketFactory(context)); + setServerSocketFactory(new FTPSServerSocketFactory(context)); + initSslContext(); + } + } + + /** + * Check the value that can be 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 (String element : PROT_COMMAND_VALUE) + { + if (element.equals(prot)) { + return true; + } + } + return false; + } + + /** + * Send an FTP command. + * A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} + * instance to be assigned to a plain {@link Socket} + * @param command The FTP command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @throws SSLException if a CCC command fails + * @see FTP#sendCommand(String) + */ + // Would like to remove this method, but that will break any existing clients that are using CCC + @Override + public int sendCommand(String command, String args) throws IOException { + int repCode = super.sendCommand(command, args); + /* If CCC is issued, restore socket i/o streams to unsecured versions */ + if (CMD_CCC.equals(command)) { + if (FTPReply.COMMAND_OK == repCode) { + _socket_.close(); + _socket_ = plainSocket; + _controlInput_ = new BufferedReader( + new InputStreamReader( + _socket_ .getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter( + new OutputStreamWriter( + _socket_.getOutputStream(), getControlEncoding())); + } else { + throw new SSLException(getReplyString()); + } + } + return repCode; + } + + /** + * Returns a socket of the data connection. + * Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * @param command The int 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 arguments. + * @return 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 FTPClient#_openDataConnection_(int, String) + * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead + */ + @Override + // Strictly speaking this is not needed, but it works round a Clirr bug + // So rather than invoke the parent code, we do it here + @Deprecated + protected Socket _openDataConnection_(int command, String arg) + throws IOException { + return _openDataConnection_(FTPCommand.getCommand(command), arg); + } + + /** + * Returns a socket of the data connection. + * Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * @param command The textual 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 arguments. + * @return 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 FTPClient#_openDataConnection_(int, String) + * @since 3.2 + */ + @Override + protected Socket _openDataConnection_(String command, String arg) + throws IOException { + Socket socket = super._openDataConnection_(command, arg); + _prepareDataSocket_(socket); + if (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; + } + + /** + * Performs any custom initialization for a newly created SSLSocket (before + * the SSL handshake happens). + * Called by {@link #_openDataConnection_(int, String)} immediately + * after creating the socket. + * The default implementation is a no-op + * @param socket the socket to set up + * @throws IOException on error + * @since 3.1 + */ + protected void _prepareDataSocket_(Socket socket) + throws IOException { + } + + /** + * Get the currently configured {@link TrustManager}. + * + * @return A TrustManager instance. + */ + public TrustManager getTrustManager() { + return trustManager; + } + + /** + * Override the default {@link TrustManager} to use; if set to {@code null}, + * the default TrustManager from the JVM will be used. + * + * @param trustManager The TrustManager implementation to set, may be {@code null} + * @see org.apache.commons.net.util.TrustManagerUtils + */ + public void setTrustManager(TrustManager trustManager) { + this.trustManager = trustManager; + } + + /** + * Get the currently configured {@link HostnameVerifier}. + * The verifier is only used on client mode connections. + * @return A HostnameVerifier instance. + * @since 3.4 + */ + public HostnameVerifier getHostnameVerifier() + { + return hostnameVerifier; + } + + /** + * Override the default {@link HostnameVerifier} to use. + * The verifier is only used on client mode connections. + * @param newHostnameVerifier The HostnameVerifier implementation to set or null to disable. + * @since 3.4 + */ + public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) + { + hostnameVerifier = newHostnameVerifier; + } + + /** + * Return whether or not endpoint identification using the HTTPS algorithm + * on Java 1.7+ is enabled. The default behaviour is for this to be disabled. + * + * This check is only performed on client mode connections. + * + * @return True if enabled, false if not. + * @since 3.4 + */ + public boolean isEndpointCheckingEnabled() + { + return tlsEndpointChecking; + } + + /** + * Automatic endpoint identification checking using the HTTPS algorithm + * is supported on Java 1.7+. The default behaviour is for this to be disabled. + * + * This check is only performed on client mode connections. + * + * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. + * @since 3.4 + */ + public void setEndpointCheckingEnabled(boolean enable) + { + tlsEndpointChecking = enable; + } + + /** + * Closes the connection to the FTP server and restores + * connection parameters to the default values. + *

+ * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)} + * to reset the factories that may have been changed during the session, + * e.g. by {@link #execPROT(String)} + * @throws IOException If an error occurs while disconnecting. + * @since 3.0 + */ + @Override + public void disconnect() throws IOException + { + super.disconnect(); + if (plainSocket != null) { + plainSocket.close(); + } + setSocketFactory(null); + setServerSocketFactory(null); + } + + /** + * Send the AUTH command with the specified mechanism. + * @param mechanism The mechanism name to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execAUTH(String mechanism) throws IOException + { + return sendCommand(CMD_AUTH, mechanism); + } + + /** + * Send the ADAT command with the specified authentication data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execADAT(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_ADAT, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_ADAT); + } + } + + /** + * Send the CCC command to the server. + * The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned + * to a plain {@link Socket} instances + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execCCC() throws IOException + { + int repCode = sendCommand(CMD_CCC); +// This will be performed by sendCommand(String, String) +// if (FTPReply.isPositiveCompletion(repCode)) { +// _socket_.close(); +// _socket_ = plainSocket; +// _controlInput_ = new BufferedReader( +// new InputStreamReader( +// _socket_.getInputStream(), getControlEncoding())); +// _controlOutput_ = new BufferedWriter( +// new OutputStreamWriter( +// _socket_.getOutputStream(), getControlEncoding())); +// } + return repCode; + } + + /** + * Send the MIC command with the specified data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execMIC(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_MIC, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)? + } + } + + /** + * Send the CONF command with the specified data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execCONF(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_CONF, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)? + } + } + + /** + * Send the ENC command with the specified data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execENC(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_ENC, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)? + } + } + + /** + * Parses the given ADAT response line and base64-decodes the data. + * @param reply The ADAT reply to parse. + * @return the data in the reply, base64-decoded. + * @since 3.0 + */ + public byte[] parseADATReply(String reply) + { + if (reply == null) { + return null; + } else { + return Base64.decodeBase64(extractPrefixedData("ADAT=", reply)); + } + } + + /** + * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234 + * @param prefix the prefix to find + * @param reply where to find the prefix + * @return the remainder of the string after the prefix, or null if the prefix was not present. + */ + private String extractPrefixedData(String prefix, String reply) { + int idx = reply.indexOf(prefix); + if (idx == -1) { + return null; + } + // N.B. Cannot use trim before substring as leading space would affect the offset. + return reply.substring(idx+prefix.length()).trim(); + } + + // DEPRECATED - for API compatibility only - DO NOT USE + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String KEYSTORE_ALGORITHM; + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String TRUSTSTORE_ALGORITHM; + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String PROVIDER; + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String STORE_TYPE; + +} +/* kate: indent-width 4; replace-tabs on; */ diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java new file mode 100644 index 00000000..c6bade31 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSCommand.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; + +/** + * FTPS-specific commands. + * @since 2.0 + * @deprecated 3.0 DO NOT USE + */ +@Deprecated +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; + + private 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/Aria/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java new file mode 100644 index 00000000..abd382d0 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java @@ -0,0 +1,74 @@ +/* + * 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 javax.net.ServerSocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; + +/** + * Server socket factory for FTPS connections. + * @since 2.2 + */ +public class FTPSServerSocketFactory extends ServerSocketFactory { + + /** Factory for secure socket factories */ + private final SSLContext context; + + public FTPSServerSocketFactory(SSLContext context) { + this.context = context; + } + + // Override the default superclass method + @Override + public ServerSocket createServerSocket() throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket()); + } + + @Override + public ServerSocket createServerSocket(int port) throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket(port)); + } + + @Override + public ServerSocket createServerSocket(int port, int backlog) throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + } + + @Override + public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + } + + /** + * Sets the socket so newly accepted connections will use SSL client mode. + * + * @param socket the SSLServerSocket to initialise + * @return the socket + * @throws ClassCastException if socket is not an instance of SSLServerSocket + */ + public ServerSocket init(ServerSocket socket) { + ((SSLServerSocket) socket).setUseClientMode(true); + return socket; + } +} + diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java new file mode 100644 index 00000000..b148180a --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java @@ -0,0 +1,112 @@ +/* + * 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.Socket; +import java.net.UnknownHostException; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; + +/** + * + * Implementation of org.apache.commons.net.SocketFactory + * + * @since 2.0 + */ +public class FTPSSocketFactory extends SocketFactory { + + private final SSLContext context; + + public FTPSSocketFactory(SSLContext context) { + this.context = context; + } + + // Override the default implementation + @Override + public Socket createSocket() throws IOException{ + return this.context.getSocketFactory().createSocket(); + } + + @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); + } + + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** @param port the port + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead} */ + @Deprecated + public java.net.ServerSocket createServerSocket(int port) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port)); + } + + /** @param port the port + * @param backlog the backlog + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead} */ + @Deprecated + public java.net.ServerSocket createServerSocket(int port, int backlog) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + } + + /** @param port the port + * @param backlog the backlog + * @param ifAddress the interface + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress) instead} */ + @Deprecated + public java.net.ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + } + + /** @param socket the socket + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#init(java.net.ServerSocket)} */ + @Deprecated + public java.net.ServerSocket init(java.net.ServerSocket socket) throws IOException { + ((javax.net.ssl.SSLServerSocket) socket).setUseClientMode(true); + return socket; + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java new file mode 100644 index 00000000..56fbea23 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java @@ -0,0 +1,60 @@ +/* + * 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.X509TrustManager; + +/** + * Do not use. + * @since 2.0 + * @deprecated 3.0 use + * {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager() + * TrustManagerUtils#getValidateServerCertificateTrustManager()} instead + */ +@Deprecated +public class FTPSTrustManager implements X509TrustManager +{ + private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{}; + + /** + * No-op + */ + @Override + public void checkClientTrusted(X509Certificate[] certificates, String authType) + { + return; + } + + @Override + public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException + { + for (X509Certificate certificate : certificates) + { + certificate.checkValidity(); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return EMPTY_X509CERTIFICATE_ARRAY; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/package-info.java b/Aria/src/main/java/org/apache/commons/net/ftp/package-info.java new file mode 100644 index 00000000..496aba13 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * FTP and FTPS support classes + */ +package org.apache.commons.net.ftp; \ No newline at end of file diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java new file mode 100644 index 00000000..7c266431 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.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.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. + */ +public class CompositeFileEntryParser extends FTPFileEntryParserImpl +{ + private final FTPFileEntryParser[] ftpFileEntryParsers; + private FTPFileEntryParser cachedFtpFileEntryParser; + + public CompositeFileEntryParser(FTPFileEntryParser[] ftpFileEntryParsers) + { + super(); + + this.cachedFtpFileEntryParser = null; + this.ftpFileEntryParsers = ftpFileEntryParsers; + } + + @Override + public FTPFile parseFTPEntry(String listEntry) + { + if (cachedFtpFileEntryParser != null) + { + FTPFile matched = cachedFtpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) + { + return matched; + } + } + else + { + for (FTPFileEntryParser ftpFileEntryParser : ftpFileEntryParsers) + { + FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) + { + cachedFtpFileEntryParser = ftpFileEntryParser; + return matched; + } + } + } + return null; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java new file mode 100644 index 00000000..d61693ec --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java @@ -0,0 +1,128 @@ +/* + * 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 + */ +public abstract class ConfigurableFTPFileEntryParserImpl +extends RegexFTPFileEntryParserImpl +implements Configurable +{ + + private final FTPTimestampParser timestampParser; + + /** + * constructor for this abstract class. + * @param regex Regular expression used main parsing of the + * file listing. + */ + public ConfigurableFTPFileEntryParserImpl(String regex) + { + super(regex); + this.timestampParser = new FTPTimestampParserImpl(); + } + + /** + * constructor for this abstract class. + * @param regex Regular expression used main parsing of the + * file listing. + * @param flags the flags to apply, see + * {@link java.util.regex.Pattern#compile(String, int) Pattern#compile(String, int)}. Use 0 for none. + * @since 3.4 + */ + public ConfigurableFTPFileEntryParserImpl(String regex, int flags) + { + super(regex, flags); + 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. + * @throws ParseException on parse error + */ + 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. + */ + @Override + 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/Aria/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java new file mode 100644 index 00000000..19e4c8e3 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.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.util.regex.Pattern; + +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 +{ + + // Match a plain Java Identifier + private static final String JAVA_IDENTIFIER = "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*"; + // Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc. + private static final String JAVA_QUALIFIED_NAME = "("+JAVA_IDENTIFIER+"\\.)+"+JAVA_IDENTIFIER; + // Create the pattern, as it will be reused many times + private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME); + + /** + * 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 (default package is not allowed) 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_AS400 AS/400}
  • + *
  • {@link FTPClientConfig#SYST_VMS VMS}
  • + *
  • {@link FTPClientConfig#SYST_MVS MVS}
  • + *
  • {@link FTPClientConfig#SYST_NETWARE NETWARE}
  • + *
  • {@link FTPClientConfig#SYST_L8 TYPE:L8}
  • + *
+ * @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 + */ + @Override + public FTPFileEntryParser createFileEntryParser(String key) + { + if (key == null) { + throw new ParserInitializationException("Parser key cannot be null"); + } + return createFileEntryParser(key, null); + } + + // Common method to process both key and config parameters. + private FTPFileEntryParser createFileEntryParser(String key, FTPClientConfig config) { + FTPFileEntryParser parser = null; + + // Is the key a possible class name? + if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) { + try + { + Class parserClass = Class.forName(key); + try { + parser = (FTPFileEntryParser) parserClass.newInstance(); + } catch (ClassCastException e) { + throw new ParserInitializationException(parserClass.getName() + + " does not implement the interface " + + "org.apache.commons.net.ftp.FTPFileEntryParser.", e); + } catch (Exception e) { + throw new ParserInitializationException("Error initializing parser", e); + } catch (ExceptionInInitializerError e) { + throw new ParserInitializationException("Error initializing parser", e); + } + } catch (ClassNotFoundException e) { + // OK, assume it is an alias + } + } + + if (parser == null) { // Now try for aliases + String ukey = key.toUpperCase(java.util.Locale.ENGLISH); + if (ukey.indexOf(FTPClientConfig.SYST_UNIX_TRIM_LEADING) >= 0) + { + parser = new UnixFTPEntryParser(config, true); + } + // must check this after SYST_UNIX_TRIM_LEADING as it is a substring of it + else if (ukey.indexOf(FTPClientConfig.SYST_UNIX) >= 0) + { + parser = new UnixFTPEntryParser(config, false); + } + else if (ukey.indexOf(FTPClientConfig.SYST_VMS) >= 0) + { + parser = new VMSVersioningFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_NT) >= 0) + { + parser = createNTFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_OS2) >= 0) + { + parser = new OS2FTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_OS400) >= 0 || + ukey.indexOf(FTPClientConfig.SYST_AS400) >= 0) + { + parser = createOS400FTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_MVS) >= 0) + { + parser = new MVSFTPEntryParser(); // Does not currently support config parameter + } + else if (ukey.indexOf(FTPClientConfig.SYST_NETWARE) >= 0) + { + parser = new NetwareFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_MACOS_PETER) >= 0) + { + parser = new MacOsPeterFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0) + { + // L8 normally means Unix, but move it to the end for some L8 systems that aren't. + // This check should be last! + parser = new UnixFTPEntryParser(config); + } + else + { + throw new ParserInitializationException("Unknown parser type: " + key); + } + } + + if (parser instanceof Configurable) { + ((Configurable)parser).configure(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. + * @throws ParserInitializationException + * Thrown on any exception in instantiation + * @throws NullPointerException if {@code config} is {@code null} + * @since 1.4 + */ + @Override + public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) + throws ParserInitializationException + { + String key = config.getServerSystemKey(); + return createFileEntryParser(key, config); + } + + + public FTPFileEntryParser createUnixFTPEntryParser() + { + return new UnixFTPEntryParser(); + } + + public FTPFileEntryParser createVMSVersioningFTPEntryParser() + { + return new VMSVersioningFTPEntryParser(); + } + + public FTPFileEntryParser createNetwareFTPEntryParser() { + return new NetwareFTPEntryParser(); + } + + public FTPFileEntryParser createNTFTPEntryParser() + { + return createNTFTPEntryParser(null); + } + + /** + * Creates an NT FTP parser: if the config exists, and the system key equals + * {@link FTPClientConfig.SYST_NT} then a plain {@link NTFTPEntryParser} is used, + * otherwise a composite of {@link NTFTPEntryParser} and {@link UnixFTPEntryParser} is used. + * @param config the config to use, may be {@code null} + * @return the parser + */ + private FTPFileEntryParser createNTFTPEntryParser(FTPClientConfig config) + { + if (config != null && FTPClientConfig.SYST_NT.equals( + config.getServerSystemKey())) + { + return new NTFTPEntryParser(config); + } else { + // clone the config as it may be changed by the parsers (NET-602) + final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; + return new CompositeFileEntryParser(new FTPFileEntryParser[] + { + new NTFTPEntryParser(config), + new UnixFTPEntryParser(config2, + config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) + }); + } + } + + public FTPFileEntryParser createOS2FTPEntryParser() + { + return new OS2FTPEntryParser(); + } + + public FTPFileEntryParser createOS400FTPEntryParser() + { + return createOS400FTPEntryParser(null); + } + + /** + * Creates an OS400 FTP parser: if the config exists, and the system key equals + * {@link FTPClientConfig.SYST_OS400} then a plain {@link OS400FTPEntryParser} is used, + * otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used. + * @param config the config to use, may be {@code null} + * @return the parser + */ + private FTPFileEntryParser createOS400FTPEntryParser(FTPClientConfig config) + { + if (config != null && + FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) + { + return new OS400FTPEntryParser(config); + } else { + // clone the config as it may be changed by the parsers (NET-602) + final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; + return new CompositeFileEntryParser(new FTPFileEntryParser[] + { + new OS400FTPEntryParser(config), + new UnixFTPEntryParser(config2, + config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) + }); + } + } + + public FTPFileEntryParser createMVSEntryParser() + { + return new MVSFTPEntryParser(); + } + +} + diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java new file mode 100644 index 00000000..142cbf2d --- /dev/null +++ b/Aria/src/main/java/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 1741829 2016-05-01 00:24:44Z sebb $ + * @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*" // 12 + + "(\\S+)\\s*" // 13 + + "(\\S*)\\s*" // 14 user + + "(\\d*)\\s*" // 15 group + + "(\\d*)\\s*" // 16 filesize + + MONTHS // 17 month + + "\\s*" // TODO should the space be optional? + // TODO \\d* should be \\d? surely ? Otherwise 01111 is allowed + + "((?:[012]\\d*)|(?:3[01]))\\s*" // 18 date [012]\d* or 3[01] + + "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s" + // 20 \d\d\d\d = year OR + // 21 [01]\d or 2[0123] hour + ':' + // 22 [012345]\d = minute + + "(\\S*)(\\s*.*)"; // 23 name + + /** + * 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 + */ + @Override + 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); + + int pos = MONTHS.indexOf(mo); + int month = pos / 4; + final int missingUnit; // the first missing unit + try + { + + if (yr != null) + { + // it's a year; there are no hours and minutes + cal.set(Calendar.YEAR, Integer.parseInt(yr)); + missingUnit = Calendar.HOUR_OF_DAY; + } + else + { + // it must be hour/minute or we wouldn't have matched + missingUnit = Calendar.SECOND; + 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.DAY_OF_MONTH, Integer.parseInt(da)); + cal.clear(missingUnit); + file.setTimestamp(cal); + } + catch (NumberFormatException e) + { + // do nothing, date will be uninitialized + } + file.setName(name); + + return file; + } + return null; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java new file mode 100644 index 00000000..9901a97f --- /dev/null +++ b/Aria/src/main/java/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. + * @throws 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. + * @throws ParserInitializationException + * Thrown on any exception in instantiation + * @since 1.4 + */ + public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) + throws ParserInitializationException; + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java new file mode 100644 index 00000000..0f744370 --- /dev/null +++ b/Aria/src/main/java/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/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java new file mode 100644 index 00000000..bb4c9970 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java @@ -0,0 +1,406 @@ +/* + * 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 +{ + + + /** The date format for all dates, except possibly recent dates. Assumed to include the year. */ + private SimpleDateFormat defaultDateFormat; + /* The index in CALENDAR_UNITS of the smallest time unit in defaultDateFormat */ + private int defaultDateSmallestUnitIndex; + + /** The format used for recent dates (which don't have the year). May be null. */ + private SimpleDateFormat recentDateFormat; + /* The index in CALENDAR_UNITS of the smallest time unit in recentDateFormat */ + private int recentDateSmallestUnitIndex; + + private boolean lenientFutureDates = false; + + /* + * List of units in order of increasing significance. + * This allows the code to clear all units in the Calendar until it + * reaches the least significant unit in the parse string. + * The date formats are analysed to find the least significant + * unit (e.g. Minutes or Milliseconds) and the appropriate index to + * the array is saved. + * This is done by searching the array for the unit specifier, + * and returning the index. When clearing the Calendar units, + * the code loops through the array until the previous entry. + * e.g. for MINUTE it would clear MILLISECOND and SECOND + */ + private static final int[] CALENDAR_UNITS = { + Calendar.MILLISECOND, + Calendar.SECOND, + Calendar.MINUTE, + Calendar.HOUR_OF_DAY, + Calendar.DAY_OF_MONTH, + Calendar.MONTH, + Calendar.YEAR}; + + /* + * Return the index to the array representing the least significant + * unit found in the date format. + * Default is 0 (to avoid dropping precision) + */ + private static int getEntry(SimpleDateFormat dateFormat) { + if (dateFormat == null) { + return 0; + } + final String FORMAT_CHARS="SsmHdM"; + final String pattern = dateFormat.toPattern(); + for(char ch : FORMAT_CHARS.toCharArray()) { + if (pattern.indexOf(ch) != -1){ // found the character + switch(ch) { + case 'S': + return indexOf(Calendar.MILLISECOND); + case 's': + return indexOf(Calendar.SECOND); + case 'm': + return indexOf(Calendar.MINUTE); + case 'H': + return indexOf(Calendar.HOUR_OF_DAY); + case 'd': + return indexOf(Calendar.DAY_OF_MONTH); + case 'M': + return indexOf(Calendar.MONTH); + } + } + } + return 0; + } + + /* + * Find the entry in the CALENDAR_UNITS array. + */ + private static int indexOf(int calendarUnit) { + int i; + for(i = 0; i NET-83) + now.add(Calendar.DAY_OF_MONTH, 1); + } + // The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input + // As 1970 was not a leap year, it cannot parse "Feb 29" correctly. + // Java 1.5+ returns Mar 1 1970 + // Temporarily add the current year to the short date time + // to cope with short-date leap year strings. + // Since Feb 29 is more that 6 months from the end of the year, this should be OK for + // all instances of short dates which are +- 6 months from current date. + // TODO this won't always work for systems that use short dates +0/-12months + // e.g. if today is Jan 1 2001 and the short date is Feb 29 + String year = Integer.toString(now.get(Calendar.YEAR)); + String timeStampStrPlusYear = timestampStr + " " + year; + SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", + recentDateFormat.getDateFormatSymbols()); + hackFormatter.setLenient(false); + hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); + ParsePosition pp = new ParsePosition(0); + parsed = hackFormatter.parse(timeStampStrPlusYear, pp); + // Check if we parsed the full string, if so it must have been a short date originally + if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) { + working.setTime(parsed); + if (working.after(now)) { // must have been last year instead + working.add(Calendar.YEAR, -1); + } + setPrecision(recentDateSmallestUnitIndex, working); + return working; + } + } + + ParsePosition 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 '"+timestampStr+"' could not be parsed using a server time of " + +serverTime.getTime().toString(), + pp.getErrorIndex()); + } + setPrecision(defaultDateSmallestUnitIndex, working); + return working; + } + + /** + * @return Returns the defaultDateFormat. + */ + public SimpleDateFormat getDefaultDateFormat() { + return defaultDateFormat; + } + /** + * @return Returns the defaultDateFormat pattern string. + */ + public String getDefaultDateFormatString() { + return defaultDateFormat.toPattern(); + } + /** + * @param format The defaultDateFormat to be set. + * @param dfs the symbols to use (may be null) + */ + private void setDefaultDateFormat(String format, DateFormatSymbols dfs) { + if (format != null) { + if (dfs != null) { + this.defaultDateFormat = new SimpleDateFormat(format, dfs); + } else { + this.defaultDateFormat = new SimpleDateFormat(format); + } + this.defaultDateFormat.setLenient(false); + } else { + this.defaultDateFormat = null; + } + this.defaultDateSmallestUnitIndex = getEntry(this.defaultDateFormat); + } + /** + * @return Returns the recentDateFormat. + */ + public SimpleDateFormat getRecentDateFormat() { + return recentDateFormat; + } + /** + * @return Returns the recentDateFormat. + */ + public String getRecentDateFormatString() { + return recentDateFormat.toPattern(); + } + /** + * @param format The recentDateFormat to set. + * @param dfs the symbols to use (may be null) + */ + private void setRecentDateFormat(String format, DateFormatSymbols dfs) { + if (format != null) { + if (dfs != null) { + this.recentDateFormat = new SimpleDateFormat(format, dfs); + } else { + this.recentDateFormat = new SimpleDateFormat(format); + } + this.recentDateFormat.setLenient(false); + } else { + this.recentDateFormat = null; + } + this.recentDateSmallestUnitIndex = getEntry(this.recentDateFormat); + } + + /** + * @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(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. + *

+ */ + @Override + 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(); + setRecentDateFormat(recentFormatString, dfs); + + String defaultFormatString = config.getDefaultDateFormatStr(); + if (defaultFormatString == null) { + throw new IllegalArgumentException("defaultFormatString cannot be null"); + } + setDefaultDateFormat(defaultFormatString, dfs); + + 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/Aria/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java new file mode 100644 index 00000000..6cdf9ea8 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java @@ -0,0 +1,269 @@ +/* + * 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.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPFileEntryParserImpl; + +/** + * Parser class for MSLT and MLSD replies. See RFC 3659. + *

+ * Format is as follows: + *

+ * entry            = [ facts ] SP pathname
+ * facts            = 1*( fact ";" )
+ * fact             = factname "=" value
+ * factname         = "Size" / "Modify" / "Create" /
+ *                    "Type" / "Unique" / "Perm" /
+ *                    "Lang" / "Media-Type" / "CharSet" /
+ * os-depend-fact / local-fact
+ * os-depend-fact   = {IANA assigned OS name} "." token
+ * local-fact       = "X." token
+ * value            = *SCHAR
+ *
+ * Sample os-depend-fact:
+ * UNIX.group=0;UNIX.mode=0755;UNIX.owner=0;
+ * 
+ * A single control response entry (MLST) is returned with a leading space; + * multiple (data) entries are returned without any leading spaces. + * The parser requires that the leading space from the MLST entry is removed. + * MLSD entries can begin with a single space if there are no facts. + * + * @since 3.0 + */ +public class MLSxEntryParser extends FTPFileEntryParserImpl +{ + // This class is immutable, so a single instance can be shared. + private static final MLSxEntryParser PARSER = new MLSxEntryParser(); + + private static final HashMap TYPE_TO_INT = new HashMap(); + static { + TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE)); + TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory + TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a parent dir + TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir + } + + private static int UNIX_GROUPS[] = { // Groups in order of mode digits + FTPFile.USER_ACCESS, + FTPFile.GROUP_ACCESS, + FTPFile.WORLD_ACCESS, + }; + + private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value +/* 0 */ {}, +/* 1 */ {FTPFile.EXECUTE_PERMISSION}, +/* 2 */ {FTPFile.WRITE_PERMISSION}, +/* 3 */ {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION}, +/* 4 */ {FTPFile.READ_PERMISSION}, +/* 5 */ {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION}, +/* 6 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION}, +/* 7 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION}, + }; + + /** + * Create the parser for MSLT and MSLD listing entries + * This class is immutable, so one can use {@link #getInstance()} instead. + */ + public MLSxEntryParser() + { + super(); + } + + @Override + public FTPFile parseFTPEntry(String entry) { + if (entry.startsWith(" ")) {// leading space means no facts are present + if (entry.length() > 1) { // is there a path name? + FTPFile file = new FTPFile(); + file.setRawListing(entry); + file.setName(entry.substring(1)); + return file; + } else { + return null; // Invalid - no pathname + } + + } + String parts[] = entry.split(" ",2); // Path may contain space + if (parts.length != 2 || parts[1].length() == 0) { + return null; // no space found or no file name + } + final String factList = parts[0]; + if (!factList.endsWith(";")) { + return null; + } + FTPFile file = new FTPFile(); + file.setRawListing(entry); + file.setName(parts[1]); + String[] facts = factList.split(";"); + boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode="); + for(String fact : facts) { + String []factparts = fact.split("=", -1); // Don't drop empty values +// Sample missing permission +// drwx------ 2 mirror mirror 4096 Mar 13 2010 subversion +// modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion + if (factparts.length != 2) { + return null; // invalid - there was no "=" sign + } + String factname = factparts[0].toLowerCase(Locale.ENGLISH); + String factvalue = factparts[1]; + if (factvalue.length() == 0) { + continue; // nothing to see here + } + String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH); + if ("size".equals(factname)) { + file.setSize(Long.parseLong(factvalue)); + } + else if ("sizd".equals(factname)) { // Directory size + file.setSize(Long.parseLong(factvalue)); + } + else if ("modify".equals(factname)) { + final Calendar parsed = parseGMTdateTime(factvalue); + if (parsed == null) { + return null; + } + file.setTimestamp(parsed); + } + else if ("type".equals(factname)) { + Integer intType = TYPE_TO_INT.get(valueLowerCase); + if (intType == null) { + file.setType(FTPFile.UNKNOWN_TYPE); + } else { + file.setType(intType.intValue()); + } + } + else if (factname.startsWith("unix.")) { + String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH); + if ("group".equals(unixfact)){ + file.setGroup(factvalue); + } else if ("owner".equals(unixfact)){ + file.setUser(factvalue); + } else if ("mode".equals(unixfact)){ // e.g. 0[1]755 + int off = factvalue.length()-3; // only parse last 3 digits + for(int i=0; i < 3; i++){ + int ch = factvalue.charAt(off+i)-'0'; + if (ch >= 0 && ch <= 7) { // Check it's valid octal + for(int p : UNIX_PERMS[ch]) { + file.setPermission(UNIX_GROUPS[i], p, true); + } + } else { + // TODO should this cause failure, or can it be reported somehow? + } + } // digits + } // mode + } // unix. + else if (!hasUnixMode && "perm".equals(factname)) { // skip if we have the UNIX.mode + doUnixPerms(file, valueLowerCase); + } // process "perm" + } // each fact + return file; + } + + /** + * Parse a GMT time stamp of the form YYYYMMDDHHMMSS[.sss] + * + * @param timestamp the date-time to parse + * @return a Calendar entry, may be {@code null} + * @since 3.4 + */ + public static Calendar parseGMTdateTime(String timestamp) { + final SimpleDateFormat sdf; + final boolean hasMillis; + if (timestamp.contains(".")){ + sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); + hasMillis = true; + } else { + sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + hasMillis = false; + } + TimeZone GMT = TimeZone.getTimeZone("GMT"); + // both timezones need to be set for the parse to work OK + sdf.setTimeZone(GMT); + GregorianCalendar gc = new GregorianCalendar(GMT); + ParsePosition pos = new ParsePosition(0); + sdf.setLenient(false); // We want to parse the whole string + final Date parsed = sdf.parse(timestamp, pos); + if (pos.getIndex() != timestamp.length()) { + return null; // did not fully parse the input + } + gc.setTime(parsed); + if (!hasMillis) { + gc.clear(Calendar.MILLISECOND); // flag up missing ms units + } + return gc; + } + + // perm-fact = "Perm" "=" *pvals + // pvals = "a" / "c" / "d" / "e" / "f" / + // "l" / "m" / "p" / "r" / "w" + private void doUnixPerms(FTPFile file, String valueLowerCase) { + for(char c : valueLowerCase.toCharArray()) { + // TODO these are mostly just guesses at present + switch (c) { + case 'a': // (file) may APPEnd + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'c': // (dir) files may be created in the dir + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'd': // deletable + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'e': // (dir) can change to this dir + file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); + break; + case 'f': // (file) renamable + // ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'l': // (dir) can be listed + file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true); + break; + case 'm': // (dir) can create directory here + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'p': // (dir) entries may be deleted + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'r': // (files) file may be RETRieved + file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); + break; + case 'w': // (files) file may be STORed + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + default: + break; + // ignore unexpected flag for now. + } // switch + } // each char + } + + public static FTPFile parseEntry(String entry) { + return PARSER.parseFTPEntry(entry); + } + + public static MLSxEntryParser getInstance() { + return PARSER; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java new file mode 100644 index 00000000..28a45dcd --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java @@ -0,0 +1,554 @@ +/* + * 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. + * + * @version $Id: MVSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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
+     * 
+ * Sample output from FTP session: + *
+     * ftp> quote site filetype=jes
+     * 200 SITE command was accepted
+     * ftp> ls
+     * 200 Port request OK.
+     * 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER
+     * 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
+     * 250 List completed successfully.
+     * ftp> ls job01906
+     * 200 Port request OK.
+     * 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER
+     * JOBNAME  JOBID    OWNER    STATUS CLASS
+     * IBMUSER1 JOB01906 IBMUSER  OUTPUT A        RC=0000
+     * --------
+     * ID  STEPNAME PROCSTEP C DDNAME   BYTE-COUNT
+     * 001 JES2              A JESMSGLG       858
+     * 002 JES2              A JESJCL         128
+     * 003 JES2              A JESYSMSG       443
+     * 3 spool files
+     * 250 List completed successfully.
+     * 
+ */ + + 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 + */ + @Override + 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: + *
+     *    0         1        2          3        4     5     6      7    8
+     *   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 true if the entry string is non-null and non-empty + */ + private boolean parseSimpleEntry(FTPFile file, String entry) { + if (entry != null && entry.trim().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: + *
+     * [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); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java new file mode 100644 index 00000000..ac88f3b2 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java @@ -0,0 +1,278 @@ +/* + * 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 pre MacOS-X Systems. + * + * @version $Id: MacOsPeterFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @since 3.1 + */ +public class MacOsPeterFTPEntryParser 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 + + /** + * 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-])" // type (1) + + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission + + "(" + + "(folder\\s+)" + + "|" + + "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size + + ")" + + "(\\d+)\\s+" // size + /* + * numeric or standard format date: + * yyyy-mm-dd (expecting hh:mm to follow) + * MMM [d]d + * [d]d MMM + * N.B. use non-space for MMM to allow for languages such as German which use + * diacritics (e.g. umlaut) in some abbreviations. + */ + + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+" + /* + year (for non-recent standard format) - yyyy + or time (for numeric or recent standard format) [h]h:mm + */ + + "(\\d+(?::\\d+)?)\\s+" + + + "(\\S*)(\\s*.*)"; // the rest + + + /** + * The default constructor for a UnixFTPEntryParser object. + * + * @throws 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 MacOsPeterFTPEntryParser() + { + 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. + * @throws 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 MacOsPeterFTPEntryParser(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 + */ + @Override + 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 = "0"; + String usr = null; + String grp = null; + String filesize = group(20); + String datestr = group(21) + " " + group(22); + String name = group(23); + String endtoken = group(24); + + try + { + file.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + // A 'whiteout' file is an ARTIFICIAL entry in any of several types of + // 'translucent' filesystems, of which a 'union' filesystem is one. + + // bcdelfmpSs- + switch (typeStr.charAt(0)) + { + case 'd': + type = FTPFile.DIRECTORY_TYPE; + break; + case 'e': // NET-39 => z/OS external link + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'l': + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'b': + case 'c': + isDevice = true; + type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented + break; + case 'f': + case '-': + type = FTPFile.FILE_TYPE; + break; + default: // e.g. ? and w = whiteout + 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); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java new file mode 100644 index 00000000..1c78aadc --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java @@ -0,0 +1,167 @@ +/* + * 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.regex.Pattern; + +import org.apache.commons.net.ftp.Configurable; +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation of FTPFileEntryParser and FTPFileListParser for NT Systems. + * + * @version $Id: NTFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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 + + private static final String DEFAULT_DATE_FORMAT2 + = "MM-dd-yy kk:mm"; //11-09-01 18:30 + + private final FTPTimestampParser timestampParser; + + /** + * this is the regular expression used by this parser. + */ + private static final String REGEX = + "(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm; swallow trailing spaces + + "(?:()|([0-9]+))\\s+" // or ddddd; swallow trailing spaces + + "(\\S.*)"; // First non-space followed by rest of line (name) + + /** + * The sole constructor for an NTFTPEntryParser object. + * + * @throws 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. + * @throws 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, Pattern.DOTALL); + configure(config); + FTPClientConfig config2 = new FTPClientConfig( + FTPClientConfig.SYST_NT, + DEFAULT_DATE_FORMAT2, + null); + config2.setDefaultDateFormatStr(DEFAULT_DATE_FORMAT2); + this.timestampParser = new FTPTimestampParserImpl(); + ((Configurable)this.timestampParser).configure(config2); + } + + /** + * 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 + */ + @Override + 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) + { + // parsing fails, try the other date format + try + { + f.setTimestamp(timestampParser.parseTimestamp(datestr)); + } + catch (ParseException e2) + { + // 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); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java new file mode 100644 index 00000000..2d1b340f --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java @@ -0,0 +1,177 @@ +/* + * 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. + * + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @version $Id: NetwareFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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 + + "\\[([-A-Z]+)\\]\\s+" // Attributes RWCEAFMS or - + + "(\\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. + * + * @throws 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. + * @throws 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 + */ + @Override + 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); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java new file mode 100644 index 00000000..fbd78286 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.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.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. + * + * @version $Id: OS2FTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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. + * + * @throws 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. + * @throws 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 + */ + @Override + 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); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java new file mode 100644 index 00000000..5248a819 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java @@ -0,0 +1,435 @@ +/* + * 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.File; +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * @version $Id: OS400FTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ + *
+ * Example *FILE/*MEM FTP entries, when the current
+ * working directory is a file of file system QSYS:
+ * ------------------------------------------------
+ *
+ * $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file
+ *   250-NAMEFMT set to 1.
+ *   250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory.
+ * $ dir
+ *   227 Entering Passive Mode (10,200,36,33,40,249).
+ *   125 List started.
+ *   QPGMR          135168 22.06.13 13:18:19 *FILE
+ *   QPGMR                                   *MEM       MKCMD.MBR
+ *   QPGMR                                   *MEM       RUCALLTST.MBR
+ *   QPGMR                                   *MEM       RUCMDHLP.MBR
+ *   QPGMR                                   *MEM       RUCRTTST.MBR
+ *   250 List completed.
+ *
+ *
+ * Example *FILE entry of an OS/400 save file:
+ * ---------------------------------------------------
+ *
+ * $ cwd /qsys.lib/rpgunit.lib
+ *   250 "/QSYS.LIB/RPGUNIT.LIB" is current library.
+ * $ dir rpgunit.file
+ *   227 Entering Passive Mode (10,200,36,33,188,106).
+ *   125 List started.
+ *   QPGMR        16347136 29.06.13 15:45:09 *FILE      RPGUNIT.SAVF
+ *   250 List completed.
+ *
+ *
+ * Example *STMF/*DIR FTP entries, when the
+ * current working directory is in file system "root":
+ * ---------------------------------------------------
+ *
+ * $ cwd /home/raddatz
+ *   250 "/home/raddatz" is current directory.
+ * $ dir test*
+ *   227 Entering Passive Mode (10,200,36,33,200,189).
+ *   125 List started.
+ *   RADDATZ           200 21.05.11 12:31:18 *STMF      TEST_RG_02_CRLF.properties
+ *   RADDATZ           187 08.05.11 12:31:40 *STMF      TEST_RG_02_LF.properties
+ *   RADDATZ           187 08.05.11 12:31:52 *STMF      TEST_RG_02_CR.properties
+ *   RADDATZ          8192 04.07.13 09:04:14 *DIR       testDir1/
+ *   RADDATZ          8192 04.07.13 09:04:17 *DIR       testDir2/
+ *   250 List completed.
+ *
+ *
+ * Example 1, using ANT to list specific members of a file:
+ * --------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Listing members of a file:</echo>
+ *
+ *      <ftp action="list"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
+ *           systemTypeKey="OS/400"
+ *           listing="ftp-listing.txt"
+ *           >
+ *          <fileset dir="./i5-downloads-file" casesensitive="false">
+ *              <include name="run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Listing members of a file:
+ *    [ftp] listing files
+ *    [ftp] listing RUN.MBR
+ *    [ftp] listing RUNNER.MBR
+ *    [ftp] listing RUNNERBND.MBR
+ *    [ftp] 3 files listed
+ *
+ *
+ * Example 2, using ANT to list specific members of all files of a library:
+ * ------------------------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Listing members of all files of a library:</echo>
+ *
+ *      <ftp action="list"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
+ *           systemTypeKey="OS/400"
+ *           listing="ftp-listing.txt"
+ *           >
+ *          <fileset dir="./i5-downloads-lib" casesensitive="false">
+ *              <include name="**\run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Listing members of all files of a library:
+ *    [ftp] listing files
+ *    [ftp] listing RPGUNIT1.FILE\RUN.MBR
+ *    [ftp] listing RPGUNIT1.FILE\RUNRMT.MBR
+ *    [ftp] listing RPGUNITT1.FILE\RUNT.MBR
+ *    [ftp] listing RPGUNITY1.FILE\RUN.MBR
+ *    [ftp] listing RPGUNITY1.FILE\RUNNER.MBR
+ *    [ftp] listing RPGUNITY1.FILE\RUNNERBND.MBR
+ *    [ftp] 6 files listed
+ *
+ *
+ * Example 3, using ANT to download specific members of a file:
+ * ------------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Downloading members of a file:</echo>
+ *
+ *      <ftp action="get"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
+ *           systemTypeKey="OS/400"
+ *           >
+ *          <fileset dir="./i5-downloads-file" casesensitive="false">
+ *              <include name="run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Downloading members of a file:
+ *    [ftp] getting files
+ *    [ftp] transferring RUN.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUN.MBR
+ *    [ftp] transferring RUNNER.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNER.MBR
+ *    [ftp] transferring RUNNERBND.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNERBND.MBR
+ *    [ftp] 3 files retrieved
+ *
+ *
+ * Example 4, using ANT to download specific members of all files of a library:
+ * ----------------------------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Downloading members of all files of a library:</echo>
+ *
+ *      <ftp action="get"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
+ *           systemTypeKey="OS/400"
+ *           >
+ *          <fileset dir="./i5-downloads-lib" casesensitive="false">
+ *              <include name="**\run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Downloading members of all files of a library:
+ *    [ftp] getting files
+ *    [ftp] transferring RPGUNIT1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUN.MBR
+ *    [ftp] transferring RPGUNIT1.FILE\RUNRMT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUNRMT.MBR
+ *    [ftp] transferring RPGUNITT1.FILE\RUNT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITT1.FILE\RUNT.MBR
+ *    [ftp] transferring RPGUNITY1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUN.MBR
+ *    [ftp] transferring RPGUNITY1.FILE\RUNNER.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNER.MBR
+ *    [ftp] transferring RPGUNITY1.FILE\RUNNERBND.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNERBND.MBR
+ *    [ftp] 6 files retrieved
+ *
+ *
+ * Example 5, using ANT to download a save file of a library:
+ * ----------------------------------------------------------
+ *
+ *      <ftp action="get"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="true"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
+ *           systemTypeKey="OS/400"
+ *           >
+ *        <fileset dir="./i5-downloads-savf" casesensitive="false">
+ *            <include name="RPGUNIT.SAVF" />
+ *        </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *   [echo] Downloading save file:
+ *    [ftp] getting files
+ *    [ftp] transferring RPGUNIT.SAVF to C:\workspaces\rdp_080\workspace\net-Test\i5-downloads-lib\RPGUNIT.SAVF
+ *    [ftp] 1 files retrieved
+ *
+ * 
+ */ +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, empty for members + + "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members + + "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM + + "(?:(\\S+)\\s*)?"; // filename, missing, when CWD is a *FILE + + + /** + * The default constructor for a OS400FTPEntryParser object. + * + * @throws 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. + * @throws 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); + } + + + @Override + 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 = ""; + if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) + { + datestr = group(3)+" "+group(4); + } + String typeStr = group(5); + String name = group(6); + + boolean mustScanForPathSeparator = true; + + try + { + file.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + + if (typeStr.equalsIgnoreCase("*STMF")) + { + type = FTPFile.FILE_TYPE; + if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) + { + return null; + } + } + else if (typeStr.equalsIgnoreCase("*DIR")) + { + type = FTPFile.DIRECTORY_TYPE; + if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) + { + return null; + } + } + else if (typeStr.equalsIgnoreCase("*FILE")) + { + // File, defines the structure of the data (columns of a row) + // but the data is stored in one or more members. Typically a + // source file contains multiple members whereas it is + // recommended (but not enforced) to use one member per data + // file. + // Save files are a special type of files which are used + // to save objects, e.g. for backups. + if (name != null && name.toUpperCase().endsWith(".SAVF")) + { + mustScanForPathSeparator = false; + type = FTPFile.FILE_TYPE; + } + else + { + return null; + } + } + else if (typeStr.equalsIgnoreCase("*MEM")) + { + mustScanForPathSeparator = false; + type = FTPFile.FILE_TYPE; + + if (isNullOrEmpty(name)) + { + return null; + } + if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) + { + return null; + } + + // Quick and dirty bug fix to make SelectorUtils work. + // Class SelectorUtils uses 'File.separator' to splitt + // a given path into pieces. But actually it had to + // use the separator of the FTP server, which is a forward + // slash in case of an AS/400. + name = name.replace('/', File.separatorChar); + } + 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); + } + if (mustScanForPathSeparator) + { + int pos = name.lastIndexOf('/'); + if (pos > -1) + { + name = name.substring(pos + 1); + } + } + + file.setName(name); + + return file; + } + return null; + } + + /** + * + * @param string String value that is checked for null + * or empty. + * @return true for null or empty values, + * else false. + */ + private boolean isNullOrEmpty(String string) { + if (string == null || string.length() == 0) { + return true; + } + 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_OS400, + DEFAULT_DATE_FORMAT, + null); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java new file mode 100644 index 00000000..080f8097 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java @@ -0,0 +1,62 @@ +/* + * 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 { + + private static final long serialVersionUID = 5563335279583210658L; + + /** + * Constucts a ParserInitializationException with just a message + * + * @param message Exception message + */ + public ParserInitializationException(String message) { + super(message); + } + + /** + * 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, 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 + * @deprecated use {@link #getCause()} instead + */ + @Deprecated + public Throwable getRootCause() { + return super.getCause(); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java new file mode 100644 index 00000000..50ef365a --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java @@ -0,0 +1,202 @@ +/* + * 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 class for all regular expression based FTPFileEntryParser classes + */ +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. + * The expression is compiled with flags = 0. + * + * @param regex The regular expression with which this object is + * initialized. + * + * @throws 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(); + compileRegex(regex, 0); + } + + /** + * The constructor for a RegexFTPFileEntryParserImpl object. + * + * @param regex The regular expression with which this object is + * initialized. + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * + * @throws 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. + * @since 3.4 + */ + public RegexFTPFileEntryParserImpl(String regex, final int flags) { + super(); + compileRegex(regex, flags); + } + + /** + * 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() { + StringBuilder b = new StringBuilder(); + 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 true + * @since 2.0 + * @throws IllegalArgumentException if the regex cannot be compiled + */ + public boolean setRegex(final String regex) { + compileRegex(regex, 0); + return true; + } + + + /** + * Alter the current regular expression being utilised for entry parsing + * and create a new {@link Pattern} instance. + * @param regex The new regular expression + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * @return true + * @since 3.4 + * @throws IllegalArgumentException if the regex cannot be compiled + */ + public boolean setRegex(final String regex, final int flags) { + compileRegex(regex, flags); + return true; + } + + /** + * Compile the regex and store the {@link Pattern}. + * + * This is an internal method to do the work so the constructor does not + * have to call an overrideable method. + * + * @param regex the expression to compile + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * @throws IllegalArgumentException if the regex cannot be compiled + */ + private void compileRegex(final String regex, final int flags) { + try { + pattern = Pattern.compile(regex, flags); + } catch (PatternSyntaxException pse) { + throw new IllegalArgumentException("Unparseable regex supplied: " + regex); + } + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java new file mode 100644 index 00000000..f48cf728 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java @@ -0,0 +1,379 @@ +/* + * 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 java.util.ListIterator; + +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 1781925 2017-02-06 16:43:40Z 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 + + // Suffixes used in Japanese listings after the numeric values + private static final String JA_MONTH = "\u6708"; + private static final String JA_DAY = "\u65e5"; + private static final String JA_YEAR = "\u5e74"; + + private static final String DEFAULT_DATE_FORMAT_JA + = "M'" + JA_MONTH + "' d'" + JA_DAY + "' yyyy'" + JA_YEAR + "'"; //6月 3日 2003年 + + private static final String DEFAULT_RECENT_DATE_FORMAT_JA + = "M'" + JA_MONTH + "' d'" + JA_DAY + "' HH:mm"; //8月 17日 20:10 + + /** + * 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); + + /** + * 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 + * Final letter may be appended: + * + file has extended security attributes (e.g. ACL) + * Note: local listings on MacOSX also use '@'; + * this is not allowed for here as does not appear to be shown by FTP servers + * {@code @} file has extended attributes + */ + private static final String REGEX = + "([bcdelfmpSs-])" // file type + +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions + + + "\\s*" // separator TODO why allow it to be omitted?? + + + "(\\d+)" // link count + + + "\\s+" // separator + + + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces) + + "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces) + + "(\\d+(?:,\\s*\\d+)?)" // size or n,m + + + "\\s+" // separator + + /* + * numeric or standard format date: + * yyyy-mm-dd (expecting hh:mm to follow) + * MMM [d]d + * [d]d MMM + * N.B. use non-space for MMM to allow for languages such as German which use + * diacritics (e.g. umlaut) in some abbreviations. + * Japanese uses numeric day and month with suffixes to distinguish them + * [d]dXX [d]dZZ + */ + + "("+ + "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd + "|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d + "|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM + "|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")"+ + ")" + + + "\\s+" // separator + + /* + year (for non-recent standard format) - yyyy + or time (for numeric or recent standard format) [h]h:mm + or Japanese year - yyyyXX + */ + + "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20) + + + "\\s" // separator + + + "(.*)"; // the rest (21) + + + // if true, leading spaces are trimmed from file names + // this was the case for the original implementation + final boolean trimLeadingSpaces; // package protected for access from test code + + /** + * The default constructor for a UnixFTPEntryParser object. + * + * @throws 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. + * @throws 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) + { + this(config, false); + } + + /** + * 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. + * @param trimLeadingSpaces if {@code true}, trim leading spaces from file names + * @throws 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 3.4 + */ + public UnixFTPEntryParser(FTPClientConfig config, boolean trimLeadingSpaces) + { + super(REGEX); + configure(config); + this.trimLeadingSpaces = trimLeadingSpaces; + } + + /** + * Preparse the list to discard "total nnn" lines + */ + @Override + public List preParse(List original) { + ListIterator iter = original.listIterator(); + while (iter.hasNext()) { + String entry = iter.next(); + if (entry.matches("^total \\d+$")) { // NET-389 + iter.remove(); + } + } + return original; + } + + /** + * 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 + */ + @Override + 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); + if (trimLeadingSpaces) { + name = name.replaceFirst("^\\s+", ""); + } + + try + { + if (group(19).contains(JA_MONTH)) { // special processing for Japanese format + FTPTimestampParserImpl jaParser = new FTPTimestampParserImpl(); + jaParser.configure(new FTPClientConfig( + FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT_JA, DEFAULT_RECENT_DATE_FORMAT_JA)); + file.setTimestamp(jaParser.parseTimestamp(datestr)); + } else { + file.setTimestamp(super.parseTimestamp(datestr)); + } + } + catch (ParseException e) + { + // intentionally do nothing + } + + // A 'whiteout' file is an ARTIFICIAL entry in any of several types of + // 'translucent' filesystems, of which a 'union' filesystem is one. + + // bcdelfmpSs- + switch (typeStr.charAt(0)) + { + case 'd': + type = FTPFile.DIRECTORY_TYPE; + break; + case 'e': // NET-39 => z/OS external link + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'l': + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'b': + case 'c': + isDevice = true; + type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented + break; + case 'f': + case '-': + type = FTPFile.FILE_TYPE; + break; + default: // e.g. ? and w = whiteout + 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 + } + + // oddball cases like symbolic links, file names + // with spaces in them. + 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); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java new file mode 100644 index 00000000..73b3840e --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.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.ftp.parser; +import java.io.BufferedReader; +import java.io.IOException; +import java.text.ParseException; +import java.util.StringTokenizer; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * 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. + * + *

+ * + * @version $Id: VMSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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. + * + * @throws 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. + * @throws 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 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 + */ + @Override + 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. + * @throws IOException thrown on any IO Error reading from the reader. + */ + @Override + public String readNextEntry(BufferedReader reader) throws IOException + { + String line = reader.readLine(); + StringBuilder entry = new StringBuilder(); + 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); + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** + * DO NOT USE + * @param listStream the stream + * @return the array of files + * @throws IOException on error + * @deprecated (2.2) No other FTPFileEntryParser implementations have this method. + */ + @Deprecated + public FTPFile[] parseFileList(java.io.InputStream listStream) throws IOException { + org.apache.commons.net.ftp.FTPListParseEngine engine = new org.apache.commons.net.ftp.FTPListParseEngine(this); + engine.readServerList(listStream, null); + return engine.getFiles(); + } + +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/Aria/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java new file mode 100644 index 00000000..554f11ee --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java @@ -0,0 +1,167 @@ +/* + * 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)", + *

+ * + * @version $Id: VMSVersioningFTPEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ + * + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + */ +public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser +{ + + private final Pattern _preparse_pattern_; + private static final String PRE_PARSE_REGEX = + "(.*?);([0-9]+)\\s*.*"; + + /** + * Constructor for a VMSFTPEntryParser object. + * + * @throws 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. + * @throws 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); + } + + } + + /** + * 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) { + HashMap existingEntries = new HashMap(); + ListIterator iter = original.listIterator(); + while (iter.hasNext()) { + String entry = iter.next().trim(); + MatchResult result = null; + Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); + if (_preparse_matcher_.matches()) { + result = _preparse_matcher_.toMatchResult(); + String name = result.group(1); + String version = result.group(2); + Integer nv = Integer.valueOf(version); + Integer existing = existingEntries.get(name); + if (null != existing) { + if (nv.intValue() < existing.intValue()) { + iter.remove(); // removes older version 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; + Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); + if (_preparse_matcher_.matches()) { + result = _preparse_matcher_.toMatchResult(); + String name = result.group(1); + String version = result.group(2); + Integer nv = Integer.valueOf(version); + Integer existing = existingEntries.get(name); + if (null != existing) { + if (nv.intValue() < existing.intValue()) { + iter.remove(); // removes older version 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/Aria/src/main/java/org/apache/commons/net/ftp/parser/package-info.java b/Aria/src/main/java/org/apache/commons/net/ftp/parser/package-info.java new file mode 100644 index 00000000..7dc0ffd5 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/ftp/parser/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * FTP file listing parser classes + */ +package org.apache.commons.net.ftp.parser; \ No newline at end of file diff --git a/Aria/src/main/java/org/apache/commons/net/io/CRLFLineReader.java b/Aria/src/main/java/org/apache/commons/net/io/CRLFLineReader.java new file mode 100644 index 00000000..9ba51a9b --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/CRLFLineReader.java @@ -0,0 +1,77 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.Reader; + +/** + * CRLFLineReader implements a readLine() method that requires + * exactly CRLF to terminate an input line. + * This is required for IMAP, which allows bare CR and LF. + * + * @since 3.0 + */ +public final class CRLFLineReader extends BufferedReader +{ + private static final char LF = '\n'; + private static final char CR = '\r'; + + /** + * Creates a CRLFLineReader that wraps an existing Reader + * input source. + * @param reader The Reader input source. + */ + public CRLFLineReader(Reader reader) + { + super(reader); + } + + /** + * Read a line of text. + * A line is considered to be terminated by carriage return followed immediately by a linefeed. + * This contrasts with BufferedReader which also allows other combinations. + * @since 3.0 + */ + @Override + public String readLine() throws IOException { + StringBuilder sb = new StringBuilder(); + int intch; + boolean prevWasCR = false; + synchronized(lock) { // make thread-safe (hopefully!) + while((intch = read()) != -1) + { + if (prevWasCR && intch == LF) { + return sb.substring(0, sb.length()-1); + } + if (intch == CR) { + prevWasCR = true; + } else { + prevWasCR = false; + } + sb.append((char) intch); + } + } + String string = sb.toString(); + if (string.length() == 0) { // immediate EOF + return null; + } + return string; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java new file mode 100644 index 00000000..328ab010 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java @@ -0,0 +1,118 @@ +/* + * 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 + * @version $Id: CopyStreamAdapter.java 1741829 2016-05-01 00:24:44Z sebb $ + */ +public class CopyStreamAdapter implements CopyStreamListener +{ + private final 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. + */ + @Override + public void bytesTransferred(CopyStreamEvent event) + { + for (EventListener listener : internalListeners) + { + ((CopyStreamListener) (listener)).bytesTransferred(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. 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. + */ + @Override + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, long streamSize) + { + for (EventListener listener : internalListeners) + { + ((CopyStreamListener) (listener)).bytesTransferred( + totalBytesTransferred, bytesTransferred, streamSize); + } + } + + /** + * 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/Aria/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java new file mode 100644 index 00000000..db5f5510 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java @@ -0,0 +1,111 @@ +/* + * 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 + * @version $Id: CopyStreamEvent.java 1652801 2015-01-18 17:10:05Z sebb $ + */ +public class CopyStreamEvent extends EventObject +{ + private static final long serialVersionUID = -964927635655051867L; + + /** + * Constant used to indicate the stream size is unknown. + */ + public static final long UNKNOWN_STREAM_SIZE = -1; + + private final int bytesTransferred; + private final long totalBytesTransferred; + private final 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; + } + + /** + * @since 3.0 + */ + @Override + public String toString(){ + return getClass().getName() + "[source=" + source + + ", total=" + totalBytesTransferred + + ", bytes=" + bytesTransferred + + ", size=" + streamSize + + "]"; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/io/CopyStreamException.java b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamException.java new file mode 100644 index 00000000..54a576a1 --- /dev/null +++ b/Aria/src/main/java/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 + * @version $Id: CopyStreamException.java 1299238 2012-03-10 17:12:28Z sebb $ + */ +public class CopyStreamException extends IOException +{ + private static final long serialVersionUID = -2602899129433221532L; + + private final long totalBytesTransferred; + + /** + * 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); + initCause(exception); // merge this into super() call once we need 1.6+ + totalBytesTransferred = bytesTransferred; + } + + /** + * 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) getCause(); // cast is OK because it was initialised with an IOException + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/io/CopyStreamListener.java b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamListener.java new file mode 100644 index 00000000..b19542e8 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/CopyStreamListener.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.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 + * @version $Id: CopyStreamListener.java 1652801 2015-01-18 17:10:05Z sebb $ + */ +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/Aria/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java b/Aria/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java new file mode 100644 index 00000000..49d3b5fd --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java @@ -0,0 +1,251 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +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, and ensures you cannot read past the end of the message. + *

+ * Note: versions since 3.0 extend BufferedReader rather than Reader, + * and no longer change the CRLF into the local EOL. Also only DOT CR LF + * acts as EOF. + * @version $Id: DotTerminatedMessageReader.java 1747119 2016-06-07 02:22:24Z ggregory $ + */ +public final class DotTerminatedMessageReader extends BufferedReader +{ + private static final char LF = '\n'; + private static final char CR = '\r'; + private static final int DOT = '.'; + + private boolean atBeginning; + private boolean eof; + private boolean seenCR; // was last character CR? + + /** + * 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); + // Assumes input is at start of message + atBeginning = true; + eof = false; + } + + /** + * 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. + * @throws IOException If an error occurs while reading the underlying + * stream. + */ + @Override + public int read() throws IOException { + synchronized (lock) { + if (eof) { + return -1; // Don't allow read past EOF + } + int chint = super.read(); + if (chint == -1) { // True EOF + eof = true; + return -1; + } + if (atBeginning) { + atBeginning = false; + if (chint == DOT) { // Have DOT + mark(2); // need to check for CR LF or DOT + chint = super.read(); + if (chint == -1) { // Should not happen + // new Throwable("Trailing DOT").printStackTrace(); + eof = true; + return DOT; // return the trailing DOT + } + if (chint == DOT) { // Have DOT DOT + // no need to reset as we want to lose the first DOT + return chint; // i.e. DOT + } + if (chint == CR) { // Have DOT CR + chint = super.read(); + if (chint == -1) { // Still only DOT CR - should not happen + //new Throwable("Trailing DOT CR").printStackTrace(); + reset(); // So CR is picked up next time + return DOT; // return the trailing DOT + } + if (chint == LF) { // DOT CR LF + atBeginning = true; + eof = true; + // Do we need to clear the mark somehow? + return -1; + } + } + // Should not happen - lone DOT at beginning + //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); + reset(); + return DOT; + } // have DOT + } // atBeginning + + // Handle CRLF in normal flow + if (seenCR) { + seenCR = false; + if (chint == LF) { + atBeginning = true; + } + } + if (chint == CR) { + seenCR = true; + } + return chint; + } + } + + + /** + * 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. + * @throws 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. + * @throws IOException If an error occurs in reading the underlying + * stream. + */ + @Override + public int read(char[] buffer, int offset, int length) throws IOException + { + if (length < 1) + { + return 0; + } + int ch; + synchronized (lock) + { + if ((ch = read()) == -1) + { + return -1; + } + + int off = offset; + + do + { + buffer[offset++] = (char) ch; + } + while (--length > 0 && (ch = read()) != -1); + + return (offset - off); + } + } + + /** + * 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. + * @throws IOException If an error occurs while reading the + * underlying stream. + */ + @Override + public void close() throws IOException + { + synchronized (lock) + { + if (!eof) + { + while (read() != -1) + { + // read to EOF + } + } + eof = true; + atBeginning = false; + } + } + + /** + * Read a line of text. + * A line is considered to be terminated by carriage return followed immediately by a linefeed. + * This contrasts with BufferedReader which also allows other combinations. + * @since 3.0 + */ + @Override + public String readLine() throws IOException { + StringBuilder sb = new StringBuilder(); + int intch; + synchronized(lock) { // make thread-safe (hopefully!) + while((intch = read()) != -1) + { + if (intch == LF && atBeginning) { + return sb.substring(0, sb.length()-1); + } + sb.append((char) intch); + } + } + String string = sb.toString(); + if (string.length() == 0) { // immediate EOF + return null; + } + // Should not happen - EOF without CRLF + //new Throwable(string).printStackTrace(); + return string; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java b/Aria/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java new file mode 100644 index 00000000..cb98cb85 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java @@ -0,0 +1,219 @@ +/* + * 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. + * + * + ***/ + +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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * + * @throws 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. + * + * @throws 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/Aria/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java b/Aria/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java new file mode 100644 index 00000000..3d1b231e --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java @@ -0,0 +1,221 @@ +/* + * 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; +import java.io.UnsupportedEncodingException; + +/*** + * 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. + * + * + ***/ + +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"); + try { + _lineSeparatorBytes = _lineSeparator.getBytes("US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Broken JVM - cannot find US-ASCII charset!",e); + } + } + + 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. + * @param input the stream to wrap + ***/ + 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. + * @throws 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. + * @throws 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. + * @throws IOException If an error occurs while reading the underlying + * stream. + ***/ + @Override + public int read(byte buffer[], int offset, int length) throws IOException + { + if (_noConversionRequired) { + return super.read(buffer, offset, length); + } + + if (length < 1) { + return 0; + } + + int ch, off; + + ch = available(); + + __length = (length > ch ? ch : length); + + // 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); + } + + + // PushbackInputStream in JDK 1.1.3 returns the wrong thing + // TODO - can we delete this override now? + /*** + * 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 + { + if (in == null) { + throw new IOException("Stream closed"); + } + return (buf.length - pos) + in.available(); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java b/Aria/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java new file mode 100644 index 00000000..412addcd --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java @@ -0,0 +1,175 @@ +/* + * 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. + * + * + ***/ + +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. + * @throws 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. + * @throws 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. + * @throws 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. + * + * @throws 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/Aria/src/main/java/org/apache/commons/net/io/SocketInputStream.java b/Aria/src/main/java/org/apache/commons/net/io/SocketInputStream.java new file mode 100644 index 00000000..b84a4a2b --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/SocketInputStream.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.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. + * + * + * @see SocketOutputStream + ***/ + +public class SocketInputStream extends FilterInputStream +{ + private final 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. + * + * @throws 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/Aria/src/main/java/org/apache/commons/net/io/SocketOutputStream.java b/Aria/src/main/java/org/apache/commons/net/io/SocketOutputStream.java new file mode 100644 index 00000000..6af8f54d --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/SocketOutputStream.java @@ -0,0 +1,88 @@ +/* + * 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. + * + * + * @see SocketInputStream + ***/ + +public class SocketOutputStream extends FilterOutputStream +{ + private final 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. + * @throws 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. + * + * @throws 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/Aria/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java b/Aria/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java new file mode 100644 index 00000000..f0ec930c --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java @@ -0,0 +1,185 @@ +/* + * 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. + * + * + ***/ + +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 wrap. + ***/ + 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. + * @throws 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'; + } + //$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. + * @throws 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. + * @throws 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/Aria/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java b/Aria/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java new file mode 100644 index 00000000..989bfc34 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java @@ -0,0 +1,120 @@ +/* + * 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. + * + * + ***/ + +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. + * @throws 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. + * @throws 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. + * @throws 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/Aria/src/main/java/org/apache/commons/net/io/Util.java b/Aria/src/main/java/org/apache/commons/net/io/Util.java new file mode 100644 index 00000000..1f4e5b50 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/Util.java @@ -0,0 +1,390 @@ +/* + * 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.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.Socket; + +/*** + * The Util class cannot be instantiated and stores short static convenience + * methods that are often quite useful. + * + * + * @see CopyStreamException + * @see CopyStreamListener + * @see CopyStreamAdapter + ***/ + +public final class Util +{ + /** + * The default buffer size ({@value}) used by + * {@link #copyStream copyStream } and {@link #copyReader copyReader} + * and by the copyReader/copyStream methods if a zero or negative buffer size is supplied. + */ + 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of bytes in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @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. + * @return number of bytes read/written + * @throws 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 numBytes; + long total = 0; + byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; + + try + { + while ((numBytes = source.read(buffer)) != -1) + { + // Technically, some read(byte[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + + if (numBytes == 0) + { + int singleByte = source.read(); + if (singleByte < 0) { + break; + } + dest.write(singleByte); + if(flush) { + dest.flush(); + } + ++total; + if (listener != null) { + listener.bytesTransferred(total, 1, streamSize); + } + continue; + } + + dest.write(buffer, 0, numBytes); + if(flush) { + dest.flush(); + } + total += numBytes; + if (listener != null) { + listener.bytesTransferred(total, numBytes, 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of bytes in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @param listener The CopyStreamListener to notify of progress. If + * this parameter is null, notification is not attempted. + * @return number of bytes read/written + * @throws 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. + * @param bufferSize The number of bytes to buffer during the copy. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @return The number of bytes read/written in the copy operation. + * @throws 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); + * @param source where to copy from + * @param dest where to copy to + * @return number of bytes copied + * @throws CopyStreamException on error + ***/ + 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of characters in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @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. + * @throws 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 numChars; + long total = 0; + char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; + + try + { + while ((numChars = source.read(buffer)) != -1) + { + // Technically, some read(char[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + if (numChars == 0) + { + int singleChar = source.read(); + if (singleChar < 0) { + break; + } + dest.write(singleChar); + dest.flush(); + ++total; + if (listener != null) { + listener.bytesTransferred(total, 1, streamSize); + } + continue; + } + + dest.write(buffer, 0, numChars); + dest.flush(); + total += numChars; + if (listener != null) { + listener.bytesTransferred(total, numChars, 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @return The number of characters read/written in the copy operation. + * @throws 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); + * @param source where to copy from + * @param dest where to copy to + * @return number of bytes copied + * @throws CopyStreamException on error + ***/ + public static final long copyReader(Reader source, Writer dest) + throws CopyStreamException + { + return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); + } + + /** + * Closes the object quietly, catching rather than throwing IOException. + * Intended for use from finally blocks. + * + * @param closeable the object to close, may be {@code null} + * @since 3.0 + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + // Ignored + } + } + } + + /** + * Closes the socket quietly, catching rather than throwing IOException. + * Intended for use from finally blocks. + * + * @param socket the socket to close, may be {@code null} + * @since 3.0 + */ + public static void closeQuietly(Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // Ignored + } + } + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/io/package-info.java b/Aria/src/main/java/org/apache/commons/net/io/package-info.java new file mode 100644 index 00000000..90fd4baa --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/io/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Utility classes for IO support. + */ +package org.apache.commons.net.io; \ No newline at end of file diff --git a/Aria/src/main/java/org/apache/commons/net/util/Base64.java b/Aria/src/main/java/org/apache/commons/net/util/Base64.java new file mode 100644 index 00000000..4e859885 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/Base64.java @@ -0,0 +1,1046 @@ +/* + * 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.UnsupportedEncodingException; +import java.math.BigInteger; + + + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + *

+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

+ *

+ * The class can be parameterized in the following manner with various constructors: + *

    + *
  • URL-safe mode: Default off.
  • + *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of + * 4 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n")
  • + *
+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ * + * @see RFC 2045 + * @since 2.2 + * @version $Id: Base64.java 1697293 2015-08-24 01:01:00Z sebb $ + */ +public class Base64 { + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * Chunk size per RFC 2045 section 6.8. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

+ * + * @see RFC 2045 section 6.8 + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / + * changed to - and _ to make the encoded Base64 results more URL-SAFE. + * This table is only used when the Base64's mode is set to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + /** + * Byte used to pad output. + */ + private static final byte PAD = '='; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in + * Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 + * alphabet but fall within the bounds of the array are translated to -1. + * + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both + * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] DECODE_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + /** Mask used to extract 8 bits, used in decoding base64 bytes */ + private static final int MASK_8BITS = 0xff; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able + * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch + * between the two modes. + */ + private final byte[] encodeTable; + + /** + * Line length for encoding. Not used when decoding. A value of zero or less implies no chunking of the base64 + * encoded data. + */ + private final int lineLength; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * decodeSize = 3 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * encodeSize = 4 + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Buffer for streaming. + */ + private byte[] buffer; + + /** + * Position where next character should be written in the buffer. + */ + private int pos; + + /** + * Position where next character should be read from the buffer. + */ + private int readPos; + + /** + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to + * make sure each encoded line never goes beyond lineLength (if lineLength > 0). + */ + private int currentLinePos; + + /** + * Writes to the buffer only occur after every 3 reads when encoding, an every 4 reads when decoding. This variable + * helps track that. + */ + private int modulus; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this Base64 object becomes useless, + * and must be thrown away. + */ + private boolean eof; + + /** + * Place holder for the 3 bytes we're dealing with for our base64 logic. Bitwise operations store and extract the + * base64 encoding or decoding from this variable. + */ + private int x; + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ */ + public Base64() { + this(false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ * + * @param urlSafe + * if true, URL-safe encoding is used. In most cases this should be set to + * false. + * @since 1.4 + */ + public Base64(boolean urlSafe) { + this(CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @since 1.4 + */ + public Base64(int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException + * Thrown when the provided lineSeparator included some base64 characters. + * @since 1.4 + */ + public Base64(int lineLength, byte[] lineSeparator) { + this(lineLength, lineSeparator, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param urlSafe + * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * @throws IllegalArgumentException + * The provided lineSeparator included some base64 characters. That's not going to work! + * @since 1.4 + */ + public Base64(int lineLength, byte[] lineSeparator, boolean urlSafe) { + if (lineSeparator == null) { + lineLength = 0; // disable chunk-separating + lineSeparator = EMPTY_BYTE_ARRAY; // this just gets ignored + } + this.lineLength = lineLength > 0 ? (lineLength / 4) * 4 : 0; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + if (lineLength > 0) { + this.encodeSize = 4 + lineSeparator.length; + } else { + this.encodeSize = 4; + } + this.decodeSize = this.encodeSize - 1; + if (containsBase64Byte(lineSeparator)) { + String sep = newStringUtf8(lineSeparator); + throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); + } + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + /** + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. + * + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 + */ + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } + + /** + * Returns true if this Base64 object has buffered data for reading. + * + * @return true if there is Base64 object still available for reading. + */ + boolean hasData() { + return this.buffer != null; + } + + /** + * Returns the amount of buffered data available for reading. + * + * @return The amount of buffered data available for reading. + */ + int avail() { + return buffer != null ? pos - readPos : 0; + } + + /** Doubles our buffer. */ + private void resizeBuffer() { + if (buffer == null) { + buffer = new byte[DEFAULT_BUFFER_SIZE]; + pos = 0; + readPos = 0; + } else { + byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(buffer, 0, b, 0, buffer.length); + buffer = b; + } + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail + * bytes. Returns how many bytes were actually extracted. + * + * @param b + * byte[] array to extract the buffered data into. + * @param bPos + * position in byte[] array to start extraction at. + * @param bAvail + * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(byte[] b, int bPos, int bAvail) { + if (buffer != null) { + int len = Math.min(avail(), bAvail); + if (buffer != b) { + System.arraycopy(buffer, readPos, b, bPos, len); + readPos += len; + if (readPos >= pos) { + buffer = null; + } + } else { + // Re-using the original consumer's output array is only + // allowed for one round. + buffer = null; + } + return len; + } + return eof ? -1 : 0; + } + + /** + * Sets the streaming buffer. This is a small optimization where we try to buffer directly to the consumer's output + * array for one round (if the consumer calls this method first) instead of starting our own buffer. + * + * @param out + * byte[] array to buffer directly to. + * @param outPos + * Position to start buffering into. + * @param outAvail + * Amount of bytes available for direct buffering. + */ + void setInitialBuffer(byte[] out, int outPos, int outAvail) { + // We can re-use consumer's original output array under + // special circumstances, saving on some System.arraycopy(). + if (out != null && out.length == outAvail) { + buffer = out; + pos = outPos; + readPos = outPos; + } + } + + /** + *

+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last + * remaining bytes (if not multiple of 3). + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of binary data to base64 encode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void encode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + eof = true; + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + switch (modulus) { + case 1 : + buffer[pos++] = encodeTable[(x >> 2) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 4) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + buffer[pos++] = PAD; + } + break; + + case 2 : + buffer[pos++] = encodeTable[(x >> 10) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 4) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 2) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + } + break; + default: + break; // other values ignored + } + if (lineLength > 0 && pos > 0) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + modulus = (++modulus) % 3; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + x = (x << 8) + b; + if (0 == modulus) { + buffer[pos++] = encodeTable[(x >> 18) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 12) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 6) & MASK_6BITS]; + buffer[pos++] = encodeTable[x & MASK_6BITS]; + currentLinePos += 4; + if (lineLength > 0 && lineLength <= currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + currentLinePos = 0; + } + } + } + } + } + + /** + *

+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

+ *

+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of ascii data to base64 decode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void decode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + if (inAvail < 0) { + eof = true; + } + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < decodeSize) { + resizeBuffer(); + } + byte b = in[inPos++]; + if (b == PAD) { + // We're done. + eof = true; + break; + } else { + if (b >= 0 && b < DECODE_TABLE.length) { + int result = DECODE_TABLE[b]; + if (result >= 0) { + modulus = (++modulus) % 4; + x = (x << 6) + result; + if (modulus == 0) { + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + buffer[pos++] = (byte) (x & MASK_8BITS); + } + } + } + } + } + + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (eof && modulus != 0) { + x = x << 6; + switch (modulus) { + case 2 : + x = x << 6; + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + break; + case 3 : + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + break; + default: + break; // other values ignored + } + } + } + + /** + * Returns whether or not the octet is in the base 64 alphabet. + * + * @param octet + * The value to test + * @return true if the value is defined in the the base 64 alphabet, false otherwise. + * @since 1.4 + */ + public static boolean isBase64(byte octet) { + return octet == PAD || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the + * method treats whitespace as valid. + * + * @param arrayOctet + * byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; + * false, otherwise + */ + public static boolean isArrayByteBase64(byte[] arrayOctet) { + for (int i = 0; i < arrayOctet.length; i++) { + if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { + return false; + } + } + return true; + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. + * + * @param arrayOctet + * byte array to test + * @return true if any byte is a valid character in the Base64 alphabet; false herwise + */ + private static boolean containsBase64Byte(byte[] arrayOctet) { + for (byte element : arrayOctet) + { + if (isBase64(element)) { + return true; + } + } + return false; + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm into 76 character blocks separated by CRLF. + *

+ * For a non-chunking version, see {@link #encodeBase64StringUnChunked(byte[])}. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 1.4 + */ + public static String encodeBase64String(byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, true)); + } + + /** + * Encodes binary data using the base64 algorithm, without using chunking. + *

+ * For a chunking version, see {@link #encodeBase64String(byte[])}. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64StringUnChunked(byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false)); + } + + /** + * Encodes binary data using the base64 algorithm. + * + * @param binaryData + * binary data to encode + * @param useChunking whether to split the output into chunks + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64String(byte[] binaryData, boolean useChunking) { + return newStringUtf8(encodeBase64(binaryData, useChunking)); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + * @since 1.4 + */ + public static byte[] encodeBase64URLSafe(byte[] binaryData) { + return encodeBase64(binaryData, false, true); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters + * @since 1.4 + */ + public static String encodeBase64URLSafeString(byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false, true)); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks + * + * @param binaryData + * binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Decodes a String containing containing characters in the Base64 alphabet. + * + * @param pArray + * A String containing Base64 character data + * @return a byte array containing binary data + * @since 1.4 + */ + public byte[] decode(String pArray) { + return decode(getBytesUtf8(pArray)); + } + + private byte[] getBytesUtf8(String pArray) { + try { + return pArray.getBytes("UTF8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Decodes a byte[] containing containing characters in the Base64 alphabet. + * + * @param pArray + * A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public byte[] decode(byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + long len = (pArray.length * 3) / 4; + byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + decode(pArray, 0, pArray.length); + decode(pArray, 0, -1); // Notify decoder of EOF. + + // Would be nice to just return buf (like we sometimes do in the encode + // logic), but we have no idea what the line-length was (could even be + // variable). So we cannot determine ahead of time exactly how big an + // array is necessary. Hence the need to construct a 2nd byte array to + // hold the final result: + + byte[] result = new byte[pos]; + readResults(result, 0, result.length); + return result; + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { + return encodeBase64(binaryData, isChunked, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + * @since 1.4 + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe) { + return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @param maxResultSize + * The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe, int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + + long len = getEncodeLength(binaryData, isChunked ? CHUNK_SIZE : 0, isChunked ? CHUNK_SEPARATOR : EMPTY_BYTE_ARRAY); + if (len > maxResultSize) { + throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + + len + + ") than the specified maxium size of " + + maxResultSize); + } + + Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + return b64.encode(binaryData); + } + + /** + * Decodes a Base64 String into octets + * + * @param base64String + * String containing Base64 data + * @return Array containing decoded data. + * @since 1.4 + */ + public static byte[] decodeBase64(String base64String) { + return new Base64().decode(base64String); + } + + /** + * Decodes Base64 data into octets + * + * @param base64Data + * Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + return new Base64().decode(base64Data); + } + + + + /** + * Checks if a byte value is whitespace or not. + * + * @param byteToCheck + * the byte to check + * @return true if byte is whitespace, false otherwise + */ + private static boolean isWhiteSpace(byte byteToCheck) { + switch (byteToCheck) { + case ' ' : + case '\n' : + case '\r' : + case '\t' : + return true; + default : + return false; + } + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A String containing only Base64 character data + * @since 1.4 + */ + public String encodeToString(byte[] pArray) { + return newStringUtf8(encode(pArray)); + } + + private static String newStringUtf8(byte[] encode) { + String str = null; + try { + str = new String(encode, "UTF8"); + } catch (UnsupportedEncodingException ue) { + throw new RuntimeException(ue); + } + return str; + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public byte[] encode(byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + long len = getEncodeLength(pArray, lineLength, lineSeparator); + byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + encode(pArray, 0, pArray.length); + encode(pArray, 0, -1); // Notify encoder of EOF. + // Encoder might have resized, even though it was unnecessary. + if (buffer != buf) { + readResults(buf, 0, buf.length); + } + // In URL-SAFE mode we skip the padding characters, so sometimes our + // final length is a bit smaller. + if (isUrlSafe() && pos < buf.length) { + byte[] smallerBuf = new byte[pos]; + System.arraycopy(buf, 0, smallerBuf, 0, pos); + buf = smallerBuf; + } + return buf; + } + + /** + * Pre-calculates the amount of space needed to base64-encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * @param chunkSize line-length of the output (<= 0 means no chunking) between each + * chunkSeparator (e.g. CRLF). + * @param chunkSeparator the sequence of bytes used to separate chunks of output (e.g. CRLF). + * + * @return amount of space needed to encoded the supplied array. Returns + * a long since a max-len array will require Integer.MAX_VALUE + 33%. + */ + private static long getEncodeLength(byte[] pArray, int chunkSize, byte[] chunkSeparator) { + // base64 always encodes to multiples of 4. + chunkSize = (chunkSize / 4) * 4; + + long len = (pArray.length * 4) / 3; + long mod = len % 4; + if (mod != 0) { + len += 4 - mod; + } + if (chunkSize > 0) { + boolean lenChunksPerfectly = len % chunkSize == 0; + len += (len / chunkSize) * chunkSeparator.length; + if (!lenChunksPerfectly) { + len += chunkSeparator.length; + } + } + return len; + } + + // Implementation of integer encoding used for crypto + /** + * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param pArray + * a byte array containing base64 character data + * @return A BigInteger + * @since 1.4 + */ + public static BigInteger decodeInteger(byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param bigInt + * a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException + * if null is passed in + * @since 1.4 + */ + public static byte[] encodeInteger(BigInteger bigInt) { + if (bigInt == null) { + throw new NullPointerException("encodeInteger called with null parameter"); + } + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Returns a byte-array representation of a BigInteger without sign bit. + * + * @param bigInt + * BigInteger to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(BigInteger bigInt) { + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; + } + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; + } + int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + + /** + * Resets this Base64 object to its initial newly constructed state. + */ + private void reset() { + buffer = null; + pos = 0; + readPos = 0; + currentLinePos = 0; + modulus = 0; + eof = false; + } + + // Getters for use in testing + + int getLineLength() { + return lineLength; + } + + byte[] getLineSeparator() { + return lineSeparator.clone(); + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/Charsets.java b/Aria/src/main/java/org/apache/commons/net/util/Charsets.java new file mode 100644 index 00000000..bf18c6c7 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/Charsets.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.util; + +import java.nio.charset.Charset; + +/** + * Helps dealing with Charsets. + * + * @since 3.3 + */ +public class Charsets { + + /** + * Returns a charset object for the given charset name. + * + * @param charsetName + * The name of the requested charset; may be a canonical name, an alias, or null. If null, return the + * default charset. + * @return A charset object for the named charset + */ + public static Charset toCharset(String charsetName) { + return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); + } + + /** + * Returns a charset object for the given charset name. + * + * @param charsetName + * The name of the requested charset; may be a canonical name, an alias, or null. + * If null, return the default charset. + * @param defaultCharsetName the charset name to use if the requested charset is null + * + * @return A charset object for the named charset + */ + public static Charset toCharset(String charsetName, String defaultCharsetName) { + return charsetName == null ? Charset.forName(defaultCharsetName) : Charset.forName(charsetName); + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java b/Aria/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java new file mode 100644 index 00000000..01a0b8a3 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java @@ -0,0 +1,246 @@ +/* + * 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.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.Socket; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.X509ExtendedKeyManager; + +import org.apache.commons.net.io.Util; + +/** + * General KeyManager utilities + *

+ * How to use with a client certificate: + *

+ * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS",
+ *     "/path/to/privatekeystore.jks","storepassword",
+ *     "privatekeyalias", "keypassword");
+ * FTPSClient cl = new FTPSClient();
+ * cl.setKeyManager(km);
+ * cl.connect(...);
+ * 
+ * If using the default store type and the key password is the same as the + * store password, these parameters can be omitted.
+ * If the desired key is the first or only key in the keystore, the keyAlias parameter + * can be omitted, in which case the code becomes: + *
+ * KeyManager km = KeyManagerUtils.createClientKeyManager(
+ *     "/path/to/privatekeystore.jks","storepassword");
+ * FTPSClient cl = new FTPSClient();
+ * cl.setKeyManager(km);
+ * cl.connect(...);
+ * 
+ * + * @since 3.0 + */ +public final class KeyManagerUtils { + + private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType(); + + private KeyManagerUtils(){ + // Not instantiable + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * + * @param ks the keystore to use + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @param keyPass the password of the key to use + * @return the customised KeyManager + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass) + throws GeneralSecurityException + { + ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass); + return new X509KeyManager(cks); + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * + * @param storeType the type of the keyStore, e.g. "JKS" + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @param keyPass the password of the key to use + * @return the customised KeyManager + * @throws GeneralSecurityException if there is a problem creating the keystore + * @throws IOException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager( + String storeType, File storePath, String storePass, String keyAlias, String keyPass) + throws IOException, GeneralSecurityException + { + KeyStore ks = loadStore(storeType, storePath, storePass); + return createClientKeyManager(ks, keyAlias, keyPass); + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * Uses the default store type and assumes the key password is the same as the store password + * + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @return the customised KeyManager + * @throws IOException if there is a problem creating the keystore + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias) + throws IOException, GeneralSecurityException + { + return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass); + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * Uses the default store type and assumes the key password is the same as the store password. + * The key alias is found by searching the keystore for the first private key entry + * + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @return the customised KeyManager + * @throws IOException if there is a problem creating the keystore + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(File storePath, String storePass) + throws IOException, GeneralSecurityException + { + return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass); + } + + private static KeyStore loadStore(String storeType, File storePath, String storePass) + throws KeyStoreException, IOException, GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(storeType); + FileInputStream stream = null; + try { + stream = new FileInputStream(storePath); + ks.load(stream, storePass.toCharArray()); + } finally { + Util.closeQuietly(stream); + } + return ks; + } + + private static String findAlias(KeyStore ks) throws KeyStoreException { + Enumeration e = ks.aliases(); + while(e.hasMoreElements()) { + String entry = e.nextElement(); + if (ks.isKeyEntry(entry)) { + return entry; + } + } + throw new KeyStoreException("Cannot find a private key entry"); + } + + private static class ClientKeyStore { + + private final X509Certificate[] certChain; + private final PrivateKey key; + private final String keyAlias; + + ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException + { + this.keyAlias = keyAlias; + this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray()); + Certificate[] certs = ks.getCertificateChain(this.keyAlias); + X509Certificate[] X509certs = new X509Certificate[certs.length]; + for (int i=0; i < certs.length; i++) { + X509certs[i] = (X509Certificate) certs[i]; + } + this.certChain = X509certs; + } + + final X509Certificate[] getCertificateChain() { + return this.certChain; + } + + final PrivateKey getPrivateKey() { + return this.key; + } + + final String getAlias() { + return this.keyAlias; + } + } + + private static class X509KeyManager extends X509ExtendedKeyManager { + + private final ClientKeyStore keyStore; + + X509KeyManager(final ClientKeyStore keyStore) { + this.keyStore = keyStore; + } + + // Call sequence: 1 + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return keyStore.getAlias(); + } + + // Call sequence: 2 + @Override + public X509Certificate[] getCertificateChain(String alias) { + return keyStore.getCertificateChain(); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return new String[]{ keyStore.getAlias()}; + } + + // Call sequence: 3 + @Override + public PrivateKey getPrivateKey(String alias) { + return keyStore.getPrivateKey(); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return null; + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return null; + } + + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/ListenerList.java b/Aria/src/main/java/org/apache/commons/net/util/ListenerList.java new file mode 100644 index 00000000..10a3ffce --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/ListenerList.java @@ -0,0 +1,66 @@ +/* + * 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; + +/** + */ + +public class ListenerList implements Serializable, Iterable +{ + private static final long serialVersionUID = -1934227607974228213L; + + 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. + * + * @return an {@link Iterator} for the {@link EventListener} instances + * @since 2.0 + * TODO Check that this is a good defensive strategy + */ + @Override + public Iterator iterator() { + return __listeners.iterator(); + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/SSLContextUtils.java b/Aria/src/main/java/org/apache/commons/net/util/SSLContextUtils.java new file mode 100644 index 00000000..61c8ddac --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/SSLContextUtils.java @@ -0,0 +1,73 @@ +/* + * 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.IOException; +import java.security.GeneralSecurityException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * General utilities for SSLContext. + * @since 3.0 + */ +public class SSLContextUtils { + + private SSLContextUtils() { + // Not instantiable + } + + /** + * Create and initialise an SSLContext. + * @param protocol the protocol used to instatiate the context + * @param keyManager the key manager, may be {@code null} + * @param trustManager the trust manager, may be {@code null} + * @return the initialised context. + * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs + */ + public static SSLContext createSSLContext(String protocol, KeyManager keyManager, TrustManager trustManager) + throws IOException { + return createSSLContext(protocol, + keyManager == null ? null : new KeyManager[] { keyManager }, + trustManager == null ? null : new TrustManager[] { trustManager }); + } + + /** + * Create and initialise an SSLContext. + * @param protocol the protocol used to instatiate the context + * @param keyManagers the array of key managers, may be {@code null} but array entries must not be {@code null} + * @param trustManagers the array of trust managers, may be {@code null} but array entries must not be {@code null} + * @return the initialised context. + * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs + */ + public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers, TrustManager[] trustManagers) + throws IOException { + SSLContext ctx; + try { + ctx = SSLContext.getInstance(protocol); + ctx.init(keyManagers, trustManagers, /*SecureRandom*/ null); + } catch (GeneralSecurityException e) { + IOException ioe = new IOException("Could not initialize SSL context"); + ioe.initCause(e); + throw ioe; + } + return ctx; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java b/Aria/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java new file mode 100644 index 00000000..bd8ce840 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/SSLSocketUtils.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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.net.ssl.SSLSocket; + +/** + * General utilities for SSLSocket. + * @since 3.4 + */ +public class SSLSocketUtils { + private SSLSocketUtils() { + // Not instantiable + } + + /** + * Enable the HTTPS endpoint identification algorithm on an SSLSocket. + * @param socket the SSL socket + * @return {@code true} on success (this is only supported on Java 1.7+) + */ + public static boolean enableEndpointNameVerification(SSLSocket socket) { + try { + Class cls = Class.forName("javax.net.ssl.SSLParameters"); + Method setEndpointIdentificationAlgorithm = cls.getDeclaredMethod("setEndpointIdentificationAlgorithm", String.class); + Method getSSLParameters = SSLSocket.class.getDeclaredMethod("getSSLParameters"); + Method setSSLParameters = SSLSocket.class.getDeclaredMethod("setSSLParameters", cls); + if (setEndpointIdentificationAlgorithm != null && getSSLParameters != null && setSSLParameters != null) { + Object sslParams = getSSLParameters.invoke(socket); + if (sslParams != null) { + setEndpointIdentificationAlgorithm.invoke(sslParams, "HTTPS"); + setSSLParameters.invoke(socket, sslParams); + return true; + } + } + } catch (SecurityException e) { // Ignored + } catch (ClassNotFoundException e) { // Ignored + } catch (NoSuchMethodException e) { // Ignored + } catch (IllegalArgumentException e) { // Ignored + } catch (IllegalAccessException e) { // Ignored + } catch (InvocationTargetException e) { // Ignored + } + return false; + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/SubnetUtils.java b/Aria/src/main/java/org/apache/commons/net/util/SubnetUtils.java new file mode 100644 index 00000000..4c7ba5c0 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/SubnetUtils.java @@ -0,0 +1,362 @@ +/* + * 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" + * @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; + + /** Whether the broadcast/network address are included in host count */ + private boolean inclusiveHostCount = false; + + + /** + * 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" + * @throws IllegalArgumentException if the parameter is invalid, + * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32 + */ + public SubnetUtils(String cidrNotation) { + calculate(cidrNotation); + } + + /** + * Constructor that takes a dotted decimal address and a dotted decimal mask. + * @param address An IP address, e.g. "192.168.0.1" + * @param mask A dotted decimal netmask e.g. "255.255.0.0" + * @throws IllegalArgumentException if the address or mask is invalid, + * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros + */ + public SubnetUtils(String address, String mask) { + calculate(toCidrNotation(address, mask)); + } + + + /** + * Returns true if the return value of {@link SubnetInfo#getAddressCount()} + * includes the network and broadcast addresses. + * @since 2.2 + * @return true if the hostcount includes the network and broadcast addresses + */ + public boolean isInclusiveHostCount() { + return inclusiveHostCount; + } + + /** + * Set to true if you want the return value of {@link SubnetInfo#getAddressCount()} + * to include the network and broadcast addresses. + * @param inclusiveHostCount true if network and broadcast addresses are to be included + * @since 2.2 + */ + public void setInclusiveHostCount(boolean inclusiveHostCount) { + this.inclusiveHostCount = inclusiveHostCount; + } + + + + /** + * Convenience container for subnet summary information. + * + */ + public final class SubnetInfo { + /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */ + private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; + + private SubnetInfo() {} + + private int netmask() { return netmask; } + private int network() { return network; } + private int address() { return address; } + private int broadcast() { return broadcast; } + + // long versions of the values (as unsigned int) which are more suitable for range checking + private long networkLong() { return network & UNSIGNED_INT_MASK; } + private long broadcastLong(){ return broadcast & UNSIGNED_INT_MASK; } + + private int low() { + return (isInclusiveHostCount() ? network() : + broadcastLong() - networkLong() > 1 ? network() + 1 : 0); + } + + private int high() { + return (isInclusiveHostCount() ? broadcast() : + broadcastLong() - networkLong() > 1 ? broadcast() -1 : 0); + } + + /** + * Returns true if the parameter address is in the + * range of usable endpoint addresses for this subnet. This excludes the + * network and broadcast adresses. + * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" + * @return True if in range, false otherwise + */ + public boolean isInRange(String address) { + return isInRange(toInteger(address)); + } + + /** + * + * @param address the address to check + * @return true if it is in range + * @since 3.4 (made public) + */ + public boolean isInRange(int address) { + long addLong = address & UNSIGNED_INT_MASK; + long lowLong = low() & UNSIGNED_INT_MASK; + long highLong = high() & UNSIGNED_INT_MASK; + return addLong >= lowLong && addLong <= highLong; + } + + 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())); + } + + /** + * Return the low address as a dotted IP address. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getLowAddress() { + return format(toArray(low())); + } + + /** + * Return the high address as a dotted IP address. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getHighAddress() { + return format(toArray(high())); + } + + /** + * Get the count of available addresses. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * @return the count of addresses, may be zero. + * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} + * @deprecated (3.4) use {@link #getAddressCountLong()} instead + */ + @Deprecated + public int getAddressCount() { + long countLong = getAddressCountLong(); + if (countLong > Integer.MAX_VALUE) { + throw new RuntimeException("Count is larger than an integer: " + countLong); + } + // N.B. cannot be negative + return (int)countLong; + } + + /** + * Get the count of available addresses. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * @return the count of addresses, may be zero. + * @since 3.4 + */ + public long getAddressCountLong() { + long b = broadcastLong(); + long n = networkLong(); + long count = b - n + (isInclusiveHostCount() ? 1 : -1); + return count < 0 ? 0 : count; + } + + public int asInteger(String address) { + return toInteger(address); + } + + public String getCidrSignature() { + return toCidrNotation( + format(toArray(address())), + format(toArray(netmask())) + ); + } + + public String[] getAllAddresses() { + int ct = getAddressCount(); + String[] addresses = new String[ct]; + if (ct == 0) { + return addresses; + } + for (int add = low(), j=0; add <= high(); ++add, ++j) { + addresses[j] = format(toArray(add)); + } + return addresses; + } + + /** + * {@inheritDoc} + * @since 2.2 + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]") + .append(" Netmask: [").append(getNetmask()).append("]\n") + .append("Network:\t[").append(getNetworkAddress()).append("]\n") + .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n") + .append("First Address:\t[").append(getLowAddress()).append("]\n") + .append("Last Address:\t[").append(getHighAddress()).append("]\n") + .append("# Addresses:\t[").append(getAddressCount()).append("]\n"); + return buf.toString(); + } + } + + /** + * Return a {@link SubnetInfo} instance that contains subnet-specific statistics + * @return new instance + */ + 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); + 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. + * Checks if a value x is in the range [begin,end]. + * Returns x if it is in range, throws an exception otherwise. + */ + private int rangeCheck(int value, int begin, int end) { + if (value >= begin && value <= end) { // (begin,end] + return value; + } + + throw new IllegalArgumentException("Value [" + value + "] not in range ["+begin+","+end+"]"); + } + + /* + * 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)); + } +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java b/Aria/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java new file mode 100644 index 00000000..c2649f79 --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java @@ -0,0 +1,117 @@ +/* + * 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.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +/** + * TrustManager utilities for generating TrustManagers. + * + * @since 3.0 + */ +public final class TrustManagerUtils +{ + private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{}; + + private static class TrustManager implements X509TrustManager { + + private final boolean checkServerValidity; + + TrustManager(boolean checkServerValidity) { + this.checkServerValidity = checkServerValidity; + } + + /** + * Never generates a CertificateException. + */ + @Override + public void checkClientTrusted(X509Certificate[] certificates, String authType) + { + return; + } + + @Override + public void checkServerTrusted(X509Certificate[] certificates, String authType) + throws CertificateException + { + if (checkServerValidity) { + for (X509Certificate certificate : certificates) + { + certificate.checkValidity(); + } + } + } + + /** + * @return an empty array of certificates + */ + @Override + public X509Certificate[] getAcceptedIssuers() + { + return EMPTY_X509CERTIFICATE_ARRAY; + } + } + + private static final X509TrustManager ACCEPT_ALL=new TrustManager(false); + + private static final X509TrustManager CHECK_SERVER_VALIDITY=new TrustManager(true); + + /** + * Generate a TrustManager that performs no checks. + * + * @return the TrustManager + */ + public static X509TrustManager getAcceptAllTrustManager(){ + return ACCEPT_ALL; + } + + /** + * Generate a TrustManager that checks server certificates for validity, + * but otherwise performs no checks. + * + * @return the validating TrustManager + */ + public static X509TrustManager getValidateServerCertificateTrustManager(){ + return CHECK_SERVER_VALIDITY; + } + + /** + * Return the default TrustManager provided by the JVM. + *

+ * This should be the same as the default used by + * {@link javax.net.ssl.SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom) + * SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} + * when the TrustManager parameter is set to {@code null} + * @param keyStore the KeyStore to use, may be {@code null} + * @return the default TrustManager + * @throws GeneralSecurityException if an error occurs + */ + public static X509TrustManager getDefaultTrustManager(KeyStore keyStore) throws GeneralSecurityException { + String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory instance = TrustManagerFactory.getInstance(defaultAlgorithm); + instance.init(keyStore); + return (X509TrustManager) instance.getTrustManagers()[0]; + } + +} diff --git a/Aria/src/main/java/org/apache/commons/net/util/package-info.java b/Aria/src/main/java/org/apache/commons/net/util/package-info.java new file mode 100644 index 00000000..5515190e --- /dev/null +++ b/Aria/src/main/java/org/apache/commons/net/util/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Utility classes + */ +package org.apache.commons.net.util; \ No newline at end of file diff --git a/AriaAnnotations/src/main/java/com/arialyy/annotations/Upload.java b/AriaAnnotations/src/main/java/com/arialyy/annotations/Upload.java index 609c6663..cc197f92 100644 --- a/AriaAnnotations/src/main/java/com/arialyy/annotations/Upload.java +++ b/AriaAnnotations/src/main/java/com/arialyy/annotations/Upload.java @@ -44,18 +44,12 @@ import java.lang.annotation.Target; } /** - * 如果你在方法中添加{@code @Upload.onTaskPre}注解,在任务预处理完成时,Aria会调用该方法 + * 如果你在方法中添加{@code @Upload.onTaskResume}注解,在任务恢复下载时,Aria会调用该方法 */ - @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) @interface onTaskPre { + @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) @interface onTaskResume { String[] value() default { AriaConstance.NO_URL }; } - /** - * 如果你在方法中添加{@code @Upload.onTaskResume}注解,在任务恢复下载时,Aria会调用该方法 - */ - //@Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) public @interface onTaskResume { - //} - /** * 如果你在方法中添加{@code @Upload.onTaskStart}注解,在任务开始下载时,Aria会调用该方法 */ @@ -101,8 +95,7 @@ import java.lang.annotation.Target; /** * 如果你在方法中添加{@code @Upload.onNoSupportBreakPoint}注解,如果该任务不支持断点,Aria会调用该方法 */ - @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) - @interface onNoSupportBreakPoint { + @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) @interface onNoSupportBreakPoint { String[] value() default { AriaConstance.NO_URL }; } } diff --git a/AriaCompiler/src/main/java/com/arialyy/compiler/AriaProcessor.java b/AriaCompiler/src/main/java/com/arialyy/compiler/AriaProcessor.java index 814c6945..72c4b985 100644 --- a/AriaCompiler/src/main/java/com/arialyy/compiler/AriaProcessor.java +++ b/AriaCompiler/src/main/java/com/arialyy/compiler/AriaProcessor.java @@ -70,8 +70,7 @@ import javax.lang.model.element.TypeElement; annotataions.add(Upload.onTaskCancel.class.getCanonicalName()); annotataions.add(Upload.onTaskComplete.class.getCanonicalName()); annotataions.add(Upload.onTaskFail.class.getCanonicalName()); - annotataions.add(Upload.onTaskPre.class.getCanonicalName()); - //annotataions.add(Upload.onTaskResume.class.getCanonicalName()); + annotataions.add(Upload.onTaskResume.class.getCanonicalName()); annotataions.add(Upload.onTaskRunning.class.getCanonicalName()); annotataions.add(Upload.onTaskStart.class.getCanonicalName()); annotataions.add(Upload.onTaskStop.class.getCanonicalName()); diff --git a/AriaCompiler/src/main/java/com/arialyy/compiler/ElementHandler.java b/AriaCompiler/src/main/java/com/arialyy/compiler/ElementHandler.java index 5cbdd005..fa4ce99e 100644 --- a/AriaCompiler/src/main/java/com/arialyy/compiler/ElementHandler.java +++ b/AriaCompiler/src/main/java/com/arialyy/compiler/ElementHandler.java @@ -120,8 +120,7 @@ class ElementHandler { saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskComplete.class, ProxyConstance.TASK_COMPLETE); saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskFail.class, ProxyConstance.TASK_FAIL); - saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskPre.class, ProxyConstance.TASK_PRE); - //saveMethod(false, roundEnv, Upload.onTaskResume.class); + saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskResume.class, ProxyConstance.TASK_RESUME); saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskRunning.class, ProxyConstance.TASK_RUNNING); saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskStart.class, ProxyConstance.TASK_START); saveMethod(TaskEnum.UPLOAD, roundEnv, Upload.onTaskStop.class, ProxyConstance.TASK_STOP); @@ -484,10 +483,10 @@ class ElementHandler { values = method.getAnnotation(Upload.onPre.class).value(); break; case ProxyConstance.TASK_PRE: - values = method.getAnnotation(Upload.onTaskPre.class).value(); + //values = method.getAnnotation(Upload.onTaskPre.class).value(); break; case ProxyConstance.TASK_RESUME: - //values = method.getAnnotation(Upload.onTaskResume.class).value(); + values = method.getAnnotation(Upload.onTaskResume.class).value(); break; case ProxyConstance.TASK_START: values = method.getAnnotation(Upload.onTaskStart.class).value(); diff --git a/AriaFtpPlug/.gitignore b/AriaFtpPlug/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/AriaFtpPlug/.gitignore @@ -0,0 +1 @@ +/build diff --git a/AriaFtpPlug/bintray-release.gradle b/AriaFtpPlug/bintray-release.gradle new file mode 100644 index 00000000..e9de9e77 --- /dev/null +++ b/AriaFtpPlug/bintray-release.gradle @@ -0,0 +1,12 @@ +apply plugin: 'bintray-release' +publish { + artifactId = 'aria-ftp-plug' + userOrg = rootProject.userOrg + groupId = rootProject.groupId + // uploadName = rootProject.uploadName + uploadName = 'AriaFtpPlug' + publishVersion = '1.0.0' + description = rootProject.description + website = rootProject.website + licences = rootProject.licences +} \ No newline at end of file diff --git a/AriaFtpPlug/build.gradle b/AriaFtpPlug/build.gradle new file mode 100644 index 00000000..0b581ad5 --- /dev/null +++ b/AriaFtpPlug/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'java' + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +sourceCompatibility = JavaVersion.VERSION_1_7 +targetCompatibility = JavaVersion.VERSION_1_7 + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +//apply from: 'bintray-release.gradle' \ No newline at end of file diff --git a/AriaFtpPlug/proguard-rules.pro b/AriaFtpPlug/proguard-rules.pro new file mode 100644 index 00000000..36df18ca --- /dev/null +++ b/AriaFtpPlug/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketClient.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketClient.java new file mode 100644 index 00000000..a2980f0b --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketClient.java @@ -0,0 +1,314 @@ +/* + * 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; +import java.nio.charset.Charset; + +/*** + * 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. + * + * + * @see DatagramSocketFactory + ***/ + +public abstract class DatagramSocketClient +{ + /*** + * The default DatagramSocketFactory shared by all DatagramSocketClient + * instances. + ***/ + private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY = + new DefaultDatagramSocketFactory(); + + /** + * Charset to use for byte IO. + */ + private Charset charset = Charset.defaultCharset(); + + /*** 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. + * + * @throws 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. + * @throws 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. + * @throws 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. + ***/ + public void close() + { + if (_socket_ != null) { + _socket_.close(); + } + _socket_ = null; + _isOpen_ = false; + } + + + /*** + * Returns true if the client has a currently open socket. + * + * @return True if the client has a currently 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. + * @throws SocketException if an error setting the timeout + ***/ + 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. + * @throws SocketException if an error getting the timeout + ***/ + 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; + } + } + + /** + * Gets the charset name. + * + * @return the charset name. + * @since 3.3 + * TODO Will be deprecated once the code requires Java 1.6 as a mininmum + */ + public String getCharsetName() { + return charset.name(); + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 3.3 + */ + public Charset getCharset() { + return charset; + } + + /** + * Sets the charset. + * + * @param charset the charset. + * @since 3.3 + */ + public void setCharset(Charset charset) { + this.charset = charset; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketFactory.java new file mode 100644 index 00000000..c05a5321 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketFactory.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; + +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 DatagramSocketClient} + * . + * This allows you to provide your own DatagramSocket implementations and + * to perform security checks or browser capability requests before + * creating a DatagramSocket. + * + * + ***/ + +public interface DatagramSocketFactory +{ + + /*** + * Creates a DatagramSocket on the local host at the first available port. + * @return the socket + * + * @throws 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. + * @return the socket + * @throws 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. + * @return the socket + * @throws SocketException If the socket could not be created. + ***/ + public DatagramSocket createDatagramSocket(int port, InetAddress laddr) + throws SocketException; +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java new file mode 100644 index 00000000..0f7d4475 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java @@ -0,0 +1,79 @@ +/* + * 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 DatagramSocketClient} + * implementations. + * + * + * @see DatagramSocketFactory + * @see DatagramSocketClient + * @see DatagramSocketClient#setDatagramSocketFactory + ***/ + +public class DefaultDatagramSocketFactory implements DatagramSocketFactory +{ + + /*** + * Creates a DatagramSocket on the local host at the first available port. + * @return a new DatagramSocket + * @throws SocketException If the socket could not be created. + ***/ + @Override + 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. + * @return a new DatagramSocket + * @throws SocketException If the socket could not be created. + ***/ + @Override + 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. + * @return a new DatagramSocket + * @throws SocketException If the socket could not be created. + ***/ + @Override + public DatagramSocket createDatagramSocket(int port, InetAddress laddr) + throws SocketException + { + return new DatagramSocket(port, laddr); + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultSocketFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultSocketFactory.java new file mode 100644 index 00000000..3f1aad18 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultSocketFactory.java @@ -0,0 +1,230 @@ +/* + * 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.InetSocketAddress; +import java.net.Proxy; +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 SocketClient} + * implementations. + * + * + * @see SocketFactory + * @see SocketClient + * @see SocketClient#setSocketFactory + ***/ + +public class DefaultSocketFactory extends SocketFactory +{ + /** The proxy to use when creating new sockets. */ + private final Proxy connProxy; + + /** + * The default constructor. + */ + public DefaultSocketFactory() + { + this(null); + } + + /** + * A constructor for sockets with proxy support. + * + * @param proxy The Proxy to use when creating new Sockets. + * @since 3.2 + */ + public DefaultSocketFactory(Proxy proxy) + { + connProxy = proxy; + } + + /** + * Creates an unconnected Socket. + * + * @return A new unconnected Socket. + * @throws IOException If an I/O error occurs while creating the Socket. + * @since 3.2 + */ + @Override + public Socket createSocket() throws IOException + { + if (connProxy != null) + { + return new Socket(connProxy); + } + return new Socket(); + } + + /*** + * 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. + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(String host, int port) + throws UnknownHostException, IOException + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.connect(new InetSocketAddress(host, port)); + return s; + } + 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. + * @throws IOException If an I/O error occurs while creating the Socket. + ***/ + @Override + public Socket createSocket(InetAddress address, int port) + throws IOException + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.connect(new InetSocketAddress(address, port)); + return s; + } + 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. + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws 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 + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.bind(new InetSocketAddress(localAddr, localPort)); + s.connect(new InetSocketAddress(host, port)); + return s; + } + 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. + * @throws 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 + { + if (connProxy != null) + { + Socket s = new Socket(connProxy); + s.bind(new InetSocketAddress(localAddr, localPort)); + s.connect(new InetSocketAddress(address, port)); + return s; + } + 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. + * @throws 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. + * @throws 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. + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/MalformedServerReplyException.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/MalformedServerReplyException.java new file mode 100644 index 00000000..e5ade4e3 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/MalformedServerReplyException.java @@ -0,0 +1,56 @@ +/* + * 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. + * + * + ***/ + +public class MalformedServerReplyException extends IOException +{ + + private static final long serialVersionUID = 6006765264250543945L; + + /*** 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/AriaFtpPlug/src/main/java/org/apache/commons/net/PrintCommandListener.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/PrintCommandListener.java new file mode 100644 index 00000000..8fff4ed1 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/PrintCommandListener.java @@ -0,0 +1,202 @@ +/* + * 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.PrintStream; +import java.io.PrintWriter; + +/*** + * 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 final PrintWriter __writer; + private final boolean __nologin; + private final char __eolMarker; + private final boolean __directionMarker; + + /** + * Create the default instance which prints everything. + * + * @param stream where to write the commands and responses + * e.g. System.out + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream) + { + this(new PrintWriter(stream)); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param stream where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream, boolean suppressLogin) { + this(new PrintWriter(stream), suppressLogin); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param stream where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker) { + this(new PrintWriter(stream), suppressLogin, eolMarker); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param stream where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output + * + * @since 3.0 + */ + public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker, boolean showDirection) { + this(new PrintWriter(stream), suppressLogin, eolMarker, showDirection); + } + + /** + * Create the default instance which prints everything. + * + * @param writer where to write the commands and responses + */ + public PrintCommandListener(PrintWriter writer) + { + this(writer, false); + } + + /** + * Create an instance which optionally suppresses login command text. + * + * @param writer where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * + * @since 3.0 + */ + public PrintCommandListener(PrintWriter writer, boolean suppressLogin) + { + this(writer, suppressLogin, (char) 0); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param writer where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * + * @since 3.0 + */ + public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker) + { + this(writer, suppressLogin, eolMarker, false); + } + + /** + * Create an instance which optionally suppresses login command text + * and indicates where the EOL starts with the specified character. + * + * @param writer where to write the commands and responses + * @param suppressLogin if {@code true}, only print command name for login + * @param eolMarker if non-zero, add a marker just before the EOL. + * @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output + * + * @since 3.0 + */ + public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker, boolean showDirection) + { + __writer = writer; + __nologin = suppressLogin; + __eolMarker = eolMarker; + __directionMarker = showDirection; + } + + @Override + public void protocolCommandSent(ProtocolCommandEvent event) + { + if (__directionMarker) { + __writer.print("> "); + } + if (__nologin) { + String cmd = event.getCommand(); + if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) { + __writer.print(cmd); + __writer.println(" *******"); // Don't bother with EOL marker for this! + } else { + final String IMAP_LOGIN = "LOGIN"; + if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP + String msg = event.getMessage(); + msg=msg.substring(0, msg.indexOf(IMAP_LOGIN)+IMAP_LOGIN.length()); + __writer.print(msg); + __writer.println(" *******"); // Don't bother with EOL marker for this! + } else { + __writer.print(getPrintableString(event.getMessage())); + } + } + } else { + __writer.print(getPrintableString(event.getMessage())); + } + __writer.flush(); + } + + private String getPrintableString(String msg){ + if (__eolMarker == 0) { + return msg; + } + int pos = msg.indexOf(SocketClient.NETASCII_EOL); + if (pos > 0) { + StringBuilder sb = new StringBuilder(); + sb.append(msg.substring(0,pos)); + sb.append(__eolMarker); + sb.append(msg.substring(pos)); + return sb.toString(); + } + return msg; + } + + @Override + public void protocolReplyReceived(ProtocolCommandEvent event) + { + if (__directionMarker) { + __writer.print("< "); + } + __writer.print(event.getMessage()); + __writer.flush(); + } +} + diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java new file mode 100644 index 00000000..d921c615 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java @@ -0,0 +1,148 @@ +/* + * 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 + ***/ + +public class ProtocolCommandEvent extends EventObject +{ + private static final long serialVersionUID = 403743538418947240L; + + private final int __replyCode; + private final boolean __isCommand; + private final 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. + * Includes the line terminator. + * + * @return The entire message sent to or received from the server. + ***/ + public String getMessage() + { + return __message; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandListener.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandListener.java new file mode 100644 index 00000000..f6d4231d --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandListener.java @@ -0,0 +1,58 @@ +/* + * 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 + ***/ + +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/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java new file mode 100644 index 00000000..95611f01 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java @@ -0,0 +1,135 @@ +/* + * 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 + ***/ + +public class ProtocolCommandSupport implements Serializable +{ + private static final long serialVersionUID = -8017692739988399978L; + + private final Object __source; + private final ListenerList __listeners; + + /*** + * Creates a ProtocolCommandSupport instance using the indicated source + * as the source of 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/AriaFtpPlug/src/main/java/org/apache/commons/net/SocketClient.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/SocketClient.java new file mode 100644 index 00000000..1b78872b --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/SocketClient.java @@ -0,0 +1,887 @@ +/* + * 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.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.SocketException; +import java.nio.charset.Charset; + +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 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 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. + * @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(); + + /** The default {@link ServerSocketFactory} */ + private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY = + ServerSocketFactory.getDefault(); + + /** + * A ProtocolCommandSupport object used to manage the registering of + * ProtocolCommandListeners and the firing of ProtocolCommandEvents. + */ + private ProtocolCommandSupport __commandSupport; + + /** The timeout to use after opening a socket. */ + protected int _timeout_; + + /** The socket used for the connection. */ + protected Socket _socket_; + + /** The hostname used for the connection (null = no hostname supplied). */ + protected String _hostname_; + + /** 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; + + /** Hint for SO_RCVBUF size */ + private int receiveBufferSize = -1; + + /** Hint for SO_SNDBUF size */ + private int sendBufferSize = -1; + + /** The proxy to use when connecting. */ + private Proxy connProxy; + + /** + * Charset to use for byte IO. + */ + private Charset charset = Charset.defaultCharset(); + + /** + * Default constructor for SocketClient. Initializes + * _socket_ to null, _timeout_ to 0, _defaultPort to 0, + * _isConnected_ to false, charset to {@code Charset.defaultCharset()} + * and _socketFactory_ to a shared instance of + * {@link org.apache.commons.net.DefaultSocketFactory}. + */ + public SocketClient() + { + _socket_ = null; + _hostname_ = 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. + * @throws IOException (SocketException) if a problem occurs with the socket + */ + 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. + * @throws SocketException If the socket timeout could not be set. + * @throws 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 + { + _hostname_ = null; + _connect(host, port, null, -1); + } + + /** + * 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. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname, int port) + throws SocketException, IOException + { + _hostname_ = hostname; + _connect(InetAddress.getByName(hostname), port, null, -1); + } + + + /** + * 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. + * @throws SocketException If the socket timeout could not be set. + * @throws 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 + { + _hostname_ = null; + _connect(host, port, localAddr, localPort); + } + + // helper method to allow code to be shared with connect(String,...) methods + private void _connect(InetAddress host, int port, InetAddress localAddr, int localPort) + throws SocketException, IOException + { + _socket_ = _socketFactory_.createSocket(); + if (receiveBufferSize != -1) { + _socket_.setReceiveBufferSize(receiveBufferSize); + } + if (sendBufferSize != -1) { + _socket_.setSendBufferSize(sendBufferSize); + } + if (localAddr != null) { + _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. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.UnknownHostException If the hostname cannot be resolved. + */ + public void connect(String hostname, int port, + InetAddress localAddr, int localPort) + throws SocketException, IOException + { + _hostname_ = hostname; + _connect(InetAddress.getByName(hostname), port, localAddr, localPort); + } + + + /** + * 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. + * @throws SocketException If the socket timeout could not be set. + * @throws 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 + { + _hostname_ = null; + 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. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most + * cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.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. + *

+ * @throws IOException If there is an error closing the socket. + */ + public void disconnect() throws IOException + { + closeQuietly(_socket_); + closeQuietly(_input_); + closeQuietly(_output_); + _socket_ = null; + _hostname_ = null; + _input_ = null; + _output_ = null; + } + + private void closeQuietly(Socket socket) { + if (socket != null){ + try { + socket.close(); + } catch (IOException e) { + // Ignored + } + } + } + + private void closeQuietly(Closeable close){ + if (close != null){ + try { + close.close(); + } catch (IOException e) { + // Ignored + } + } + } + /** + * Returns true if the client is currently connected to a server. + *

+ * Delegates to {@link Socket#isConnected()} + * @return True if the client is currently connected to a server, + * false otherwise. + */ + public boolean isConnected() + { + if (_socket_ == null) { + return false; + } + + return _socket_.isConnected(); + } + + /** + * Make various checks on the socket to test if it is available for use. + * Note that the only sure test is to use it, but these checks may help + * in some cases. + * @see NET-350 + * @return {@code true} if the socket appears to be available for use + * @since 3.0 + */ + public boolean isAvailable(){ + if (isConnected()) { + try + { + if (_socket_.getInetAddress() == null) { + return false; + } + if (_socket_.getPort() == 0) { + return false; + } + if (_socket_.getRemoteSocketAddress() == null) { + return false; + } + if (_socket_.isClosed()) { + return false; + } + /* these aren't exact checks (a Socket can be half-open), + but since we usually require two-way data transfer, + we check these here too: */ + if (_socket_.isInputShutdown()) { + return false; + } + if (_socket_.isOutputShutdown()) { + return false; + } + /* ignore the result, catch exceptions: */ + _socket_.getInputStream(); + _socket_.getOutputStream(); + } + catch (IOException ioex) + { + return false; + } + return true; + } else { + return false; + } + } + + /** + * 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()}. + *

+ * To set the initial timeout, use {@link #setDefaultTimeout(int)} instead. + * + * @param timeout The timeout in milliseconds to use for the currently + * open socket connection. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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 never thrown, but subclasses might want to do so + * @since 2.0 + */ + public void setSendBufferSize(int size) throws SocketException { + sendBufferSize = size; + } + + /** + * Get the current sendBuffer size + * @return the size, or -1 if not initialised + * @since 3.0 + */ + protected int getSendBufferSize(){ + return sendBufferSize; + } + + /** + * Sets the underlying socket receive buffer size. + *

+ * @param size The size of the buffer in bytes. + * @throws SocketException never (but subclasses may wish to do so) + * @since 2.0 + */ + public void setReceiveBufferSize(int size) throws SocketException { + receiveBufferSize = size; + } + + /** + * Get the current receivedBuffer size + * @return the size, or -1 if not initialised + * @since 3.0 + */ + protected int getReceiveBufferSize(){ + return receiveBufferSize; + } + + /** + * Returns the timeout in milliseconds of the currently opened socket. + *

+ * @return The timeout in milliseconds of the currently opened socket. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + public boolean getTcpNoDelay() throws SocketException + { + return _socket_.getTcpNoDelay(); + } + + /** + * Sets the SO_KEEPALIVE flag on the currently opened socket. + * + * From the Javadocs, the default keepalive time is 2 hours (although this is + * implementation dependent). It looks as though the Windows WSA sockets implementation + * allows a specific keepalive value to be set, although this seems not to be the case on + * other systems. + * @param keepAlive If true, keepAlive is turned on + * @throws SocketException if there is a problem with the socket + * @throws NullPointerException if the socket is not currently open + * @since 2.2 + */ + public void setKeepAlive(boolean keepAlive) throws SocketException { + _socket_.setKeepAlive(keepAlive); + } + + /** + * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket. + * Delegates to {@link Socket#getKeepAlive()} + * @return True if SO_KEEPALIVE is enabled. + * @throws SocketException if there is a problem with the socket + * @throws NullPointerException if the socket is not currently open + * @since 2.2 + */ + public boolean getKeepAlive() throws SocketException { + return _socket_.getKeepAlive(); + } + + /** + * 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?) + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + 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. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open + */ + public int getSoLinger() throws SocketException + { + return _socket_.getSoLinger(); + } + + + /** + * Returns the port number of the open socket on the local host used + * for the connection. + * Delegates to {@link Socket#getLocalPort()} + *

+ * @return The port number of the open socket on the local host used + * for the connection. + * @throws NullPointerException if the socket is not currently open + */ + public int getLocalPort() + { + return _socket_.getLocalPort(); + } + + + /** + * Returns the local address to which the client's socket is bound. + * Delegates to {@link Socket#getLocalAddress()} + *

+ * @return The local address to which the client's socket is bound. + * @throws NullPointerException if the socket is not currently open + */ + public InetAddress getLocalAddress() + { + return _socket_.getLocalAddress(); + } + + /** + * Returns the port number of the remote host to which the client is + * connected. + * Delegates to {@link Socket#getPort()} + *

+ * @return The port number of the remote host to which the client is + * connected. + * @throws NullPointerException if the socket is not currently open + */ + public int getRemotePort() + { + return _socket_.getPort(); + } + + + /** + * @return The remote address to which the client is connected. + * Delegates to {@link Socket#getInetAddress()} + * @throws NullPointerException if the socket is not currently open + */ + 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. + *

+ * @param socket the item to check against + * @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). + * Any proxy setting is discarded. + *

+ * @param factory The new SocketFactory the SocketClient should use. + */ + public void setSocketFactory(SocketFactory factory) + { + if (factory == null) { + _socketFactory_ = __DEFAULT_SOCKET_FACTORY; + } else { + _socketFactory_ = factory; + } + // re-setting the socket factory makes the proxy setting useless, + // so set the field to null so that getProxy() doesn't return a + // Proxy that we're actually not using. + connProxy = null; + } + + /** + * 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 timeout (in ms) + * @since 2.0 + */ + public int getConnectTimeout() { + return connectTimeout; + } + + /** + * Get the underlying {@link ServerSocketFactory} + * @return The server socket factory + * @since 2.2 + */ + public ServerSocketFactory getServerSocketFactory() { + return _serverSocketFactory_; + } + + + /** + * Adds a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to add. + * @since 3.0 + */ + public void addProtocolCommandListener(ProtocolCommandListener listener) { + getCommandSupport().addProtocolCommandListener(listener); + } + + /** + * Removes a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to remove. + * @since 3.0 + */ + public void removeProtocolCommandListener(ProtocolCommandListener listener) { + getCommandSupport().removeProtocolCommandListener(listener); + } + + /** + * If there are any listeners, send them the reply details. + * + * @param replyCode the code extracted from the reply + * @param reply the full reply text + * @since 3.0 + */ + protected void fireReplyReceived(int replyCode, String reply) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireReplyReceived(replyCode, reply); + } + } + + /** + * If there are any listeners, send them the command details. + * + * @param command the command name + * @param message the complete message, including command name + * @since 3.0 + */ + protected void fireCommandSent(String command, String message) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireCommandSent(command, message); + } + } + + /** + * Create the CommandSupport instance if required + */ + protected void createCommandSupport(){ + __commandSupport = new ProtocolCommandSupport(this); + } + + /** + * Subclasses can override this if they need to provide their own + * instance field for backwards compatibilty. + * + * @return the CommandSupport instance, may be {@code null} + * @since 3.0 + */ + protected ProtocolCommandSupport getCommandSupport() { + return __commandSupport; + } + + /** + * Sets the proxy for use with all the connections. + * The proxy is used for connections established after the + * call to this method. + * + * @param proxy the new proxy for connections. + * @since 3.2 + */ + public void setProxy(Proxy proxy) { + setSocketFactory(new DefaultSocketFactory(proxy)); + connProxy = proxy; + } + + /** + * Gets the proxy for use with all the connections. + * @return the current proxy for connections. + */ + public Proxy getProxy() { + return connProxy; + } + + /** + * Gets the charset name. + * + * @return the charset. + * @since 3.3 + * @deprecated Since the code now requires Java 1.6 as a mininmum + */ + @Deprecated + public String getCharsetName() { + return charset.name(); + } + + /** + * Gets the charset. + * + * @return the charset. + * @since 3.3 + */ + public Charset getCharset() { + return charset; + } + + /** + * Sets the charset. + * + * @param charset the charset. + * @since 3.3 + */ + public void setCharset(Charset charset) { + this.charset = charset; + } + + /* + * N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility, + * so the abstract method is needed to pass the instance to the methods which were moved here. + */ +} + + diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/Configurable.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/Configurable.java new file mode 100644 index 00000000..b04ded9a --- /dev/null +++ b/AriaFtpPlug/src/main/java/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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTP.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTP.java new file mode 100644 index 00000000..8341f88b --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTP.java @@ -0,0 +1,1870 @@ +/* + * 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.io.Reader; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; + +import org.apache.commons.net.MalformedServerReplyException; +import org.apache.commons.net.ProtocolCommandSupport; +import org.apache.commons.net.SocketClient; +import org.apache.commons.net.io.CRLFLineReader; + +/*** + * 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. + * + * @see FTPClient + * @see FTPConnectionClosedException + * @see org.apache.commons.net.MalformedServerReplyException + * @version $Id: FTP.java 1782546 2017-02-11 00:05:41Z 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 transferred 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 transferred 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 transferred 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 transferred 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 transferred 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 transferred 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 transferred 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"; + + /** Length of the FTP reply code (3 alphanumerics) */ + public static final int REPLY_CODE_LEN = 3; + + private static final String __modes = "AEILNTCFRPSBC"; + + protected int _replyCode; + protected ArrayList _replyLines; + protected boolean _newReplyString; + protected String _replyString; + protected String _controlEncoding; + + /** + * A ProtocolCommandSupport object used to manage the registering of + * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + */ + protected ProtocolCommandSupport _commandSupport_; + + /** + * 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 RFC 959 for details. + */ + protected boolean strictMultilineParsing = false; + + /** + * If this is true, then non-multiline replies must have the format: + * 3 digit code + * If false, then the 3 digit code does not have to be followed by space + * See section 4.2 of RFC 959 for details. + */ + private boolean strictReplyParsing = true; + + /** + * Wraps SocketClient._input_ to facilitate the reading of text + * from 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 writing of text + * to 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_; + + /*** + * 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; + _controlEncoding = DEFAULT_CONTROL_ENCODING; + _commandSupport_ = new ProtocolCommandSupport(this); + } + + // The RFC-compliant multiline termination check + private boolean __strictCheck(String line, String code) { + return (!(line.startsWith(code) && line.charAt(REPLY_CODE_LEN) == ' ')); + } + + // 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() > REPLY_CODE_LEN&& line.charAt(REPLY_CODE_LEN) != '-' && + Character.isDigit(line.charAt(0)))); + } + + /** + * Get the reply, and pass it to command listeners + */ + private void __getReply() throws IOException + { + __getReply(true); + } + + /** + * Get the reply, but don't pass it to command listeners. + * Used for keep-alive processing only. + * @since 3.0 + * @throws IOException on error + */ + protected void __getReplyNoReport() throws IOException + { + __getReply(false); + } + + private void __getReply(boolean reportReply) 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 < REPLY_CODE_LEN) { + throw new MalformedServerReplyException( + "Truncated server reply: " + line); + } + + String code = null; + try + { + code = line.substring(0, REPLY_CODE_LEN); + _replyCode = Integer.parseInt(code); + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse response code.\nServer Reply: " + line); + } + + _replyLines.add(line); + + // Check the server reply type + if (length > REPLY_CODE_LEN) { + char sep = line.charAt(REPLY_CODE_LEN); + // Get extra lines if message continues. + if (sep == '-') { + 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)); + + } else if (isStrictReplyParsing()) { + if (length == REPLY_CODE_LEN + 1) { // expecting some text + throw new MalformedServerReplyException("Truncated server reply: '" + line +"'"); + } else if (sep != ' ') { + throw new MalformedServerReplyException("Invalid server reply: '" + line +"'"); + } + } + } else if (isStrictReplyParsing()) { + throw new MalformedServerReplyException("Truncated server reply: '" + line +"'"); + } + + if (reportReply) { + 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 + { + _connectAction_(null); + } + + + /** + * Initiates control connections and gets initial reply. + * Initializes {@link #_controlInput_} and {@link #_controlOutput_}. + * + * @param socketIsReader the reader to reuse (if non-null) + * @throws IOException on error + * @since 3.4 + */ + protected void _connectAction_(Reader socketIsReader) throws IOException { + super._connectAction_(); // sets up _input_ and _output_ + if(socketIsReader == null) { + _controlInput_ = + new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); + } else { + _controlInput_ = new CRLFLineReader(socketIsReader); + } + _controlOutput_ = + new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); + if (connectTimeout > 0) { // NET-385 + int original = _socket_.getSoTimeout(); + _socket_.setSoTimeout(connectTimeout); + try { + __getReply(); + // If we received code 120, we have to fetch completion reply. + if (FTPReply.isPositivePreliminary(_replyCode)) { + __getReply(); + } + } catch (SocketTimeoutException e) { + IOException ioe = new IOException("Timed out waiting for initial connect reply"); + ioe.initCause(e); + throw ioe; + } finally { + _socket_.setSoTimeout(original); + } + } else { + __getReply(); + // If we received code 120, we have to fetch completion reply. + if (FTPReply.isPositivePreliminary(_replyCode)) { + __getReply(); + } + } + } + + + /** + * Saves the character encoding to be 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. + *

+ * Please note that this has to be set before the connection is established. + * + * @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; + } + + + /*** + * 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. + * + * @throws 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. + * @throws 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. + * @throws 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 + { + if (_controlOutput_ == null) { + throw new IOException("Connection is not open"); + } + + final String message = __buildMessage(command, args); + + __send(message); + + fireCommandSent(command, message); + + __getReply(); + return _replyCode; + } + + private String __buildMessage(String command, String args) { + final StringBuilder __commandBuffer = new StringBuilder(); + + __commandBuffer.append(command); + + if (args != null) + { + __commandBuffer.append(' '); + __commandBuffer.append(args); + } + __commandBuffer.append(SocketClient.NETASCII_EOL); + return __commandBuffer.toString(); + } + + private void __send(String message) throws IOException, + FTPConnectionClosedException, SocketException { + try{ + _controlOutput_.write(message); + _controlOutput_.flush(); + } + catch (SocketException e) + { + if (!isConnected()) + { + throw new FTPConnectionClosedException("Connection unexpectedly closed."); + } + else + { + throw e; + } + } + } + + /** + * Send a noop and get the reply without reporting to the command listener. + * Intended for use with keep-alive. + * + * @throws IOException on error + * @since 3.0 + */ + protected void __noop() throws IOException { + String msg = __buildMessage(FTPCmd.NOOP.getCommand(), null); + __send(msg); + __getReplyNoReport(); // This may timeout + } + + /*** + * 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @deprecated (3.3) Use {@link #sendCommand(FTPCmd, String)} instead + ***/ + @Deprecated + public int sendCommand(int command, String args) throws IOException + { + return sendCommand(FTPCommand.getCommand(command), args); + } + + /** + * 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 FTPCmd enum 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.3 + */ + public int sendCommand(FTPCmd command) throws IOException{ + return sendCommand(command, 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 FTPCmd enum 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.3 + */ + public int sendCommand(FTPCmd command, String args) throws IOException{ + return sendCommand(command.getCommand(), 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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() + { + return _replyLines.toArray(new String[_replyLines.size()]); + } + + /*** + * 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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int abor() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int cdup() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int quit() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int rein() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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; + StringBuilder info = new StringBuilder(24); + + info.append(host.getHostAddress().replace('.', ',')); + num = port >>> 8; + info.append(','); + info.append(num); + info.append(','); + num = port & 0xff; + info.append(num); + + return sendCommand(FTPCmd.PORT, info.toString()); + } + + /*** + * A convenience method to send the FTP EPRT command to the server, + * receive the reply, and return the reply code. + * + * Examples: + *

    + *
  • EPRT |1|132.235.1.2|6275|
  • + *
  • EPRT |2|1080::8:800:200C:417A|5282|
  • + *
+ * + * @see "http://www.faqs.org/rfcs/rfc2428.html" + * + * @param host The host owning the port. + * @param port The new port. + * @return The reply code received from the server. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + ***/ + public int eprt(InetAddress host, int port) throws IOException + { + int num; + StringBuilder info = new StringBuilder(); + String h; + + // If IPv6, trim the zone index + h = host.getHostAddress(); + num = h.indexOf("%"); + if (num > 0) { + h = h.substring(0, num); + } + + info.append("|"); + + if (host instanceof Inet4Address) { + info.append("1"); + } else if (host instanceof Inet6Address) { + info.append("2"); + } + info.append("|"); + info.append(h); + info.append("|"); + info.append(port); + info.append("|"); + + return sendCommand(FTPCmd.EPRT, 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int pasv() throws IOException + { + return sendCommand(FTPCmd.PASV); + } + + /*** + * A convenience method to send the FTP EPSV 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + ***/ + public int epsv() throws IOException + { + return sendCommand(FTPCmd.EPSV); + } + + /** + * 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. + * @throws 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. + * @throws 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 + { + StringBuilder arg = new StringBuilder(); + + arg.append(__modes.charAt(fileType)); + arg.append(' '); + if (fileType == LOCAL_FILE_TYPE) { + arg.append(formatOrByteSize); + } else { + arg.append(__modes.charAt(formatOrByteSize)); + } + + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stou() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.ALLO, Integer.toString(bytes)); + } + + /** + * A convenience method to send the FTP FEAT command to the server, receive the reply, + * and return the reply code. + * @return The reply code received by the server + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + */ + public int feat() throws IOException + { + return sendCommand(FTPCmd.FEAT); + } + + /*** + * 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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.REST, marker); + } + + + /** + * @param file name of file + * @return the status + * @throws IOException on error + * @since 2.0 + **/ + public int mdtm(String file) throws IOException + { + return sendCommand(FTPCmd.MDTM, file); + } + + + /** + * A convenience method to send the FTP MFMT command to the server, + * receive the reply, and return the reply code. + * + * @param pathname The pathname for which mtime is to be changed + * @param timeval Timestamp in YYYYMMDDhhmmss format + * @return The reply code received from the server. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 2.2 + * @see http://tools.ietf.org/html/draft-somers-ftp-mfxx-04 + **/ + public int mfmt(String pathname, String timeval) throws IOException + { + return sendCommand(FTPCmd.MFMT, timeval + " " + pathname); + } + + + /*** + * 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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int pwd() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int list() throws IOException + { + return sendCommand(FTPCmd.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, + * may be {@code null} in which case the command is sent with no parameters + * @return The reply code received from the server. + * @throws 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. + * @throws 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(FTPCmd.LIST, pathname); + } + + /** + * A convenience method to send the FTP MLSD 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlsd() throws IOException + { + return sendCommand(FTPCmd.MLSD); + } + + /** + * A convenience method to send the FTP MLSD 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 path the path to report on + * @return The reply code received from the server, + * may be {@code null} in which case the command is sent with no parameters + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlsd(String path) throws IOException + { + return sendCommand(FTPCmd.MLSD, path); + } + + /** + * A convenience method to send the FTP MLST 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlst() throws IOException + { + return sendCommand(FTPCmd.MLST); + } + + /** + * A convenience method to send the FTP MLST 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 path the path to report on + * @return The reply code received from the server, + * may be {@code null} in which case the command is sent with no parameters + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + * @since 3.0 + */ + public int mlst(String path) throws IOException + { + return sendCommand(FTPCmd.MLST, path); + } + + /*** + * 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int nlst() throws IOException + { + return sendCommand(FTPCmd.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, + * may be {@code null} in which case the command is sent with no parameters + * @return The reply code received from the server. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int syst() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int stat() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int help() throws IOException + { + return sendCommand(FTPCmd.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. + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending the + * command or receiving the server reply. + ***/ + public int noop() throws IOException + { + return sendCommand(FTPCmd.NOOP); + } + + /** + * Return whether strict multiline parsing is enabled, as per RFC 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 the setting + * @since 2.0 + */ + public void setStrictMultilineParsing(boolean strictMultilineParsing) { + this.strictMultilineParsing = strictMultilineParsing; + } + + /** + * Return whether strict non-multiline parsing is enabled, as per RFC 959, section 4.2. + *

+ * The default is true, which requires the 3 digit code be followed by space and some text. + *
+ * If false, only the 3 digit code is required (as was the case for versions up to 3.5) + *
+ * @return True if strict (default), false if additional checks are not made + * @since 3.6 + */ + public boolean isStrictReplyParsing() { + return strictReplyParsing; + } + + /** + * Set strict non-multiline parsing. + *

+ * If true, it requires the 3 digit code be followed by space and some text. + *
+ * If false, only the 3 digit code is required (as was the case for versions up to 3.5) + *

+ * This should not be required by a well-behaved FTP server + *
+ * @param strictReplyParsing the setting + * @since 3.6 + */ + public void setStrictReplyParsing(boolean strictReplyParsing) { + this.strictReplyParsing = strictReplyParsing; + } + + /** + * Provide command support to super-class + */ + @Override + protected ProtocolCommandSupport getCommandSupport() { + return _commandSupport_; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClient.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClient.java new file mode 100644 index 00000000..fa9f5fd6 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClient.java @@ -0,0 +1,3980 @@ +/* + * 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.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Properties; +import java.util.Random; +import java.util.Set; + +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.MLSxEntryParser; +import org.apache.commons.net.io.CRLFLineReader; +import org.apache.commons.net.io.CopyStreamAdapter; +import org.apache.commons.net.io.CopyStreamEvent; +import org.apache.commons.net.io.CopyStreamListener; +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: + *

+ *    FTPClient ftp = new FTPClient();
+ *    FTPClientConfig config = new FTPClientConfig();
+ *    config.setXXX(YYY); // change required options
+ *    // for example config.setServerTimeZoneId("Pacific/Pitcairn")
+ *    ftp.configure(config );
+ *    boolean error = false;
+ *    try {
+ *      int reply;
+ *      String server = "ftp.example.com";
+ *      ftp.connect(server);
+ *      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 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 (or EPRT) 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 (or EPRT) 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 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 FTP#getReplyCode getReplyCode }, + * {@link FTP#getReplyString getReplyString }, + * and + * {@link FTP#getReplyStrings getReplyStrings}. + * You may avoid server disconnections while the client is idle by + * periodically 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 = new FTPClient();
+ *    f.connect(server);
+ *    f.login(username, password);
+ *    FTPFile[] files = f.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 = new 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 = new 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}. + *

+ * Control channel keep-alive feature: + *

+ * Please note: this does not apply to the methods where the user is responsible for writing or reading + * the data stream, i.e. {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} + * and the other xxxFileStream methods + *

+ * During file transfers, the data connection is busy, but the control connection is idle. + * FTP servers know that the control connection is in use, so won't close it through lack of activity, + * but it's a lot harder for network routers to know that the control and data connections are associated + * with each other. + * Some routers may treat the control connection as idle, and disconnect it if the transfer over the data + * connection takes longer than the allowable idle time for the router. + *
+ * One solution to this is to send a safe command (i.e. NOOP) over the control connection to reset the router's + * idle timer. This is enabled as follows: + *

+ *     ftpClient.setControlKeepAliveTimeout(300); // set timeout to 5 minutes
+ * 
+ * This will cause the file upload/download methods to send a NOOP approximately every 5 minutes. + * The following public methods support this: + *
    + *
  • {@link #retrieveFile(String, OutputStream)}
  • + *
  • {@link #appendFile(String, InputStream)}
  • + *
  • {@link #storeFile(String, InputStream)}
  • + *
  • {@link #storeUniqueFile(InputStream)}
  • + *
  • {@link #storeUniqueFileStream(String)}
  • + *
+ * This feature does not apply to the methods where the user is responsible for writing or reading + * the data stream, i.e. {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} + * and the other xxxFileStream methods. + * In such cases, the user is responsible for keeping the control connection alive if necessary. + *

+ * The implementation currently uses a {@link CopyStreamListener} which is passed to the + * {@link Util#copyStream(InputStream, OutputStream, int, long, CopyStreamListener, boolean)} + * method, so the timing is partially dependent on how long each block transfer takes. + * + * @see #FTP_SYSTEM_TYPE + * @see #SYSTEM_TYPE_PROPERTIES + * @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 +{ + /** + * The system property ({@value}) which can be used to override the system type.
+ * If defined, the value will be used to create any automatically created parsers. + * + * @since 3.0 + */ + public static final String FTP_SYSTEM_TYPE = "org.apache.commons.net.ftp.systemType"; + + /** + * The system property ({@value}) which can be used as the default system type.
+ * If defined, the value will be used if the SYST command fails. + * + * @since 3.1 + */ + public static final String FTP_SYSTEM_TYPE_DEFAULT = "org.apache.commons.net.ftp.systemType.default"; + + /** + * The name of an optional systemType properties file ({@value}), which is loaded + * using {@link Class#getResourceAsStream(String)}.
+ * The entries are the systemType (as determined by {@link FTPClient#getSystemType}) + * and the values are the replacement type or parserClass, which is passed to + * {@link FTPFileEntryParserFactory#createFileEntryParser(String)}.
+ * For example: + *

+     * Plan 9=Unix
+     * OS410=org.apache.commons.net.ftp.parser.OS400FTPEntryParser
+     * 
+ * + * @since 3.0 + */ + public static final String SYSTEM_TYPE_PROPERTIES = "/systemType.properties"; + + /** + * 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; + private int __dataTimeout; + private int __passivePort; + private String __passiveHost; + private final Random __random; + private int __activeMinPort; + private int __activeMaxPort; + private InetAddress __activeExternalHost; + private InetAddress __reportActiveExternalHost; // overrides __activeExternalHost in EPRT/PORT commands + /** The address to bind to on passive connections, if necessary. */ + private InetAddress __passiveLocalHost; + + private int __fileType; + @SuppressWarnings("unused") // fields are written, but currently not read + private int __fileFormat; + @SuppressWarnings("unused") // field is written, but currently not read + private int __fileStructure; + @SuppressWarnings("unused") // field is written, but currently not read + private int __fileTransferMode; + private boolean __remoteVerificationEnabled; + private long __restartOffset; + private FTPFileEntryParserFactory __parserFactory; + private int __bufferSize; // buffersize for buffered data streams + private int __sendDataSocketBufferSize; + private int __receiveDataSocketBufferSize; + private boolean __listHiddenFiles; + private boolean __useEPSVwithIPv4; // whether to attempt EPSV with an IPv4 connection + + // __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; + + // Key used to create the parser; necessary to ensure that the parser type is not ignored + private String __entryParserKey; + + private FTPClientConfig __configuration; + + // Listener used by store/retrieve methods to handle keepalive + private CopyStreamListener __copyStreamListener; + + // How long to wait before sending another control keep-alive message + private long __controlKeepAliveTimeout; + + // How long to wait (ms) for keepalive message replies before continuing + // Most FTP servers don't seem to support concurrent control and data connection usage + private int __controlKeepAliveReplyTimeout=1000; + + /** + * Enable or disable replacement of internal IP in passive mode. Default enabled + * using {code NatServerResolverImpl}. + */ + private HostnameResolver __passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); + + /** Pattern for PASV mode responses. Groups: (n,n,n,n),(n),(n) */ + private static final java.util.regex.Pattern __PARMS_PAT; + + static { + __PARMS_PAT = java.util.regex.Pattern.compile( + "(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})"); + } + + /** Controls the automatic server encoding detection (only UTF-8 supported). */ + private boolean __autodetectEncoding = false; + + /** Map of FEAT responses. If null, has not been initialised. */ + private HashMap> __featuresMap; + + private static class PropertiesSingleton { + + static final Properties PROPERTIES; + + static { + InputStream resourceAsStream = FTPClient.class.getResourceAsStream(SYSTEM_TYPE_PROPERTIES); + Properties p = null; + if (resourceAsStream != null) { + p = new Properties(); + try { + p.load(resourceAsStream); + } catch (IOException e) { + // Ignored + } finally { + try { + resourceAsStream.close(); + } catch (IOException e) { + // Ignored + } + } + } + PROPERTIES = p; + } + + } + private static Properties getOverrideProperties(){ + return PropertiesSingleton.PROPERTIES; + } + + /** + * 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 . + *

+ * The list parsing auto-detect feature can be configured to use lenient future + * dates (short dates may be up to one day in the future) as follows: + *

+     * FTPClient ftp = new FTPClient();
+     * FTPClientConfig config = new FTPClientConfig();
+     * config.setLenientFutureDates(true);
+     * ftp.configure(config );
+     * 
+ */ + public FTPClient() + { + __initDefaults(); + __dataTimeout = -1; + __remoteVerificationEnabled = true; + __parserFactory = new DefaultFTPFileEntryParserFactory(); + __configuration = null; + __listHiddenFiles = false; + __useEPSVwithIPv4 = false; + __random = new Random(); + __passiveLocalHost = null; + } + + + private void __initDefaults() + { + __dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; + __passiveHost = null; + __passivePort = -1; + __activeExternalHost = null; + __reportActiveExternalHost = null; + __activeMinPort = 0; + __activeMaxPort = 0; + __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; + __entryParserKey = ""; + __featuresMap = null; + } + + /** + * Parse the pathname from a CWD reply. + *

+ * According to RFC959 (http://www.ietf.org/rfc/rfc959.txt), + * it should be the same as for MKD i.e. + * {@code 257""[commentary]} + * where any double-quotes in {@code } are doubled. + * Unlike MKD, the commentary is optional. + *

+ * However, see NET-442 for an exception. + * + * @param reply + * @return the pathname, without enclosing quotes, + * or the full string after the reply code and space if the syntax is invalid + * (i.e. enclosing quotes are missing or embedded quotes are not doubled) + */ + // package protected for access by test cases + static String __parsePathname(String reply) + { + String param = reply.substring(REPLY_CODE_LEN + 1); + if (param.startsWith("\"")) { + StringBuilder sb = new StringBuilder(); + boolean quoteSeen = false; + // start after initial quote + for(int i=1; i < param.length(); i++) { + char ch = param.charAt(i); + if (ch=='"') { + if (quoteSeen) { + sb.append(ch); + quoteSeen=false; + } else { + // don't output yet, in case doubled + quoteSeen=true; + } + } else { + if (quoteSeen) { // found lone trailing quote within string + return sb.toString(); + } + sb.append(ch); // just another character + } + } + if (quoteSeen) { // found lone trailing quote at end of string + return sb.toString(); + } + } + // malformed reply, return all after reply code and space + return param; + } + + /** + * @since 3.1 + * @param reply the reply to parse + * @throws MalformedServerReplyException if the server reply does not match (n,n,n,n),(n),(n) + */ + protected 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); + } + + __passiveHost = m.group(1).replace(',', '.'); // Fix up to look like IP address + + try + { + int oct1 = Integer.parseInt(m.group(2)); + int oct2 = Integer.parseInt(m.group(3)); + __passivePort = (oct1 << 8) | oct2; + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse passive port information.\nServer Reply: " + reply); + } + + if (__passiveNatWorkaroundStrategy != null) { + try { + String passiveHost = __passiveNatWorkaroundStrategy.resolve(__passiveHost); + if (!__passiveHost.equals(passiveHost)) { + fireReplyReceived(0, + "[Replacing PASV mode reply address "+__passiveHost+" with "+passiveHost+"]\n"); + __passiveHost = passiveHost; + } + } catch (UnknownHostException e) { // Should not happen as we are passing in an IP address + throw new MalformedServerReplyException( + "Could not parse passive host information.\nServer Reply: " + reply); + } + } + } + + protected void _parseExtendedPassiveModeReply(String reply) + throws MalformedServerReplyException + { + reply = reply.substring(reply.indexOf('(') + 1, + reply.indexOf(')')).trim(); + + char delim1, delim2, delim3, delim4; + delim1 = reply.charAt(0); + delim2 = reply.charAt(1); + delim3 = reply.charAt(2); + delim4 = reply.charAt(reply.length()-1); + + if (!(delim1 == delim2) || !(delim2 == delim3) + || !(delim3 == delim4)) { + throw new MalformedServerReplyException( + "Could not parse extended passive host information.\nServer Reply: " + reply); + } + + int port; + try + { + port = Integer.parseInt(reply.substring(3, reply.length()-1)); + } + catch (NumberFormatException e) + { + throw new MalformedServerReplyException( + "Could not parse extended passive host information.\nServer Reply: " + reply); + } + + + // in EPSV mode, the passive host address is implicit + __passiveHost = getRemoteAddress().getHostAddress(); + __passivePort = port; + } + + private boolean __storeFile(FTPCmd command, String remote, InputStream local) + throws IOException + { + return _storeFile(command.getCommand(), remote, local); + } + + /** + * @since 3.1 + * @param command the command to send + * @param remote the remote file name + * @param local the local file name + * @return true if successful + * @throws IOException on error + */ + protected boolean _storeFile(String command, String remote, InputStream local) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return false; + } + + final OutputStream output; + + if (__fileType == ASCII_FILE_TYPE) { + output = new ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); + } else { + output = getBufferedOutputStream(socket.getOutputStream()); + } + + CSL csl = null; + if (__controlKeepAliveTimeout > 0) { + csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); + } + + // Treat everything else as binary for now + try + { + Util.copyStream(local, output, getBufferSize(), + CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), + false); + } + catch (IOException e) + { + Util.closeQuietly(socket); // ignore close errors here + if (csl != null) { + csl.cleanUp(); // fetch any outstanding keepalive replies + } + throw e; + } + + output.close(); // ensure the file is fully written + socket.close(); // done writing the file + if (csl != null) { + csl.cleanUp(); // fetch any outstanding keepalive replies + } + // Get the transfer response + boolean ok = completePendingCommand(); + return ok; + } + + private OutputStream __storeFileStream(FTPCmd command, String remote) + throws IOException + { + return _storeFileStream(command.getCommand(), remote); + } + + /** + * @param command the command to send + * @param remote the remote file name + * @return the output stream to write to + * @throws IOException on error + * @since 3.1 + */ + protected OutputStream _storeFileStream(String command, String remote) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return null; + } + + final OutputStream output; + 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 ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); + } else { + output = socket.getOutputStream(); + } + 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 int 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 an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @deprecated (3.3) Use {@link #_openDataConnection_(FTPCmd, String)} instead + */ + @Deprecated + protected Socket _openDataConnection_(int command, String arg) + throws IOException + { + return _openDataConnection_(FTPCommand.getCommand(command), arg); + } + + /** + * 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 int 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 an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.3 + */ + protected Socket _openDataConnection_(FTPCmd command, String arg) + throws IOException + { + return _openDataConnection_(command.getCommand(), arg); + } + + /** + * 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.1 + */ + protected Socket _openDataConnection_(String command, String arg) + throws IOException + { + if (__dataConnectionMode != ACTIVE_LOCAL_DATA_CONNECTION_MODE && + __dataConnectionMode != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { + return null; + } + + final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; + + Socket socket; + + if (__dataConnectionMode == ACTIVE_LOCAL_DATA_CONNECTION_MODE) + { + // if no activePortRange was set (correctly) -> getActivePort() = 0 + // -> new ServerSocket(0) -> bind to any free local port + ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress()); + + try { + // Try EPRT only if remote server is over IPv6, if not use PORT, + // because EPRT has no advantage over PORT on IPv4. + // It could even have the disadvantage, + // that EPRT will make the data connection fail, because + // today's intelligent NAT Firewalls are able to + // substitute IP addresses in the PORT command, + // but might not be able to recognize the EPRT command. + if (isInet6Address) { + if (!FTPReply.isPositiveCompletion(eprt(getReportHostAddress(), server.getLocalPort()))) { + return null; + } + } else { + if (!FTPReply.isPositiveCompletion(port(getReportHostAddress(), server.getLocalPort()))) { + return null; + } + } + + if ((__restartOffset > 0) && !restart(__restartOffset)) { + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { + 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); + } + socket = server.accept(); + + // Ensure the timeout is set before any commands are issued on the new socket + if (__dataTimeout >= 0) { + socket.setSoTimeout(__dataTimeout); + } + if (__receiveDataSocketBufferSize > 0) { + socket.setReceiveBufferSize(__receiveDataSocketBufferSize); + } + if (__sendDataSocketBufferSize > 0) { + socket.setSendBufferSize(__sendDataSocketBufferSize); + } + } finally { + server.close(); + } + } + else + { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE + + // Try EPSV command first on IPv6 - and IPv4 if enabled. + // When using IPv4 with NAT it has the advantage + // to work with more rare configurations. + // E.g. if FTP server has a static PASV address (external network) + // and the client is coming from another internal network. + // In that case the data connection after PASV command would fail, + // while EPSV would make the client succeed by taking just the port. + boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) + { + _parseExtendedPassiveModeReply(_replyLines.get(0)); + } + else + { + if (isInet6Address) { + return null; // Must use EPSV for IPV6 + } + // If EPSV failed on IPV4, revert to PASV + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { + return null; + } + _parsePassiveModeReply(_replyLines.get(0)); + } + + socket = _socketFactory_.createSocket(); + if (__receiveDataSocketBufferSize > 0) { + socket.setReceiveBufferSize(__receiveDataSocketBufferSize); + } + if (__sendDataSocketBufferSize > 0) { + socket.setSendBufferSize(__sendDataSocketBufferSize); + } + if (__passiveLocalHost != null) { + socket.bind(new InetSocketAddress(__passiveLocalHost, 0)); + } + + // 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) { + socket.setSoTimeout(__dataTimeout); + } + + socket.connect(new InetSocketAddress(__passiveHost, __passivePort), connectTimeout); + if ((__restartOffset > 0) && !restart(__restartOffset)) + { + socket.close(); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) + { + socket.close(); + return null; + } + } + + if (__remoteVerificationEnabled && !verifyRemote(socket)) + { + socket.close(); + + throw new IOException( + "Host attempting data connection " + socket.getInetAddress().getHostAddress() + + " is not same as server " + getRemoteAddress().getHostAddress()); + } + + return socket; + } + + + @Override + protected void _connectAction_() throws IOException + { + _connectAction_(null); + } + + + /** + * @param socketIsReader the reader to reuse (if non-null) + * @throws IOException on error + * @since 3.4 + */ + @Override + protected void _connectAction_(Reader socketIsReader) throws IOException + { + super._connectAction_(socketIsReader); // sets up _input_ and _output_ + __initDefaults(); + // must be after super._connectAction_(), because otherwise we get an + // Exception claiming we're not connected + if ( __autodetectEncoding ) + { + ArrayList oldReplyLines = new ArrayList (_replyLines); + int oldReplyCode = _replyCode; + if ( hasFeature("UTF8") || hasFeature("UTF-8")) // UTF8 appears to be the default + { + setControlEncoding("UTF-8"); + _controlInput_ = + new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); + _controlOutput_ = + new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); + } + // restore the original reply (server greeting) + _replyLines.clear(); + _replyLines.addAll(oldReplyLines); + _replyCode = oldReplyCode; + _newReplyString = true; + } + } + + + /** + * Sets the timeout in milliseconds to use when reading from the + * data connection. This timeout will be set immediately after + * opening the data connection, provided that the value is ≥ 0. + *

+ * Note: the timeout will also be applied when calling accept() + * whilst establishing an active local data connection. + * @param timeout The default timeout in milliseconds that is used when + * opening a data connection socket. The value 0 means an infinite timeout. + */ + 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 FTPFileEntryParserFactory + * @see DefaultFTPFileEntryParserFactory + */ + public void setParserFactory(FTPFileEntryParserFactory parserFactory) { + __parserFactory = parserFactory; + } + + + /** + * Closes the connection to the FTP server and restores + * connection parameters to the default values. + * + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.4 (made public) + */ + public 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 (or EPSV) 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() } + *

+ * N.B. currently calling any connect method will reset the mode to + * ACTIVE_LOCAL_DATA_CONNECTION_MODE. + */ + 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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; + } + + /** + * Get the client port for active mode. + * + * @return The client port for active mode. + */ + private int getActivePort() + { + if (__activeMinPort > 0 && __activeMaxPort >= __activeMinPort) + { + if (__activeMaxPort == __activeMinPort) { + return __activeMaxPort; + } + // Get a random port between the min and max port range + return __random.nextInt(__activeMaxPort - __activeMinPort + 1) + __activeMinPort; + } + else + { + // default port + return 0; + } + } + + /** + * Get the host address for active mode; allows the local address to be overridden. + * + * @return __activeExternalHost if non-null, else getLocalAddress() + * @see #setActiveExternalIPAddress(String) + */ + private InetAddress getHostAddress() + { + if (__activeExternalHost != null) + { + return __activeExternalHost; + } + else + { + // default local address + return getLocalAddress(); + } + } + + /** + * Get the reported host address for active mode EPRT/PORT commands; + * allows override of {@link #getHostAddress()}. + * + * Useful for FTP Client behind Firewall NAT. + * + * @return __reportActiveExternalHost if non-null, else getHostAddress(); + */ + private InetAddress getReportHostAddress() { + if (__reportActiveExternalHost != null) { + return __reportActiveExternalHost ; + } else { + return getHostAddress(); + } + } + + /** + * Set the client side port range in active mode. + * + * @param minPort The lowest available port (inclusive). + * @param maxPort The highest available port (inclusive). + * @since 2.2 + */ + public void setActivePortRange(int minPort, int maxPort) + { + this.__activeMinPort = minPort; + this.__activeMaxPort = maxPort; + } + + /** + * Set the external IP address in active mode. + * Useful when there are multiple network cards. + * + * @param ipAddress The external IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + * @since 2.2 + */ + public void setActiveExternalIPAddress(String ipAddress) throws UnknownHostException + { + this.__activeExternalHost = InetAddress.getByName(ipAddress); + } + + /** + * Set the local IP address to use in passive mode. + * Useful when there are multiple network cards. + * + * @param ipAddress The local IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + */ + public void setPassiveLocalIPAddress(String ipAddress) throws UnknownHostException + { + this.__passiveLocalHost = InetAddress.getByName(ipAddress); + } + + /** + * Set the local IP address to use in passive mode. + * Useful when there are multiple network cards. + * + * @param inetAddress The local IP address of this machine. + */ + public void setPassiveLocalIPAddress(InetAddress inetAddress) + { + this.__passiveLocalHost = inetAddress; + } + + /** + * Set the local IP address in passive mode. + * Useful when there are multiple network cards. + * + * @return The local IP address in passive mode. + */ + public InetAddress getPassiveLocalIPAddress() + { + return this.__passiveLocalHost; + } + + /** + * Set the external IP address to report in EPRT/PORT commands in active mode. + * Useful when there are multiple network cards. + * + * @param ipAddress The external IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + * @since 3.1 + * @see #getReportHostAddress() + */ + public void setReportActiveExternalIPAddress(String ipAddress) throws UnknownHostException + { + this.__reportActiveExternalHost = InetAddress.getByName(ipAddress); + } + + + /** + * 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. + *
+ * The server default is supposed to be ASCII (see RFC 959), however many + * ftp servers default to BINARY. To ensure correct operation with all servers, + * always specify the appropriate file type after connecting to the server. + *
+ *

+ * N.B. currently calling any connect method will reset the type to + * FTP.ASCII_FILE_TYPE. + * @param fileType The _FILE_TYPE constant indcating the + * type of file. + * @return True if successfully completed, false if not. + * @throws 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. + * @throws 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 server default is supposed to be ASCII (see RFC 959), however many + * ftp servers default to BINARY. To ensure correct operation with all servers, + * always specify the appropriate file type after connecting to the server. + *
+ * 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. + *

+ * N.B. currently calling any connect method will reset the type to + * FTP.ASCII_FILE_TYPE and the formatOrByteSize to FTP.NON_PRINT_TEXT_FORMAT. + * + * @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. + * @throws 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. + * @throws 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 + * or if a connect method is called. + * + * @param structure The structure of the file (one of the FTP class + * _STRUCTURE constants). + * @return True if successfully completed, false if not. + * @throws 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. + * @throws 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 + * or if a connect method is 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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(appe(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. + * @throws 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. + * @throws 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. + *

+ * Note: if you have used {@link #setRestartOffset(long)}, + * the file data will start from the selected offset. + * @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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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 + { + return _retrieveFile(FTPCmd.RETR.getCommand(), remote, local); + } + + /** + * @param command the command to get + * @param remote the remote file name + * @param local the local file name + * @return true if successful + * @throws IOException on error + * @since 3.1 + */ + protected boolean _retrieveFile(String command, String remote, OutputStream local) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return false; + } + + final InputStream input; + if (__fileType == ASCII_FILE_TYPE) { + input = new FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); + } else { + input = getBufferedInputStream(socket.getInputStream()); + } + + CSL csl = null; + if (__controlKeepAliveTimeout > 0) { + csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); + } + + // Treat everything else as binary for now + try + { + Util.copyStream(input, local, getBufferSize(), + CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), + false); + } finally { + Util.closeQuietly(input); + Util.closeQuietly(socket); + if (csl != null) { + csl.cleanUp(); // fetch any outstanding keepalive replies + } + } + + // Get the transfer response + boolean ok = completePendingCommand(); + return ok; + } + + /** + * 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. + * If this is not done, subsequent commands may behave unexpectedly. + *

+ * Note: if you have used {@link #setRestartOffset(long)}, + * the file data will start from the selected offset. + * + * @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). + * @throws 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. + * @throws 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 + { + return _retrieveFileStream(FTPCmd.RETR.getCommand(), remote); + } + + /** + * @param command the command to send + * @param remote the remote file name + * @return the stream from which to read the file + * @throws IOException on error + * @since 3.1 + */ + protected InputStream _retrieveFileStream(String command, String remote) + throws IOException + { + Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return null; + } + + final InputStream input; + 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 FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); + } else { + input = socket.getInputStream(); + } + 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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws org.apache.commons.net.io.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. + * @throws 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(FTPCmd.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. + * If this is not done, subsequent commands may behave unexpectedly. + * + * @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). + * @throws 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. + * @throws 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(FTPCmd.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. + * @throws 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. + * @throws 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)); + } + + /** + * Query the server for supported features. The server may reply with a list of server-supported exensions. + * For example, a typical client-server interaction might be (from RFC 2389): + *

+        C> feat
+        S> 211-Extensions supported:
+        S>  MLST size*;create;modify*;perm;media-type
+        S>  SIZE
+        S>  COMPRESSION
+        S>  MDTM
+        S> 211 END
+     * 
+ * @see http://www.faqs.org/rfcs/rfc2389.html + * @return True if successfully completed, false if not. + * @throws IOException on error + * @since 2.2 + */ + public boolean features() throws IOException { + return FTPReply.isPositiveCompletion(feat()); + } + + /** + * Query the server for a supported feature, and returns its values (if any). + * Caches the parsed response to avoid resending the command repeatedly. + * @param feature the feature to check + * + * @return if the feature is present, returns the feature values (empty array if none) + * Returns {@code null} if the feature is not found or the command failed. + * Check {@link #getReplyCode()} or {@link #getReplyString()} if so. + * @throws IOException on error + * @since 3.0 + */ + public String[] featureValues(String feature) throws IOException { + if (!initFeatureMap()) { + return null; + } + Set entries = __featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); + if (entries != null) { + return entries.toArray(new String[entries.size()]); + } + return null; + } + + /** + * Query the server for a supported feature, and returns the its value (if any). + * Caches the parsed response to avoid resending the command repeatedly. + * @param feature the feature to check + * + * @return if the feature is present, returns the feature value or the empty string + * if the feature exists but has no value. + * Returns {@code null} if the feature is not found or the command failed. + * Check {@link #getReplyCode()} or {@link #getReplyString()} if so. + * @throws IOException on error + * @since 3.0 + */ + public String featureValue(String feature) throws IOException { + String [] values = featureValues(feature); + if (values != null) { + return values[0]; + } + return null; + } + + /** + * Query the server for a supported feature. + * Caches the parsed response to avoid resending the command repeatedly. + * + * @param feature the name of the feature; it is converted to upper case. + * @return {@code true} if the feature is present, {@code false} if the feature is not present + * or the {@link #feat()} command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} + * if it is necessary to distinguish these cases. + * + * @throws IOException on error + * @since 3.0 + */ + public boolean hasFeature(String feature) throws IOException { + if (!initFeatureMap()) { + return false; + } + return __featuresMap.containsKey(feature.toUpperCase(Locale.ENGLISH)); + } + + /** + * Query the server for a supported feature with particular value, + * for example "AUTH SSL" or "AUTH TLS". + * Caches the parsed response to avoid resending the command repeatedly. + * + * @param feature the name of the feature; it is converted to upper case. + * @param value the value to find. + * + * @return {@code true} if the feature is present, {@code false} if the feature is not present + * or the {@link #feat()} command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} + * if it is necessary to distinguish these cases. + * + * @throws IOException on error + * @since 3.0 + */ + public boolean hasFeature(String feature, String value) throws IOException { + if (!initFeatureMap()) { + return false; + } + Set entries = __featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); + if (entries != null) { + return entries.contains(value); + } + return false; + } + + /* + * Create the feature map if not already created. + */ + private boolean initFeatureMap() throws IOException { + if (__featuresMap == null) { + // Don't create map here, because next line may throw exception + final int replyCode = feat(); + if (replyCode == FTPReply.NOT_LOGGED_IN) { // 503 + return false; // NET-518; don't create empy map + } + boolean success = FTPReply.isPositiveCompletion(replyCode); + // we init the map here, so we don't keep trying if we know the command will fail + __featuresMap = new HashMap>(); + if (!success) { + return false; + } + for (String l : getReplyStrings()) { + if (l.startsWith(" ")) { // it's a FEAT entry + String key; + String value=""; + int varsep = l.indexOf(' ', 1); + if (varsep > 0) { + key = l.substring(1, varsep); + value = l.substring(varsep+1); + } else { + key = l.substring(1); + } + key = key.toUpperCase(Locale.ENGLISH); + Set entries = __featuresMap.get(key); + if (entries == null) { + entries = new HashSet(); + __featuresMap.put(key, entries); + } + entries.add(value); + } + } + } + return true; + } + + /** + * 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. + * @throws 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. + * @throws 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)); + } + + + /** + * Issue a command and wait for the reply. + *

+ * Should only be used with commands that return replies on the + * command channel - do not use for LIST, NLST, MLSD etc. + * + * @param command The command to invoke + * @param params The parameters string, may be {@code null} + * @return True if successfully completed, false if not, in which case + * call {@link #getReplyCode()} or {@link #getReplyString()} + * to get the reason. + * + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.0 + */ + public boolean doCommand(String command, String params) throws IOException + { + return FTPReply.isPositiveCompletion(sendCommand(command, params)); + } + + /** + * Issue a command and wait for the reply, returning it as an array of strings. + *

+ * Should only be used with commands that return replies on the + * command channel - do not use for LIST, NLST, MLSD etc. + * + * @param command The command to invoke + * @param params The parameters string, may be {@code null} + * @return The array of replies, or {@code null} if the command failed, in which case + * call {@link #getReplyCode()} or {@link #getReplyString()} + * to get the reason. + * + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.0 + */ + public String[] doCommandAsStrings(String command, String params) throws IOException + { + boolean success = FTPReply.isPositiveCompletion(sendCommand(command, params)); + if (success){ + return getReplyStrings(); + } else { + return null; + } + } + + /** + * Get file details using the MLST command + * + * @param pathname the file or directory to list, may be {@code null} + * @return the file details, may be {@code null} + * @throws IOException on error + * @since 3.0 + */ + public FTPFile mlistFile(String pathname) throws IOException + { + boolean success = FTPReply.isPositiveCompletion(sendCommand(FTPCmd.MLST, pathname)); + if (success){ + String reply = getReplyStrings()[1]; + /* check the response makes sense. + * Must have space before fact(s) and between fact(s) and filename + * Fact(s) can be absent, so at least 3 chars are needed. + */ + if (reply.length() < 3 || reply.charAt(0) != ' ') { + throw new MalformedServerReplyException("Invalid server reply (MLST): '" + reply + "'"); + } + String entry = reply.substring(1); // skip leading space for parser + return MLSxEntryParser.parseEntry(entry); + } else { + return null; + } + } + + /** + * Generate a directory listing for the current directory using the MLSD command. + * + * @return the array of file entries + * @throws IOException on error + * @since 3.0 + */ + public FTPFile[] mlistDir() throws IOException + { + return mlistDir(null); + } + + /** + * Generate a directory listing using the MLSD command. + * + * @param pathname the directory name, may be {@code null} + * @return the array of file entries + * @throws IOException on error + * @since 3.0 + */ + public FTPFile[] mlistDir(String pathname) throws IOException + { + FTPListParseEngine engine = initiateMListParsing( pathname); + return engine.getFiles(); + } + + /** + * Generate a directory listing using the MLSD command. + * + * @param pathname the directory name, may be {@code null} + * @param filter the filter to apply to the responses + * @return the array of file entries + * @throws IOException on error + * @since 3.0 + */ + public FTPFile[] mlistDir(String pathname, FTPFileFilter filter) throws IOException + { + FTPListParseEngine engine = initiateMListParsing( pathname); + return engine.getFiles(filter); + } + + /** + * 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. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @since 3.1 (changed from private to protected) + */ + protected boolean restart(long offset) throws IOException + { + __restartOffset = 0; + return FTPReply.isPositiveIntermediate(rest(Long.toString(offset))); + } + + /** + * Sets the restart offset for file transfers. + *

+ * The restart command is not sent to the server immediately. + * It is sent when a data connection is created as part of a + * subsequent command. + * The restart marker is reset to zero after use. + *

+ *

+ * Note: This method should only be invoked immediately prior to + * the transfer to which it applies. + * + * @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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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 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. + *

+ * If the SYST command fails, and the system property + * {@link #FTP_SYSTEM_TYPE_DEFAULT} is defined, then this is used instead. + * @return The system type obtained from the server. Never null. + * @throws 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. + * @throws IOException If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server (and the default + * system type property is not defined) + * @since 2.2 + */ + public String getSystemType() 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){ + if (FTPReply.isPositiveCompletion(syst())) { + // Assume that response is not empty here (cannot be null) + __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + } else { + // Check if the user has provided a default for when the SYST command fails + String systDefault = System.getProperty(FTP_SYSTEM_TYPE_DEFAULT); + if (systDefault != null) { + __systemName = systDefault; + } else { + throw new IOException("Unable to determine system type - response: " + getReplyString()); + } + } + } + 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * Warning: the server may treat a leading '-' as an + * option introducer. If so, try using an absolute path, + * or prefix the path with ./ (unix style servers). + * Some servers may support "--" as meaning end of options, + * in which case "-- -xyz" should work. + * @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. + * @throws 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. + * @throws 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 + { + Socket socket = _openDataConnection_(FTPCmd.NLST, getListArguments(pathname)); + + if (socket == null) { + return null; + } + + BufferedReader reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding())); + + ArrayList results = new ArrayList(); + String line; + 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. + * @throws 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. + * @throws 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. + *

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * + * @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. + * Also, some servers treat a leading '-' as being an option. + * To avoid this interpretation, use an absolute pathname + * or prefix the pathname with ./ (unix style servers). + * Some servers may support "--" as meaning end of options, + * in which case "-- -xyz" should work. + * + * @return The list of file information contained in the given path in + * the format determined by the autodetection mechanism + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles(String pathname) + throws IOException + { + FTPListParseEngine engine = initiateListParsing((String) null, 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. + *

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * + * @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. + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles() + throws IOException + { + return listFiles((String) null); + } + + /** + * Version of {@link #listFiles(String)} which allows a filter to be provided. + * For example: listFiles("site", FTPFileFilters.DIRECTORY); + * @param pathname the initial path, may be null + * @param filter the filter, non-null + * @return the list of FTPFile entries. + * @throws IOException on error + * @since 2.2 + */ + public FTPFile[] listFiles(String pathname, FTPFileFilter filter) + throws IOException + { + FTPListParseEngine engine = initiateListParsing((String) null, pathname); + return engine.getFiles(filter); + + } + + /** + * Using the default system autodetect mechanism, obtain a + * list of directories contained in the current working directory. + *

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

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * + * @return The list of directories contained in the current directory + * in the format determined by the autodetection mechanism. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @since 3.0 + */ + public FTPFile[] listDirectories() throws IOException { + return listDirectories((String) null); + } + + /** + * Using the default system autodetect mechanism, obtain a + * list of directories contained in the specified directory. + *

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

+ * N.B. the LIST command does not generally return very precise timestamps. + * For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. + * If the server supports it, the MLSD command returns timestamps with a precision + * of seconds, and may include milliseconds. See {@link #mlistDir()} + * @param parent the starting directory + * + * @return The list of directories contained in the specified directory + * in the format determined by the autodetection mechanism. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply + * from the server. + * @throws org.apache.commons.net.ftp.parser.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 DefaultFTPFileEntryParserFactory + * @see FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @since 3.0 + */ + public FTPFile[] listDirectories(String parent) throws IOException { + return listFiles(parent, FTPFileFilters.DIRECTORIES); + } + + /** + * 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. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @throws org.apache.commons.net.ftp.parser.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.
+     *    }
+     * 
+ * @param pathname the starting directory + * + * @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. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @throws org.apache.commons.net.ftp.parser.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 + { + return initiateListParsing((String) null, 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. + * May be {@code null}, in which case the code checks first + * the system property {@link #FTP_SYSTEM_TYPE}, and if that is + * not defined the SYST command is used to provide the value. + * To allow for arbitrary system types, the return from the + * SYST command is used to look up an alias for the type in the + * {@link #SYSTEM_TYPE_PROPERTIES} properties file if it is available. + * @param pathname the starting directory + * + * @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. + * + * @throws 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. + * @throws IOException + * If an I/O error occurs while either sending a + * command to the server or receiving a reply from the server. + * @throws org.apache.commons.net.ftp.parser.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 + { + __createParser(parserKey); // create and cache parser + return initiateListParsing(__entryParser, pathname); + } + + // package access for test purposes + void __createParser(String parserKey) throws IOException { + // We cache the value to avoid creation of a new object every + // time a file listing is generated. + // Note: we don't check against a null parserKey (NET-544) + if(__entryParser == null || (parserKey != null && ! __entryParserKey.equals(parserKey))) { + if (null != parserKey) { + // if a parser key was supplied in the parameters, + // use that to create the parser + __entryParser = + __parserFactory.createFileEntryParser(parserKey); + __entryParserKey = parserKey; + + } else { + // if no parserKey was supplied, check for a configuration + // in the params, and if it has a non-empty system type, use that. + if (null != __configuration && __configuration.getServerSystemKey().length() > 0) { + __entryParser = + __parserFactory.createFileEntryParser(__configuration); + __entryParserKey = __configuration.getServerSystemKey(); + } else { + // if a parserKey hasn't been supplied, and a configuration + // hasn't been supplied, and the override property is not set + // then autodetect by calling + // the SYST command and use that to choose the parser. + String systemType = System.getProperty(FTP_SYSTEM_TYPE); + if (systemType == null) { + systemType = getSystemType(); // cannot be null + Properties override = getOverrideProperties(); + if (override != null) { + String newType = override.getProperty(systemType); + if (newType != null) { + systemType = newType; + } + } + } + if (null != __configuration) { // system type must have been empty above + __entryParser = __parserFactory.createFileEntryParser(new FTPClientConfig(systemType, __configuration)); + } else { + __entryParser = __parserFactory.createFileEntryParser(systemType); + } + __entryParserKey = systemType; + } + } + } + + + } + + /** + * private method through which all listFiles() and + * initiateListParsing methods pass once a parser is determined. + * + * @throws 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. + * @throws 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 = _openDataConnection_(FTPCmd.LIST, getListArguments(pathname)); + + FTPListParseEngine engine = new FTPListParseEngine(parser, __configuration); + if (socket == null) + { + return engine; + } + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } + finally { + Util.closeQuietly(socket); + } + + completePendingCommand(); + return engine; + } + + /** + * Initiate list parsing for MLSD listings. + * + * @param pathname + * @return the engine + * @throws IOException + */ + private FTPListParseEngine initiateMListParsing(String pathname) throws IOException + { + Socket socket = _openDataConnection_(FTPCmd.MLSD, pathname); + FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance(), __configuration); + if (socket == null) + { + return engine; + } + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } + finally { + Util.closeQuietly(socket); + completePendingCommand(); + } + return engine; + } + + /** + * @param pathname the initial pathname + * @return the adjusted string with "-a" added if necessary + * @since 2.0 + */ + protected String getListArguments(String pathname) { + if (getListHiddenFiles()) + { + if (pathname != null) + { + StringBuilder sb = new StringBuilder(pathname.length() + 3); + sb.append("-a "); + sb.append(pathname); + return sb.toString(); + } + else + { + return "-a"; + } + } + + return pathname; + } + + + /** + * Issue the FTP STAT command to the server. + * + * @return The status information returned by the server. + * @throws 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. + * @throws 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. + * @param pathname the filename + * + * @return The status information returned by the server. + * @throws 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. + * @throws 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 getReplyStrings()[0].substring(4); // skip the return code (e.g. 213) and the space + } + 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 FTPFile representing the last file modification time, may be {@code null}. + * The FTPFile timestamp will be null if a parse error occurs. + * @throws IOException if an I/O error occurs. + * @since 3.4 + */ + public FTPFile mdtmFile(String pathname) throws IOException { + if (FTPReply.isPositiveCompletion(mdtm(pathname))) { + String reply = getReplyStrings()[0].substring(4); // skip the return code (e.g. 213) and the space + FTPFile file = new FTPFile(); + file.setName(pathname); + file.setRawListing(reply); + file.setTimestamp(MLSxEntryParser.parseGMTdateTime(reply)); + return file; + } + return null; + } + + + /** + * Issue the FTP MFMT command (not supported by all servers) which sets the last + * modified time of a file. + * + * The timestamp should be in the form YYYYMMDDhhmmss. It should also + * be in GMT, but not all servers honour this. + * + * An FTP server would indicate its support of this feature by including "MFMT" + * in its response to the FEAT command, which may be retrieved by FTPClient.features() + * + * @param pathname The file path for which last modified time is to be changed. + * @param timeval The timestamp to set to, in YYYYMMDDhhmmss format. + * @return true if successfully set, false if not + * @throws IOException if an I/O error occurs. + * @since 2.2 + * @see http://tools.ietf.org/html/draft-somers-ftp-mfxx-04 + */ + public boolean setModificationTime(String pathname, String timeval) throws IOException { + return (FTPReply.isPositiveCompletion(mfmt(pathname, timeval))); + } + + + /** + * Set the internal buffer size for buffered data streams. + * + * @param bufSize The size of the buffer. Use a non-positive value to use the default. + */ + public void setBufferSize(int bufSize) { + __bufferSize = bufSize; + } + + /** + * Retrieve the current internal buffer size for buffered data streams. + * @return The current buffer size. + */ + public int getBufferSize() { + return __bufferSize; + } + + /** + * Sets the value to be used for the data socket SO_SNDBUF option. + * If the value is positive, the option will be set when the data socket has been created. + * + * @param bufSize The size of the buffer, zero or negative means the value is ignored. + * @since 3.3 + */ + public void setSendDataSocketBufferSize(int bufSize) { + __sendDataSocketBufferSize = bufSize; + } + + /** + * Retrieve the value to be used for the data socket SO_SNDBUF option. + * @return The current buffer size. + * @since 3.3 + */ + public int getSendDataSocketBufferSize() { + return __sendDataSocketBufferSize; + } + + /** + * Sets the value to be used for the data socket SO_RCVBUF option. + * If the value is positive, the option will be set when the data socket has been created. + * + * @param bufSize The size of the buffer, zero or negative means the value is ignored. + * @since 3.3 + */ + public void setReceieveDataSocketBufferSize(int bufSize) { + __receiveDataSocketBufferSize = bufSize; + } + + /** + * Retrieve the value to be used for the data socket SO_RCVBUF option. + * @return The current buffer size. + * @since 3.3 + */ + public int getReceiveDataSocketBufferSize() { + return __receiveDataSocketBufferSize; + } + + /** + * 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 + */ + @Override + 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; + } + + /** + * Whether should attempt to use EPSV with IPv4. + * Default (if not set) is false + * @return true if should attempt EPSV + * @since 2.2 + */ + public boolean isUseEPSVwithIPv4() { + return __useEPSVwithIPv4; + } + + + /** + * Set whether to use EPSV with IPv4. + * Might be worth enabling in some circumstances. + * + * For example, when using IPv4 with NAT it + * may work with some rare configurations. + * E.g. if FTP server has a static PASV address (external network) + * and the client is coming from another internal network. + * In that case the data connection after PASV command would fail, + * while EPSV would make the client succeed by taking just the port. + * + * @param selected value to set. + * @since 2.2 + */ + public void setUseEPSVwithIPv4(boolean selected) { + this.__useEPSVwithIPv4 = selected; + } + + /** + * Set the listener to be used when performing store/retrieve operations. + * The default value (if not set) is {@code null}. + * + * @param listener to be used, may be {@code null} to disable + * @since 3.0 + */ + public void setCopyStreamListener(CopyStreamListener listener){ + __copyStreamListener = listener; + } + + /** + * Obtain the currently active listener. + * + * @return the listener, may be {@code null} + * @since 3.0 + */ + public CopyStreamListener getCopyStreamListener(){ + return __copyStreamListener; + } + + /** + * Set the time to wait between sending control connection keepalive messages + * when processing file upload or download. + * + * @param controlIdle the wait (in secs) between keepalive messages. Zero (or less) disables. + * @since 3.0 + * @see #setControlKeepAliveReplyTimeout(int) + */ + public void setControlKeepAliveTimeout(long controlIdle){ + __controlKeepAliveTimeout = controlIdle * 1000; + } + + /** + * Get the time to wait between sending control connection keepalive messages. + * @return the number of seconds between keepalive messages. + * @since 3.0 + */ + public long getControlKeepAliveTimeout() { + return __controlKeepAliveTimeout / 1000; + } + + /** + * Set how long to wait for control keep-alive message replies. + * + * @param timeout number of milliseconds to wait (defaults to 1000) + * @since 3.0 + * @see #setControlKeepAliveTimeout(long) + */ + public void setControlKeepAliveReplyTimeout(int timeout) { + __controlKeepAliveReplyTimeout = timeout; + } + + /** + * Get how long to wait for control keep-alive message replies. + * @return wait time in msec + * @since 3.0 + */ + public int getControlKeepAliveReplyTimeout() { + return __controlKeepAliveReplyTimeout; + } + + /** + * Enable or disable passive mode NAT workaround. + * If enabled, a site-local PASV mode reply address will be replaced with the + * remote host address to which the PASV mode request was sent + * (unless that is also a site local address). + * This gets around the problem that some NAT boxes may change the + * reply. + * + * The default is true, i.e. site-local replies are replaced. + * @param enabled true to enable replacing internal IP's in passive + * mode. + * @deprecated (3.6) use {@link #setPassiveNatWorkaroundStrategy(HostnameResolver)} instead + */ + @Deprecated + public void setPassiveNatWorkaround(boolean enabled) { + if (enabled) { + this.__passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); + } else { + this.__passiveNatWorkaroundStrategy = null; + } + } + + /** + * Set the workaround strategy to replace the PASV mode reply addresses. + * This gets around the problem that some NAT boxes may change the reply. + * + * The default implementation is {@code NatServerResolverImpl}, i.e. site-local + * replies are replaced. + * @param resolver strategy to replace internal IP's in passive mode + * or null to disable the workaround (i.e. use PASV mode reply address.) + * @since 3.6 + */ + public void setPassiveNatWorkaroundStrategy(HostnameResolver resolver) { + this.__passiveNatWorkaroundStrategy = resolver; + } + + /** + * Strategy interface for updating host names received from FTP server + * for passive NAT workaround. + * + * @since 3.6 + */ + public static interface HostnameResolver { + String resolve(String hostname) throws UnknownHostException; + } + + /** + * Default strategy for passive NAT workaround (site-local + * replies are replaced.) + * @since 3.6 + */ + public static class NatServerResolverImpl implements HostnameResolver { + private FTPClient client; + + public NatServerResolverImpl(FTPClient client) { + this.client = client; + } + + @Override + public String resolve(String hostname) throws UnknownHostException { + String newHostname = hostname; + InetAddress host = InetAddress.getByName(newHostname); + // reply is a local address, but target is not - assume NAT box changed the PASV reply + if (host.isSiteLocalAddress()) { + InetAddress remote = this.client.getRemoteAddress(); + if (!remote.isSiteLocalAddress()){ + newHostname = remote.getHostAddress(); + } + } + return newHostname; + } + } + + private OutputStream getBufferedOutputStream(OutputStream outputStream) { + if (__bufferSize > 0) { + return new BufferedOutputStream(outputStream, __bufferSize); + } + return new BufferedOutputStream(outputStream); + } + + private InputStream getBufferedInputStream(InputStream inputStream) { + if (__bufferSize > 0) { + return new BufferedInputStream(inputStream, __bufferSize); + } + return new BufferedInputStream(inputStream); + } + + // @since 3.0 + private static class CSL implements CopyStreamListener { + + private final FTPClient parent; + private final long idle; + private final int currentSoTimeout; + + private long time = System.currentTimeMillis(); + private int notAcked; + + CSL(FTPClient parent, long idleTime, int maxWait) throws SocketException { + this.idle = idleTime; + this.parent = parent; + this.currentSoTimeout = parent.getSoTimeout(); + parent.setSoTimeout(maxWait); + } + + @Override + public void bytesTransferred(CopyStreamEvent event) { + bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, long streamSize) { + long now = System.currentTimeMillis(); + if (now - time > idle) { + try { + parent.__noop(); + } catch (SocketTimeoutException e) { + notAcked++; + } catch (IOException e) { + // Ignored + } + time = now; + } + } + + void cleanUp() throws IOException { + try { + while(notAcked-- > 0) { + parent.__getReplyNoReport(); + } + } finally { + parent.setSoTimeout(currentSoTimeout); + } + } + + } + + /** + * Merge two copystream listeners, either or both of which may be null. + * + * @param local the listener used by this class, may be null + * @return a merged listener or a single listener or null + * @since 3.0 + */ + private CopyStreamListener __mergeListeners(CopyStreamListener local) { + if (local == null) { + return __copyStreamListener; + } + if (__copyStreamListener == null) { + return local; + } + // Both are non-null + CopyStreamAdapter merged = new CopyStreamAdapter(); + merged.addCopyStreamListener(local); + merged.addCopyStreamListener(__copyStreamListener); + return merged; + } + + /** + * Enables or disables automatic server encoding detection (only UTF-8 supported). + *

+ * Does not affect existing connections; must be invoked before a connection is established. + * + * @param autodetect If true, automatic server encoding detection will be enabled. + */ + public void setAutodetectUTF8(boolean autodetect) + { + __autodetectEncoding = autodetect; + } + + /** + * Tells if automatic server encoding detection is enabled or disabled. + * @return true, if automatic server encoding detection is enabled. + */ + public boolean getAutodetectUTF8() + { + return __autodetectEncoding; + } + + // Method for use by unit test code only + FTPFileEntryParser getEntryParser() { + return __entryParser; + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** + * @return the name + * @throws IOException on error + * @deprecated use {@link #getSystemType()} instead + */ + @Deprecated + public String getSystemName() throws IOException + { + if (__systemName == null && FTPReply.isPositiveCompletion(syst())) { + __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + } + return __systemName; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ +/* kate: indent-width 4; replace-tabs on; */ diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java new file mode 100644 index 00000000..892beb9e --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java @@ -0,0 +1,709 @@ +/* + * 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 FTPClient FTPClient} object and + * also subordinate objects which it uses. Any class implementing the + * {@link 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 Configurable + * @see 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 for alternate UNIX parser; same as {@link #SYST_UNIX} but leading spaces are + * trimmed from file names. This is to maintain backwards compatibility with + * the original behaviour of the parser which ignored multiple spaces between the date + * and the start of the file name. + * @since 3.4 + */ + public static final String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM"; + + /** + * 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"; + + /** + * Identifier by which a Mac pre OS-X -based ftp server is known throughout + * the commons-net ftp system. + * + * @since 3.1 + */ + // Full string is "MACOS Peter's Server"; the substring below should be enough + public static final String SYST_MACOS_PETER = "MACOS PETER"; // NET-436 + + private final String serverSystemKey; + private String defaultDateFormatStr = null; + private String recentDateFormatStr = null; + private boolean lenientFutureDates = true; // NET-407 + private String serverLanguageCode = null; + private String shortMonthNames = null; + private String serverTimeZoneId = null; + private boolean saveUnparseableEntries = false; + + + /** + * The main constructor for an FTPClientConfig object + * @param systemKey key representing system type of the server being + * connected to. See {@link #getServerSystemKey() serverSystemKey} + * If set to the empty string, then FTPClient uses the system type returned by the server. + * However this is not recommended for general use; + * the correct system type should be set if it is known. + */ + 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 the format string 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} + * @since 3.6 + */ + public FTPClientConfig(String systemKey, + String defaultDateFormatStr, + String recentDateFormatStr) + { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.recentDateFormatStr = recentDateFormatStr; + } + + /** + * Constructor which allows setting of most 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; + } + + /** + * 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} + * @param lenientFutureDates See + * {@link #setLenientFutureDates(boolean) lenientFutureDates} + * @param saveUnparseableEntries See + * {@link #setUnparseableEntries(boolean) saveUnparseableEntries} + */ + public FTPClientConfig(String systemKey, + String defaultDateFormatStr, + String recentDateFormatStr, + String serverLanguageCode, + String shortMonthNames, + String serverTimeZoneId, + boolean lenientFutureDates, + boolean saveUnparseableEntries) + { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.lenientFutureDates = lenientFutureDates; + this.recentDateFormatStr = recentDateFormatStr; + this.saveUnparseableEntries = saveUnparseableEntries; + this.serverLanguageCode = serverLanguageCode; + this.shortMonthNames = shortMonthNames; + this.serverTimeZoneId = serverTimeZoneId; + } + + // Copy constructor, intended for use by FTPClient only + FTPClientConfig(String systemKey, FTPClientConfig config) { + this.serverSystemKey = systemKey; + this.defaultDateFormatStr = config.defaultDateFormatStr; + this.lenientFutureDates = config.lenientFutureDates; + this.recentDateFormatStr = config.recentDateFormatStr; + this.saveUnparseableEntries = config.saveUnparseableEntries; + this.serverLanguageCode = config.serverLanguageCode; + this.serverTimeZoneId = config.serverTimeZoneId; + this.shortMonthNames = config.shortMonthNames; + } + + /** + * Copy constructor + * @param config source + * @since 3.6 + */ + public FTPClientConfig(FTPClientConfig config) { + this.serverSystemKey = config.serverSystemKey; + this.defaultDateFormatStr = config.defaultDateFormatStr; + this.lenientFutureDates = config.lenientFutureDates; + this.recentDateFormatStr = config.recentDateFormatStr; + this.saveUnparseableEntries = config.saveUnparseableEntries; + this.serverLanguageCode = config.serverLanguageCode; + this.serverTimeZoneId = config.serverTimeZoneId; + this.shortMonthNames = config.shortMonthNames; + } + + private static final 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 Apache 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(); + } + + /** + * Allow list parsing methods to create basic FTPFile entries if parsing fails. + *

+ * In this case, the FTPFile will contain only the unparsed entry {@link FTPFile#getRawListing()} + * and {@link FTPFile#isValid()} will return {@code false} + * @param saveUnparseable if true, then create FTPFile entries if parsing fails + * @since 3.4 + */ + public void setUnparseableEntries(boolean saveUnparseable) { + this.saveUnparseableEntries = saveUnparseable; + } + + /** + * @return true if list parsing should return FTPFile entries even for unparseable response lines + *

+ * If true, the FTPFile for any unparseable entries will contain only the unparsed entry + * {@link FTPFile#getRawListing()} and {@link FTPFile#isValid()} will return {@code false} + * @since 3.4 + */ + public boolean getUnparseableEntries() { + return this.saveUnparseableEntries; + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCmd.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCmd.java new file mode 100644 index 00000000..bd020ed2 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCmd.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; + +/** +* @since 3.3 + */ +public enum FTPCmd { + ABOR, + ACCT, + ALLO, + APPE, + CDUP, + CWD, + DELE, + EPRT, + EPSV, + FEAT, + HELP, + LIST, + MDTM, + MFMT, + MKD, + MLSD, + MLST, + MODE, + NLST, + NOOP, + PASS, + PASV, + PORT, + PWD, + QUIT, + REIN, + REST, + RETR, + RMD, + RNFR, + RNTO, + SITE, + SMNT, + STAT, + STOR, + STOU, + STRU, + SYST, + TYPE, + USER, + ; + + // Aliases + + public static final FTPCmd ABORT = ABOR; + public static final FTPCmd ACCOUNT = ACCT; + public static final FTPCmd ALLOCATE = ALLO; + public static final FTPCmd APPEND = APPE; + public static final FTPCmd CHANGE_TO_PARENT_DIRECTORY = CDUP; + public static final FTPCmd CHANGE_WORKING_DIRECTORY = CWD; + public static final FTPCmd DATA_PORT = PORT; + public static final FTPCmd DELETE = DELE; + public static final FTPCmd FEATURES = FEAT; + public static final FTPCmd FILE_STRUCTURE = STRU; + public static final FTPCmd GET_MOD_TIME = MDTM; + public static final FTPCmd LOGOUT = QUIT; + public static final FTPCmd MAKE_DIRECTORY = MKD; + public static final FTPCmd MOD_TIME = MDTM; + public static final FTPCmd NAME_LIST = NLST; + public static final FTPCmd PASSIVE = PASV; + public static final FTPCmd PASSWORD = PASS; + public static final FTPCmd PRINT_WORKING_DIRECTORY = PWD; + public static final FTPCmd REINITIALIZE = REIN; + public static final FTPCmd REMOVE_DIRECTORY = RMD; + public static final FTPCmd RENAME_FROM = RNFR; + public static final FTPCmd RENAME_TO = RNTO; + public static final FTPCmd REPRESENTATION_TYPE = TYPE; + public static final FTPCmd RESTART = REST; + public static final FTPCmd RETRIEVE = RETR; + public static final FTPCmd SET_MOD_TIME = MFMT; + public static final FTPCmd SITE_PARAMETERS = SITE; + public static final FTPCmd STATUS = STAT; + public static final FTPCmd STORE = STOR; + public static final FTPCmd STORE_UNIQUE = STOU; + public static final FTPCmd STRUCTURE_MOUNT = SMNT; + public static final FTPCmd SYSTEM = SYST; + public static final FTPCmd TRANSFER_MODE = MODE; + public static final FTPCmd USERNAME = USER; + + /** + * Retrieve the FTP protocol command string corresponding to a specified + * command code. + * + * @return The FTP protcol command string corresponding to a specified + * command code. + */ + public final String getCommand() + { + return this.name(); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCommand.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCommand.java new file mode 100644 index 00000000..c5739d82 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCommand.java @@ -0,0 +1,171 @@ +/* + * 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. + * + * @deprecated use {@link FTPCmd} instead + */ +@Deprecated +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; + /** @since 2.2 */ + public static final int FEAT = 34; + /** @since 2.2 */ + public static final int MFMT = 35; + /** @since 2.2 */ + public static final int EPSV = 36; + /** @since 2.2 */ + public static final int EPRT = 37; + + /** + * Machine parseable list for a directory + * @since 3.0 + */ + public static final int MLSD = 38; + + /** + * Machine parseable list for a single file + * @since 3.0 + */ + public static final int MLST = 39; + + // Must agree with final entry above; used to check array size + private static final int LAST = MLST; + + 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; + + /** @since 2.2 */ + public static final int FEATURES = FEAT; + /** @since 2.2 */ + public static final int GET_MOD_TIME = MDTM; + /** @since 2.2 */ + public static final int SET_MOD_TIME = MFMT; + + // Cannot be instantiated + private FTPCommand() + {} + + private 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", "MDTM", "FEAT", "MFMT", + "EPSV", "EPRT", "MLSD", "MLST" }; + + + + // default access needed for Unit test + static void checkArray(){ + int expectedLength = LAST+1; + if (_commands.length != expectedLength) { + throw new RuntimeException("Incorrect _commands array. Should have length " + +expectedLength+" found "+_commands.length); + } + } + + /** + * 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java new file mode 100644 index 00000000..e6857bf6 --- /dev/null +++ b/AriaFtpPlug/src/main/java/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 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. + * + * @see FTP + * @see FTPClient + ***/ + +public class FTPConnectionClosedException extends IOException +{ + + private static final long serialVersionUID = 3500547241659379952L; + + /*** 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFile.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFile.java new file mode 100644 index 00000000..b208e276 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFile.java @@ -0,0 +1,536 @@ +/* + * 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; +import java.util.Date; +import java.util.Formatter; +import java.util.TimeZone; + +/*** + * The FTPFile class is used to represent information about files stored + * on an FTP server. + * + * @see FTPFileEntryParser + * @see FTPClient#listFiles + ***/ + +public class FTPFile implements Serializable +{ + private static final long serialVersionUID = 9010790363003271996L; + + /** 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; + + private int _type, _hardLinkCount; + private long _size; + private String _rawListing, _user, _group, _name, _link; + private Calendar _date; + // If this is null, then list entry parsing failed + private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION] + + /*** Creates an empty FTPFile. ***/ + public FTPFile() + { + _permissions = new boolean[3][3]; + _type = UNKNOWN_TYPE; + // init these to values that do not occur in listings + // so can distinguish which fields are unset + _hardLinkCount = 0; // 0 is invalid as a link count + _size = -1; // 0 is valid, so use -1 + _user = ""; + _group = ""; + _date = null; + _name = null; + } + + /** + * Constructor for use by {@link FTPListParseEngine} only. + * Used to create FTPFile entries for failed parses + * @param rawListing line that could not be parsed. + * @since 3.4 + */ + FTPFile(String rawListing) + { + _permissions = null; // flag that entry is invalid + _rawListing = rawListing; + _type = UNKNOWN_TYPE; + // init these to values that do not occur in listings + // so can distinguish which fields are unset + _hardLinkCount = 0; // 0 is invalid as a link count + _size = -1; // 0 is valid, so use -1 + _user = ""; + _group = ""; + _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); + } + + /** + * Used to indicate whether an entry is valid or not. + * If the entry is invalid, only the {@link #getRawListing()} method will be useful. + * Other methods may fail. + * + * Used in conjunction with list parsing that preseverves entries that failed to parse. + * @see FTPClientConfig#setUnparseableEntries(boolean) + * @return true if the entry is valid + * @since 3.4 + */ + public boolean isValid() { + return (_permissions != null); + } + + /*** + * 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. + * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range + ***/ + 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) + * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range + * @return true if {@link #isValid()} is {@code true &&} the associated permission is set; + * {@code false} otherwise. + ***/ + public boolean hasPermission(int access, int permission) + { + if (_permissions == null) { + return false; + } + return _permissions[access][permission]; + } + + /*** + * Returns a string representation of the FTPFile information. + * + * @return A string representation of the FTPFile information. + */ + @Override + public String toString() + { + return getRawListing(); + } + + /*** + * Returns a string representation of the FTPFile information. + * This currently mimics the Unix listing format. + * This method uses the timezone of the Calendar entry, which is + * the server time zone (if one was provided) otherwise it is + * the local time zone. + *

+ * Note: if the instance is not valid {@link #isValid()}, no useful + * information can be returned. In this case, use {@link #getRawListing()} + * instead. + * + * @return A string representation of the FTPFile information. + * @since 3.0 + */ + public String toFormattedString() + { + return toFormattedString(null); + } + + /** + * Returns a string representation of the FTPFile information. + * This currently mimics the Unix listing format. + * This method allows the Calendar time zone to be overridden. + *

+ * Note: if the instance is not valid {@link #isValid()}, no useful + * information can be returned. In this case, use {@link #getRawListing()} + * instead. + * @param timezone the timezone to use for displaying the time stamp + * If {@code null}, then use the Calendar entry timezone + * @return A string representation of the FTPFile information. + * @since 3.4 + */ + public String toFormattedString(final String timezone) + { + + if (!isValid()) { + return "[Invalid: could not parse file entry]"; + } + StringBuilder sb = new StringBuilder(); + Formatter fmt = new Formatter(sb); + sb.append(formatType()); + sb.append(permissionToString(USER_ACCESS)); + sb.append(permissionToString(GROUP_ACCESS)); + sb.append(permissionToString(WORLD_ACCESS)); + fmt.format(" %4d", Integer.valueOf(getHardLinkCount())); + fmt.format(" %-8s %-8s", getUser(), getGroup()); + fmt.format(" %8d", Long.valueOf(getSize())); + Calendar timestamp = getTimestamp(); + if (timestamp != null) { + if (timezone != null) { + TimeZone newZone = TimeZone.getTimeZone(timezone); + if (!newZone.equals(timestamp.getTimeZone())){ + Date original = timestamp.getTime(); + Calendar newStamp = Calendar.getInstance(newZone); + newStamp.setTime(original); + timestamp = newStamp; + } + } + fmt.format(" %1$tY-%1$tm-%1$td", timestamp); + // Only display time units if they are present + if (timestamp.isSet(Calendar.HOUR_OF_DAY)) { + fmt.format(" %1$tH", timestamp); + if (timestamp.isSet(Calendar.MINUTE)) { + fmt.format(":%1$tM", timestamp); + if (timestamp.isSet(Calendar.SECOND)) { + fmt.format(":%1$tS", timestamp); + if (timestamp.isSet(Calendar.MILLISECOND)) { + fmt.format(".%1$tL", timestamp); + } + } + } + fmt.format(" %1$tZ", timestamp); + } + } + sb.append(' '); + sb.append(getName()); + fmt.close(); + return sb.toString(); + } + + private char formatType(){ + switch(_type) { + case FILE_TYPE: + return '-'; + case DIRECTORY_TYPE: + return 'd'; + case SYMBOLIC_LINK_TYPE: + return 'l'; + default: + return '?'; + } + } + + private String permissionToString(int access ){ + StringBuilder sb = new StringBuilder(); + if (hasPermission(access, READ_PERMISSION)) { + sb.append('r'); + } else { + sb.append('-'); + } + if (hasPermission(access, WRITE_PERMISSION)) { + sb.append('w'); + } else { + sb.append('-'); + } + if (hasPermission(access, EXECUTE_PERMISSION)) { + sb.append('x'); + } else { + sb.append('-'); + } + return sb.toString(); + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java new file mode 100644 index 00000000..48675a57 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.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.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 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 uses the 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 second example uses the 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 third example uses the 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");
+ * 
+ * + * For an alternative approach, see the {@link FTPListParseEngine} class + * which provides iterative access. + * + * @version $Id: FTPFileEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ + * @see FTPFile + * @see FTPClient#listFiles() + */ +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. + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java new file mode 100644 index 00000000..05c45c55 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java @@ -0,0 +1,79 @@ +/* + * 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; + +/** + * 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 delimits 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. + * @throws IOException thrown on any IO Error reading from the reader. + */ + @Override + 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 does nothing. + * + * @param original Original list after it has been created from the server stream + * + * @return original unmodified. + */ + @Override + public List preParse(List original) { + return original; + } +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java new file mode 100644 index 00000000..16f9c130 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Perform filtering on FTPFile entries. + * @since 2.2 + */ +public interface FTPFileFilter { + /** + * Checks if an FTPFile entry should be included or not. + * + * @param file entry to be checked for inclusion. May be null. + * @return true if the file is to be included, false otherwise + */ + public boolean accept(FTPFile file); +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java new file mode 100644 index 00000000..29eba603 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * Implements some simple FTPFileFilter classes. + * @since 2.2 + */ +public class FTPFileFilters { + + /** + * Accepts all FTPFile entries, including null. + */ + public static final FTPFileFilter ALL = new FTPFileFilter() { + @Override + public boolean accept(FTPFile file) { + return true; + } + }; + + /** + * Accepts all non-null FTPFile entries. + */ + public static final FTPFileFilter NON_NULL = new FTPFileFilter() { + @Override + public boolean accept(FTPFile file) { + return file != null; + } + }; + + /** + * Accepts all (non-null) FTPFile directory entries. + */ + public static final FTPFileFilter DIRECTORIES = new FTPFileFilter() { + @Override + public boolean accept(FTPFile file) { + return file != null && file.isDirectory(); + } + }; + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java new file mode 100644 index 00000000..52df2c83 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java @@ -0,0 +1,202 @@ +/* + * 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.io.OutputStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.Inet6Address; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.net.util.Base64; + +/** + * Experimental attempt at FTP client that tunnels over an HTTP proxy connection. + * + * @since 2.2 + */ +public class FTPHTTPClient extends FTPClient { + private final String proxyHost; + private final int proxyPort; + private final String proxyUsername; + private final String proxyPassword; + + private static final byte[] CRLF={'\r', '\n'}; + private final Base64 base64 = new Base64(); + + private String tunnelHost; // Save the host when setting up a tunnel (needed for EPSV) + + public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUsername = proxyUser; + this.proxyPassword = proxyPass; + this.tunnelHost = null; + } + + public FTPHTTPClient(String proxyHost, int proxyPort) { + this(proxyHost, proxyPort, null, null); + } + + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if connection mode is not passive + * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead + */ + // Kept to maintain binary compatibility + // Not strictly necessary, but Clirr complains even though there is a super-impl + @Override + @Deprecated + protected Socket _openDataConnection_(int command, String arg) + throws IOException { + return super._openDataConnection_(command, arg); + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if connection mode is not passive + * @since 3.1 + */ + @Override + protected Socket _openDataConnection_(String command, String arg) + throws IOException { + //Force local passive mode, active mode not supported by through proxy + if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { + throw new IllegalStateException("Only passive connection mode supported"); + } + + final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; + String passiveHost = null; + + boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) { + _parseExtendedPassiveModeReply(_replyLines.get(0)); + passiveHost = this.tunnelHost; + } else { + if (isInet6Address) { + return null; // Must use EPSV for IPV6 + } + // If EPSV failed on IPV4, revert to PASV + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { + return null; + } + _parsePassiveModeReply(_replyLines.get(0)); + passiveHost = this.getPassiveHost(); + } + + Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort); + InputStream is = socket.getInputStream(); + OutputStream os = socket.getOutputStream(); + tunnelHandshake(passiveHost, this.getPassivePort(), is, os); + if ((getRestartOffset() > 0) && !restart(getRestartOffset())) { + socket.close(); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { + socket.close(); + return null; + } + + return socket; + } + + @Override + public void connect(String host, int port) throws SocketException, IOException { + + _socket_ = _socketFactory_.createSocket(proxyHost, proxyPort); + _input_ = _socket_.getInputStream(); + _output_ = _socket_.getOutputStream(); + Reader socketIsReader; + try { + socketIsReader = tunnelHandshake(host, port, _input_, _output_); + } + catch (Exception e) { + IOException ioe = new IOException("Could not connect to " + host+ " using port " + port); + ioe.initCause(e); + throw ioe; + } + super._connectAction_(socketIsReader); + } + + private BufferedReader tunnelHandshake(String host, int port, InputStream input, OutputStream output) throws IOException, + UnsupportedEncodingException { + final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1"; + final String hostString = "Host: " + host + ":" + port; + + this.tunnelHost = host; + output.write(connectString.getBytes("UTF-8")); // TODO what is the correct encoding? + output.write(CRLF); + output.write(hostString.getBytes("UTF-8")); + output.write(CRLF); + + if (proxyUsername != null && proxyPassword != null) { + final String auth = proxyUsername + ":" + proxyPassword; + final String header = "Proxy-Authorization: Basic " + + base64.encodeToString(auth.getBytes("UTF-8")); + output.write(header.getBytes("UTF-8")); + } + output.write(CRLF); + + List response = new ArrayList(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(input, getCharset())); + + for (String line = reader.readLine(); line != null + && line.length() > 0; line = reader.readLine()) { + response.add(line); + } + + int size = response.size(); + if (size == 0) { + throw new IOException("No response from proxy"); + } + + String code = null; + String resp = response.get(0); + if (resp.startsWith("HTTP/") && resp.length() >= 12) { + code = resp.substring(9, 12); + } else { + throw new IOException("Invalid response from proxy: " + resp); + } + + if (!"200".equals(code)) { + StringBuilder msg = new StringBuilder(); + msg.append("HTTPTunnelConnector: connection failed\r\n"); + msg.append("Response received from the proxy:\r\n"); + for (String line : response) { + msg.append(line); + msg.append("\r\n"); + } + throw new IOException(msg.toString()); + } + return reader; + } +} + + diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java new file mode 100644 index 00000000..29e1bae4 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java @@ -0,0 +1,329 @@ +/* + * 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.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import org.apache.commons.net.util.Charsets; + + +/** + * 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 1747119 2016-06-07 02:22:24Z ggregory $ + */ +public class FTPListParseEngine { + private List entries = new LinkedList(); + private ListIterator _internalIterator = entries.listIterator(); + + private final FTPFileEntryParser parser; + // Should invalid files (parse failures) be allowed? + private final boolean saveUnparseableEntries; + + public FTPListParseEngine(FTPFileEntryParser parser) { + this(parser, null); + } + + /** + * Intended for use by FTPClient only + * @since 3.4 + */ + FTPListParseEngine(FTPFileEntryParser parser, FTPClientConfig configuration) { + this.parser = parser; + if (configuration != null) { + this.saveUnparseableEntries = configuration.getUnparseableEntries(); + } else { + this.saveUnparseableEntries = false; + } + } + + /** + * handle the initial 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. + * @param encoding the encoding to be used for reading the stream + * + * @throws 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(); + } + + /** + * 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. + * + * @throws IOException + * thrown on any failure to read the stream + */ + private void readStream(InputStream stream, String encoding) throws IOException + { + BufferedReader reader = new BufferedReader( + new InputStreamReader(stream, Charsets.toCharset(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); + if (temp == null && saveUnparseableEntries) { + temp = new FTPFile(entry); + } + tmpResults.add(temp); + count--; + } + return tmpResults.toArray(new FTPFile[tmpResults.size()]); + + } + + /** + * 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); + if (temp == null && saveUnparseableEntries) { + temp = new FTPFile(entry); + } + tmpResults.add(0,temp); + count--; + } + return tmpResults.toArray(new FTPFile[tmpResults.size()]); + } + + /** + * 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. + * None of the entries will be null + * @throws IOException - not ever thrown, may be removed in a later release + */ + public FTPFile[] getFiles() + throws IOException // TODO remove; not actually thrown + { + return getFiles(FTPFileFilters.NON_NULL); + } + + /** + * Returns an array of FTPFile objects containing the whole list of + * files returned by the server as read by this object's parser. + * The files are filtered before being added to the array. + * + * @param filter FTPFileFilter, must not be null. + * + * @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, or use the + * a filter such as {@link FTPFileFilters#NON_NULL} which does not + * allow null entries. + * @since 2.2 + * @throws IOException - not ever thrown, may be removed in a later release + */ + public FTPFile[] getFiles(FTPFileFilter filter) + throws IOException // TODO remove; not actually thrown + { + List tmpResults = new ArrayList(); + Iterator iter = this.entries.iterator(); + while (iter.hasNext()) { + String entry = iter.next(); + FTPFile temp = this.parser.parseFTPEntry(entry); + if (temp == null && saveUnparseableEntries) { + temp = new FTPFile(entry); + } + if (filter.accept(temp)){ + tmpResults.add(temp); + } + } + return tmpResults.toArray(new FTPFile[tmpResults.size()]); + + } + + /** + * 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(); + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** + * Do not use. + * @param stream the stream from which to read + * @throws IOException on error + * @deprecated use {@link #readServerList(InputStream, String)} instead + */ + @Deprecated + public void readServerList(InputStream stream) + throws IOException + { + readServerList(stream, null); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPReply.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPReply.java new file mode 100644 index 00000000..37d5df50 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPReply.java @@ -0,0 +1,201 @@ +/* + * 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. + *

+ * TODO replace with an enum + ***/ + +public final class FTPReply +{ + + public static final int RESTART_MARKER = 110; + public static final int SERVICE_NOT_READY = 120; + public static final int DATA_CONNECTION_ALREADY_OPEN = 125; + public static final int FILE_STATUS_OK = 150; + public static final int COMMAND_OK = 200; + public static final int COMMAND_IS_SUPERFLUOUS = 202; + public static final int SYSTEM_STATUS = 211; + public static final int DIRECTORY_STATUS = 212; + public static final int FILE_STATUS = 213; + public static final int HELP_MESSAGE = 214; + public static final int NAME_SYSTEM_TYPE = 215; + public static final int SERVICE_READY = 220; + public static final int SERVICE_CLOSING_CONTROL_CONNECTION = 221; + public static final int DATA_CONNECTION_OPEN = 225; + public static final int CLOSING_DATA_CONNECTION = 226; + public static final int ENTERING_PASSIVE_MODE = 227; + /** @since 2.2 */ + public static final int ENTERING_EPSV_MODE = 229; + public static final int USER_LOGGED_IN = 230; + public static final int FILE_ACTION_OK = 250; + public static final int PATHNAME_CREATED = 257; + public static final int NEED_PASSWORD = 331; + public static final int NEED_ACCOUNT = 332; + public static final int FILE_ACTION_PENDING = 350; + public static final int SERVICE_NOT_AVAILABLE = 421; + public static final int CANNOT_OPEN_DATA_CONNECTION = 425; + public static final int TRANSFER_ABORTED = 426; + public static final int FILE_ACTION_NOT_TAKEN = 450; + public static final int ACTION_ABORTED = 451; + public static final int INSUFFICIENT_STORAGE = 452; + public static final int UNRECOGNIZED_COMMAND = 500; + public static final int SYNTAX_ERROR_IN_ARGUMENTS = 501; + public static final int COMMAND_NOT_IMPLEMENTED = 502; + public static final int BAD_COMMAND_SEQUENCE = 503; + public static final int COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER = 504; + public static final int NOT_LOGGED_IN = 530; + public static final int NEED_ACCOUNT_FOR_STORING_FILES = 532; + public static final int FILE_UNAVAILABLE = 550; + public static final int PAGE_TYPE_UNKNOWN = 551; + public static final int STORAGE_ALLOCATION_EXCEEDED = 552; + public static final int FILE_NAME_NOT_ALLOWED = 553; + + // FTPS Reply Codes + + /** @since 2.0 */ + public static final int SECURITY_DATA_EXCHANGE_COMPLETE = 234; + /** @since 2.0 */ + public static final int SECURITY_DATA_EXCHANGE_SUCCESSFULLY = 235; + /** @since 2.0 */ + public static final int SECURITY_MECHANISM_IS_OK = 334; + /** @since 2.0 */ + public static final int SECURITY_DATA_IS_ACCEPTABLE = 335; + /** @since 2.0 */ + public static final int UNAVAILABLE_RESOURCE = 431; + /** @since 2.2 */ + public static final int BAD_TLS_NEGOTIATION_OR_DATA_ENCRYPTION_REQUIRED = 522; + /** @since 2.0 */ + public static final int DENIED_FOR_POLICY_REASONS = 533; + /** @since 2.0 */ + public static final int REQUEST_DENIED = 534; + /** @since 2.0 */ + public static final int FAILED_SECURITY_CHECK = 535; + /** @since 2.0 */ + public static final int REQUESTED_PROT_LEVEL_NOT_SUPPORTED = 536; + + // IPv6 error codes + // Note this is also used as an FTPS error code reply + /** @since 2.2 */ + public static final int EXTENDED_PORT_FAILURE = 522; + + // 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); + } + + /** + * Determine if a reply code is a protected response. + * @param reply The reply code to test. + * @return True if a reply code is a protected response, false + * if not. + * @since 3.0 + */ + public static boolean isProtectedReplyCode(int reply) + { + // actually, only 3 protected reply codes are + // defined in RFC 2228: 631, 632 and 633. + return (reply >= 600 && reply < 700); + } + + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSClient.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSClient.java new file mode 100644 index 00000000..51152697 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSClient.java @@ -0,0 +1,925 @@ +/* + * 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 javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import org.apache.commons.net.util.Base64; +import org.apache.commons.net.util.SSLContextUtils; +import org.apache.commons.net.util.SSLSocketUtils; +import org.apache.commons.net.util.TrustManagerUtils; + +/** + * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to + * see wire-level SSL details. + * + * Warning: the hostname is not verified against the certificate by default, use + * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} + * (on Java 1.7+) to enable verification. Verification is only performed on client mode connections. + * @version $Id: FTPSClient.java 1747829 2016-06-11 00:57:57Z sebb $ + * @since 2.0 + */ +public class FTPSClient extends FTPClient { + +// From http://www.iana.org/assignments/port-numbers + +// ftps-data 989/tcp ftp protocol, data, over TLS/SSL +// ftps-data 989/udp ftp protocol, data, over TLS/SSL +// ftps 990/tcp ftp protocol, control, over TLS/SSL +// ftps 990/udp ftp protocol, control, over TLS/SSL + + public static final int DEFAULT_FTPS_DATA_PORT = 989; + public static final int DEFAULT_FTPS_PORT = 990; + + /** The value that I can set in PROT command (C = Clear, P = Protected) */ + private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"}; + /** Default PROT Command */ + private static final String DEFAULT_PROT = "C"; + /** Default secure socket protocol name, i.e. TLS */ + private static final String DEFAULT_PROTOCOL = "TLS"; + + /** The AUTH (Authentication/Security Mechanism) command. */ + private static final String CMD_AUTH = "AUTH"; + /** The ADAT (Authentication/Security Data) command. */ + private static final String CMD_ADAT = "ADAT"; + /** The PROT (Data Channel Protection Level) command. */ + private static final String CMD_PROT = "PROT"; + /** The PBSZ (Protection Buffer Size) command. */ + private static final String CMD_PBSZ = "PBSZ"; + /** The MIC (Integrity Protected Command) command. */ + private static final String CMD_MIC = "MIC"; + /** The CONF (Confidentiality Protected Command) command. */ + private static final String CMD_CONF = "CONF"; + /** The ENC (Privacy Protected Command) command. */ + private static final String CMD_ENC = "ENC"; + /** The CCC (Clear Command Channel) command. */ + private static final String CMD_CCC = "CCC"; + + /** The security mode. (True - Implicit Mode / False - Explicit Mode) */ + private final boolean isImplicit; + /** The secure socket protocol to be used, e.g. SSL/TLS. */ + private final String protocol; + /** The AUTH Command value */ + private String auth = DEFAULT_PROTOCOL; + /** The context object. */ + private SSLContext context; + /** The socket object. */ + private Socket plainSocket; + /** Controls whether a new SSL session may be established by this socket. Default true. */ + 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, default validate only + * {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}. + */ + private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager(); + + /** The {@link KeyManager}, default null (i.e. use system default). */ + private KeyManager keyManager = null; + + /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ + private HostnameVerifier hostnameVerifier = null; + + /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ + private boolean tlsEndpointChecking; + + /** + * Constructor for FTPSClient, calls {@link #FTPSClient(String, boolean)}. + * + * Sets protocol to {@link #DEFAULT_PROTOCOL} - i.e. TLS - and security mode to explicit (isImplicit = false) + */ + public FTPSClient() { + this(DEFAULT_PROTOCOL, false); + } + + /** + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS + * Calls {@link #FTPSClient(String, boolean)} + * @param isImplicit The security mode (Implicit/Explicit). + */ + public FTPSClient(boolean isImplicit) { + this(DEFAULT_PROTOCOL, isImplicit); + } + + /** + * Constructor for FTPSClient, using explict mode, calls {@link #FTPSClient(String, boolean)}. + * + * @param protocol the protocol to use + */ + public FTPSClient(String protocol) { + this(protocol, false); + } + + /** + * Constructor for FTPSClient allowing specification of protocol + * and security mode. If isImplicit is true, the port is set to + * {@link #DEFAULT_FTPS_PORT} i.e. 990. + * The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} + * @param protocol the protocol + * @param isImplicit The security mode(Implicit/Explicit). + */ + public FTPSClient(String protocol, boolean isImplicit) { + super(); + this.protocol = protocol; + this.isImplicit = isImplicit; + if (isImplicit) { + setDefaultPort(DEFAULT_FTPS_PORT); + } + } + + /** + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS + * The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} + * @param isImplicit The security mode(Implicit/Explicit). + * @param context A pre-configured SSL Context + */ + public FTPSClient(boolean isImplicit, SSLContext context) { + this(DEFAULT_PROTOCOL, isImplicit); + this.context = context; + } + + /** + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS + * and isImplicit {@code false} + * Calls {@link #FTPSClient(boolean, SSLContext)} + * @param context A pre-configured SSL Context + */ + public FTPSClient(SSLContext context) { + this(false, context); + } + + + /** + * 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. + */ + protected void execAUTH() throws SSLException, IOException { + int replyCode = sendCommand(CMD_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) { + context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); + } + } + + /** + * SSL/TLS negotiation. Acquires an SSL socket of a control + * connection and carries out handshake processing. + * @throws IOException If server negotiation fails + */ + protected void sslNegotiation() throws IOException { + plainSocket = _socket_; + initSslContext(); + + SSLSocketFactory ssf = context.getSocketFactory(); + String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress(); + int port = _socket_.getPort(); + SSLSocket socket = + (SSLSocket) ssf.createSocket(_socket_, host, port, false); + socket.setEnableSessionCreation(isCreation); + socket.setUseClientMode(isClientMode); + + // client mode + if (isClientMode) { + if (tlsEndpointChecking) { + SSLSocketUtils.enableEndpointNameVerification(socket); + } + } else { // server mode + socket.setNeedClientAuth(isNeedClientAuth); + socket.setWantClientAuth(isWantClientAuth); + } + + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + if (suites != null) { + socket.setEnabledCipherSuites(suites); + } + socket.startHandshake(); + + // TODO the following setup appears to duplicate that in the super class methods + _socket_ = socket; + _controlInput_ = new BufferedReader(new InputStreamReader( + socket .getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter( + socket.getOutputStream(), getControlEncoding())); + + if (isClientMode) { + if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { + throw new SSLHandshakeException("Hostname doesn't match certificate"); + } + } + } + + /** + * 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. + * @see org.apache.commons.net.util.KeyManagerUtils + */ + public void setKeyManager(KeyManager keyManager) { + this.keyManager = keyManager; + } + + /** + * Controls whether a new 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 the underlying {@link Socket} instance is not SSL-enabled (i.e. an + * instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled, + * this returns 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns 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. Called before 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null. + * @return An array of cipher suite names, or null + */ + 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 the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null. + * @return An array of protocols, or null + */ + 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 the server reply code does not equal "200". + * @throws IOException If an I/O error occurs while sending + * the command. + * @see #parsePBSZ(long) + */ + public void execPBSZ(long pbsz) throws SSLException, IOException { + if (pbsz < 0 || 4294967295L < pbsz) { // 32-bit unsigned number + throw new IllegalArgumentException(); + } + int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz)); + if (FTPReply.COMMAND_OK != status) { + throw new SSLException(getReplyString()); + } + } + + /** + * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. + * Issues the command and parses the response to return the negotiated value. + * + * @param pbsz Protection Buffer Size. + * @throws SSLException If the server reply code does not equal "200". + * @throws IOException If an I/O error occurs while sending + * the command. + * @return the negotiated value. + * @see #execPBSZ(long) + * @since 3.0 + */ + public long parsePBSZ(long pbsz) throws SSLException, IOException { + execPBSZ(pbsz); + long minvalue = pbsz; + String remainder = extractPrefixedData("PBSZ=", getReplyString()); + if (remainder != null) { + long replysz = Long.parseLong(remainder); + if (replysz < minvalue) { + minvalue = replysz; + } + } + return minvalue; + } + + /** + * PROT command. + *

    + *
  • C - Clear
  • + *
  • S - Safe(SSL protocol only)
  • + *
  • E - Confidential(SSL protocol only)
  • + *
  • P - Private
  • + *
+ * N.B. the method calls + * {@link #setSocketFactory(javax.net.SocketFactory)} and + * {@link #setServerSocketFactory(javax.net.ServerSocketFactory)} + * + * @param prot Data Channel Protection Level, if {@code null}, use {@link #DEFAULT_PROT}. + * @throws SSLException If the server reply code does not equal {@code 200}. + * @throws IOException If an I/O error occurs while 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(CMD_PROT, prot)) { + throw new SSLException(getReplyString()); + } + if (DEFAULT_PROT.equals(prot)) { + setSocketFactory(null); + setServerSocketFactory(null); + } else { + setSocketFactory(new FTPSSocketFactory(context)); + setServerSocketFactory(new FTPSServerSocketFactory(context)); + initSslContext(); + } + } + + /** + * Check the value that can be 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 (String element : PROT_COMMAND_VALUE) + { + if (element.equals(prot)) { + return true; + } + } + return false; + } + + /** + * Send an FTP command. + * A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} + * instance to be assigned to a plain {@link Socket} + * @param command The FTP command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @throws SSLException if a CCC command fails + * @see FTP#sendCommand(String) + */ + // Would like to remove this method, but that will break any existing clients that are using CCC + @Override + public int sendCommand(String command, String args) throws IOException { + int repCode = super.sendCommand(command, args); + /* If CCC is issued, restore socket i/o streams to unsecured versions */ + if (CMD_CCC.equals(command)) { + if (FTPReply.COMMAND_OK == repCode) { + _socket_.close(); + _socket_ = plainSocket; + _controlInput_ = new BufferedReader( + new InputStreamReader( + _socket_ .getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter( + new OutputStreamWriter( + _socket_.getOutputStream(), getControlEncoding())); + } else { + throw new SSLException(getReplyString()); + } + } + return repCode; + } + + /** + * Returns a socket of the data connection. + * Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * @param command The int 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 arguments. + * @return 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 FTPClient#_openDataConnection_(int, String) + * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead + */ + @Override + // Strictly speaking this is not needed, but it works round a Clirr bug + // So rather than invoke the parent code, we do it here + @Deprecated + protected Socket _openDataConnection_(int command, String arg) + throws IOException { + return _openDataConnection_(FTPCommand.getCommand(command), arg); + } + + /** + * Returns a socket of the data connection. + * Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * @param command The textual 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 arguments. + * @return 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 FTPClient#_openDataConnection_(int, String) + * @since 3.2 + */ + @Override + protected Socket _openDataConnection_(String command, String arg) + throws IOException { + Socket socket = super._openDataConnection_(command, arg); + _prepareDataSocket_(socket); + if (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; + } + + /** + * Performs any custom initialization for a newly created SSLSocket (before + * the SSL handshake happens). + * Called by {@link #_openDataConnection_(int, String)} immediately + * after creating the socket. + * The default implementation is a no-op + * @param socket the socket to set up + * @throws IOException on error + * @since 3.1 + */ + protected void _prepareDataSocket_(Socket socket) + throws IOException { + } + + /** + * Get the currently configured {@link TrustManager}. + * + * @return A TrustManager instance. + */ + public TrustManager getTrustManager() { + return trustManager; + } + + /** + * Override the default {@link TrustManager} to use; if set to {@code null}, + * the default TrustManager from the JVM will be used. + * + * @param trustManager The TrustManager implementation to set, may be {@code null} + * @see org.apache.commons.net.util.TrustManagerUtils + */ + public void setTrustManager(TrustManager trustManager) { + this.trustManager = trustManager; + } + + /** + * Get the currently configured {@link HostnameVerifier}. + * The verifier is only used on client mode connections. + * @return A HostnameVerifier instance. + * @since 3.4 + */ + public HostnameVerifier getHostnameVerifier() + { + return hostnameVerifier; + } + + /** + * Override the default {@link HostnameVerifier} to use. + * The verifier is only used on client mode connections. + * @param newHostnameVerifier The HostnameVerifier implementation to set or null to disable. + * @since 3.4 + */ + public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) + { + hostnameVerifier = newHostnameVerifier; + } + + /** + * Return whether or not endpoint identification using the HTTPS algorithm + * on Java 1.7+ is enabled. The default behaviour is for this to be disabled. + * + * This check is only performed on client mode connections. + * + * @return True if enabled, false if not. + * @since 3.4 + */ + public boolean isEndpointCheckingEnabled() + { + return tlsEndpointChecking; + } + + /** + * Automatic endpoint identification checking using the HTTPS algorithm + * is supported on Java 1.7+. The default behaviour is for this to be disabled. + * + * This check is only performed on client mode connections. + * + * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. + * @since 3.4 + */ + public void setEndpointCheckingEnabled(boolean enable) + { + tlsEndpointChecking = enable; + } + + /** + * Closes the connection to the FTP server and restores + * connection parameters to the default values. + *

+ * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)} + * to reset the factories that may have been changed during the session, + * e.g. by {@link #execPROT(String)} + * @throws IOException If an error occurs while disconnecting. + * @since 3.0 + */ + @Override + public void disconnect() throws IOException + { + super.disconnect(); + if (plainSocket != null) { + plainSocket.close(); + } + setSocketFactory(null); + setServerSocketFactory(null); + } + + /** + * Send the AUTH command with the specified mechanism. + * @param mechanism The mechanism name to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execAUTH(String mechanism) throws IOException + { + return sendCommand(CMD_AUTH, mechanism); + } + + /** + * Send the ADAT command with the specified authentication data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execADAT(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_ADAT, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_ADAT); + } + } + + /** + * Send the CCC command to the server. + * The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned + * to a plain {@link Socket} instances + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execCCC() throws IOException + { + int repCode = sendCommand(CMD_CCC); +// This will be performed by sendCommand(String, String) +// if (FTPReply.isPositiveCompletion(repCode)) { +// _socket_.close(); +// _socket_ = plainSocket; +// _controlInput_ = new BufferedReader( +// new InputStreamReader( +// _socket_.getInputStream(), getControlEncoding())); +// _controlOutput_ = new BufferedWriter( +// new OutputStreamWriter( +// _socket_.getOutputStream(), getControlEncoding())); +// } + return repCode; + } + + /** + * Send the MIC command with the specified data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execMIC(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_MIC, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)? + } + } + + /** + * Send the CONF command with the specified data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execCONF(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_CONF, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)? + } + } + + /** + * Send the ENC command with the specified data. + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending + * the command. + * @since 3.0 + */ + public int execENC(byte[] data) throws IOException + { + if (data != null) + { + return sendCommand(CMD_ENC, Base64.encodeBase64StringUnChunked(data)); + } + else + { + return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)? + } + } + + /** + * Parses the given ADAT response line and base64-decodes the data. + * @param reply The ADAT reply to parse. + * @return the data in the reply, base64-decoded. + * @since 3.0 + */ + public byte[] parseADATReply(String reply) + { + if (reply == null) { + return null; + } else { + return Base64.decodeBase64(extractPrefixedData("ADAT=", reply)); + } + } + + /** + * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234 + * @param prefix the prefix to find + * @param reply where to find the prefix + * @return the remainder of the string after the prefix, or null if the prefix was not present. + */ + private String extractPrefixedData(String prefix, String reply) { + int idx = reply.indexOf(prefix); + if (idx == -1) { + return null; + } + // N.B. Cannot use trim before substring as leading space would affect the offset. + return reply.substring(idx+prefix.length()).trim(); + } + + // DEPRECATED - for API compatibility only - DO NOT USE + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String KEYSTORE_ALGORITHM; + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String TRUSTSTORE_ALGORITHM; + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String PROVIDER; + + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String STORE_TYPE; + +} +/* kate: indent-width 4; replace-tabs on; */ diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java new file mode 100644 index 00000000..c6bade31 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSCommand.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; + +/** + * FTPS-specific commands. + * @since 2.0 + * @deprecated 3.0 DO NOT USE + */ +@Deprecated +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; + + private 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java new file mode 100644 index 00000000..abd382d0 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java @@ -0,0 +1,74 @@ +/* + * 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 javax.net.ServerSocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; + +/** + * Server socket factory for FTPS connections. + * @since 2.2 + */ +public class FTPSServerSocketFactory extends ServerSocketFactory { + + /** Factory for secure socket factories */ + private final SSLContext context; + + public FTPSServerSocketFactory(SSLContext context) { + this.context = context; + } + + // Override the default superclass method + @Override + public ServerSocket createServerSocket() throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket()); + } + + @Override + public ServerSocket createServerSocket(int port) throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket(port)); + } + + @Override + public ServerSocket createServerSocket(int port, int backlog) throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + } + + @Override + public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { + return init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + } + + /** + * Sets the socket so newly accepted connections will use SSL client mode. + * + * @param socket the SSLServerSocket to initialise + * @return the socket + * @throws ClassCastException if socket is not an instance of SSLServerSocket + */ + public ServerSocket init(ServerSocket socket) { + ((SSLServerSocket) socket).setUseClientMode(true); + return socket; + } +} + diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java new file mode 100644 index 00000000..b148180a --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java @@ -0,0 +1,112 @@ +/* + * 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.Socket; +import java.net.UnknownHostException; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; + +/** + * + * Implementation of org.apache.commons.net.SocketFactory + * + * @since 2.0 + */ +public class FTPSSocketFactory extends SocketFactory { + + private final SSLContext context; + + public FTPSSocketFactory(SSLContext context) { + this.context = context; + } + + // Override the default implementation + @Override + public Socket createSocket() throws IOException{ + return this.context.getSocketFactory().createSocket(); + } + + @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); + } + + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** @param port the port + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead} */ + @Deprecated + public java.net.ServerSocket createServerSocket(int port) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port)); + } + + /** @param port the port + * @param backlog the backlog + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead} */ + @Deprecated + public java.net.ServerSocket createServerSocket(int port, int backlog) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + } + + /** @param port the port + * @param backlog the backlog + * @param ifAddress the interface + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress) instead} */ + @Deprecated + public java.net.ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + } + + /** @param socket the socket + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#init(java.net.ServerSocket)} */ + @Deprecated + public java.net.ServerSocket init(java.net.ServerSocket socket) throws IOException { + ((javax.net.ssl.SSLServerSocket) socket).setUseClientMode(true); + return socket; + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java new file mode 100644 index 00000000..56fbea23 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java @@ -0,0 +1,60 @@ +/* + * 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.X509TrustManager; + +/** + * Do not use. + * @since 2.0 + * @deprecated 3.0 use + * {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager() + * TrustManagerUtils#getValidateServerCertificateTrustManager()} instead + */ +@Deprecated +public class FTPSTrustManager implements X509TrustManager +{ + private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{}; + + /** + * No-op + */ + @Override + public void checkClientTrusted(X509Certificate[] certificates, String authType) + { + return; + } + + @Override + public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException + { + for (X509Certificate certificate : certificates) + { + certificate.checkValidity(); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return EMPTY_X509CERTIFICATE_ARRAY; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/package-info.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/package-info.java new file mode 100644 index 00000000..496aba13 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * FTP and FTPS support classes + */ +package org.apache.commons.net.ftp; \ No newline at end of file diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java new file mode 100644 index 00000000..7c266431 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.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.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. + */ +public class CompositeFileEntryParser extends FTPFileEntryParserImpl +{ + private final FTPFileEntryParser[] ftpFileEntryParsers; + private FTPFileEntryParser cachedFtpFileEntryParser; + + public CompositeFileEntryParser(FTPFileEntryParser[] ftpFileEntryParsers) + { + super(); + + this.cachedFtpFileEntryParser = null; + this.ftpFileEntryParsers = ftpFileEntryParsers; + } + + @Override + public FTPFile parseFTPEntry(String listEntry) + { + if (cachedFtpFileEntryParser != null) + { + FTPFile matched = cachedFtpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) + { + return matched; + } + } + else + { + for (FTPFileEntryParser ftpFileEntryParser : ftpFileEntryParsers) + { + FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) + { + cachedFtpFileEntryParser = ftpFileEntryParser; + return matched; + } + } + } + return null; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java new file mode 100644 index 00000000..d61693ec --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java @@ -0,0 +1,128 @@ +/* + * 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 + */ +public abstract class ConfigurableFTPFileEntryParserImpl +extends RegexFTPFileEntryParserImpl +implements Configurable +{ + + private final FTPTimestampParser timestampParser; + + /** + * constructor for this abstract class. + * @param regex Regular expression used main parsing of the + * file listing. + */ + public ConfigurableFTPFileEntryParserImpl(String regex) + { + super(regex); + this.timestampParser = new FTPTimestampParserImpl(); + } + + /** + * constructor for this abstract class. + * @param regex Regular expression used main parsing of the + * file listing. + * @param flags the flags to apply, see + * {@link java.util.regex.Pattern#compile(String, int) Pattern#compile(String, int)}. Use 0 for none. + * @since 3.4 + */ + public ConfigurableFTPFileEntryParserImpl(String regex, int flags) + { + super(regex, flags); + 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. + * @throws ParseException on parse error + */ + 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. + */ + @Override + 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java new file mode 100644 index 00000000..19e4c8e3 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.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.util.regex.Pattern; + +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 +{ + + // Match a plain Java Identifier + private static final String JAVA_IDENTIFIER = "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*"; + // Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc. + private static final String JAVA_QUALIFIED_NAME = "("+JAVA_IDENTIFIER+"\\.)+"+JAVA_IDENTIFIER; + // Create the pattern, as it will be reused many times + private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME); + + /** + * 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 (default package is not allowed) 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_AS400 AS/400}
  • + *
  • {@link FTPClientConfig#SYST_VMS VMS}
  • + *
  • {@link FTPClientConfig#SYST_MVS MVS}
  • + *
  • {@link FTPClientConfig#SYST_NETWARE NETWARE}
  • + *
  • {@link FTPClientConfig#SYST_L8 TYPE:L8}
  • + *
+ * @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 + */ + @Override + public FTPFileEntryParser createFileEntryParser(String key) + { + if (key == null) { + throw new ParserInitializationException("Parser key cannot be null"); + } + return createFileEntryParser(key, null); + } + + // Common method to process both key and config parameters. + private FTPFileEntryParser createFileEntryParser(String key, FTPClientConfig config) { + FTPFileEntryParser parser = null; + + // Is the key a possible class name? + if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) { + try + { + Class parserClass = Class.forName(key); + try { + parser = (FTPFileEntryParser) parserClass.newInstance(); + } catch (ClassCastException e) { + throw new ParserInitializationException(parserClass.getName() + + " does not implement the interface " + + "org.apache.commons.net.ftp.FTPFileEntryParser.", e); + } catch (Exception e) { + throw new ParserInitializationException("Error initializing parser", e); + } catch (ExceptionInInitializerError e) { + throw new ParserInitializationException("Error initializing parser", e); + } + } catch (ClassNotFoundException e) { + // OK, assume it is an alias + } + } + + if (parser == null) { // Now try for aliases + String ukey = key.toUpperCase(java.util.Locale.ENGLISH); + if (ukey.indexOf(FTPClientConfig.SYST_UNIX_TRIM_LEADING) >= 0) + { + parser = new UnixFTPEntryParser(config, true); + } + // must check this after SYST_UNIX_TRIM_LEADING as it is a substring of it + else if (ukey.indexOf(FTPClientConfig.SYST_UNIX) >= 0) + { + parser = new UnixFTPEntryParser(config, false); + } + else if (ukey.indexOf(FTPClientConfig.SYST_VMS) >= 0) + { + parser = new VMSVersioningFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_NT) >= 0) + { + parser = createNTFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_OS2) >= 0) + { + parser = new OS2FTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_OS400) >= 0 || + ukey.indexOf(FTPClientConfig.SYST_AS400) >= 0) + { + parser = createOS400FTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_MVS) >= 0) + { + parser = new MVSFTPEntryParser(); // Does not currently support config parameter + } + else if (ukey.indexOf(FTPClientConfig.SYST_NETWARE) >= 0) + { + parser = new NetwareFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_MACOS_PETER) >= 0) + { + parser = new MacOsPeterFTPEntryParser(config); + } + else if (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0) + { + // L8 normally means Unix, but move it to the end for some L8 systems that aren't. + // This check should be last! + parser = new UnixFTPEntryParser(config); + } + else + { + throw new ParserInitializationException("Unknown parser type: " + key); + } + } + + if (parser instanceof Configurable) { + ((Configurable)parser).configure(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. + * @throws ParserInitializationException + * Thrown on any exception in instantiation + * @throws NullPointerException if {@code config} is {@code null} + * @since 1.4 + */ + @Override + public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) + throws ParserInitializationException + { + String key = config.getServerSystemKey(); + return createFileEntryParser(key, config); + } + + + public FTPFileEntryParser createUnixFTPEntryParser() + { + return new UnixFTPEntryParser(); + } + + public FTPFileEntryParser createVMSVersioningFTPEntryParser() + { + return new VMSVersioningFTPEntryParser(); + } + + public FTPFileEntryParser createNetwareFTPEntryParser() { + return new NetwareFTPEntryParser(); + } + + public FTPFileEntryParser createNTFTPEntryParser() + { + return createNTFTPEntryParser(null); + } + + /** + * Creates an NT FTP parser: if the config exists, and the system key equals + * {@link FTPClientConfig.SYST_NT} then a plain {@link NTFTPEntryParser} is used, + * otherwise a composite of {@link NTFTPEntryParser} and {@link UnixFTPEntryParser} is used. + * @param config the config to use, may be {@code null} + * @return the parser + */ + private FTPFileEntryParser createNTFTPEntryParser(FTPClientConfig config) + { + if (config != null && FTPClientConfig.SYST_NT.equals( + config.getServerSystemKey())) + { + return new NTFTPEntryParser(config); + } else { + // clone the config as it may be changed by the parsers (NET-602) + final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; + return new CompositeFileEntryParser(new FTPFileEntryParser[] + { + new NTFTPEntryParser(config), + new UnixFTPEntryParser(config2, + config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) + }); + } + } + + public FTPFileEntryParser createOS2FTPEntryParser() + { + return new OS2FTPEntryParser(); + } + + public FTPFileEntryParser createOS400FTPEntryParser() + { + return createOS400FTPEntryParser(null); + } + + /** + * Creates an OS400 FTP parser: if the config exists, and the system key equals + * {@link FTPClientConfig.SYST_OS400} then a plain {@link OS400FTPEntryParser} is used, + * otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used. + * @param config the config to use, may be {@code null} + * @return the parser + */ + private FTPFileEntryParser createOS400FTPEntryParser(FTPClientConfig config) + { + if (config != null && + FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) + { + return new OS400FTPEntryParser(config); + } else { + // clone the config as it may be changed by the parsers (NET-602) + final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; + return new CompositeFileEntryParser(new FTPFileEntryParser[] + { + new OS400FTPEntryParser(config), + new UnixFTPEntryParser(config2, + config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) + }); + } + } + + public FTPFileEntryParser createMVSEntryParser() + { + return new MVSFTPEntryParser(); + } + +} + diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java new file mode 100644 index 00000000..142cbf2d --- /dev/null +++ b/AriaFtpPlug/src/main/java/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 1741829 2016-05-01 00:24:44Z sebb $ + * @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*" // 12 + + "(\\S+)\\s*" // 13 + + "(\\S*)\\s*" // 14 user + + "(\\d*)\\s*" // 15 group + + "(\\d*)\\s*" // 16 filesize + + MONTHS // 17 month + + "\\s*" // TODO should the space be optional? + // TODO \\d* should be \\d? surely ? Otherwise 01111 is allowed + + "((?:[012]\\d*)|(?:3[01]))\\s*" // 18 date [012]\d* or 3[01] + + "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s" + // 20 \d\d\d\d = year OR + // 21 [01]\d or 2[0123] hour + ':' + // 22 [012345]\d = minute + + "(\\S*)(\\s*.*)"; // 23 name + + /** + * 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 + */ + @Override + 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); + + int pos = MONTHS.indexOf(mo); + int month = pos / 4; + final int missingUnit; // the first missing unit + try + { + + if (yr != null) + { + // it's a year; there are no hours and minutes + cal.set(Calendar.YEAR, Integer.parseInt(yr)); + missingUnit = Calendar.HOUR_OF_DAY; + } + else + { + // it must be hour/minute or we wouldn't have matched + missingUnit = Calendar.SECOND; + 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.DAY_OF_MONTH, Integer.parseInt(da)); + cal.clear(missingUnit); + file.setTimestamp(cal); + } + catch (NumberFormatException e) + { + // do nothing, date will be uninitialized + } + file.setName(name); + + return file; + } + return null; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java new file mode 100644 index 00000000..9901a97f --- /dev/null +++ b/AriaFtpPlug/src/main/java/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. + * @throws 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. + * @throws ParserInitializationException + * Thrown on any exception in instantiation + * @since 1.4 + */ + public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) + throws ParserInitializationException; + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java new file mode 100644 index 00000000..0f744370 --- /dev/null +++ b/AriaFtpPlug/src/main/java/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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java new file mode 100644 index 00000000..bb4c9970 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java @@ -0,0 +1,406 @@ +/* + * 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 +{ + + + /** The date format for all dates, except possibly recent dates. Assumed to include the year. */ + private SimpleDateFormat defaultDateFormat; + /* The index in CALENDAR_UNITS of the smallest time unit in defaultDateFormat */ + private int defaultDateSmallestUnitIndex; + + /** The format used for recent dates (which don't have the year). May be null. */ + private SimpleDateFormat recentDateFormat; + /* The index in CALENDAR_UNITS of the smallest time unit in recentDateFormat */ + private int recentDateSmallestUnitIndex; + + private boolean lenientFutureDates = false; + + /* + * List of units in order of increasing significance. + * This allows the code to clear all units in the Calendar until it + * reaches the least significant unit in the parse string. + * The date formats are analysed to find the least significant + * unit (e.g. Minutes or Milliseconds) and the appropriate index to + * the array is saved. + * This is done by searching the array for the unit specifier, + * and returning the index. When clearing the Calendar units, + * the code loops through the array until the previous entry. + * e.g. for MINUTE it would clear MILLISECOND and SECOND + */ + private static final int[] CALENDAR_UNITS = { + Calendar.MILLISECOND, + Calendar.SECOND, + Calendar.MINUTE, + Calendar.HOUR_OF_DAY, + Calendar.DAY_OF_MONTH, + Calendar.MONTH, + Calendar.YEAR}; + + /* + * Return the index to the array representing the least significant + * unit found in the date format. + * Default is 0 (to avoid dropping precision) + */ + private static int getEntry(SimpleDateFormat dateFormat) { + if (dateFormat == null) { + return 0; + } + final String FORMAT_CHARS="SsmHdM"; + final String pattern = dateFormat.toPattern(); + for(char ch : FORMAT_CHARS.toCharArray()) { + if (pattern.indexOf(ch) != -1){ // found the character + switch(ch) { + case 'S': + return indexOf(Calendar.MILLISECOND); + case 's': + return indexOf(Calendar.SECOND); + case 'm': + return indexOf(Calendar.MINUTE); + case 'H': + return indexOf(Calendar.HOUR_OF_DAY); + case 'd': + return indexOf(Calendar.DAY_OF_MONTH); + case 'M': + return indexOf(Calendar.MONTH); + } + } + } + return 0; + } + + /* + * Find the entry in the CALENDAR_UNITS array. + */ + private static int indexOf(int calendarUnit) { + int i; + for(i = 0; i NET-83) + now.add(Calendar.DAY_OF_MONTH, 1); + } + // The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input + // As 1970 was not a leap year, it cannot parse "Feb 29" correctly. + // Java 1.5+ returns Mar 1 1970 + // Temporarily add the current year to the short date time + // to cope with short-date leap year strings. + // Since Feb 29 is more that 6 months from the end of the year, this should be OK for + // all instances of short dates which are +- 6 months from current date. + // TODO this won't always work for systems that use short dates +0/-12months + // e.g. if today is Jan 1 2001 and the short date is Feb 29 + String year = Integer.toString(now.get(Calendar.YEAR)); + String timeStampStrPlusYear = timestampStr + " " + year; + SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", + recentDateFormat.getDateFormatSymbols()); + hackFormatter.setLenient(false); + hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); + ParsePosition pp = new ParsePosition(0); + parsed = hackFormatter.parse(timeStampStrPlusYear, pp); + // Check if we parsed the full string, if so it must have been a short date originally + if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) { + working.setTime(parsed); + if (working.after(now)) { // must have been last year instead + working.add(Calendar.YEAR, -1); + } + setPrecision(recentDateSmallestUnitIndex, working); + return working; + } + } + + ParsePosition 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 '"+timestampStr+"' could not be parsed using a server time of " + +serverTime.getTime().toString(), + pp.getErrorIndex()); + } + setPrecision(defaultDateSmallestUnitIndex, working); + return working; + } + + /** + * @return Returns the defaultDateFormat. + */ + public SimpleDateFormat getDefaultDateFormat() { + return defaultDateFormat; + } + /** + * @return Returns the defaultDateFormat pattern string. + */ + public String getDefaultDateFormatString() { + return defaultDateFormat.toPattern(); + } + /** + * @param format The defaultDateFormat to be set. + * @param dfs the symbols to use (may be null) + */ + private void setDefaultDateFormat(String format, DateFormatSymbols dfs) { + if (format != null) { + if (dfs != null) { + this.defaultDateFormat = new SimpleDateFormat(format, dfs); + } else { + this.defaultDateFormat = new SimpleDateFormat(format); + } + this.defaultDateFormat.setLenient(false); + } else { + this.defaultDateFormat = null; + } + this.defaultDateSmallestUnitIndex = getEntry(this.defaultDateFormat); + } + /** + * @return Returns the recentDateFormat. + */ + public SimpleDateFormat getRecentDateFormat() { + return recentDateFormat; + } + /** + * @return Returns the recentDateFormat. + */ + public String getRecentDateFormatString() { + return recentDateFormat.toPattern(); + } + /** + * @param format The recentDateFormat to set. + * @param dfs the symbols to use (may be null) + */ + private void setRecentDateFormat(String format, DateFormatSymbols dfs) { + if (format != null) { + if (dfs != null) { + this.recentDateFormat = new SimpleDateFormat(format, dfs); + } else { + this.recentDateFormat = new SimpleDateFormat(format); + } + this.recentDateFormat.setLenient(false); + } else { + this.recentDateFormat = null; + } + this.recentDateSmallestUnitIndex = getEntry(this.recentDateFormat); + } + + /** + * @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(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. + *

+ */ + @Override + 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(); + setRecentDateFormat(recentFormatString, dfs); + + String defaultFormatString = config.getDefaultDateFormatStr(); + if (defaultFormatString == null) { + throw new IllegalArgumentException("defaultFormatString cannot be null"); + } + setDefaultDateFormat(defaultFormatString, dfs); + + 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java new file mode 100644 index 00000000..6cdf9ea8 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java @@ -0,0 +1,269 @@ +/* + * 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.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPFileEntryParserImpl; + +/** + * Parser class for MSLT and MLSD replies. See RFC 3659. + *

+ * Format is as follows: + *

+ * entry            = [ facts ] SP pathname
+ * facts            = 1*( fact ";" )
+ * fact             = factname "=" value
+ * factname         = "Size" / "Modify" / "Create" /
+ *                    "Type" / "Unique" / "Perm" /
+ *                    "Lang" / "Media-Type" / "CharSet" /
+ * os-depend-fact / local-fact
+ * os-depend-fact   = {IANA assigned OS name} "." token
+ * local-fact       = "X." token
+ * value            = *SCHAR
+ *
+ * Sample os-depend-fact:
+ * UNIX.group=0;UNIX.mode=0755;UNIX.owner=0;
+ * 
+ * A single control response entry (MLST) is returned with a leading space; + * multiple (data) entries are returned without any leading spaces. + * The parser requires that the leading space from the MLST entry is removed. + * MLSD entries can begin with a single space if there are no facts. + * + * @since 3.0 + */ +public class MLSxEntryParser extends FTPFileEntryParserImpl +{ + // This class is immutable, so a single instance can be shared. + private static final MLSxEntryParser PARSER = new MLSxEntryParser(); + + private static final HashMap TYPE_TO_INT = new HashMap(); + static { + TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE)); + TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory + TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a parent dir + TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir + } + + private static int UNIX_GROUPS[] = { // Groups in order of mode digits + FTPFile.USER_ACCESS, + FTPFile.GROUP_ACCESS, + FTPFile.WORLD_ACCESS, + }; + + private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value +/* 0 */ {}, +/* 1 */ {FTPFile.EXECUTE_PERMISSION}, +/* 2 */ {FTPFile.WRITE_PERMISSION}, +/* 3 */ {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION}, +/* 4 */ {FTPFile.READ_PERMISSION}, +/* 5 */ {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION}, +/* 6 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION}, +/* 7 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION}, + }; + + /** + * Create the parser for MSLT and MSLD listing entries + * This class is immutable, so one can use {@link #getInstance()} instead. + */ + public MLSxEntryParser() + { + super(); + } + + @Override + public FTPFile parseFTPEntry(String entry) { + if (entry.startsWith(" ")) {// leading space means no facts are present + if (entry.length() > 1) { // is there a path name? + FTPFile file = new FTPFile(); + file.setRawListing(entry); + file.setName(entry.substring(1)); + return file; + } else { + return null; // Invalid - no pathname + } + + } + String parts[] = entry.split(" ",2); // Path may contain space + if (parts.length != 2 || parts[1].length() == 0) { + return null; // no space found or no file name + } + final String factList = parts[0]; + if (!factList.endsWith(";")) { + return null; + } + FTPFile file = new FTPFile(); + file.setRawListing(entry); + file.setName(parts[1]); + String[] facts = factList.split(";"); + boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode="); + for(String fact : facts) { + String []factparts = fact.split("=", -1); // Don't drop empty values +// Sample missing permission +// drwx------ 2 mirror mirror 4096 Mar 13 2010 subversion +// modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion + if (factparts.length != 2) { + return null; // invalid - there was no "=" sign + } + String factname = factparts[0].toLowerCase(Locale.ENGLISH); + String factvalue = factparts[1]; + if (factvalue.length() == 0) { + continue; // nothing to see here + } + String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH); + if ("size".equals(factname)) { + file.setSize(Long.parseLong(factvalue)); + } + else if ("sizd".equals(factname)) { // Directory size + file.setSize(Long.parseLong(factvalue)); + } + else if ("modify".equals(factname)) { + final Calendar parsed = parseGMTdateTime(factvalue); + if (parsed == null) { + return null; + } + file.setTimestamp(parsed); + } + else if ("type".equals(factname)) { + Integer intType = TYPE_TO_INT.get(valueLowerCase); + if (intType == null) { + file.setType(FTPFile.UNKNOWN_TYPE); + } else { + file.setType(intType.intValue()); + } + } + else if (factname.startsWith("unix.")) { + String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH); + if ("group".equals(unixfact)){ + file.setGroup(factvalue); + } else if ("owner".equals(unixfact)){ + file.setUser(factvalue); + } else if ("mode".equals(unixfact)){ // e.g. 0[1]755 + int off = factvalue.length()-3; // only parse last 3 digits + for(int i=0; i < 3; i++){ + int ch = factvalue.charAt(off+i)-'0'; + if (ch >= 0 && ch <= 7) { // Check it's valid octal + for(int p : UNIX_PERMS[ch]) { + file.setPermission(UNIX_GROUPS[i], p, true); + } + } else { + // TODO should this cause failure, or can it be reported somehow? + } + } // digits + } // mode + } // unix. + else if (!hasUnixMode && "perm".equals(factname)) { // skip if we have the UNIX.mode + doUnixPerms(file, valueLowerCase); + } // process "perm" + } // each fact + return file; + } + + /** + * Parse a GMT time stamp of the form YYYYMMDDHHMMSS[.sss] + * + * @param timestamp the date-time to parse + * @return a Calendar entry, may be {@code null} + * @since 3.4 + */ + public static Calendar parseGMTdateTime(String timestamp) { + final SimpleDateFormat sdf; + final boolean hasMillis; + if (timestamp.contains(".")){ + sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); + hasMillis = true; + } else { + sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + hasMillis = false; + } + TimeZone GMT = TimeZone.getTimeZone("GMT"); + // both timezones need to be set for the parse to work OK + sdf.setTimeZone(GMT); + GregorianCalendar gc = new GregorianCalendar(GMT); + ParsePosition pos = new ParsePosition(0); + sdf.setLenient(false); // We want to parse the whole string + final Date parsed = sdf.parse(timestamp, pos); + if (pos.getIndex() != timestamp.length()) { + return null; // did not fully parse the input + } + gc.setTime(parsed); + if (!hasMillis) { + gc.clear(Calendar.MILLISECOND); // flag up missing ms units + } + return gc; + } + + // perm-fact = "Perm" "=" *pvals + // pvals = "a" / "c" / "d" / "e" / "f" / + // "l" / "m" / "p" / "r" / "w" + private void doUnixPerms(FTPFile file, String valueLowerCase) { + for(char c : valueLowerCase.toCharArray()) { + // TODO these are mostly just guesses at present + switch (c) { + case 'a': // (file) may APPEnd + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'c': // (dir) files may be created in the dir + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'd': // deletable + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'e': // (dir) can change to this dir + file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); + break; + case 'f': // (file) renamable + // ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'l': // (dir) can be listed + file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true); + break; + case 'm': // (dir) can create directory here + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'p': // (dir) entries may be deleted + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'r': // (files) file may be RETRieved + file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); + break; + case 'w': // (files) file may be STORed + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + default: + break; + // ignore unexpected flag for now. + } // switch + } // each char + } + + public static FTPFile parseEntry(String entry) { + return PARSER.parseFTPEntry(entry); + } + + public static MLSxEntryParser getInstance() { + return PARSER; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java new file mode 100644 index 00000000..28a45dcd --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java @@ -0,0 +1,554 @@ +/* + * 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. + * + * @version $Id: MVSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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
+     * 
+ * Sample output from FTP session: + *
+     * ftp> quote site filetype=jes
+     * 200 SITE command was accepted
+     * ftp> ls
+     * 200 Port request OK.
+     * 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER
+     * 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
+     * 250 List completed successfully.
+     * ftp> ls job01906
+     * 200 Port request OK.
+     * 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER
+     * JOBNAME  JOBID    OWNER    STATUS CLASS
+     * IBMUSER1 JOB01906 IBMUSER  OUTPUT A        RC=0000
+     * --------
+     * ID  STEPNAME PROCSTEP C DDNAME   BYTE-COUNT
+     * 001 JES2              A JESMSGLG       858
+     * 002 JES2              A JESJCL         128
+     * 003 JES2              A JESYSMSG       443
+     * 3 spool files
+     * 250 List completed successfully.
+     * 
+ */ + + 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 + */ + @Override + 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: + *
+     *    0         1        2          3        4     5     6      7    8
+     *   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 true if the entry string is non-null and non-empty + */ + private boolean parseSimpleEntry(FTPFile file, String entry) { + if (entry != null && entry.trim().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: + *
+     * [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); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java new file mode 100644 index 00000000..ac88f3b2 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java @@ -0,0 +1,278 @@ +/* + * 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 pre MacOS-X Systems. + * + * @version $Id: MacOsPeterFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @since 3.1 + */ +public class MacOsPeterFTPEntryParser 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 + + /** + * 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-])" // type (1) + + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission + + "(" + + "(folder\\s+)" + + "|" + + "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size + + ")" + + "(\\d+)\\s+" // size + /* + * numeric or standard format date: + * yyyy-mm-dd (expecting hh:mm to follow) + * MMM [d]d + * [d]d MMM + * N.B. use non-space for MMM to allow for languages such as German which use + * diacritics (e.g. umlaut) in some abbreviations. + */ + + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+" + /* + year (for non-recent standard format) - yyyy + or time (for numeric or recent standard format) [h]h:mm + */ + + "(\\d+(?::\\d+)?)\\s+" + + + "(\\S*)(\\s*.*)"; // the rest + + + /** + * The default constructor for a UnixFTPEntryParser object. + * + * @throws 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 MacOsPeterFTPEntryParser() + { + 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. + * @throws 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 MacOsPeterFTPEntryParser(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 + */ + @Override + 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 = "0"; + String usr = null; + String grp = null; + String filesize = group(20); + String datestr = group(21) + " " + group(22); + String name = group(23); + String endtoken = group(24); + + try + { + file.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + // A 'whiteout' file is an ARTIFICIAL entry in any of several types of + // 'translucent' filesystems, of which a 'union' filesystem is one. + + // bcdelfmpSs- + switch (typeStr.charAt(0)) + { + case 'd': + type = FTPFile.DIRECTORY_TYPE; + break; + case 'e': // NET-39 => z/OS external link + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'l': + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'b': + case 'c': + isDevice = true; + type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented + break; + case 'f': + case '-': + type = FTPFile.FILE_TYPE; + break; + default: // e.g. ? and w = whiteout + 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); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java new file mode 100644 index 00000000..1c78aadc --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java @@ -0,0 +1,167 @@ +/* + * 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.regex.Pattern; + +import org.apache.commons.net.ftp.Configurable; +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * Implementation of FTPFileEntryParser and FTPFileListParser for NT Systems. + * + * @version $Id: NTFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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 + + private static final String DEFAULT_DATE_FORMAT2 + = "MM-dd-yy kk:mm"; //11-09-01 18:30 + + private final FTPTimestampParser timestampParser; + + /** + * this is the regular expression used by this parser. + */ + private static final String REGEX = + "(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm; swallow trailing spaces + + "(?:()|([0-9]+))\\s+" // or ddddd; swallow trailing spaces + + "(\\S.*)"; // First non-space followed by rest of line (name) + + /** + * The sole constructor for an NTFTPEntryParser object. + * + * @throws 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. + * @throws 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, Pattern.DOTALL); + configure(config); + FTPClientConfig config2 = new FTPClientConfig( + FTPClientConfig.SYST_NT, + DEFAULT_DATE_FORMAT2, + null); + config2.setDefaultDateFormatStr(DEFAULT_DATE_FORMAT2); + this.timestampParser = new FTPTimestampParserImpl(); + ((Configurable)this.timestampParser).configure(config2); + } + + /** + * 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 + */ + @Override + 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) + { + // parsing fails, try the other date format + try + { + f.setTimestamp(timestampParser.parseTimestamp(datestr)); + } + catch (ParseException e2) + { + // 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); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java new file mode 100644 index 00000000..2d1b340f --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java @@ -0,0 +1,177 @@ +/* + * 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. + * + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + * @version $Id: NetwareFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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 + + "\\[([-A-Z]+)\\]\\s+" // Attributes RWCEAFMS or - + + "(\\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. + * + * @throws 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. + * @throws 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 + */ + @Override + 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); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java new file mode 100644 index 00000000..fbd78286 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.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.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. + * + * @version $Id: OS2FTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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. + * + * @throws 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. + * @throws 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 + */ + @Override + 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); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java new file mode 100644 index 00000000..5248a819 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java @@ -0,0 +1,435 @@ +/* + * 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.File; +import java.text.ParseException; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * @version $Id: OS400FTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ + *
+ * Example *FILE/*MEM FTP entries, when the current
+ * working directory is a file of file system QSYS:
+ * ------------------------------------------------
+ *
+ * $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file
+ *   250-NAMEFMT set to 1.
+ *   250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory.
+ * $ dir
+ *   227 Entering Passive Mode (10,200,36,33,40,249).
+ *   125 List started.
+ *   QPGMR          135168 22.06.13 13:18:19 *FILE
+ *   QPGMR                                   *MEM       MKCMD.MBR
+ *   QPGMR                                   *MEM       RUCALLTST.MBR
+ *   QPGMR                                   *MEM       RUCMDHLP.MBR
+ *   QPGMR                                   *MEM       RUCRTTST.MBR
+ *   250 List completed.
+ *
+ *
+ * Example *FILE entry of an OS/400 save file:
+ * ---------------------------------------------------
+ *
+ * $ cwd /qsys.lib/rpgunit.lib
+ *   250 "/QSYS.LIB/RPGUNIT.LIB" is current library.
+ * $ dir rpgunit.file
+ *   227 Entering Passive Mode (10,200,36,33,188,106).
+ *   125 List started.
+ *   QPGMR        16347136 29.06.13 15:45:09 *FILE      RPGUNIT.SAVF
+ *   250 List completed.
+ *
+ *
+ * Example *STMF/*DIR FTP entries, when the
+ * current working directory is in file system "root":
+ * ---------------------------------------------------
+ *
+ * $ cwd /home/raddatz
+ *   250 "/home/raddatz" is current directory.
+ * $ dir test*
+ *   227 Entering Passive Mode (10,200,36,33,200,189).
+ *   125 List started.
+ *   RADDATZ           200 21.05.11 12:31:18 *STMF      TEST_RG_02_CRLF.properties
+ *   RADDATZ           187 08.05.11 12:31:40 *STMF      TEST_RG_02_LF.properties
+ *   RADDATZ           187 08.05.11 12:31:52 *STMF      TEST_RG_02_CR.properties
+ *   RADDATZ          8192 04.07.13 09:04:14 *DIR       testDir1/
+ *   RADDATZ          8192 04.07.13 09:04:17 *DIR       testDir2/
+ *   250 List completed.
+ *
+ *
+ * Example 1, using ANT to list specific members of a file:
+ * --------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Listing members of a file:</echo>
+ *
+ *      <ftp action="list"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
+ *           systemTypeKey="OS/400"
+ *           listing="ftp-listing.txt"
+ *           >
+ *          <fileset dir="./i5-downloads-file" casesensitive="false">
+ *              <include name="run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Listing members of a file:
+ *    [ftp] listing files
+ *    [ftp] listing RUN.MBR
+ *    [ftp] listing RUNNER.MBR
+ *    [ftp] listing RUNNERBND.MBR
+ *    [ftp] 3 files listed
+ *
+ *
+ * Example 2, using ANT to list specific members of all files of a library:
+ * ------------------------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Listing members of all files of a library:</echo>
+ *
+ *      <ftp action="list"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
+ *           systemTypeKey="OS/400"
+ *           listing="ftp-listing.txt"
+ *           >
+ *          <fileset dir="./i5-downloads-lib" casesensitive="false">
+ *              <include name="**\run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Listing members of all files of a library:
+ *    [ftp] listing files
+ *    [ftp] listing RPGUNIT1.FILE\RUN.MBR
+ *    [ftp] listing RPGUNIT1.FILE\RUNRMT.MBR
+ *    [ftp] listing RPGUNITT1.FILE\RUNT.MBR
+ *    [ftp] listing RPGUNITY1.FILE\RUN.MBR
+ *    [ftp] listing RPGUNITY1.FILE\RUNNER.MBR
+ *    [ftp] listing RPGUNITY1.FILE\RUNNERBND.MBR
+ *    [ftp] 6 files listed
+ *
+ *
+ * Example 3, using ANT to download specific members of a file:
+ * ------------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Downloading members of a file:</echo>
+ *
+ *      <ftp action="get"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
+ *           systemTypeKey="OS/400"
+ *           >
+ *          <fileset dir="./i5-downloads-file" casesensitive="false">
+ *              <include name="run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Downloading members of a file:
+ *    [ftp] getting files
+ *    [ftp] transferring RUN.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUN.MBR
+ *    [ftp] transferring RUNNER.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNER.MBR
+ *    [ftp] transferring RUNNERBND.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNERBND.MBR
+ *    [ftp] 3 files retrieved
+ *
+ *
+ * Example 4, using ANT to download specific members of all files of a library:
+ * ----------------------------------------------------------------------------
+ *
+ *      <echo/>
+ *      <echo>Downloading members of all files of a library:</echo>
+ *
+ *      <ftp action="get"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="false"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
+ *           systemTypeKey="OS/400"
+ *           >
+ *          <fileset dir="./i5-downloads-lib" casesensitive="false">
+ *              <include name="**\run*.mbr" />
+ *          </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *
+ *   [echo] Downloading members of all files of a library:
+ *    [ftp] getting files
+ *    [ftp] transferring RPGUNIT1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUN.MBR
+ *    [ftp] transferring RPGUNIT1.FILE\RUNRMT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUNRMT.MBR
+ *    [ftp] transferring RPGUNITT1.FILE\RUNT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITT1.FILE\RUNT.MBR
+ *    [ftp] transferring RPGUNITY1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUN.MBR
+ *    [ftp] transferring RPGUNITY1.FILE\RUNNER.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNER.MBR
+ *    [ftp] transferring RPGUNITY1.FILE\RUNNERBND.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNERBND.MBR
+ *    [ftp] 6 files retrieved
+ *
+ *
+ * Example 5, using ANT to download a save file of a library:
+ * ----------------------------------------------------------
+ *
+ *      <ftp action="get"
+ *           server="${ftp.server}"
+ *           userid="${ftp.user}"
+ *           password="${ftp.password}"
+ *           binary="true"
+ *           verbose="true"
+ *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
+ *           systemTypeKey="OS/400"
+ *           >
+ *        <fileset dir="./i5-downloads-savf" casesensitive="false">
+ *            <include name="RPGUNIT.SAVF" />
+ *        </fileset>
+ *      </ftp>
+ *
+ * Output:
+ * -------
+ *   [echo] Downloading save file:
+ *    [ftp] getting files
+ *    [ftp] transferring RPGUNIT.SAVF to C:\workspaces\rdp_080\workspace\net-Test\i5-downloads-lib\RPGUNIT.SAVF
+ *    [ftp] 1 files retrieved
+ *
+ * 
+ */ +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, empty for members + + "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members + + "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM + + "(?:(\\S+)\\s*)?"; // filename, missing, when CWD is a *FILE + + + /** + * The default constructor for a OS400FTPEntryParser object. + * + * @throws 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. + * @throws 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); + } + + + @Override + 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 = ""; + if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) + { + datestr = group(3)+" "+group(4); + } + String typeStr = group(5); + String name = group(6); + + boolean mustScanForPathSeparator = true; + + try + { + file.setTimestamp(super.parseTimestamp(datestr)); + } + catch (ParseException e) + { + // intentionally do nothing + } + + + if (typeStr.equalsIgnoreCase("*STMF")) + { + type = FTPFile.FILE_TYPE; + if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) + { + return null; + } + } + else if (typeStr.equalsIgnoreCase("*DIR")) + { + type = FTPFile.DIRECTORY_TYPE; + if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) + { + return null; + } + } + else if (typeStr.equalsIgnoreCase("*FILE")) + { + // File, defines the structure of the data (columns of a row) + // but the data is stored in one or more members. Typically a + // source file contains multiple members whereas it is + // recommended (but not enforced) to use one member per data + // file. + // Save files are a special type of files which are used + // to save objects, e.g. for backups. + if (name != null && name.toUpperCase().endsWith(".SAVF")) + { + mustScanForPathSeparator = false; + type = FTPFile.FILE_TYPE; + } + else + { + return null; + } + } + else if (typeStr.equalsIgnoreCase("*MEM")) + { + mustScanForPathSeparator = false; + type = FTPFile.FILE_TYPE; + + if (isNullOrEmpty(name)) + { + return null; + } + if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) + { + return null; + } + + // Quick and dirty bug fix to make SelectorUtils work. + // Class SelectorUtils uses 'File.separator' to splitt + // a given path into pieces. But actually it had to + // use the separator of the FTP server, which is a forward + // slash in case of an AS/400. + name = name.replace('/', File.separatorChar); + } + 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); + } + if (mustScanForPathSeparator) + { + int pos = name.lastIndexOf('/'); + if (pos > -1) + { + name = name.substring(pos + 1); + } + } + + file.setName(name); + + return file; + } + return null; + } + + /** + * + * @param string String value that is checked for null + * or empty. + * @return true for null or empty values, + * else false. + */ + private boolean isNullOrEmpty(String string) { + if (string == null || string.length() == 0) { + return true; + } + 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_OS400, + DEFAULT_DATE_FORMAT, + null); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java new file mode 100644 index 00000000..080f8097 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java @@ -0,0 +1,62 @@ +/* + * 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 { + + private static final long serialVersionUID = 5563335279583210658L; + + /** + * Constucts a ParserInitializationException with just a message + * + * @param message Exception message + */ + public ParserInitializationException(String message) { + super(message); + } + + /** + * 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, 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 + * @deprecated use {@link #getCause()} instead + */ + @Deprecated + public Throwable getRootCause() { + return super.getCause(); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java new file mode 100644 index 00000000..50ef365a --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java @@ -0,0 +1,202 @@ +/* + * 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 class for all regular expression based FTPFileEntryParser classes + */ +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. + * The expression is compiled with flags = 0. + * + * @param regex The regular expression with which this object is + * initialized. + * + * @throws 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(); + compileRegex(regex, 0); + } + + /** + * The constructor for a RegexFTPFileEntryParserImpl object. + * + * @param regex The regular expression with which this object is + * initialized. + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * + * @throws 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. + * @since 3.4 + */ + public RegexFTPFileEntryParserImpl(String regex, final int flags) { + super(); + compileRegex(regex, flags); + } + + /** + * 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() { + StringBuilder b = new StringBuilder(); + 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 true + * @since 2.0 + * @throws IllegalArgumentException if the regex cannot be compiled + */ + public boolean setRegex(final String regex) { + compileRegex(regex, 0); + return true; + } + + + /** + * Alter the current regular expression being utilised for entry parsing + * and create a new {@link Pattern} instance. + * @param regex The new regular expression + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * @return true + * @since 3.4 + * @throws IllegalArgumentException if the regex cannot be compiled + */ + public boolean setRegex(final String regex, final int flags) { + compileRegex(regex, flags); + return true; + } + + /** + * Compile the regex and store the {@link Pattern}. + * + * This is an internal method to do the work so the constructor does not + * have to call an overrideable method. + * + * @param regex the expression to compile + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * @throws IllegalArgumentException if the regex cannot be compiled + */ + private void compileRegex(final String regex, final int flags) { + try { + pattern = Pattern.compile(regex, flags); + } catch (PatternSyntaxException pse) { + throw new IllegalArgumentException("Unparseable regex supplied: " + regex); + } + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java new file mode 100644 index 00000000..f48cf728 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java @@ -0,0 +1,379 @@ +/* + * 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 java.util.ListIterator; + +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 1781925 2017-02-06 16:43:40Z 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 + + // Suffixes used in Japanese listings after the numeric values + private static final String JA_MONTH = "\u6708"; + private static final String JA_DAY = "\u65e5"; + private static final String JA_YEAR = "\u5e74"; + + private static final String DEFAULT_DATE_FORMAT_JA + = "M'" + JA_MONTH + "' d'" + JA_DAY + "' yyyy'" + JA_YEAR + "'"; //6月 3日 2003年 + + private static final String DEFAULT_RECENT_DATE_FORMAT_JA + = "M'" + JA_MONTH + "' d'" + JA_DAY + "' HH:mm"; //8月 17日 20:10 + + /** + * 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); + + /** + * 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 + * Final letter may be appended: + * + file has extended security attributes (e.g. ACL) + * Note: local listings on MacOSX also use '@'; + * this is not allowed for here as does not appear to be shown by FTP servers + * {@code @} file has extended attributes + */ + private static final String REGEX = + "([bcdelfmpSs-])" // file type + +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions + + + "\\s*" // separator TODO why allow it to be omitted?? + + + "(\\d+)" // link count + + + "\\s+" // separator + + + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces) + + "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces) + + "(\\d+(?:,\\s*\\d+)?)" // size or n,m + + + "\\s+" // separator + + /* + * numeric or standard format date: + * yyyy-mm-dd (expecting hh:mm to follow) + * MMM [d]d + * [d]d MMM + * N.B. use non-space for MMM to allow for languages such as German which use + * diacritics (e.g. umlaut) in some abbreviations. + * Japanese uses numeric day and month with suffixes to distinguish them + * [d]dXX [d]dZZ + */ + + "("+ + "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd + "|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d + "|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM + "|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")"+ + ")" + + + "\\s+" // separator + + /* + year (for non-recent standard format) - yyyy + or time (for numeric or recent standard format) [h]h:mm + or Japanese year - yyyyXX + */ + + "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20) + + + "\\s" // separator + + + "(.*)"; // the rest (21) + + + // if true, leading spaces are trimmed from file names + // this was the case for the original implementation + final boolean trimLeadingSpaces; // package protected for access from test code + + /** + * The default constructor for a UnixFTPEntryParser object. + * + * @throws 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. + * @throws 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) + { + this(config, false); + } + + /** + * 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. + * @param trimLeadingSpaces if {@code true}, trim leading spaces from file names + * @throws 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 3.4 + */ + public UnixFTPEntryParser(FTPClientConfig config, boolean trimLeadingSpaces) + { + super(REGEX); + configure(config); + this.trimLeadingSpaces = trimLeadingSpaces; + } + + /** + * Preparse the list to discard "total nnn" lines + */ + @Override + public List preParse(List original) { + ListIterator iter = original.listIterator(); + while (iter.hasNext()) { + String entry = iter.next(); + if (entry.matches("^total \\d+$")) { // NET-389 + iter.remove(); + } + } + return original; + } + + /** + * 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 + */ + @Override + 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); + if (trimLeadingSpaces) { + name = name.replaceFirst("^\\s+", ""); + } + + try + { + if (group(19).contains(JA_MONTH)) { // special processing for Japanese format + FTPTimestampParserImpl jaParser = new FTPTimestampParserImpl(); + jaParser.configure(new FTPClientConfig( + FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT_JA, DEFAULT_RECENT_DATE_FORMAT_JA)); + file.setTimestamp(jaParser.parseTimestamp(datestr)); + } else { + file.setTimestamp(super.parseTimestamp(datestr)); + } + } + catch (ParseException e) + { + // intentionally do nothing + } + + // A 'whiteout' file is an ARTIFICIAL entry in any of several types of + // 'translucent' filesystems, of which a 'union' filesystem is one. + + // bcdelfmpSs- + switch (typeStr.charAt(0)) + { + case 'd': + type = FTPFile.DIRECTORY_TYPE; + break; + case 'e': // NET-39 => z/OS external link + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'l': + type = FTPFile.SYMBOLIC_LINK_TYPE; + break; + case 'b': + case 'c': + isDevice = true; + type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented + break; + case 'f': + case '-': + type = FTPFile.FILE_TYPE; + break; + default: // e.g. ? and w = whiteout + 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 + } + + // oddball cases like symbolic links, file names + // with spaces in them. + 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); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java new file mode 100644 index 00000000..73b3840e --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.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.ftp.parser; +import java.io.BufferedReader; +import java.io.IOException; +import java.text.ParseException; +import java.util.StringTokenizer; + +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; + +/** + * 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. + * + *

+ * + * @version $Id: VMSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z 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. + * + * @throws 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. + * @throws 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 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 + */ + @Override + 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. + * @throws IOException thrown on any IO Error reading from the reader. + */ + @Override + public String readNextEntry(BufferedReader reader) throws IOException + { + String line = reader.readLine(); + StringBuilder entry = new StringBuilder(); + 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); + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + + /** + * DO NOT USE + * @param listStream the stream + * @return the array of files + * @throws IOException on error + * @deprecated (2.2) No other FTPFileEntryParser implementations have this method. + */ + @Deprecated + public FTPFile[] parseFileList(java.io.InputStream listStream) throws IOException { + org.apache.commons.net.ftp.FTPListParseEngine engine = new org.apache.commons.net.ftp.FTPListParseEngine(this); + engine.readServerList(listStream, null); + return engine.getFiles(); + } + +} + +/* Emacs configuration + * Local variables: ** + * mode: java ** + * c-basic-offset: 4 ** + * indent-tabs-mode: nil ** + * End: ** + */ diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java new file mode 100644 index 00000000..554f11ee --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java @@ -0,0 +1,167 @@ +/* + * 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)", + *

+ * + * @version $Id: VMSVersioningFTPEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ + * + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) + */ +public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser +{ + + private final Pattern _preparse_pattern_; + private static final String PRE_PARSE_REGEX = + "(.*?);([0-9]+)\\s*.*"; + + /** + * Constructor for a VMSFTPEntryParser object. + * + * @throws 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. + * @throws 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); + } + + } + + /** + * 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) { + HashMap existingEntries = new HashMap(); + ListIterator iter = original.listIterator(); + while (iter.hasNext()) { + String entry = iter.next().trim(); + MatchResult result = null; + Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); + if (_preparse_matcher_.matches()) { + result = _preparse_matcher_.toMatchResult(); + String name = result.group(1); + String version = result.group(2); + Integer nv = Integer.valueOf(version); + Integer existing = existingEntries.get(name); + if (null != existing) { + if (nv.intValue() < existing.intValue()) { + iter.remove(); // removes older version 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; + Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); + if (_preparse_matcher_.matches()) { + result = _preparse_matcher_.toMatchResult(); + String name = result.group(1); + String version = result.group(2); + Integer nv = Integer.valueOf(version); + Integer existing = existingEntries.get(name); + if (null != existing) { + if (nv.intValue() < existing.intValue()) { + iter.remove(); // removes older version 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/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/package-info.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/package-info.java new file mode 100644 index 00000000..7dc0ffd5 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * FTP file listing parser classes + */ +package org.apache.commons.net.ftp.parser; \ No newline at end of file diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CRLFLineReader.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CRLFLineReader.java new file mode 100644 index 00000000..9ba51a9b --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CRLFLineReader.java @@ -0,0 +1,77 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.Reader; + +/** + * CRLFLineReader implements a readLine() method that requires + * exactly CRLF to terminate an input line. + * This is required for IMAP, which allows bare CR and LF. + * + * @since 3.0 + */ +public final class CRLFLineReader extends BufferedReader +{ + private static final char LF = '\n'; + private static final char CR = '\r'; + + /** + * Creates a CRLFLineReader that wraps an existing Reader + * input source. + * @param reader The Reader input source. + */ + public CRLFLineReader(Reader reader) + { + super(reader); + } + + /** + * Read a line of text. + * A line is considered to be terminated by carriage return followed immediately by a linefeed. + * This contrasts with BufferedReader which also allows other combinations. + * @since 3.0 + */ + @Override + public String readLine() throws IOException { + StringBuilder sb = new StringBuilder(); + int intch; + boolean prevWasCR = false; + synchronized(lock) { // make thread-safe (hopefully!) + while((intch = read()) != -1) + { + if (prevWasCR && intch == LF) { + return sb.substring(0, sb.length()-1); + } + if (intch == CR) { + prevWasCR = true; + } else { + prevWasCR = false; + } + sb.append((char) intch); + } + } + String string = sb.toString(); + if (string.length() == 0) { // immediate EOF + return null; + } + return string; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java new file mode 100644 index 00000000..328ab010 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java @@ -0,0 +1,118 @@ +/* + * 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 + * @version $Id: CopyStreamAdapter.java 1741829 2016-05-01 00:24:44Z sebb $ + */ +public class CopyStreamAdapter implements CopyStreamListener +{ + private final 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. + */ + @Override + public void bytesTransferred(CopyStreamEvent event) + { + for (EventListener listener : internalListeners) + { + ((CopyStreamListener) (listener)).bytesTransferred(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. 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. + */ + @Override + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, long streamSize) + { + for (EventListener listener : internalListeners) + { + ((CopyStreamListener) (listener)).bytesTransferred( + totalBytesTransferred, bytesTransferred, streamSize); + } + } + + /** + * 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java new file mode 100644 index 00000000..db5f5510 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java @@ -0,0 +1,111 @@ +/* + * 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 + * @version $Id: CopyStreamEvent.java 1652801 2015-01-18 17:10:05Z sebb $ + */ +public class CopyStreamEvent extends EventObject +{ + private static final long serialVersionUID = -964927635655051867L; + + /** + * Constant used to indicate the stream size is unknown. + */ + public static final long UNKNOWN_STREAM_SIZE = -1; + + private final int bytesTransferred; + private final long totalBytesTransferred; + private final 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; + } + + /** + * @since 3.0 + */ + @Override + public String toString(){ + return getClass().getName() + "[source=" + source + + ", total=" + totalBytesTransferred + + ", bytes=" + bytesTransferred + + ", size=" + streamSize + + "]"; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamException.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamException.java new file mode 100644 index 00000000..54a576a1 --- /dev/null +++ b/AriaFtpPlug/src/main/java/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 + * @version $Id: CopyStreamException.java 1299238 2012-03-10 17:12:28Z sebb $ + */ +public class CopyStreamException extends IOException +{ + private static final long serialVersionUID = -2602899129433221532L; + + private final long totalBytesTransferred; + + /** + * 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); + initCause(exception); // merge this into super() call once we need 1.6+ + totalBytesTransferred = bytesTransferred; + } + + /** + * 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) getCause(); // cast is OK because it was initialised with an IOException + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamListener.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamListener.java new file mode 100644 index 00000000..b19542e8 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamListener.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.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 + * @version $Id: CopyStreamListener.java 1652801 2015-01-18 17:10:05Z sebb $ + */ +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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java new file mode 100644 index 00000000..49d3b5fd --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java @@ -0,0 +1,251 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +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, and ensures you cannot read past the end of the message. + *

+ * Note: versions since 3.0 extend BufferedReader rather than Reader, + * and no longer change the CRLF into the local EOL. Also only DOT CR LF + * acts as EOF. + * @version $Id: DotTerminatedMessageReader.java 1747119 2016-06-07 02:22:24Z ggregory $ + */ +public final class DotTerminatedMessageReader extends BufferedReader +{ + private static final char LF = '\n'; + private static final char CR = '\r'; + private static final int DOT = '.'; + + private boolean atBeginning; + private boolean eof; + private boolean seenCR; // was last character CR? + + /** + * 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); + // Assumes input is at start of message + atBeginning = true; + eof = false; + } + + /** + * 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. + * @throws IOException If an error occurs while reading the underlying + * stream. + */ + @Override + public int read() throws IOException { + synchronized (lock) { + if (eof) { + return -1; // Don't allow read past EOF + } + int chint = super.read(); + if (chint == -1) { // True EOF + eof = true; + return -1; + } + if (atBeginning) { + atBeginning = false; + if (chint == DOT) { // Have DOT + mark(2); // need to check for CR LF or DOT + chint = super.read(); + if (chint == -1) { // Should not happen + // new Throwable("Trailing DOT").printStackTrace(); + eof = true; + return DOT; // return the trailing DOT + } + if (chint == DOT) { // Have DOT DOT + // no need to reset as we want to lose the first DOT + return chint; // i.e. DOT + } + if (chint == CR) { // Have DOT CR + chint = super.read(); + if (chint == -1) { // Still only DOT CR - should not happen + //new Throwable("Trailing DOT CR").printStackTrace(); + reset(); // So CR is picked up next time + return DOT; // return the trailing DOT + } + if (chint == LF) { // DOT CR LF + atBeginning = true; + eof = true; + // Do we need to clear the mark somehow? + return -1; + } + } + // Should not happen - lone DOT at beginning + //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); + reset(); + return DOT; + } // have DOT + } // atBeginning + + // Handle CRLF in normal flow + if (seenCR) { + seenCR = false; + if (chint == LF) { + atBeginning = true; + } + } + if (chint == CR) { + seenCR = true; + } + return chint; + } + } + + + /** + * 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. + * @throws 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. + * @throws IOException If an error occurs in reading the underlying + * stream. + */ + @Override + public int read(char[] buffer, int offset, int length) throws IOException + { + if (length < 1) + { + return 0; + } + int ch; + synchronized (lock) + { + if ((ch = read()) == -1) + { + return -1; + } + + int off = offset; + + do + { + buffer[offset++] = (char) ch; + } + while (--length > 0 && (ch = read()) != -1); + + return (offset - off); + } + } + + /** + * 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. + * @throws IOException If an error occurs while reading the + * underlying stream. + */ + @Override + public void close() throws IOException + { + synchronized (lock) + { + if (!eof) + { + while (read() != -1) + { + // read to EOF + } + } + eof = true; + atBeginning = false; + } + } + + /** + * Read a line of text. + * A line is considered to be terminated by carriage return followed immediately by a linefeed. + * This contrasts with BufferedReader which also allows other combinations. + * @since 3.0 + */ + @Override + public String readLine() throws IOException { + StringBuilder sb = new StringBuilder(); + int intch; + synchronized(lock) { // make thread-safe (hopefully!) + while((intch = read()) != -1) + { + if (intch == LF && atBeginning) { + return sb.substring(0, sb.length()-1); + } + sb.append((char) intch); + } + } + String string = sb.toString(); + if (string.length() == 0) { // immediate EOF + return null; + } + // Should not happen - EOF without CRLF + //new Throwable(string).printStackTrace(); + return string; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java new file mode 100644 index 00000000..cb98cb85 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java @@ -0,0 +1,219 @@ +/* + * 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. + * + * + ***/ + +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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * @throws 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. + * + * @throws 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. + * + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java new file mode 100644 index 00000000..3d1b231e --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java @@ -0,0 +1,221 @@ +/* + * 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; +import java.io.UnsupportedEncodingException; + +/*** + * 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. + * + * + ***/ + +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"); + try { + _lineSeparatorBytes = _lineSeparator.getBytes("US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Broken JVM - cannot find US-ASCII charset!",e); + } + } + + 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. + * @param input the stream to wrap + ***/ + 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. + * @throws 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. + * @throws 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. + * @throws IOException If an error occurs while reading the underlying + * stream. + ***/ + @Override + public int read(byte buffer[], int offset, int length) throws IOException + { + if (_noConversionRequired) { + return super.read(buffer, offset, length); + } + + if (length < 1) { + return 0; + } + + int ch, off; + + ch = available(); + + __length = (length > ch ? ch : length); + + // 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); + } + + + // PushbackInputStream in JDK 1.1.3 returns the wrong thing + // TODO - can we delete this override now? + /*** + * 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 + { + if (in == null) { + throw new IOException("Stream closed"); + } + return (buf.length - pos) + in.available(); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java new file mode 100644 index 00000000..412addcd --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java @@ -0,0 +1,175 @@ +/* + * 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. + * + * + ***/ + +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. + * @throws 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. + * @throws 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. + * @throws 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. + * + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketInputStream.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketInputStream.java new file mode 100644 index 00000000..b84a4a2b --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketInputStream.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.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. + * + * + * @see SocketOutputStream + ***/ + +public class SocketInputStream extends FilterInputStream +{ + private final 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. + * + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketOutputStream.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketOutputStream.java new file mode 100644 index 00000000..6af8f54d --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketOutputStream.java @@ -0,0 +1,88 @@ +/* + * 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. + * + * + * @see SocketInputStream + ***/ + +public class SocketOutputStream extends FilterOutputStream +{ + private final 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. + * @throws 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. + * + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java new file mode 100644 index 00000000..f0ec930c --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java @@ -0,0 +1,185 @@ +/* + * 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. + * + * + ***/ + +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 wrap. + ***/ + 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. + * @throws 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'; + } + //$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. + * @throws 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. + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java new file mode 100644 index 00000000..989bfc34 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java @@ -0,0 +1,120 @@ +/* + * 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. + * + * + ***/ + +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. + * @throws 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. + * @throws 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. + * @throws 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/AriaFtpPlug/src/main/java/org/apache/commons/net/io/Util.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/Util.java new file mode 100644 index 00000000..1f4e5b50 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/Util.java @@ -0,0 +1,390 @@ +/* + * 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.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.Socket; + +/*** + * The Util class cannot be instantiated and stores short static convenience + * methods that are often quite useful. + * + * + * @see CopyStreamException + * @see CopyStreamListener + * @see CopyStreamAdapter + ***/ + +public final class Util +{ + /** + * The default buffer size ({@value}) used by + * {@link #copyStream copyStream } and {@link #copyReader copyReader} + * and by the copyReader/copyStream methods if a zero or negative buffer size is supplied. + */ + 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of bytes in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @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. + * @return number of bytes read/written + * @throws 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 numBytes; + long total = 0; + byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; + + try + { + while ((numBytes = source.read(buffer)) != -1) + { + // Technically, some read(byte[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + + if (numBytes == 0) + { + int singleByte = source.read(); + if (singleByte < 0) { + break; + } + dest.write(singleByte); + if(flush) { + dest.flush(); + } + ++total; + if (listener != null) { + listener.bytesTransferred(total, 1, streamSize); + } + continue; + } + + dest.write(buffer, 0, numBytes); + if(flush) { + dest.flush(); + } + total += numBytes; + if (listener != null) { + listener.bytesTransferred(total, numBytes, 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of bytes in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @param listener The CopyStreamListener to notify of progress. If + * this parameter is null, notification is not attempted. + * @return number of bytes read/written + * @throws 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. + * @param bufferSize The number of bytes to buffer during the copy. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @return The number of bytes read/written in the copy operation. + * @throws 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); + * @param source where to copy from + * @param dest where to copy to + * @return number of bytes copied + * @throws CopyStreamException on error + ***/ + 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of characters in the stream being copied. + * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. + * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @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. + * @throws 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 numChars; + long total = 0; + char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; + + try + { + while ((numChars = source.read(buffer)) != -1) + { + // Technically, some read(char[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + if (numChars == 0) + { + int singleChar = source.read(); + if (singleChar < 0) { + break; + } + dest.write(singleChar); + dest.flush(); + ++total; + if (listener != null) { + listener.bytesTransferred(total, 1, streamSize); + } + continue; + } + + dest.write(buffer, 0, numChars); + dest.flush(); + total += numChars; + if (listener != null) { + listener.bytesTransferred(total, numChars, 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. + * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @return The number of characters read/written in the copy operation. + * @throws 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); + * @param source where to copy from + * @param dest where to copy to + * @return number of bytes copied + * @throws CopyStreamException on error + ***/ + public static final long copyReader(Reader source, Writer dest) + throws CopyStreamException + { + return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); + } + + /** + * Closes the object quietly, catching rather than throwing IOException. + * Intended for use from finally blocks. + * + * @param closeable the object to close, may be {@code null} + * @since 3.0 + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + // Ignored + } + } + } + + /** + * Closes the socket quietly, catching rather than throwing IOException. + * Intended for use from finally blocks. + * + * @param socket the socket to close, may be {@code null} + * @since 3.0 + */ + public static void closeQuietly(Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // Ignored + } + } + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/io/package-info.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/package-info.java new file mode 100644 index 00000000..90fd4baa --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/io/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Utility classes for IO support. + */ +package org.apache.commons.net.io; \ No newline at end of file diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/Base64.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/Base64.java new file mode 100644 index 00000000..4e859885 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/Base64.java @@ -0,0 +1,1046 @@ +/* + * 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.UnsupportedEncodingException; +import java.math.BigInteger; + + + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + *

+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

+ *

+ * The class can be parameterized in the following manner with various constructors: + *

    + *
  • URL-safe mode: Default off.
  • + *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of + * 4 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n")
  • + *
+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ * + * @see RFC 2045 + * @since 2.2 + * @version $Id: Base64.java 1697293 2015-08-24 01:01:00Z sebb $ + */ +public class Base64 { + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * Chunk size per RFC 2045 section 6.8. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

+ * + * @see RFC 2045 section 6.8 + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / + * changed to - and _ to make the encoded Base64 results more URL-SAFE. + * This table is only used when the Base64's mode is set to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + /** + * Byte used to pad output. + */ + private static final byte PAD = '='; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in + * Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 + * alphabet but fall within the bounds of the array are translated to -1. + * + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both + * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] DECODE_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + /** Mask used to extract 8 bits, used in decoding base64 bytes */ + private static final int MASK_8BITS = 0xff; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able + * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch + * between the two modes. + */ + private final byte[] encodeTable; + + /** + * Line length for encoding. Not used when decoding. A value of zero or less implies no chunking of the base64 + * encoded data. + */ + private final int lineLength; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * decodeSize = 3 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * encodeSize = 4 + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Buffer for streaming. + */ + private byte[] buffer; + + /** + * Position where next character should be written in the buffer. + */ + private int pos; + + /** + * Position where next character should be read from the buffer. + */ + private int readPos; + + /** + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to + * make sure each encoded line never goes beyond lineLength (if lineLength > 0). + */ + private int currentLinePos; + + /** + * Writes to the buffer only occur after every 3 reads when encoding, an every 4 reads when decoding. This variable + * helps track that. + */ + private int modulus; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this Base64 object becomes useless, + * and must be thrown away. + */ + private boolean eof; + + /** + * Place holder for the 3 bytes we're dealing with for our base64 logic. Bitwise operations store and extract the + * base64 encoding or decoding from this variable. + */ + private int x; + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ */ + public Base64() { + this(false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ * + * @param urlSafe + * if true, URL-safe encoding is used. In most cases this should be set to + * false. + * @since 1.4 + */ + public Base64(boolean urlSafe) { + this(CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @since 1.4 + */ + public Base64(int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException + * Thrown when the provided lineSeparator included some base64 characters. + * @since 1.4 + */ + public Base64(int lineLength, byte[] lineSeparator) { + this(lineLength, lineSeparator, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param urlSafe + * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * @throws IllegalArgumentException + * The provided lineSeparator included some base64 characters. That's not going to work! + * @since 1.4 + */ + public Base64(int lineLength, byte[] lineSeparator, boolean urlSafe) { + if (lineSeparator == null) { + lineLength = 0; // disable chunk-separating + lineSeparator = EMPTY_BYTE_ARRAY; // this just gets ignored + } + this.lineLength = lineLength > 0 ? (lineLength / 4) * 4 : 0; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + if (lineLength > 0) { + this.encodeSize = 4 + lineSeparator.length; + } else { + this.encodeSize = 4; + } + this.decodeSize = this.encodeSize - 1; + if (containsBase64Byte(lineSeparator)) { + String sep = newStringUtf8(lineSeparator); + throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); + } + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + /** + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. + * + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 + */ + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } + + /** + * Returns true if this Base64 object has buffered data for reading. + * + * @return true if there is Base64 object still available for reading. + */ + boolean hasData() { + return this.buffer != null; + } + + /** + * Returns the amount of buffered data available for reading. + * + * @return The amount of buffered data available for reading. + */ + int avail() { + return buffer != null ? pos - readPos : 0; + } + + /** Doubles our buffer. */ + private void resizeBuffer() { + if (buffer == null) { + buffer = new byte[DEFAULT_BUFFER_SIZE]; + pos = 0; + readPos = 0; + } else { + byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(buffer, 0, b, 0, buffer.length); + buffer = b; + } + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail + * bytes. Returns how many bytes were actually extracted. + * + * @param b + * byte[] array to extract the buffered data into. + * @param bPos + * position in byte[] array to start extraction at. + * @param bAvail + * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(byte[] b, int bPos, int bAvail) { + if (buffer != null) { + int len = Math.min(avail(), bAvail); + if (buffer != b) { + System.arraycopy(buffer, readPos, b, bPos, len); + readPos += len; + if (readPos >= pos) { + buffer = null; + } + } else { + // Re-using the original consumer's output array is only + // allowed for one round. + buffer = null; + } + return len; + } + return eof ? -1 : 0; + } + + /** + * Sets the streaming buffer. This is a small optimization where we try to buffer directly to the consumer's output + * array for one round (if the consumer calls this method first) instead of starting our own buffer. + * + * @param out + * byte[] array to buffer directly to. + * @param outPos + * Position to start buffering into. + * @param outAvail + * Amount of bytes available for direct buffering. + */ + void setInitialBuffer(byte[] out, int outPos, int outAvail) { + // We can re-use consumer's original output array under + // special circumstances, saving on some System.arraycopy(). + if (out != null && out.length == outAvail) { + buffer = out; + pos = outPos; + readPos = outPos; + } + } + + /** + *

+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last + * remaining bytes (if not multiple of 3). + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of binary data to base64 encode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void encode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + eof = true; + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + switch (modulus) { + case 1 : + buffer[pos++] = encodeTable[(x >> 2) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 4) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + buffer[pos++] = PAD; + } + break; + + case 2 : + buffer[pos++] = encodeTable[(x >> 10) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 4) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 2) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + } + break; + default: + break; // other values ignored + } + if (lineLength > 0 && pos > 0) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + modulus = (++modulus) % 3; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + x = (x << 8) + b; + if (0 == modulus) { + buffer[pos++] = encodeTable[(x >> 18) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 12) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 6) & MASK_6BITS]; + buffer[pos++] = encodeTable[x & MASK_6BITS]; + currentLinePos += 4; + if (lineLength > 0 && lineLength <= currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + currentLinePos = 0; + } + } + } + } + } + + /** + *

+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

+ *

+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of ascii data to base64 decode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void decode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + if (inAvail < 0) { + eof = true; + } + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < decodeSize) { + resizeBuffer(); + } + byte b = in[inPos++]; + if (b == PAD) { + // We're done. + eof = true; + break; + } else { + if (b >= 0 && b < DECODE_TABLE.length) { + int result = DECODE_TABLE[b]; + if (result >= 0) { + modulus = (++modulus) % 4; + x = (x << 6) + result; + if (modulus == 0) { + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + buffer[pos++] = (byte) (x & MASK_8BITS); + } + } + } + } + } + + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (eof && modulus != 0) { + x = x << 6; + switch (modulus) { + case 2 : + x = x << 6; + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + break; + case 3 : + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + break; + default: + break; // other values ignored + } + } + } + + /** + * Returns whether or not the octet is in the base 64 alphabet. + * + * @param octet + * The value to test + * @return true if the value is defined in the the base 64 alphabet, false otherwise. + * @since 1.4 + */ + public static boolean isBase64(byte octet) { + return octet == PAD || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the + * method treats whitespace as valid. + * + * @param arrayOctet + * byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; + * false, otherwise + */ + public static boolean isArrayByteBase64(byte[] arrayOctet) { + for (int i = 0; i < arrayOctet.length; i++) { + if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { + return false; + } + } + return true; + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. + * + * @param arrayOctet + * byte array to test + * @return true if any byte is a valid character in the Base64 alphabet; false herwise + */ + private static boolean containsBase64Byte(byte[] arrayOctet) { + for (byte element : arrayOctet) + { + if (isBase64(element)) { + return true; + } + } + return false; + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm into 76 character blocks separated by CRLF. + *

+ * For a non-chunking version, see {@link #encodeBase64StringUnChunked(byte[])}. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 1.4 + */ + public static String encodeBase64String(byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, true)); + } + + /** + * Encodes binary data using the base64 algorithm, without using chunking. + *

+ * For a chunking version, see {@link #encodeBase64String(byte[])}. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64StringUnChunked(byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false)); + } + + /** + * Encodes binary data using the base64 algorithm. + * + * @param binaryData + * binary data to encode + * @param useChunking whether to split the output into chunks + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64String(byte[] binaryData, boolean useChunking) { + return newStringUtf8(encodeBase64(binaryData, useChunking)); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + * @since 1.4 + */ + public static byte[] encodeBase64URLSafe(byte[] binaryData) { + return encodeBase64(binaryData, false, true); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters + * @since 1.4 + */ + public static String encodeBase64URLSafeString(byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false, true)); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks + * + * @param binaryData + * binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Decodes a String containing containing characters in the Base64 alphabet. + * + * @param pArray + * A String containing Base64 character data + * @return a byte array containing binary data + * @since 1.4 + */ + public byte[] decode(String pArray) { + return decode(getBytesUtf8(pArray)); + } + + private byte[] getBytesUtf8(String pArray) { + try { + return pArray.getBytes("UTF8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Decodes a byte[] containing containing characters in the Base64 alphabet. + * + * @param pArray + * A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public byte[] decode(byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + long len = (pArray.length * 3) / 4; + byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + decode(pArray, 0, pArray.length); + decode(pArray, 0, -1); // Notify decoder of EOF. + + // Would be nice to just return buf (like we sometimes do in the encode + // logic), but we have no idea what the line-length was (could even be + // variable). So we cannot determine ahead of time exactly how big an + // array is necessary. Hence the need to construct a 2nd byte array to + // hold the final result: + + byte[] result = new byte[pos]; + readResults(result, 0, result.length); + return result; + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { + return encodeBase64(binaryData, isChunked, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + * @since 1.4 + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe) { + return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @param maxResultSize + * The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe, int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + + long len = getEncodeLength(binaryData, isChunked ? CHUNK_SIZE : 0, isChunked ? CHUNK_SEPARATOR : EMPTY_BYTE_ARRAY); + if (len > maxResultSize) { + throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + + len + + ") than the specified maxium size of " + + maxResultSize); + } + + Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + return b64.encode(binaryData); + } + + /** + * Decodes a Base64 String into octets + * + * @param base64String + * String containing Base64 data + * @return Array containing decoded data. + * @since 1.4 + */ + public static byte[] decodeBase64(String base64String) { + return new Base64().decode(base64String); + } + + /** + * Decodes Base64 data into octets + * + * @param base64Data + * Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + return new Base64().decode(base64Data); + } + + + + /** + * Checks if a byte value is whitespace or not. + * + * @param byteToCheck + * the byte to check + * @return true if byte is whitespace, false otherwise + */ + private static boolean isWhiteSpace(byte byteToCheck) { + switch (byteToCheck) { + case ' ' : + case '\n' : + case '\r' : + case '\t' : + return true; + default : + return false; + } + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A String containing only Base64 character data + * @since 1.4 + */ + public String encodeToString(byte[] pArray) { + return newStringUtf8(encode(pArray)); + } + + private static String newStringUtf8(byte[] encode) { + String str = null; + try { + str = new String(encode, "UTF8"); + } catch (UnsupportedEncodingException ue) { + throw new RuntimeException(ue); + } + return str; + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public byte[] encode(byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + long len = getEncodeLength(pArray, lineLength, lineSeparator); + byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + encode(pArray, 0, pArray.length); + encode(pArray, 0, -1); // Notify encoder of EOF. + // Encoder might have resized, even though it was unnecessary. + if (buffer != buf) { + readResults(buf, 0, buf.length); + } + // In URL-SAFE mode we skip the padding characters, so sometimes our + // final length is a bit smaller. + if (isUrlSafe() && pos < buf.length) { + byte[] smallerBuf = new byte[pos]; + System.arraycopy(buf, 0, smallerBuf, 0, pos); + buf = smallerBuf; + } + return buf; + } + + /** + * Pre-calculates the amount of space needed to base64-encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * @param chunkSize line-length of the output (<= 0 means no chunking) between each + * chunkSeparator (e.g. CRLF). + * @param chunkSeparator the sequence of bytes used to separate chunks of output (e.g. CRLF). + * + * @return amount of space needed to encoded the supplied array. Returns + * a long since a max-len array will require Integer.MAX_VALUE + 33%. + */ + private static long getEncodeLength(byte[] pArray, int chunkSize, byte[] chunkSeparator) { + // base64 always encodes to multiples of 4. + chunkSize = (chunkSize / 4) * 4; + + long len = (pArray.length * 4) / 3; + long mod = len % 4; + if (mod != 0) { + len += 4 - mod; + } + if (chunkSize > 0) { + boolean lenChunksPerfectly = len % chunkSize == 0; + len += (len / chunkSize) * chunkSeparator.length; + if (!lenChunksPerfectly) { + len += chunkSeparator.length; + } + } + return len; + } + + // Implementation of integer encoding used for crypto + /** + * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param pArray + * a byte array containing base64 character data + * @return A BigInteger + * @since 1.4 + */ + public static BigInteger decodeInteger(byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param bigInt + * a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException + * if null is passed in + * @since 1.4 + */ + public static byte[] encodeInteger(BigInteger bigInt) { + if (bigInt == null) { + throw new NullPointerException("encodeInteger called with null parameter"); + } + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Returns a byte-array representation of a BigInteger without sign bit. + * + * @param bigInt + * BigInteger to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(BigInteger bigInt) { + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; + } + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; + } + int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + + /** + * Resets this Base64 object to its initial newly constructed state. + */ + private void reset() { + buffer = null; + pos = 0; + readPos = 0; + currentLinePos = 0; + modulus = 0; + eof = false; + } + + // Getters for use in testing + + int getLineLength() { + return lineLength; + } + + byte[] getLineSeparator() { + return lineSeparator.clone(); + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/Charsets.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/Charsets.java new file mode 100644 index 00000000..bf18c6c7 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/Charsets.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.util; + +import java.nio.charset.Charset; + +/** + * Helps dealing with Charsets. + * + * @since 3.3 + */ +public class Charsets { + + /** + * Returns a charset object for the given charset name. + * + * @param charsetName + * The name of the requested charset; may be a canonical name, an alias, or null. If null, return the + * default charset. + * @return A charset object for the named charset + */ + public static Charset toCharset(String charsetName) { + return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); + } + + /** + * Returns a charset object for the given charset name. + * + * @param charsetName + * The name of the requested charset; may be a canonical name, an alias, or null. + * If null, return the default charset. + * @param defaultCharsetName the charset name to use if the requested charset is null + * + * @return A charset object for the named charset + */ + public static Charset toCharset(String charsetName, String defaultCharsetName) { + return charsetName == null ? Charset.forName(defaultCharsetName) : Charset.forName(charsetName); + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java new file mode 100644 index 00000000..01a0b8a3 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java @@ -0,0 +1,246 @@ +/* + * 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.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.Socket; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.X509ExtendedKeyManager; + +import org.apache.commons.net.io.Util; + +/** + * General KeyManager utilities + *

+ * How to use with a client certificate: + *

+ * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS",
+ *     "/path/to/privatekeystore.jks","storepassword",
+ *     "privatekeyalias", "keypassword");
+ * FTPSClient cl = new FTPSClient();
+ * cl.setKeyManager(km);
+ * cl.connect(...);
+ * 
+ * If using the default store type and the key password is the same as the + * store password, these parameters can be omitted.
+ * If the desired key is the first or only key in the keystore, the keyAlias parameter + * can be omitted, in which case the code becomes: + *
+ * KeyManager km = KeyManagerUtils.createClientKeyManager(
+ *     "/path/to/privatekeystore.jks","storepassword");
+ * FTPSClient cl = new FTPSClient();
+ * cl.setKeyManager(km);
+ * cl.connect(...);
+ * 
+ * + * @since 3.0 + */ +public final class KeyManagerUtils { + + private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType(); + + private KeyManagerUtils(){ + // Not instantiable + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * + * @param ks the keystore to use + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @param keyPass the password of the key to use + * @return the customised KeyManager + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass) + throws GeneralSecurityException + { + ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass); + return new X509KeyManager(cks); + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * + * @param storeType the type of the keyStore, e.g. "JKS" + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @param keyPass the password of the key to use + * @return the customised KeyManager + * @throws GeneralSecurityException if there is a problem creating the keystore + * @throws IOException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager( + String storeType, File storePath, String storePass, String keyAlias, String keyPass) + throws IOException, GeneralSecurityException + { + KeyStore ks = loadStore(storeType, storePath, storePass); + return createClientKeyManager(ks, keyAlias, keyPass); + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * Uses the default store type and assumes the key password is the same as the store password + * + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @return the customised KeyManager + * @throws IOException if there is a problem creating the keystore + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias) + throws IOException, GeneralSecurityException + { + return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass); + } + + /** + * Create a client key manager which returns a particular key. + * Does not handle server keys. + * Uses the default store type and assumes the key password is the same as the store password. + * The key alias is found by searching the keystore for the first private key entry + * + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @return the customised KeyManager + * @throws IOException if there is a problem creating the keystore + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(File storePath, String storePass) + throws IOException, GeneralSecurityException + { + return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass); + } + + private static KeyStore loadStore(String storeType, File storePath, String storePass) + throws KeyStoreException, IOException, GeneralSecurityException { + KeyStore ks = KeyStore.getInstance(storeType); + FileInputStream stream = null; + try { + stream = new FileInputStream(storePath); + ks.load(stream, storePass.toCharArray()); + } finally { + Util.closeQuietly(stream); + } + return ks; + } + + private static String findAlias(KeyStore ks) throws KeyStoreException { + Enumeration e = ks.aliases(); + while(e.hasMoreElements()) { + String entry = e.nextElement(); + if (ks.isKeyEntry(entry)) { + return entry; + } + } + throw new KeyStoreException("Cannot find a private key entry"); + } + + private static class ClientKeyStore { + + private final X509Certificate[] certChain; + private final PrivateKey key; + private final String keyAlias; + + ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException + { + this.keyAlias = keyAlias; + this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray()); + Certificate[] certs = ks.getCertificateChain(this.keyAlias); + X509Certificate[] X509certs = new X509Certificate[certs.length]; + for (int i=0; i < certs.length; i++) { + X509certs[i] = (X509Certificate) certs[i]; + } + this.certChain = X509certs; + } + + final X509Certificate[] getCertificateChain() { + return this.certChain; + } + + final PrivateKey getPrivateKey() { + return this.key; + } + + final String getAlias() { + return this.keyAlias; + } + } + + private static class X509KeyManager extends X509ExtendedKeyManager { + + private final ClientKeyStore keyStore; + + X509KeyManager(final ClientKeyStore keyStore) { + this.keyStore = keyStore; + } + + // Call sequence: 1 + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return keyStore.getAlias(); + } + + // Call sequence: 2 + @Override + public X509Certificate[] getCertificateChain(String alias) { + return keyStore.getCertificateChain(); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return new String[]{ keyStore.getAlias()}; + } + + // Call sequence: 3 + @Override + public PrivateKey getPrivateKey(String alias) { + return keyStore.getPrivateKey(); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return null; + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return null; + } + + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/ListenerList.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/ListenerList.java new file mode 100644 index 00000000..10a3ffce --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/ListenerList.java @@ -0,0 +1,66 @@ +/* + * 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; + +/** + */ + +public class ListenerList implements Serializable, Iterable +{ + private static final long serialVersionUID = -1934227607974228213L; + + 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. + * + * @return an {@link Iterator} for the {@link EventListener} instances + * @since 2.0 + * TODO Check that this is a good defensive strategy + */ + @Override + public Iterator iterator() { + return __listeners.iterator(); + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLContextUtils.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLContextUtils.java new file mode 100644 index 00000000..61c8ddac --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLContextUtils.java @@ -0,0 +1,73 @@ +/* + * 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.IOException; +import java.security.GeneralSecurityException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * General utilities for SSLContext. + * @since 3.0 + */ +public class SSLContextUtils { + + private SSLContextUtils() { + // Not instantiable + } + + /** + * Create and initialise an SSLContext. + * @param protocol the protocol used to instatiate the context + * @param keyManager the key manager, may be {@code null} + * @param trustManager the trust manager, may be {@code null} + * @return the initialised context. + * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs + */ + public static SSLContext createSSLContext(String protocol, KeyManager keyManager, TrustManager trustManager) + throws IOException { + return createSSLContext(protocol, + keyManager == null ? null : new KeyManager[] { keyManager }, + trustManager == null ? null : new TrustManager[] { trustManager }); + } + + /** + * Create and initialise an SSLContext. + * @param protocol the protocol used to instatiate the context + * @param keyManagers the array of key managers, may be {@code null} but array entries must not be {@code null} + * @param trustManagers the array of trust managers, may be {@code null} but array entries must not be {@code null} + * @return the initialised context. + * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs + */ + public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers, TrustManager[] trustManagers) + throws IOException { + SSLContext ctx; + try { + ctx = SSLContext.getInstance(protocol); + ctx.init(keyManagers, trustManagers, /*SecureRandom*/ null); + } catch (GeneralSecurityException e) { + IOException ioe = new IOException("Could not initialize SSL context"); + ioe.initCause(e); + throw ioe; + } + return ctx; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java new file mode 100644 index 00000000..bd8ce840 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLSocketUtils.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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.net.ssl.SSLSocket; + +/** + * General utilities for SSLSocket. + * @since 3.4 + */ +public class SSLSocketUtils { + private SSLSocketUtils() { + // Not instantiable + } + + /** + * Enable the HTTPS endpoint identification algorithm on an SSLSocket. + * @param socket the SSL socket + * @return {@code true} on success (this is only supported on Java 1.7+) + */ + public static boolean enableEndpointNameVerification(SSLSocket socket) { + try { + Class cls = Class.forName("javax.net.ssl.SSLParameters"); + Method setEndpointIdentificationAlgorithm = cls.getDeclaredMethod("setEndpointIdentificationAlgorithm", String.class); + Method getSSLParameters = SSLSocket.class.getDeclaredMethod("getSSLParameters"); + Method setSSLParameters = SSLSocket.class.getDeclaredMethod("setSSLParameters", cls); + if (setEndpointIdentificationAlgorithm != null && getSSLParameters != null && setSSLParameters != null) { + Object sslParams = getSSLParameters.invoke(socket); + if (sslParams != null) { + setEndpointIdentificationAlgorithm.invoke(sslParams, "HTTPS"); + setSSLParameters.invoke(socket, sslParams); + return true; + } + } + } catch (SecurityException e) { // Ignored + } catch (ClassNotFoundException e) { // Ignored + } catch (NoSuchMethodException e) { // Ignored + } catch (IllegalArgumentException e) { // Ignored + } catch (IllegalAccessException e) { // Ignored + } catch (InvocationTargetException e) { // Ignored + } + return false; + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SubnetUtils.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SubnetUtils.java new file mode 100644 index 00000000..4c7ba5c0 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/SubnetUtils.java @@ -0,0 +1,362 @@ +/* + * 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" + * @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; + + /** Whether the broadcast/network address are included in host count */ + private boolean inclusiveHostCount = false; + + + /** + * 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" + * @throws IllegalArgumentException if the parameter is invalid, + * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32 + */ + public SubnetUtils(String cidrNotation) { + calculate(cidrNotation); + } + + /** + * Constructor that takes a dotted decimal address and a dotted decimal mask. + * @param address An IP address, e.g. "192.168.0.1" + * @param mask A dotted decimal netmask e.g. "255.255.0.0" + * @throws IllegalArgumentException if the address or mask is invalid, + * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros + */ + public SubnetUtils(String address, String mask) { + calculate(toCidrNotation(address, mask)); + } + + + /** + * Returns true if the return value of {@link SubnetInfo#getAddressCount()} + * includes the network and broadcast addresses. + * @since 2.2 + * @return true if the hostcount includes the network and broadcast addresses + */ + public boolean isInclusiveHostCount() { + return inclusiveHostCount; + } + + /** + * Set to true if you want the return value of {@link SubnetInfo#getAddressCount()} + * to include the network and broadcast addresses. + * @param inclusiveHostCount true if network and broadcast addresses are to be included + * @since 2.2 + */ + public void setInclusiveHostCount(boolean inclusiveHostCount) { + this.inclusiveHostCount = inclusiveHostCount; + } + + + + /** + * Convenience container for subnet summary information. + * + */ + public final class SubnetInfo { + /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */ + private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; + + private SubnetInfo() {} + + private int netmask() { return netmask; } + private int network() { return network; } + private int address() { return address; } + private int broadcast() { return broadcast; } + + // long versions of the values (as unsigned int) which are more suitable for range checking + private long networkLong() { return network & UNSIGNED_INT_MASK; } + private long broadcastLong(){ return broadcast & UNSIGNED_INT_MASK; } + + private int low() { + return (isInclusiveHostCount() ? network() : + broadcastLong() - networkLong() > 1 ? network() + 1 : 0); + } + + private int high() { + return (isInclusiveHostCount() ? broadcast() : + broadcastLong() - networkLong() > 1 ? broadcast() -1 : 0); + } + + /** + * Returns true if the parameter address is in the + * range of usable endpoint addresses for this subnet. This excludes the + * network and broadcast adresses. + * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" + * @return True if in range, false otherwise + */ + public boolean isInRange(String address) { + return isInRange(toInteger(address)); + } + + /** + * + * @param address the address to check + * @return true if it is in range + * @since 3.4 (made public) + */ + public boolean isInRange(int address) { + long addLong = address & UNSIGNED_INT_MASK; + long lowLong = low() & UNSIGNED_INT_MASK; + long highLong = high() & UNSIGNED_INT_MASK; + return addLong >= lowLong && addLong <= highLong; + } + + 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())); + } + + /** + * Return the low address as a dotted IP address. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getLowAddress() { + return format(toArray(low())); + } + + /** + * Return the high address as a dotted IP address. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getHighAddress() { + return format(toArray(high())); + } + + /** + * Get the count of available addresses. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * @return the count of addresses, may be zero. + * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} + * @deprecated (3.4) use {@link #getAddressCountLong()} instead + */ + @Deprecated + public int getAddressCount() { + long countLong = getAddressCountLong(); + if (countLong > Integer.MAX_VALUE) { + throw new RuntimeException("Count is larger than an integer: " + countLong); + } + // N.B. cannot be negative + return (int)countLong; + } + + /** + * Get the count of available addresses. + * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * @return the count of addresses, may be zero. + * @since 3.4 + */ + public long getAddressCountLong() { + long b = broadcastLong(); + long n = networkLong(); + long count = b - n + (isInclusiveHostCount() ? 1 : -1); + return count < 0 ? 0 : count; + } + + public int asInteger(String address) { + return toInteger(address); + } + + public String getCidrSignature() { + return toCidrNotation( + format(toArray(address())), + format(toArray(netmask())) + ); + } + + public String[] getAllAddresses() { + int ct = getAddressCount(); + String[] addresses = new String[ct]; + if (ct == 0) { + return addresses; + } + for (int add = low(), j=0; add <= high(); ++add, ++j) { + addresses[j] = format(toArray(add)); + } + return addresses; + } + + /** + * {@inheritDoc} + * @since 2.2 + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]") + .append(" Netmask: [").append(getNetmask()).append("]\n") + .append("Network:\t[").append(getNetworkAddress()).append("]\n") + .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n") + .append("First Address:\t[").append(getLowAddress()).append("]\n") + .append("Last Address:\t[").append(getHighAddress()).append("]\n") + .append("# Addresses:\t[").append(getAddressCount()).append("]\n"); + return buf.toString(); + } + } + + /** + * Return a {@link SubnetInfo} instance that contains subnet-specific statistics + * @return new instance + */ + 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); + 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. + * Checks if a value x is in the range [begin,end]. + * Returns x if it is in range, throws an exception otherwise. + */ + private int rangeCheck(int value, int begin, int end) { + if (value >= begin && value <= end) { // (begin,end] + return value; + } + + throw new IllegalArgumentException("Value [" + value + "] not in range ["+begin+","+end+"]"); + } + + /* + * 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)); + } +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java new file mode 100644 index 00000000..c2649f79 --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java @@ -0,0 +1,117 @@ +/* + * 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.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +/** + * TrustManager utilities for generating TrustManagers. + * + * @since 3.0 + */ +public final class TrustManagerUtils +{ + private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{}; + + private static class TrustManager implements X509TrustManager { + + private final boolean checkServerValidity; + + TrustManager(boolean checkServerValidity) { + this.checkServerValidity = checkServerValidity; + } + + /** + * Never generates a CertificateException. + */ + @Override + public void checkClientTrusted(X509Certificate[] certificates, String authType) + { + return; + } + + @Override + public void checkServerTrusted(X509Certificate[] certificates, String authType) + throws CertificateException + { + if (checkServerValidity) { + for (X509Certificate certificate : certificates) + { + certificate.checkValidity(); + } + } + } + + /** + * @return an empty array of certificates + */ + @Override + public X509Certificate[] getAcceptedIssuers() + { + return EMPTY_X509CERTIFICATE_ARRAY; + } + } + + private static final X509TrustManager ACCEPT_ALL=new TrustManager(false); + + private static final X509TrustManager CHECK_SERVER_VALIDITY=new TrustManager(true); + + /** + * Generate a TrustManager that performs no checks. + * + * @return the TrustManager + */ + public static X509TrustManager getAcceptAllTrustManager(){ + return ACCEPT_ALL; + } + + /** + * Generate a TrustManager that checks server certificates for validity, + * but otherwise performs no checks. + * + * @return the validating TrustManager + */ + public static X509TrustManager getValidateServerCertificateTrustManager(){ + return CHECK_SERVER_VALIDITY; + } + + /** + * Return the default TrustManager provided by the JVM. + *

+ * This should be the same as the default used by + * {@link javax.net.ssl.SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom) + * SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} + * when the TrustManager parameter is set to {@code null} + * @param keyStore the KeyStore to use, may be {@code null} + * @return the default TrustManager + * @throws GeneralSecurityException if an error occurs + */ + public static X509TrustManager getDefaultTrustManager(KeyStore keyStore) throws GeneralSecurityException { + String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory instance = TrustManagerFactory.getInstance(defaultAlgorithm); + instance.init(keyStore); + return (X509TrustManager) instance.getTrustManagers()[0]; + } + +} diff --git a/AriaFtpPlug/src/main/java/org/apache/commons/net/util/package-info.java b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/package-info.java new file mode 100644 index 00000000..5515190e --- /dev/null +++ b/AriaFtpPlug/src/main/java/org/apache/commons/net/util/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Utility classes + */ +package org.apache.commons.net.util; \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore index 796b96d1..fcd3a08d 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,2 @@ /build +*.log \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index c0683d76..e1d8afc6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,5 +43,6 @@ dependencies { compile 'com.arialyy.frame:MVVM2:2.2.0' compile project(':Aria') compile project(':AriaCompiler') - +// compile 'com.arialyy.aria:aria-core:3.2.11' +// annotationProcessor 'com.arialyy.aria:aria-compiler:3.2.11' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4443482d..3848e920 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,7 +24,6 @@ - @@ -32,6 +31,10 @@ + + + + diff --git a/app/src/main/assets/aria_config.xml b/app/src/main/assets/aria_config.xml index 43a593a2..621b0464 100644 --- a/app/src/main/assets/aria_config.xml +++ b/app/src/main/assets/aria_config.xml @@ -5,10 +5,10 @@ - + - + @@ -37,6 +37,8 @@ + + diff --git a/app/src/main/java/com/arialyy/simple/MainActivity.java b/app/src/main/java/com/arialyy/simple/MainActivity.java index 5867a402..8a8a7cd6 100644 --- a/app/src/main/java/com/arialyy/simple/MainActivity.java +++ b/app/src/main/java/com/arialyy/simple/MainActivity.java @@ -23,9 +23,11 @@ import butterknife.OnClick; import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.databinding.ActivityMainBinding; import com.arialyy.simple.download.DownloadActivity; +import com.arialyy.simple.download.FtpDownloadActivity; import com.arialyy.simple.download.group.DownloadGroupActivity; -import com.arialyy.simple.test.TestMutilTaskSysDownload; -import com.arialyy.simple.upload.UploadActivity; +import com.arialyy.simple.download.group.FTPDirDownloadActivity; +import com.arialyy.simple.upload.FtpUploadActivity; +import com.arialyy.simple.upload.HttpUploadActivity; /** * Created by Aria.Lao on 2017/3/1. @@ -43,20 +45,29 @@ public class MainActivity extends BaseActivity { return R.layout.activity_main; } - - @OnClick({R.id.download, R.id.upload, R.id.download_task_group}) - public void funcation(View view){ - switch (view.getId()){ + @OnClick({ + R.id.download, R.id.upload, R.id.download_task_group, R.id.ftp_download, + R.id.ftp_dir_download, R.id.ftp_upload + }) public void funcation(View view) { + switch (view.getId()) { case R.id.download: startActivity(new Intent(this, DownloadActivity.class)); break; case R.id.upload: - startActivity(new Intent(this, UploadActivity.class)); + startActivity(new Intent(this, HttpUploadActivity.class)); break; case R.id.download_task_group: startActivity(new Intent(this, DownloadGroupActivity.class)); break; + case R.id.ftp_download: + startActivity(new Intent(this, FtpDownloadActivity.class)); + break; + case R.id.ftp_dir_download: + startActivity(new Intent(this, FTPDirDownloadActivity.class)); + break; + case R.id.ftp_upload: + startActivity(new Intent(this, FtpUploadActivity.class)); + break; } - } } diff --git a/app/src/main/java/com/arialyy/simple/download/DownloadModule.java b/app/src/main/java/com/arialyy/simple/download/DownloadModule.java index faef1cf3..c4e84c1a 100644 --- a/app/src/main/java/com/arialyy/simple/download/DownloadModule.java +++ b/app/src/main/java/com/arialyy/simple/download/DownloadModule.java @@ -76,7 +76,7 @@ public class DownloadModule extends BaseModule { String path = Environment.getExternalStorageDirectory() + "/download/" + name + ".apk"; DownloadEntity entity = new DownloadEntity(); entity.setFileName(name); - entity.setDownloadUrl(downloadUrl); + entity.setUrl(downloadUrl); entity.setDownloadPath(path); return entity; } diff --git a/app/src/main/java/com/arialyy/simple/download/FtpDownloadActivity.java b/app/src/main/java/com/arialyy/simple/download/FtpDownloadActivity.java new file mode 100644 index 00000000..20e22607 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/download/FtpDownloadActivity.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed 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 com.arialyy.simple.download; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import com.arialyy.annotations.Download; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadTask; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.frame.util.show.L; +import com.arialyy.frame.util.show.T; +import com.arialyy.simple.R; +import com.arialyy.simple.base.BaseActivity; +import com.arialyy.simple.databinding.ActivityFtpDownloadBinding; +import java.io.File; + +/** + * Created by Aria.Lao on 2017/7/25. + * Ftp下载测试 + */ +public class FtpDownloadActivity extends BaseActivity { + //private final String URL = "ftp://172.18.104.129:21/haha/large.rar"; + //private final String URL = "ftp://172.18.104.129:21/haha/large.rar"; + private final String URL = "ftp://172.18.104.129:21/haha/很大的文件_v100.rar"; + + @Override protected void init(Bundle savedInstanceState) { + super.init(savedInstanceState); + setTitle("FTP文件下载"); + Aria.download(this).register(); + DownloadEntity entity = Aria.download(this).getDownloadEntity(URL); + if (entity != null) { + getBinding().setFileSize(entity.getConvertFileSize()); + if (entity.getFileSize() == 0) { + getBinding().setProgress(0); + } else { + getBinding().setProgress(entity.isComplete() ? 100 + : (int) (entity.getCurrentProgress() * 100 / entity.getFileSize())); + } + } + } + + public void onClick(View view) { + switch (view.getId()) { + case R.id.start: + Aria.download(this) + .loadFtp(URL) + .login("lao", "123456") + .setDownloadPath("/mnt/sdcard/") + .start(); + break; + case R.id.stop: + Aria.download(this).loadFtp(URL).stop(); + break; + case R.id.cancel: + Aria.download(this).loadFtp(URL).cancel(); + break; + } + } + + @Download.onPre() protected void onPre(DownloadTask task) { + L.d(TAG, "ftp pre"); + } + + @Download.onTaskPre() protected void onTaskPre(DownloadTask task) { + L.d(TAG, "ftp task pre"); + getBinding().setFileSize(task.getConvertFileSize()); + } + + @Download.onTaskStart() void taskStart(DownloadTask task) { + L.d(TAG, "ftp task start"); + } + + @Download.onTaskRunning() protected void running(DownloadTask task) { + getBinding().setProgress(task.getPercent()); + getBinding().setSpeed(task.getConvertSpeed()); + } + + @Download.onTaskResume() void taskResume(DownloadTask task) { + L.d(TAG, "ftp task resume"); + } + + @Download.onTaskStop() void taskStop(DownloadTask task) { + L.d(TAG, "ftp task stop"); + getBinding().setSpeed(""); + } + + @Download.onTaskCancel() void taskCancel(DownloadTask task) { + getBinding().setSpeed(""); + getBinding().setProgress(0); + } + + @Download.onTaskFail() void taskFail(DownloadTask task) { + L.d(TAG, "ftp task fail"); + } + + @Download.onTaskComplete() void taskComplete(DownloadTask task) { + getBinding().setSpeed(""); + getBinding().setProgress(100); + Log.d(TAG, "md5 ==> " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); + T.showShort(this, "FTP下载完成"); + } + + @Override protected int setLayoutId() { + return R.layout.activity_ftp_download; + } +} diff --git a/app/src/main/java/com/arialyy/simple/download/HighestPriorityActivity.java b/app/src/main/java/com/arialyy/simple/download/HighestPriorityActivity.java index 611861dd..73cb28c4 100644 --- a/app/src/main/java/com/arialyy/simple/download/HighestPriorityActivity.java +++ b/app/src/main/java/com/arialyy/simple/download/HighestPriorityActivity.java @@ -87,9 +87,9 @@ public class HighestPriorityActivity extends BaseActivity temp = Aria.download(this).getSimpleTaskList(); if (temp != null && !temp.isEmpty()) { for (DownloadEntity entity : temp) { - if (entity.getDownloadUrl().equals(DOWNLOAD_URL)) continue; + if (entity.getUrl().equals(DOWNLOAD_URL)) continue; mData.add(entity); - mRecord.add(entity.getDownloadUrl()); + mRecord.add(entity.getUrl()); } } mAdapter = new DownloadAdapter(this, mData); @@ -112,7 +112,7 @@ public class HighestPriorityActivity extends BaseActivity temp = getModule(DownloadModule.class).getHighestTestList(); for (DownloadEntity entity : temp) { - String url = entity.getDownloadUrl(); + String url = entity.getUrl(); if (mRecord.contains(url)) { continue; } diff --git a/app/src/main/java/com/arialyy/simple/download/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/download/SingleTaskActivity.java index 67825479..4bbc0e08 100644 --- a/app/src/main/java/com/arialyy/simple/download/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/download/SingleTaskActivity.java @@ -24,7 +24,6 @@ import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.RadioGroup; -import android.widget.TextView; import android.widget.Toast; import butterknife.Bind; import com.arialyy.annotations.Download; @@ -83,7 +82,7 @@ public class SingleTaskActivity extends BaseActivity { msg = "一些小知识点:\n" + "1、你可以在注解中增加链接,用于指定被注解的方法只能被特定的下载任务回调,以防止progress乱跳\n" + "2、当遇到网络慢的情况时,你可以先使用onPre()更新UI界面,待连接成功时,再在onTaskPre()获取完整的task数据,然后给UI界面设置正确的数据\n" - + "3、你可以在界面初始化时通过Aria.download(this).load(DOWNLOAD_URL).getPercent()等方法快速获取相关任务的一些数据"; + + "3、你可以在界面初始化时通过Aria.download(this).load(URL).getPercent()等方法快速获取相关任务的一些数据"; showMsgDialog("tip", msg); break; case R.id.speed_0: @@ -188,24 +187,12 @@ public class SingleTaskActivity extends BaseActivity { public void onClick(View view) { switch (view.getId()) { case R.id.start: - //String text = ((TextView) view).getText().toString(); - //if (text.equals("重新开始?") || text.equals("开始")) { - // Aria.download(this) - // .load(DOWNLOAD_URL) - // .addHeader("groupName", "value") - // .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") - // .setFileName("hehe.apk") - // .start(); - //} else if (text.equals("恢复")) { - // Aria.download(this).load(DOWNLOAD_URL).resume(); - //} Aria.download(this) .load(DOWNLOAD_URL) .addHeader("groupName", "value") .setExtendField("str___ggggggg") .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/gggg.apk") .start(); - break; case R.id.stop: Aria.download(this).load(DOWNLOAD_URL).stop(); diff --git a/app/src/main/java/com/arialyy/simple/download/fragment_download/DownloadFragment.java b/app/src/main/java/com/arialyy/simple/download/fragment_download/DownloadFragment.java index 8e34589b..5fd45147 100644 --- a/app/src/main/java/com/arialyy/simple/download/fragment_download/DownloadFragment.java +++ b/app/src/main/java/com/arialyy/simple/download/fragment_download/DownloadFragment.java @@ -42,7 +42,7 @@ public class DownloadFragment extends AbsFragment { @Bind(R.id.stop) Button mStop; @Bind(R.id.cancel) Button mCancel; - private static final String DOWNLOAD_URL = "http://static.ilongyuan.cn/rayark/RayarkFZ_2.0.7.apk"; + private static final String DOWNLOAD_URL = "https://res5.d.cn/2137e42d610b3488d9420c6421529386eee5bdbfd9be1fafe0a05d6dabaec8c156ddbd00581055bbaeac03904fb63310e80010680235d16bd4c040b50096a0c20dd1c4b0854529a1.apk"; @Override protected void init(Bundle savedInstanceState) { if (Aria.download(this).taskExists(DOWNLOAD_URL)) { @@ -65,7 +65,7 @@ public class DownloadFragment extends AbsFragment { case R.id.start: Aria.download(this) .load(DOWNLOAD_URL) - .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/兰空VOEZ.apk") + .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/王者军团.apk") .start(); break; case R.id.stop: @@ -84,13 +84,13 @@ public class DownloadFragment extends AbsFragment { @Download.onTaskStop public void onTaskStop(DownloadTask task) { setBtState(true); - getBinding().setSpeed("0.0kb/s"); + getBinding().setSpeed(""); } @Download.onTaskCancel public void onTaskCancel(DownloadTask task) { setBtState(true); getBinding().setProgress(0); - getBinding().setSpeed("0.0kb/s"); + getBinding().setSpeed(""); } @Download.onTaskRunning public void onTaskRunning(DownloadTask task) { diff --git a/app/src/main/java/com/arialyy/simple/download/fragment_download/FragmentActivity.java b/app/src/main/java/com/arialyy/simple/download/fragment_download/FragmentActivity.java index 93ab023f..adb2fc96 100644 --- a/app/src/main/java/com/arialyy/simple/download/fragment_download/FragmentActivity.java +++ b/app/src/main/java/com/arialyy/simple/download/fragment_download/FragmentActivity.java @@ -17,6 +17,7 @@ package com.arialyy.simple.download.fragment_download; import android.os.Bundle; +import com.arialyy.aria.core.Aria; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.databinding.FragmentDownloadBinding; @@ -33,5 +34,6 @@ public class FragmentActivity extends BaseActivity { @Override protected void init(Bundle savedInstanceState) { super.init(savedInstanceState); setTile("Fragment 中使用"); + Aria.download(this).register(); } } diff --git a/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java b/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java index 4226ed3a..e1c6bcfb 100644 --- a/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java +++ b/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java @@ -40,9 +40,6 @@ import java.util.List; */ public class DownloadGroupActivity extends BaseActivity { - @Bind(R.id.start) Button mStart; - @Bind(R.id.stop) Button mStop; - @Bind(R.id.cancel) Button mCancel; @Bind(R.id.child_list) SubStateLinearLayout mChildList; List mUrls; @@ -102,6 +99,9 @@ public class DownloadGroupActivity extends BaseActivity { + private static final String dir = "ftp://172.18.104.129:21/haha/"; + + @Bind(R.id.child_list) SubStateLinearLayout mChildList; + + @Override protected void init(Bundle savedInstanceState) { + super.init(savedInstanceState); + Aria.download(this).register(); + setTitle("FTP文件夹下载"); + DownloadGroupTaskEntity entity = Aria.download(this).getDownloadGroupTask(dir); + if (entity != null && entity.getEntity() != null) { + DownloadGroupEntity groupEntity = entity.getEntity(); + mChildList.addData(groupEntity.getSubTask()); + getBinding().setFileSize(groupEntity.getConvertFileSize()); + if (groupEntity.getFileSize() == 0) { + getBinding().setProgress(0); + } else { + getBinding().setProgress(groupEntity.isComplete() ? 100 + : (int) (groupEntity.getCurrentProgress() * 100 / groupEntity.getFileSize())); + } + } + } + + @Override protected int setLayoutId() { + return R.layout.activity_download_group; + } + + public void onClick(View view) { + switch (view.getId()) { + case R.id.start: + Aria.download(this) + .loadFtpDir(dir) + .setDownloadDirPath( + Environment.getExternalStorageDirectory().getPath() + "/Download/ftp_dir") + .setGroupAlias("ftp文件夹下载") + //.setSubTaskFileName(getModule(GroupModule.class).getSubName()) + .login("lao", "123456") + .start(); + break; + case R.id.stop: + Aria.download(this).loadFtpDir(dir).stop(); + break; + case R.id.cancel: + Aria.download(this).loadFtpDir(dir).cancel(); + break; + } + } + + @DownloadGroup.onPre() protected void onPre(DownloadGroupTask task) { + L.d(TAG, "group pre"); + } + + @DownloadGroup.onTaskPre() protected void onTaskPre(DownloadGroupTask task) { + if (mChildList.getSubData().size() <= 0) { + mChildList.addData(task.getEntity().getSubTask()); + } + L.d(TAG, "group task pre"); + getBinding().setFileSize(task.getConvertFileSize()); + } + + @DownloadGroup.onTaskStart() void taskStart(DownloadGroupTask task) { + L.d(TAG, "group task start"); + } + + @DownloadGroup.onTaskRunning() protected void running(DownloadGroupTask task) { + getBinding().setProgress(task.getPercent()); + getBinding().setSpeed(task.getConvertSpeed()); + mChildList.updateChildProgress(task.getEntity().getSubTask()); + } + + @DownloadGroup.onTaskResume() void taskResume(DownloadGroupTask task) { + L.d(TAG, "group task resume"); + } + + @DownloadGroup.onTaskStop() void taskStop(DownloadGroupTask task) { + L.d(TAG, "group task stop"); + getBinding().setSpeed(""); + } + + @DownloadGroup.onTaskCancel() void taskCancel(DownloadGroupTask task) { + getBinding().setSpeed(""); + getBinding().setProgress(0); + } + + @DownloadGroup.onTaskFail() void taskFail(DownloadGroupTask task) { + L.d(TAG, "group task fail"); + } + + @DownloadGroup.onTaskComplete() void taskComplete(DownloadGroupTask task) { + getBinding().setProgress(100); + mChildList.updateChildProgress(task.getEntity().getSubTask()); + T.showShort(this, "任务组下载完成"); + L.d(TAG, "任务组下载完成"); + } +} diff --git a/app/src/main/java/com/arialyy/simple/download/multi_download/DownloadAdapter.java b/app/src/main/java/com/arialyy/simple/download/multi_download/DownloadAdapter.java index 676d2bf8..06a2e572 100644 --- a/app/src/main/java/com/arialyy/simple/download/multi_download/DownloadAdapter.java +++ b/app/src/main/java/com/arialyy/simple/download/multi_download/DownloadAdapter.java @@ -58,7 +58,7 @@ public class DownloadAdapter extends AbsRVAdapter { + private final String FILE_PATH = "/mnt/sdcard/gggg.apk"; + private final String URL = "ftp://172.18.104.129:21/upload/"; + + @Override protected void init(Bundle savedInstanceState) { + setTile("FTP 文件上传"); + super.init(savedInstanceState); + Aria.upload(this).register(); + UploadEntity entity = Aria.upload(this).getUploadEntity(FILE_PATH); + if (entity != null) { + getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize())); + getBinding().setProgress(entity.isComplete() ? 100 + : (int) (entity.getCurrentProgress() * 100 / entity.getFileSize())); + } + } + + @Override protected int setLayoutId() { + return R.layout.activity_ftp_upload; + } + + public void onClick(View view) { + switch (view.getId()) { + case R.id.start: + Aria.upload(this).loadFtp(FILE_PATH).setUploadUrl(URL).login("lao", "123456").start(); + break; + case R.id.stop: + Aria.upload(this).loadFtp(FILE_PATH).stop(); + break; + case R.id.cancel: + Aria.upload(this).loadFtp(FILE_PATH).cancel(); + break; + } + } + + @Upload.onPre public void onPre(UploadTask task) { + getBinding().setFileSize(task.getConvertFileSize()); + } + + @Upload.onTaskStart public void taskStart(UploadTask task) { + Log.d(TAG, "开始上传"); + } + + @Upload.onTaskResume public void taskResume(UploadTask task) { + Log.d(TAG, "恢复上传"); + } + + @Upload.onTaskStop public void taskStop(UploadTask task) { + getBinding().setSpeed(""); + Log.d(TAG, "停止上传"); + } + + @Upload.onTaskCancel public void taskCancel(UploadTask task) { + getBinding().setSpeed(""); + getBinding().setFileSize(""); + getBinding().setProgress(0); + Log.d(TAG, "删除任务"); + } + + @Upload.onTaskRunning public void taskRunning(UploadTask task) { + getBinding().setProgress(task.getPercent()); + getBinding().setSpeed(task.getConvertSpeed()); + } + + @Upload.onTaskComplete public void taskComplete(UploadTask task) { + getBinding().setProgress(100); + getBinding().setSpeed(""); + T.showShort(this, "上传完成"); + } +} diff --git a/app/src/main/java/com/arialyy/simple/upload/UploadActivity.java b/app/src/main/java/com/arialyy/simple/upload/HttpUploadActivity.java similarity index 53% rename from app/src/main/java/com/arialyy/simple/upload/UploadActivity.java rename to app/src/main/java/com/arialyy/simple/upload/HttpUploadActivity.java index 70373fbe..e8a1b419 100644 --- a/app/src/main/java/com/arialyy/simple/upload/UploadActivity.java +++ b/app/src/main/java/com/arialyy/simple/upload/HttpUploadActivity.java @@ -17,67 +17,32 @@ package com.arialyy.simple.upload; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import butterknife.Bind; import butterknife.OnClick; import com.arialyy.annotations.Upload; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.upload.UploadTask; -import com.arialyy.frame.util.FileUtil; import com.arialyy.frame.util.show.L; -import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; -import com.arialyy.simple.databinding.ActivityUploadMeanBinding; +import com.arialyy.simple.databinding.ActivityUploadBinding; import com.arialyy.simple.widget.HorizontalProgressBarWithNumber; -import java.lang.ref.WeakReference; /** * Created by Aria.Lao on 2017/2/9. */ -public class UploadActivity extends BaseActivity { - private static final String TAG = "UploadActivity"; +public class HttpUploadActivity extends BaseActivity { + private static final String TAG = "HttpUploadActivity"; @Bind(R.id.pb) HorizontalProgressBarWithNumber mPb; - private static final int START = 0; - private static final int STOP = 1; - private static final int CANCEL = 2; - private static final int RUNNING = 3; - private static final int COMPLETE = 4; - private static final String FILE_PATH = "/sdcard/Download/test.zip"; - - private Handler mHandler = new Handler() { - @Override public void handleMessage(Message msg) { - super.handleMessage(msg); - UploadTask task = (UploadTask) msg.obj; - switch (msg.what) { - case START: - getBinding().setFileSize(FileUtil.formatFileSize(task.getFileSize())); - break; - case STOP: - mPb.setProgress(0); - break; - case CANCEL: - mPb.setProgress(0); - break; - case RUNNING: - int p = (int) (task.getCurrentProgress() * 100 / task.getFileSize()); - mPb.setProgress(p); - break; - case COMPLETE: - T.showShort(UploadActivity.this, "上传完成"); - mPb.setProgress(100); - break; - } - } - }; + private static final String FILE_PATH = "/sdcard/large.rar"; @Override protected int setLayoutId() { - return R.layout.activity_upload_mean; + return R.layout.activity_upload; } @Override protected void init(Bundle savedInstanceState) { + setTile("HTTP 上传"); super.init(savedInstanceState); Aria.upload(this).register(); } @@ -101,27 +66,31 @@ public class UploadActivity extends BaseActivity { @Upload.onPre public void onPre(UploadTask task) { } - @Upload.onTaskPre public void taskPre(UploadTask task) { - L.d(TAG, "fileSize = " + task.getConvertFileSize()); - } - @Upload.onTaskStart public void taskStart(UploadTask task) { - mHandler.obtainMessage(START, task).sendToTarget(); + L.d(TAG, "upload start"); + getBinding().setFileSize(task.getConvertFileSize()); } @Upload.onTaskStop public void taskStop(UploadTask task) { - mHandler.obtainMessage(STOP, task).sendToTarget(); + L.d(TAG, "upload stop"); + getBinding().setSpeed(""); + getBinding().setProgress(0); } @Upload.onTaskCancel public void taskCancel(UploadTask task) { - mHandler.obtainMessage(CANCEL, task).sendToTarget(); + L.d(TAG, "upload cancel"); + getBinding().setSpeed(""); + getBinding().setProgress(0); } @Upload.onTaskRunning public void taskRunning(UploadTask task) { - mHandler.obtainMessage(RUNNING, task).sendToTarget(); + getBinding().setSpeed(task.getConvertSpeed()); + getBinding().setProgress(task.getPercent()); } @Upload.onTaskComplete public void taskComplete(UploadTask task) { - mHandler.obtainMessage(COMPLETE, task).sendToTarget(); + L.d(TAG, "上传完成"); + getBinding().setSpeed(""); + getBinding().setProgress(100); } } diff --git a/app/src/main/res/layout/activity_ftp_download.xml b/app/src/main/res/layout/activity_ftp_download.xml new file mode 100644 index 00000000..46ac3b31 --- /dev/null +++ b/app/src/main/res/layout/activity_ftp_download.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_ftp_upload.xml b/app/src/main/res/layout/activity_ftp_upload.xml new file mode 100644 index 00000000..46ac3b31 --- /dev/null +++ b/app/src/main/res/layout/activity_ftp_upload.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 17407578..177b1590 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -13,7 +13,7 @@ android:id="@+id/download" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="下载" + android:text="http 下载" style="?buttonBarButtonStyle" /> @@ -21,7 +21,7 @@ android:id="@+id/upload" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="上传" + android:text="http 上传" style="?buttonBarButtonStyle" /> @@ -29,7 +29,31 @@ android:id="@+id/download_task_group" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="下载任务组" + android:text="http 下载任务组" + style="?buttonBarButtonStyle" + /> + +