commit
						0d4f29d921
					
				| @ -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<ENTITY extends AbsNormalEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>> | ||||
|     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<AbsThreadTask> 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<Object> 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<TASK_ENTITY> 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<TASK_ENTITY> 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<TASK_ENTITY> config); | ||||
| 
 | ||||
|   protected void failDownload(String errorMsg) { | ||||
|     closeTimer(); | ||||
|     Log.e(TAG, errorMsg); | ||||
|     mConstance.isRunning = false; | ||||
|     mListener.onFail(); | ||||
|   } | ||||
| } | ||||
| @ -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<ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>> | ||||
|     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<TASK_ENTITY> mConfig; | ||||
|   protected ENTITY mEntity; | ||||
|   protected TASK_ENTITY mTaskEntity; | ||||
|   /** | ||||
|    * FTP 服务器编码 | ||||
|    */ | ||||
|   public static String SERVER_CHARSET = "ISO-8859-1"; | ||||
| 
 | ||||
|   protected AbsThreadTask(StateConstance constance, IEventListener listener, | ||||
|       SubThreadConfig<TASK_ENTITY> 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); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| package com.arialyy.aria.core.common; | ||||
| 
 | ||||
| import com.arialyy.aria.core.inf.AbsTaskEntity; | ||||
| import java.io.File; | ||||
| 
 | ||||
| /** | ||||
|  * 子线程下载信息类 | ||||
|  */ | ||||
| public class SubThreadConfig<TASK_ENTITY extends AbsTaskEntity> { | ||||
|   //线程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; | ||||
| } | ||||
| @ -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<TARGET extends AbsTarget, TASK_ENTITY extends AbsTaskEntity> | ||||
|     extends AbsDownloadTarget<TARGET, DownloadGroupEntity, TASK_ENTITY> { | ||||
| 
 | ||||
|   List<String> mUrls = new ArrayList<>(); | ||||
|   String mGroupName; | ||||
|   /** | ||||
|    * 子任务文件名 | ||||
|    */ | ||||
|   private List<String> 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" | ||||
|    * <pre> | ||||
|    *   {@code | ||||
|    *      + mnt | ||||
|    *        + sdcard | ||||
|    *          + download | ||||
|    *            + group_test | ||||
|    *              - task1.apk | ||||
|    *              - task2.apk | ||||
|    *              - task3.apk | ||||
|    *              .... | ||||
|    * | ||||
|    *   } | ||||
|    * </pre> | ||||
|    * | ||||
|    * @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<DownloadEntity> 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<String> subTaskFileName) { | ||||
|     if (subTaskFileName == null || subTaskFileName.isEmpty()) return (TARGET) this; | ||||
|     mSubTaskFileName.addAll(subTaskFileName); | ||||
|     if (mUrls.size() != subTaskFileName.size()) { | ||||
|       throw new IllegalArgumentException("下载链接数必须要和保存路径的数量一致"); | ||||
|     } | ||||
|     if (isSetDirPathed) { | ||||
|       List<DownloadEntity> 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<DownloadEntity> createSubTask() { | ||||
|     List<DownloadEntity> 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; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -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<FtpDirDownloadTarget, DownloadGroupTaskEntity> { | ||||
|   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; | ||||
|   } | ||||
| } | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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<ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>> | ||||
|     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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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<String, DownloadTaskEntity> mExeMap = new HashMap<>(); | ||||
| 
 | ||||
|   /** | ||||
|    * 下载失败的映射表,key为下载地址 | ||||
|    */ | ||||
|   Map<String, DownloadTaskEntity> mFailMap = new HashMap<>(); | ||||
| 
 | ||||
|   /** | ||||
|    * 下载器映射表,key为下载地址 | ||||
|    */ | ||||
|   private Map<String, Downloader> mDownloaderMap = new HashMap<>(); | ||||
| 
 | ||||
|   /** | ||||
|    * 该任务组对应的所有任务 | ||||
|    */ | ||||
|   private Map<String, DownloadTaskEntity> 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<DownloadTaskEntity> 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<String> 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<DownloadTaskEntity> 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<String> 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) { | ||||
| 
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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; | ||||
| } | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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<String> keys = mExeMap.keySet(); | ||||
|     for (String key : keys) { | ||||
|       DownloadTaskEntity taskEntity = mExeMap.get(key); | ||||
|       if (taskEntity != null) { | ||||
|         startChildDownload(taskEntity); | ||||
|         i++; | ||||
|       } | ||||
|     } | ||||
|     if (i == mExeMap.size()) startRunningFlow(); | ||||
|   } | ||||
| } | ||||
| @ -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<DownloadGroupEntity, DownloadGroupTaskEntity> { | ||||
|   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<String>()); | ||||
|     } | ||||
|     if (mEntity.getSubTask() == null) { | ||||
|       mEntity.setSubTasks(new ArrayList<DownloadEntity>()); | ||||
|     } | ||||
|     mEntity.getSubTask().add(entity); | ||||
|   } | ||||
| } | ||||
| @ -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() { | ||||
| 
 | ||||
|   } | ||||
| } | ||||
| @ -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<DownloadEntity, DownloadTaskEntity> { | ||||
| 
 | ||||
|   FtpFileInfoThread(DownloadTaskEntity taskEntity, OnFileInfoCallback callback) { | ||||
|     super(taskEntity, callback); | ||||
|   } | ||||
| } | ||||
| @ -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<DownloadEntity, DownloadTaskEntity> { | ||||
|   private final String TAG = "FtpThreadTask"; | ||||
| 
 | ||||
|   FtpThreadTask(StateConstance constance, IDownloadListener listener, | ||||
|       SubThreadConfig<DownloadTaskEntity> 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(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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<DownloadEntity, DownloadTaskEntity> { | ||||
|   private final String TAG = "HttpThreadTask"; | ||||
| 
 | ||||
|   HttpThreadTask(StateConstance constance, IDownloadListener listener, | ||||
|       SubThreadConfig<DownloadTaskEntity> 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(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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); | ||||
| } | ||||
| @ -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); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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<ENTITY extends AbsEntity, TASK extends AbsTask<ENTITY>> | ||||
|     implements IUploadListener { | ||||
|   private WeakReference<Handler> 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(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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<FtpUploadTarget, UploadEntity, UploadTaskEntity> { | ||||
|   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; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,185 @@ | ||||
| /* | ||||
|  * 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 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.inf.IEventListener; | ||||
| import com.arialyy.aria.core.upload.UploadEntity; | ||||
| import com.arialyy.aria.core.upload.UploadTaskEntity; | ||||
| import com.arialyy.aria.util.BufferedRandomAccessFile; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| 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/28. | ||||
|  * FTP 单线程上传任务,需要FTP 服务器给用户打开删除和读入IO的权限 | ||||
|  */ | ||||
| class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> { | ||||
|   private final String TAG = "FtpThreadTask"; | ||||
|   private String dir, remotePath, charSet; | ||||
| 
 | ||||
|   FtpThreadTask(StateConstance constance, IEventListener listener, | ||||
|       SubThreadConfig<UploadTaskEntity> 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; | ||||
|   } | ||||
| } | ||||
| @ -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); | ||||
|   } | ||||
| } | ||||
| @ -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<UploadEntity, UploadTaskEntity> { | ||||
| 
 | ||||
|   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<UploadTaskEntity> 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; | ||||
|   } | ||||
| } | ||||
| @ -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() }. | ||||
|      * <p> | ||||
|      * _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() }. | ||||
|      * <p> | ||||
|      * _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() }. | ||||
|      * <p> | ||||
|      * _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; | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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. | ||||
|  * <p> | ||||
|  * 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); | ||||
| 
 | ||||
| } | ||||
| @ -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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @ -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. | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * <p> | ||||
|      * Subclasses overriding this method should start by calling | ||||
|      * <code> super._connectAction_() </code> 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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * 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 <a href="https://issues.apache.org/jira/browse/NET-350">NET-350</a> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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_ }). | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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()}. | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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()} | ||||
|      * <p> | ||||
|      * @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()} | ||||
|      * <p> | ||||
|      * @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()} | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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. | ||||
|      * <p> | ||||
|      * @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). | ||||
|      * <p> | ||||
|      * @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. | ||||
|      */ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -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 | ||||
|      * <code>config</code> are somehow inadequate to configure the | ||||
|      * Configurable object. | ||||
|      */ | ||||
|     public void configure(FTPClientConfig config); | ||||
| } | ||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						| @ -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; | ||||
| 
 | ||||
| /** | ||||
|  * <p> | ||||
|  * 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. | ||||
|  * </p><p> | ||||
|  * 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. | ||||
|  * </p><p> | ||||
|  * 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. | ||||
|  * </p> | ||||
|  * <h3>Examples of use of FTPClientConfig</h3> | ||||
|  * Use cases: | ||||
|  * You are trying to access a server that | ||||
|  * <ul> | ||||
|  * <li>lists files with timestamps that use month names in languages other | ||||
|  * than English</li> | ||||
|  * <li>lists files with timestamps that use date formats other | ||||
|  * than the American English "standard" <code>MM dd yyyy</code></li> | ||||
|  * <li>is in different timezone and you need accurate timestamps for | ||||
|  * dependency checking as in Ant</li> | ||||
|  * </ul> | ||||
|  * <p> | ||||
|  * Unpaged (whole list) access on a UNIX server that uses French month names | ||||
|  * but uses the "standard" <code>MMM d yyyy</code> date formatting | ||||
|  * <pre> | ||||
|  *    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); | ||||
|  * </pre> | ||||
|  * <p> | ||||
|  * 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. | ||||
|  * <pre> | ||||
|  *    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.
 | ||||
|  *    } | ||||
|  * </pre> | ||||
|  * <p> | ||||
|  * 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" <code>MMM d yyyy</code> date formatting | ||||
|  * <pre> | ||||
|  *    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); | ||||
|  * </pre> | ||||
|  * <p> | ||||
|  * 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). | ||||
|  * <pre> | ||||
|  *    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); | ||||
|  * </pre> | ||||
|  * 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. | ||||
|  * <pre> | ||||
|  *    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); | ||||
|  * </pre> | ||||
|  * | ||||
|  * @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<String, Object> LANGUAGE_CODE_MAP = new TreeMap<String, Object>(); | ||||
|     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 <code>FTPClientConfig.SYST_*</code> codes | ||||
|      * or else the fully qualified class name of a parser implementing both | ||||
|      * the <code>FTPFileEntryParser</code> and <code>Configurable</code> | ||||
|      * 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; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * getter for the {@link  #setShortMonthNames(String)  shortMonthNames} | ||||
|      * property. | ||||
|      * </p> | ||||
|      * @return Returns the shortMonthNames. | ||||
|      */ | ||||
|     public String getShortMonthNames() { | ||||
|         return shortMonthNames; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * getter for the {@link  #setServerLanguageCode(String)  serverLanguageCode} property. | ||||
|      * </p> | ||||
|      * @return Returns the serverLanguageCode property. | ||||
|      */ | ||||
|     public String getServerLanguageCode() { | ||||
|         return serverLanguageCode; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * getter for the {@link  #setLenientFutureDates(boolean)  lenientFutureDates} property. | ||||
|      * </p> | ||||
|      * @return Returns the lenientFutureDates. | ||||
|      * @since 1.5 | ||||
|      */ | ||||
|     public boolean isLenientFutureDates() { | ||||
|         return lenientFutureDates; | ||||
|     } | ||||
|     /** | ||||
|      * <p> | ||||
|      * 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 <code>en_US</code> locales. | ||||
|      * </p><p> | ||||
|      * This should be in the format described for | ||||
|      * <code>java.text.SimpleDateFormat</code>. | ||||
|      * property. | ||||
|      * </p> | ||||
|      * @param defaultDateFormatStr The defaultDateFormatStr to set. | ||||
|      */ | ||||
|     public void setDefaultDateFormatStr(String defaultDateFormatStr) { | ||||
|         this.defaultDateFormatStr = defaultDateFormatStr; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * </p> | ||||
|      * <p> | ||||
|      * This is used primarily in unix-based systems. | ||||
|      * </p> | ||||
|      * <p> | ||||
|      * This should be in the format described for | ||||
|      * <code>java.text.SimpleDateFormat</code>. | ||||
|      * </p> | ||||
|      * @param recentDateFormatStr The recentDateFormatStr to set. | ||||
|      */ | ||||
|     public void setRecentDateFormatStr(String recentDateFormatStr) { | ||||
|         this.recentDateFormatStr = recentDateFormatStr; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * </p><p> | ||||
|      * This is used primarily in unix-based systems. | ||||
|      * </p> | ||||
|      * @param lenientFutureDates set true to compensate for out-of-synch | ||||
|      * conditions. | ||||
|      */ | ||||
|     public void setLenientFutureDates(boolean lenientFutureDates) { | ||||
|         this.lenientFutureDates = lenientFutureDates; | ||||
|     } | ||||
|     /** | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * </p><p> | ||||
|      * This should be one of the identifiers used by | ||||
|      * <code>java.util.TimeZone</code> to refer to time zones, for example, | ||||
|      * <code>America/Chicago</code> or <code>Asia/Rangoon</code>. | ||||
|      * </p> | ||||
|      * @param serverTimeZoneId The serverTimeZoneId to set. | ||||
|      */ | ||||
|     public void setServerTimeZoneId(String serverTimeZoneId) { | ||||
|         this.serverTimeZoneId = serverTimeZoneId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * </p><p> | ||||
|      * 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 | ||||
|      * <code>"jan|feb|mar|apr|maí|jún|júl|ágú|sep|okt|nóv|des"</code>. | ||||
|      * </p> | ||||
|      * @param shortMonthNames The value to set to the shortMonthNames property. | ||||
|      */ | ||||
|     public void setShortMonthNames(String shortMonthNames) { | ||||
|         this.shortMonthNames = shortMonthNames; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * <p> | ||||
|      * setter for the serverLanguageCode property.  This property allows | ||||
|      * user to specify a | ||||
|      * <a href="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt"> | ||||
|      * two-letter ISO-639 language code</a> 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. | ||||
|      * </p> | ||||
|      * <p>If the code supplied is not supported here, <code>en_US</code> | ||||
|      * month names will be used.  We are supporting here those language | ||||
|      * codes which, when a <code> java.util.Locale</code> is constucted | ||||
|      * using it, and a <code>java.text.SimpleDateFormat</code> is | ||||
|      * constructed using that Locale, the array returned by the | ||||
|      * SimpleDateFormat's <code>getShortMonths()</code> 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. | ||||
|      * </p> | ||||
|      * <p><strong> | ||||
|      * Please note that this attribute will NOT be used to determine a | ||||
|      * locale-based date format for the language.  </strong> | ||||
|      * Experience has shown that many if not most FTP servers outside the | ||||
|      * United States employ the standard <code>en_US</code> date format | ||||
|      * orderings of <code>MMM d yyyy</code> and <code>MMM d HH:mm</code> | ||||
|      * 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. | ||||
|      * </p> | ||||
|      * @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 | ||||
|      * <code>Locale.US</code> | ||||
|      * @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 | ||||
|      * <code>Locale.US</code> 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<String> getSupportedLanguageCodes() { | ||||
|         return LANGUAGE_CODE_MAP.keySet(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Allow list parsing methods to create basic FTPFile entries if parsing fails. | ||||
|      * <p> | ||||
|      * 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 | ||||
|      * <p> | ||||
|      * 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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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]; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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 <code>DIRECTORY_TYPE</code>, 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 <code>FILE_TYPE</code>, 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 <code>UNKNOWN_TYPE</code>, 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 <code>UNKNOWN_TYPE</code>, 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 (<code>DIRECTORY_TYPE</code>, | ||||
|      * <code>FILE_TYPE</code>, 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 <code>_TYPE</code> 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 <code> _ACCESS </code> | ||||
|      * constants) has the given access permission (one of the | ||||
|      * <code> _PERMISSION </code> constants) to the file. | ||||
|      * | ||||
|      * @param access The access group (one of the <code> _ACCESS </code> | ||||
|      *               constants) | ||||
|      * @param permission The access permission (one of the | ||||
|      *               <code> _PERMISSION </code> 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 <code> _ACCESS </code> | ||||
|      * constants) has the given access permission (one of the | ||||
|      * <code> _PERMISSION </code> constants) to the file. | ||||
|      * | ||||
|      * @param access The access group (one of the <code> _ACCESS </code> | ||||
|      *               constants) | ||||
|      * @param permission The access permission (one of the | ||||
|      *               <code> _PERMISSION </code> 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. | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * <p> | ||||
|      * 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(); | ||||
|     } | ||||
| } | ||||
| @ -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. | ||||
|  * <p> | ||||
|  * Here are some examples showing how to use one of the classes that | ||||
|  * implement this interface. | ||||
|  * <p> | ||||
|  * | ||||
|  * The first example uses the <code>FTPClient.listFiles()</code> | ||||
|  * API to pull the whole list from the subfolder <code>subfolder</code> in | ||||
|  * one call, attempting to automatically detect the parser type.  This | ||||
|  * method, without a parserKey parameter, indicates that autodection should | ||||
|  * be used. | ||||
|  * | ||||
|  * <pre> | ||||
|  *    FTPClient f=FTPClient(); | ||||
|  *    f.connect(server); | ||||
|  *    f.login(username, password); | ||||
|  *    FTPFile[] files = f.listFiles("subfolder"); | ||||
|  * </pre> | ||||
|  * | ||||
|  * The second example uses the <code>FTPClient.listFiles()</code> | ||||
|  * 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. | ||||
|  * | ||||
|  * <pre> | ||||
|  *    FTPClient f=FTPClient(); | ||||
|  *    f.connect(server); | ||||
|  *    f.login(username, password); | ||||
|  *    FTPFile[] files = f.listFiles( | ||||
|  *      "org.apache.commons.net.ftp.parser.EnterpriseUnixFTPFileEntryParser", | ||||
|  *      "."); | ||||
|  * </pre> | ||||
|  * | ||||
|  * The third example uses the <code>FTPClient.listFiles()</code> | ||||
|  * 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. | ||||
|  * | ||||
|  * <pre> | ||||
|  *    FTPClient f=FTPClient(); | ||||
|  *    f.connect(server); | ||||
|  *    f.login(username, password); | ||||
|  *    FTPFile[] files = f.listFiles("VMS", "subfolder/foo.java"); | ||||
|  * </pre> | ||||
|  * | ||||
|  * 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 <code> FTPFile </code> instance.  If the | ||||
|      * file listing line doesn't describe a file, <code> null </code> should be | ||||
|      * returned, otherwise a <code> FTPFile </code> 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<String> preParse(List<String> original); | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Emacs configuration | ||||
|  * Local variables:        ** | ||||
|  * mode:             java  ** | ||||
|  * c-basic-offset:   4     ** | ||||
|  * indent-tabs-mode: nil   ** | ||||
|  * End:                    ** | ||||
|  */ | ||||
| @ -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 <code>original</code> unmodified. | ||||
|      */ | ||||
|     @Override | ||||
|     public List<String> preParse(List<String> original) { | ||||
|          return original; | ||||
|      } | ||||
| } | ||||
| 
 | ||||
| /* Emacs configuration | ||||
|  * Local variables:        ** | ||||
|  * mode:             java  ** | ||||
|  * c-basic-offset:   4     ** | ||||
|  * indent-tabs-mode: nil   ** | ||||
|  * End:                    ** | ||||
|  */ | ||||
| @ -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 <code>null</code>. | ||||
|      * @return <code>true</code> if the file is to be included, <code>false</code> otherwise | ||||
|      */ | ||||
|     public boolean accept(FTPFile file); | ||||
| } | ||||
| @ -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(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| } | ||||
| @ -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<String> response = new ArrayList<String>(); | ||||
|         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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -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. | ||||
|  * <p> | ||||
|  * This object defines a two-part parsing mechanism. | ||||
|  * <p> | ||||
|  * 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 <code>readNextEntry()</code> - which handles the issue of | ||||
|  * what delimits one entry from another, usually but not always a line | ||||
|  * feed and <code>preParse()</code> - which handles removal of | ||||
|  * extraneous matter such as the preliminary lines of a listing, removal | ||||
|  * of duplicates on versioning systems, etc. | ||||
|  * <p> | ||||
|  * 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 <code>getNext()</code> and | ||||
|  * <code>getPrevious()</code> methods to provide "paged" output of less | ||||
|  * than the whole list at one time, or by calling the | ||||
|  * <code>getFiles()</code> method to return the entire list. | ||||
|  * <p> | ||||
|  * Examples: | ||||
|  * <p> | ||||
|  * Paged access: | ||||
|  * <pre> | ||||
|  *    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.
 | ||||
|  *    } | ||||
|  * </pre> | ||||
|  * <p> | ||||
|  * 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<String> entries = new LinkedList<String>(); | ||||
|     private ListIterator<String> _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<String>(); | ||||
|         readStream(stream, encoding); | ||||
|         this.parser.preParse(this.entries); | ||||
|         resetIterator(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Internal method for reading the input into the <code>entries</code> list. | ||||
|      * After this method has completed, <code>entries</code> will contain a | ||||
|      * collection of entries (as defined by | ||||
|      * <code>FTPFileEntryParser.readNextEntry()</code>), 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 <code>quantityRequested</code> FTPFile | ||||
|      * objects starting at this object's internal iterator's current position. | ||||
|      * If fewer than <code>quantityRequested</code> 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 <code>quantityRequested</code> 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. | ||||
|      * <p><b> | ||||
|      * NOTE:</b> 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<FTPFile> tmpResults = new LinkedList<FTPFile>(); | ||||
|         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 <code>quantityRequested</code> FTPFile | ||||
|      * objects starting at this object's internal iterator's current position, | ||||
|      * and working back toward the beginning. | ||||
|      * | ||||
|      * If fewer than <code>quantityRequested</code> 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 <code>quantityRequested</code> 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). | ||||
|      * <p><b> | ||||
|      * NOTE:</b> 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<FTPFile> tmpResults = new LinkedList<FTPFile>(); | ||||
|         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 <code>null</code>. | ||||
|      * | ||||
|      * @return an array of FTPFile objects containing the whole list of | ||||
|      *         files returned by the server as read by this object's parser. | ||||
|      * <p><b> | ||||
|      * NOTE:</b> 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<FTPFile> tmpResults = new ArrayList<FTPFile>(); | ||||
|         Iterator<String> 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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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. | ||||
|  * <p> | ||||
|  * 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); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -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 <code>null</code> | ||||
|      */ | ||||
|     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 <code>null</code> | ||||
|      */ | ||||
|     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. | ||||
|      * <ul> | ||||
|      * <li>C - Clear</li> | ||||
|      * <li>S - Safe(SSL protocol only)</li> | ||||
|      * <li>E - Confidential(SSL protocol only)</li> | ||||
|      * <li>P - Private</li> | ||||
|      * </ul> | ||||
|      * <b>N.B.</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 <code>null</code> 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. | ||||
|      * <p> | ||||
|      * 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; */ | ||||
| @ -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]; | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue