修复FTP上传文件不完整的bug,尝试处理ftp 断点续传上传

pull/330/head
AriaLyy 7 years ago
parent 9d354fa935
commit afb47c8446
  1. 4
      Aria/build.gradle
  2. 196
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFtpInfoThread.java
  3. 81
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFtpThreadTask.java
  4. 25
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  5. 4
      Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java
  6. 155
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/AbsFtpInfoThread.java
  7. 1
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/DownloadGroupUtil.java
  8. 1
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirDownloadUtil.java
  9. 14
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpDirInfoThread.java
  10. 13
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java
  11. 44
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java
  12. 1
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java
  13. 1
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java
  14. 3
      Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java
  15. 55
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java
  16. 129
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java
  17. 74
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/ProgressInputStream.java
  18. 15
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java
  19. 2
      Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java
  20. 1
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  21. 497
      AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketClient.java
  22. 54
      AriaFtpPlug/src/main/java/org/apache/commons/net/DatagramSocketFactory.java
  23. 71
      AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java
  24. 318
      AriaFtpPlug/src/main/java/org/apache/commons/net/DefaultSocketFactory.java
  25. 38
      AriaFtpPlug/src/main/java/org/apache/commons/net/MalformedServerReplyException.java
  26. 312
      AriaFtpPlug/src/main/java/org/apache/commons/net/PrintCommandListener.java
  27. 201
      AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java
  28. 33
      AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandListener.java
  29. 180
      AriaFtpPlug/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java
  30. 1616
      AriaFtpPlug/src/main/java/org/apache/commons/net/SocketClient.java
  31. 15
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/Configurable.java
  32. 3433
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTP.java
  33. 7122
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClient.java
  34. 1140
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java
  35. 135
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCmd.java
  36. 257
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPCommand.java
  37. 33
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java
  38. 957
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFile.java
  39. 79
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java
  40. 77
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java
  41. 15
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java
  42. 53
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java
  43. 286
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java
  44. 442
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java
  45. 336
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPReply.java
  46. 1744
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSClient.java
  47. 48
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java
  48. 64
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java
  49. 158
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java
  50. 43
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java
  51. 33
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/OnFtpInputStreamListener.java
  52. 56
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java
  53. 156
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java
  54. 455
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
  55. 249
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java
  56. 77
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java
  57. 43
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java
  58. 676
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java
  59. 384
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
  60. 809
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java
  61. 430
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java
  62. 241
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java
  63. 278
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java
  64. 203
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java
  65. 735
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java
  66. 62
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java
  67. 324
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java
  68. 600
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java
  69. 421
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java
  70. 204
      AriaFtpPlug/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java
  71. 80
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/CRLFLineReader.java
  72. 137
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java
  73. 144
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java
  74. 74
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamException.java
  75. 55
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/CopyStreamListener.java
  76. 367
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java
  77. 322
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java
  78. 307
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java
  79. 227
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java
  80. 54
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketInputStream.java
  81. 88
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/SocketOutputStream.java
  82. 242
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java
  83. 141
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java
  84. 615
      AriaFtpPlug/src/main/java/org/apache/commons/net/io/Util.java
  85. 1944
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/Base64.java
  86. 47
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/Charsets.java
  87. 311
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java
  88. 69
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/ListenerList.java
  89. 78
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLContextUtils.java
  90. 63
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java
  91. 609
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/SubnetUtils.java
  92. 133
      AriaFtpPlug/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java
  93. 2
      app/src/main/java/com/arialyy/simple/download/FtpDownloadActivity.java
  94. 4
      app/src/main/java/com/arialyy/simple/upload/FtpUploadActivity.java

@ -23,8 +23,8 @@ dependencies {
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:appcompat-v7:23.1.1'
compile project(':AriaAnnotations') compile project(':AriaAnnotations')
compile 'com.arialyy.aria:aria-ftp-plug:1.0.0' // compile 'com.arialyy.aria:aria-ftp-plug:1.0.0'
// compile project(':AriaFtpPlug') compile project(':AriaFtpPlug')
} }
apply from: 'bintray-release.gradle' apply from: 'bintray-release.gradle'

@ -0,0 +1,196 @@
/*
* 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.text.TextUtils;
import android.util.Log;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.upload.UploadEntity;
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文件夹信息
*/
public 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;
protected OnFileInfoCallback mCallback;
protected long mSize = 0;
protected String mServerIp, mPort;
protected String charSet = "UTF-8";
private boolean isUpload = false;
public AbsFtpInfoThread(TASK_ENTITY taskEntity, OnFileInfoCallback callback) {
mTaskEntity = taskEntity;
mEntity = taskEntity.getEntity();
mConnectTimeOut =
AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getConnectTimeOut();
mCallback = callback;
if (mEntity instanceof UploadEntity) {
isUpload = true;
}
}
/**
* 设置请求的远程文件路径
*
* @return 远程文件路径
*/
protected abstract String setRemotePath();
@Override public void run() {
FTPClient client = null;
try {
client = createFtpClient();
if (client == null) return;
String remotePath =
new String(setRemotePath().getBytes(charSet), AbsFtpThreadTask.SERVER_CHARSET);
FTPFile[] files = client.listFiles(remotePath);
mSize = getFileSize(files, client, remotePath);
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
if (isUpload) {
//服务器上没有该文件路径,表示该任务为新的上传任务
mTaskEntity.isNewTask = true;
} else {
client.disconnect();
failDownload("获取文件信息错误,错误码为:" + reply);
return;
}
}
mTaskEntity.code = reply;
mEntity.update();
mTaskEntity.update();
onPreComplete(reply);
} catch (IOException e) {
failDownload(e.getMessage());
} finally {
if (client != null) {
try {
client.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void start() {
new Thread(this).start();
}
protected void onPreComplete(int code) {
}
/**
* 创建FTP客户端
*/
private FTPClient createFtpClient() throws IOException {
String url = "";
if (mEntity instanceof DownloadEntity) {
url = ((DownloadEntity) mEntity).getUrl();
} else if (mEntity instanceof UploadEntity) {
url = ((UploadEntity) mEntity).getUrl();
} else if (mEntity instanceof DownloadGroupEntity) {
url = mEntity.getKey();
} else {
failDownload("未知实体");
Log.e(TAG, "未知实体");
return null;
}
String[] pp = url.split("/")[2].split(":");
mServerIp = pp[0];
mPort = pp[1];
FTPClient client = new FTPClient();
// 连接服务器
client.connect(mServerIp, Integer.parseInt(mPort));
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 null;
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
charSet = "UTF-8";
if (!TextUtils.isEmpty(mTaskEntity.charSet) || !FTPReply.isPositiveCompletion(
client.sendCommand("OPTS UTF8", "ON"))) {
charSet = mTaskEntity.charSet;
}
client.setControlEncoding(charSet);
client.setDataTimeout(10 * 1000);
client.enterLocalPassiveMode();
client.setFileType(FTP.BINARY_FILE_TYPE);
client.setControlKeepAliveTimeout(5);
return client;
}
/**
* 遍历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服务器上对应的文件
*/
protected 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,81 @@
/*
* 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.text.TextUtils;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IEventListener;
import java.io.IOException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
/**
* Created by lyy on 2017/9/26.
* FTP单任务父类
*/
public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
extends AbsThreadTask<ENTITY, TASK_ENTITY> {
protected String charSet, serverIp, port;
/**
* FTP 服务器编码
*/
public static String SERVER_CHARSET = "ISO-8859-1";
protected AbsFtpThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<TASK_ENTITY> info) {
super(constance, listener, info);
}
/**
* 构建FTP客户端
*/
protected FTPClient createClient() throws IOException {
String url = mEntity.getUrl();
String[] pp = url.split("/")[2].split(":");
serverIp = pp[0];
port = pp[1];
FTPClient client = new FTPClient();
// 连接服务器
client.connect(serverIp, Integer.parseInt(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;
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
charSet = "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.setBufferSize(mBufSize);
//client.setControlKeepAliveTimeout(5);
//client.setCopyStreamListener();
return client;
}
}

@ -19,7 +19,7 @@ import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadEntity;
@ -37,7 +37,7 @@ import java.util.TimerTask;
* Created by lyy on 2017/1/18. * Created by lyy on 2017/1/18.
* 任务线程 * 任务线程
*/ */
public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>> public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTITY extends AbsTaskEntity<ENTITY>>
implements Runnable { implements Runnable {
/** /**
* 线程重试次数 * 线程重试次数
@ -56,10 +56,6 @@ public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extend
protected SubThreadConfig<TASK_ENTITY> mConfig; protected SubThreadConfig<TASK_ENTITY> mConfig;
protected ENTITY mEntity; protected ENTITY mEntity;
protected TASK_ENTITY mTaskEntity; protected TASK_ENTITY mTaskEntity;
/**
* FTP 服务器编码
*/
public static String SERVER_CHARSET = "ISO-8859-1";
private int mFailNum = 0; private int mFailNum = 0;
private String mTaskType; private String mTaskType;
private Timer mFailTimer; private Timer mFailTimer;
@ -74,9 +70,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extend
mConfig = info; mConfig = info;
mTaskEntity = mConfig.TASK_ENTITY; mTaskEntity = mConfig.TASK_ENTITY;
mEntity = mTaskEntity.getEntity(); mEntity = mTaskEntity.getEntity();
if (mConfig.SUPPORT_BP) { mConfigFPath = info.CONFIG_FILE_PATH;
mConfigFPath = info.CONFIG_FILE_PATH;
}
mBufSize = manager.getDownloadConfig().getBuffSize(); mBufSize = manager.getDownloadConfig().getBuffSize();
setMaxSpeed(AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getMsxSpeed()); setMaxSpeed(AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getMsxSpeed());
mTaskType = getTaskType(); mTaskType = getTaskType();
@ -105,14 +99,15 @@ public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extend
synchronized (AriaManager.LOCK) { synchronized (AriaManager.LOCK) {
try { try {
if (mConfig.SUPPORT_BP) { if (mConfig.SUPPORT_BP) {
final long currentTemp = mChildCurrentLocation;
STATE.STOP_NUM++; STATE.STOP_NUM++;
Log.d(TAG, "任务【" Log.d(TAG, "任务【"
+ mConfig.TEMP_FILE.getName() + mConfig.TEMP_FILE.getName()
+ "】thread__" + "】thread__"
+ mConfig.THREAD_ID + mConfig.THREAD_ID
+ "__停止, stop location ==> " + "__停止, stop location ==> "
+ mChildCurrentLocation); + currentTemp);
writeConfig(false, mChildCurrentLocation); writeConfig(false, currentTemp);
if (STATE.isStop()) { if (STATE.isStop()) {
Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已停止"); Log.d(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】已停止");
STATE.isRunning = false; STATE.isRunning = false;
@ -170,7 +165,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extend
/** /**
* 任务失败 * 任务失败
*/ */
protected void fail(long currentLocation, String msg, Exception ex) { protected void fail(final long currentLocation, String msg, Exception ex) {
synchronized (AriaManager.LOCK) { synchronized (AriaManager.LOCK) {
try { try {
if (ex != null) { if (ex != null) {
@ -213,7 +208,8 @@ public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extend
mFailNum++; mFailNum++;
Log.w(TAG, Log.w(TAG,
"任务【" + mConfig.TEMP_FILE.getName() + "】thread__" + mConfig.THREAD_ID + "__正在重试"); "任务【" + mConfig.TEMP_FILE.getName() + "】thread__" + mConfig.THREAD_ID + "__正在重试");
mConfig.START_LOCATION = mChildCurrentLocation; final long retryLocation = mChildCurrentLocation;
mConfig.START_LOCATION = retryLocation;
AbsThreadTask.this.run(); AbsThreadTask.this.run();
} }
}, RETRY_INTERVAL); }, RETRY_INTERVAL);
@ -231,8 +227,9 @@ public abstract class AbsThreadTask<ENTITY extends AbsEntity, TASK_ENTITY extend
/** /**
* 将记录写入到配置文件 * 将记录写入到配置文件
*/ */
protected void writeConfig(boolean isComplete, long record) throws IOException { protected void writeConfig(boolean isComplete, final long record) throws IOException {
synchronized (AriaManager.LOCK) { synchronized (AriaManager.LOCK) {
Log.d(TAG, "really record == " + record);
String key = null, value = null; String key = null, value = null;
if (0 < record && record < mConfig.END_LOCATION) { if (0 < record && record < mConfig.END_LOCATION) {
key = mConfig.TEMP_FILE.getName() + "_record_" + mConfig.THREAD_ID; key = mConfig.TEMP_FILE.getName() + "_record_" + mConfig.THREAD_ID;

@ -1,6 +1,6 @@
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.common;
interface OnFileInfoCallback { public interface OnFileInfoCallback {
/** /**
* 处理完成 * 处理完成
* *

@ -1,155 +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.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;
protected long mSize = 0;
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));
mSize = getFileSize(files, client, remotePath);
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);
}
}
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.download.downloader;
import android.util.SparseArray; import android.util.SparseArray;
import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity; import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;

@ -15,6 +15,7 @@
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity; import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;

@ -15,6 +15,8 @@
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.AbsFtpInfoThread;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.download.DownloadGroupTaskEntity; import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
@ -33,14 +35,20 @@ class FtpDirInfoThread extends AbsFtpInfoThread<DownloadGroupEntity, DownloadGro
super(taskEntity, callback); super(taskEntity, callback);
} }
@Override void handleFile(String remotePath, FTPFile ftpFile) { @Override protected String setRemotePath() {
String url = mEntity.getKey();
return url.substring(url.indexOf(mPort) + mPort.length(), url.length());
}
@Override protected void handleFile(String remotePath, FTPFile ftpFile) {
super.handleFile(remotePath, ftpFile); super.handleFile(remotePath, ftpFile);
addEntity(remotePath, ftpFile); addEntity(remotePath, ftpFile);
} }
@Override protected void onPreComplete() { @Override protected void onPreComplete(int code) {
super.onPreComplete(); super.onPreComplete(code);
mEntity.setFileSize(mSize); mEntity.setFileSize(mSize);
mCallback.onComplete(mEntity.getKey(), code);
} }
private void addEntity(String remotePath, FTPFile ftpFile) { private void addEntity(String remotePath, FTPFile ftpFile) {

@ -15,6 +15,8 @@
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.AbsFtpInfoThread;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
@ -28,8 +30,15 @@ class FtpFileInfoThread extends AbsFtpInfoThread<DownloadEntity, DownloadTaskEnt
super(taskEntity, callback); super(taskEntity, callback);
} }
@Override protected void onPreComplete() { @Override protected String setRemotePath() {
super.onPreComplete(); String url = mEntity.getUrl();
return url.substring(url.indexOf(mPort) + mPort.length(), url.length())
+ "/"
+ mEntity.getFileName();
}
@Override protected void onPreComplete(int code) {
super.onPreComplete(code);
if (mSize != mTaskEntity.getEntity().getFileSize()) { if (mSize != mTaskEntity.getEntity().getFileSize()) {
mTaskEntity.isNewTask = true; mTaskEntity.isNewTask = true;
} }

@ -15,9 +15,8 @@
*/ */
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.core.common.AbsThreadTask; import com.arialyy.aria.core.common.AbsFtpThreadTask;
import com.arialyy.aria.core.common.StateConstance; import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
@ -27,7 +26,6 @@ import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.net.ftp.FTPReply;
@ -35,7 +33,7 @@ import org.apache.commons.net.ftp.FTPReply;
* Created by Aria.Lao on 2017/7/24. * Created by Aria.Lao on 2017/7/24.
* Ftp下载任务 * Ftp下载任务
*/ */
class FtpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEntity> { class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DownloadTaskEntity> {
private final String TAG = "FtpThreadTask"; private final String TAG = "FtpThreadTask";
FtpThreadTask(StateConstance constance, IDownloadListener listener, FtpThreadTask(StateConstance constance, IDownloadListener listener,
@ -57,40 +55,16 @@ class FtpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEntity> {
+ ",结束位置:" + ",结束位置:"
+ mConfig.END_LOCATION + mConfig.END_LOCATION
+ "】"); + "】");
client = createClient();
if (client == null) return;
String url = mEntity.getUrl(); String url = mEntity.getUrl();
String[] pp = url.split("/")[2].split(":"); String remotePath = new String(
String serverIp = pp[0]; url.substring(url.indexOf(port) + port.length(), url.length()).getBytes(charSet),
int port = Integer.parseInt(pp[1]); SERVER_CHARSET);
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.setRestartOffset(mConfig.START_LOCATION);
client.allocate(mBufSize); is = client.retrieveFileStream(remotePath);
is = client.retrieveFileStream(
new String(remotePath.getBytes(charSet), SERVER_CHARSET));
//发送第二次指令时,还需要再做一次判断 //发送第二次指令时,还需要再做一次判断
reply = client.getReplyCode(); int reply = client.getReplyCode();
if (!FTPReply.isPositivePreliminary(reply)) { if (!FTPReply.isPositivePreliminary(reply)) {
client.disconnect(); client.disconnect();
fail(mChildCurrentLocation, "获取文件信息错误,错误码为:" + reply, null); fail(mChildCurrentLocation, "获取文件信息错误,错误码为:" + reply, null);

@ -18,6 +18,7 @@ package com.arialyy.aria.core.download.downloader;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;

@ -17,6 +17,7 @@
package com.arialyy.aria.core.download.downloader; package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.download.DownloadTaskEntity; import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IDownloadListener;

@ -45,6 +45,9 @@ public class FtpUploadTarget
File file = new File(filePath); File file = new File(filePath);
mEntity.setFileName(file.getName()); mEntity.setFileName(file.getName());
mEntity.setFileSize(file.length()); mEntity.setFileSize(file.length());
//暂时不支持断点续传上传
mTaskEntity.isSupportBP = false;
} }
/** /**

@ -0,0 +1,55 @@
/*
* 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.AbsFtpInfoThread;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity;
import org.apache.commons.net.ftp.FTPFile;
/**
* Created by Aria.Lao on 2017/9/26.
* FTP远程服务器文件信息
*/
class FtpFileInfoThread extends AbsFtpInfoThread<UploadEntity, UploadTaskEntity> {
static final int CODE_EXISTS = 0xab1;
private boolean exists = false;
FtpFileInfoThread(UploadTaskEntity taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback);
}
@Override protected String setRemotePath() {
String url = mEntity.getUrl();
return url.substring(url.indexOf(mPort) + mPort.length(), url.length())
+ "/"
+ mEntity.getFileName();
}
@Override protected void handleFile(String remotePath, FTPFile ftpFile) {
super.handleFile(remotePath, ftpFile);
//远程文件已完成
if (ftpFile != null && ftpFile.getSize() == mEntity.getFileSize()) {
exists = true;
}
}
@Override protected void onPreComplete(int code) {
super.onPreComplete(code);
mCallback.onComplete(mEntity.getKey(), exists ? CODE_EXISTS : code);
}
}

@ -15,9 +15,8 @@
*/ */
package com.arialyy.aria.core.upload.uploader; package com.arialyy.aria.core.upload.uploader;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.arialyy.aria.core.common.AbsThreadTask; import com.arialyy.aria.core.common.AbsFtpThreadTask;
import com.arialyy.aria.core.common.StateConstance; import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.inf.IEventListener;
@ -27,28 +26,29 @@ import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import org.apache.commons.net.ftp.FTP; import java.io.UnsupportedEncodingException;
import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.OnFtpInputStreamListener;
import org.apache.commons.net.io.CopyStreamEvent;
import org.apache.commons.net.io.CopyStreamListener;
/** /**
* Created by Aria.Lao on 2017/7/28. * Created by Aria.Lao on 2017/7/28.
* FTP 单线程上传任务需要FTP 服务器给用户打开删除和读入IO的权限 * FTP 单线程上传任务需要FTP 服务器给用户打开删除和读入IO的权限
*/ */
class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> { class FtpThreadTask extends AbsFtpThreadTask<UploadEntity, UploadTaskEntity> {
private final String TAG = "FtpThreadTask"; private final String TAG = "FtpThreadTask";
private String dir, remotePath, charSet; private String dir, remotePath;
FtpThreadTask(StateConstance constance, IEventListener listener, FtpThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<UploadTaskEntity> info) { SubThreadConfig<UploadTaskEntity> info) {
super(constance, listener, info); super(constance, listener, info);
} }
@Override public void run() { @Override public void run() {
FTPClient client = null; FTPClient client = null;
OutputStream os = null;
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
try { try {
Log.d(TAG, "任务【" Log.d(TAG, "任务【"
@ -64,22 +64,16 @@ class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
mChildCurrentLocation = mConfig.START_LOCATION; mChildCurrentLocation = mConfig.START_LOCATION;
client = createClient(); client = createClient();
if (client == null) return; if (client == null) return;
initPath();
client.makeDirectory(dir); client.makeDirectory(dir);
client.changeWorkingDirectory(dir); client.changeWorkingDirectory(dir);
client.setRestartOffset(mConfig.START_LOCATION); client.setRestartOffset(mConfig.START_LOCATION);
file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize); file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize);
file.seek(mConfig.START_LOCATION); if (mConfig.START_LOCATION != 0) {
if (!isRemoteComplete(client)) { file.skipBytes((int) mConfig.START_LOCATION);
os = client.storeFileStream(new String(remotePath.getBytes(charSet), SERVER_CHARSET)); //file.seek(mConfig.START_LOCATION);
//发送第二次指令时,还需要再做一次判断
int reply = client.getReplyCode();
if (!FTPReply.isPositivePreliminary(reply)) {
client.disconnect();
fail(mChildCurrentLocation, "上传文件错误,错误码为:" + reply, null);
return;
}
upload(file, os);
} }
upload(client, file);
if (STATE.isCancel || STATE.isStop) return; if (STATE.isCancel || STATE.isStop) return;
Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__上传完毕"); Log.i(TAG, "任务【" + mConfig.TEMP_FILE.getName() + "】线程__" + mConfig.THREAD_ID + "__上传完毕");
writeConfig(true, 1); writeConfig(true, 1);
@ -101,9 +95,6 @@ class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
if (file != null) { if (file != null) {
file.close(); file.close();
} }
if (os != null) {
os.close();
}
if (client != null && client.isConnected()) { if (client != null && client.isConnected()) {
client.disconnect(); client.disconnect();
} }
@ -113,14 +104,55 @@ class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
} }
} }
/** private void initPath() throws UnsupportedEncodingException {
* 远程文件是否已经玩飞车 String url = mEntity.getUrl();
* String temp = url.substring(url.indexOf(port) + port.length(), url.length());
* @return true 任务已经完成 dir = new String(temp.getBytes(charSet), SERVER_CHARSET);
*/ remotePath = new String((temp + "/" + mEntity.getFileName()).getBytes(charSet), SERVER_CHARSET);
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(final FTPClient client, final BufferedRandomAccessFile bis)
throws IOException {
//client.storeFile(remotePath,
// new ProgressInputStream(bis, new ProgressInputStream.ProgressCallback() {
// @Override public void onProgressCallback(byte[] buffer, int byteOffset, int byteCount)
// throws IOException {
// if (STATE.isCancel || STATE.isStop) {
// long s = client.abor();
// Log.d(TAG, "s = " + s);
// client.disconnect();
// }
// progress(byteCount);
// }
// }));
try {
client.storeFile(remotePath, new ProgressInputStream(bis), new OnFtpInputStreamListener() {
@Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred,
int bytesTransferred, long streamSize) {
if (STATE.isCancel || STATE.isStop) {
try {
client.abor();
} catch (IOException e) {
e.printStackTrace();
}
}
progress(bytesTransferred);
}
});
} catch (IOException e) {
//e.printStackTrace();
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
fail(mChildCurrentLocation, "上传文件错误,错误码为:" + reply, null);
}
} }
/** /**
@ -143,48 +175,11 @@ class FtpThreadTask extends AbsThreadTask<UploadEntity, UploadTaskEntity> {
os.write(buffer, 0, len); os.write(buffer, 0, len);
progress(len); progress(len);
} }
Log.d(TAG, 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;
}
@Override protected String getTaskType() { @Override protected String getTaskType() {
return "FTP_UPLOAD"; return "FTP_UPLOAD";
} }
} }

@ -0,0 +1,74 @@
/*
* 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.support.annotation.NonNull;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by lyy on 2017/9/26.
* 具有进度的InputStream
*/
final class ProgressInputStream extends InputStream {
private BufferedRandomAccessFile mIs;
private ProgressCallback mCallback;
private int count;
interface ProgressCallback {
void onProgressCallback(byte[] buffer, int byteOffset, int byteCount) throws IOException;
}
ProgressInputStream(@NonNull BufferedRandomAccessFile is, @NonNull ProgressCallback callback) {
mIs = is;
mCallback = callback;
}
ProgressInputStream(@NonNull BufferedRandomAccessFile is) {
mIs = is;
}
@Override public void close() throws IOException {
mIs.close();
}
@Override public int read() throws IOException {
return mIs.read();
}
@Override public int read(@NonNull byte[] buffer) throws IOException {
count = mIs.read(buffer);
if (mCallback != null) {
mCallback.onProgressCallback(buffer, 0, count);
}
return count;
}
@Override public int read(@NonNull byte[] buffer, int byteOffset, int byteCount)
throws IOException {
count = mIs.read(buffer, byteOffset, byteCount);
if (mCallback != null) {
mCallback.onProgressCallback(buffer, byteOffset, byteCount);
}
return count;
}
@Override public long skip(long byteCount) throws IOException {
return mIs.skipBytes((int) byteCount);
}
}

@ -16,6 +16,7 @@
package com.arialyy.aria.core.upload.uploader; package com.arialyy.aria.core.upload.uploader;
import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.inf.IUploadListener; import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadTaskEntity; import com.arialyy.aria.core.upload.UploadTaskEntity;
@ -46,7 +47,19 @@ public class SimpleUploadUtil implements IUtil, Runnable {
@Override public void run() { @Override public void run() {
mListener.onPre(); mListener.onPre();
mUploader.start(); new FtpFileInfoThread(mTaskEntity, new OnFileInfoCallback() {
@Override public void onComplete(String url, int code) {
if (code == FtpFileInfoThread.CODE_EXISTS) {
mListener.onComplete();
} else {
mUploader.start();
}
}
@Override public void onFail(String url, String errorMsg) {
mListener.onFail(true);
}
}).start();
} }
@Override public long getFileSize() { @Override public long getFileSize() {

@ -230,7 +230,7 @@ final class SqlHelper extends SQLiteOpenHelper {
} }
/** /**
* 遍历所有数据 * 查找表的所有数据
*/ */
static synchronized <T extends DbEntity> List<T> findAllData(SQLiteDatabase db, Class<T> clazz) { static synchronized <T extends DbEntity> List<T> findAllData(SQLiteDatabase db, Class<T> clazz) {
db = checkDb(db); db = checkDb(db);

@ -705,6 +705,7 @@ public class CommonUtil {
* 设置打印的异常格式 * 设置打印的异常格式
*/ */
public static String getPrintException(Throwable ex) { public static String getPrintException(Throwable ex) {
if (ex == null) return "";
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("ExceptionDetailed:\n"); err.append("ExceptionDetailed:\n");
err.append("====================Exception Info====================\n"); err.append("====================Exception Info====================\n");

@ -47,268 +47,241 @@ import java.nio.charset.Charset;
* @see DatagramSocketFactory * @see DatagramSocketFactory
***/ ***/
public abstract class DatagramSocketClient public abstract class DatagramSocketClient {
{ /***
/*** * The default DatagramSocketFactory shared by all DatagramSocketClient
* The default DatagramSocketFactory shared by all DatagramSocketClient * instances.
* instances. ***/
***/ private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY =
private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY = new DefaultDatagramSocketFactory();
new DefaultDatagramSocketFactory();
/**
/** * Charset to use for byte IO.
* Charset to use for byte IO. */
*/ private Charset charset = Charset.defaultCharset();
private Charset charset = Charset.defaultCharset();
/*** The timeout to use after opening a socket. ***/
/*** The timeout to use after opening a socket. ***/ protected int _timeout_;
protected int _timeout_;
/*** The datagram socket used for the connection. ***/
/*** The datagram socket used for the connection. ***/ protected DatagramSocket _socket_;
protected DatagramSocket _socket_;
/***
/*** * A status variable indicating if the client's socket is currently open.
* A status variable indicating if the client's socket is currently open. ***/
***/ protected boolean _isOpen_;
protected boolean _isOpen_;
/*** The datagram socket's DatagramSocketFactory. ***/
/*** The datagram socket's DatagramSocketFactory. ***/ protected DatagramSocketFactory _socketFactory_;
protected DatagramSocketFactory _socketFactory_;
/***
/*** * Default constructor for DatagramSocketClient. Initializes
* Default constructor for DatagramSocketClient. Initializes * _socket_ to null, _timeout_ to 0, and _isOpen_ to false.
* _socket_ to null, _timeout_ to 0, and _isOpen_ to false. ***/
***/ public DatagramSocketClient() {
public DatagramSocketClient() _socket_ = null;
{ _timeout_ = 0;
_socket_ = null; _isOpen_ = false;
_timeout_ = 0; _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
_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() }.
* Opens a DatagramSocket on the local host at the first available port. * <p>
* Also sets the timeout on the socket to the default timeout set * _isOpen_ is set to true after calling this method and _socket_
* by {@link #setDefaultTimeout setDefaultTimeout() }. * is set to the newly opened socket.
* <p> *
* _isOpen_ is set to true after calling this method and _socket_ * @throws SocketException If the socket could not be opened or the
* is set to the newly opened socket. * timeout could not be set.
* ***/
* @throws SocketException If the socket could not be opened or the public void open() throws SocketException {
* timeout could not be set. _socket_ = _socketFactory_.createDatagramSocket();
***/ _socket_.setSoTimeout(_timeout_);
public void open() throws SocketException _isOpen_ = true;
{ }
_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_
* Opens a DatagramSocket on the local host at a specified port. * is set to the newly opened socket.
* Also sets the timeout on the socket to the default timeout set *
* by {@link #setDefaultTimeout setDefaultTimeout() }. * @param port The port to use for the socket.
* <p> * @throws SocketException If the socket could not be opened or the
* _isOpen_ is set to true after calling this method and _socket_ * timeout could not be set.
* is set to the newly opened socket. ***/
* public void open(int port) throws SocketException {
* @param port The port to use for the socket. _socket_ = _socketFactory_.createDatagramSocket(port);
* @throws SocketException If the socket could not be opened or the _socket_.setSoTimeout(_timeout_);
* timeout could not be set. _isOpen_ = true;
***/ }
public void open(int port) throws SocketException
{ /***
_socket_ = _socketFactory_.createDatagramSocket(port); * Opens a DatagramSocket at the specified address on the local host
_socket_.setSoTimeout(_timeout_); * at a specified port.
_isOpen_ = true; * 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.
* Opens a DatagramSocket at the specified address on the local host *
* at a specified port. * @param port The port to use for the socket.
* Also sets the timeout on the socket to the default timeout set * @param laddr The local address to use.
* by {@link #setDefaultTimeout setDefaultTimeout() }. * @throws SocketException If the socket could not be opened or the
* <p> * timeout could not be set.
* _isOpen_ is set to true after calling this method and _socket_ ***/
* is set to the newly opened socket. public void open(int port, InetAddress laddr) throws SocketException {
* _socket_ = _socketFactory_.createDatagramSocket(port, laddr);
* @param port The port to use for the socket. _socket_.setSoTimeout(_timeout_);
* @param laddr The local address to use. _isOpen_ = true;
* @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 * Closes the DatagramSocket used for the connection.
{ * You should call this method after you've finished using the class
_socket_ = _socketFactory_.createDatagramSocket(port, laddr); * instance and also before you call {@link #open open() }
_socket_.setSoTimeout(_timeout_); * again. _isOpen_ is set to false and _socket_ is set to null.
_isOpen_ = true; ***/
} public void close() {
if (_socket_ != null) {
_socket_.close();
/***
* 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();
} }
_socket_ = null;
/** _isOpen_ = false;
* Gets the charset. }
*
* @return the charset. /***
* @since 3.3 * Returns true if the client has a currently open socket.
*/ *
public Charset getCharset() { * @return True if the client has a currently open socket, false otherwise.
return charset; ***/
} public boolean isOpen() {
return _isOpen_;
/** }
* Sets the charset.
* /***
* @param charset the charset. * Set the default timeout in milliseconds to use when opening a socket.
* @since 3.3 * 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()}
public void setCharset(Charset charset) { * and should not be confused with {@link #setSoTimeout setSoTimeout()}
this.charset = charset; * 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;
}
} }

@ -35,35 +35,33 @@ import java.net.SocketException;
* *
***/ ***/
public interface DatagramSocketFactory public interface DatagramSocketFactory {
{
/*** /***
* Creates a DatagramSocket on the local host at the first available port. * Creates a DatagramSocket on the local host at the first available port.
* @return the socket * @return the socket
* *
* @throws SocketException If the socket could not be created. * @throws SocketException If the socket could not be created.
***/ ***/
public DatagramSocket createDatagramSocket() throws SocketException; public DatagramSocket createDatagramSocket() throws SocketException;
/*** /***
* Creates a DatagramSocket on the local host at a specified port. * Creates a DatagramSocket on the local host at a specified port.
* *
* @param port The port to use for the socket. * @param port The port to use for the socket.
* @return the socket * @return the socket
* @throws SocketException If the socket could not be created. * @throws SocketException If the socket could not be created.
***/ ***/
public DatagramSocket createDatagramSocket(int port) throws SocketException; public DatagramSocket createDatagramSocket(int port) throws SocketException;
/*** /***
* Creates a DatagramSocket at the specified address on the local host * Creates a DatagramSocket at the specified address on the local host
* at a specified port. * at a specified port.
* *
* @param port The port to use for the socket. * @param port The port to use for the socket.
* @param laddr The local address to use. * @param laddr The local address to use.
* @return the socket * @return the socket
* @throws SocketException If the socket could not be created. * @throws SocketException If the socket could not be created.
***/ ***/
public DatagramSocket createDatagramSocket(int port, InetAddress laddr) public DatagramSocket createDatagramSocket(int port, InetAddress laddr) throws SocketException;
throws SocketException;
} }

@ -34,46 +34,39 @@ import java.net.SocketException;
* @see DatagramSocketClient#setDatagramSocketFactory * @see DatagramSocketClient#setDatagramSocketFactory
***/ ***/
public class DefaultDatagramSocketFactory implements DatagramSocketFactory public class DefaultDatagramSocketFactory implements DatagramSocketFactory {
{
/*** /***
* Creates a DatagramSocket on the local host at the first available port. * Creates a DatagramSocket on the local host at the first available port.
* @return a new DatagramSocket * @return a new DatagramSocket
* @throws SocketException If the socket could not be created. * @throws SocketException If the socket could not be created.
***/ ***/
@Override @Override public DatagramSocket createDatagramSocket() throws SocketException {
public DatagramSocket createDatagramSocket() throws SocketException return new DatagramSocket();
{ }
return new DatagramSocket();
}
/*** /***
* Creates a DatagramSocket on the local host at a specified port. * Creates a DatagramSocket on the local host at a specified port.
* *
* @param port The port to use for the socket. * @param port The port to use for the socket.
* @return a new DatagramSocket * @return a new DatagramSocket
* @throws SocketException If the socket could not be created. * @throws SocketException If the socket could not be created.
***/ ***/
@Override @Override public DatagramSocket createDatagramSocket(int port) throws SocketException {
public DatagramSocket createDatagramSocket(int port) throws SocketException return new DatagramSocket(port);
{ }
return new DatagramSocket(port);
}
/*** /***
* Creates a DatagramSocket at the specified address on the local host * Creates a DatagramSocket at the specified address on the local host
* at a specified port. * at a specified port.
* *
* @param port The port to use for the socket. * @param port The port to use for the socket.
* @param laddr The local address to use. * @param laddr The local address to use.
* @return a new DatagramSocket * @return a new DatagramSocket
* @throws SocketException If the socket could not be created. * @throws SocketException If the socket could not be created.
***/ ***/
@Override @Override public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
public DatagramSocket createDatagramSocket(int port, InetAddress laddr) throws SocketException {
throws SocketException return new DatagramSocket(port, laddr);
{ }
return new DatagramSocket(port, laddr);
}
} }

@ -40,191 +40,165 @@ import javax.net.SocketFactory;
* @see SocketClient#setSocketFactory * @see SocketClient#setSocketFactory
***/ ***/
public class DefaultSocketFactory extends SocketFactory public class DefaultSocketFactory extends SocketFactory {
{ /** The proxy to use when creating new sockets. */
/** The proxy to use when creating new sockets. */ private final Proxy connProxy;
private final Proxy connProxy;
/** /**
* The default constructor. * The default constructor.
*/ */
public DefaultSocketFactory() public DefaultSocketFactory() {
{ this(null);
this(null); }
}
/** /**
* A constructor for sockets with proxy support. * A constructor for sockets with proxy support.
* *
* @param proxy The Proxy to use when creating new Sockets. * @param proxy The Proxy to use when creating new Sockets.
* @since 3.2 * @since 3.2
*/ */
public DefaultSocketFactory(Proxy proxy) public DefaultSocketFactory(Proxy proxy) {
{ connProxy = proxy;
connProxy = proxy; }
}
/** /**
* Creates an unconnected Socket. * Creates an unconnected Socket.
* *
* @return A new unconnected Socket. * @return A new unconnected Socket.
* @throws IOException If an I/O error occurs while creating the Socket. * @throws IOException If an I/O error occurs while creating the Socket.
* @since 3.2 * @since 3.2
*/ */
@Override @Override public Socket createSocket() throws IOException {
public Socket createSocket() throws IOException if (connProxy != null) {
{ return new Socket(connProxy);
if (connProxy != null)
{
return new Socket(connProxy);
}
return new Socket();
} }
return new Socket();
}
/*** /***
* Creates a Socket connected to the given host and port. * Creates a Socket connected to the given host and port.
* *
* @param host The hostname to connect to. * @param host The hostname to connect to.
* @param port The port to connect to. * @param port The port to connect to.
* @return A Socket connected to the given host and port. * @return A Socket connected to the given host and port.
* @throws UnknownHostException If the hostname cannot be resolved. * @throws UnknownHostException If the hostname cannot be resolved.
* @throws IOException If an I/O error occurs while creating the Socket. * @throws IOException If an I/O error occurs while creating the Socket.
***/ ***/
@Override @Override public Socket createSocket(String host, int port)
public Socket createSocket(String host, int port) throws UnknownHostException, IOException {
throws UnknownHostException, IOException if (connProxy != null) {
{ Socket s = new Socket(connProxy);
if (connProxy != null) s.connect(new InetSocketAddress(host, port));
{ return s;
Socket s = new Socket(connProxy);
s.connect(new InetSocketAddress(host, port));
return s;
}
return new Socket(host, port);
} }
return new Socket(host, port);
}
/*** /***
* Creates a Socket connected to the given host and port. * Creates a Socket connected to the given host and port.
* *
* @param address The address of the host to connect to. * @param address The address of the host to connect to.
* @param port The port to connect to. * @param port The port to connect to.
* @return A Socket connected to the given host and port. * @return A Socket connected to the given host and port.
* @throws IOException If an I/O error occurs while creating the Socket. * @throws IOException If an I/O error occurs while creating the Socket.
***/ ***/
@Override @Override public Socket createSocket(InetAddress address, int port) throws IOException {
public Socket createSocket(InetAddress address, int port) if (connProxy != null) {
throws IOException Socket s = new Socket(connProxy);
{ s.connect(new InetSocketAddress(address, port));
if (connProxy != null) return s;
{
Socket s = new Socket(connProxy);
s.connect(new InetSocketAddress(address, port));
return s;
}
return new Socket(address, port);
} }
return new Socket(address, port);
}
/*** /***
* Creates a Socket connected to the given host and port and * Creates a Socket connected to the given host and port and
* originating from the specified local address and port. * originating from the specified local address and port.
* *
* @param host The hostname to connect to. * @param host The hostname to connect to.
* @param port The port to connect to. * @param port The port to connect to.
* @param localAddr The local address to use. * @param localAddr The local address to use.
* @param localPort The local port to use. * @param localPort The local port to use.
* @return A Socket connected to the given host and port. * @return A Socket connected to the given host and port.
* @throws UnknownHostException If the hostname cannot be resolved. * @throws UnknownHostException If the hostname cannot be resolved.
* @throws IOException If an I/O error occurs while creating the Socket. * @throws IOException If an I/O error occurs while creating the Socket.
***/ ***/
@Override @Override public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
public Socket createSocket(String host, int port, throws UnknownHostException, IOException {
InetAddress localAddr, int localPort) if (connProxy != null) {
throws UnknownHostException, IOException Socket s = new Socket(connProxy);
{ s.bind(new InetSocketAddress(localAddr, localPort));
if (connProxy != null) s.connect(new InetSocketAddress(host, port));
{ return s;
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);
} }
return new Socket(host, port, localAddr, localPort);
}
/*** /***
* Creates a Socket connected to the given host and port and * Creates a Socket connected to the given host and port and
* originating from the specified local address and port. * originating from the specified local address and port.
* *
* @param address The address of the host to connect to. * @param address The address of the host to connect to.
* @param port The port to connect to. * @param port The port to connect to.
* @param localAddr The local address to use. * @param localAddr The local address to use.
* @param localPort The local port to use. * @param localPort The local port to use.
* @return A Socket connected to the given host and port. * @return A Socket connected to the given host and port.
* @throws IOException If an I/O error occurs while creating the Socket. * @throws IOException If an I/O error occurs while creating the Socket.
***/ ***/
@Override @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddr,
public Socket createSocket(InetAddress address, int port, int localPort) throws IOException {
InetAddress localAddr, int localPort) if (connProxy != null) {
throws IOException Socket s = new Socket(connProxy);
{ s.bind(new InetSocketAddress(localAddr, localPort));
if (connProxy != null) s.connect(new InetSocketAddress(address, port));
{ return s;
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);
} }
return new Socket(address, port, localAddr, localPort);
}
/*** /***
* Creates a ServerSocket bound to a specified port. A port * Creates a ServerSocket bound to a specified port. A port
* of 0 will create the ServerSocket on a system-determined free 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 port The port on which to listen, or 0 to use any free port.
* @return A ServerSocket that will listen on a specified port. * @return A ServerSocket that will listen on a specified port.
* @throws IOException If an I/O error occurs while creating * @throws IOException If an I/O error occurs while creating
* the ServerSocket. * the ServerSocket.
***/ ***/
public ServerSocket createServerSocket(int port) throws IOException public ServerSocket createServerSocket(int port) throws IOException {
{ return new ServerSocket(port);
return new ServerSocket(port); }
}
/*** /***
* Creates a ServerSocket bound to a specified port with a given * Creates a ServerSocket bound to a specified port with a given
* maximum queue length for incoming connections. A port of 0 will * maximum queue length for incoming connections. A port of 0 will
* create the ServerSocket on a system-determined free port. * 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 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 backlog The maximum length of the queue for incoming connections.
* @return A ServerSocket that will listen on a specified port. * @return A ServerSocket that will listen on a specified port.
* @throws IOException If an I/O error occurs while creating * @throws IOException If an I/O error occurs while creating
* the ServerSocket. * the ServerSocket.
***/ ***/
public ServerSocket createServerSocket(int port, int backlog) public ServerSocket createServerSocket(int port, int backlog) throws IOException {
throws IOException return new ServerSocket(port, backlog);
{ }
return new ServerSocket(port, backlog);
}
/*** /***
* Creates a ServerSocket bound to a specified port on a given local * Creates a ServerSocket bound to a specified port on a given local
* address with a given maximum queue length for incoming connections. * address with a given maximum queue length for incoming connections.
* A port of 0 will * A port of 0 will
* create the ServerSocket on a system-determined free port. * 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 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 backlog The maximum length of the queue for incoming connections.
* @param bindAddr The local address to which the ServerSocket should bind. * @param bindAddr The local address to which the ServerSocket should bind.
* @return A ServerSocket that will listen on a specified port. * @return A ServerSocket that will listen on a specified port.
* @throws IOException If an I/O error occurs while creating * @throws IOException If an I/O error occurs while creating
* the ServerSocket. * the ServerSocket.
***/ ***/
public ServerSocket createServerSocket(int port, int backlog, public ServerSocket createServerSocket(int port, int backlog, InetAddress bindAddr)
InetAddress bindAddr) throws IOException {
throws IOException return new ServerSocket(port, backlog, bindAddr);
{ }
return new ServerSocket(port, backlog, bindAddr);
}
} }

@ -32,25 +32,21 @@ import java.io.IOException;
* *
***/ ***/
public class MalformedServerReplyException extends IOException public class MalformedServerReplyException extends IOException {
{
private static final long serialVersionUID = 6006765264250543945L;
private static final long serialVersionUID = 6006765264250543945L;
/*** Constructs a MalformedServerReplyException with no message ***/
/*** Constructs a MalformedServerReplyException with no message ***/ public MalformedServerReplyException() {
public MalformedServerReplyException() super();
{ }
super();
} /***
* Constructs a MalformedServerReplyException with a specified message.
/*** *
* Constructs a MalformedServerReplyException with a specified message. * @param message The message explaining the reason for the exception.
* ***/
* @param message The message explaining the reason for the exception. public MalformedServerReplyException(String message) {
***/ super(message);
public MalformedServerReplyException(String message) }
{
super(message);
}
} }

@ -28,175 +28,163 @@ import java.io.PrintWriter;
* @since 2.0 * @since 2.0
***/ ***/
public class PrintCommandListener implements ProtocolCommandListener public class PrintCommandListener implements ProtocolCommandListener {
{ private final PrintWriter __writer;
private final PrintWriter __writer; private final boolean __nologin;
private final boolean __nologin; private final char __eolMarker;
private final char __eolMarker; private final boolean __directionMarker;
private final boolean __directionMarker;
/**
/** * Create the default instance which prints everything.
* Create the default instance which prints everything. *
* * @param stream where to write the commands and responses
* @param stream where to write the commands and responses * e.g. System.out
* e.g. System.out * @since 3.0
* @since 3.0 */
*/ public PrintCommandListener(PrintStream stream) {
public PrintCommandListener(PrintStream stream) this(new PrintWriter(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();
* Create an instance which optionally suppresses login command text if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) {
* and indicates where the EOL starts with the specified character. __writer.print(cmd);
* __writer.println(" *******"); // Don't bother with EOL marker for this!
* @param stream where to write the commands and responses } else {
* @param suppressLogin if {@code true}, only print command name for login final String IMAP_LOGIN = "LOGIN";
* if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP
* @since 3.0 String msg = event.getMessage();
*/ msg = msg.substring(0, msg.indexOf(IMAP_LOGIN) + IMAP_LOGIN.length());
public PrintCommandListener(PrintStream stream, boolean suppressLogin) { __writer.print(msg);
this(new PrintWriter(stream), suppressLogin); __writer.println(" *******"); // Don't bother with EOL marker for this!
}
/**
* 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 { } else {
__writer.print(getPrintableString(event.getMessage())); __writer.print(getPrintableString(event.getMessage()));
} }
__writer.flush(); }
} else {
__writer.print(getPrintableString(event.getMessage()));
} }
__writer.flush();
}
private String getPrintableString(String msg){ private String getPrintableString(String msg) {
if (__eolMarker == 0) { if (__eolMarker == 0) {
return msg; 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;
} }
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 @Override public void protocolReplyReceived(ProtocolCommandEvent event) {
public void protocolReplyReceived(ProtocolCommandEvent event) if (__directionMarker) {
{ __writer.print("< ");
if (__directionMarker) {
__writer.print("< ");
}
__writer.print(event.getMessage());
__writer.flush();
} }
__writer.print(event.getMessage());
__writer.flush();
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net; package org.apache.commons.net;
import java.util.EventObject; import java.util.EventObject;
/*** /***
@ -32,117 +33,107 @@ import java.util.EventObject;
* @see ProtocolCommandSupport * @see ProtocolCommandSupport
***/ ***/
public class ProtocolCommandEvent extends EventObject public class ProtocolCommandEvent extends EventObject {
{ private static final long serialVersionUID = 403743538418947240L;
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;
}
private final int __replyCode;
private final boolean __isCommand;
private final String __message, __command;
/*** /***
* Creates a ProtocolCommandEvent signalling a reply to a command was * Creates a ProtocolCommandEvent signalling a command was sent to
* received. ProtocolCommandEvents created with this constructor * the server. ProtocolCommandEvents created with this constructor
* should only be sent after a complete command reply has been received * should only be sent after a command has been sent, but before the
* fromt a server. * reply has been received.
* *
* @param source The source of the event. * @param source The source of the event.
* @param replyCode The integer code indicating the natureof the reply. * @param command The string representation of the command type sent, not
* This will be the protocol integer value for protocols * including the arguments (e.g., "STAT" or "GET").
* that use integer reply codes, or the reply class constant * @param message The entire command string verbatim as sent to the server,
* corresponding to the reply for protocols like POP3 that use * including all arguments.
* 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, String command, String message) {
***/ super(source);
public ProtocolCommandEvent(Object source, int replyCode, String message) __replyCode = 0;
{ __message = message;
super(source); __isCommand = true;
__replyCode = replyCode; __command = command;
__message = message; }
__isCommand = false;
__command = null;
}
/*** /***
* Returns the string representation of the command type sent (e.g., "STAT" * Creates a ProtocolCommandEvent signalling a reply to a command was
* or "GET"). If the ProtocolCommandEvent is a reply event, then null * received. ProtocolCommandEvents created with this constructor
* is returned. * should only be sent after a complete command reply has been received
* * fromt a server.
* @return The string representation of the command type sent, or null *
* if this is a reply event. * @param source The source of the event.
***/ * @param replyCode The integer code indicating the natureof the reply.
public String getCommand() * This will be the protocol integer value for protocols
{ * that use integer reply codes, or the reply class constant
return __command; * 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 * Returns the reply code of the received server reply. Undefined if
* this is not a reply event. * this is not a reply event.
* *
* @return The reply code of the received server reply. Undefined if * @return The reply code of the received server reply. Undefined if
* not a reply event. * not a reply event.
***/ ***/
public int getReplyCode() public int getReplyCode() {
{ return __replyCode;
return __replyCode; }
}
/*** /***
* Returns true if the ProtocolCommandEvent was generated as a result * Returns true if the ProtocolCommandEvent was generated as a result
* of sending a command. * of sending a command.
* *
* @return true If the ProtocolCommandEvent was generated as a result * @return true If the ProtocolCommandEvent was generated as a result
* of sending a command. False otherwise. * of sending a command. False otherwise.
***/ ***/
public boolean isCommand() public boolean isCommand() {
{ return __isCommand;
return __isCommand; }
}
/*** /***
* Returns true if the ProtocolCommandEvent was generated as a result * Returns true if the ProtocolCommandEvent was generated as a result
* of receiving a reply. * of receiving a reply.
* *
* @return true If the ProtocolCommandEvent was generated as a result * @return true If the ProtocolCommandEvent was generated as a result
* of receiving a reply. False otherwise. * of receiving a reply. False otherwise.
***/ ***/
public boolean isReply() public boolean isReply() {
{ return !isCommand();
return !isCommand(); }
}
/*** /***
* Returns the entire message sent to or received from the server. * Returns the entire message sent to or received from the server.
* Includes the line terminator. * Includes the line terminator.
* *
* @return The entire message sent to or received from the server. * @return The entire message sent to or received from the server.
***/ ***/
public String getMessage() public String getMessage() {
{ return __message;
return __message; }
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net; package org.apache.commons.net;
import java.util.EventListener; import java.util.EventListener;
/*** /***
@ -36,23 +37,21 @@ import java.util.EventListener;
* @see ProtocolCommandSupport * @see ProtocolCommandSupport
***/ ***/
public interface ProtocolCommandListener extends EventListener 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 * This method is invoked by a ProtocolCommandEvent source after
* receiving a reply from a server. * sending a protocol command to a server.
* *
* @param event The ProtocolCommandEvent fired. * @param event The ProtocolCommandEvent fired.
***/ ***/
public void protocolReplyReceived(ProtocolCommandEvent event); 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);
} }

@ -33,103 +33,91 @@ import org.apache.commons.net.util.ListenerList;
* @see ProtocolCommandListener * @see ProtocolCommandListener
***/ ***/
public class ProtocolCommandSupport implements Serializable public class ProtocolCommandSupport implements Serializable {
{ private static final long serialVersionUID = -8017692739988399978L;
private static final long serialVersionUID = -8017692739988399978L;
private final Object __source;
private final Object __source; private final ListenerList __listeners;
private final ListenerList __listeners;
/***
/*** * Creates a ProtocolCommandSupport instance using the indicated source
* Creates a ProtocolCommandSupport instance using the indicated source * as the source of ProtocolCommandEvents.
* as the source of ProtocolCommandEvents. *
* * @param source The source to use for all generated ProtocolCommandEvents.
* @param source The source to use for all generated ProtocolCommandEvents. ***/
***/ public ProtocolCommandSupport(Object source) {
public ProtocolCommandSupport(Object source) __listeners = new ListenerList();
{ __source = 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 sending of a command to all * Fires a ProtocolCommandEvent signalling the reception of a command reply
* registered listeners, invoking their * to all registered listeners, invoking their
* {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() } * {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() }
* methods. * methods.
* *
* @param command The string representation of the command type sent, not * @param replyCode The integer code indicating the natureof the reply.
* including the arguments (e.g., "STAT" or "GET"). * This will be the protocol integer value for protocols
* @param message The entire command string verbatim as sent to the server, * that use integer reply codes, or the reply class constant
* including all arguments. * corresponding to the reply for protocols like POP3 that use
***/ * strings like OK rather than integer codes (i.e., POP3Repy.OK).
public void fireCommandSent(String command, String message) * @param message The entire reply as received from the server.
{ ***/
ProtocolCommandEvent event; public void fireReplyReceived(int replyCode, String message) {
ProtocolCommandEvent event;
event = new ProtocolCommandEvent(__source, command, message); event = new ProtocolCommandEvent(__source, replyCode, message);
for (EventListener listener : __listeners) for (EventListener listener : __listeners) {
{ ((ProtocolCommandListener) listener).protocolReplyReceived(event);
((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. /***
* * Adds a ProtocolCommandListener.
* @param listener The ProtocolCommandListener to remove. *
***/ * @param listener The ProtocolCommandListener to add.
public void removeProtocolCommandListener(ProtocolCommandListener listener) ***/
{ public void addProtocolCommandListener(ProtocolCommandListener listener) {
__listeners.removeListener(listener); __listeners.addListener(listener);
} }
/***
/*** * Removes a ProtocolCommandListener.
* Returns the number of ProtocolCommandListeners currently registered. *
* * @param listener The ProtocolCommandListener to remove.
* @return The number of ProtocolCommandListeners currently registered. ***/
***/ public void removeProtocolCommandListener(ProtocolCommandListener listener) {
public int getListenerCount() __listeners.removeListener(listener);
{ }
return __listeners.getListenerCount();
} /***
* Returns the number of ProtocolCommandListeners currently registered.
*
* @return The number of ProtocolCommandListeners currently registered.
***/
public int getListenerCount() {
return __listeners.getListenerCount();
}
} }

@ -17,7 +17,6 @@
package org.apache.commons.net.ftp; package org.apache.commons.net.ftp;
/** /**
* This interface adds the aspect of configurability by means of * This interface adds the aspect of configurability by means of
* a supplied FTPClientConfig object to other classes in the * a supplied FTPClientConfig object to other classes in the
@ -25,11 +24,11 @@ package org.apache.commons.net.ftp;
*/ */
public interface Configurable { public interface Configurable {
/** /**
* @param config the object containing the configuration data * @param config the object containing the configuration data
* @throws IllegalArgumentException if the elements of the * @throws IllegalArgumentException if the elements of the
* <code>config</code> are somehow inadequate to configure the * <code>config</code> are somehow inadequate to configure the
* Configurable object. * Configurable object.
*/ */
public void configure(FTPClientConfig config); public void configure(FTPClientConfig config);
} }

@ -19,98 +19,57 @@
package org.apache.commons.net.ftp; package org.apache.commons.net.ftp;
/** /**
* @since 3.3 * @since 3.3
*/ */
public enum FTPCmd { public enum FTPCmd {
ABOR, 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,;
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 // Aliases
public static final FTPCmd ABORT = ABOR; public static final FTPCmd ABORT = ABOR;
public static final FTPCmd ACCOUNT = ACCT; public static final FTPCmd ACCOUNT = ACCT;
public static final FTPCmd ALLOCATE = ALLO; public static final FTPCmd ALLOCATE = ALLO;
public static final FTPCmd APPEND = APPE; public static final FTPCmd APPEND = APPE;
public static final FTPCmd CHANGE_TO_PARENT_DIRECTORY = CDUP; public static final FTPCmd CHANGE_TO_PARENT_DIRECTORY = CDUP;
public static final FTPCmd CHANGE_WORKING_DIRECTORY = CWD; public static final FTPCmd CHANGE_WORKING_DIRECTORY = CWD;
public static final FTPCmd DATA_PORT = PORT; public static final FTPCmd DATA_PORT = PORT;
public static final FTPCmd DELETE = DELE; public static final FTPCmd DELETE = DELE;
public static final FTPCmd FEATURES = FEAT; public static final FTPCmd FEATURES = FEAT;
public static final FTPCmd FILE_STRUCTURE = STRU; public static final FTPCmd FILE_STRUCTURE = STRU;
public static final FTPCmd GET_MOD_TIME = MDTM; public static final FTPCmd GET_MOD_TIME = MDTM;
public static final FTPCmd LOGOUT = QUIT; public static final FTPCmd LOGOUT = QUIT;
public static final FTPCmd MAKE_DIRECTORY = MKD; public static final FTPCmd MAKE_DIRECTORY = MKD;
public static final FTPCmd MOD_TIME = MDTM; public static final FTPCmd MOD_TIME = MDTM;
public static final FTPCmd NAME_LIST = NLST; public static final FTPCmd NAME_LIST = NLST;
public static final FTPCmd PASSIVE = PASV; public static final FTPCmd PASSIVE = PASV;
public static final FTPCmd PASSWORD = PASS; public static final FTPCmd PASSWORD = PASS;
public static final FTPCmd PRINT_WORKING_DIRECTORY = PWD; public static final FTPCmd PRINT_WORKING_DIRECTORY = PWD;
public static final FTPCmd REINITIALIZE = REIN; public static final FTPCmd REINITIALIZE = REIN;
public static final FTPCmd REMOVE_DIRECTORY = RMD; public static final FTPCmd REMOVE_DIRECTORY = RMD;
public static final FTPCmd RENAME_FROM = RNFR; public static final FTPCmd RENAME_FROM = RNFR;
public static final FTPCmd RENAME_TO = RNTO; public static final FTPCmd RENAME_TO = RNTO;
public static final FTPCmd REPRESENTATION_TYPE = TYPE; public static final FTPCmd REPRESENTATION_TYPE = TYPE;
public static final FTPCmd RESTART = REST; public static final FTPCmd RESTART = REST;
public static final FTPCmd RETRIEVE = RETR; public static final FTPCmd RETRIEVE = RETR;
public static final FTPCmd SET_MOD_TIME = MFMT; public static final FTPCmd SET_MOD_TIME = MFMT;
public static final FTPCmd SITE_PARAMETERS = SITE; public static final FTPCmd SITE_PARAMETERS = SITE;
public static final FTPCmd STATUS = STAT; public static final FTPCmd STATUS = STAT;
public static final FTPCmd STORE = STOR; public static final FTPCmd STORE = STOR;
public static final FTPCmd STORE_UNIQUE = STOU; public static final FTPCmd STORE_UNIQUE = STOU;
public static final FTPCmd STRUCTURE_MOUNT = SMNT; public static final FTPCmd STRUCTURE_MOUNT = SMNT;
public static final FTPCmd SYSTEM = SYST; public static final FTPCmd SYSTEM = SYST;
public static final FTPCmd TRANSFER_MODE = MODE; public static final FTPCmd TRANSFER_MODE = MODE;
public static final FTPCmd USERNAME = USER; public static final FTPCmd USERNAME = USER;
/** /**
* Retrieve the FTP protocol command string corresponding to a specified * Retrieve the FTP protocol command string corresponding to a specified
* command code. * command code.
* *
* @return The FTP protcol command string corresponding to a specified * @return The FTP protcol command string corresponding to a specified
* command code. * command code.
*/ */
public final String getCommand() public final String getCommand() {
{ return this.name();
return this.name(); }
}
} }

@ -27,145 +27,144 @@ package org.apache.commons.net.ftp;
* *
* @deprecated use {@link FTPCmd} instead * @deprecated use {@link FTPCmd} instead
*/ */
@Deprecated @Deprecated public final class FTPCommand {
public final class FTPCommand
{
public static final int USER = 0; public static final int USER = 0;
public static final int PASS = 1; public static final int PASS = 1;
public static final int ACCT = 2; public static final int ACCT = 2;
public static final int CWD = 3; public static final int CWD = 3;
public static final int CDUP = 4; public static final int CDUP = 4;
public static final int SMNT = 5; public static final int SMNT = 5;
public static final int REIN = 6; public static final int REIN = 6;
public static final int QUIT = 7; public static final int QUIT = 7;
public static final int PORT = 8; public static final int PORT = 8;
public static final int PASV = 9; public static final int PASV = 9;
public static final int TYPE = 10; public static final int TYPE = 10;
public static final int STRU = 11; public static final int STRU = 11;
public static final int MODE = 12; public static final int MODE = 12;
public static final int RETR = 13; public static final int RETR = 13;
public static final int STOR = 14; public static final int STOR = 14;
public static final int STOU = 15; public static final int STOU = 15;
public static final int APPE = 16; public static final int APPE = 16;
public static final int ALLO = 17; public static final int ALLO = 17;
public static final int REST = 18; public static final int REST = 18;
public static final int RNFR = 19; public static final int RNFR = 19;
public static final int RNTO = 20; public static final int RNTO = 20;
public static final int ABOR = 21; public static final int ABOR = 21;
public static final int DELE = 22; public static final int DELE = 22;
public static final int RMD = 23; public static final int RMD = 23;
public static final int MKD = 24; public static final int MKD = 24;
public static final int PWD = 25; public static final int PWD = 25;
public static final int LIST = 26; public static final int LIST = 26;
public static final int NLST = 27; public static final int NLST = 27;
public static final int SITE = 28; public static final int SITE = 28;
public static final int SYST = 29; public static final int SYST = 29;
public static final int STAT = 30; public static final int STAT = 30;
public static final int HELP = 31; public static final int HELP = 31;
public static final int NOOP = 32; public static final int NOOP = 32;
/** @since 2.0 */ /** @since 2.0 */
public static final int MDTM = 33; public static final int MDTM = 33;
/** @since 2.2 */ /** @since 2.2 */
public static final int FEAT = 34; public static final int FEAT = 34;
/** @since 2.2 */ /** @since 2.2 */
public static final int MFMT = 35; public static final int MFMT = 35;
/** @since 2.2 */ /** @since 2.2 */
public static final int EPSV = 36; public static final int EPSV = 36;
/** @since 2.2 */ /** @since 2.2 */
public static final int EPRT = 37; public static final int EPRT = 37;
/** /**
* Machine parseable list for a directory * Machine parseable list for a directory
* @since 3.0 *
*/ * @since 3.0
public static final int MLSD = 38; */
public static final int MLSD = 38;
/** /**
* Machine parseable list for a single file * Machine parseable list for a single file
* @since 3.0 *
*/ * @since 3.0
public static final int MLST = 39; */
public static final int MLST = 39;
// Must agree with final entry above; used to check array size // Must agree with final entry above; used to check array size
private static final int LAST = MLST; private static final int LAST = MLST;
public static final int USERNAME = USER; public static final int USERNAME = USER;
public static final int PASSWORD = PASS; public static final int PASSWORD = PASS;
public static final int ACCOUNT = ACCT; public static final int ACCOUNT = ACCT;
public static final int CHANGE_WORKING_DIRECTORY = CWD; public static final int CHANGE_WORKING_DIRECTORY = CWD;
public static final int CHANGE_TO_PARENT_DIRECTORY = CDUP; public static final int CHANGE_TO_PARENT_DIRECTORY = CDUP;
public static final int STRUCTURE_MOUNT = SMNT; public static final int STRUCTURE_MOUNT = SMNT;
public static final int REINITIALIZE = REIN; public static final int REINITIALIZE = REIN;
public static final int LOGOUT = QUIT; public static final int LOGOUT = QUIT;
public static final int DATA_PORT = PORT; public static final int DATA_PORT = PORT;
public static final int PASSIVE = PASV; public static final int PASSIVE = PASV;
public static final int REPRESENTATION_TYPE = TYPE; public static final int REPRESENTATION_TYPE = TYPE;
public static final int FILE_STRUCTURE = STRU; public static final int FILE_STRUCTURE = STRU;
public static final int TRANSFER_MODE = MODE; public static final int TRANSFER_MODE = MODE;
public static final int RETRIEVE = RETR; public static final int RETRIEVE = RETR;
public static final int STORE = STOR; public static final int STORE = STOR;
public static final int STORE_UNIQUE = STOU; public static final int STORE_UNIQUE = STOU;
public static final int APPEND = APPE; public static final int APPEND = APPE;
public static final int ALLOCATE = ALLO; public static final int ALLOCATE = ALLO;
public static final int RESTART = REST; public static final int RESTART = REST;
public static final int RENAME_FROM = RNFR; public static final int RENAME_FROM = RNFR;
public static final int RENAME_TO = RNTO; public static final int RENAME_TO = RNTO;
public static final int ABORT = ABOR; public static final int ABORT = ABOR;
public static final int DELETE = DELE; public static final int DELETE = DELE;
public static final int REMOVE_DIRECTORY = RMD; public static final int REMOVE_DIRECTORY = RMD;
public static final int MAKE_DIRECTORY = MKD; public static final int MAKE_DIRECTORY = MKD;
public static final int PRINT_WORKING_DIRECTORY = PWD; public static final int PRINT_WORKING_DIRECTORY = PWD;
// public static final int LIST = LIST; // public static final int LIST = LIST;
public static final int NAME_LIST = NLST; public static final int NAME_LIST = NLST;
public static final int SITE_PARAMETERS = SITE; public static final int SITE_PARAMETERS = SITE;
public static final int SYSTEM = SYST; public static final int SYSTEM = SYST;
public static final int STATUS = STAT; public static final int STATUS = STAT;
//public static final int HELP = HELP; //public static final int HELP = HELP;
//public static final int NOOP = NOOP; //public static final int NOOP = NOOP;
/** @since 2.0 */ /** @since 2.0 */
public static final int MOD_TIME = MDTM; public static final int MOD_TIME = MDTM;
/** @since 2.2 */ /** @since 2.2 */
public static final int FEATURES = FEAT; public static final int FEATURES = FEAT;
/** @since 2.2 */ /** @since 2.2 */
public static final int GET_MOD_TIME = MDTM; public static final int GET_MOD_TIME = MDTM;
/** @since 2.2 */ /** @since 2.2 */
public static final int SET_MOD_TIME = MFMT; public static final int SET_MOD_TIME = MFMT;
// Cannot be instantiated // Cannot be instantiated
private FTPCommand() private FTPCommand() {
{} }
private static final String[] _commands = { private static final String[] _commands = {
"USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT", "PORT", "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT", "PORT", "PASV", "TYPE", "STRU",
"PASV", "TYPE", "STRU", "MODE", "RETR", "STOR", "STOU", "APPE", "ALLO", "MODE", "RETR", "STOR", "STOU", "APPE", "ALLO", "REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD",
"REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", "MKD", "PWD", "LIST", "MKD", "PWD", "LIST", "NLST", "SITE", "SYST", "STAT", "HELP", "NOOP", "MDTM", "FEAT", "MFMT",
"NLST", "SITE", "SYST", "STAT", "HELP", "NOOP", "MDTM", "FEAT", "MFMT", "EPSV", "EPRT", "MLSD", "MLST"
"EPSV", "EPRT", "MLSD", "MLST" }; };
// default access needed for Unit test
static void checkArray() {
// default access needed for Unit test int expectedLength = LAST + 1;
static void checkArray(){ if (_commands.length != expectedLength) {
int expectedLength = LAST+1; throw new RuntimeException("Incorrect _commands array. Should have length "
if (_commands.length != expectedLength) { + expectedLength
throw new RuntimeException("Incorrect _commands array. Should have length " + " found "
+expectedLength+" found "+_commands.length); + _commands.length);
}
} }
}
/** /**
* Retrieve the FTP protocol command string corresponding to a specified * Retrieve the FTP protocol command string corresponding to a specified
* command code. * command code.
* *
* @param command The command code. * @param command The command code.
* @return The FTP protcol command string corresponding to a specified * @return The FTP protcol command string corresponding to a specified
* command code. * command code.
*/ */
public static final String getCommand(int command) public static final String getCommand(int command) {
{ return _commands[command];
return _commands[command]; }
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp; package org.apache.commons.net.ftp;
import java.io.IOException; import java.io.IOException;
/*** /***
@ -31,25 +32,21 @@ import java.io.IOException;
* @see FTPClient * @see FTPClient
***/ ***/
public class FTPConnectionClosedException extends IOException public class FTPConnectionClosedException extends IOException {
{
private static final long serialVersionUID = 3500547241659379952L;
/*** Constructs a FTPConnectionClosedException with no message ***/ private static final long serialVersionUID = 3500547241659379952L;
public FTPConnectionClosedException()
{
super();
}
/*** /*** Constructs a FTPConnectionClosedException with no message ***/
* Constructs a FTPConnectionClosedException with a specified message. public FTPConnectionClosedException() {
* super();
* @param message The message explaining the reason for the exception. }
***/
public FTPConnectionClosedException(String message)
{
super(message);
}
/***
* Constructs a FTPConnectionClosedException with a specified message.
*
* @param message The message explaining the reason for the exception.
***/
public FTPConnectionClosedException(String message) {
super(message);
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp; package org.apache.commons.net.ftp;
import java.io.Serializable; import java.io.Serializable;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -30,507 +31,463 @@ import java.util.TimeZone;
* @see FTPClient#listFiles * @see FTPClient#listFiles
***/ ***/
public class FTPFile implements Serializable public class FTPFile implements Serializable {
{ private static final long serialVersionUID = 9010790363003271996L;
private static final long serialVersionUID = 9010790363003271996L;
/** A constant indicating an FTPFile is a file. ***/
/** A constant indicating an FTPFile is a file. ***/ public static final int FILE_TYPE = 0;
public static final int FILE_TYPE = 0; /** A constant indicating an FTPFile is a directory. ***/
/** A constant indicating an FTPFile is a directory. ***/ public static final int DIRECTORY_TYPE = 1;
public static final int DIRECTORY_TYPE = 1; /** A constant indicating an FTPFile is a symbolic link. ***/
/** A constant indicating an FTPFile is a symbolic link. ***/ public static final int SYMBOLIC_LINK_TYPE = 2;
public static final int SYMBOLIC_LINK_TYPE = 2; /** A constant indicating an FTPFile is of unknown type. ***/
/** A constant indicating an FTPFile is of unknown type. ***/ public static final int UNKNOWN_TYPE = 3;
public static final int UNKNOWN_TYPE = 3;
/** A constant indicating user access permissions. ***/
/** A constant indicating user access permissions. ***/ public static final int USER_ACCESS = 0;
public static final int USER_ACCESS = 0; /** A constant indicating group access permissions. ***/
/** A constant indicating group access permissions. ***/ public static final int GROUP_ACCESS = 1;
public static final int GROUP_ACCESS = 1; /** A constant indicating world access permissions. ***/
/** A constant indicating world access permissions. ***/ public static final int WORLD_ACCESS = 2;
public static final int WORLD_ACCESS = 2;
/** A constant indicating file/directory read permission. ***/
/** A constant indicating file/directory read permission. ***/ public static final int READ_PERMISSION = 0;
public static final int READ_PERMISSION = 0; /** A constant indicating file/directory write permission. ***/
/** A constant indicating file/directory write permission. ***/ public static final int WRITE_PERMISSION = 1;
public static final int WRITE_PERMISSION = 1; /**
/** * A constant indicating file execute permission or directory listing
* A constant indicating file execute permission or directory listing * permission.
* permission. ***/
***/ public static final int EXECUTE_PERMISSION = 2;
public static final int EXECUTE_PERMISSION = 2;
private int _type, _hardLinkCount;
private int _type, _hardLinkCount; private long _size;
private long _size; private String _rawListing, _user, _group, _name, _link;
private String _rawListing, _user, _group, _name, _link; private Calendar _date;
private Calendar _date; // If this is null, then list entry parsing failed
// If this is null, then list entry parsing failed private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
/*** Creates an empty FTPFile. ***/
/*** Creates an empty FTPFile. ***/ public FTPFile() {
public FTPFile() _permissions = new boolean[3][3];
{ _type = UNKNOWN_TYPE;
_permissions = new boolean[3][3]; // init these to values that do not occur in listings
_type = UNKNOWN_TYPE; // so can distinguish which fields are unset
// init these to values that do not occur in listings _hardLinkCount = 0; // 0 is invalid as a link count
// so can distinguish which fields are unset _size = -1; // 0 is valid, so use -1
_hardLinkCount = 0; // 0 is invalid as a link count _user = "";
_size = -1; // 0 is valid, so use -1 _group = "";
_user = ""; _date = null;
_group = ""; _name = null;
_date = null; }
_name = null;
} /**
* Constructor for use by {@link FTPListParseEngine} only.
/** * Used to create FTPFile entries for failed parses
* Constructor for use by {@link FTPListParseEngine} only. *
* Used to create FTPFile entries for failed parses * @param rawListing line that could not be parsed.
* @param rawListing line that could not be parsed. * @since 3.4
* @since 3.4 */
*/ FTPFile(String rawListing) {
FTPFile(String rawListing) _permissions = null; // flag that entry is invalid
{ _rawListing = rawListing;
_permissions = null; // flag that entry is invalid _type = UNKNOWN_TYPE;
_rawListing = rawListing; // init these to values that do not occur in listings
_type = UNKNOWN_TYPE; // so can distinguish which fields are unset
// init these to values that do not occur in listings _hardLinkCount = 0; // 0 is invalid as a link count
// so can distinguish which fields are unset _size = -1; // 0 is valid, so use -1
_hardLinkCount = 0; // 0 is invalid as a link count _user = "";
_size = -1; // 0 is valid, so use -1 _group = "";
_user = ""; _date = null;
_group = ""; _name = null;
_date = null; }
_name = null;
} /***
* Set the original FTP server raw listing from which the FTPFile was
* created.
/*** *
* Set the original FTP server raw listing from which the FTPFile was * @param rawListing The raw FTP server listing.
* created. ***/
* public void setRawListing(String rawListing) {
* @param rawListing The raw FTP server listing. _rawListing = rawListing;
***/ }
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.
* Get the original FTP server raw listing used to initialize the FTPFile. ***/
* public String getRawListing() {
* @return The original FTP server raw listing used to initialize the return _rawListing;
* FTPFile. }
***/
public String getRawListing() /***
{ * Determine if the file is a directory.
return _rawListing; *
} * @return True if the file is of type <code>DIRECTORY_TYPE</code>, false if
* not.
***/
/*** public boolean isDirectory() {
* Determine if the file is a directory. return (_type == DIRECTORY_TYPE);
* }
* @return True if the file is of type <code>DIRECTORY_TYPE</code>, false if
* not. /***
***/ * Determine if the file is a regular file.
public boolean isDirectory() *
{ * @return True if the file is of type <code>FILE_TYPE</code>, false if
return (_type == DIRECTORY_TYPE); * not.
} ***/
public boolean isFile() {
/*** return (_type == FILE_TYPE);
* Determine if the file is a regular file. }
*
* @return True if the file is of type <code>FILE_TYPE</code>, false if /***
* not. * Determine if the file is a symbolic link.
***/ *
public boolean isFile() * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if
{ * not.
return (_type == FILE_TYPE); ***/
} public boolean isSymbolicLink() {
return (_type == SYMBOLIC_LINK_TYPE);
/*** }
* Determine if the file is a symbolic link.
* /***
* @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if * Determine if the type of the file is unknown.
* not. *
***/ * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if
public boolean isSymbolicLink() * not.
{ ***/
return (_type == SYMBOLIC_LINK_TYPE); public boolean isUnknown() {
} return (_type == UNKNOWN_TYPE);
}
/***
* Determine if the type of the file is unknown. /**
* * Used to indicate whether an entry is valid or not.
* @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if * If the entry is invalid, only the {@link #getRawListing()} method will be useful.
* not. * Other methods may fail.
***/ *
public boolean isUnknown() * Used in conjunction with list parsing that preseverves entries that failed to parse.
{ *
return (_type == UNKNOWN_TYPE); * @return true if the entry is valid
} * @see FTPClientConfig#setUnparseableEntries(boolean)
* @since 3.4
/** */
* Used to indicate whether an entry is valid or not. public boolean isValid() {
* If the entry is invalid, only the {@link #getRawListing()} method will be useful. return (_permissions != null);
* Other methods may fail. }
*
* Used in conjunction with list parsing that preseverves entries that failed to parse. /***
* @see FTPClientConfig#setUnparseableEntries(boolean) * Set the type of the file (<code>DIRECTORY_TYPE</code>,
* @return true if the entry is valid * <code>FILE_TYPE</code>, etc.).
* @since 3.4 *
*/ * @param type The integer code representing the type of the file.
public boolean isValid() { ***/
return (_permissions != null); public void setType(int type) {
} _type = type;
}
/***
* Set the type of the file (<code>DIRECTORY_TYPE</code>, /***
* <code>FILE_TYPE</code>, etc.). * 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.
* @param type The integer code representing the type of the file. *
***/ * @return The type of the file.
public void setType(int type) ***/
{ public int getType() {
_type = type; return _type;
} }
/***
/*** * Set the name of the file.
* 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. * @param name The name of the file.
* ***/
* @return The type of the file. public void setName(String name) {
***/ _name = name;
public int getType() }
{
return _type; /***
} * Return the name of the file.
*
* @return The name of the file.
/*** ***/
* Set the name of the file. public String getName() {
* return _name;
* @param name The name of the file. }
***/
public void setName(String name) /**
{ * Set the file size in bytes.
_name = name; *
} * @param size The file size in bytes.
*/
/*** public void setSize(long size) {
* Return the name of the file. _size = size;
* }
* @return The name of the file.
***/ /***
public String getName() * Return the file size in bytes.
{ *
return _name; * @return The file size in bytes.
} ***/
public long getSize() {
return _size;
/** }
* Set the file size in bytes.
* @param size The file size in bytes. /***
*/ * Set the number of hard links to this file. This is not to be
public void setSize(long size) * confused with symbolic links.
{ *
_size = size; * @param links The number of hard links to this file.
} ***/
public void setHardLinkCount(int links) {
_hardLinkCount = links;
/*** }
* Return the file size in bytes.
* /***
* @return The file size in bytes. * Return the number of hard links to this file. This is not to be
***/ * confused with symbolic links.
public long getSize() *
{ * @return The number of hard links to this file.
return _size; ***/
} public int getHardLinkCount() {
return _hardLinkCount;
}
/***
* Set the number of hard links to this file. This is not to be /***
* confused with symbolic links. * Set the name of the group owning the file. This may be
* * a string representation of the group number.
* @param links The number of hard links to this file. *
***/ * @param group The name of the group owning the file.
public void setHardLinkCount(int links) ***/
{ public void setGroup(String group) {
_hardLinkCount = links; _group = group;
} }
/***
/*** * Returns the name of the group owning the file. Sometimes this will be
* Return the number of hard links to this file. This is not to be * a string representation of the group number.
* confused with symbolic links. *
* * @return The name of the group owning the file.
* @return The number of hard links to this file. ***/
***/ public String getGroup() {
public int getHardLinkCount() return _group;
{ }
return _hardLinkCount;
} /***
* Set the name of the user owning the file. This may be
* a string representation of the user number;
/*** *
* Set the name of the group owning the file. This may be * @param user The name of the user owning the file.
* a string representation of the group number. ***/
* public void setUser(String user) {
* @param group The name of the group owning the file. _user = user;
***/ }
public void setGroup(String group)
{ /***
_group = group; * 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.
/*** ***/
* Returns the name of the group owning the file. Sometimes this will be public String getUser() {
* a string representation of the group number. return _user;
* }
* @return The name of the group owning the file.
***/ /***
public String getGroup() * If the FTPFile is a symbolic link, use this method to set the name of the
{ * file being pointed to by the symbolic link.
return _group; *
} * @param link The file pointed to by the symbolic link.
***/
public void setLink(String link) {
/*** _link = link;
* 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. * 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.
public void setUser(String user) *
{ * @return The file pointed to by the symbolic link (null if the FTPFile
_user = user; * is not a symbolic link).
} ***/
public String getLink() {
/*** return _link;
* 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. * Set the file timestamp. This usually the last modification time.
***/ * The parameter is not cloned, so do not alter its value after calling
public String getUser() * this method.
{ *
return _user; * @param date A Calendar instance representing the file timestamp.
} ***/
public void setTimestamp(Calendar date) {
_date = date;
/*** }
* If the FTPFile is a symbolic link, use this method to set the name of the
* file being pointed to by the symbolic link. /***
* * Returns the file timestamp. This usually the last modification time.
* @param link The file pointed to by the symbolic link. *
***/ * @return A Calendar instance representing the file timestamp.
public void setLink(String link) ***/
{ public Calendar getTimestamp() {
_link = link; return _date;
} }
/***
/*** * Set if the given access group (one of the <code> _ACCESS </code>
* If the FTPFile is a symbolic link, this method returns the name of the * constants) has the given access permission (one of the
* file being pointed to by the symbolic link. Otherwise it returns null. * <code> _PERMISSION </code> constants) to the file.
* *
* @return The file pointed to by the symbolic link (null if the FTPFile * @param access The access group (one of the <code> _ACCESS </code>
* is not a symbolic link). * constants)
***/ * @param permission The access permission (one of the
public String getLink() * <code> _PERMISSION </code> constants)
{ * @param value True if permission is allowed, false if not.
return _link; * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
} ***/
public void setPermission(int access, int permission, boolean value) {
_permissions[access][permission] = value;
/*** }
* 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. * Determines if the given access group (one of the <code> _ACCESS </code>
* * constants) has the given access permission (one of the
* @param date A Calendar instance representing the file timestamp. * <code> _PERMISSION </code> constants) to the file.
***/ *
public void setTimestamp(Calendar date) * @param access The access group (one of the <code> _ACCESS </code>
{ * constants)
_date = date; * @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.
* Returns the file timestamp. This usually the last modification time. ***/
* public boolean hasPermission(int access, int permission) {
* @return A Calendar instance representing the file timestamp. if (_permissions == null) {
***/ return false;
public Calendar getTimestamp() }
{ return _permissions[access][permission];
return _date; }
}
/***
* Returns a string representation of the FTPFile information.
/*** *
* Set if the given access group (one of the <code> _ACCESS </code> * @return A string representation of the FTPFile information.
* constants) has the given access permission (one of the */
* <code> _PERMISSION </code> constants) to the file. @Override public String toString() {
* return getRawListing();
* @param access The access group (one of the <code> _ACCESS </code> }
* constants)
* @param permission The access permission (one of the /***
* <code> _PERMISSION </code> constants) * Returns a string representation of the FTPFile information.
* @param value True if permission is allowed, false if not. * This currently mimics the Unix listing format.
* @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range * This method uses the timezone of the Calendar entry, which is
***/ * the server time zone (if one was provided) otherwise it is
public void setPermission(int access, int permission, boolean value) * the local time zone.
{ * <p>
_permissions[access][permission] = value; * 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.
* Determines if the given access group (one of the <code> _ACCESS </code> * @since 3.0
* constants) has the given access permission (one of the */
* <code> _PERMISSION </code> constants) to the file. public String toFormattedString() {
* return toFormattedString(null);
* @param access The access group (one of the <code> _ACCESS </code> }
* constants)
* @param permission The access permission (one of the /**
* <code> _PERMISSION </code> constants) * Returns a string representation of the FTPFile information.
* @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range * This currently mimics the Unix listing format.
* @return true if {@link #isValid()} is {@code true &&} the associated permission is set; * This method allows the Calendar time zone to be overridden.
* {@code false} otherwise. * <p>
***/ * Note: if the instance is not valid {@link #isValid()}, no useful
public boolean hasPermission(int access, int permission) * information can be returned. In this case, use {@link #getRawListing()}
{ * instead.
if (_permissions == null) { *
return false; * @param timezone the timezone to use for displaying the time stamp
} * If {@code null}, then use the Calendar entry timezone
return _permissions[access][permission]; * @return A string representation of the FTPFile information.
} * @since 3.4
*/
/*** public String toFormattedString(final String timezone) {
* Returns a string representation of the FTPFile information.
* if (!isValid()) {
* @return A string representation of the FTPFile information. return "[Invalid: could not parse file entry]";
*/ }
@Override StringBuilder sb = new StringBuilder();
public String toString() Formatter fmt = new Formatter(sb);
{ sb.append(formatType());
return getRawListing(); sb.append(permissionToString(USER_ACCESS));
} sb.append(permissionToString(GROUP_ACCESS));
sb.append(permissionToString(WORLD_ACCESS));
/*** fmt.format(" %4d", Integer.valueOf(getHardLinkCount()));
* Returns a string representation of the FTPFile information. fmt.format(" %-8s %-8s", getUser(), getGroup());
* This currently mimics the Unix listing format. fmt.format(" %8d", Long.valueOf(getSize()));
* This method uses the timezone of the Calendar entry, which is Calendar timestamp = getTimestamp();
* the server time zone (if one was provided) otherwise it is if (timestamp != null) {
* the local time zone. if (timezone != null) {
* <p> TimeZone newZone = TimeZone.getTimeZone(timezone);
* Note: if the instance is not valid {@link #isValid()}, no useful if (!newZone.equals(timestamp.getTimeZone())) {
* information can be returned. In this case, use {@link #getRawListing()} Date original = timestamp.getTime();
* instead. Calendar newStamp = Calendar.getInstance(newZone);
* newStamp.setTime(original);
* @return A string representation of the FTPFile information. timestamp = newStamp;
* @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); fmt.format(" %1$tY-%1$tm-%1$td", timestamp);
sb.append(formatType()); // Only display time units if they are present
sb.append(permissionToString(USER_ACCESS)); if (timestamp.isSet(Calendar.HOUR_OF_DAY)) {
sb.append(permissionToString(GROUP_ACCESS)); fmt.format(" %1$tH", timestamp);
sb.append(permissionToString(WORLD_ACCESS)); if (timestamp.isSet(Calendar.MINUTE)) {
fmt.format(" %4d", Integer.valueOf(getHardLinkCount())); fmt.format(":%1$tM", timestamp);
fmt.format(" %-8s %-8s", getUser(), getGroup()); if (timestamp.isSet(Calendar.SECOND)) {
fmt.format(" %8d", Long.valueOf(getSize())); fmt.format(":%1$tS", timestamp);
Calendar timestamp = getTimestamp(); if (timestamp.isSet(Calendar.MILLISECOND)) {
if (timestamp != null) { fmt.format(".%1$tL", timestamp);
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)) { fmt.format(" %1$tZ", timestamp);
sb.append('x'); }
} else { }
sb.append('-'); sb.append(' ');
} sb.append(getName());
return sb.toString(); 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();
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp; package org.apache.commons.net.ftp;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -78,50 +79,44 @@ import java.util.List;
* @see FTPFile * @see FTPFile
* @see FTPClient#listFiles() * @see FTPClient#listFiles()
*/ */
public interface FTPFileEntryParser public interface FTPFileEntryParser {
{ /**
/** * Parses a line of an FTP server file listing and converts it into a usable
* 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
* 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
* file listing line doesn't describe a file, <code> null </code> should be * returned, otherwise a <code> FTPFile </code> instance representing the
* returned, otherwise a <code> FTPFile </code> instance representing the * files in the directory is returned.
* files in the directory is returned. *
* * @param listEntry A line of text from the file listing
* @param listEntry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry
* @return An FTPFile instance corresponding to the supplied entry */
*/ FTPFile parseFTPEntry(String listEntry);
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);
/**
* 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);
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp; package org.apache.commons.net.ftp;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -24,50 +25,42 @@ import java.util.List;
* This abstract class implements both the older FTPFileListParser and * This abstract class implements both the older FTPFileListParser and
* newer FTPFileEntryParser interfaces with default functionality. * newer FTPFileEntryParser interfaces with default functionality.
* All the classes in the parser subpackage inherit from this. * All the classes in the parser subpackage inherit from this.
*
*/ */
public abstract class FTPFileEntryParserImpl public abstract class FTPFileEntryParserImpl implements FTPFileEntryParser {
implements FTPFileEntryParser /**
{ * The constructor for a FTPFileEntryParserImpl object.
/** */
* The constructor for a FTPFileEntryParserImpl object. public FTPFileEntryParserImpl() {
*/ }
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();
}
/** /**
* Reads the next entry using the supplied BufferedReader object up to * This method is a hook for those implementors (such as
* whatever delimits one entry from the next. This default implementation * VMSVersioningFTPEntryParser, and possibly others) which need to
* simply calls BufferedReader.readLine(). * perform some action upon the FTPFileList after it has been created
* * from the server stream, but before any clients see the list.
* @param reader The BufferedReader object from which entries are to be *
* read. * This default implementation does nothing.
* *
* @return A string representing the next ftp entry or null if none found. * @param original Original list after it has been created from the server stream
* @throws IOException thrown on any IO Error reading from the reader. * @return <code>original</code> unmodified.
*/ */
@Override @Override public List<String> preParse(List<String> original) {
public String readNextEntry(BufferedReader reader) throws IOException return original;
{ }
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 /* Emacs configuration

@ -20,14 +20,15 @@ package org.apache.commons.net.ftp;
/** /**
* Perform filtering on FTPFile entries. * Perform filtering on FTPFile entries.
*
* @since 2.2 * @since 2.2
*/ */
public interface FTPFileFilter { public interface FTPFileFilter {
/** /**
* Checks if an FTPFile entry should be included or not. * Checks if an FTPFile entry should be included or not.
* *
* @param file entry to be checked for inclusion. May be <code>null</code>. * @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 * @return <code>true</code> if the file is to be included, <code>false</code> otherwise
*/ */
public boolean accept(FTPFile file); public boolean accept(FTPFile file);
} }

@ -20,38 +20,35 @@ package org.apache.commons.net.ftp;
/** /**
* Implements some simple FTPFileFilter classes. * Implements some simple FTPFileFilter classes.
*
* @since 2.2 * @since 2.2
*/ */
public class FTPFileFilters { public class FTPFileFilters {
/** /**
* Accepts all FTPFile entries, including null. * Accepts all FTPFile entries, including null.
*/ */
public static final FTPFileFilter ALL = new FTPFileFilter() { public static final FTPFileFilter ALL = new FTPFileFilter() {
@Override @Override public boolean accept(FTPFile file) {
public boolean accept(FTPFile file) { return true;
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. * Accepts all non-null FTPFile entries.
*/ */
public static final FTPFileFilter DIRECTORIES = new FTPFileFilter() { public static final FTPFileFilter NON_NULL = new FTPFileFilter() {
@Override @Override public boolean accept(FTPFile file) {
public boolean accept(FTPFile file) { return file != null;
return file != null && file.isDirectory(); }
} };
};
/**
* 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();
}
};
} }

@ -38,165 +38,157 @@ import org.apache.commons.net.util.Base64;
* @since 2.2 * @since 2.2
*/ */
public class FTPHTTPClient extends FTPClient { public class FTPHTTPClient extends FTPClient {
private final String proxyHost; private final String proxyHost;
private final int proxyPort; private final int proxyPort;
private final String proxyUsername; private final String proxyUsername;
private final String proxyPassword; private final String proxyPassword;
private static final byte[] CRLF={'\r', '\n'}; private static final byte[] CRLF = { '\r', '\n' };
private final Base64 base64 = new Base64(); private final Base64 base64 = new Base64();
private String tunnelHost; // Save the host when setting up a tunnel (needed for EPSV) private String tunnelHost; // Save the host when setting up a tunnel (needed for EPSV)
public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) {
this.proxyHost = proxyHost; this.proxyHost = proxyHost;
this.proxyPort = proxyPort; this.proxyPort = proxyPort;
this.proxyUsername = proxyUser; this.proxyUsername = proxyUser;
this.proxyPassword = proxyPass; this.proxyPassword = proxyPass;
this.tunnelHost = null; 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");
} }
public FTPHTTPClient(String proxyHost, int proxyPort) { final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address;
this(proxyHost, proxyPort, null, null); 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;
* {@inheritDoc} line = reader.readLine()) {
* response.add(line);
* @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);
} }
/** int size = response.size();
* {@inheritDoc} if (size == 0) {
* throw new IOException("No response from proxy");
* @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 String code = null;
public void connect(String host, int port) throws SocketException, IOException { String resp = response.get(0);
if (resp.startsWith("HTTP/") && resp.length() >= 12) {
_socket_ = _socketFactory_.createSocket(proxyHost, proxyPort); code = resp.substring(9, 12);
_input_ = _socket_.getInputStream(); } else {
_output_ = _socket_.getOutputStream(); throw new IOException("Invalid response from proxy: " + resp);
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, if (!"200".equals(code)) {
UnsupportedEncodingException { StringBuilder msg = new StringBuilder();
final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1"; msg.append("HTTPTunnelConnector: connection failed\r\n");
final String hostString = "Host: " + host + ":" + port; msg.append("Response received from the proxy:\r\n");
for (String line : response) {
this.tunnelHost = host; msg.append(line);
output.write(connectString.getBytes("UTF-8")); // TODO what is the correct encoding? msg.append("\r\n");
output.write(CRLF); }
output.write(hostString.getBytes("UTF-8")); throw new IOException(msg.toString());
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;
} }
return reader;
}
} }

@ -29,7 +29,6 @@ import java.util.ListIterator;
import org.apache.commons.net.util.Charsets; import org.apache.commons.net.util.Charsets;
/** /**
* This class handles the entire process of parsing a listing of * This class handles the entire process of parsing a listing of
* file entries from the server. * file entries from the server.
@ -73,257 +72,240 @@ import org.apache.commons.net.util.Charsets;
* <p> * <p>
* For unpaged access, simply use FTPClient.listFiles(). That method * For unpaged access, simply use FTPClient.listFiles(). That method
* uses this class transparently. * uses this class transparently.
*
* @version $Id: FTPListParseEngine.java 1747119 2016-06-07 02:22:24Z ggregory $ * @version $Id: FTPListParseEngine.java 1747119 2016-06-07 02:22:24Z ggregory $
*/ */
public class FTPListParseEngine { public class FTPListParseEngine {
private List<String> entries = new LinkedList<String>(); private List<String> entries = new LinkedList<String>();
private ListIterator<String> _internalIterator = entries.listIterator(); 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) { private final FTPFileEntryParser parser;
this(parser, null); // Should invalid files (parse failures) be allowed?
} private final boolean saveUnparseableEntries;
/** public FTPListParseEngine(FTPFileEntryParser parser) {
* Intended for use by FTPClient only this(parser, null);
* @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 * Intended for use by FTPClient only
* the server. After this method has completed, this object will contain *
* a list of unparsed entries (Strings) each referring to a unique file * @since 3.4
* on the server. */
* FTPListParseEngine(FTPFileEntryParser parser, FTPClientConfig configuration) {
* @param stream input stream provided by the server socket. this.parser = parser;
* @param encoding the encoding to be used for reading the stream if (configuration != null) {
* this.saveUnparseableEntries = configuration.getUnparseableEntries();
* @throws IOException } else {
* thrown on any failure to read from the sever. this.saveUnparseableEntries = false;
*/
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. * handle the initial reading and preparsing of the list returned by
* After this method has completed, <code>entries</code> will contain a * the server. After this method has completed, this object will contain
* collection of entries (as defined by * a list of unparsed entries (Strings) each referring to a unique file
* <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain * on the server.
* various non-entry preliminary lines from the server output, duplicates, *
* and other data that will not be part of the final listing. * @param stream input stream provided by the server socket.
* * @param encoding the encoding to be used for reading the stream
* @param stream The socket stream on which the input will be read. * @throws IOException thrown on any failure to read from the sever.
* @param encoding The encoding to use. */
* public void readServerList(InputStream stream, String encoding) throws IOException {
* @throws IOException this.entries = new LinkedList<String>();
* thrown on any failure to read the stream readStream(stream, encoding);
*/ this.parser.preParse(this.entries);
private void readStream(InputStream stream, String encoding) throws IOException resetIterator();
{ }
BufferedReader reader = new BufferedReader(
new InputStreamReader(stream, Charsets.toCharset(encoding)));
String line = this.parser.readNextEntry(reader);
while (line != null) /**
{ * Internal method for reading the input into the <code>entries</code> list.
this.entries.add(line); * After this method has completed, <code>entries</code> will contain a
line = this.parser.readNextEntry(reader); * collection of entries (as defined by
} * <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain
reader.close(); * 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);
* 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()]);
while (line != null) {
this.entries.add(line);
line = this.parser.readNextEntry(reader);
} }
reader.close();
}
/** /**
* Returns an array of at most <code>quantityRequested</code> FTPFile * Returns an array of at most <code>quantityRequested</code> FTPFile
* objects starting at this object's internal iterator's current position, * 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
* If fewer than <code>quantityRequested</code> such * to the number of entries at and after after the current position.
* elements are available, the returned array will have a length equal * If no such entries are found, this array will have a length of 0.
* 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.
* 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
* @param quantityRequested * objects starting at the current position of this iterator within its
* the maximum number of entries we want to get. * list and at least the number of elements which exist in the list at
* * and after its current position.
* @return an array of at most <code>quantityRequested</code> FTPFile * <p><b>
* objects starting at the current position of this iterator within its * NOTE:</b> This array may contain null members if any of the
* list and at least the number of elements which exist in the list at * individual file listings failed to parse. The caller should
* and after its current position. This array will be in the same order * check each entry for null before referencing it.
* as the underlying list (not reversed). */
* <p><b> public FTPFile[] getNext(int quantityRequested) {
* NOTE:</b> This array may contain null members if any of the List<FTPFile> tmpResults = new LinkedList<FTPFile>();
* individual file listings failed to parse. The caller should int count = quantityRequested;
* check each entry for null before referencing it. while (count > 0 && this._internalIterator.hasNext()) {
*/ String entry = this._internalIterator.next();
public FTPFile[] getPrevious(int quantityRequested) { FTPFile temp = this.parser.parseFTPEntry(entry);
List<FTPFile> tmpResults = new LinkedList<FTPFile>(); if (temp == null && saveUnparseableEntries) {
int count = quantityRequested; temp = new FTPFile(entry);
while (count > 0 && this._internalIterator.hasPrevious()) { }
String entry = this._internalIterator.previous(); tmpResults.add(temp);
FTPFile temp = this.parser.parseFTPEntry(entry); count--;
if (temp == null && saveUnparseableEntries) {
temp = new FTPFile(entry);
}
tmpResults.add(0,temp);
count--;
}
return tmpResults.toArray(new FTPFile[tmpResults.size()]);
} }
return tmpResults.toArray(new FTPFile[tmpResults.size()]);
}
/** /**
* Returns an array of FTPFile objects containing the whole list of * Returns an array of at most <code>quantityRequested</code> FTPFile
* files returned by the server as read by this object's parser. * objects starting at this object's internal iterator's current position,
* * and working back toward the beginning.
* @return an array of FTPFile objects containing the whole list of *
* files returned by the server as read by this object's parser. * If fewer than <code>quantityRequested</code> such
* None of the entries will be null * elements are available, the returned array will have a length equal
* @throws IOException - not ever thrown, may be removed in a later release * 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.
public FTPFile[] getFiles() *
throws IOException // TODO remove; not actually thrown * 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.
return getFiles(FTPFileFilters.NON_NULL); *
* @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 * Returns an array of FTPFile objects containing the whole list of
* files returned by the server as read by this object's parser. * files returned by the server as read by this object's parser.
* The files are filtered before being added to the array. *
* * @return an array of FTPFile objects containing the whole list of
* @param filter FTPFileFilter, must not be <code>null</code>. * files returned by the server as read by this object's parser.
* * None of the entries will be null
* @return an array of FTPFile objects containing the whole list of * @throws IOException - not ever thrown, may be removed in a later release
* files returned by the server as read by this object's parser. */
* <p><b> public FTPFile[] getFiles() throws IOException // TODO remove; not actually thrown
* NOTE:</b> This array may contain null members if any of the {
* individual file listings failed to parse. The caller should return getFiles(FTPFileFilters.NON_NULL);
* 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 * Returns an array of FTPFile objects containing the whole list of
* internal iterator's current position is at the end of the list. * files returned by the server as read by this object's parser.
* * The files are filtered before being added to the array.
* @return true if internal iterator is not at end of list, false *
* otherwise. * @param filter FTPFileFilter, must not be <code>null</code>.
*/ * @return an array of FTPFile objects containing the whole list of
public boolean hasNext() { * files returned by the server as read by this object's parser.
return _internalIterator.hasNext(); * <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.
* @throws IOException - not ever thrown, may be removed in a later release
* @since 2.2
*/
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 * convenience method to allow clients to know whether this object's
* internal iterator's current position is at the beginning of the list. * internal iterator's current position is at the end of the list.
* *
* @return true if internal iterator is not at beginning of list, false * @return true if internal iterator is not at end of list, false
* otherwise. * otherwise.
*/ */
public boolean hasPrevious() { public boolean hasNext() {
return _internalIterator.hasPrevious(); return _internalIterator.hasNext();
} }
/** /**
* resets this object's internal iterator to the beginning of the list. * convenience method to allow clients to know whether this object's
*/ * internal iterator's current position is at the beginning of the list.
public void resetIterator() { *
this._internalIterator = this.entries.listIterator(); * @return true if internal iterator is not at beginning of list, false
} * otherwise.
*/
public boolean hasPrevious() {
return _internalIterator.hasPrevious();
}
// DEPRECATED METHODS - for API compatibility only - DO NOT USE /**
* 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);
}
/**
* 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);
}
} }

@ -26,176 +26,168 @@ package org.apache.commons.net.ftp;
* TODO replace with an enum * TODO replace with an enum
***/ ***/
public final class FTPReply public final class FTPReply {
{
public static final int RESTART_MARKER = 110;
public static final int RESTART_MARKER = 110; public static final int SERVICE_NOT_READY = 120;
public static final int SERVICE_NOT_READY = 120; public static final int DATA_CONNECTION_ALREADY_OPEN = 125;
public static final int DATA_CONNECTION_ALREADY_OPEN = 125; public static final int FILE_STATUS_OK = 150;
public static final int FILE_STATUS_OK = 150; public static final int COMMAND_OK = 200;
public static final int COMMAND_OK = 200; public static final int COMMAND_IS_SUPERFLUOUS = 202;
public static final int COMMAND_IS_SUPERFLUOUS = 202; public static final int SYSTEM_STATUS = 211;
public static final int SYSTEM_STATUS = 211; public static final int DIRECTORY_STATUS = 212;
public static final int DIRECTORY_STATUS = 212; public static final int FILE_STATUS = 213;
public static final int FILE_STATUS = 213; public static final int HELP_MESSAGE = 214;
public static final int HELP_MESSAGE = 214; public static final int NAME_SYSTEM_TYPE = 215;
public static final int NAME_SYSTEM_TYPE = 215; public static final int SERVICE_READY = 220;
public static final int SERVICE_READY = 220; public static final int SERVICE_CLOSING_CONTROL_CONNECTION = 221;
public static final int SERVICE_CLOSING_CONTROL_CONNECTION = 221; public static final int DATA_CONNECTION_OPEN = 225;
public static final int DATA_CONNECTION_OPEN = 225; public static final int CLOSING_DATA_CONNECTION = 226;
public static final int CLOSING_DATA_CONNECTION = 226; public static final int ENTERING_PASSIVE_MODE = 227;
public static final int ENTERING_PASSIVE_MODE = 227; /** @since 2.2 */
/** @since 2.2 */ public static final int ENTERING_EPSV_MODE = 229;
public static final int ENTERING_EPSV_MODE = 229; public static final int USER_LOGGED_IN = 230;
public static final int USER_LOGGED_IN = 230; public static final int FILE_ACTION_OK = 250;
public static final int FILE_ACTION_OK = 250; public static final int PATHNAME_CREATED = 257;
public static final int PATHNAME_CREATED = 257; public static final int NEED_PASSWORD = 331;
public static final int NEED_PASSWORD = 331; public static final int NEED_ACCOUNT = 332;
public static final int NEED_ACCOUNT = 332; public static final int FILE_ACTION_PENDING = 350;
public static final int FILE_ACTION_PENDING = 350; public static final int SERVICE_NOT_AVAILABLE = 421;
public static final int SERVICE_NOT_AVAILABLE = 421; public static final int CANNOT_OPEN_DATA_CONNECTION = 425;
public static final int CANNOT_OPEN_DATA_CONNECTION = 425; public static final int TRANSFER_ABORTED = 426;
public static final int TRANSFER_ABORTED = 426; public static final int FILE_ACTION_NOT_TAKEN = 450;
public static final int FILE_ACTION_NOT_TAKEN = 450; public static final int ACTION_ABORTED = 451;
public static final int ACTION_ABORTED = 451; public static final int INSUFFICIENT_STORAGE = 452;
public static final int INSUFFICIENT_STORAGE = 452; public static final int UNRECOGNIZED_COMMAND = 500;
public static final int UNRECOGNIZED_COMMAND = 500; public static final int SYNTAX_ERROR_IN_ARGUMENTS = 501;
public static final int SYNTAX_ERROR_IN_ARGUMENTS = 501; public static final int COMMAND_NOT_IMPLEMENTED = 502;
public static final int COMMAND_NOT_IMPLEMENTED = 502; public static final int BAD_COMMAND_SEQUENCE = 503;
public static final int BAD_COMMAND_SEQUENCE = 503; public static final int COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER = 504;
public static final int COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER = 504; public static final int NOT_LOGGED_IN = 530;
public static final int NOT_LOGGED_IN = 530; public static final int NEED_ACCOUNT_FOR_STORING_FILES = 532;
public static final int NEED_ACCOUNT_FOR_STORING_FILES = 532; public static final int FILE_UNAVAILABLE = 550;
public static final int FILE_UNAVAILABLE = 550; public static final int PAGE_TYPE_UNKNOWN = 551;
public static final int PAGE_TYPE_UNKNOWN = 551; public static final int STORAGE_ALLOCATION_EXCEEDED = 552;
public static final int STORAGE_ALLOCATION_EXCEEDED = 552; public static final int FILE_NAME_NOT_ALLOWED = 553;
public static final int FILE_NAME_NOT_ALLOWED = 553;
// FTPS Reply Codes
// FTPS Reply Codes
/** @since 2.0 */
/** @since 2.0 */ public static final int SECURITY_DATA_EXCHANGE_COMPLETE = 234;
public static final int SECURITY_DATA_EXCHANGE_COMPLETE = 234; /** @since 2.0 */
/** @since 2.0 */ public static final int SECURITY_DATA_EXCHANGE_SUCCESSFULLY = 235;
public static final int SECURITY_DATA_EXCHANGE_SUCCESSFULLY = 235; /** @since 2.0 */
/** @since 2.0 */ public static final int SECURITY_MECHANISM_IS_OK = 334;
public static final int SECURITY_MECHANISM_IS_OK = 334; /** @since 2.0 */
/** @since 2.0 */ public static final int SECURITY_DATA_IS_ACCEPTABLE = 335;
public static final int SECURITY_DATA_IS_ACCEPTABLE = 335; /** @since 2.0 */
/** @since 2.0 */ public static final int UNAVAILABLE_RESOURCE = 431;
public static final int UNAVAILABLE_RESOURCE = 431; /** @since 2.2 */
/** @since 2.2 */ public static final int BAD_TLS_NEGOTIATION_OR_DATA_ENCRYPTION_REQUIRED = 522;
public static final int BAD_TLS_NEGOTIATION_OR_DATA_ENCRYPTION_REQUIRED = 522; /** @since 2.0 */
/** @since 2.0 */ public static final int DENIED_FOR_POLICY_REASONS = 533;
public static final int DENIED_FOR_POLICY_REASONS = 533; /** @since 2.0 */
/** @since 2.0 */ public static final int REQUEST_DENIED = 534;
public static final int REQUEST_DENIED = 534; /** @since 2.0 */
/** @since 2.0 */ public static final int FAILED_SECURITY_CHECK = 535;
public static final int FAILED_SECURITY_CHECK = 535; /** @since 2.0 */
/** @since 2.0 */ public static final int REQUESTED_PROT_LEVEL_NOT_SUPPORTED = 536;
public static final int REQUESTED_PROT_LEVEL_NOT_SUPPORTED = 536;
// IPv6 error codes
// IPv6 error codes // Note this is also used as an FTPS error code reply
// Note this is also used as an FTPS error code reply /** @since 2.2 */
/** @since 2.2 */ public static final int EXTENDED_PORT_FAILURE = 522;
public static final int EXTENDED_PORT_FAILURE = 522;
// Cannot be instantiated
// Cannot be instantiated private FTPReply() {
private FTPReply() }
{}
/***
/*** * Determine if a reply code is a positive preliminary response. All
* Determine if a reply code is a positive preliminary response. All * codes beginning with a 1 are positive preliminary responses.
* codes beginning with a 1 are positive preliminary responses. * Postitive preliminary responses are used to indicate tentative success.
* Postitive preliminary responses are used to indicate tentative success. * No further commands can be issued to the FTP server after a positive
* No further commands can be issued to the FTP server after a positive * preliminary response until a follow up response is received from the
* preliminary response until a follow up response is received from the * server.
* server. *
* * @param reply The reply code to test.
* @param reply The reply code to test. * @return True if a reply code is a postive preliminary response, false
* @return True if a reply code is a postive preliminary response, false * if not.
* if not. ***/
***/ public static boolean isPositivePreliminary(int reply) {
public static boolean isPositivePreliminary(int reply) return (reply >= 100 && reply < 200);
{ }
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.
* Determine if a reply code is a positive completion response. All * The FTP server will send a positive completion response on the final
* codes beginning with a 2 are positive completion responses. * successful completion of a command.
* 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
* @param reply The reply code to test. * if not.
* @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);
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
* Determine if a reply code is a positive intermediate response. All * successful completion of one part of a multi-part sequence of
* codes beginning with a 3 are positive intermediate responses. * commands. For example, after a successful USER command, a positive
* The FTP server will send a positive intermediate response on the * intermediate response will be sent to indicate that the server is
* successful completion of one part of a multi-part sequence of * ready for the PASS command.
* commands. For example, after a successful USER command, a positive *
* intermediate response will be sent to indicate that the server is * @param reply The reply code to test.
* ready for the PASS command. * @return True if a reply code is a postive intermediate response, false
* * if not.
* @param reply The reply code to test. ***/
* @return True if a reply code is a postive intermediate response, false public static boolean isPositiveIntermediate(int reply) {
* if not. return (reply >= 300 && reply < 400);
***/ }
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.
* Determine if a reply code is a negative transient response. All *
* codes beginning with a 4 are negative transient responses. * @param reply The reply code to test.
* The FTP server will send a negative transient response on the * @return True if a reply code is a negative transient response, false
* failure of a command that can be reattempted with success. * if not.
* ***/
* @param reply The reply code to test. public static boolean isNegativeTransient(int reply) {
* @return True if a reply code is a negative transient response, false return (reply >= 400 && reply < 500);
* if not. }
***/
public static boolean isNegativeTransient(int reply) /***
{ * Determine if a reply code is a negative permanent response. All
return (reply >= 400 && reply < 500); * 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.
/*** *
* Determine if a reply code is a negative permanent response. All * @param reply The reply code to test.
* codes beginning with a 5 are negative permanent responses. * @return True if a reply code is a negative permanent response, false
* The FTP server will send a negative permanent response on the * if not.
* failure of a command that cannot be reattempted with success. ***/
* public static boolean isNegativePermanent(int reply) {
* @param reply The reply code to test. return (reply >= 500 && reply < 600);
* @return True if a reply code is a negative permanent response, false }
* if not.
***/ /**
public static boolean isNegativePermanent(int reply) * Determine if a reply code is a protected response.
{ *
return (reply >= 500 && reply < 600); * @param reply The reply code to test.
} * @return True if a reply code is a protected response, false
* if not.
/** * @since 3.0
* Determine if a reply code is a protected response. */
* @param reply The reply code to test. public static boolean isProtectedReplyCode(int reply) {
* @return True if a reply code is a protected response, false // actually, only 3 protected reply codes are
* if not. // defined in RFC 2228: 631, 632 and 633.
* @since 3.0 return (reply >= 600 && reply < 700);
*/ }
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);
}
} }

@ -19,34 +19,34 @@ package org.apache.commons.net.ftp;
/** /**
* FTPS-specific commands. * FTPS-specific commands.
*
* @since 2.0 * @since 2.0
* @deprecated 3.0 DO NOT USE * @deprecated 3.0 DO NOT USE
*/ */
@Deprecated @Deprecated public final class FTPSCommand {
public final class FTPSCommand { public static final int AUTH = 0;
public static final int AUTH = 0; public static final int ADAT = 1;
public static final int ADAT = 1; public static final int PBSZ = 2;
public static final int PBSZ = 2; public static final int PROT = 3;
public static final int PROT = 3; public static final int CCC = 4;
public static final int CCC = 4;
public static final int AUTHENTICATION_SECURITY_MECHANISM = AUTH; public static final int AUTHENTICATION_SECURITY_MECHANISM = AUTH;
public static final int AUTHENTICATION_SECURITY_DATA = ADAT; public static final int AUTHENTICATION_SECURITY_DATA = ADAT;
public static final int PROTECTION_BUFFER_SIZE = PBSZ; public static final int PROTECTION_BUFFER_SIZE = PBSZ;
public static final int DATA_CHANNEL_PROTECTION_LEVEL = PROT; public static final int DATA_CHANNEL_PROTECTION_LEVEL = PROT;
public static final int CLEAR_COMMAND_CHANNEL = CCC; public static final int CLEAR_COMMAND_CHANNEL = CCC;
private static final String[] _commands = {"AUTH","ADAT","PBSZ","PROT","CCC"}; private static final String[] _commands = { "AUTH", "ADAT", "PBSZ", "PROT", "CCC" };
/** /**
* Retrieve the FTPS command string corresponding to a specified * Retrieve the FTPS command string corresponding to a specified
* command code. * command code.
* *
* @param command The command code. * @param command The command code.
* @return The FTPS command string corresponding to a specified * @return The FTPS command string corresponding to a specified
* command code. * command code.
*/ */
public static final String getCommand(int command) { public static final String getCommand(int command) {
return _commands[command]; return _commands[command];
} }
} }

@ -27,48 +27,46 @@ import javax.net.ssl.SSLServerSocket;
/** /**
* Server socket factory for FTPS connections. * Server socket factory for FTPS connections.
*
* @since 2.2 * @since 2.2
*/ */
public class FTPSServerSocketFactory extends ServerSocketFactory { public class FTPSServerSocketFactory extends ServerSocketFactory {
/** Factory for secure socket factories */ /** Factory for secure socket factories */
private final SSLContext context; private final SSLContext context;
public FTPSServerSocketFactory(SSLContext context) { public FTPSServerSocketFactory(SSLContext context) {
this.context = context; this.context = context;
} }
// Override the default superclass method // Override the default superclass method
@Override @Override public ServerSocket createServerSocket() throws IOException {
public ServerSocket createServerSocket() throws IOException { return init(this.context.getServerSocketFactory().createServerSocket());
return init(this.context.getServerSocketFactory().createServerSocket()); }
}
@Override @Override public ServerSocket createServerSocket(int port) throws IOException {
public ServerSocket createServerSocket(int port) throws IOException { return init(this.context.getServerSocketFactory().createServerSocket(port));
return init(this.context.getServerSocketFactory().createServerSocket(port)); }
}
@Override @Override public ServerSocket createServerSocket(int port, int backlog) throws IOException {
public ServerSocket createServerSocket(int port, int backlog) throws IOException { return init(this.context.getServerSocketFactory().createServerSocket(port, backlog));
return init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); }
}
@Override @Override public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress)
public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { throws IOException {
return init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); return init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress));
} }
/** /**
* Sets the socket so newly accepted connections will use SSL client mode. * Sets the socket so newly accepted connections will use SSL client mode.
* *
* @param socket the SSLServerSocket to initialise * @param socket the SSLServerSocket to initialise
* @return the socket * @return the socket
* @throws ClassCastException if socket is not an instance of SSLServerSocket * @throws ClassCastException if socket is not an instance of SSLServerSocket
*/ */
public ServerSocket init(ServerSocket socket) { public ServerSocket init(ServerSocket socket) {
((SSLServerSocket) socket).setUseClientMode(true); ((SSLServerSocket) socket).setUseClientMode(true);
return socket; return socket;
} }
} }

@ -26,87 +26,91 @@ import javax.net.SocketFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
/** /**
*
* Implementation of org.apache.commons.net.SocketFactory * Implementation of org.apache.commons.net.SocketFactory
* *
* @since 2.0 * @since 2.0
*/ */
public class FTPSSocketFactory extends SocketFactory { public class FTPSSocketFactory extends SocketFactory {
private final SSLContext context; private final SSLContext context;
public FTPSSocketFactory(SSLContext context) { public FTPSSocketFactory(SSLContext context) {
this.context = context; this.context = context;
} }
// Override the default implementation // Override the default implementation
@Override @Override public Socket createSocket() throws IOException {
public Socket createSocket() throws IOException{ return this.context.getSocketFactory().createSocket();
return this.context.getSocketFactory().createSocket(); }
}
@Override public Socket createSocket(String address, int port)
@Override throws UnknownHostException, IOException {
public Socket createSocket(String address, int port) throws UnknownHostException, IOException { return this.context.getSocketFactory().createSocket(address, port);
return this.context.getSocketFactory().createSocket(address, port); }
}
@Override public Socket createSocket(InetAddress address, int port) throws IOException {
@Override return this.context.getSocketFactory().createSocket(address, port);
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)
@Override throws UnknownHostException, IOException {
public Socket createSocket(String address, int port, InetAddress localAddress, int localPort) return this.context.getSocketFactory().createSocket(address, port, localAddress, 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)
@Override throws IOException {
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort);
return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); }
}
// DEPRECATED METHODS - for API compatibility only - DO NOT USE
// DEPRECATED METHODS - for API compatibility only - DO NOT USE /**
* @param port the port
/** @param port the port * @return the socket
* @return the socket * @throws IOException on error
* @throws IOException on error * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead}
* @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead} */ */
@Deprecated @Deprecated public java.net.ServerSocket createServerSocket(int port) throws IOException {
public java.net.ServerSocket createServerSocket(int port) throws IOException { return this.init(this.context.getServerSocketFactory().createServerSocket(port));
return this.init(this.context.getServerSocketFactory().createServerSocket(port)); }
}
/**
/** @param port the port * @param port the port
* @param backlog the backlog * @param backlog the backlog
* @return the socket * @return the socket
* @throws IOException on error * @throws IOException on error
* @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead} */ * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead}
@Deprecated */
public java.net.ServerSocket createServerSocket(int port, int backlog) throws IOException { @Deprecated public java.net.ServerSocket createServerSocket(int port, int backlog)
return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); throws IOException {
} return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog));
}
/** @param port the port
* @param backlog the backlog /**
* @param ifAddress the interface * @param port the port
* @return the socket * @param backlog the backlog
* @throws IOException on error * @param ifAddress the interface
* @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress) instead} */ * @return the socket
@Deprecated * @throws IOException on error
public java.net.ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress)
return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); * instead}
} */
@Deprecated public java.net.ServerSocket createServerSocket(int port, int backlog,
/** @param socket the socket InetAddress ifAddress) throws IOException {
* @return the socket return this.init(
* @throws IOException on error this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress));
* @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); * @param socket the socket
return 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;
}
} }

@ -24,37 +24,30 @@ import javax.net.ssl.X509TrustManager;
/** /**
* Do not use. * Do not use.
*
* @since 2.0 * @since 2.0
* @deprecated 3.0 use * @deprecated 3.0 use
* {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager() * {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager()
* TrustManagerUtils#getValidateServerCertificateTrustManager()} instead * TrustManagerUtils#getValidateServerCertificateTrustManager()} instead
*/ */
@Deprecated @Deprecated public class FTPSTrustManager implements X509TrustManager {
public class FTPSTrustManager implements X509TrustManager private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[] {};
{
private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{};
/** /**
* No-op * No-op
*/ */
@Override @Override public void checkClientTrusted(X509Certificate[] certificates, String authType) {
public void checkClientTrusted(X509Certificate[] certificates, String authType) return;
{ }
return;
}
@Override @Override public void checkServerTrusted(X509Certificate[] certificates, String authType)
public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException throws CertificateException {
{ for (X509Certificate certificate : certificates) {
for (X509Certificate certificate : certificates) certificate.checkValidity();
{
certificate.checkValidity();
}
} }
}
@Override @Override public X509Certificate[] getAcceptedIssuers() {
public X509Certificate[] getAcceptedIssuers() return EMPTY_X509CERTIFICATE_ARRAY;
{ }
return EMPTY_X509CERTIFICATE_ARRAY;
}
} }

@ -0,0 +1,33 @@
/*
* 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 org.apache.commons.net.ftp;
import org.apache.commons.net.io.CopyStreamListener;
/**
* Created by AriaL on 2017/9/26.
* ftp 上传文件流事件监听
*/
public interface OnFtpInputStreamListener {
/**
* {@link CopyStreamListener#bytesTransferred(long, int, long)}
*
* @param totalBytesTransferred 已经上传的文件长度
* @param bytesTransferred 上传byte长度
*/
void onFtpInputStream(FTPClient client, long totalBytesTransferred, int bytesTransferred,
long streamSize);
}

@ -28,42 +28,32 @@ import org.apache.commons.net.ftp.FTPFileEntryParserImpl;
* If the cached parser wont match due to the server changed the dirstyle, * If the cached parser wont match due to the server changed the dirstyle,
* a new matching parser will be searched. * a new matching parser will be searched.
*/ */
public class CompositeFileEntryParser extends FTPFileEntryParserImpl public class CompositeFileEntryParser extends FTPFileEntryParserImpl {
{ private final FTPFileEntryParser[] ftpFileEntryParsers;
private final FTPFileEntryParser[] ftpFileEntryParsers; private FTPFileEntryParser cachedFtpFileEntryParser;
private FTPFileEntryParser cachedFtpFileEntryParser;
public CompositeFileEntryParser(FTPFileEntryParser[] ftpFileEntryParsers) public CompositeFileEntryParser(FTPFileEntryParser[] ftpFileEntryParsers) {
{ super();
super();
this.cachedFtpFileEntryParser = null; this.cachedFtpFileEntryParser = null;
this.ftpFileEntryParsers = ftpFileEntryParsers; this.ftpFileEntryParsers = ftpFileEntryParsers;
} }
@Override @Override public FTPFile parseFTPEntry(String listEntry) {
public FTPFile parseFTPEntry(String listEntry) if (cachedFtpFileEntryParser != null) {
{ FTPFile matched = cachedFtpFileEntryParser.parseFTPEntry(listEntry);
if (cachedFtpFileEntryParser != null) if (matched != null) {
{ return matched;
FTPFile matched = cachedFtpFileEntryParser.parseFTPEntry(listEntry); }
if (matched != null) } else {
{ for (FTPFileEntryParser ftpFileEntryParser : ftpFileEntryParsers) {
return matched; FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry);
} if (matched != null) {
} cachedFtpFileEntryParser = ftpFileEntryParser;
else return matched;
{
for (FTPFileEntryParser ftpFileEntryParser : ftpFileEntryParsers)
{
FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry);
if (matched != null)
{
cachedFtpFileEntryParser = ftpFileEntryParser;
return matched;
}
}
} }
return null; }
} }
return null;
}
} }

@ -23,7 +23,6 @@ import java.util.Calendar;
import org.apache.commons.net.ftp.Configurable; import org.apache.commons.net.ftp.Configurable;
import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPClientConfig;
/** /**
* <p> * <p>
* This abstract class implements the common timestamp parsing * This abstract class implements the common timestamp parsing
@ -38,91 +37,88 @@ import org.apache.commons.net.ftp.FTPClientConfig;
* *
* @since 1.4 * @since 1.4
*/ */
public abstract class ConfigurableFTPFileEntryParserImpl public abstract class ConfigurableFTPFileEntryParserImpl extends RegexFTPFileEntryParserImpl
extends RegexFTPFileEntryParserImpl implements Configurable {
implements Configurable
{
private final FTPTimestampParser timestampParser;
/** private final FTPTimestampParser timestampParser;
* constructor for this abstract class.
* @param regex Regular expression used main parsing of the
* file listing.
*/
public ConfigurableFTPFileEntryParserImpl(String regex)
{
super(regex);
this.timestampParser = new FTPTimestampParserImpl();
}
/** /**
* constructor for this abstract class. * constructor for this abstract class.
* @param regex Regular expression used main parsing of the *
* file listing. * @param regex Regular expression used main parsing of the
* @param flags the flags to apply, see * file listing.
* {@link java.util.regex.Pattern#compile(String, int) Pattern#compile(String, int)}. Use 0 for none. */
* @since 3.4 public ConfigurableFTPFileEntryParserImpl(String regex) {
*/ super(regex);
public ConfigurableFTPFileEntryParserImpl(String regex, int flags) this.timestampParser = new FTPTimestampParserImpl();
{ }
super(regex, flags);
this.timestampParser = new FTPTimestampParserImpl();
}
/** /**
* This method is called by the concrete parsers to delegate * constructor for this abstract class.
* timestamp parsing to the timestamp parser. *
* * @param regex Regular expression used main parsing of the
* @param timestampStr the timestamp string pulled from the * file listing.
* file listing by the regular expression parser, to be submitted * @param flags the flags to apply, see
* to the <code>timestampParser</code> for extracting the timestamp. * {@link java.util.regex.Pattern#compile(String, int) Pattern#compile(String, int)}. Use 0 for
* @return a <code>java.util.Calendar</code> containing results of the * none.
* timestamp parse. * @since 3.4
* @throws ParseException on parse error */
*/ public ConfigurableFTPFileEntryParserImpl(String regex, int flags) {
public Calendar parseTimestamp(String timestampStr) throws ParseException { super(regex, flags);
return this.timestampParser.parseTimestamp(timestampStr); this.timestampParser = new FTPTimestampParserImpl();
} }
/**
* This method is called by the concrete parsers to delegate
* timestamp parsing to the timestamp parser.
*
* @param timestampStr the timestamp string pulled from the
* file listing by the regular expression parser, to be submitted
* to the <code>timestampParser</code> for extracting the timestamp.
* @return a <code>java.util.Calendar</code> containing results of the
* timestamp parse.
* @throws ParseException on parse error
*/
public Calendar parseTimestamp(String timestampStr) throws ParseException {
return this.timestampParser.parseTimestamp(timestampStr);
}
/** /**
* Implementation of the {@link Configurable Configurable} * Implementation of the {@link Configurable Configurable}
* interface. Configures this parser by delegating to the * interface. Configures this parser by delegating to the
* underlying Configurable FTPTimestampParser implementation, ' * underlying Configurable FTPTimestampParser implementation, '
* passing it the supplied {@link FTPClientConfig FTPClientConfig} * passing it the supplied {@link FTPClientConfig FTPClientConfig}
* if that is non-null or a default configuration defined by * if that is non-null or a default configuration defined by
* each concrete subclass. * each concrete subclass.
* *
* @param config the configuration to be used to configure this parser. * @param config the configuration to be used to configure this parser.
* If it is null, a default configuration defined by * If it is null, a default configuration defined by
* each concrete subclass is used instead. * each concrete subclass is used instead.
*/ */
@Override @Override public void configure(FTPClientConfig config) {
public void configure(FTPClientConfig config) if (this.timestampParser instanceof Configurable) {
{ FTPClientConfig defaultCfg = getDefaultConfiguration();
if (this.timestampParser instanceof Configurable) { if (config != null) {
FTPClientConfig defaultCfg = getDefaultConfiguration(); if (null == config.getDefaultDateFormatStr()) {
if (config != null) { config.setDefaultDateFormatStr(defaultCfg.getDefaultDateFormatStr());
if (null == config.getDefaultDateFormatStr()) { }
config.setDefaultDateFormatStr(defaultCfg.getDefaultDateFormatStr()); if (null == config.getRecentDateFormatStr()) {
} config.setRecentDateFormatStr(defaultCfg.getRecentDateFormatStr());
if (null == config.getRecentDateFormatStr()) {
config.setRecentDateFormatStr(defaultCfg.getRecentDateFormatStr());
}
((Configurable)this.timestampParser).configure(config);
} else {
((Configurable)this.timestampParser).configure(defaultCfg);
}
} }
((Configurable) this.timestampParser).configure(config);
} else {
((Configurable) this.timestampParser).configure(defaultCfg);
}
} }
}
/** /**
* Each concrete subclass must define this member to create * Each concrete subclass must define this member to create
* a default configuration to be used when that subclass is * a default configuration to be used when that subclass is
* instantiated without a {@link FTPClientConfig FTPClientConfig} * instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified. * parameter being specified.
* @return the default configuration for the subclass. *
*/ * @return the default configuration for the subclass.
protected abstract FTPClientConfig getDefaultConfiguration(); */
protected abstract FTPClientConfig getDefaultConfiguration();
} }

@ -23,7 +23,6 @@ import org.apache.commons.net.ftp.Configurable;
import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFileEntryParser; import org.apache.commons.net.ftp.FTPFileEntryParser;
/** /**
* This is the default implementation of the * This is the default implementation of the
* FTPFileEntryParserFactory interface. This is the * FTPFileEntryParserFactory interface. This is the
@ -34,262 +33,222 @@ import org.apache.commons.net.ftp.FTPFileEntryParser;
* @see org.apache.commons.net.ftp.FTPClient#listFiles * @see org.apache.commons.net.ftp.FTPClient#listFiles
* @see org.apache.commons.net.ftp.FTPClient#setParserFactory * @see org.apache.commons.net.ftp.FTPClient#setParserFactory
*/ */
public class DefaultFTPFileEntryParserFactory public class DefaultFTPFileEntryParserFactory implements FTPFileEntryParserFactory {
implements FTPFileEntryParserFactory
{ // Match a plain Java Identifier
private static final String JAVA_IDENTIFIER =
// Match a plain Java Identifier "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*";
private static final String JAVA_IDENTIFIER = "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*"; // Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc.
// Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc. private static final String JAVA_QUALIFIED_NAME =
private static final String JAVA_QUALIFIED_NAME = "("+JAVA_IDENTIFIER+"\\.)+"+JAVA_IDENTIFIER; "(" + JAVA_IDENTIFIER + "\\.)+" + JAVA_IDENTIFIER;
// Create the pattern, as it will be reused many times // Create the pattern, as it will be reused many times
private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME); private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME);
/** /**
* This default implementation of the FTPFileEntryParserFactory * This default implementation of the FTPFileEntryParserFactory
* interface works according to the following logic: * interface works according to the following logic:
* First it attempts to interpret the supplied key as a fully * First it attempts to interpret the supplied key as a fully
* qualified classname (default package is not allowed) of a class implementing the * qualified classname (default package is not allowed) of a class implementing the
* FTPFileEntryParser interface. If that succeeds, a parser * FTPFileEntryParser interface. If that succeeds, a parser
* object of this class is instantiated and is returned; * object of this class is instantiated and is returned;
* otherwise it attempts to interpret the key as an identirier * otherwise it attempts to interpret the key as an identirier
* commonly used by the FTP SYST command to identify systems. * commonly used by the FTP SYST command to identify systems.
* <p> * <p>
* If <code>key</code> is not recognized as a fully qualified * If <code>key</code> is not recognized as a fully qualified
* classname known to the system, this method will then attempt * classname known to the system, this method will then attempt
* to see whether it <b>contains</b> a string identifying one of * to see whether it <b>contains</b> a string identifying one of
* the known parsers. This comparison is <b>case-insensitive</b>. * the known parsers. This comparison is <b>case-insensitive</b>.
* The intent here is where possible, to select as keys strings * The intent here is where possible, to select as keys strings
* which are returned by the SYST command on the systems which * which are returned by the SYST command on the systems which
* the corresponding parser successfully parses. This enables * the corresponding parser successfully parses. This enables
* this factory to be used in the auto-detection system. * this factory to be used in the auto-detection system.
* *
* @param key should be a fully qualified classname corresponding to * @param key should be a fully qualified classname corresponding to
* a class implementing the FTPFileEntryParser interface<br> * a class implementing the FTPFileEntryParser interface<br>
* OR<br> * OR<br>
* a string containing (case-insensitively) one of the * a string containing (case-insensitively) one of the
* following keywords: * following keywords:
* <ul> * <ul>
* <li>{@link FTPClientConfig#SYST_UNIX UNIX}</li> * <li>{@link FTPClientConfig#SYST_UNIX UNIX}</li>
* <li>{@link FTPClientConfig#SYST_NT WINDOWS}</li> * <li>{@link FTPClientConfig#SYST_NT WINDOWS}</li>
* <li>{@link FTPClientConfig#SYST_OS2 OS/2}</li> * <li>{@link FTPClientConfig#SYST_OS2 OS/2}</li>
* <li>{@link FTPClientConfig#SYST_OS400 OS/400}</li> * <li>{@link FTPClientConfig#SYST_OS400 OS/400}</li>
* <li>{@link FTPClientConfig#SYST_AS400 AS/400}</li> * <li>{@link FTPClientConfig#SYST_AS400 AS/400}</li>
* <li>{@link FTPClientConfig#SYST_VMS VMS}</li> * <li>{@link FTPClientConfig#SYST_VMS VMS}</li>
* <li>{@link FTPClientConfig#SYST_MVS MVS}</li> * <li>{@link FTPClientConfig#SYST_MVS MVS}</li>
* <li>{@link FTPClientConfig#SYST_NETWARE NETWARE}</li> * <li>{@link FTPClientConfig#SYST_NETWARE NETWARE}</li>
* <li>{@link FTPClientConfig#SYST_L8 TYPE:L8}</li> * <li>{@link FTPClientConfig#SYST_L8 TYPE:L8}</li>
* </ul> * </ul>
* @return the FTPFileEntryParser corresponding to the supplied key. * @return the FTPFileEntryParser corresponding to the supplied key.
* @throws ParserInitializationException thrown if for any reason the factory cannot resolve * @throws ParserInitializationException thrown if for any reason the factory cannot resolve
* the supplied key into an FTPFileEntryParser. * the supplied key into an FTPFileEntryParser.
* @see FTPFileEntryParser * @see FTPFileEntryParser
*/ */
@Override @Override public FTPFileEntryParser createFileEntryParser(String key) {
public FTPFileEntryParser createFileEntryParser(String key) if (key == null) {
{ throw new ParserInitializationException("Parser key cannot be null");
if (key == null) {
throw new ParserInitializationException("Parser key cannot be null");
}
return createFileEntryParser(key, null);
} }
return createFileEntryParser(key, null);
// Common method to process both key and config parameters. }
private FTPFileEntryParser createFileEntryParser(String key, FTPClientConfig config) {
FTPFileEntryParser parser = null; // Common method to process both key and config parameters.
private FTPFileEntryParser createFileEntryParser(String key, FTPClientConfig config) {
// Is the key a possible class name? FTPFileEntryParser parser = null;
if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) {
try // Is the key a possible class name?
{ if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) {
Class<?> parserClass = Class.forName(key); try {
try { Class<?> parserClass = Class.forName(key);
parser = (FTPFileEntryParser) parserClass.newInstance(); try {
} catch (ClassCastException e) { parser = (FTPFileEntryParser) parserClass.newInstance();
throw new ParserInitializationException(parserClass.getName() } catch (ClassCastException e) {
+ " does not implement the interface " throw new ParserInitializationException(parserClass.getName()
+ "org.apache.commons.net.ftp.FTPFileEntryParser.", e); + " does not implement the interface "
} catch (Exception e) { + "org.apache.commons.net.ftp.FTPFileEntryParser.", e);
throw new ParserInitializationException("Error initializing parser", e); } catch (Exception e) {
} catch (ExceptionInInitializerError e) { throw new ParserInitializationException("Error initializing parser", e);
throw new ParserInitializationException("Error initializing parser", e); } catch (ExceptionInInitializerError e) {
} throw new ParserInitializationException("Error initializing parser", e);
} catch (ClassNotFoundException e) {
// OK, assume it is an alias
}
}
if (parser == null) { // Now try for aliases
String ukey = key.toUpperCase(java.util.Locale.ENGLISH);
if (ukey.indexOf(FTPClientConfig.SYST_UNIX_TRIM_LEADING) >= 0)
{
parser = new UnixFTPEntryParser(config, true);
}
// must check this after SYST_UNIX_TRIM_LEADING as it is a substring of it
else if (ukey.indexOf(FTPClientConfig.SYST_UNIX) >= 0)
{
parser = new UnixFTPEntryParser(config, false);
}
else if (ukey.indexOf(FTPClientConfig.SYST_VMS) >= 0)
{
parser = new VMSVersioningFTPEntryParser(config);
}
else if (ukey.indexOf(FTPClientConfig.SYST_NT) >= 0)
{
parser = createNTFTPEntryParser(config);
}
else if (ukey.indexOf(FTPClientConfig.SYST_OS2) >= 0)
{
parser = new OS2FTPEntryParser(config);
}
else if (ukey.indexOf(FTPClientConfig.SYST_OS400) >= 0 ||
ukey.indexOf(FTPClientConfig.SYST_AS400) >= 0)
{
parser = createOS400FTPEntryParser(config);
}
else if (ukey.indexOf(FTPClientConfig.SYST_MVS) >= 0)
{
parser = new MVSFTPEntryParser(); // Does not currently support config parameter
}
else if (ukey.indexOf(FTPClientConfig.SYST_NETWARE) >= 0)
{
parser = new NetwareFTPEntryParser(config);
}
else if (ukey.indexOf(FTPClientConfig.SYST_MACOS_PETER) >= 0)
{
parser = new MacOsPeterFTPEntryParser(config);
}
else if (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0)
{
// L8 normally means Unix, but move it to the end for some L8 systems that aren't.
// This check should be last!
parser = new UnixFTPEntryParser(config);
}
else
{
throw new ParserInitializationException("Unknown parser type: " + key);
}
}
if (parser instanceof Configurable) {
((Configurable)parser).configure(config);
} }
return parser; } catch (ClassNotFoundException e) {
// OK, assume it is an alias
}
} }
/** if (parser == null) { // Now try for aliases
* <p>Implementation extracts a key from the supplied String ukey = key.toUpperCase(java.util.Locale.ENGLISH);
* {@link FTPClientConfig FTPClientConfig} if (ukey.indexOf(FTPClientConfig.SYST_UNIX_TRIM_LEADING) >= 0) {
* parameter and creates an object implementing the parser = new UnixFTPEntryParser(config, true);
* interface FTPFileEntryParser and uses the supplied configuration }
* to configure it. // must check this after SYST_UNIX_TRIM_LEADING as it is a substring of it
* </p><p> else if (ukey.indexOf(FTPClientConfig.SYST_UNIX) >= 0) {
* Note that this method will generally not be called in scenarios parser = new UnixFTPEntryParser(config, false);
* that call for autodetection of parser type but rather, for situations } else if (ukey.indexOf(FTPClientConfig.SYST_VMS) >= 0) {
* where the user knows that the server uses a non-default configuration parser = new VMSVersioningFTPEntryParser(config);
* and knows what that configuration is. } else if (ukey.indexOf(FTPClientConfig.SYST_NT) >= 0) {
* </p> parser = createNTFTPEntryParser(config);
* @param config A {@link FTPClientConfig FTPClientConfig} } else if (ukey.indexOf(FTPClientConfig.SYST_OS2) >= 0) {
* used to configure the parser created parser = new OS2FTPEntryParser(config);
* } else if (ukey.indexOf(FTPClientConfig.SYST_OS400) >= 0
* @return the @link FTPFileEntryParser FTPFileEntryParser} so created. || ukey.indexOf(FTPClientConfig.SYST_AS400) >= 0) {
* @throws ParserInitializationException parser = createOS400FTPEntryParser(config);
* Thrown on any exception in instantiation } else if (ukey.indexOf(FTPClientConfig.SYST_MVS) >= 0) {
* @throws NullPointerException if {@code config} is {@code null} parser = new MVSFTPEntryParser(); // Does not currently support config parameter
* @since 1.4 } else if (ukey.indexOf(FTPClientConfig.SYST_NETWARE) >= 0) {
*/ parser = new NetwareFTPEntryParser(config);
@Override } else if (ukey.indexOf(FTPClientConfig.SYST_MACOS_PETER) >= 0) {
public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) parser = new MacOsPeterFTPEntryParser(config);
throws ParserInitializationException } else if (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0) {
{ // L8 normally means Unix, but move it to the end for some L8 systems that aren't.
String key = config.getServerSystemKey(); // This check should be last!
return createFileEntryParser(key, config); parser = new UnixFTPEntryParser(config);
} else {
throw new ParserInitializationException("Unknown parser type: " + key);
}
} }
if (parser instanceof Configurable) {
public FTPFileEntryParser createUnixFTPEntryParser() ((Configurable) parser).configure(config);
{
return new UnixFTPEntryParser();
} }
return parser;
public FTPFileEntryParser createVMSVersioningFTPEntryParser() }
{
return new VMSVersioningFTPEntryParser(); /**
} * <p>Implementation extracts a key from the supplied
* {@link FTPClientConfig FTPClientConfig}
public FTPFileEntryParser createNetwareFTPEntryParser() { * parameter and creates an object implementing the
return new NetwareFTPEntryParser(); * interface FTPFileEntryParser and uses the supplied configuration
* to configure it.
* </p><p>
* Note that this method will generally not be called in scenarios
* that call for autodetection of parser type but rather, for situations
* where the user knows that the server uses a non-default configuration
* and knows what that configuration is.
* </p>
*
* @param config A {@link FTPClientConfig FTPClientConfig}
* used to configure the parser created
* @return the @link FTPFileEntryParser FTPFileEntryParser} so created.
* @throws ParserInitializationException Thrown on any exception in instantiation
* @throws NullPointerException if {@code config} is {@code null}
* @since 1.4
*/
@Override public FTPFileEntryParser createFileEntryParser(FTPClientConfig config)
throws ParserInitializationException {
String key = config.getServerSystemKey();
return createFileEntryParser(key, config);
}
public FTPFileEntryParser createUnixFTPEntryParser() {
return new UnixFTPEntryParser();
}
public FTPFileEntryParser createVMSVersioningFTPEntryParser() {
return new VMSVersioningFTPEntryParser();
}
public FTPFileEntryParser createNetwareFTPEntryParser() {
return new NetwareFTPEntryParser();
}
public FTPFileEntryParser createNTFTPEntryParser() {
return createNTFTPEntryParser(null);
}
/**
* Creates an NT FTP parser: if the config exists, and the system key equals
* {@link FTPClientConfig.SYST_NT} then a plain {@link NTFTPEntryParser} is used,
* otherwise a composite of {@link NTFTPEntryParser} and {@link UnixFTPEntryParser} is used.
*
* @param config the config to use, may be {@code null}
* @return the parser
*/
private FTPFileEntryParser createNTFTPEntryParser(FTPClientConfig config) {
if (config != null && FTPClientConfig.SYST_NT.equals(config.getServerSystemKey())) {
return new NTFTPEntryParser(config);
} else {
// clone the config as it may be changed by the parsers (NET-602)
final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null;
return new CompositeFileEntryParser(new FTPFileEntryParser[] {
new NTFTPEntryParser(config), new UnixFTPEntryParser(config2,
config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(
config2.getServerSystemKey()))
});
} }
}
public FTPFileEntryParser createNTFTPEntryParser()
{ public FTPFileEntryParser createOS2FTPEntryParser() {
return createNTFTPEntryParser(null); return new OS2FTPEntryParser();
} }
/** public FTPFileEntryParser createOS400FTPEntryParser() {
* Creates an NT FTP parser: if the config exists, and the system key equals return createOS400FTPEntryParser(null);
* {@link FTPClientConfig.SYST_NT} then a plain {@link NTFTPEntryParser} is used, }
* otherwise a composite of {@link NTFTPEntryParser} and {@link UnixFTPEntryParser} is used.
* @param config the config to use, may be {@code null} /**
* @return the parser * Creates an OS400 FTP parser: if the config exists, and the system key equals
*/ * {@link FTPClientConfig.SYST_OS400} then a plain {@link OS400FTPEntryParser} is used,
private FTPFileEntryParser createNTFTPEntryParser(FTPClientConfig config) * otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used.
{ *
if (config != null && FTPClientConfig.SYST_NT.equals( * @param config the config to use, may be {@code null}
config.getServerSystemKey())) * @return the parser
{ */
return new NTFTPEntryParser(config); private FTPFileEntryParser createOS400FTPEntryParser(FTPClientConfig config) {
} else { if (config != null && FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) {
// clone the config as it may be changed by the parsers (NET-602) return new OS400FTPEntryParser(config);
final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; } else {
return new CompositeFileEntryParser(new FTPFileEntryParser[] // clone the config as it may be changed by the parsers (NET-602)
{ final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null;
new NTFTPEntryParser(config), return new CompositeFileEntryParser(new FTPFileEntryParser[] {
new UnixFTPEntryParser(config2, new OS400FTPEntryParser(config), new UnixFTPEntryParser(config2,
config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(
}); config2.getServerSystemKey()))
} });
}
public FTPFileEntryParser createOS2FTPEntryParser()
{
return new OS2FTPEntryParser();
}
public FTPFileEntryParser createOS400FTPEntryParser()
{
return createOS400FTPEntryParser(null);
}
/**
* Creates an OS400 FTP parser: if the config exists, and the system key equals
* {@link FTPClientConfig.SYST_OS400} then a plain {@link OS400FTPEntryParser} is used,
* otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used.
* @param config the config to use, may be {@code null}
* @return the parser
*/
private FTPFileEntryParser createOS400FTPEntryParser(FTPClientConfig config)
{
if (config != null &&
FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey()))
{
return new OS400FTPEntryParser(config);
} else {
// clone the config as it may be changed by the parsers (NET-602)
final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null;
return new CompositeFileEntryParser(new FTPFileEntryParser[]
{
new OS400FTPEntryParser(config),
new UnixFTPEntryParser(config2,
config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey()))
});
}
}
public FTPFileEntryParser createMVSEntryParser()
{
return new MVSFTPEntryParser();
} }
}
public FTPFileEntryParser createMVSEntryParser() {
return new MVSFTPEntryParser();
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.util.Calendar; import java.util.Calendar;
import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFile;
@ -23,144 +24,136 @@ import org.apache.commons.net.ftp.FTPFile;
/** /**
* Parser for the Connect Enterprise Unix FTP Server From Sterling Commerce. * Parser for the Connect Enterprise Unix FTP Server From Sterling Commerce.
* Here is a sample of the sort of output line this parser processes: * Here is a sample of the sort of output line this parser processes:
* "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST" * "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST"
* <P><B> * <P><B>
* Note: EnterpriseUnixFTPEntryParser can only be instantiated through the * Note: EnterpriseUnixFTPEntryParser can only be instantiated through the
* DefaultFTPParserFactory by classname. It will not be chosen * DefaultFTPParserFactory by classname. It will not be chosen
* by the autodetection scheme. * by the autodetection scheme.
* </B> * </B>
*
* @version $Id: EnterpriseUnixFTPEntryParser.java 1741829 2016-05-01 00:24:44Z sebb $ * @version $Id: EnterpriseUnixFTPEntryParser.java 1741829 2016-05-01 00:24:44Z sebb $
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
* @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory
*/ */
public class EnterpriseUnixFTPEntryParser extends RegexFTPFileEntryParserImpl public class EnterpriseUnixFTPEntryParser extends RegexFTPFileEntryParserImpl {
{
/**
/** * months abbreviations looked for by this parser. Also used
* months abbreviations looked for by this parser. Also used * to determine <b>which</b> month has been matched by the parser.
* to determine <b>which</b> month has been matched by the parser. */
*/ private static final String MONTHS = "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
private static final String MONTHS =
"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"; /**
* this is the regular expression used by this parser.
/** */
* this is the regular expression used by this parser. private static final String REGEX =
*/ "(([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])"
private static final String REGEX = + "([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z]))"
"(([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])" + "(\\S*)\\s*"
+ "([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z]))" // 12
+ "(\\S*)\\s*" // 12 + "(\\S+)\\s*"
+ "(\\S+)\\s*" // 13 // 13
+ "(\\S*)\\s*" // 14 user + "(\\S*)\\s*"
+ "(\\d*)\\s*" // 15 group // 14 user
+ "(\\d*)\\s*" // 16 filesize + "(\\d*)\\s*"
+ MONTHS // 17 month // 15 group
+ "\\s*" // TODO should the space be optional? + "(\\d*)\\s*"
// TODO \\d* should be \\d? surely ? Otherwise 01111 is allowed // 16 filesize
+ "((?:[012]\\d*)|(?:3[01]))\\s*" // 18 date [012]\d* or 3[01] + MONTHS
+ "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s" // 17 month
// 20 \d\d\d\d = year OR + "\\s*"
// 21 [01]\d or 2[0123] hour + ':' // TODO should the space be optional?
// 22 [012345]\d = minute // TODO \\d* should be \\d? surely ? Otherwise 01111 is allowed
+ "(\\S*)(\\s*.*)"; // 23 name + "((?:[012]\\d*)|(?:3[01]))\\s*"
// 18 date [012]\d* or 3[01]
/** + "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s"
* The sole constructor for a EnterpriseUnixFTPEntryParser object. // 20 \d\d\d\d = year OR
* // 21 [01]\d or 2[0123] hour + ':'
*/ // 22 [012345]\d = minute
public EnterpriseUnixFTPEntryParser() + "(\\S*)(\\s*.*)"; // 23 name
{
super(REGEX); /**
} * The sole constructor for a EnterpriseUnixFTPEntryParser object.
*/
public EnterpriseUnixFTPEntryParser() {
super(REGEX);
}
/**
* Parses a line of a unix FTP server file listing and converts it into a
* usable format in the form of an <code> FTPFile </code> instance. If
* the file listing line doesn't describe a file, <code> null </code> is
* returned, otherwise a <code> FTPFile </code> instance representing the
* files in the directory is returned.
*
* @param entry A line of text from the file listing
* @return An FTPFile instance corresponding to the supplied entry
*/
@Override public FTPFile parseFTPEntry(String entry) {
/** FTPFile file = new FTPFile();
* Parses a line of a unix FTP server file listing and converts it into a file.setRawListing(entry);
* usable format in the form of an <code> FTPFile </code> instance. If
* the file listing line doesn't describe a file, <code> null </code> is if (matches(entry)) {
* returned, otherwise a <code> FTPFile </code> instance representing the String usr = group(14);
* files in the directory is returned. String grp = group(15);
* String filesize = group(16);
* @param entry A line of text from the file listing String mo = group(17);
* @return An FTPFile instance corresponding to the supplied entry String da = group(18);
*/ String yr = group(20);
@Override String hr = group(21);
public FTPFile parseFTPEntry(String entry) String min = group(22);
{ String name = group(23);
FTPFile file = new FTPFile(); file.setType(FTPFile.FILE_TYPE);
file.setRawListing(entry); file.setUser(usr);
file.setGroup(grp);
if (matches(entry)) try {
{ file.setSize(Long.parseLong(filesize));
String usr = group(14); } catch (NumberFormatException e) {
String grp = group(15); // intentionally do nothing
String filesize = group(16); }
String mo = group(17);
String da = group(18); Calendar cal = Calendar.getInstance();
String yr = group(20); cal.set(Calendar.MILLISECOND, 0);
String hr = group(21); cal.set(Calendar.SECOND, 0);
String min = group(22); cal.set(Calendar.MINUTE, 0);
String name = group(23); cal.set(Calendar.HOUR_OF_DAY, 0);
file.setType(FTPFile.FILE_TYPE); int pos = MONTHS.indexOf(mo);
file.setUser(usr); int month = pos / 4;
file.setGroup(grp); final int missingUnit; // the first missing unit
try try {
{
file.setSize(Long.parseLong(filesize)); if (yr != null) {
} // it's a year; there are no hours and minutes
catch (NumberFormatException e) cal.set(Calendar.YEAR, Integer.parseInt(yr));
{ missingUnit = Calendar.HOUR_OF_DAY;
// intentionally do nothing } else {
} // it must be hour/minute or we wouldn't have matched
missingUnit = Calendar.SECOND;
Calendar cal = Calendar.getInstance(); int year = cal.get(Calendar.YEAR);
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0); // if the month we're reading is greater than now, it must
cal.set(Calendar.MINUTE, 0); // be last year
cal.set(Calendar.HOUR_OF_DAY, 0); if (cal.get(Calendar.MONTH) < month) {
year--;
int pos = MONTHS.indexOf(mo); }
int month = pos / 4; cal.set(Calendar.YEAR, year);
final int missingUnit; // the first missing unit cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hr));
try cal.set(Calendar.MINUTE, Integer.parseInt(min));
{
if (yr != null)
{
// it's a year; there are no hours and minutes
cal.set(Calendar.YEAR, Integer.parseInt(yr));
missingUnit = Calendar.HOUR_OF_DAY;
}
else
{
// it must be hour/minute or we wouldn't have matched
missingUnit = Calendar.SECOND;
int year = cal.get(Calendar.YEAR);
// if the month we're reading is greater than now, it must
// be last year
if (cal.get(Calendar.MONTH) < month)
{
year--;
}
cal.set(Calendar.YEAR, year);
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hr));
cal.set(Calendar.MINUTE, Integer.parseInt(min));
}
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(da));
cal.clear(missingUnit);
file.setTimestamp(cal);
}
catch (NumberFormatException e)
{
// do nothing, date will be uninitialized
}
file.setName(name);
return file;
} }
return null; cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(da));
cal.clear(missingUnit);
file.setTimestamp(cal);
} catch (NumberFormatException e) {
// do nothing, date will be uninitialized
}
file.setName(name);
return file;
} }
return null;
}
} }

@ -16,53 +16,48 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFileEntryParser; import org.apache.commons.net.ftp.FTPFileEntryParser;
/** /**
* The interface describes a factory for creating FTPFileEntryParsers. * The interface describes a factory for creating FTPFileEntryParsers.
*
* @since 1.2 * @since 1.2
*/ */
public interface FTPFileEntryParserFactory public interface FTPFileEntryParserFactory {
{ /**
/** * Implementation should be a method that decodes the
* Implementation should be a method that decodes the * supplied key and creates an object implementing the
* supplied key and creates an object implementing the * interface FTPFileEntryParser.
* interface FTPFileEntryParser. *
* * @param key A string that somehow identifies an
* @param key A string that somehow identifies an * FTPFileEntryParser to be created.
* FTPFileEntryParser to be created. * @return the FTPFileEntryParser created.
* * @throws ParserInitializationException Thrown on any exception in instantiation
* @return the FTPFileEntryParser created. */
* @throws ParserInitializationException public FTPFileEntryParser createFileEntryParser(String key) throws ParserInitializationException;
* Thrown on any exception in instantiation
*/
public FTPFileEntryParser createFileEntryParser(String key)
throws ParserInitializationException;
/**
*<p>
* Implementation should be a method that extracts
* a key from the supplied {@link FTPClientConfig FTPClientConfig}
* parameter and creates an object implementing the
* interface FTPFileEntryParser and uses the supplied configuration
* to configure it.
* </p><p>
* Note that this method will generally not be called in scenarios
* that call for autodetection of parser type but rather, for situations
* where the user knows that the server uses a non-default configuration
* and knows what that configuration is.
* </p>
*
* @param config A {@link FTPClientConfig FTPClientConfig}
* used to configure the parser created
*
* @return the @link FTPFileEntryParser FTPFileEntryParser} so created.
* @throws ParserInitializationException
* Thrown on any exception in instantiation
* @since 1.4
*/
public FTPFileEntryParser createFileEntryParser(FTPClientConfig config)
throws ParserInitializationException;
/**
* <p>
* Implementation should be a method that extracts
* a key from the supplied {@link FTPClientConfig FTPClientConfig}
* parameter and creates an object implementing the
* interface FTPFileEntryParser and uses the supplied configuration
* to configure it.
* </p><p>
* Note that this method will generally not be called in scenarios
* that call for autodetection of parser type but rather, for situations
* where the user knows that the server uses a non-default configuration
* and knows what that configuration is.
* </p>
*
* @param config A {@link FTPClientConfig FTPClientConfig}
* used to configure the parser created
* @return the @link FTPFileEntryParser FTPFileEntryParser} so created.
* @throws ParserInitializationException Thrown on any exception in instantiation
* @since 1.4
*/
public FTPFileEntryParser createFileEntryParser(FTPClientConfig config)
throws ParserInitializationException;
} }

@ -23,30 +23,31 @@ import java.util.Calendar;
/** /**
* This interface specifies the concept of parsing an FTP server's * This interface specifies the concept of parsing an FTP server's
* timestamp. * timestamp.
*
* @since 1.4 * @since 1.4
*/ */
public interface FTPTimestampParser { public interface FTPTimestampParser {
/** /**
* the default default date format. * the default default date format.
*/ */
public static final String DEFAULT_SDF = UnixFTPEntryParser.DEFAULT_DATE_FORMAT; public static final String DEFAULT_SDF = UnixFTPEntryParser.DEFAULT_DATE_FORMAT;
/** /**
* the default recent date format. * the default recent date format.
*/ */
public static final String DEFAULT_RECENT_SDF = UnixFTPEntryParser.DEFAULT_RECENT_DATE_FORMAT; public static final String DEFAULT_RECENT_SDF = UnixFTPEntryParser.DEFAULT_RECENT_DATE_FORMAT;
/**
* Parses the supplied datestamp parameter. This parameter typically would
* have been pulled from a longer FTP listing via the regular expression
* mechanism
* @param timestampStr - the timestamp portion of the FTP directory listing
* to be parsed
* @return a <code>java.util.Calendar</code> object initialized to the date
* parsed by the parser
* @throws ParseException if none of the parser mechanisms belonging to
* the implementor can parse the input.
*/
public Calendar parseTimestamp(String timestampStr) throws ParseException;
/**
* Parses the supplied datestamp parameter. This parameter typically would
* have been pulled from a longer FTP listing via the regular expression
* mechanism
*
* @param timestampStr - the timestamp portion of the FTP directory listing
* to be parsed
* @return a <code>java.util.Calendar</code> object initialized to the date
* parsed by the parser
* @throws ParseException if none of the parser mechanisms belonging to
* the implementor can parse the input.
*/
public Calendar parseTimestamp(String timestampStr) throws ParseException;
} }

@ -36,371 +36,369 @@ import org.apache.commons.net.ftp.FTPClientConfig;
* @see ConfigurableFTPFileEntryParserImpl * @see ConfigurableFTPFileEntryParserImpl
* @since 1.4 * @since 1.4
*/ */
public class FTPTimestampParserImpl implements public class FTPTimestampParserImpl implements FTPTimestampParser, Configurable {
FTPTimestampParser, Configurable
{
/** The date format for all dates, except possibly recent dates. Assumed to include the year. */
private SimpleDateFormat defaultDateFormat;
/* The index in CALENDAR_UNITS of the smallest time unit in defaultDateFormat */
private int defaultDateSmallestUnitIndex;
/** The format used for recent dates (which don't have the year). May be null. */
private SimpleDateFormat recentDateFormat;
/* The index in CALENDAR_UNITS of the smallest time unit in recentDateFormat */
private int recentDateSmallestUnitIndex;
private boolean lenientFutureDates = false;
/*
* List of units in order of increasing significance.
* This allows the code to clear all units in the Calendar until it
* reaches the least significant unit in the parse string.
* The date formats are analysed to find the least significant
* unit (e.g. Minutes or Milliseconds) and the appropriate index to
* the array is saved.
* This is done by searching the array for the unit specifier,
* and returning the index. When clearing the Calendar units,
* the code loops through the array until the previous entry.
* e.g. for MINUTE it would clear MILLISECOND and SECOND
*/
private static final int[] CALENDAR_UNITS = {
Calendar.MILLISECOND,
Calendar.SECOND,
Calendar.MINUTE,
Calendar.HOUR_OF_DAY,
Calendar.DAY_OF_MONTH,
Calendar.MONTH,
Calendar.YEAR};
/*
* Return the index to the array representing the least significant
* unit found in the date format.
* Default is 0 (to avoid dropping precision)
*/
private static int getEntry(SimpleDateFormat dateFormat) {
if (dateFormat == null) {
return 0;
}
final String FORMAT_CHARS="SsmHdM";
final String pattern = dateFormat.toPattern();
for(char ch : FORMAT_CHARS.toCharArray()) {
if (pattern.indexOf(ch) != -1){ // found the character
switch(ch) {
case 'S':
return indexOf(Calendar.MILLISECOND);
case 's':
return indexOf(Calendar.SECOND);
case 'm':
return indexOf(Calendar.MINUTE);
case 'H':
return indexOf(Calendar.HOUR_OF_DAY);
case 'd':
return indexOf(Calendar.DAY_OF_MONTH);
case 'M':
return indexOf(Calendar.MONTH);
}
}
}
return 0;
}
/* /** The date format for all dates, except possibly recent dates. Assumed to include the year. */
* Find the entry in the CALENDAR_UNITS array. private SimpleDateFormat defaultDateFormat;
*/ /* The index in CALENDAR_UNITS of the smallest time unit in defaultDateFormat */
private static int indexOf(int calendarUnit) { private int defaultDateSmallestUnitIndex;
int i;
for(i = 0; i <CALENDAR_UNITS.length; i++) {
if (calendarUnit == CALENDAR_UNITS[i]) {
return i;
}
}
return 0;
}
/* /** The format used for recent dates (which don't have the year). May be null. */
* Sets the Calendar precision (used by FTPFile#toFormattedDate) by clearing private SimpleDateFormat recentDateFormat;
* the immediately preceeding unit (if any). /* The index in CALENDAR_UNITS of the smallest time unit in recentDateFormat */
* Unfortunately the clear(int) method results in setting all other units. private int recentDateSmallestUnitIndex;
*/
private static void setPrecision(int index, Calendar working) { private boolean lenientFutureDates = false;
if (index <= 0) { // e.g. MILLISECONDS
return; /*
} * List of units in order of increasing significance.
final int field = CALENDAR_UNITS[index-1]; * This allows the code to clear all units in the Calendar until it
// Just in case the analysis is wrong, stop clearing if * reaches the least significant unit in the parse string.
// field value is not the default. * The date formats are analysed to find the least significant
final int value = working.get(field); * unit (e.g. Minutes or Milliseconds) and the appropriate index to
if (value != 0) { // don't reset if it has a value * the array is saved.
// new Throwable("Unexpected value "+value).printStackTrace(); // DEBUG * This is done by searching the array for the unit specifier,
} else { * and returning the index. When clearing the Calendar units,
working.clear(field); // reset just the required field * the code loops through the array until the previous entry.
* e.g. for MINUTE it would clear MILLISECOND and SECOND
*/
private static final int[] CALENDAR_UNITS = {
Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY,
Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR
};
/*
* Return the index to the array representing the least significant
* unit found in the date format.
* Default is 0 (to avoid dropping precision)
*/
private static int getEntry(SimpleDateFormat dateFormat) {
if (dateFormat == null) {
return 0;
}
final String FORMAT_CHARS = "SsmHdM";
final String pattern = dateFormat.toPattern();
for (char ch : FORMAT_CHARS.toCharArray()) {
if (pattern.indexOf(ch) != -1) { // found the character
switch (ch) {
case 'S':
return indexOf(Calendar.MILLISECOND);
case 's':
return indexOf(Calendar.SECOND);
case 'm':
return indexOf(Calendar.MINUTE);
case 'H':
return indexOf(Calendar.HOUR_OF_DAY);
case 'd':
return indexOf(Calendar.DAY_OF_MONTH);
case 'M':
return indexOf(Calendar.MONTH);
} }
}
} }
return 0;
}
/** /*
* The only constructor for this class. * Find the entry in the CALENDAR_UNITS array.
*/ */
public FTPTimestampParserImpl() { private static int indexOf(int calendarUnit) {
setDefaultDateFormat(DEFAULT_SDF, null); int i;
setRecentDateFormat(DEFAULT_RECENT_SDF, null); for (i = 0; i < CALENDAR_UNITS.length; i++) {
if (calendarUnit == CALENDAR_UNITS[i]) {
return i;
}
} }
return 0;
}
/** /*
* Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} * Sets the Calendar precision (used by FTPFile#toFormattedDate) by clearing
* in the {@link FTPTimestampParser FTPTimestampParser} interface * the immediately preceeding unit (if any).
* according to this algorithm: * Unfortunately the clear(int) method results in setting all other units.
* */
* If the recentDateFormat member has been defined, try to parse the private static void setPrecision(int index, Calendar working) {
* supplied string with that. If that parse fails, or if the recentDateFormat if (index <= 0) { // e.g. MILLISECONDS
* member has not been defined, attempt to parse with the defaultDateFormat return;
* member. If that fails, throw a ParseException.
*
* This method assumes that the server time is the same as the local time.
*
* @see FTPTimestampParserImpl#parseTimestamp(String, Calendar)
*
* @param timestampStr The timestamp to be parsed
* @return a Calendar with the parsed timestamp
*/
@Override
public Calendar parseTimestamp(String timestampStr) throws ParseException {
Calendar now = Calendar.getInstance();
return parseTimestamp(timestampStr, now);
} }
final int field = CALENDAR_UNITS[index - 1];
// Just in case the analysis is wrong, stop clearing if
// field value is not the default.
final int value = working.get(field);
if (value != 0) { // don't reset if it has a value
// new Throwable("Unexpected value "+value).printStackTrace(); // DEBUG
} else {
working.clear(field); // reset just the required field
}
}
/** /**
* If the recentDateFormat member has been defined, try to parse the * The only constructor for this class.
* supplied string with that. If that parse fails, or if the recentDateFormat */
* member has not been defined, attempt to parse with the defaultDateFormat public FTPTimestampParserImpl() {
* member. If that fails, throw a ParseException. setDefaultDateFormat(DEFAULT_SDF, null);
* setRecentDateFormat(DEFAULT_RECENT_SDF, null);
* This method allows a {@link Calendar} instance to be passed in which represents the }
* current (system) time.
* /**
* @see FTPTimestampParser#parseTimestamp(String) * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method}
* @param timestampStr The timestamp to be parsed * in the {@link FTPTimestampParser FTPTimestampParser} interface
* @param serverTime The current time for the server * according to this algorithm:
* @return the calendar *
* @throws ParseException if timestamp cannot be parsed * If the recentDateFormat member has been defined, try to parse the
* @since 1.5 * supplied string with that. If that parse fails, or if the recentDateFormat
*/ * member has not been defined, attempt to parse with the defaultDateFormat
public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException { * member. If that fails, throw a ParseException.
Calendar working = (Calendar) serverTime.clone(); *
working.setTimeZone(getServerTimeZone()); // is this needed? * This method assumes that the server time is the same as the local time.
*
Date parsed = null; * @param timestampStr The timestamp to be parsed
* @return a Calendar with the parsed timestamp
if (recentDateFormat != null) { * @see FTPTimestampParserImpl#parseTimestamp(String, Calendar)
Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it */
now.setTimeZone(this.getServerTimeZone()); @Override public Calendar parseTimestamp(String timestampStr) throws ParseException {
if (lenientFutureDates) { Calendar now = Calendar.getInstance();
// add a day to "now" so that "slop" doesn't cause a date return parseTimestamp(timestampStr, now);
// slightly in the future to roll back a full year. (Bug 35181 => NET-83) }
now.add(Calendar.DAY_OF_MONTH, 1);
} /**
// The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input * If the recentDateFormat member has been defined, try to parse the
// As 1970 was not a leap year, it cannot parse "Feb 29" correctly. * supplied string with that. If that parse fails, or if the recentDateFormat
// Java 1.5+ returns Mar 1 1970 * member has not been defined, attempt to parse with the defaultDateFormat
// Temporarily add the current year to the short date time * member. If that fails, throw a ParseException.
// to cope with short-date leap year strings. *
// Since Feb 29 is more that 6 months from the end of the year, this should be OK for * This method allows a {@link Calendar} instance to be passed in which represents the
// all instances of short dates which are +- 6 months from current date. * current (system) time.
// TODO this won't always work for systems that use short dates +0/-12months *
// e.g. if today is Jan 1 2001 and the short date is Feb 29 * @param timestampStr The timestamp to be parsed
String year = Integer.toString(now.get(Calendar.YEAR)); * @param serverTime The current time for the server
String timeStampStrPlusYear = timestampStr + " " + year; * @return the calendar
SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", * @throws ParseException if timestamp cannot be parsed
recentDateFormat.getDateFormatSymbols()); * @see FTPTimestampParser#parseTimestamp(String)
hackFormatter.setLenient(false); * @since 1.5
hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); */
ParsePosition pp = new ParsePosition(0); public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException {
parsed = hackFormatter.parse(timeStampStrPlusYear, pp); Calendar working = (Calendar) serverTime.clone();
// Check if we parsed the full string, if so it must have been a short date originally working.setTimeZone(getServerTimeZone()); // is this needed?
if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) {
working.setTime(parsed); Date parsed = null;
if (working.after(now)) { // must have been last year instead
working.add(Calendar.YEAR, -1);
}
setPrecision(recentDateSmallestUnitIndex, working);
return working;
}
}
ParsePosition pp = new ParsePosition(0); if (recentDateFormat != null) {
parsed = defaultDateFormat.parse(timestampStr, pp); Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it
// note, length checks are mandatory for us since now.setTimeZone(this.getServerTimeZone());
// SimpleDateFormat methods will succeed if less than if (lenientFutureDates) {
// full string is matched. They will also accept, // add a day to "now" so that "slop" doesn't cause a date
// despite "leniency" setting, a two-digit number as // slightly in the future to roll back a full year. (Bug 35181 => NET-83)
// a valid year (e.g. 22:04 will parse as 22 A.D.) now.add(Calendar.DAY_OF_MONTH, 1);
// so could mistakenly confuse an hour with a year, }
// if we don't insist on full length parsing. // The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input
if (parsed != null && pp.getIndex() == timestampStr.length()) { // As 1970 was not a leap year, it cannot parse "Feb 29" correctly.
working.setTime(parsed); // Java 1.5+ returns Mar 1 1970
} else { // Temporarily add the current year to the short date time
throw new ParseException( // to cope with short-date leap year strings.
"Timestamp '"+timestampStr+"' could not be parsed using a server time of " // Since Feb 29 is more that 6 months from the end of the year, this should be OK for
+serverTime.getTime().toString(), // all instances of short dates which are +- 6 months from current date.
pp.getErrorIndex()); // TODO this won't always work for systems that use short dates +0/-12months
// e.g. if today is Jan 1 2001 and the short date is Feb 29
String year = Integer.toString(now.get(Calendar.YEAR));
String timeStampStrPlusYear = timestampStr + " " + year;
SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy",
recentDateFormat.getDateFormatSymbols());
hackFormatter.setLenient(false);
hackFormatter.setTimeZone(recentDateFormat.getTimeZone());
ParsePosition pp = new ParsePosition(0);
parsed = hackFormatter.parse(timeStampStrPlusYear, pp);
// Check if we parsed the full string, if so it must have been a short date originally
if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) {
working.setTime(parsed);
if (working.after(now)) { // must have been last year instead
working.add(Calendar.YEAR, -1);
} }
setPrecision(defaultDateSmallestUnitIndex, working); setPrecision(recentDateSmallestUnitIndex, working);
return working; return working;
}
} }
/** ParsePosition pp = new ParsePosition(0);
* @return Returns the defaultDateFormat. parsed = defaultDateFormat.parse(timestampStr, pp);
*/ // note, length checks are mandatory for us since
public SimpleDateFormat getDefaultDateFormat() { // SimpleDateFormat methods will succeed if less than
return defaultDateFormat; // full string is matched. They will also accept,
} // despite "leniency" setting, a two-digit number as
/** // a valid year (e.g. 22:04 will parse as 22 A.D.)
* @return Returns the defaultDateFormat pattern string. // so could mistakenly confuse an hour with a year,
*/ // if we don't insist on full length parsing.
public String getDefaultDateFormatString() { if (parsed != null && pp.getIndex() == timestampStr.length()) {
return defaultDateFormat.toPattern(); working.setTime(parsed);
} } else {
/** throw new ParseException("Timestamp '"
* @param format The defaultDateFormat to be set. + timestampStr
* @param dfs the symbols to use (may be null) + "' could not be parsed using a server time of "
*/ + serverTime.getTime().toString(), pp.getErrorIndex());
private void setDefaultDateFormat(String format, DateFormatSymbols dfs) {
if (format != null) {
if (dfs != null) {
this.defaultDateFormat = new SimpleDateFormat(format, dfs);
} else {
this.defaultDateFormat = new SimpleDateFormat(format);
}
this.defaultDateFormat.setLenient(false);
} else {
this.defaultDateFormat = null;
}
this.defaultDateSmallestUnitIndex = getEntry(this.defaultDateFormat);
}
/**
* @return Returns the recentDateFormat.
*/
public SimpleDateFormat getRecentDateFormat() {
return recentDateFormat;
}
/**
* @return Returns the recentDateFormat.
*/
public String getRecentDateFormatString() {
return recentDateFormat.toPattern();
}
/**
* @param format The recentDateFormat to set.
* @param dfs the symbols to use (may be null)
*/
private void setRecentDateFormat(String format, DateFormatSymbols dfs) {
if (format != null) {
if (dfs != null) {
this.recentDateFormat = new SimpleDateFormat(format, dfs);
} else {
this.recentDateFormat = new SimpleDateFormat(format);
}
this.recentDateFormat.setLenient(false);
} else {
this.recentDateFormat = null;
}
this.recentDateSmallestUnitIndex = getEntry(this.recentDateFormat);
} }
setPrecision(defaultDateSmallestUnitIndex, working);
return working;
}
/** /**
* @return returns an array of 12 strings representing the short * @return Returns the defaultDateFormat.
* month names used by this parse. */
*/ public SimpleDateFormat getDefaultDateFormat() {
public String[] getShortMonths() { return defaultDateFormat;
return defaultDateFormat.getDateFormatSymbols().getShortMonths(); }
}
/**
* @return Returns the defaultDateFormat pattern string.
*/
public String getDefaultDateFormatString() {
return defaultDateFormat.toPattern();
}
/** /**
* @return Returns the serverTimeZone used by this parser. * @param format The defaultDateFormat to be set.
*/ * @param dfs the symbols to use (may be null)
public TimeZone getServerTimeZone() { */
return this.defaultDateFormat.getTimeZone(); private void setDefaultDateFormat(String format, DateFormatSymbols dfs) {
} if (format != null) {
/** if (dfs != null) {
* sets a TimeZone represented by the supplied ID string into all this.defaultDateFormat = new SimpleDateFormat(format, dfs);
* of the parsers used by this server. } else {
* @param serverTimeZone Time Id java.util.TimeZone id used by this.defaultDateFormat = new SimpleDateFormat(format);
* the ftp server. If null the client's local time zone is assumed. }
*/ this.defaultDateFormat.setLenient(false);
private void setServerTimeZone(String serverTimeZoneId) { } else {
TimeZone serverTimeZone = TimeZone.getDefault(); this.defaultDateFormat = null;
if (serverTimeZoneId != null) {
serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
}
this.defaultDateFormat.setTimeZone(serverTimeZone);
if (this.recentDateFormat != null) {
this.recentDateFormat.setTimeZone(serverTimeZone);
}
} }
this.defaultDateSmallestUnitIndex = getEntry(this.defaultDateFormat);
}
/** /**
* Implementation of the {@link Configurable Configurable} * @return Returns the recentDateFormat.
* interface. Configures this <code>FTPTimestampParser</code> according */
* to the following logic: public SimpleDateFormat getRecentDateFormat() {
* <p> return recentDateFormat;
* Set up the {@link FTPClientConfig#setDefaultDateFormatStr(String) defaultDateFormat} }
* and optionally the {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat}
* to values supplied in the config based on month names configured as follows:
* </p>
* <ul>
* <li>If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString}
* has been supplied in the <code>config</code>, use that to parse parse timestamps.</li>
* <li>Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode}
* has been supplied in the <code>config</code>, use the month names represented
* by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language}
* to parse timestamps.</li>
* <li>otherwise use default English month names</li>
* </ul><p>
* Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId}
* has been supplied via the config, set that into all date formats that have
* been configured.
* </p>
*/
@Override
public void configure(FTPClientConfig config) {
DateFormatSymbols dfs = null;
String languageCode = config.getServerLanguageCode();
String shortmonths = config.getShortMonthNames();
if (shortmonths != null) {
dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
} else if (languageCode != null) {
dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
} else {
dfs = FTPClientConfig.lookupDateFormatSymbols("en");
}
/**
* @return Returns the recentDateFormat.
*/
public String getRecentDateFormatString() {
return recentDateFormat.toPattern();
}
String recentFormatString = config.getRecentDateFormatStr(); /**
setRecentDateFormat(recentFormatString, dfs); * @param format The recentDateFormat to set.
* @param dfs the symbols to use (may be null)
*/
private void setRecentDateFormat(String format, DateFormatSymbols dfs) {
if (format != null) {
if (dfs != null) {
this.recentDateFormat = new SimpleDateFormat(format, dfs);
} else {
this.recentDateFormat = new SimpleDateFormat(format);
}
this.recentDateFormat.setLenient(false);
} else {
this.recentDateFormat = null;
}
this.recentDateSmallestUnitIndex = getEntry(this.recentDateFormat);
}
String defaultFormatString = config.getDefaultDateFormatStr(); /**
if (defaultFormatString == null) { * @return returns an array of 12 strings representing the short
throw new IllegalArgumentException("defaultFormatString cannot be null"); * month names used by this parse.
} */
setDefaultDateFormat(defaultFormatString, dfs); public String[] getShortMonths() {
return defaultDateFormat.getDateFormatSymbols().getShortMonths();
}
setServerTimeZone(config.getServerTimeZoneId()); /**
* @return Returns the serverTimeZone used by this parser.
*/
public TimeZone getServerTimeZone() {
return this.defaultDateFormat.getTimeZone();
}
this.lenientFutureDates = config.isLenientFutureDates(); /**
* sets a TimeZone represented by the supplied ID string into all
* of the parsers used by this server.
*
* @param serverTimeZone Time Id java.util.TimeZone id used by
* the ftp server. If null the client's local time zone is assumed.
*/
private void setServerTimeZone(String serverTimeZoneId) {
TimeZone serverTimeZone = TimeZone.getDefault();
if (serverTimeZoneId != null) {
serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
} }
/** this.defaultDateFormat.setTimeZone(serverTimeZone);
* @return Returns the lenientFutureDates. if (this.recentDateFormat != null) {
*/ this.recentDateFormat.setTimeZone(serverTimeZone);
boolean isLenientFutureDates() {
return lenientFutureDates;
} }
/** }
* @param lenientFutureDates The lenientFutureDates to set.
*/ /**
void setLenientFutureDates(boolean lenientFutureDates) { * Implementation of the {@link Configurable Configurable}
this.lenientFutureDates = lenientFutureDates; * interface. Configures this <code>FTPTimestampParser</code> according
* to the following logic:
* <p>
* Set up the {@link FTPClientConfig#setDefaultDateFormatStr(String) defaultDateFormat}
* and optionally the {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat}
* to values supplied in the config based on month names configured as follows:
* </p>
* <ul>
* <li>If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString}
* has been supplied in the <code>config</code>, use that to parse parse timestamps.</li>
* <li>Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode}
* has been supplied in the <code>config</code>, use the month names represented
* by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language}
* to parse timestamps.</li>
* <li>otherwise use default English month names</li>
* </ul><p>
* Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String)
* serverTimeZoneId}
* has been supplied via the config, set that into all date formats that have
* been configured.
* </p>
*/
@Override public void configure(FTPClientConfig config) {
DateFormatSymbols dfs = null;
String languageCode = config.getServerLanguageCode();
String shortmonths = config.getShortMonthNames();
if (shortmonths != null) {
dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
} else if (languageCode != null) {
dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
} else {
dfs = FTPClientConfig.lookupDateFormatSymbols("en");
}
String recentFormatString = config.getRecentDateFormatStr();
setRecentDateFormat(recentFormatString, dfs);
String defaultFormatString = config.getDefaultDateFormatStr();
if (defaultFormatString == null) {
throw new IllegalArgumentException("defaultFormatString cannot be null");
} }
setDefaultDateFormat(defaultFormatString, dfs);
setServerTimeZone(config.getServerTimeZoneId());
this.lenientFutureDates = config.isLenientFutureDates();
}
/**
* @return Returns the lenientFutureDates.
*/
boolean isLenientFutureDates() {
return lenientFutureDates;
}
/**
* @param lenientFutureDates The lenientFutureDates to set.
*/
void setLenientFutureDates(boolean lenientFutureDates) {
this.lenientFutureDates = lenientFutureDates;
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
@ -54,216 +55,207 @@ import org.apache.commons.net.ftp.FTPFileEntryParserImpl;
* *
* @since 3.0 * @since 3.0
*/ */
public class MLSxEntryParser extends FTPFileEntryParserImpl public class MLSxEntryParser extends FTPFileEntryParserImpl {
{ // This class is immutable, so a single instance can be shared.
// This class is immutable, so a single instance can be shared. private static final MLSxEntryParser PARSER = new MLSxEntryParser();
private static final MLSxEntryParser PARSER = new MLSxEntryParser();
private static final HashMap<String, Integer> TYPE_TO_INT = new HashMap<String, Integer>(); private static final HashMap<String, Integer> TYPE_TO_INT = new HashMap<String, Integer>();
static {
TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE));
TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory
TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a parent dir
TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir
}
private static int UNIX_GROUPS[] = { // Groups in order of mode digits static {
FTPFile.USER_ACCESS, TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE));
FTPFile.GROUP_ACCESS, TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory
FTPFile.WORLD_ACCESS, TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a parent dir
}; TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir
}
private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value private static int UNIX_GROUPS[] = { // Groups in order of mode digits
/* 0 */ {}, FTPFile.USER_ACCESS, FTPFile.GROUP_ACCESS, FTPFile.WORLD_ACCESS,
/* 1 */ {FTPFile.EXECUTE_PERMISSION}, };
/* 2 */ {FTPFile.WRITE_PERMISSION},
/* 3 */ {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION},
/* 4 */ {FTPFile.READ_PERMISSION},
/* 5 */ {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION},
/* 6 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION},
/* 7 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION},
};
/** private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value
* Create the parser for MSLT and MSLD listing entries /* 0 */ {},
* This class is immutable, so one can use {@link #getInstance()} instead. /* 1 */ { FTPFile.EXECUTE_PERMISSION },
*/ /* 2 */ { FTPFile.WRITE_PERMISSION },
public MLSxEntryParser() /* 3 */ { FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION },
{ /* 4 */ { FTPFile.READ_PERMISSION },
super(); /* 5 */ { FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION },
} /* 6 */ { FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION },
/* 7 */ { FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION },
};
@Override /**
public FTPFile parseFTPEntry(String entry) { * Create the parser for MSLT and MSLD listing entries
if (entry.startsWith(" ")) {// leading space means no facts are present * This class is immutable, so one can use {@link #getInstance()} instead.
if (entry.length() > 1) { // is there a path name? */
FTPFile file = new FTPFile(); public MLSxEntryParser() {
file.setRawListing(entry); super();
file.setName(entry.substring(1)); }
return file;
} else {
return null; // Invalid - no pathname
}
} @Override public FTPFile parseFTPEntry(String entry) {
String parts[] = entry.split(" ",2); // Path may contain space if (entry.startsWith(" ")) {// leading space means no facts are present
if (parts.length != 2 || parts[1].length() == 0) { if (entry.length() > 1) { // is there a path name?
return null; // no space found or no file name
}
final String factList = parts[0];
if (!factList.endsWith(";")) {
return null;
}
FTPFile file = new FTPFile(); FTPFile file = new FTPFile();
file.setRawListing(entry); file.setRawListing(entry);
file.setName(parts[1]); file.setName(entry.substring(1));
String[] facts = factList.split(";");
boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode=");
for(String fact : facts) {
String []factparts = fact.split("=", -1); // Don't drop empty values
// Sample missing permission
// drwx------ 2 mirror mirror 4096 Mar 13 2010 subversion
// modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion
if (factparts.length != 2) {
return null; // invalid - there was no "=" sign
}
String factname = factparts[0].toLowerCase(Locale.ENGLISH);
String factvalue = factparts[1];
if (factvalue.length() == 0) {
continue; // nothing to see here
}
String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH);
if ("size".equals(factname)) {
file.setSize(Long.parseLong(factvalue));
}
else if ("sizd".equals(factname)) { // Directory size
file.setSize(Long.parseLong(factvalue));
}
else if ("modify".equals(factname)) {
final Calendar parsed = parseGMTdateTime(factvalue);
if (parsed == null) {
return null;
}
file.setTimestamp(parsed);
}
else if ("type".equals(factname)) {
Integer intType = TYPE_TO_INT.get(valueLowerCase);
if (intType == null) {
file.setType(FTPFile.UNKNOWN_TYPE);
} else {
file.setType(intType.intValue());
}
}
else if (factname.startsWith("unix.")) {
String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH);
if ("group".equals(unixfact)){
file.setGroup(factvalue);
} else if ("owner".equals(unixfact)){
file.setUser(factvalue);
} else if ("mode".equals(unixfact)){ // e.g. 0[1]755
int off = factvalue.length()-3; // only parse last 3 digits
for(int i=0; i < 3; i++){
int ch = factvalue.charAt(off+i)-'0';
if (ch >= 0 && ch <= 7) { // Check it's valid octal
for(int p : UNIX_PERMS[ch]) {
file.setPermission(UNIX_GROUPS[i], p, true);
}
} else {
// TODO should this cause failure, or can it be reported somehow?
}
} // digits
} // mode
} // unix.
else if (!hasUnixMode && "perm".equals(factname)) { // skip if we have the UNIX.mode
doUnixPerms(file, valueLowerCase);
} // process "perm"
} // each fact
return file; return file;
} else {
return null; // Invalid - no pathname
}
} }
String parts[] = entry.split(" ", 2); // Path may contain space
/** if (parts.length != 2 || parts[1].length() == 0) {
* Parse a GMT time stamp of the form YYYYMMDDHHMMSS[.sss] return null; // no space found or no file name
* }
* @param timestamp the date-time to parse final String factList = parts[0];
* @return a Calendar entry, may be {@code null} if (!factList.endsWith(";")) {
* @since 3.4 return null;
*/ }
public static Calendar parseGMTdateTime(String timestamp) { FTPFile file = new FTPFile();
final SimpleDateFormat sdf; file.setRawListing(entry);
final boolean hasMillis; file.setName(parts[1]);
if (timestamp.contains(".")){ String[] facts = factList.split(";");
sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode=");
hasMillis = true; for (String fact : facts) {
} else { String[] factparts = fact.split("=", -1); // Don't drop empty values
sdf = new SimpleDateFormat("yyyyMMddHHmmss"); // Sample missing permission
hasMillis = false; // drwx------ 2 mirror mirror 4096 Mar 13 2010 subversion
} // modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion
TimeZone GMT = TimeZone.getTimeZone("GMT"); if (factparts.length != 2) {
// both timezones need to be set for the parse to work OK return null; // invalid - there was no "=" sign
sdf.setTimeZone(GMT); }
GregorianCalendar gc = new GregorianCalendar(GMT); String factname = factparts[0].toLowerCase(Locale.ENGLISH);
ParsePosition pos = new ParsePosition(0); String factvalue = factparts[1];
sdf.setLenient(false); // We want to parse the whole string if (factvalue.length() == 0) {
final Date parsed = sdf.parse(timestamp, pos); continue; // nothing to see here
if (pos.getIndex() != timestamp.length()) { }
return null; // did not fully parse the input String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH);
if ("size".equals(factname)) {
file.setSize(Long.parseLong(factvalue));
} else if ("sizd".equals(factname)) { // Directory size
file.setSize(Long.parseLong(factvalue));
} else if ("modify".equals(factname)) {
final Calendar parsed = parseGMTdateTime(factvalue);
if (parsed == null) {
return null;
} }
gc.setTime(parsed); file.setTimestamp(parsed);
if (!hasMillis) { } else if ("type".equals(factname)) {
gc.clear(Calendar.MILLISECOND); // flag up missing ms units Integer intType = TYPE_TO_INT.get(valueLowerCase);
if (intType == null) {
file.setType(FTPFile.UNKNOWN_TYPE);
} else {
file.setType(intType.intValue());
} }
return gc; } else if (factname.startsWith("unix.")) {
} String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH);
if ("group".equals(unixfact)) {
file.setGroup(factvalue);
} else if ("owner".equals(unixfact)) {
file.setUser(factvalue);
} else if ("mode".equals(unixfact)) { // e.g. 0[1]755
int off = factvalue.length() - 3; // only parse last 3 digits
for (int i = 0; i < 3; i++) {
int ch = factvalue.charAt(off + i) - '0';
if (ch >= 0 && ch <= 7) { // Check it's valid octal
for (int p : UNIX_PERMS[ch]) {
file.setPermission(UNIX_GROUPS[i], p, true);
}
} else {
// TODO should this cause failure, or can it be reported somehow?
}
} // digits
} // mode
} // unix.
else if (!hasUnixMode && "perm".equals(factname)) { // skip if we have the UNIX.mode
doUnixPerms(file, valueLowerCase);
} // process "perm"
} // each fact
return file;
}
// perm-fact = "Perm" "=" *pvals /**
// pvals = "a" / "c" / "d" / "e" / "f" / * Parse a GMT time stamp of the form YYYYMMDDHHMMSS[.sss]
// "l" / "m" / "p" / "r" / "w" *
private void doUnixPerms(FTPFile file, String valueLowerCase) { * @param timestamp the date-time to parse
for(char c : valueLowerCase.toCharArray()) { * @return a Calendar entry, may be {@code null}
// TODO these are mostly just guesses at present * @since 3.4
switch (c) { */
case 'a': // (file) may APPEnd public static Calendar parseGMTdateTime(String timestamp) {
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); final SimpleDateFormat sdf;
break; final boolean hasMillis;
case 'c': // (dir) files may be created in the dir if (timestamp.contains(".")) {
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
break; hasMillis = true;
case 'd': // deletable } else {
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); sdf = new SimpleDateFormat("yyyyMMddHHmmss");
break; hasMillis = false;
case 'e': // (dir) can change to this dir
file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
break;
case 'f': // (file) renamable
// ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'l': // (dir) can be listed
file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true);
break;
case 'm': // (dir) can create directory here
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'p': // (dir) entries may be deleted
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'r': // (files) file may be RETRieved
file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
break;
case 'w': // (files) file may be STORed
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
default:
break;
// ignore unexpected flag for now.
} // switch
} // each char
} }
TimeZone GMT = TimeZone.getTimeZone("GMT");
public static FTPFile parseEntry(String entry) { // both timezones need to be set for the parse to work OK
return PARSER.parseFTPEntry(entry); sdf.setTimeZone(GMT);
GregorianCalendar gc = new GregorianCalendar(GMT);
ParsePosition pos = new ParsePosition(0);
sdf.setLenient(false); // We want to parse the whole string
final Date parsed = sdf.parse(timestamp, pos);
if (pos.getIndex() != timestamp.length()) {
return null; // did not fully parse the input
} }
gc.setTime(parsed);
public static MLSxEntryParser getInstance() { if (!hasMillis) {
return PARSER; gc.clear(Calendar.MILLISECOND); // flag up missing ms units
} }
return gc;
}
// perm-fact = "Perm" "=" *pvals
// pvals = "a" / "c" / "d" / "e" / "f" /
// "l" / "m" / "p" / "r" / "w"
private void doUnixPerms(FTPFile file, String valueLowerCase) {
for (char c : valueLowerCase.toCharArray()) {
// TODO these are mostly just guesses at present
switch (c) {
case 'a': // (file) may APPEnd
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'c': // (dir) files may be created in the dir
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'd': // deletable
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'e': // (dir) can change to this dir
file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
break;
case 'f': // (file) renamable
// ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'l': // (dir) can be listed
file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true);
break;
case 'm': // (dir) can create directory here
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'p': // (dir) entries may be deleted
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
case 'r': // (files) file may be RETRieved
file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
break;
case 'w': // (files) file may be STORed
file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
break;
default:
break;
// ignore unexpected flag for now.
} // switch
} // each char
}
public static FTPFile parseEntry(String entry) {
return PARSER.parseFTPEntry(entry);
}
public static MLSxEntryParser getInstance() {
return PARSER;
}
} }

@ -29,127 +29,125 @@ import org.apache.commons.net.ftp.FTPFile;
* *
* @version $Id: MVSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @version $Id: MVSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for
* usage instructions) * usage instructions)
*/ */
public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
static final int UNKNOWN_LIST_TYPE = -1; static final int UNKNOWN_LIST_TYPE = -1;
static final int FILE_LIST_TYPE = 0; static final int FILE_LIST_TYPE = 0;
static final int MEMBER_LIST_TYPE = 1; static final int MEMBER_LIST_TYPE = 1;
static final int UNIX_LIST_TYPE = 2; static final int UNIX_LIST_TYPE = 2;
static final int JES_LEVEL_1_LIST_TYPE = 3; static final int JES_LEVEL_1_LIST_TYPE = 3;
static final int JES_LEVEL_2_LIST_TYPE = 4; static final int JES_LEVEL_2_LIST_TYPE = 4;
private int isType = UNKNOWN_LIST_TYPE; private int isType = UNKNOWN_LIST_TYPE;
/** /**
* Fallback parser for Unix-style listings * Fallback parser for Unix-style listings
*/ */
private UnixFTPEntryParser unixFTPEntryParser; private UnixFTPEntryParser unixFTPEntryParser;
/** /**
* Dates are ignored for file lists, but are used for member lists where * Dates are ignored for file lists, but are used for member lists where
* possible * possible
*/ */
static final String DEFAULT_DATE_FORMAT = "yyyy/MM/dd HH:mm"; // 2001/09/18 static final String DEFAULT_DATE_FORMAT = "yyyy/MM/dd HH:mm"; // 2001/09/18
// 13:52 // 13:52
/** /**
* Matches these entries: * Matches these entries:
* <pre> * <pre>
* Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname * Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname
* B10142 3390 2006/03/20 2 31 F 80 80 PS MDI.OKL.WORK * B10142 3390 2006/03/20 2 31 F 80 80 PS MDI.OKL.WORK
* </pre> * </pre>
*/ */
static final String FILE_LIST_REGEX = "\\S+\\s+" + // volume static final String FILE_LIST_REGEX = "\\S+\\s+" + // volume
// ignored // ignored
"\\S+\\s+" + // unit - ignored "\\S+\\s+" + // unit - ignored
"\\S+\\s+" + // access date - ignored "\\S+\\s+" + // access date - ignored
"\\S+\\s+" + // extents -ignored "\\S+\\s+" + // extents -ignored
"\\S+\\s+" + // used - ignored "\\S+\\s+" + // used - ignored
"[FV]\\S*\\s+" + // recfm - must start with F or V "[FV]\\S*\\s+" + // recfm - must start with F or V
"\\S+\\s+" + // logical record length -ignored "\\S+\\s+" + // logical record length -ignored
"\\S+\\s+" + // block size - ignored "\\S+\\s+" + // block size - ignored
"(PS|PO|PO-E)\\s+" + // Dataset organisation. Many exist "(PS|PO|PO-E)\\s+" + // Dataset organisation. Many exist
// but only support: PS, PO, PO-E // but only support: PS, PO, PO-E
"(\\S+)\\s*"; // Dataset Name (file name) "(\\S+)\\s*"; // Dataset Name (file name)
/** /**
* Matches these entries: * Matches these entries:
* <pre> * <pre>
* Name VV.MM Created Changed Size Init Mod Id * Name VV.MM Created Changed Size Init Mod Id
* TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001 * TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001
* </pre> * </pre>
*/ */
static final String MEMBER_LIST_REGEX = "(\\S+)\\s+" + // name static final String MEMBER_LIST_REGEX = "(\\S+)\\s+" + // name
"\\S+\\s+" + // version, modification (ignored) "\\S+\\s+" + // version, modification (ignored)
"\\S+\\s+" + // create date (ignored) "\\S+\\s+" + // create date (ignored)
"(\\S+)\\s+" + // modification date "(\\S+)\\s+" + // modification date
"(\\S+)\\s+" + // modification time "(\\S+)\\s+" + // modification time
"\\S+\\s+" + // size in lines (ignored) "\\S+\\s+" + // size in lines (ignored)
"\\S+\\s+" + // size in lines at creation(ignored) "\\S+\\s+" + // size in lines at creation(ignored)
"\\S+\\s+" + // lines modified (ignored) "\\S+\\s+" + // lines modified (ignored)
"\\S+\\s*"; // id of user who modified (ignored) "\\S+\\s*"; // id of user who modified (ignored)
/** /**
* Matches these entries, note: no header: * Matches these entries, note: no header:
* <pre> * <pre>
* IBMUSER1 JOB01906 OUTPUT 3 Spool Files * IBMUSER1 JOB01906 OUTPUT 3 Spool Files
* 012345678901234567890123456789012345678901234 * 012345678901234567890123456789012345678901234
* 1 2 3 4 * 1 2 3 4
* </pre> * </pre>
*/ */
static final String JES_LEVEL_1_LIST_REGEX = static final String JES_LEVEL_1_LIST_REGEX = "(\\S+)\\s+" + // job name ignored
"(\\S+)\\s+" + // job name ignored "(\\S+)\\s+" + // job number
"(\\S+)\\s+" + // job number "(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE)
"(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) "(\\S+)\\s+" + // number of spool files
"(\\S+)\\s+" + // number of spool files "(\\S+)\\s+" + // Text "Spool" ignored
"(\\S+)\\s+" + // Text "Spool" ignored "(\\S+)\\s*" // Text "Files" ignored
"(\\S+)\\s*" // Text "Files" ignored ;
;
/**
/** * JES INTERFACE LEVEL 2 parser
* JES INTERFACE LEVEL 2 parser * Matches these entries:
* Matches these entries: * <pre>
* <pre> * JOBNAME JOBID OWNER STATUS CLASS
* JOBNAME JOBID OWNER STATUS CLASS * IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files
* IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files * IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files
* IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files * </pre>
* </pre> * Sample output from FTP session:
* Sample output from FTP session: * <pre>
* <pre> * ftp> quote site filetype=jes
* ftp> quote site filetype=jes * 200 SITE command was accepted
* 200 SITE command was accepted * ftp> ls
* ftp> ls * 200 Port request OK.
* 200 Port request OK. * 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER
* 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER * JOBNAME JOBID OWNER STATUS CLASS
* JOBNAME JOBID OWNER STATUS CLASS * IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files
* IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files * IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files
* IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files * 250 List completed successfully.
* 250 List completed successfully. * ftp> ls job01906
* ftp> ls job01906 * 200 Port request OK.
* 200 Port request OK. * 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER
* 125 List started OK for JESJOBNAME=IBMUSER*, JESSTATUS=ALL and JESOWNER=IBMUSER * JOBNAME JOBID OWNER STATUS CLASS
* JOBNAME JOBID OWNER STATUS CLASS * IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000
* IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 * --------
* -------- * ID STEPNAME PROCSTEP C DDNAME BYTE-COUNT
* ID STEPNAME PROCSTEP C DDNAME BYTE-COUNT * 001 JES2 A JESMSGLG 858
* 001 JES2 A JESMSGLG 858 * 002 JES2 A JESJCL 128
* 002 JES2 A JESJCL 128 * 003 JES2 A JESYSMSG 443
* 003 JES2 A JESYSMSG 443 * 3 spool files
* 3 spool files * 250 List completed successfully.
* 250 List completed successfully. * </pre>
* </pre> */
*/
static final String JES_LEVEL_2_LIST_REGEX = "(\\S+)\\s+" + // job name ignored
static final String JES_LEVEL_2_LIST_REGEX = "(\\S+)\\s+" + // job number
"(\\S+)\\s+" + // job name ignored "(\\S+)\\s+" + // owner ignored
"(\\S+)\\s+" + // job number "(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) ignored
"(\\S+)\\s+" + // owner ignored "(\\S+)\\s+" + // job class ignored
"(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) ignored "(\\S+).*" // rest ignored
"(\\S+)\\s+" + // job class ignored ;
"(\\S+).*" // rest ignored
;
/* /*
* --------------------------------------------------------------------- * ---------------------------------------------------------------------
@ -238,317 +236,302 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
* *
*/ */
/** /**
* The sole constructor for a MVSFTPEntryParser object. * The sole constructor for a MVSFTPEntryParser object.
* */
*/ public MVSFTPEntryParser() {
public MVSFTPEntryParser() { super(""); // note the regex is set in preParse.
super(""); // note the regex is set in preParse. super.configure(null); // configure parser with default configurations
super.configure(null); // configure parser with default configurations }
}
/**
/** * Parses a line of an z/OS - MVS FTP server file listing and converts it
* Parses a line of an z/OS - MVS FTP server file listing and converts it * into a usable format in the form of an <code> FTPFile </code> instance.
* into a usable format in the form of an <code> FTPFile </code> instance. * If the file listing line doesn't describe a file, then
* If the file listing line doesn't describe a file, then * <code> null </code> is returned. Otherwise a <code> FTPFile </code>
* <code> null </code> is returned. Otherwise a <code> FTPFile </code> * instance representing the file is returned.
* instance representing the file is returned. *
* * @param entry A line of text from the file listing
* @param entry * @return An FTPFile instance corresponding to the supplied entry
* A line of text from the file listing */
* @return An FTPFile instance corresponding to the supplied entry @Override public FTPFile parseFTPEntry(String entry) {
*/ boolean isParsed = false;
@Override FTPFile f = new FTPFile();
public FTPFile parseFTPEntry(String entry) {
boolean isParsed = false; if (isType == FILE_LIST_TYPE) {
FTPFile f = new FTPFile(); isParsed = parseFileList(f, entry);
} else if (isType == MEMBER_LIST_TYPE) {
if (isType == FILE_LIST_TYPE) { isParsed = parseMemberList(f, entry);
isParsed = parseFileList(f, entry); if (!isParsed) {
} else if (isType == MEMBER_LIST_TYPE) { isParsed = parseSimpleEntry(f, entry);
isParsed = parseMemberList(f, entry); }
if (!isParsed) { } else if (isType == UNIX_LIST_TYPE) {
isParsed = parseSimpleEntry(f, entry); isParsed = parseUnixList(f, entry);
} } else if (isType == JES_LEVEL_1_LIST_TYPE) {
} else if (isType == UNIX_LIST_TYPE) { isParsed = parseJeslevel1List(f, entry);
isParsed = parseUnixList(f, entry); } else if (isType == JES_LEVEL_2_LIST_TYPE) {
} else if (isType == JES_LEVEL_1_LIST_TYPE) { isParsed = parseJeslevel2List(f, entry);
isParsed = parseJeslevel1List(f, entry);
} else if (isType == JES_LEVEL_2_LIST_TYPE) {
isParsed = parseJeslevel2List(f, entry);
}
if (!isParsed) {
f = null;
}
return f;
} }
/** if (!isParsed) {
* Parse entries representing a dataset list. Only datasets with DSORG PS or f = null;
* PO or PO-E and with RECFM F* or V* will be parsed.
*
* Format of ZOS/MVS file list: 1 2 3 4 5 6 7 8 9 10 Volume Unit Referred
* Ext Used Recfm Lrecl BlkSz Dsorg Dsname B10142 3390 2006/03/20 2 31 F 80
* 80 PS MDI.OKL.WORK ARCIVE Not Direct Access Device
* KJ.IOP998.ERROR.PL.UNITTEST B1N231 3390 2006/03/20 1 15 VB 256 27998 PO
* PLU B1N231 3390 2006/03/20 1 15 VB 256 27998 PO-E PLB
*
* ----------------------------------- Group within Regex [1] Volume [2]
* Unit [3] Referred [4] Ext: number of extents [5] Used [6] Recfm: Record
* format [7] Lrecl: Logical record length [8] BlkSz: Block size [9] Dsorg:
* Dataset organisation. Many exists but only support: PS, PO, PO-E [10]
* Dsname: Dataset name
*
* Note: When volume is ARCIVE, it means the dataset is stored somewhere in
* a tape archive. These entries is currently not supported by this parser.
* A null value is returned.
*
* @param file
* will be updated with Name, Type, Timestamp if parsed.
* @param entry zosDirectoryEntry
* @return true: entry was parsed, false: entry was not parsed.
*/
private boolean parseFileList(FTPFile file, String entry) {
if (matches(entry)) {
file.setRawListing(entry);
String name = group(2);
String dsorg = group(1);
file.setName(name);
// DSORG
if ("PS".equals(dsorg)) {
file.setType(FTPFile.FILE_TYPE);
}
else if ("PO".equals(dsorg) || "PO-E".equals(dsorg)) {
// regex already ruled out anything other than PO or PO-E
file.setType(FTPFile.DIRECTORY_TYPE);
}
else {
return false;
}
return true;
}
return false;
} }
/** return f;
* Parse entries within a partitioned dataset. }
*
* Format of a memberlist within a PDS: /**
* <pre> * Parse entries representing a dataset list. Only datasets with DSORG PS or
* 0 1 2 3 4 5 6 7 8 * PO or PO-E and with RECFM F* or V* will be parsed.
* Name VV.MM Created Changed Size Init Mod Id *
* TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001 * Format of ZOS/MVS file list: 1 2 3 4 5 6 7 8 9 10 Volume Unit Referred
* TBTOOL 01.12 2002/09/12 2004/11/26 19:54 51 28 0 KIL001 * Ext Used Recfm Lrecl BlkSz Dsorg Dsname B10142 3390 2006/03/20 2 31 F 80
* * 80 PS MDI.OKL.WORK ARCIVE Not Direct Access Device
* ------------------------------------------- * KJ.IOP998.ERROR.PL.UNITTEST B1N231 3390 2006/03/20 1 15 VB 256 27998 PO
* [1] Name * PLU B1N231 3390 2006/03/20 1 15 VB 256 27998 PO-E PLB
* [2] VV.MM: Version . modification *
* [3] Created: yyyy / MM / dd * ----------------------------------- Group within Regex [1] Volume [2]
* [4,5] Changed: yyyy / MM / dd HH:mm * Unit [3] Referred [4] Ext: number of extents [5] Used [6] Recfm: Record
* [6] Size: number of lines * format [7] Lrecl: Logical record length [8] BlkSz: Block size [9] Dsorg:
* [7] Init: number of lines when first created * Dataset organisation. Many exists but only support: PS, PO, PO-E [10]
* [8] Mod: number of modified lines a last save * Dsname: Dataset name
* [9] Id: User id for last update *
* </pre> * Note: When volume is ARCIVE, it means the dataset is stored somewhere in
* * a tape archive. These entries is currently not supported by this parser.
* @param file * A null value is returned.
* will be updated with Name, Type and Timestamp if parsed. *
* @param entry zosDirectoryEntry * @param file will be updated with Name, Type, Timestamp if parsed.
* @return true: entry was parsed, false: entry was not parsed. * @param entry zosDirectoryEntry
*/ * @return true: entry was parsed, false: entry was not parsed.
private boolean parseMemberList(FTPFile file, String entry) { */
if (matches(entry)) { private boolean parseFileList(FTPFile file, String entry) {
file.setRawListing(entry); if (matches(entry)) {
String name = group(1); file.setRawListing(entry);
String datestr = group(2) + " " + group(3); String name = group(2);
file.setName(name); String dsorg = group(1);
file.setType(FTPFile.FILE_TYPE); file.setName(name);
try {
file.setTimestamp(super.parseTimestamp(datestr)); // DSORG
} catch (ParseException e) { if ("PS".equals(dsorg)) {
e.printStackTrace(); file.setType(FTPFile.FILE_TYPE);
// just ignore parsing errors. } else if ("PO".equals(dsorg) || "PO-E".equals(dsorg)) {
// TODO check this is ok // regex already ruled out anything other than PO or PO-E
return false; // this is a parsing failure too. file.setType(FTPFile.DIRECTORY_TYPE);
} } else {
return true;
}
return false; return false;
} }
/** return true;
* Assigns the name to the first word of the entry. Only to be used from a
* safe context, for example from a memberlist, where the regex for some
* reason fails. Then just assign the name field of FTPFile.
*
* @param file
* @param entry
* @return true if the entry string is non-null and non-empty
*/
private boolean parseSimpleEntry(FTPFile file, String entry) {
if (entry != null && entry.trim().length() > 0) {
file.setRawListing(entry);
String name = entry.split(" ")[0];
file.setName(name);
file.setType(FTPFile.FILE_TYPE);
return true;
}
return false;
} }
/** return false;
* Parse the entry as a standard unix file. Using the UnixFTPEntryParser. }
*
* @param file /**
* @param entry * Parse entries within a partitioned dataset.
* @return true: entry is parsed, false: entry could not be parsed. *
*/ * Format of a memberlist within a PDS:
private boolean parseUnixList(FTPFile file, String entry) { * <pre>
file = unixFTPEntryParser.parseFTPEntry(entry); * 0 1 2 3 4 5 6 7 8
if (file == null) { * Name VV.MM Created Changed Size Init Mod Id
return false; * TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001
} * TBTOOL 01.12 2002/09/12 2004/11/26 19:54 51 28 0 KIL001
return true; *
* -------------------------------------------
* [1] Name
* [2] VV.MM: Version . modification
* [3] Created: yyyy / MM / dd
* [4,5] Changed: yyyy / MM / dd HH:mm
* [6] Size: number of lines
* [7] Init: number of lines when first created
* [8] Mod: number of modified lines a last save
* [9] Id: User id for last update
* </pre>
*
* @param file will be updated with Name, Type and Timestamp if parsed.
* @param entry zosDirectoryEntry
* @return true: entry was parsed, false: entry was not parsed.
*/
private boolean parseMemberList(FTPFile file, String entry) {
if (matches(entry)) {
file.setRawListing(entry);
String name = group(1);
String datestr = group(2) + " " + group(3);
file.setName(name);
file.setType(FTPFile.FILE_TYPE);
try {
file.setTimestamp(super.parseTimestamp(datestr));
} catch (ParseException e) {
e.printStackTrace();
// just ignore parsing errors.
// TODO check this is ok
return false; // this is a parsing failure too.
}
return true;
} }
/** return false;
* Matches these entries, note: no header: }
* <pre>
* [1] [2] [3] [4] [5] /**
* IBMUSER1 JOB01906 OUTPUT 3 Spool Files * Assigns the name to the first word of the entry. Only to be used from a
* 012345678901234567890123456789012345678901234 * safe context, for example from a memberlist, where the regex for some
* 1 2 3 4 * reason fails. Then just assign the name field of FTPFile.
* ------------------------------------------- *
* Group in regex * @return true if the entry string is non-null and non-empty
* [1] Job name */
* [2] Job number private boolean parseSimpleEntry(FTPFile file, String entry) {
* [3] Job status (INPUT,ACTIVE,OUTPUT) if (entry != null && entry.trim().length() > 0) {
* [4] Number of sysout files file.setRawListing(entry);
* [5] The string "Spool Files" String name = entry.split(" ")[0];
*</pre> file.setName(name);
* file.setType(FTPFile.FILE_TYPE);
* @param file return true;
* will be updated with Name, Type and Timestamp if parsed.
* @param entry zosDirectoryEntry
* @return true: entry was parsed, false: entry was not parsed.
*/
private boolean parseJeslevel1List(FTPFile file, String entry) {
if (matches(entry)) {
if (group(3).equalsIgnoreCase("OUTPUT")) {
file.setRawListing(entry);
String name = group(2); /* Job Number, used by GET */
file.setName(name);
file.setType(FTPFile.FILE_TYPE);
return true;
}
}
return false;
} }
return false;
/** }
* Matches these entries:
* <pre> /**
* [1] [2] [3] [4] [5] * Parse the entry as a standard unix file. Using the UnixFTPEntryParser.
* JOBNAME JOBID OWNER STATUS CLASS *
* IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files * @return true: entry is parsed, false: entry could not be parsed.
* IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files */
* 012345678901234567890123456789012345678901234 private boolean parseUnixList(FTPFile file, String entry) {
* 1 2 3 4 file = unixFTPEntryParser.parseFTPEntry(entry);
* ------------------------------------------- if (file == null) {
* Group in regex return false;
* [1] Job name
* [2] Job number
* [3] Owner
* [4] Job status (INPUT,ACTIVE,OUTPUT)
* [5] Job Class
* [6] The rest
* </pre>
*
* @param file
* will be updated with Name, Type and Timestamp if parsed.
* @param entry zosDirectoryEntry
* @return true: entry was parsed, false: entry was not parsed.
*/
private boolean parseJeslevel2List(FTPFile file, String entry) {
if (matches(entry)) {
if (group(4).equalsIgnoreCase("OUTPUT")) {
file.setRawListing(entry);
String name = group(2); /* Job Number, used by GET */
file.setName(name);
file.setType(FTPFile.FILE_TYPE);
return true;
}
}
return false;
} }
return true;
/** }
* preParse is called as part of the interface. Per definition is is called
* before the parsing takes place. /**
* Three kind of lists is recognize: * Matches these entries, note: no header:
* z/OS-MVS File lists * <pre>
* z/OS-MVS Member lists * [1] [2] [3] [4] [5]
* unix file lists * IBMUSER1 JOB01906 OUTPUT 3 Spool Files
* @since 2.0 * 012345678901234567890123456789012345678901234
*/ * 1 2 3 4
@Override * -------------------------------------------
public List<String> preParse(List<String> orig) { * Group in regex
// simply remove the header line. Composite logic will take care of the * [1] Job name
// two different types of * [2] Job number
// list in short order. * [3] Job status (INPUT,ACTIVE,OUTPUT)
if (orig != null && orig.size() > 0) { * [4] Number of sysout files
String header = orig.get(0); * [5] The string "Spool Files"
if (header.indexOf("Volume") >= 0 && header.indexOf("Dsname") >= 0) { * </pre>
setType(FILE_LIST_TYPE); *
super.setRegex(FILE_LIST_REGEX); * @param file will be updated with Name, Type and Timestamp if parsed.
} else if (header.indexOf("Name") >= 0 && header.indexOf("Id") >= 0) { * @param entry zosDirectoryEntry
setType(MEMBER_LIST_TYPE); * @return true: entry was parsed, false: entry was not parsed.
super.setRegex(MEMBER_LIST_REGEX); */
} else if (header.indexOf("total") == 0) { private boolean parseJeslevel1List(FTPFile file, String entry) {
setType(UNIX_LIST_TYPE); if (matches(entry)) {
unixFTPEntryParser = new UnixFTPEntryParser(); if (group(3).equalsIgnoreCase("OUTPUT")) {
} else if (header.indexOf("Spool Files") >= 30) { file.setRawListing(entry);
setType(JES_LEVEL_1_LIST_TYPE); String name = group(2); /* Job Number, used by GET */
super.setRegex(JES_LEVEL_1_LIST_REGEX); file.setName(name);
} else if (header.indexOf("JOBNAME") == 0 file.setType(FTPFile.FILE_TYPE);
&& header.indexOf("JOBID") > 8) {// header contains JOBNAME JOBID OWNER // STATUS CLASS return true;
setType(JES_LEVEL_2_LIST_TYPE); }
super.setRegex(JES_LEVEL_2_LIST_REGEX);
} else {
setType(UNKNOWN_LIST_TYPE);
}
if (isType != JES_LEVEL_1_LIST_TYPE) { // remove header is necessary
orig.remove(0);
}
}
return orig;
} }
/** return false;
* Explicitly set the type of listing being processed. }
* @param type The listing type.
*/ /**
void setType(int type) { * Matches these entries:
isType = type; * <pre>
* [1] [2] [3] [4] [5]
* JOBNAME JOBID OWNER STATUS CLASS
* IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files
* IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files
* 012345678901234567890123456789012345678901234
* 1 2 3 4
* -------------------------------------------
* Group in regex
* [1] Job name
* [2] Job number
* [3] Owner
* [4] Job status (INPUT,ACTIVE,OUTPUT)
* [5] Job Class
* [6] The rest
* </pre>
*
* @param file will be updated with Name, Type and Timestamp if parsed.
* @param entry zosDirectoryEntry
* @return true: entry was parsed, false: entry was not parsed.
*/
private boolean parseJeslevel2List(FTPFile file, String entry) {
if (matches(entry)) {
if (group(4).equalsIgnoreCase("OUTPUT")) {
file.setRawListing(entry);
String name = group(2); /* Job Number, used by GET */
file.setName(name);
file.setType(FTPFile.FILE_TYPE);
return true;
}
} }
/* return false;
* @return }
*/
@Override /**
protected FTPClientConfig getDefaultConfiguration() { * preParse is called as part of the interface. Per definition is is called
return new FTPClientConfig(FTPClientConfig.SYST_MVS, * before the parsing takes place.
DEFAULT_DATE_FORMAT, null); * Three kind of lists is recognize:
* z/OS-MVS File lists
* z/OS-MVS Member lists
* unix file lists
*
* @since 2.0
*/
@Override public List<String> preParse(List<String> orig) {
// simply remove the header line. Composite logic will take care of the
// two different types of
// list in short order.
if (orig != null && orig.size() > 0) {
String header = orig.get(0);
if (header.indexOf("Volume") >= 0 && header.indexOf("Dsname") >= 0) {
setType(FILE_LIST_TYPE);
super.setRegex(FILE_LIST_REGEX);
} else if (header.indexOf("Name") >= 0 && header.indexOf("Id") >= 0) {
setType(MEMBER_LIST_TYPE);
super.setRegex(MEMBER_LIST_REGEX);
} else if (header.indexOf("total") == 0) {
setType(UNIX_LIST_TYPE);
unixFTPEntryParser = new UnixFTPEntryParser();
} else if (header.indexOf("Spool Files") >= 30) {
setType(JES_LEVEL_1_LIST_TYPE);
super.setRegex(JES_LEVEL_1_LIST_REGEX);
} else if (header.indexOf("JOBNAME") == 0
&& header.indexOf("JOBID") > 8) {// header contains JOBNAME JOBID OWNER // STATUS CLASS
setType(JES_LEVEL_2_LIST_TYPE);
super.setRegex(JES_LEVEL_2_LIST_REGEX);
} else {
setType(UNKNOWN_LIST_TYPE);
}
if (isType != JES_LEVEL_1_LIST_TYPE) { // remove header is necessary
orig.remove(0);
}
} }
return orig;
}
/**
* Explicitly set the type of listing being processed.
*
* @param type The listing type.
*/
void setType(int type) {
isType = type;
}
/*
* @return
*/
@Override protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(FTPClientConfig.SYST_MVS, DEFAULT_DATE_FORMAT, null);
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.text.ParseException; import java.text.ParseException;
import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPClientConfig;
@ -28,44 +29,44 @@ import org.apache.commons.net.ftp.FTPFile;
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
* @since 3.1 * @since 3.1
*/ */
public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
{
static final String DEFAULT_DATE_FORMAT = "MMM d yyyy"; //Nov 9 2001
static final String DEFAULT_DATE_FORMAT
= "MMM d yyyy"; //Nov 9 2001 static final String DEFAULT_RECENT_DATE_FORMAT = "MMM d HH:mm"; //Nov 9 20:06
static final String DEFAULT_RECENT_DATE_FORMAT /**
= "MMM d HH:mm"; //Nov 9 20:06 * this is the regular expression used by this parser.
*
/** * Permissions:
* this is the regular expression used by this parser. * r the file is readable
* * w the file is writable
* Permissions: * x the file is executable
* r the file is readable * - the indicated permission is not granted
* w the file is writable * L mandatory locking occurs during access (the set-group-ID bit is
* x the file is executable * on and the group execution bit is off)
* - the indicated permission is not granted * s the set-user-ID or set-group-ID bit is on, and the corresponding
* L mandatory locking occurs during access (the set-group-ID bit is * user or group execution bit is also on
* on and the group execution bit is off) * S undefined bit-state (the set-user-ID bit is on and the user
* s the set-user-ID or set-group-ID bit is on, and the corresponding * execution bit is off)
* user or group execution bit is also on * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and
* S undefined bit-state (the set-user-ID bit is on and the user * execution is on
* execution bit is off) * T the 1000 bit is turned on, and execution is off (undefined bit-
* t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and * state)
* execution is on * e z/OS external link bit
* T the 1000 bit is turned on, and execution is off (undefined bit- */
* state) private static final String REGEX = "([bcdelfmpSs-])"
* e z/OS external link bit // type (1)
*/ + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+"
private static final String REGEX = // permission
"([bcdelfmpSs-])" // type (1) + "("
+ "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission + "(folder\\s+)"
+ "(" + "|"
+ "(folder\\s+)" + "((\\d+)\\s+(\\d+)\\s+)"
+ "|" // resource size & data size
+ "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size + ")"
+ ")" + "(\\d+)\\s+"
+ "(\\d+)\\s+" // size // size
/* /*
* numeric or standard format date: * numeric or standard format date:
* yyyy-mm-dd (expecting hh:mm to follow) * yyyy-mm-dd (expecting hh:mm to follow)
@ -74,205 +75,170 @@ public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl
* N.B. use non-space for MMM to allow for languages such as German which use * N.B. use non-space for MMM to allow for languages such as German which use
* diacritics (e.g. umlaut) in some abbreviations. * diacritics (e.g. umlaut) in some abbreviations.
*/ */
+ "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+" + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+"
/* /*
year (for non-recent standard format) - yyyy year (for non-recent standard format) - yyyy
or time (for numeric or recent standard format) [h]h:mm or time (for numeric or recent standard format) [h]h:mm
*/ */
+ "(\\d+(?::\\d+)?)\\s+" + "(\\d+(?::\\d+)?)\\s+"
+ "(\\S*)(\\s*.*)"; // the rest + "(\\S*)(\\s*.*)"; // the rest
/**
/** * The default constructor for a UnixFTPEntryParser object.
* The default constructor for a UnixFTPEntryParser object. *
* * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* @throws IllegalArgumentException * be seen
* Thrown if the regular expression is unparseable. Should not be seen * under normal conditions. It it is seen, this is a sign that
* under normal conditions. It it is seen, this is a sign that * <code>REGEX</code> is not a valid regular expression.
* <code>REGEX</code> is not a valid regular expression. */
*/ public MacOsPeterFTPEntryParser() {
public MacOsPeterFTPEntryParser() this(null);
{ }
this(null);
} /**
* This constructor allows the creation of a UnixFTPEntryParser object with
/** * something other than the default configuration.
* This constructor allows the creation of a UnixFTPEntryParser object with *
* something other than the default configuration. * @param config The {@link FTPClientConfig configuration} object used to
* * configure this parser.
* @param config The {@link FTPClientConfig configuration} object used to * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* configure this parser. * be seen
* @throws IllegalArgumentException * under normal conditions. It it is seen, this is a sign that
* Thrown if the regular expression is unparseable. Should not be seen * <code>REGEX</code> is not a valid regular expression.
* under normal conditions. It it is seen, this is a sign that * @since 1.4
* <code>REGEX</code> is not a valid regular expression. */
* @since 1.4 public MacOsPeterFTPEntryParser(FTPClientConfig config) {
*/ super(REGEX);
public MacOsPeterFTPEntryParser(FTPClientConfig config) configure(config);
{ }
super(REGEX);
configure(config); /**
} * Parses a line of a unix (standard) FTP server file listing and converts
* it into a usable format in the form of an <code> FTPFile </code>
/** * instance. If the file listing line doesn't describe a file,
* Parses a line of a unix (standard) FTP server file listing and converts * <code> null </code> is returned, otherwise a <code> FTPFile </code>
* it into a usable format in the form of an <code> FTPFile </code> * instance representing the files in the directory is returned.
* instance. If the file listing line doesn't describe a file, *
* <code> null </code> is returned, otherwise a <code> FTPFile </code> * @param entry A line of text from the file listing
* instance representing the files in the directory is returned. * @return An FTPFile instance corresponding to the supplied entry
* */
* @param entry A line of text from the file listing @Override public FTPFile parseFTPEntry(String entry) {
* @return An FTPFile instance corresponding to the supplied entry FTPFile file = new FTPFile();
*/ file.setRawListing(entry);
@Override int type;
public FTPFile parseFTPEntry(String entry) { boolean isDevice = false;
FTPFile file = new FTPFile();
file.setRawListing(entry); if (matches(entry)) {
int type; String typeStr = group(1);
boolean isDevice = false; String hardLinkCount = "0";
String usr = null;
if (matches(entry)) String grp = null;
{ String filesize = group(20);
String typeStr = group(1); String datestr = group(21) + " " + group(22);
String hardLinkCount = "0"; String name = group(23);
String usr = null; String endtoken = group(24);
String grp = null;
String filesize = group(20); try {
String datestr = group(21) + " " + group(22); file.setTimestamp(super.parseTimestamp(datestr));
String name = group(23); } catch (ParseException e) {
String endtoken = group(24); // intentionally do nothing
}
try
{ // A 'whiteout' file is an ARTIFICIAL entry in any of several types of
file.setTimestamp(super.parseTimestamp(datestr)); // 'translucent' filesystems, of which a 'union' filesystem is one.
}
catch (ParseException e) // bcdelfmpSs-
{ switch (typeStr.charAt(0)) {
// intentionally do nothing case 'd':
} type = FTPFile.DIRECTORY_TYPE;
break;
// A 'whiteout' file is an ARTIFICIAL entry in any of several types of case 'e': // NET-39 => z/OS external link
// 'translucent' filesystems, of which a 'union' filesystem is one. type = FTPFile.SYMBOLIC_LINK_TYPE;
break;
// bcdelfmpSs- case 'l':
switch (typeStr.charAt(0)) type = FTPFile.SYMBOLIC_LINK_TYPE;
{ break;
case 'd': case 'b':
type = FTPFile.DIRECTORY_TYPE; case 'c':
break; isDevice = true;
case 'e': // NET-39 => z/OS external link type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
type = FTPFile.SYMBOLIC_LINK_TYPE; break;
break; case 'f':
case 'l': case '-':
type = FTPFile.SYMBOLIC_LINK_TYPE; type = FTPFile.FILE_TYPE;
break; break;
case 'b': default: // e.g. ? and w = whiteout
case 'c': type = FTPFile.UNKNOWN_TYPE;
isDevice = true; }
type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
break; file.setType(type);
case 'f':
case '-': int g = 4;
type = FTPFile.FILE_TYPE; for (int access = 0; access < 3; access++, g += 4) {
break; // Use != '-' to avoid having to check for suid and sticky bits
default: // e.g. ? and w = whiteout file.setPermission(access, FTPFile.READ_PERMISSION, (!group(g).equals("-")));
type = FTPFile.UNKNOWN_TYPE; file.setPermission(access, FTPFile.WRITE_PERMISSION, (!group(g + 1).equals("-")));
}
String execPerm = group(g + 2);
file.setType(type); if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))) {
file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
int g = 4; } else {
for (int access = 0; access < 3; access++, g += 4) file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
{
// Use != '-' to avoid having to check for suid and sticky bits
file.setPermission(access, FTPFile.READ_PERMISSION,
(!group(g).equals("-")));
file.setPermission(access, FTPFile.WRITE_PERMISSION,
(!group(g + 1).equals("-")));
String execPerm = group(g + 2);
if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)))
{
file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
}
else
{
file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
}
}
if (!isDevice)
{
try
{
file.setHardLinkCount(Integer.parseInt(hardLinkCount));
}
catch (NumberFormatException e)
{
// intentionally do nothing
}
}
file.setUser(usr);
file.setGroup(grp);
try
{
file.setSize(Long.parseLong(filesize));
}
catch (NumberFormatException e)
{
// intentionally do nothing
}
if (null == endtoken)
{
file.setName(name);
}
else
{
// oddball cases like symbolic links, file names
// with spaces in them.
name += endtoken;
if (type == FTPFile.SYMBOLIC_LINK_TYPE)
{
int end = name.indexOf(" -> ");
// Give up if no link indicator is present
if (end == -1)
{
file.setName(name);
}
else
{
file.setName(name.substring(0, end));
file.setLink(name.substring(end + 4));
}
}
else
{
file.setName(name);
}
}
return file;
} }
return null; }
}
/** if (!isDevice) {
* Defines a default configuration to be used when this class is try {
* instantiated without a {@link FTPClientConfig FTPClientConfig} file.setHardLinkCount(Integer.parseInt(hardLinkCount));
* parameter being specified. } catch (NumberFormatException e) {
* @return the default configuration for this parser. // intentionally do nothing
*/ }
@Override }
protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig( file.setUser(usr);
FTPClientConfig.SYST_UNIX, file.setGroup(grp);
DEFAULT_DATE_FORMAT,
DEFAULT_RECENT_DATE_FORMAT); try {
file.setSize(Long.parseLong(filesize));
} catch (NumberFormatException e) {
// intentionally do nothing
}
if (null == endtoken) {
file.setName(name);
} else {
// oddball cases like symbolic links, file names
// with spaces in them.
name += endtoken;
if (type == FTPFile.SYMBOLIC_LINK_TYPE) {
int end = name.indexOf(" -> ");
// Give up if no link indicator is present
if (end == -1) {
file.setName(name);
} else {
file.setName(name.substring(0, end));
file.setLink(name.substring(end + 4));
}
} else {
file.setName(name);
}
}
return file;
} }
return null;
}
/**
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified.
*
* @return the default configuration for this parser.
*/
@Override protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT,
DEFAULT_RECENT_DATE_FORMAT);
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.text.ParseException; import java.text.ParseException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -29,139 +30,113 @@ import org.apache.commons.net.ftp.FTPFile;
* @version $Id: NTFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @version $Id: NTFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
*/ */
public class NTFTPEntryParser extends ConfigurableFTPFileEntryParserImpl public class NTFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
{
private static final String DEFAULT_DATE_FORMAT = "MM-dd-yy hh:mma"; //11-09-01 12:30PM
private static final String DEFAULT_DATE_FORMAT
= "MM-dd-yy hh:mma"; //11-09-01 12:30PM private static final String DEFAULT_DATE_FORMAT2 = "MM-dd-yy kk:mm"; //11-09-01 18:30
private static final String DEFAULT_DATE_FORMAT2 private final FTPTimestampParser timestampParser;
= "MM-dd-yy kk:mm"; //11-09-01 18:30
/**
private final FTPTimestampParser timestampParser; * this is the regular expression used by this parser.
*/
/** private static final String REGEX =
* this is the regular expression used by this parser. "(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm; swallow trailing spaces
*/ + "(?:(<DIR>)|([0-9]+))\\s+" // <DIR> or ddddd; swallow trailing spaces
private static final String REGEX = + "(\\S.*)"; // First non-space followed by rest of line (name)
"(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm; swallow trailing spaces
+ "(?:(<DIR>)|([0-9]+))\\s+" // <DIR> or ddddd; swallow trailing spaces /**
+ "(\\S.*)"; // First non-space followed by rest of line (name) * The sole constructor for an NTFTPEntryParser object.
*
/** * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* The sole constructor for an NTFTPEntryParser object. * be seen
* * under normal conditions. It it is seen, this is a sign that
* @throws IllegalArgumentException * <code>REGEX</code> is not a valid regular expression.
* Thrown if the regular expression is unparseable. Should not be seen */
* under normal conditions. It it is seen, this is a sign that public NTFTPEntryParser() {
* <code>REGEX</code> is not a valid regular expression. this(null);
*/ }
public NTFTPEntryParser()
{ /**
this(null); * This constructor allows the creation of an NTFTPEntryParser object
} * with something other than the default configuration.
*
/** * @param config The {@link FTPClientConfig configuration} object used to
* This constructor allows the creation of an NTFTPEntryParser object * configure this parser.
* with something other than the default configuration. * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* * be seen
* @param config The {@link FTPClientConfig configuration} object used to * under normal conditions. It it is seen, this is a sign that
* configure this parser. * <code>REGEX</code> is not a valid regular expression.
* @throws IllegalArgumentException * @since 1.4
* Thrown if the regular expression is unparseable. Should not be seen */
* under normal conditions. It it is seen, this is a sign that public NTFTPEntryParser(FTPClientConfig config) {
* <code>REGEX</code> is not a valid regular expression. super(REGEX, Pattern.DOTALL);
* @since 1.4 configure(config);
*/ FTPClientConfig config2 =
public NTFTPEntryParser(FTPClientConfig config) new FTPClientConfig(FTPClientConfig.SYST_NT, DEFAULT_DATE_FORMAT2, null);
{ config2.setDefaultDateFormatStr(DEFAULT_DATE_FORMAT2);
super(REGEX, Pattern.DOTALL); this.timestampParser = new FTPTimestampParserImpl();
configure(config); ((Configurable) this.timestampParser).configure(config2);
FTPClientConfig config2 = new FTPClientConfig( }
FTPClientConfig.SYST_NT,
DEFAULT_DATE_FORMAT2, /**
null); * Parses a line of an NT FTP server file listing and converts it into a
config2.setDefaultDateFormatStr(DEFAULT_DATE_FORMAT2); * usable format in the form of an <code> FTPFile </code> instance. If the
this.timestampParser = new FTPTimestampParserImpl(); * file listing line doesn't describe a file, <code> null </code> is
((Configurable)this.timestampParser).configure(config2); * returned, otherwise a <code> FTPFile </code> instance representing the
} * files in the directory is returned.
*
/** * @param entry A line of text from the file listing
* Parses a line of an NT FTP server file listing and converts it into a * @return An FTPFile instance corresponding to the supplied entry
* usable format in the form of an <code> FTPFile </code> instance. If the */
* file listing line doesn't describe a file, <code> null </code> is @Override public FTPFile parseFTPEntry(String entry) {
* returned, otherwise a <code> FTPFile </code> instance representing the FTPFile f = new FTPFile();
* files in the directory is returned. f.setRawListing(entry);
*
* @param entry A line of text from the file listing if (matches(entry)) {
* @return An FTPFile instance corresponding to the supplied entry String datestr = group(1) + " " + group(2);
*/ String dirString = group(3);
@Override String size = group(4);
public FTPFile parseFTPEntry(String entry) String name = group(5);
{ try {
FTPFile f = new FTPFile(); f.setTimestamp(super.parseTimestamp(datestr));
f.setRawListing(entry); } catch (ParseException e) {
// parsing fails, try the other date format
if (matches(entry)) try {
{ f.setTimestamp(timestampParser.parseTimestamp(datestr));
String datestr = group(1)+" "+group(2); } catch (ParseException e2) {
String dirString = group(3); // intentionally do nothing
String size = group(4);
String name = group(5);
try
{
f.setTimestamp(super.parseTimestamp(datestr));
}
catch (ParseException e)
{
// parsing fails, try the other date format
try
{
f.setTimestamp(timestampParser.parseTimestamp(datestr));
}
catch (ParseException e2)
{
// intentionally do nothing
}
}
if (null == name || name.equals(".") || name.equals(".."))
{
return (null);
}
f.setName(name);
if ("<DIR>".equals(dirString))
{
f.setType(FTPFile.DIRECTORY_TYPE);
f.setSize(0);
}
else
{
f.setType(FTPFile.FILE_TYPE);
if (null != size)
{
f.setSize(Long.parseLong(size));
}
}
return (f);
} }
return null; }
}
if (null == name || name.equals(".") || name.equals("..")) {
/** return (null);
* Defines a default configuration to be used when this class is }
* instantiated without a {@link FTPClientConfig FTPClientConfig} f.setName(name);
* parameter being specified.
* @return the default configuration for this parser. if ("<DIR>".equals(dirString)) {
*/ f.setType(FTPFile.DIRECTORY_TYPE);
@Override f.setSize(0);
public FTPClientConfig getDefaultConfiguration() { } else {
return new FTPClientConfig( f.setType(FTPFile.FILE_TYPE);
FTPClientConfig.SYST_NT, if (null != size) {
DEFAULT_DATE_FORMAT, f.setSize(Long.parseLong(size));
null); }
}
return (f);
} }
return null;
}
/**
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified.
*
* @return the default configuration for this parser.
*/
@Override public FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(FTPClientConfig.SYST_NT, DEFAULT_DATE_FORMAT, null);
}
} }

@ -23,155 +23,153 @@ import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFile;
/** /**
* Implementation of FTPFileEntryParser and FTPFileListParser for Netware Systems. Note that some of the proprietary * Implementation of FTPFileEntryParser and FTPFileListParser for Netware Systems. Note that some of
* the proprietary
* extensions for Novell-specific operations are not supported. See * extensions for Novell-specific operations are not supported. See
* <a href="http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html"> * <a href="http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html">
* http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html</a> * http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html</a>
* for more details. * for more details.
* *
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
* @version $Id: NetwareFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @version $Id: NetwareFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
* @since 1.5 * @since 1.5
*/ */
public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
/** /**
* Default date format is e.g. Feb 22 2006 * Default date format is e.g. Feb 22 2006
*/ */
private static final String DEFAULT_DATE_FORMAT = "MMM dd yyyy"; private static final String DEFAULT_DATE_FORMAT = "MMM dd yyyy";
/** /**
* Default recent date format is e.g. Feb 22 17:32 * Default recent date format is e.g. Feb 22 17:32
*/ */
private static final String DEFAULT_RECENT_DATE_FORMAT = "MMM dd HH:mm"; private static final String DEFAULT_RECENT_DATE_FORMAT = "MMM dd HH:mm";
/** /**
* this is the regular expression used by this parser. * this is the regular expression used by this parser.
* Example: d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2 * Example: d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2
*/ */
private static final String REGEX = "(d|-){1}\\s+" // Directory/file flag private static final String REGEX = "(d|-){1}\\s+" // Directory/file flag
+ "\\[([-A-Z]+)\\]\\s+" // Attributes RWCEAFMS or - + "\\[([-A-Z]+)\\]\\s+" // Attributes RWCEAFMS or -
+ "(\\S+)\\s+" + "(\\d+)\\s+" // Owner and size + "(\\S+)\\s+" + "(\\d+)\\s+" // Owner and size
+ "(\\S+\\s+\\S+\\s+((\\d+:\\d+)|(\\d{4})))" // Long/short date format + "(\\S+\\s+\\S+\\s+((\\d+:\\d+)|(\\d{4})))" // Long/short date format
+ "\\s+(.*)"; // Filename (incl. spaces) + "\\s+(.*)"; // Filename (incl. spaces)
/** /**
* The default constructor for a NetwareFTPEntryParser object. * The default constructor for a NetwareFTPEntryParser object.
* *
* @throws IllegalArgumentException * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* Thrown if the regular expression is unparseable. Should not be seen * be seen
* under normal conditions. It it is seen, this is a sign that * under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression. * <code>REGEX</code> is not a valid regular expression.
*/ */
public NetwareFTPEntryParser() { public NetwareFTPEntryParser() {
this(null); this(null);
} }
/** /**
* This constructor allows the creation of an NetwareFTPEntryParser object * This constructor allows the creation of an NetwareFTPEntryParser object
* with something other than the default configuration. * with something other than the default configuration.
* *
* @param config The {@link FTPClientConfig configuration} object used to * @param config The {@link FTPClientConfig configuration} object used to
* configure this parser. * configure this parser.
* @throws IllegalArgumentException * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* Thrown if the regular expression is unparseable. Should not be seen * be seen
* under normal conditions. It it is seen, this is a sign that * under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression. * <code>REGEX</code> is not a valid regular expression.
* @since 1.4 * @since 1.4
*/ */
public NetwareFTPEntryParser(FTPClientConfig config) { public NetwareFTPEntryParser(FTPClientConfig config) {
super(REGEX); super(REGEX);
configure(config); configure(config);
} }
/** /**
* Parses a line of an NetwareFTP server file listing and converts it into a * Parses a line of an NetwareFTP server file listing and converts it into a
* usable format in the form of an <code> FTPFile </code> instance. If the * usable format in the form of an <code> FTPFile </code> instance. If the
* file listing line doesn't describe a file, <code> null </code> is * file listing line doesn't describe a file, <code> null </code> is
* returned, otherwise a <code> FTPFile </code> instance representing the * returned, otherwise a <code> FTPFile </code> instance representing the
* files in the directory is returned. * files in the directory is returned.
* <p> * <p>
* Netware file permissions are in the following format: RWCEAFMS, and are explained as follows: * Netware file permissions are in the following format: RWCEAFMS, and are explained as follows:
* <ul> * <ul>
* <li><b>S</b> - Supervisor; All rights. * <li><b>S</b> - Supervisor; All rights.
* <li><b>R</b> - Read; Right to open and read or execute. * <li><b>R</b> - Read; Right to open and read or execute.
* <li><b>W</b> - Write; Right to open and modify. * <li><b>W</b> - Write; Right to open and modify.
* <li><b>C</b> - Create; Right to create; when assigned to a file, allows a deleted file to be recovered. * <li><b>C</b> - Create; Right to create; when assigned to a file, allows a deleted file to be
* <li><b>E</b> - Erase; Right to delete. * recovered.
* <li><b>M</b> - Modify; Right to rename a file and to change attributes. * <li><b>E</b> - Erase; Right to delete.
* <li><b>F</b> - File Scan; Right to see directory or file listings. * <li><b>M</b> - Modify; Right to rename a file and to change attributes.
* <li><b>A</b> - Access Control; Right to modify trustee assignments and the Inherited Rights Mask. * <li><b>F</b> - File Scan; Right to see directory or file listings.
* </ul> * <li><b>A</b> - Access Control; Right to modify trustee assignments and the Inherited Rights
* * Mask.
* See * </ul>
* <a href="http://www.novell.com/documentation/nfap10/index.html?page=/documentation/nfap10/nfaubook/data/abxraws.html"> *
* here</a> * See
* for more details * <a href="http://www.novell.com/documentation/nfap10/index.html?page=/documentation/nfap10/nfaubook/data/abxraws.html">
* * here</a>
* @param entry A line of text from the file listing * for more details
* @return An FTPFile instance corresponding to the supplied entry *
*/ * @param entry A line of text from the file listing
@Override * @return An FTPFile instance corresponding to the supplied entry
public FTPFile parseFTPEntry(String entry) { */
@Override public FTPFile parseFTPEntry(String entry) {
FTPFile f = new FTPFile();
if (matches(entry)) { FTPFile f = new FTPFile();
String dirString = group(1); if (matches(entry)) {
String attrib = group(2); String dirString = group(1);
String user = group(3); String attrib = group(2);
String size = group(4); String user = group(3);
String datestr = group(5); String size = group(4);
String name = group(9); String datestr = group(5);
String name = group(9);
try {
f.setTimestamp(super.parseTimestamp(datestr)); try {
} catch (ParseException e) { f.setTimestamp(super.parseTimestamp(datestr));
// intentionally do nothing } catch (ParseException e) {
} // intentionally do nothing
}
//is it a DIR or a file
if (dirString.trim().equals("d")) { //is it a DIR or a file
f.setType(FTPFile.DIRECTORY_TYPE); if (dirString.trim().equals("d")) {
} else // Should be "-" f.setType(FTPFile.DIRECTORY_TYPE);
{ } else // Should be "-"
f.setType(FTPFile.FILE_TYPE); {
} f.setType(FTPFile.FILE_TYPE);
}
f.setUser(user);
f.setUser(user);
//set the name
f.setName(name.trim()); //set the name
f.setName(name.trim());
//set the size
f.setSize(Long.parseLong(size.trim())); //set the size
f.setSize(Long.parseLong(size.trim()));
// Now set the permissions (or at least a subset thereof - full permissions would probably require
// subclassing FTPFile and adding extra metainformation there) // Now set the permissions (or at least a subset thereof - full permissions would probably require
if (attrib.indexOf("R") != -1) { // subclassing FTPFile and adding extra metainformation there)
f.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, if (attrib.indexOf("R") != -1) {
true); f.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true);
} }
if (attrib.indexOf("W") != -1) { if (attrib.indexOf("W") != -1) {
f.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, f.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true);
true); }
}
return (f);
return (f);
}
return null;
} }
return null;
/** }
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig} /**
* parameter being specified. * Defines a default configuration to be used when this class is
* @return the default configuration for this parser. * instantiated without a {@link FTPClientConfig FTPClientConfig}
*/ * parameter being specified.
@Override *
protected FTPClientConfig getDefaultConfiguration() { * @return the default configuration for this parser.
return new FTPClientConfig(FTPClientConfig.SYST_NETWARE, */
DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT); @Override protected FTPClientConfig getDefaultConfiguration() {
} return new FTPClientConfig(FTPClientConfig.SYST_NETWARE, DEFAULT_DATE_FORMAT,
DEFAULT_RECENT_DATE_FORMAT);
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.text.ParseException; import java.text.ParseException;
import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPClientConfig;
@ -31,116 +32,96 @@ public class OS2FTPEntryParser extends ConfigurableFTPFileEntryParserImpl
{ {
private static final String DEFAULT_DATE_FORMAT private static final String DEFAULT_DATE_FORMAT = "MM-dd-yy HH:mm"; //11-09-01 12:30
= "MM-dd-yy HH:mm"; //11-09-01 12:30 /**
/** * this is the regular expression used by this parser.
* this is the regular expression used by this parser. */
*/ private static final String REGEX = "\\s*([0-9]+)\\s*"
private static final String REGEX = + "(\\s+|[A-Z]+)\\s*"
"\\s*([0-9]+)\\s*" + "(DIR|\\s+)\\s*"
+ "(\\s+|[A-Z]+)\\s*" + "(\\S+)\\s+(\\S+)\\s+" /* date stuff */
+ "(DIR|\\s+)\\s*" + "(\\S.*)";
+ "(\\S+)\\s+(\\S+)\\s+" /* date stuff */
+ "(\\S.*)"; /**
* The default constructor for a OS2FTPEntryParser object.
/** *
* The default constructor for a OS2FTPEntryParser object. * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* * be seen
* @throws IllegalArgumentException * under normal conditions. It it is seen, this is a sign that
* Thrown if the regular expression is unparseable. Should not be seen * <code>REGEX</code> is not a valid regular expression.
* under normal conditions. It it is seen, this is a sign that */
* <code>REGEX</code> is not a valid regular expression. public OS2FTPEntryParser() {
*/ this(null);
public OS2FTPEntryParser() }
{
this(null); /**
* This constructor allows the creation of an OS2FTPEntryParser object
* with something other than the default configuration.
*
* @param config The {@link FTPClientConfig configuration} object used to
* configure this parser.
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
* @since 1.4
*/
public OS2FTPEntryParser(FTPClientConfig config) {
super(REGEX);
configure(config);
}
/**
* Parses a line of an OS2 FTP server file listing and converts it into a
* usable format in the form of an <code> FTPFile </code> instance. If the
* file listing line doesn't describe a file, <code> null </code> is
* returned, otherwise a <code> FTPFile </code> instance representing the
* files in the directory is returned.
*
* @param entry A line of text from the file listing
* @return An FTPFile instance corresponding to the supplied entry
*/
@Override public FTPFile parseFTPEntry(String entry) {
FTPFile f = new FTPFile();
if (matches(entry)) {
String size = group(1);
String attrib = group(2);
String dirString = group(3);
String datestr = group(4) + " " + group(5);
String name = group(6);
try {
f.setTimestamp(super.parseTimestamp(datestr));
} catch (ParseException e) {
// intentionally do nothing
}
//is it a DIR or a file
if (dirString.trim().equals("DIR") || attrib.trim().equals("DIR")) {
f.setType(FTPFile.DIRECTORY_TYPE);
} else {
f.setType(FTPFile.FILE_TYPE);
}
//set the name
f.setName(name.trim());
//set the size
f.setSize(Long.parseLong(size.trim()));
return (f);
} }
return null;
/** }
* This constructor allows the creation of an OS2FTPEntryParser object
* with something other than the default configuration. /**
* * Defines a default configuration to be used when this class is
* @param config The {@link FTPClientConfig configuration} object used to * instantiated without a {@link FTPClientConfig FTPClientConfig}
* configure this parser. * parameter being specified.
* @throws IllegalArgumentException *
* Thrown if the regular expression is unparseable. Should not be seen * @return the default configuration for this parser.
* under normal conditions. It it is seen, this is a sign that */
* <code>REGEX</code> is not a valid regular expression. @Override protected FTPClientConfig getDefaultConfiguration() {
* @since 1.4 return new FTPClientConfig(FTPClientConfig.SYST_OS2, DEFAULT_DATE_FORMAT, null);
*/ }
public OS2FTPEntryParser(FTPClientConfig config)
{
super(REGEX);
configure(config);
}
/**
* Parses a line of an OS2 FTP server file listing and converts it into a
* usable format in the form of an <code> FTPFile </code> instance. If the
* file listing line doesn't describe a file, <code> null </code> is
* returned, otherwise a <code> FTPFile </code> instance representing the
* files in the directory is returned.
*
* @param entry A line of text from the file listing
* @return An FTPFile instance corresponding to the supplied entry
*/
@Override
public FTPFile parseFTPEntry(String entry)
{
FTPFile f = new FTPFile();
if (matches(entry))
{
String size = group(1);
String attrib = group(2);
String dirString = group(3);
String datestr = group(4)+" "+group(5);
String name = group(6);
try
{
f.setTimestamp(super.parseTimestamp(datestr));
}
catch (ParseException e)
{
// intentionally do nothing
}
//is it a DIR or a file
if (dirString.trim().equals("DIR") || attrib.trim().equals("DIR"))
{
f.setType(FTPFile.DIRECTORY_TYPE);
}
else
{
f.setType(FTPFile.FILE_TYPE);
}
//set the name
f.setName(name.trim());
//set the size
f.setSize(Long.parseLong(size.trim()));
return (f);
}
return null;
}
/**
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified.
* @return the default configuration for this parser.
*/
@Override
protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(
FTPClientConfig.SYST_OS2,
DEFAULT_DATE_FORMAT,
null);
}
} }

@ -25,411 +25,376 @@ import org.apache.commons.net.ftp.FTPFile;
/** /**
* @version $Id: OS400FTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @version $Id: OS400FTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $
* <pre> * <pre>
* Example *FILE/*MEM FTP entries, when the current * Example *FILE/*MEM FTP entries, when the current
* working directory is a file of file system QSYS: * working directory is a file of file system QSYS:
* ------------------------------------------------ * ------------------------------------------------
* *
* $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file * $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file
* 250-NAMEFMT set to 1. * 250-NAMEFMT set to 1.
* 250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory. * 250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory.
* $ dir * $ dir
* 227 Entering Passive Mode (10,200,36,33,40,249). * 227 Entering Passive Mode (10,200,36,33,40,249).
* 125 List started. * 125 List started.
* QPGMR 135168 22.06.13 13:18:19 *FILE * QPGMR 135168 22.06.13 13:18:19 *FILE
* QPGMR *MEM MKCMD.MBR * QPGMR *MEM MKCMD.MBR
* QPGMR *MEM RUCALLTST.MBR * QPGMR *MEM RUCALLTST.MBR
* QPGMR *MEM RUCMDHLP.MBR * QPGMR *MEM RUCMDHLP.MBR
* QPGMR *MEM RUCRTTST.MBR * QPGMR *MEM RUCRTTST.MBR
* 250 List completed. * 250 List completed.
* *
* *
* Example *FILE entry of an OS/400 save file: * Example *FILE entry of an OS/400 save file:
* --------------------------------------------------- * ---------------------------------------------------
* *
* $ cwd /qsys.lib/rpgunit.lib * $ cwd /qsys.lib/rpgunit.lib
* 250 "/QSYS.LIB/RPGUNIT.LIB" is current library. * 250 "/QSYS.LIB/RPGUNIT.LIB" is current library.
* $ dir rpgunit.file * $ dir rpgunit.file
* 227 Entering Passive Mode (10,200,36,33,188,106). * 227 Entering Passive Mode (10,200,36,33,188,106).
* 125 List started. * 125 List started.
* QPGMR 16347136 29.06.13 15:45:09 *FILE RPGUNIT.SAVF * QPGMR 16347136 29.06.13 15:45:09 *FILE RPGUNIT.SAVF
* 250 List completed. * 250 List completed.
* *
* *
* Example *STMF/*DIR FTP entries, when the * Example *STMF/*DIR FTP entries, when the
* current working directory is in file system "root": * current working directory is in file system "root":
* --------------------------------------------------- * ---------------------------------------------------
* *
* $ cwd /home/raddatz * $ cwd /home/raddatz
* 250 "/home/raddatz" is current directory. * 250 "/home/raddatz" is current directory.
* $ dir test* * $ dir test*
* 227 Entering Passive Mode (10,200,36,33,200,189). * 227 Entering Passive Mode (10,200,36,33,200,189).
* 125 List started. * 125 List started.
* RADDATZ 200 21.05.11 12:31:18 *STMF TEST_RG_02_CRLF.properties * RADDATZ 200 21.05.11 12:31:18 *STMF TEST_RG_02_CRLF.properties
* RADDATZ 187 08.05.11 12:31:40 *STMF TEST_RG_02_LF.properties * RADDATZ 187 08.05.11 12:31:40 *STMF TEST_RG_02_LF.properties
* RADDATZ 187 08.05.11 12:31:52 *STMF TEST_RG_02_CR.properties * RADDATZ 187 08.05.11 12:31:52 *STMF TEST_RG_02_CR.properties
* RADDATZ 8192 04.07.13 09:04:14 *DIR testDir1/ * RADDATZ 8192 04.07.13 09:04:14 *DIR testDir1/
* RADDATZ 8192 04.07.13 09:04:17 *DIR testDir2/ * RADDATZ 8192 04.07.13 09:04:17 *DIR testDir2/
* 250 List completed. * 250 List completed.
* *
* *
* Example 1, using ANT to list specific members of a file: * Example 1, using ANT to list specific members of a file:
* -------------------------------------------------------- * --------------------------------------------------------
* *
* &lt;echo/&gt; * &lt;echo/&gt;
* &lt;echo&gt;Listing members of a file:&lt;/echo&gt; * &lt;echo&gt;Listing members of a file:&lt;/echo&gt;
* *
* &lt;ftp action="list" * &lt;ftp action="list"
* server="${ftp.server}" * server="${ftp.server}"
* userid="${ftp.user}" * userid="${ftp.user}"
* password="${ftp.password}" * password="${ftp.password}"
* binary="false" * binary="false"
* verbose="true" * verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE" * remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
* systemTypeKey="OS/400" * systemTypeKey="OS/400"
* listing="ftp-listing.txt" * listing="ftp-listing.txt"
* &gt; * &gt;
* &lt;fileset dir="./i5-downloads-file" casesensitive="false"&gt; * &lt;fileset dir="./i5-downloads-file" casesensitive="false"&gt;
* &lt;include name="run*.mbr" /&gt; * &lt;include name="run*.mbr" /&gt;
* &lt;/fileset&gt; * &lt;/fileset&gt;
* &lt;/ftp&gt; * &lt;/ftp&gt;
* *
* Output: * Output:
* ------- * -------
* *
* [echo] Listing members of a file: * [echo] Listing members of a file:
* [ftp] listing files * [ftp] listing files
* [ftp] listing RUN.MBR * [ftp] listing RUN.MBR
* [ftp] listing RUNNER.MBR * [ftp] listing RUNNER.MBR
* [ftp] listing RUNNERBND.MBR * [ftp] listing RUNNERBND.MBR
* [ftp] 3 files listed * [ftp] 3 files listed
* *
* *
* Example 2, using ANT to list specific members of all files of a library: * Example 2, using ANT to list specific members of all files of a library:
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* *
* &lt;echo/&gt; * &lt;echo/&gt;
* &lt;echo&gt;Listing members of all files of a library:&lt;/echo&gt; * &lt;echo&gt;Listing members of all files of a library:&lt;/echo&gt;
* *
* &lt;ftp action="list" * &lt;ftp action="list"
* server="${ftp.server}" * server="${ftp.server}"
* userid="${ftp.user}" * userid="${ftp.user}"
* password="${ftp.password}" * password="${ftp.password}"
* binary="false" * binary="false"
* verbose="true" * verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB" * remotedir="/QSYS.LIB/RPGUNIT.LIB"
* systemTypeKey="OS/400" * systemTypeKey="OS/400"
* listing="ftp-listing.txt" * listing="ftp-listing.txt"
* &gt; * &gt;
* &lt;fileset dir="./i5-downloads-lib" casesensitive="false"&gt; * &lt;fileset dir="./i5-downloads-lib" casesensitive="false"&gt;
* &lt;include name="**\run*.mbr" /&gt; * &lt;include name="**\run*.mbr" /&gt;
* &lt;/fileset&gt; * &lt;/fileset&gt;
* &lt;/ftp&gt; * &lt;/ftp&gt;
* *
* Output: * Output:
* ------- * -------
* *
* [echo] Listing members of all files of a library: * [echo] Listing members of all files of a library:
* [ftp] listing files * [ftp] listing files
* [ftp] listing RPGUNIT1.FILE\RUN.MBR * [ftp] listing RPGUNIT1.FILE\RUN.MBR
* [ftp] listing RPGUNIT1.FILE\RUNRMT.MBR * [ftp] listing RPGUNIT1.FILE\RUNRMT.MBR
* [ftp] listing RPGUNITT1.FILE\RUNT.MBR * [ftp] listing RPGUNITT1.FILE\RUNT.MBR
* [ftp] listing RPGUNITY1.FILE\RUN.MBR * [ftp] listing RPGUNITY1.FILE\RUN.MBR
* [ftp] listing RPGUNITY1.FILE\RUNNER.MBR * [ftp] listing RPGUNITY1.FILE\RUNNER.MBR
* [ftp] listing RPGUNITY1.FILE\RUNNERBND.MBR * [ftp] listing RPGUNITY1.FILE\RUNNERBND.MBR
* [ftp] 6 files listed * [ftp] 6 files listed
* *
* *
* Example 3, using ANT to download specific members of a file: * Example 3, using ANT to download specific members of a file:
* ------------------------------------------------------------ * ------------------------------------------------------------
* *
* &lt;echo/&gt; * &lt;echo/&gt;
* &lt;echo&gt;Downloading members of a file:&lt;/echo&gt; * &lt;echo&gt;Downloading members of a file:&lt;/echo&gt;
* *
* &lt;ftp action="get" * &lt;ftp action="get"
* server="${ftp.server}" * server="${ftp.server}"
* userid="${ftp.user}" * userid="${ftp.user}"
* password="${ftp.password}" * password="${ftp.password}"
* binary="false" * binary="false"
* verbose="true" * verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE" * remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
* systemTypeKey="OS/400" * systemTypeKey="OS/400"
* &gt; * &gt;
* &lt;fileset dir="./i5-downloads-file" casesensitive="false"&gt; * &lt;fileset dir="./i5-downloads-file" casesensitive="false"&gt;
* &lt;include name="run*.mbr" /&gt; * &lt;include name="run*.mbr" /&gt;
* &lt;/fileset&gt; * &lt;/fileset&gt;
* &lt;/ftp&gt; * &lt;/ftp&gt;
* *
* Output: * Output:
* ------- * -------
* *
* [echo] Downloading members of a file: * [echo] Downloading members of a file:
* [ftp] getting files * [ftp] getting files
* [ftp] transferring RUN.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUN.MBR * [ftp] transferring RUN.MBR to C:\workspaces\rdp_080\workspace\ANT -
* [ftp] transferring RUNNER.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNER.MBR * FTP\i5-downloads-file\RUN.MBR
* [ftp] transferring RUNNERBND.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNERBND.MBR * [ftp] transferring RUNNER.MBR to C:\workspaces\rdp_080\workspace\ANT -
* [ftp] 3 files retrieved * FTP\i5-downloads-file\RUNNER.MBR
* * [ftp] transferring RUNNERBND.MBR to C:\workspaces\rdp_080\workspace\ANT -
* * FTP\i5-downloads-file\RUNNERBND.MBR
* Example 4, using ANT to download specific members of all files of a library: * [ftp] 3 files retrieved
* ---------------------------------------------------------------------------- *
* *
* &lt;echo/&gt; * Example 4, using ANT to download specific members of all files of a library:
* &lt;echo&gt;Downloading members of all files of a library:&lt;/echo&gt; * ----------------------------------------------------------------------------
* *
* &lt;ftp action="get" * &lt;echo/&gt;
* server="${ftp.server}" * &lt;echo&gt;Downloading members of all files of a library:&lt;/echo&gt;
* userid="${ftp.user}" *
* password="${ftp.password}" * &lt;ftp action="get"
* binary="false" * server="${ftp.server}"
* verbose="true" * userid="${ftp.user}"
* remotedir="/QSYS.LIB/RPGUNIT.LIB" * password="${ftp.password}"
* systemTypeKey="OS/400" * binary="false"
* &gt; * verbose="true"
* &lt;fileset dir="./i5-downloads-lib" casesensitive="false"&gt; * remotedir="/QSYS.LIB/RPGUNIT.LIB"
* &lt;include name="**\run*.mbr" /&gt; * systemTypeKey="OS/400"
* &lt;/fileset&gt; * &gt;
* &lt;/ftp&gt; * &lt;fileset dir="./i5-downloads-lib" casesensitive="false"&gt;
* * &lt;include name="**\run*.mbr" /&gt;
* Output: * &lt;/fileset&gt;
* ------- * &lt;/ftp&gt;
* *
* [echo] Downloading members of all files of a library: * Output:
* [ftp] getting files * -------
* [ftp] transferring RPGUNIT1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUN.MBR *
* [ftp] transferring RPGUNIT1.FILE\RUNRMT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUNRMT.MBR * [echo] Downloading members of all files of a library:
* [ftp] transferring RPGUNITT1.FILE\RUNT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITT1.FILE\RUNT.MBR * [ftp] getting files
* [ftp] transferring RPGUNITY1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUN.MBR * [ftp] transferring RPGUNIT1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT -
* [ftp] transferring RPGUNITY1.FILE\RUNNER.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNER.MBR * FTP\i5-downloads\RPGUNIT1.FILE\RUN.MBR
* [ftp] transferring RPGUNITY1.FILE\RUNNERBND.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNERBND.MBR * [ftp] transferring RPGUNIT1.FILE\RUNRMT.MBR to C:\work\rdp_080\space\ANT -
* [ftp] 6 files retrieved * FTP\i5-downloads\RPGUNIT1.FILE\RUNRMT.MBR
* * [ftp] transferring RPGUNITT1.FILE\RUNT.MBR to C:\work\rdp_080\space\ANT -
* * FTP\i5-downloads\RPGUNITT1.FILE\RUNT.MBR
* Example 5, using ANT to download a save file of a library: * [ftp] transferring RPGUNITY1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT -
* ---------------------------------------------------------- * FTP\i5-downloads\RPGUNITY1.FILE\RUN.MBR
* * [ftp] transferring RPGUNITY1.FILE\RUNNER.MBR to C:\work\rdp_080\space\ANT -
* &lt;ftp action="get" * FTP\i5-downloads\RPGUNITY1.FILE\RUNNER.MBR
* server="${ftp.server}" * [ftp] transferring RPGUNITY1.FILE\RUNNERBND.MBR to C:\work\rdp_080\space\ANT -
* userid="${ftp.user}" * FTP\i5-downloads\RPGUNITY1.FILE\RUNNERBND.MBR
* password="${ftp.password}" * [ftp] 6 files retrieved
* binary="true" *
* verbose="true" *
* remotedir="/QSYS.LIB/RPGUNIT.LIB" * Example 5, using ANT to download a save file of a library:
* systemTypeKey="OS/400" * ----------------------------------------------------------
* &gt; *
* &lt;fileset dir="./i5-downloads-savf" casesensitive="false"&gt; * &lt;ftp action="get"
* &lt;include name="RPGUNIT.SAVF" /&gt; * server="${ftp.server}"
* &lt;/fileset&gt; * userid="${ftp.user}"
* &lt;/ftp&gt; * password="${ftp.password}"
* * binary="true"
* Output: * verbose="true"
* ------- * remotedir="/QSYS.LIB/RPGUNIT.LIB"
* [echo] Downloading save file: * systemTypeKey="OS/400"
* [ftp] getting files * &gt;
* [ftp] transferring RPGUNIT.SAVF to C:\workspaces\rdp_080\workspace\net-Test\i5-downloads-lib\RPGUNIT.SAVF * &lt;fileset dir="./i5-downloads-savf" casesensitive="false"&gt;
* [ftp] 1 files retrieved * &lt;include name="RPGUNIT.SAVF" /&gt;
* * &lt;/fileset&gt;
* </pre> * &lt;/ftp&gt;
*
* Output:
* -------
* [echo] Downloading save file:
* [ftp] getting files
* [ftp] transferring RPGUNIT.SAVF to C:\workspaces\rdp_080\workspace\net-Test\i5-downloads-lib\RPGUNIT.SAVF
* [ftp] 1 files retrieved
*
* </pre>
*/ */
public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
{ private static final String DEFAULT_DATE_FORMAT = "yy/MM/dd HH:mm:ss"; //01/11/09 12:30:24
private static final String DEFAULT_DATE_FORMAT
= "yy/MM/dd HH:mm:ss"; //01/11/09 12:30:24
private static final String REGEX = "(\\S+)\\s+" // user
+ "(?:(\\d+)\\s+)?" // size, empty for members
+ "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members
+ "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM
+ "(?:(\\S+)\\s*)?"; // filename, missing, when CWD is a *FILE
/**
* The default constructor for a OS400FTPEntryParser object.
*
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
*/
public OS400FTPEntryParser() {
this(null);
}
private static final String REGEX = /**
"(\\S+)\\s+" // user * This constructor allows the creation of an OS400FTPEntryParser object
+ "(?:(\\d+)\\s+)?" // size, empty for members * with something other than the default configuration.
+ "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members *
+ "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM * @param config The {@link FTPClientConfig configuration} object used to
+ "(?:(\\S+)\\s*)?"; // filename, missing, when CWD is a *FILE * configure this parser.
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
* @since 1.4
*/
public OS400FTPEntryParser(FTPClientConfig config) {
super(REGEX);
configure(config);
}
@Override public FTPFile parseFTPEntry(String entry) {
/** FTPFile file = new FTPFile();
* The default constructor for a OS400FTPEntryParser object. file.setRawListing(entry);
* int type;
* @throws IllegalArgumentException
* Thrown if the regular expression is unparseable. Should not be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
*/
public OS400FTPEntryParser()
{
this(null);
}
/**
* This constructor allows the creation of an OS400FTPEntryParser object
* with something other than the default configuration.
*
* @param config The {@link FTPClientConfig configuration} object used to
* configure this parser.
* @throws IllegalArgumentException
* Thrown if the regular expression is unparseable. Should not be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
* @since 1.4
*/
public OS400FTPEntryParser(FTPClientConfig config)
{
super(REGEX);
configure(config);
}
@Override
public FTPFile parseFTPEntry(String entry)
{
FTPFile file = new FTPFile();
file.setRawListing(entry);
int type;
if (matches(entry))
{
String usr = group(1);
String filesize = group(2);
String datestr = "";
if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4)))
{
datestr = group(3)+" "+group(4);
}
String typeStr = group(5);
String name = group(6);
boolean mustScanForPathSeparator = true;
try
{
file.setTimestamp(super.parseTimestamp(datestr));
}
catch (ParseException e)
{
// intentionally do nothing
}
if (matches(entry)) {
String usr = group(1);
String filesize = group(2);
String datestr = "";
if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) {
datestr = group(3) + " " + group(4);
}
String typeStr = group(5);
String name = group(6);
if (typeStr.equalsIgnoreCase("*STMF")) boolean mustScanForPathSeparator = true;
{
type = FTPFile.FILE_TYPE;
if (isNullOrEmpty(filesize) || isNullOrEmpty(name))
{
return null;
}
}
else if (typeStr.equalsIgnoreCase("*DIR"))
{
type = FTPFile.DIRECTORY_TYPE;
if (isNullOrEmpty(filesize) || isNullOrEmpty(name))
{
return null;
}
}
else if (typeStr.equalsIgnoreCase("*FILE"))
{
// File, defines the structure of the data (columns of a row)
// but the data is stored in one or more members. Typically a
// source file contains multiple members whereas it is
// recommended (but not enforced) to use one member per data
// file.
// Save files are a special type of files which are used
// to save objects, e.g. for backups.
if (name != null && name.toUpperCase().endsWith(".SAVF"))
{
mustScanForPathSeparator = false;
type = FTPFile.FILE_TYPE;
}
else
{
return null;
}
}
else if (typeStr.equalsIgnoreCase("*MEM"))
{
mustScanForPathSeparator = false;
type = FTPFile.FILE_TYPE;
if (isNullOrEmpty(name)) try {
{ file.setTimestamp(super.parseTimestamp(datestr));
return null; } catch (ParseException e) {
} // intentionally do nothing
if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) }
{
return null;
}
// Quick and dirty bug fix to make SelectorUtils work. if (typeStr.equalsIgnoreCase("*STMF")) {
// Class SelectorUtils uses 'File.separator' to splitt type = FTPFile.FILE_TYPE;
// a given path into pieces. But actually it had to if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) {
// use the separator of the FTP server, which is a forward return null;
// slash in case of an AS/400. }
name = name.replace('/', File.separatorChar); } else if (typeStr.equalsIgnoreCase("*DIR")) {
} type = FTPFile.DIRECTORY_TYPE;
else if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) {
{ return null;
type = FTPFile.UNKNOWN_TYPE; }
} } else if (typeStr.equalsIgnoreCase("*FILE")) {
// File, defines the structure of the data (columns of a row)
// but the data is stored in one or more members. Typically a
// source file contains multiple members whereas it is
// recommended (but not enforced) to use one member per data
// file.
// Save files are a special type of files which are used
// to save objects, e.g. for backups.
if (name != null && name.toUpperCase().endsWith(".SAVF")) {
mustScanForPathSeparator = false;
type = FTPFile.FILE_TYPE;
} else {
return null;
}
} else if (typeStr.equalsIgnoreCase("*MEM")) {
mustScanForPathSeparator = false;
type = FTPFile.FILE_TYPE;
file.setType(type); if (isNullOrEmpty(name)) {
return null;
}
if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) {
return null;
}
file.setUser(usr); // Quick and dirty bug fix to make SelectorUtils work.
// Class SelectorUtils uses 'File.separator' to splitt
// a given path into pieces. But actually it had to
// use the separator of the FTP server, which is a forward
// slash in case of an AS/400.
name = name.replace('/', File.separatorChar);
} else {
type = FTPFile.UNKNOWN_TYPE;
}
try file.setType(type);
{
file.setSize(Long.parseLong(filesize));
}
catch (NumberFormatException e)
{
// intentionally do nothing
}
if (name.endsWith("/")) file.setUser(usr);
{
name = name.substring(0, name.length() - 1);
}
if (mustScanForPathSeparator)
{
int pos = name.lastIndexOf('/');
if (pos > -1)
{
name = name.substring(pos + 1);
}
}
file.setName(name); try {
file.setSize(Long.parseLong(filesize));
} catch (NumberFormatException e) {
// intentionally do nothing
}
return file; if (name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
if (mustScanForPathSeparator) {
int pos = name.lastIndexOf('/');
if (pos > -1) {
name = name.substring(pos + 1);
} }
return null; }
}
/** file.setName(name);
*
* @param string String value that is checked for <code>null</code> return file;
* or empty.
* @return <code>true</code> for <code>null</code> or empty values,
* else <code>false</code>.
*/
private boolean isNullOrEmpty(String string) {
if (string == null || string.length() == 0) {
return true;
}
return false;
} }
return null;
}
/** /**
* Defines a default configuration to be used when this class is * @param string String value that is checked for <code>null</code>
* instantiated without a {@link FTPClientConfig FTPClientConfig} * or empty.
* parameter being specified. * @return <code>true</code> for <code>null</code> or empty values,
* @return the default configuration for this parser. * else <code>false</code>.
*/ */
@Override private boolean isNullOrEmpty(String string) {
protected FTPClientConfig getDefaultConfiguration() { if (string == null || string.length() == 0) {
return new FTPClientConfig( return true;
FTPClientConfig.SYST_OS400,
DEFAULT_DATE_FORMAT,
null);
} }
return false;
}
/**
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified.
*
* @return the default configuration for this parser.
*/
@Override protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(FTPClientConfig.SYST_OS400, DEFAULT_DATE_FORMAT, null);
}
} }

@ -24,39 +24,37 @@ package org.apache.commons.net.ftp.parser;
*/ */
public class ParserInitializationException extends RuntimeException { public class ParserInitializationException extends RuntimeException {
private static final long serialVersionUID = 5563335279583210658L; private static final long serialVersionUID = 5563335279583210658L;
/** /**
* Constucts a ParserInitializationException with just a message * Constucts a ParserInitializationException with just a message
* *
* @param message Exception message * @param message Exception message
*/ */
public ParserInitializationException(String message) { public ParserInitializationException(String message) {
super(message); super(message);
} }
/** /**
* Constucts a ParserInitializationException with a message * Constucts a ParserInitializationException with a message
* and a root cause. * and a root cause.
* *
* @param message Exception message * @param message Exception message
* @param rootCause root cause throwable that caused * @param rootCause root cause throwable that caused
* this to be thrown * this to be thrown
*/ */
public ParserInitializationException(String message, Throwable rootCause) { public ParserInitializationException(String message, Throwable rootCause) {
super(message, rootCause); super(message, rootCause);
} }
/**
* returns the root cause of this exception or null
* if no root cause was specified.
*
* @return the root cause of this exception being thrown
* @deprecated use {@link #getCause()} instead
*/
@Deprecated
public Throwable getRootCause() {
return super.getCause();
}
/**
* returns the root cause of this exception or null
* if no root cause was specified.
*
* @return the root cause of this exception being thrown
* @deprecated use {@link #getCause()} instead
*/
@Deprecated public Throwable getRootCause() {
return super.getCause();
}
} }

@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
@ -32,171 +31,170 @@ import org.apache.commons.net.ftp.FTPFileEntryParserImpl;
* *
* This is the base class for all regular expression based FTPFileEntryParser classes * This is the base class for all regular expression based FTPFileEntryParser classes
*/ */
public abstract class RegexFTPFileEntryParserImpl extends public abstract class RegexFTPFileEntryParserImpl extends FTPFileEntryParserImpl {
FTPFileEntryParserImpl { /**
/** * internal pattern the matcher tries to match, representing a file
* internal pattern the matcher tries to match, representing a file * entry
* entry */
*/ private Pattern pattern = null;
private Pattern pattern = null;
/**
/** * internal match result used by the parser
* internal match result used by the parser */
*/ private MatchResult result = null;
private MatchResult result = null;
/**
/** * Internal PatternMatcher object used by the parser. It has protected
* Internal PatternMatcher object used by the parser. It has protected * scope in case subclasses want to make use of it for their own purposes.
* scope in case subclasses want to make use of it for their own purposes. */
*/ protected Matcher _matcher_ = null;
protected Matcher _matcher_ = null;
/**
/** * The constructor for a RegexFTPFileEntryParserImpl object.
* The constructor for a RegexFTPFileEntryParserImpl object. * The expression is compiled with flags = 0.
* The expression is compiled with flags = 0. *
* * @param regex The regular expression with which this object is
* @param regex The regular expression with which this object is * initialized.
* initialized. * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* * be seen in
* @throws IllegalArgumentException * normal conditions. It it is seen, this is a sign that a subclass has
* Thrown if the regular expression is unparseable. Should not be seen in * been created with a bad regular expression. Since the parser must be
* normal conditions. It it is seen, this is a sign that a subclass has * created before use, this means that any bad parser subclasses created
* been created with a bad regular expression. Since the parser must be * from this will bomb very quickly, leading to easy detection.
* created before use, this means that any bad parser subclasses created */
* from this will bomb very quickly, leading to easy detection.
*/ public RegexFTPFileEntryParserImpl(String regex) {
super();
public RegexFTPFileEntryParserImpl(String regex) { compileRegex(regex, 0);
super(); }
compileRegex(regex, 0);
} /**
* The constructor for a RegexFTPFileEntryParserImpl object.
/** *
* The constructor for a RegexFTPFileEntryParserImpl object. * @param regex The regular expression with which this object is
* * initialized.
* @param regex The regular expression with which this object is * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none.
* initialized. * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. * be seen in
* * normal conditions. It it is seen, this is a sign that a subclass has
* @throws IllegalArgumentException * been created with a bad regular expression. Since the parser must be
* Thrown if the regular expression is unparseable. Should not be seen in * created before use, this means that any bad parser subclasses created
* normal conditions. It it is seen, this is a sign that a subclass has * from this will bomb very quickly, leading to easy detection.
* been created with a bad regular expression. Since the parser must be * @since 3.4
* created before use, this means that any bad parser subclasses created */
* from this will bomb very quickly, leading to easy detection. public RegexFTPFileEntryParserImpl(String regex, final int flags) {
* @since 3.4 super();
*/ compileRegex(regex, flags);
public RegexFTPFileEntryParserImpl(String regex, final int flags) { }
super();
compileRegex(regex, flags); /**
} * Convenience method delegates to the internal MatchResult's matches()
* method.
/** *
* Convenience method delegates to the internal MatchResult's matches() * @param s the String to be matched
* method. * @return true if s matches this object's regular expression.
* */
* @param s the String to be matched
* @return true if s matches this object's regular expression. public boolean matches(String s) {
*/ this.result = null;
_matcher_ = pattern.matcher(s);
public boolean matches(String s) { if (_matcher_.matches()) {
this.result = null; this.result = _matcher_.toMatchResult();
_matcher_ = pattern.matcher(s);
if (_matcher_.matches()) {
this.result = _matcher_.toMatchResult();
}
return null != this.result;
}
/**
* Convenience method
*
* @return the number of groups() in the internal MatchResult.
*/
public int getGroupCnt() {
if (this.result == null) {
return 0;
}
return this.result.groupCount();
} }
return null != this.result;
/** }
* Convenience method delegates to the internal MatchResult's group()
* method. /**
* * Convenience method
* @param matchnum match group number to be retrieved *
* * @return the number of groups() in the internal MatchResult.
* @return the content of the <code>matchnum'th</code> group of the internal */
* match or null if this method is called without a match having
* been made. public int getGroupCnt() {
*/ if (this.result == null) {
public String group(int matchnum) { return 0;
if (this.result == null) {
return null;
}
return this.result.group(matchnum);
} }
return this.result.groupCount();
/** }
* For debugging purposes - returns a string shows each match group by
* number. /**
* * Convenience method delegates to the internal MatchResult's group()
* @return a string shows each match group by number. * method.
*/ *
* @param matchnum match group number to be retrieved
public String getGroupsAsString() { * @return the content of the <code>matchnum'th</code> group of the internal
StringBuilder b = new StringBuilder(); * match or null if this method is called without a match having
for (int i = 1; i <= this.result.groupCount(); i++) { * been made.
b.append(i).append(") ").append(this.result.group(i)).append( */
System.getProperty("line.separator")); public String group(int matchnum) {
} if (this.result == null) {
return b.toString(); return null;
} }
return this.result.group(matchnum);
/** }
* Alter the current regular expression being utilised for entry parsing
* and create a new {@link Pattern} instance. /**
* @param regex The new regular expression * For debugging purposes - returns a string shows each match group by
* @return true * number.
* @since 2.0 *
* @throws IllegalArgumentException if the regex cannot be compiled * @return a string shows each match group by number.
*/ */
public boolean setRegex(final String regex) {
compileRegex(regex, 0); public String getGroupsAsString() {
return true; StringBuilder b = new StringBuilder();
for (int i = 1; i <= this.result.groupCount(); i++) {
b.append(i)
.append(") ")
.append(this.result.group(i))
.append(System.getProperty("line.separator"));
} }
return b.toString();
}
/**
* Alter the current regular expression being utilised for entry parsing /**
* and create a new {@link Pattern} instance. * Alter the current regular expression being utilised for entry parsing
* @param regex The new regular expression * and create a new {@link Pattern} instance.
* @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. *
* @return true * @param regex The new regular expression
* @since 3.4 * @return true
* @throws IllegalArgumentException if the regex cannot be compiled * @throws IllegalArgumentException if the regex cannot be compiled
*/ * @since 2.0
public boolean setRegex(final String regex, final int flags) { */
compileRegex(regex, flags); public boolean setRegex(final String regex) {
return true; compileRegex(regex, 0);
} return true;
}
/**
* Compile the regex and store the {@link Pattern}. /**
* * Alter the current regular expression being utilised for entry parsing
* This is an internal method to do the work so the constructor does not * and create a new {@link Pattern} instance.
* have to call an overrideable method. *
* * @param regex The new regular expression
* @param regex the expression to compile * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none.
* @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. * @return true
* @throws IllegalArgumentException if the regex cannot be compiled * @throws IllegalArgumentException if the regex cannot be compiled
*/ * @since 3.4
private void compileRegex(final String regex, final int flags) { */
try { public boolean setRegex(final String regex, final int flags) {
pattern = Pattern.compile(regex, flags); compileRegex(regex, flags);
} catch (PatternSyntaxException pse) { return true;
throw new IllegalArgumentException("Unparseable regex supplied: " + regex); }
}
/**
* Compile the regex and store the {@link Pattern}.
*
* This is an internal method to do the work so the constructor does not
* have to call an overrideable method.
*
* @param regex the expression to compile
* @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none.
* @throws IllegalArgumentException if the regex cannot be compiled
*/
private void compileRegex(final String regex, final int flags) {
try {
pattern = Pattern.compile(regex, flags);
} catch (PatternSyntaxException pse) {
throw new IllegalArgumentException("Unparseable regex supplied: " + regex);
} }
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.text.ParseException; import java.text.ParseException;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
@ -30,90 +31,83 @@ import org.apache.commons.net.ftp.FTPFile;
* This class is based on the logic of Daniel Savarese's * This class is based on the logic of Daniel Savarese's
* DefaultFTPListParser, but adapted to use regular expressions and to fit the * DefaultFTPListParser, but adapted to use regular expressions and to fit the
* new FTPFileEntryParser interface. * new FTPFileEntryParser interface.
*
* @version $Id: UnixFTPEntryParser.java 1781925 2017-02-06 16:43:40Z sebb $ * @version $Id: UnixFTPEntryParser.java 1781925 2017-02-06 16:43:40Z sebb $
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
*/ */
public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
{
static final String DEFAULT_DATE_FORMAT = "MMM d yyyy"; //Nov 9 2001
static final String DEFAULT_DATE_FORMAT
= "MMM d yyyy"; //Nov 9 2001 static final String DEFAULT_RECENT_DATE_FORMAT = "MMM d HH:mm"; //Nov 9 20:06
static final String DEFAULT_RECENT_DATE_FORMAT static final String NUMERIC_DATE_FORMAT = "yyyy-MM-dd HH:mm"; //2001-11-09 20:06
= "MMM d HH:mm"; //Nov 9 20:06
// Suffixes used in Japanese listings after the numeric values
static final String NUMERIC_DATE_FORMAT private static final String JA_MONTH = "\u6708";
= "yyyy-MM-dd HH:mm"; //2001-11-09 20:06 private static final String JA_DAY = "\u65e5";
private static final String JA_YEAR = "\u5e74";
// Suffixes used in Japanese listings after the numeric values
private static final String JA_MONTH = "\u6708"; private static final String DEFAULT_DATE_FORMAT_JA =
private static final String JA_DAY = "\u65e5"; "M'" + JA_MONTH + "' d'" + JA_DAY + "' yyyy'" + JA_YEAR + "'"; //6月 3日 2003年
private static final String JA_YEAR = "\u5e74";
private static final String DEFAULT_RECENT_DATE_FORMAT_JA =
private static final String DEFAULT_DATE_FORMAT_JA "M'" + JA_MONTH + "' d'" + JA_DAY + "' HH:mm"; //8月 17日 20:10
= "M'" + JA_MONTH + "' d'" + JA_DAY + "' yyyy'" + JA_YEAR + "'"; //6月 3日 2003年
/**
private static final String DEFAULT_RECENT_DATE_FORMAT_JA * Some Linux distributions are now shipping an FTP server which formats
= "M'" + JA_MONTH + "' d'" + JA_DAY + "' HH:mm"; //8月 17日 20:10 * file listing dates in an all-numeric format:
* <code>"yyyy-MM-dd HH:mm</code>.
/** * This is a very welcome development, and hopefully it will soon become
* Some Linux distributions are now shipping an FTP server which formats * the standard. However, since it is so new, for now, and possibly
* file listing dates in an all-numeric format: * forever, we merely accomodate it, but do not make it the default.
* <code>"yyyy-MM-dd HH:mm</code>. * <p>
* This is a very welcome development, and hopefully it will soon become * For now end users may specify this format only via
* the standard. However, since it is so new, for now, and possibly * <code>UnixFTPEntryParser(FTPClientConfig)</code>.
* forever, we merely accomodate it, but do not make it the default. * Steve Cohen - 2005-04-17
* <p> */
* For now end users may specify this format only via public static final FTPClientConfig NUMERIC_DATE_CONFIG =
* <code>UnixFTPEntryParser(FTPClientConfig)</code>. new FTPClientConfig(FTPClientConfig.SYST_UNIX, NUMERIC_DATE_FORMAT, null);
* Steve Cohen - 2005-04-17
*/ /**
public static final FTPClientConfig NUMERIC_DATE_CONFIG = * this is the regular expression used by this parser.
new FTPClientConfig( *
FTPClientConfig.SYST_UNIX, * Permissions:
NUMERIC_DATE_FORMAT, * r the file is readable
null); * w the file is writable
* x the file is executable
/** * - the indicated permission is not granted
* this is the regular expression used by this parser. * L mandatory locking occurs during access (the set-group-ID bit is
* * on and the group execution bit is off)
* Permissions: * s the set-user-ID or set-group-ID bit is on, and the corresponding
* r the file is readable * user or group execution bit is also on
* w the file is writable * S undefined bit-state (the set-user-ID bit is on and the user
* x the file is executable * execution bit is off)
* - the indicated permission is not granted * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and
* L mandatory locking occurs during access (the set-group-ID bit is * execution is on
* on and the group execution bit is off) * T the 1000 bit is turned on, and execution is off (undefined bit-
* s the set-user-ID or set-group-ID bit is on, and the corresponding * state)
* user or group execution bit is also on * e z/OS external link bit
* S undefined bit-state (the set-user-ID bit is on and the user * Final letter may be appended:
* execution bit is off) * + file has extended security attributes (e.g. ACL)
* t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and * Note: local listings on MacOSX also use '@';
* execution is on * this is not allowed for here as does not appear to be shown by FTP servers
* T the 1000 bit is turned on, and execution is off (undefined bit- * {@code @} file has extended attributes
* state) */
* e z/OS external link bit private static final String REGEX = "([bcdelfmpSs-])" // file type
* Final letter may be appended: + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions
* + file has extended security attributes (e.g. ACL)
* Note: local listings on MacOSX also use '@'; + "\\s*" // separator TODO why allow it to be omitted??
* this is not allowed for here as does not appear to be shown by FTP servers
* {@code @} file has extended attributes + "(\\d+)" // link count
*/
private static final String REGEX = + "\\s+" // separator
"([bcdelfmpSs-])" // file type
+"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces)
+ "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces)
+ "\\s*" // separator TODO why allow it to be omitted?? + "(\\d+(?:,\\s*\\d+)?)" // size or n,m
+ "(\\d+)" // link count + "\\s+" // separator
+ "\\s+" // separator
+ "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces)
+ "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces)
+ "(\\d+(?:,\\s*\\d+)?)" // size or n,m
+ "\\s+" // separator
/* /*
* numeric or standard format date: * numeric or standard format date:
@ -124,256 +118,218 @@ public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl
* diacritics (e.g. umlaut) in some abbreviations. * diacritics (e.g. umlaut) in some abbreviations.
* Japanese uses numeric day and month with suffixes to distinguish them * Japanese uses numeric day and month with suffixes to distinguish them
* [d]dXX [d]dZZ * [d]dXX [d]dZZ
*/ */ + "(" + "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd
+ "("+ "|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d
"(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd "|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM
"|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d "|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")" + ")"
"|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM
"|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")"+
")"
+ "\\s+" // separator + "\\s+" // separator
/* /*
year (for non-recent standard format) - yyyy year (for non-recent standard format) - yyyy
or time (for numeric or recent standard format) [h]h:mm or time (for numeric or recent standard format) [h]h:mm
or Japanese year - yyyyXX or Japanese year - yyyyXX
*/ */ + "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20)
+ "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20)
+ "\\s" // separator
+ "\\s" // separator
+ "(.*)"; // the rest (21)
+ "(.*)"; // the rest (21)
// if true, leading spaces are trimmed from file names
// this was the case for the original implementation
// if true, leading spaces are trimmed from file names final boolean trimLeadingSpaces; // package protected for access from test code
// this was the case for the original implementation
final boolean trimLeadingSpaces; // package protected for access from test code /**
* The default constructor for a UnixFTPEntryParser object.
/** *
* The default constructor for a UnixFTPEntryParser object. * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* * be seen
* @throws IllegalArgumentException * under normal conditions. It it is seen, this is a sign that
* Thrown if the regular expression is unparseable. Should not be seen * <code>REGEX</code> is not a valid regular expression.
* under normal conditions. It it is seen, this is a sign that */
* <code>REGEX</code> is not a valid regular expression. public UnixFTPEntryParser() {
*/ this(null);
public UnixFTPEntryParser() }
{
this(null); /**
} * This constructor allows the creation of a UnixFTPEntryParser object with
* something other than the default configuration.
/** *
* This constructor allows the creation of a UnixFTPEntryParser object with * @param config The {@link FTPClientConfig configuration} object used to
* something other than the default configuration. * configure this parser.
* * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* @param config The {@link FTPClientConfig configuration} object used to * be seen
* configure this parser. * under normal conditions. It it is seen, this is a sign that
* @throws IllegalArgumentException * <code>REGEX</code> is not a valid regular expression.
* Thrown if the regular expression is unparseable. Should not be seen * @since 1.4
* under normal conditions. It it is seen, this is a sign that */
* <code>REGEX</code> is not a valid regular expression. public UnixFTPEntryParser(FTPClientConfig config) {
* @since 1.4 this(config, false);
*/ }
public UnixFTPEntryParser(FTPClientConfig config)
{ /**
this(config, false); * This constructor allows the creation of a UnixFTPEntryParser object with
* something other than the default configuration.
*
* @param config The {@link FTPClientConfig configuration} object used to
* configure this parser.
* @param trimLeadingSpaces if {@code true}, trim leading spaces from file names
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
* @since 3.4
*/
public UnixFTPEntryParser(FTPClientConfig config, boolean trimLeadingSpaces) {
super(REGEX);
configure(config);
this.trimLeadingSpaces = trimLeadingSpaces;
}
/**
* Preparse the list to discard "total nnn" lines
*/
@Override public List<String> preParse(List<String> original) {
ListIterator<String> iter = original.listIterator();
while (iter.hasNext()) {
String entry = iter.next();
if (entry.matches("^total \\d+$")) { // NET-389
iter.remove();
}
} }
return original;
/** }
* This constructor allows the creation of a UnixFTPEntryParser object with
* something other than the default configuration. /**
* * Parses a line of a unix (standard) FTP server file listing and converts
* @param config The {@link FTPClientConfig configuration} object used to * it into a usable format in the form of an <code> FTPFile </code>
* configure this parser. * instance. If the file listing line doesn't describe a file,
* @param trimLeadingSpaces if {@code true}, trim leading spaces from file names * <code> null </code> is returned, otherwise a <code> FTPFile </code>
* @throws IllegalArgumentException * instance representing the files in the directory is returned.
* Thrown if the regular expression is unparseable. Should not be seen *
* under normal conditions. It it is seen, this is a sign that * @param entry A line of text from the file listing
* <code>REGEX</code> is not a valid regular expression. * @return An FTPFile instance corresponding to the supplied entry
* @since 3.4 */
*/ @Override public FTPFile parseFTPEntry(String entry) {
public UnixFTPEntryParser(FTPClientConfig config, boolean trimLeadingSpaces) FTPFile file = new FTPFile();
{ file.setRawListing(entry);
super(REGEX); int type;
configure(config); boolean isDevice = false;
this.trimLeadingSpaces = trimLeadingSpaces;
} if (matches(entry)) {
String typeStr = group(1);
/** String hardLinkCount = group(15);
* Preparse the list to discard "total nnn" lines String usr = group(16);
*/ String grp = group(17);
@Override String filesize = group(18);
public List<String> preParse(List<String> original) { String datestr = group(19) + " " + group(20);
ListIterator<String> iter = original.listIterator(); String name = group(21);
while (iter.hasNext()) { if (trimLeadingSpaces) {
String entry = iter.next(); name = name.replaceFirst("^\\s+", "");
if (entry.matches("^total \\d+$")) { // NET-389 }
iter.remove();
} try {
if (group(19).contains(JA_MONTH)) { // special processing for Japanese format
FTPTimestampParserImpl jaParser = new FTPTimestampParserImpl();
jaParser.configure(new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT_JA,
DEFAULT_RECENT_DATE_FORMAT_JA));
file.setTimestamp(jaParser.parseTimestamp(datestr));
} else {
file.setTimestamp(super.parseTimestamp(datestr));
} }
return original; } catch (ParseException e) {
} // intentionally do nothing
}
/**
* Parses a line of a unix (standard) FTP server file listing and converts // A 'whiteout' file is an ARTIFICIAL entry in any of several types of
* it into a usable format in the form of an <code> FTPFile </code> // 'translucent' filesystems, of which a 'union' filesystem is one.
* instance. If the file listing line doesn't describe a file,
* <code> null </code> is returned, otherwise a <code> FTPFile </code> // bcdelfmpSs-
* instance representing the files in the directory is returned. switch (typeStr.charAt(0)) {
* case 'd':
* @param entry A line of text from the file listing type = FTPFile.DIRECTORY_TYPE;
* @return An FTPFile instance corresponding to the supplied entry break;
*/ case 'e': // NET-39 => z/OS external link
@Override type = FTPFile.SYMBOLIC_LINK_TYPE;
public FTPFile parseFTPEntry(String entry) { break;
FTPFile file = new FTPFile(); case 'l':
file.setRawListing(entry); type = FTPFile.SYMBOLIC_LINK_TYPE;
int type; break;
boolean isDevice = false; case 'b':
case 'c':
if (matches(entry)) isDevice = true;
{ type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
String typeStr = group(1); break;
String hardLinkCount = group(15); case 'f':
String usr = group(16); case '-':
String grp = group(17); type = FTPFile.FILE_TYPE;
String filesize = group(18); break;
String datestr = group(19) + " " + group(20); default: // e.g. ? and w = whiteout
String name = group(21); type = FTPFile.UNKNOWN_TYPE;
if (trimLeadingSpaces) { }
name = name.replaceFirst("^\\s+", "");
} file.setType(type);
try int g = 4;
{ for (int access = 0; access < 3; access++, g += 4) {
if (group(19).contains(JA_MONTH)) { // special processing for Japanese format // Use != '-' to avoid having to check for suid and sticky bits
FTPTimestampParserImpl jaParser = new FTPTimestampParserImpl(); file.setPermission(access, FTPFile.READ_PERMISSION, (!group(g).equals("-")));
jaParser.configure(new FTPClientConfig( file.setPermission(access, FTPFile.WRITE_PERMISSION, (!group(g + 1).equals("-")));
FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT_JA, DEFAULT_RECENT_DATE_FORMAT_JA));
file.setTimestamp(jaParser.parseTimestamp(datestr)); String execPerm = group(g + 2);
} else { if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))) {
file.setTimestamp(super.parseTimestamp(datestr)); file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
} } else {
} file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
catch (ParseException e)
{
// intentionally do nothing
}
// A 'whiteout' file is an ARTIFICIAL entry in any of several types of
// 'translucent' filesystems, of which a 'union' filesystem is one.
// bcdelfmpSs-
switch (typeStr.charAt(0))
{
case 'd':
type = FTPFile.DIRECTORY_TYPE;
break;
case 'e': // NET-39 => z/OS external link
type = FTPFile.SYMBOLIC_LINK_TYPE;
break;
case 'l':
type = FTPFile.SYMBOLIC_LINK_TYPE;
break;
case 'b':
case 'c':
isDevice = true;
type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
break;
case 'f':
case '-':
type = FTPFile.FILE_TYPE;
break;
default: // e.g. ? and w = whiteout
type = FTPFile.UNKNOWN_TYPE;
}
file.setType(type);
int g = 4;
for (int access = 0; access < 3; access++, g += 4)
{
// Use != '-' to avoid having to check for suid and sticky bits
file.setPermission(access, FTPFile.READ_PERMISSION,
(!group(g).equals("-")));
file.setPermission(access, FTPFile.WRITE_PERMISSION,
(!group(g + 1).equals("-")));
String execPerm = group(g + 2);
if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)))
{
file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
}
else
{
file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
}
}
if (!isDevice)
{
try
{
file.setHardLinkCount(Integer.parseInt(hardLinkCount));
}
catch (NumberFormatException e)
{
// intentionally do nothing
}
}
file.setUser(usr);
file.setGroup(grp);
try
{
file.setSize(Long.parseLong(filesize));
}
catch (NumberFormatException e)
{
// intentionally do nothing
}
// oddball cases like symbolic links, file names
// with spaces in them.
if (type == FTPFile.SYMBOLIC_LINK_TYPE)
{
int end = name.indexOf(" -> ");
// Give up if no link indicator is present
if (end == -1)
{
file.setName(name);
}
else
{
file.setName(name.substring(0, end));
file.setLink(name.substring(end + 4));
}
}
else
{
file.setName(name);
}
return file;
} }
return null; }
}
/** if (!isDevice) {
* Defines a default configuration to be used when this class is try {
* instantiated without a {@link FTPClientConfig FTPClientConfig} file.setHardLinkCount(Integer.parseInt(hardLinkCount));
* parameter being specified. } catch (NumberFormatException e) {
* @return the default configuration for this parser. // intentionally do nothing
*/ }
@Override }
protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig( file.setUser(usr);
FTPClientConfig.SYST_UNIX, file.setGroup(grp);
DEFAULT_DATE_FORMAT,
DEFAULT_RECENT_DATE_FORMAT); try {
file.setSize(Long.parseLong(filesize));
} catch (NumberFormatException e) {
// intentionally do nothing
}
// oddball cases like symbolic links, file names
// with spaces in them.
if (type == FTPFile.SYMBOLIC_LINK_TYPE) {
int end = name.indexOf(" -> ");
// Give up if no link indicator is present
if (end == -1) {
file.setName(name);
} else {
file.setName(name.substring(0, end));
file.setLink(name.substring(end + 4));
}
} else {
file.setName(name);
}
return file;
} }
return null;
}
/**
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified.
*
* @return the default configuration for this parser.
*/
@Override protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT,
DEFAULT_RECENT_DATE_FORMAT);
}
} }

@ -16,6 +16,7 @@
*/ */
package org.apache.commons.net.ftp.parser; package org.apache.commons.net.ftp.parser;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException; import java.text.ParseException;
@ -28,9 +29,12 @@ import org.apache.commons.net.ftp.FTPFile;
* Implementation FTPFileEntryParser and FTPFileListParser for VMS Systems. * Implementation FTPFileEntryParser and FTPFileListParser for VMS Systems.
* This is a sample of VMS LIST output * This is a sample of VMS LIST output
* *
* "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER]
* "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * (RWED,RWED,RWED,RE)",
* "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER]
* (RWED,RWED,RWED,RE)",
* "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER]
* (RWED,RWED,RWED,RE)",
* <P><B> * <P><B>
* Note: VMSFTPEntryParser can only be instantiated through the * Note: VMSFTPEntryParser can only be instantiated through the
* DefaultFTPParserFactory by classname. It will not be chosen * DefaultFTPParserFactory by classname. It will not be chosen
@ -39,231 +43,202 @@ import org.apache.commons.net.ftp.FTPFile;
* <P> * <P>
* *
* @version $Id: VMSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @version $Id: VMSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $
*
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
* @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory
*/ */
public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
{
private static final String DEFAULT_DATE_FORMAT = "d-MMM-yyyy HH:mm:ss"; //9-NOV-2001 12:30:24
private static final String DEFAULT_DATE_FORMAT
= "d-MMM-yyyy HH:mm:ss"; //9-NOV-2001 12:30:24 /**
* this is the regular expression used by this parser.
/** */
* this is the regular expression used by this parser. private static final String REGEX =
*/ "(.*?;[0-9]+)\\s*" //1 file and version
private static final String REGEX = + "(\\d+)/\\d+\\s*" //2 size/allocated
"(.*?;[0-9]+)\\s*" //1 file and version + "(\\S+)\\s+(\\S+)\\s+" //3+4 date and time
+ "(\\d+)/\\d+\\s*" //2 size/allocated + "\\[(([0-9$A-Za-z_]+)|([0-9$A-Za-z_]+),([0-9$a-zA-Z_]+))\\]?\\s*" //5(6,7,8) owner
+"(\\S+)\\s+(\\S+)\\s+" //3+4 date and time + "\\([a-zA-Z]*,([a-zA-Z]*),([a-zA-Z]*),([a-zA-Z]*)\\)";
+ "\\[(([0-9$A-Za-z_]+)|([0-9$A-Za-z_]+),([0-9$a-zA-Z_]+))\\]?\\s*" //5(6,7,8) owner //9,10,11 Permissions (O,G,W)
+ "\\([a-zA-Z]*,([a-zA-Z]*),([a-zA-Z]*),([a-zA-Z]*)\\)"; //9,10,11 Permissions (O,G,W) // TODO - perhaps restrict permissions to [RWED]* ?
// TODO - perhaps restrict permissions to [RWED]* ?
/**
* Constructor for a VMSFTPEntryParser object.
*
/** * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* Constructor for a VMSFTPEntryParser object. * be seen
* * under normal conditions. It it is seen, this is a sign that
* @throws IllegalArgumentException * <code>REGEX</code> is not a valid regular expression.
* Thrown if the regular expression is unparseable. Should not be seen */
* under normal conditions. It it is seen, this is a sign that public VMSFTPEntryParser() {
* <code>REGEX</code> is not a valid regular expression. this(null);
*/ }
public VMSFTPEntryParser()
{ /**
this(null); * This constructor allows the creation of a VMSFTPEntryParser object with
* something other than the default configuration.
*
* @param config The {@link FTPClientConfig configuration} object used to
* configure this parser.
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
* @since 1.4
*/
public VMSFTPEntryParser(FTPClientConfig config) {
super(REGEX);
configure(config);
}
/**
* Parses a line of a VMS FTP server file listing and converts it into a
* usable format in the form of an <code> FTPFile </code> instance. If the
* file listing line doesn't describe a file, <code> null </code> is
* returned, otherwise a <code> FTPFile </code> instance representing the
* files in the directory is returned.
*
* @param entry A line of text from the file listing
* @return An FTPFile instance corresponding to the supplied entry
*/
@Override public FTPFile parseFTPEntry(String entry) {
//one block in VMS equals 512 bytes
long longBlock = 512;
if (matches(entry)) {
FTPFile f = new FTPFile();
f.setRawListing(entry);
String name = group(1);
String size = group(2);
String datestr = group(3) + " " + group(4);
String owner = group(5);
String permissions[] = new String[3];
permissions[0] = group(9);
permissions[1] = group(10);
permissions[2] = group(11);
try {
f.setTimestamp(super.parseTimestamp(datestr));
} catch (ParseException e) {
// intentionally do nothing
}
String grp;
String user;
StringTokenizer t = new StringTokenizer(owner, ",");
switch (t.countTokens()) {
case 1:
grp = null;
user = t.nextToken();
break;
case 2:
grp = t.nextToken();
user = t.nextToken();
break;
default:
grp = null;
user = null;
}
if (name.lastIndexOf(".DIR") != -1) {
f.setType(FTPFile.DIRECTORY_TYPE);
} else {
f.setType(FTPFile.FILE_TYPE);
}
//set FTPFile name
//Check also for versions to be returned or not
if (isVersioning()) {
f.setName(name);
} else {
name = name.substring(0, name.lastIndexOf(";"));
f.setName(name);
}
//size is retreived in blocks and needs to be put in bytes
//for us humans and added to the FTPFile array
long sizeInBytes = Long.parseLong(size) * longBlock;
f.setSize(sizeInBytes);
f.setGroup(grp);
f.setUser(user);
//set group and owner
//Set file permission.
//VMS has (SYSTEM,OWNER,GROUP,WORLD) users that can contain
//R (read) W (write) E (execute) D (delete)
//iterate for OWNER GROUP WORLD permissions
for (int access = 0; access < 3; access++) {
String permission = permissions[access];
f.setPermission(access, FTPFile.READ_PERMISSION, permission.indexOf('R') >= 0);
f.setPermission(access, FTPFile.WRITE_PERMISSION, permission.indexOf('W') >= 0);
f.setPermission(access, FTPFile.EXECUTE_PERMISSION, permission.indexOf('E') >= 0);
}
return f;
} }
return null;
/** }
* This constructor allows the creation of a VMSFTPEntryParser object with
* something other than the default configuration. /**
* * Reads the next entry using the supplied BufferedReader object up to
* @param config The {@link FTPClientConfig configuration} object used to * whatever delemits one entry from the next. This parser cannot use
* configure this parser. * the default implementation of simply calling BufferedReader.readLine(),
* @throws IllegalArgumentException * because one entry may span multiple lines.
* Thrown if the regular expression is unparseable. Should not be seen *
* under normal conditions. It it is seen, this is a sign that * @param reader The BufferedReader object from which entries are to be
* <code>REGEX</code> is not a valid regular expression. * read.
* @since 1.4 * @return A string representing the next ftp entry or null if none found.
*/ * @throws IOException thrown on any IO Error reading from the reader.
public VMSFTPEntryParser(FTPClientConfig config) */
{ @Override public String readNextEntry(BufferedReader reader) throws IOException {
super(REGEX); String line = reader.readLine();
configure(config); StringBuilder entry = new StringBuilder();
while (line != null) {
if (line.startsWith("Directory") || line.startsWith("Total")) {
line = reader.readLine();
continue;
}
entry.append(line);
if (line.trim().endsWith(")")) {
break;
}
line = reader.readLine();
} }
return (entry.length() == 0 ? null : entry.toString());
/** }
* Parses a line of a VMS FTP server file listing and converts it into a
* usable format in the form of an <code> FTPFile </code> instance. If the protected boolean isVersioning() {
* file listing line doesn't describe a file, <code> null </code> is return false;
* returned, otherwise a <code> FTPFile </code> instance representing the }
* files in the directory is returned.
* /**
* @param entry A line of text from the file listing * Defines a default configuration to be used when this class is
* @return An FTPFile instance corresponding to the supplied entry * instantiated without a {@link FTPClientConfig FTPClientConfig}
*/ * parameter being specified.
@Override *
public FTPFile parseFTPEntry(String entry) * @return the default configuration for this parser.
{ */
//one block in VMS equals 512 bytes @Override protected FTPClientConfig getDefaultConfiguration() {
long longBlock = 512; return new FTPClientConfig(FTPClientConfig.SYST_VMS, DEFAULT_DATE_FORMAT, null);
}
if (matches(entry))
{ // DEPRECATED METHODS - for API compatibility only - DO NOT USE
FTPFile f = new FTPFile();
f.setRawListing(entry); /**
String name = group(1); * DO NOT USE
String size = group(2); *
String datestr = group(3)+" "+group(4); * @param listStream the stream
String owner = group(5); * @return the array of files
String permissions[] = new String[3]; * @throws IOException on error
permissions[0]= group(9); * @deprecated (2.2) No other FTPFileEntryParser implementations have this method.
permissions[1]= group(10); */
permissions[2]= group(11); @Deprecated public FTPFile[] parseFileList(java.io.InputStream listStream) throws IOException {
try org.apache.commons.net.ftp.FTPListParseEngine engine =
{ new org.apache.commons.net.ftp.FTPListParseEngine(this);
f.setTimestamp(super.parseTimestamp(datestr)); engine.readServerList(listStream, null);
} return engine.getFiles();
catch (ParseException e) }
{
// intentionally do nothing
}
String grp;
String user;
StringTokenizer t = new StringTokenizer(owner, ",");
switch (t.countTokens()) {
case 1:
grp = null;
user = t.nextToken();
break;
case 2:
grp = t.nextToken();
user = t.nextToken();
break;
default:
grp = null;
user = null;
}
if (name.lastIndexOf(".DIR") != -1)
{
f.setType(FTPFile.DIRECTORY_TYPE);
}
else
{
f.setType(FTPFile.FILE_TYPE);
}
//set FTPFile name
//Check also for versions to be returned or not
if (isVersioning())
{
f.setName(name);
}
else
{
name = name.substring(0, name.lastIndexOf(";"));
f.setName(name);
}
//size is retreived in blocks and needs to be put in bytes
//for us humans and added to the FTPFile array
long sizeInBytes = Long.parseLong(size) * longBlock;
f.setSize(sizeInBytes);
f.setGroup(grp);
f.setUser(user);
//set group and owner
//Set file permission.
//VMS has (SYSTEM,OWNER,GROUP,WORLD) users that can contain
//R (read) W (write) E (execute) D (delete)
//iterate for OWNER GROUP WORLD permissions
for (int access = 0; access < 3; access++)
{
String permission = permissions[access];
f.setPermission(access, FTPFile.READ_PERMISSION, permission.indexOf('R')>=0);
f.setPermission(access, FTPFile.WRITE_PERMISSION, permission.indexOf('W')>=0);
f.setPermission(access, FTPFile.EXECUTE_PERMISSION, permission.indexOf('E')>=0);
}
return f;
}
return null;
}
/**
* Reads the next entry using the supplied BufferedReader object up to
* whatever delemits one entry from the next. This parser cannot use
* the default implementation of simply calling BufferedReader.readLine(),
* because one entry may span multiple lines.
*
* @param reader The BufferedReader object from which entries are to be
* read.
*
* @return A string representing the next ftp entry or null if none found.
* @throws IOException thrown on any IO Error reading from the reader.
*/
@Override
public String readNextEntry(BufferedReader reader) throws IOException
{
String line = reader.readLine();
StringBuilder entry = new StringBuilder();
while (line != null)
{
if (line.startsWith("Directory") || line.startsWith("Total")) {
line = reader.readLine();
continue;
}
entry.append(line);
if (line.trim().endsWith(")"))
{
break;
}
line = reader.readLine();
}
return (entry.length() == 0 ? null : entry.toString());
}
protected boolean isVersioning() {
return false;
}
/**
* Defines a default configuration to be used when this class is
* instantiated without a {@link FTPClientConfig FTPClientConfig}
* parameter being specified.
* @return the default configuration for this parser.
*/
@Override
protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(
FTPClientConfig.SYST_VMS,
DEFAULT_DATE_FORMAT,
null);
}
// DEPRECATED METHODS - for API compatibility only - DO NOT USE
/**
* DO NOT USE
* @param listStream the stream
* @return the array of files
* @throws IOException on error
* @deprecated (2.2) No other FTPFileEntryParser implementations have this method.
*/
@Deprecated
public FTPFile[] parseFileList(java.io.InputStream listStream) throws IOException {
org.apache.commons.net.ftp.FTPListParseEngine engine = new org.apache.commons.net.ftp.FTPListParseEngine(this);
engine.readServerList(listStream, null);
return engine.getFiles();
}
} }
/* Emacs configuration /* Emacs configuration

@ -34,128 +34,114 @@ import org.apache.commons.net.ftp.FTPClientConfig;
* *
* This is a sample of VMS LIST output * This is a sample of VMS LIST output
* *
* "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER]
* "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * (RWED,RWED,RWED,RE)",
* "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER]
* (RWED,RWED,RWED,RE)",
* "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER]
* (RWED,RWED,RWED,RE)",
* <P> * <P>
* *
* @version $Id: VMSVersioningFTPEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ * @version $Id: VMSVersioningFTPEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $
*
* @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
*/ */
public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser {
{
private final Pattern _preparse_pattern_;
private static final String PRE_PARSE_REGEX =
"(.*?);([0-9]+)\\s*.*";
/**
* Constructor for a VMSFTPEntryParser object.
*
* @throws IllegalArgumentException
* Thrown if the regular expression is unparseable. Should not be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
*/
public VMSVersioningFTPEntryParser()
{
this(null);
}
/**
* This constructor allows the creation of a VMSVersioningFTPEntryParser
* object with something other than the default configuration.
*
* @param config The {@link FTPClientConfig configuration} object used to
* configure this parser.
* @throws IllegalArgumentException
* Thrown if the regular expression is unparseable. Should not be seen
* under normal conditions. It it is seen, this is a sign that
* <code>REGEX</code> is not a valid regular expression.
* @since 1.4
*/
public VMSVersioningFTPEntryParser(FTPClientConfig config)
{
super();
configure(config);
try
{
//_preparse_matcher_ = new Perl5Matcher();
_preparse_pattern_ = Pattern.compile(PRE_PARSE_REGEX);
}
catch (PatternSyntaxException pse)
{
throw new IllegalArgumentException (
"Unparseable regex supplied: " + PRE_PARSE_REGEX);
}
} private final Pattern _preparse_pattern_;
private static final String PRE_PARSE_REGEX = "(.*?);([0-9]+)\\s*.*";
/** /**
* Implement hook provided for those implementers (such as * Constructor for a VMSFTPEntryParser object.
* VMSVersioningFTPEntryParser, and possibly others) which return *
* multiple files with the same name to remove the duplicates .. * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
* * be seen
* @param original Original list * under normal conditions. It it is seen, this is a sign that
* * <code>REGEX</code> is not a valid regular expression.
* @return Original list purged of duplicates */
*/ public VMSVersioningFTPEntryParser() {
@Override this(null);
public List<String> preParse(List<String> original) { }
HashMap<String, Integer> existingEntries = new HashMap<String, Integer>();
ListIterator<String> iter = original.listIterator();
while (iter.hasNext()) {
String entry = iter.next().trim();
MatchResult result = null;
Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry);
if (_preparse_matcher_.matches()) {
result = _preparse_matcher_.toMatchResult();
String name = result.group(1);
String version = result.group(2);
Integer nv = Integer.valueOf(version);
Integer existing = existingEntries.get(name);
if (null != existing) {
if (nv.intValue() < existing.intValue()) {
iter.remove(); // removes older version from original list.
continue;
}
}
existingEntries.put(name, nv);
}
} /**
// we've now removed all entries less than with less than the largest * This constructor allows the creation of a VMSVersioningFTPEntryParser
// version number for each name that were listed after the largest. * object with something other than the default configuration.
// we now must remove those with smaller than the largest version number *
// for each name that were found before the largest * @param config The {@link FTPClientConfig configuration} object used to
while (iter.hasPrevious()) { * configure this parser.
String entry = iter.previous().trim(); * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not
MatchResult result = null; * be seen
Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); * under normal conditions. It it is seen, this is a sign that
if (_preparse_matcher_.matches()) { * <code>REGEX</code> is not a valid regular expression.
result = _preparse_matcher_.toMatchResult(); * @since 1.4
String name = result.group(1); */
String version = result.group(2); public VMSVersioningFTPEntryParser(FTPClientConfig config) {
Integer nv = Integer.valueOf(version); super();
Integer existing = existingEntries.get(name); configure(config);
if (null != existing) { try {
if (nv.intValue() < existing.intValue()) { //_preparse_matcher_ = new Perl5Matcher();
iter.remove(); // removes older version from original list. _preparse_pattern_ = Pattern.compile(PRE_PARSE_REGEX);
} } catch (PatternSyntaxException pse) {
} throw new IllegalArgumentException("Unparseable regex supplied: " + PRE_PARSE_REGEX);
} }
}
/**
* Implement hook provided for those implementers (such as
* VMSVersioningFTPEntryParser, and possibly others) which return
* multiple files with the same name to remove the duplicates ..
*
* @param original Original list
* @return Original list purged of duplicates
*/
@Override public List<String> preParse(List<String> original) {
HashMap<String, Integer> existingEntries = new HashMap<String, Integer>();
ListIterator<String> iter = original.listIterator();
while (iter.hasNext()) {
String entry = iter.next().trim();
MatchResult result = null;
Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry);
if (_preparse_matcher_.matches()) {
result = _preparse_matcher_.toMatchResult();
String name = result.group(1);
String version = result.group(2);
Integer nv = Integer.valueOf(version);
Integer existing = existingEntries.get(name);
if (null != existing) {
if (nv.intValue() < existing.intValue()) {
iter.remove(); // removes older version from original list.
continue;
}
} }
return original; existingEntries.put(name, nv);
}
} }
// we've now removed all entries less than with less than the largest
// version number for each name that were listed after the largest.
@Override // we now must remove those with smaller than the largest version number
protected boolean isVersioning() { // for each name that were found before the largest
return true; while (iter.hasPrevious()) {
String entry = iter.previous().trim();
MatchResult result = null;
Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry);
if (_preparse_matcher_.matches()) {
result = _preparse_matcher_.toMatchResult();
String name = result.group(1);
String version = result.group(2);
Integer nv = Integer.valueOf(version);
Integer existing = existingEntries.get(name);
if (null != existing) {
if (nv.intValue() < existing.intValue()) {
iter.remove(); // removes older version from original list.
}
}
}
} }
return original;
}
@Override protected boolean isVersioning() {
return true;
}
} }
/* Emacs configuration /* Emacs configuration

@ -28,50 +28,48 @@ import java.io.Reader;
* *
* @since 3.0 * @since 3.0
*/ */
public final class CRLFLineReader extends BufferedReader public final class CRLFLineReader extends BufferedReader {
{ private static final char LF = '\n';
private static final char LF = '\n'; private static final char CR = '\r';
private static final char CR = '\r';
/** /**
* Creates a CRLFLineReader that wraps an existing Reader * Creates a CRLFLineReader that wraps an existing Reader
* input source. * input source.
* @param reader The Reader input source. *
*/ * @param reader The Reader input source.
public CRLFLineReader(Reader reader) */
{ public CRLFLineReader(Reader reader) {
super(reader); super(reader);
} }
/** /**
* Read a line of text. * Read a line of text.
* A line is considered to be terminated by carriage return followed immediately by a linefeed. * A line is considered to be terminated by carriage return followed immediately by a linefeed.
* This contrasts with BufferedReader which also allows other combinations. * This contrasts with BufferedReader which also allows other combinations.
* @since 3.0 *
*/ * @since 3.0
@Override */
public String readLine() throws IOException { @Override public String readLine() throws IOException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int intch; int intch;
boolean prevWasCR = false; boolean prevWasCR = false;
synchronized(lock) { // make thread-safe (hopefully!) synchronized (lock) { // make thread-safe (hopefully!)
while((intch = read()) != -1) while ((intch = read()) != -1) {
{ if (prevWasCR && intch == LF) {
if (prevWasCR && intch == LF) { return sb.substring(0, sb.length() - 1);
return sb.substring(0, sb.length()-1);
}
if (intch == CR) {
prevWasCR = true;
} else {
prevWasCR = false;
}
sb.append((char) intch);
}
} }
String string = sb.toString(); if (intch == CR) {
if (string.length() == 0) { // immediate EOF prevWasCR = true;
return null; } else {
prevWasCR = false;
} }
return string; sb.append((char) intch);
}
}
String string = sb.toString();
if (string.length() == 0) { // immediate EOF
return null;
} }
return string;
}
} }

@ -32,87 +32,80 @@ import org.apache.commons.net.util.ListenerList;
* reasons and also because multiple listeners cannot be registered given * reasons and also because multiple listeners cannot be registered given
* that the methods are static. * that the methods are static.
* *
* * @version $Id: CopyStreamAdapter.java 1741829 2016-05-01 00:24:44Z sebb $
* @see CopyStreamEvent * @see CopyStreamEvent
* @see CopyStreamListener * @see CopyStreamListener
* @see Util * @see Util
* @version $Id: CopyStreamAdapter.java 1741829 2016-05-01 00:24:44Z sebb $
*/ */
public class CopyStreamAdapter implements CopyStreamListener public class CopyStreamAdapter implements CopyStreamListener {
{ private final ListenerList internalListeners;
private final ListenerList internalListeners;
/** /**
* Creates a new copyStreamAdapter. * Creates a new copyStreamAdapter.
*/ */
public CopyStreamAdapter() public CopyStreamAdapter() {
{ internalListeners = new ListenerList();
internalListeners = new ListenerList(); }
}
/** /**
* This method is invoked by a CopyStreamEvent source after copying * This method is invoked by a CopyStreamEvent source after copying
* a block of bytes from a stream. The CopyStreamEvent will contain * a block of bytes from a stream. The CopyStreamEvent will contain
* the total number of bytes transferred so far and the number of bytes * the total number of bytes transferred so far and the number of bytes
* transferred in the last write. The CopyStreamAdapater will relay * transferred in the last write. The CopyStreamAdapater will relay
* the event to all of its registered listeners, listing itself as the * the event to all of its registered listeners, listing itself as the
* source of the event. * source of the event.
* @param event The CopyStreamEvent fired by the copying of a block of *
* bytes. * @param event The CopyStreamEvent fired by the copying of a block of
*/ * bytes.
@Override */
public void bytesTransferred(CopyStreamEvent event) @Override public void bytesTransferred(CopyStreamEvent event) {
{ for (EventListener listener : internalListeners) {
for (EventListener listener : internalListeners) ((CopyStreamListener) (listener)).bytesTransferred(event);
{
((CopyStreamListener) (listener)).bytesTransferred(event);
}
} }
}
/** /**
* This method is not part of the JavaBeans model and is used by the * This method is not part of the JavaBeans model and is used by the
* static methods in the org.apache.commons.io.Util class for efficiency. * static methods in the org.apache.commons.io.Util class for efficiency.
* It is invoked after a block of bytes to inform the listener of the * It is invoked after a block of bytes to inform the listener of the
* transfer. The CopyStreamAdapater will create a CopyStreamEvent * transfer. The CopyStreamAdapater will create a CopyStreamEvent
* from the arguments and relay the event to all of its registered * from the arguments and relay the event to all of its registered
* listeners, listing itself as the source of the event. * listeners, listing itself as the source of the event.
* @param totalBytesTransferred The total number of bytes transferred *
* so far by the copy operation. * @param totalBytesTransferred The total number of bytes transferred
* @param bytesTransferred The number of bytes copied by the most recent * so far by the copy operation.
* write. * @param bytesTransferred The number of bytes copied by the most recent
* @param streamSize The number of bytes in the stream being copied. * write.
* This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if * @param streamSize The number of bytes in the stream being copied.
* the size is unknown. * This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if
*/ * the size is unknown.
@Override */
public void bytesTransferred(long totalBytesTransferred, @Override public void bytesTransferred(long totalBytesTransferred, int bytesTransferred,
int bytesTransferred, long streamSize) long streamSize) {
{ for (EventListener listener : internalListeners) {
for (EventListener listener : internalListeners) ((CopyStreamListener) (listener)).bytesTransferred(totalBytesTransferred, bytesTransferred,
{ streamSize);
((CopyStreamListener) (listener)).bytesTransferred(
totalBytesTransferred, bytesTransferred, streamSize);
}
} }
}
/** /**
* Registers a CopyStreamListener to receive CopyStreamEvents. * Registers a CopyStreamListener to receive CopyStreamEvents.
* Although this method is not declared to be synchronized, it is * Although this method is not declared to be synchronized, it is
* implemented in a thread safe manner. * implemented in a thread safe manner.
* @param listener The CopyStreamlistener to register. *
*/ * @param listener The CopyStreamlistener to register.
public void addCopyStreamListener(CopyStreamListener listener) */
{ public void addCopyStreamListener(CopyStreamListener listener) {
internalListeners.addListener(listener); internalListeners.addListener(listener);
} }
/** /**
* Unregisters a CopyStreamListener. Although this method is not * Unregisters a CopyStreamListener. Although this method is not
* synchronized, it is implemented in a thread safe manner. * synchronized, it is implemented in a thread safe manner.
* @param listener The CopyStreamlistener to unregister. *
*/ * @param listener The CopyStreamlistener to unregister.
public void removeCopyStreamListener(CopyStreamListener listener) */
{ public void removeCopyStreamListener(CopyStreamListener listener) {
internalListeners.removeListener(listener); internalListeners.removeListener(listener);
} }
} }

@ -25,87 +25,89 @@ import java.util.EventObject;
* transferred by the write triggering the event as well as the total * transferred by the write triggering the event as well as the total
* number of bytes transferred so far by the copy operation. * number of bytes transferred so far by the copy operation.
* *
* * @version $Id: CopyStreamEvent.java 1652801 2015-01-18 17:10:05Z sebb $
* @see CopyStreamListener * @see CopyStreamListener
* @see CopyStreamAdapter * @see CopyStreamAdapter
* @see Util * @see Util
* @version $Id: CopyStreamEvent.java 1652801 2015-01-18 17:10:05Z sebb $
*/ */
public class CopyStreamEvent extends EventObject public class CopyStreamEvent extends EventObject {
{ private static final long serialVersionUID = -964927635655051867L;
private static final long serialVersionUID = -964927635655051867L;
/** /**
* Constant used to indicate the stream size is unknown. * Constant used to indicate the stream size is unknown.
*/ */
public static final long UNKNOWN_STREAM_SIZE = -1; public static final long UNKNOWN_STREAM_SIZE = -1;
private final int bytesTransferred; private final int bytesTransferred;
private final long totalBytesTransferred; private final long totalBytesTransferred;
private final long streamSize; private final long streamSize;
/** /**
* Creates a new CopyStreamEvent instance. * Creates a new CopyStreamEvent instance.
* @param source The source of the event. *
* @param totalBytesTransferred The total number of bytes transferred so * @param source The source of the event.
* far during a copy operation. * @param totalBytesTransferred The total number of bytes transferred so
* @param bytesTransferred The number of bytes transferred during the * far during a copy operation.
* write that triggered the CopyStreamEvent. * @param bytesTransferred The number of bytes transferred during the
* @param streamSize The number of bytes in the stream being copied. * write that triggered the CopyStreamEvent.
* This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the * @param streamSize The number of bytes in the stream being copied.
* size is unknown. * This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the
*/ * size is unknown.
public CopyStreamEvent(Object source, long totalBytesTransferred, */
int bytesTransferred, long streamSize) public CopyStreamEvent(Object source, long totalBytesTransferred, int bytesTransferred,
{ long streamSize) {
super(source); super(source);
this.bytesTransferred = bytesTransferred; this.bytesTransferred = bytesTransferred;
this.totalBytesTransferred = totalBytesTransferred; this.totalBytesTransferred = totalBytesTransferred;
this.streamSize = streamSize; this.streamSize = streamSize;
} }
/** /**
* Returns the number of bytes transferred by the write that triggered * Returns the number of bytes transferred by the write that triggered
* the event. * the event.
* @return The number of bytes transferred by the write that triggered *
* the vent. * @return The number of bytes transferred by the write that triggered
*/ * the vent.
public int getBytesTransferred() */
{ public int getBytesTransferred() {
return bytesTransferred; return bytesTransferred;
} }
/** /**
* Returns the total number of bytes transferred so far by the copy * Returns the total number of bytes transferred so far by the copy
* operation. * operation.
* @return The total number of bytes transferred so far by the copy *
* operation. * @return The total number of bytes transferred so far by the copy
*/ * operation.
public long getTotalBytesTransferred() */
{ public long getTotalBytesTransferred() {
return totalBytesTransferred; return totalBytesTransferred;
} }
/** /**
* Returns the size of the stream being copied. * Returns the size of the stream being copied.
* This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the * This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the
* size is unknown. * size is unknown.
* @return The size of the stream being copied. *
*/ * @return The size of the stream being copied.
public long getStreamSize() */
{ public long getStreamSize() {
return streamSize; return streamSize;
} }
/** /**
* @since 3.0 * @since 3.0
*/ */
@Override @Override public String toString() {
public String toString(){ return getClass().getName()
return getClass().getName() + "[source=" + source + "[source="
+ ", total=" + totalBytesTransferred + source
+ ", bytes=" + bytesTransferred + ", total="
+ ", size=" + streamSize + totalBytesTransferred
+ ", bytes="
+ bytesTransferred
+ ", size="
+ streamSize
+ "]"; + "]";
} }
} }

@ -24,48 +24,46 @@ import java.io.IOException;
* copyStream() methods. It stores the number of bytes confirmed to * copyStream() methods. It stores the number of bytes confirmed to
* have been transferred before an I/O error as well as the IOException * have been transferred before an I/O error as well as the IOException
* responsible for the failure of a copy operation. * responsible for the failure of a copy operation.
* @see Util *
* @version $Id: CopyStreamException.java 1299238 2012-03-10 17:12:28Z sebb $ * @version $Id: CopyStreamException.java 1299238 2012-03-10 17:12:28Z sebb $
* @see Util
*/ */
public class CopyStreamException extends IOException public class CopyStreamException extends IOException {
{ private static final long serialVersionUID = -2602899129433221532L;
private static final long serialVersionUID = -2602899129433221532L;
private final long totalBytesTransferred; private final long totalBytesTransferred;
/** /**
* Creates a new CopyStreamException instance. * Creates a new CopyStreamException instance.
* @param message A message describing the error. *
* @param bytesTransferred The total number of bytes transferred before * @param message A message describing the error.
* an exception was thrown in a copy operation. * @param bytesTransferred The total number of bytes transferred before
* @param exception The IOException thrown during a copy operation. * an exception was thrown in a copy operation.
*/ * @param exception The IOException thrown during a copy operation.
public CopyStreamException(String message, */
long bytesTransferred, public CopyStreamException(String message, long bytesTransferred, IOException exception) {
IOException exception) super(message);
{ initCause(exception); // merge this into super() call once we need 1.6+
super(message); totalBytesTransferred = bytesTransferred;
initCause(exception); // merge this into super() call once we need 1.6+ }
totalBytesTransferred = bytesTransferred;
}
/** /**
* Returns the total number of bytes confirmed to have * Returns the total number of bytes confirmed to have
* been transferred by a failed copy operation. * been transferred by a failed copy operation.
* @return The total number of bytes confirmed to have *
* been transferred by a failed copy operation. * @return The total number of bytes confirmed to have
*/ * been transferred by a failed copy operation.
public long getTotalBytesTransferred() */
{ public long getTotalBytesTransferred() {
return totalBytesTransferred; return totalBytesTransferred;
} }
/** /**
* Returns the IOException responsible for the failure of a copy operation. * Returns the IOException responsible for the failure of a copy operation.
* @return The IOException responsible for the failure of a copy operation. *
*/ * @return The IOException responsible for the failure of a copy operation.
public IOException getIOException() */
{ public IOException getIOException() {
return (IOException) getCause(); // cast is OK because it was initialised with an IOException return (IOException) getCause(); // cast is OK because it was initialised with an IOException
} }
} }

@ -33,37 +33,36 @@ import java.util.EventListener;
* JavaBean event model, which is why the hooks have been included from the * JavaBean event model, which is why the hooks have been included from the
* beginning. * beginning.
* *
* * @version $Id: CopyStreamListener.java 1652801 2015-01-18 17:10:05Z sebb $
* @see CopyStreamEvent * @see CopyStreamEvent
* @see CopyStreamAdapter * @see CopyStreamAdapter
* @see Util * @see Util
* @version $Id: CopyStreamListener.java 1652801 2015-01-18 17:10:05Z sebb $
*/ */
public interface CopyStreamListener extends EventListener public interface CopyStreamListener extends EventListener {
{ /**
/** * This method is invoked by a CopyStreamEvent source after copying
* This method is invoked by a CopyStreamEvent source after copying * a block of bytes from a stream. The CopyStreamEvent will contain
* a block of bytes from a stream. The CopyStreamEvent will contain * the total number of bytes transferred so far and the number of bytes
* the total number of bytes transferred so far and the number of bytes * transferred in the last write.
* transferred in the last write. *
* @param event The CopyStreamEvent fired by the copying of a block of * @param event The CopyStreamEvent fired by the copying of a block of
* bytes. * bytes.
*/ */
public void bytesTransferred(CopyStreamEvent event); public void bytesTransferred(CopyStreamEvent event);
/** /**
* This method is not part of the JavaBeans model and is used by the * This method is not part of the JavaBeans model and is used by the
* static methods in the org.apache.commons.io.Util class for efficiency. * static methods in the org.apache.commons.io.Util class for efficiency.
* It is invoked after a block of bytes to inform the listener of the * It is invoked after a block of bytes to inform the listener of the
* transfer. * transfer.
* @param totalBytesTransferred The total number of bytes transferred *
* so far by the copy operation. * @param totalBytesTransferred The total number of bytes transferred
* @param bytesTransferred The number of bytes copied by the most recent * so far by the copy operation.
* write. * @param bytesTransferred The number of bytes copied by the most recent
* @param streamSize The number of bytes in the stream being copied. * write.
* This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if * @param streamSize The number of bytes in the stream being copied.
* the size is unknown. * This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if
*/ * the size is unknown.
public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize); */
public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize);
} }

@ -35,217 +35,204 @@ import java.io.Reader;
* Note: versions since 3.0 extend BufferedReader rather than Reader, * Note: versions since 3.0 extend BufferedReader rather than Reader,
* and no longer change the CRLF into the local EOL. Also only DOT CR LF * and no longer change the CRLF into the local EOL. Also only DOT CR LF
* acts as EOF. * acts as EOF.
*
* @version $Id: DotTerminatedMessageReader.java 1747119 2016-06-07 02:22:24Z ggregory $ * @version $Id: DotTerminatedMessageReader.java 1747119 2016-06-07 02:22:24Z ggregory $
*/ */
public final class DotTerminatedMessageReader extends BufferedReader public final class DotTerminatedMessageReader extends BufferedReader {
{ private static final char LF = '\n';
private static final char LF = '\n'; private static final char CR = '\r';
private static final char CR = '\r'; private static final int DOT = '.';
private static final int DOT = '.';
private boolean atBeginning; private boolean atBeginning;
private boolean eof; private boolean eof;
private boolean seenCR; // was last character CR? private boolean seenCR; // was last character CR?
/** /**
* Creates a DotTerminatedMessageReader that wraps an existing Reader * Creates a DotTerminatedMessageReader that wraps an existing Reader
* input source. * input source.
* @param reader The Reader input source containing the message. *
*/ * @param reader The Reader input source containing the message.
public DotTerminatedMessageReader(Reader reader) */
{ public DotTerminatedMessageReader(Reader reader) {
super(reader); super(reader);
// Assumes input is at start of message // Assumes input is at start of message
atBeginning = true; atBeginning = true;
eof = false; eof = false;
} }
/** /**
* Reads and returns the next character in the message. If the end of the * Reads and returns the next character in the message. If the end of the
* message has been reached, returns -1. Note that a call to this method * message has been reached, returns -1. Note that a call to this method
* may result in multiple reads from the underlying input stream to decode * may result in multiple reads from the underlying input stream to decode
* the message properly (removing doubled dots and so on). All of * the message properly (removing doubled dots and so on). All of
* this is transparent to the programmer and is only mentioned for * this is transparent to the programmer and is only mentioned for
* completeness. * completeness.
* @return The next character in the message. Returns -1 if the end of the *
* message has been reached. * @return The next character in the message. Returns -1 if the end of the
* @throws IOException If an error occurs while reading the underlying * message has been reached.
* stream. * @throws IOException If an error occurs while reading the underlying
*/ * stream.
@Override */
public int read() throws IOException { @Override public int read() throws IOException {
synchronized (lock) { synchronized (lock) {
if (eof) { if (eof) {
return -1; // Don't allow read past EOF return -1; // Don't allow read past EOF
}
int chint = super.read();
if (chint == -1) { // True EOF
eof = true;
return -1;
}
if (atBeginning) {
atBeginning = false;
if (chint == DOT) { // Have DOT
mark(2); // need to check for CR LF or DOT
chint = super.read();
if (chint == -1) { // Should not happen
// new Throwable("Trailing DOT").printStackTrace();
eof = true;
return DOT; // return the trailing DOT
}
if (chint == DOT) { // Have DOT DOT
// no need to reset as we want to lose the first DOT
return chint; // i.e. DOT
}
if (chint == CR) { // Have DOT CR
chint = super.read();
if (chint == -1) { // Still only DOT CR - should not happen
//new Throwable("Trailing DOT CR").printStackTrace();
reset(); // So CR is picked up next time
return DOT; // return the trailing DOT
} }
int chint = super.read(); if (chint == LF) { // DOT CR LF
if (chint == -1) { // True EOF atBeginning = true;
eof = true; eof = true;
return -1; // Do we need to clear the mark somehow?
return -1;
} }
if (atBeginning) { }
atBeginning = false; // Should not happen - lone DOT at beginning
if (chint == DOT) { // Have DOT //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace();
mark(2); // need to check for CR LF or DOT reset();
chint = super.read(); return DOT;
if (chint == -1) { // Should not happen } // have DOT
// new Throwable("Trailing DOT").printStackTrace(); } // atBeginning
eof = true;
return DOT; // return the trailing DOT
}
if (chint == DOT) { // Have DOT DOT
// no need to reset as we want to lose the first DOT
return chint; // i.e. DOT
}
if (chint == CR) { // Have DOT CR
chint = super.read();
if (chint == -1) { // Still only DOT CR - should not happen
//new Throwable("Trailing DOT CR").printStackTrace();
reset(); // So CR is picked up next time
return DOT; // return the trailing DOT
}
if (chint == LF) { // DOT CR LF
atBeginning = true;
eof = true;
// Do we need to clear the mark somehow?
return -1;
}
}
// Should not happen - lone DOT at beginning
//new Throwable("Lone DOT followed by "+(char)chint).printStackTrace();
reset();
return DOT;
} // have DOT
} // atBeginning
// Handle CRLF in normal flow // Handle CRLF in normal flow
if (seenCR) { if (seenCR) {
seenCR = false; seenCR = false;
if (chint == LF) { if (chint == LF) {
atBeginning = true; atBeginning = true;
}
}
if (chint == CR) {
seenCR = true;
}
return chint;
} }
}
if (chint == CR) {
seenCR = true;
}
return chint;
} }
}
/**
* Reads the next characters from the message into an array and
* returns the number of characters read. Returns -1 if the end of the
* message has been reached.
*
* @param buffer The character array in which to store the characters.
* @return The number of characters read. Returns -1 if the
* end of the message has been reached.
* @throws IOException If an error occurs in reading the underlying
* stream.
*/
@Override public int read(char[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
/** /**
* Reads the next characters from the message into an array and * Reads the next characters from the message into an array and
* returns the number of characters read. Returns -1 if the end of the * returns the number of characters read. Returns -1 if the end of the
* message has been reached. * message has been reached. The characters are stored in the array
* @param buffer The character array in which to store the characters. * starting from the given offset and up to the length specified.
* @return The number of characters read. Returns -1 if the *
* end of the message has been reached. * @param buffer The character array in which to store the characters.
* @throws IOException If an error occurs in reading the underlying * @param offset The offset into the array at which to start storing
* stream. * characters.
*/ * @param length The number of characters to read.
@Override * @return The number of characters read. Returns -1 if the
public int read(char[] buffer) throws IOException * end of the message has been reached.
{ * @throws IOException If an error occurs in reading the underlying
return read(buffer, 0, buffer.length); * stream.
*/
@Override public int read(char[] buffer, int offset, int length) throws IOException {
if (length < 1) {
return 0;
} }
int ch;
synchronized (lock) {
if ((ch = read()) == -1) {
return -1;
}
/** int off = offset;
* Reads the next characters from the message into an array and
* returns the number of characters read. Returns -1 if the end of the
* message has been reached. The characters are stored in the array
* starting from the given offset and up to the length specified.
* @param buffer The character array in which to store the characters.
* @param offset The offset into the array at which to start storing
* characters.
* @param length The number of characters to read.
* @return The number of characters read. Returns -1 if the
* end of the message has been reached.
* @throws IOException If an error occurs in reading the underlying
* stream.
*/
@Override
public int read(char[] buffer, int offset, int length) throws IOException
{
if (length < 1)
{
return 0;
}
int ch;
synchronized (lock)
{
if ((ch = read()) == -1)
{
return -1;
}
int off = offset; do {
buffer[offset++] = (char) ch;
} while (--length > 0 && (ch = read()) != -1);
do return (offset - off);
{
buffer[offset++] = (char) ch;
}
while (--length > 0 && (ch = read()) != -1);
return (offset - off);
}
} }
}
/** /**
* Closes the message for reading. This doesn't actually close the * Closes the message for reading. This doesn't actually close the
* underlying stream. The underlying stream may still be used for * underlying stream. The underlying stream may still be used for
* communicating with the server and therefore is not closed. * communicating with the server and therefore is not closed.
* <p> * <p>
* If the end of the message has not yet been reached, this method * If the end of the message has not yet been reached, this method
* will read the remainder of the message until it reaches the end, * will read the remainder of the message until it reaches the end,
* so that the underlying stream may continue to be used properly * so that the underlying stream may continue to be used properly
* for communicating with the server. If you do not fully read * for communicating with the server. If you do not fully read
* a message, you MUST close it, otherwise your program will likely * a message, you MUST close it, otherwise your program will likely
* hang or behave improperly. * hang or behave improperly.
* @throws IOException If an error occurs while reading the *
* underlying stream. * @throws IOException If an error occurs while reading the
*/ * underlying stream.
@Override */
public void close() throws IOException @Override public void close() throws IOException {
{ synchronized (lock) {
synchronized (lock) if (!eof) {
{ while (read() != -1) {
if (!eof) // read to EOF
{
while (read() != -1)
{
// read to EOF
}
}
eof = true;
atBeginning = false;
} }
}
eof = true;
atBeginning = false;
} }
}
/** /**
* Read a line of text. * Read a line of text.
* A line is considered to be terminated by carriage return followed immediately by a linefeed. * A line is considered to be terminated by carriage return followed immediately by a linefeed.
* This contrasts with BufferedReader which also allows other combinations. * This contrasts with BufferedReader which also allows other combinations.
* @since 3.0 *
*/ * @since 3.0
@Override */
public String readLine() throws IOException { @Override public String readLine() throws IOException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int intch; int intch;
synchronized(lock) { // make thread-safe (hopefully!) synchronized (lock) { // make thread-safe (hopefully!)
while((intch = read()) != -1) while ((intch = read()) != -1) {
{ if (intch == LF && atBeginning) {
if (intch == LF && atBeginning) { return sb.substring(0, sb.length() - 1);
return sb.substring(0, sb.length()-1);
}
sb.append((char) intch);
}
}
String string = sb.toString();
if (string.length() == 0) { // immediate EOF
return null;
} }
// Should not happen - EOF without CRLF sb.append((char) intch);
//new Throwable(string).printStackTrace(); }
return string; }
String string = sb.toString();
if (string.length() == 0) { // immediate EOF
return null;
} }
// Should not happen - EOF without CRLF
//new Throwable(string).printStackTrace();
return string;
}
} }

@ -36,184 +36,154 @@ import java.io.Writer;
* *
***/ ***/
public final class DotTerminatedMessageWriter extends Writer public final class DotTerminatedMessageWriter extends Writer {
{ private static final int __NOTHING_SPECIAL_STATE = 0;
private static final int __NOTHING_SPECIAL_STATE = 0; private static final int __LAST_WAS_CR_STATE = 1;
private static final int __LAST_WAS_CR_STATE = 1; private static final int __LAST_WAS_NL_STATE = 2;
private static final int __LAST_WAS_NL_STATE = 2;
private int __state;
private int __state; private Writer __output;
private Writer __output;
/***
* Creates a DotTerminatedMessageWriter that wraps an existing Writer
/*** * output destination.
* Creates a DotTerminatedMessageWriter that wraps an existing Writer *
* output destination. * @param output The Writer output destination to write the message.
* ***/
* @param output The Writer output destination to write the message. public DotTerminatedMessageWriter(Writer output) {
***/ super(output);
public DotTerminatedMessageWriter(Writer output) __output = output;
{ __state = __NOTHING_SPECIAL_STATE;
super(output); }
__output = output;
__state = __NOTHING_SPECIAL_STATE; /***
* Writes a character to the output. Note that a call to this method
* may result in multiple writes to the underling Writer in order to
* convert naked linefeeds to NETASCII line separators and to double
* line-leading periods. This is transparent to the programmer and
* is only mentioned for completeness.
*
* @param ch The character to write.
* @throws IOException If an error occurs while writing to the
* underlying output.
***/
@Override public void write(int ch) throws IOException {
synchronized (lock) {
switch (ch) {
case '\r':
__state = __LAST_WAS_CR_STATE;
__output.write('\r');
return;
case '\n':
if (__state != __LAST_WAS_CR_STATE) {
__output.write('\r');
}
__output.write('\n');
__state = __LAST_WAS_NL_STATE;
return;
case '.':
// Double the dot at the beginning of a line
if (__state == __LAST_WAS_NL_STATE) {
__output.write('.');
}
//$FALL-THROUGH$
default:
__state = __NOTHING_SPECIAL_STATE;
__output.write(ch);
return;
}
} }
}
/*** /***
* Writes a character to the output. Note that a call to this method * Writes a number of characters from a character array to the output
* may result in multiple writes to the underling Writer in order to * starting from a given offset.
* convert naked linefeeds to NETASCII line separators and to double *
* line-leading periods. This is transparent to the programmer and * @param buffer The character array to write.
* is only mentioned for completeness. * @param offset The offset into the array at which to start copying data.
* * @param length The number of characters to write.
* @param ch The character to write. * @throws IOException If an error occurs while writing to the underlying
* @throws IOException If an error occurs while writing to the * output.
* underlying output. ***/
***/ @Override public void write(char[] buffer, int offset, int length) throws IOException {
@Override synchronized (lock) {
public void write(int ch) throws IOException while (length-- > 0) {
{ write(buffer[offset++]);
synchronized (lock) }
{
switch (ch)
{
case '\r':
__state = __LAST_WAS_CR_STATE;
__output.write('\r');
return ;
case '\n':
if (__state != __LAST_WAS_CR_STATE) {
__output.write('\r');
}
__output.write('\n');
__state = __LAST_WAS_NL_STATE;
return ;
case '.':
// Double the dot at the beginning of a line
if (__state == __LAST_WAS_NL_STATE) {
__output.write('.');
}
//$FALL-THROUGH$
default:
__state = __NOTHING_SPECIAL_STATE;
__output.write(ch);
return ;
}
}
}
/***
* Writes a number of characters from a character array to the output
* starting from a given offset.
*
* @param buffer The character array to write.
* @param offset The offset into the array at which to start copying data.
* @param length The number of characters to write.
* @throws IOException If an error occurs while writing to the underlying
* output.
***/
@Override
public void write(char[] buffer, int offset, int length) throws IOException
{
synchronized (lock)
{
while (length-- > 0) {
write(buffer[offset++]);
}
}
} }
}
/*** /***
* Writes a character array to the output. * Writes a character array to the output.
* *
* @param buffer The character array to write. * @param buffer The character array to write.
* @throws IOException If an error occurs while writing to the underlying * @throws IOException If an error occurs while writing to the underlying
* output. * output.
***/ ***/
@Override @Override public void write(char[] buffer) throws IOException {
public void write(char[] buffer) throws IOException write(buffer, 0, buffer.length);
{ }
write(buffer, 0, buffer.length);
/***
* Writes a String to the output.
*
* @param string The String to write.
* @throws IOException If an error occurs while writing to the underlying
* output.
***/
@Override public void write(String string) throws IOException {
write(string.toCharArray());
}
/***
* Writes part of a String to the output starting from a given offset.
*
* @param string The String to write.
* @param offset The offset into the String at which to start copying data.
* @param length The number of characters to write.
* @throws IOException If an error occurs while writing to the underlying
* output.
***/
@Override public void write(String string, int offset, int length) throws IOException {
write(string.toCharArray(), offset, length);
}
/***
* Flushes the underlying output, writing all buffered output.
*
* @throws IOException If an error occurs while writing to the underlying
* output.
***/
@Override public void flush() throws IOException {
synchronized (lock) {
__output.flush();
} }
}
/*** /***
* Writes a String to the output. * Flushes the underlying output, writing all buffered output, but doesn't
* * actually close the underlying stream. The underlying stream may still
* @param string The String to write. * be used for communicating with the server and therefore is not closed.
* @throws IOException If an error occurs while writing to the underlying *
* output. * @throws IOException If an error occurs while writing to the underlying
***/ * output or closing the Writer.
@Override ***/
public void write(String string) throws IOException @Override public void close() throws IOException {
{ synchronized (lock) {
write(string.toCharArray()); if (__output == null) {
return;
}
if (__state == __LAST_WAS_CR_STATE) {
__output.write('\n');
} else if (__state != __LAST_WAS_NL_STATE) {
__output.write("\r\n");
}
__output.write(".\r\n");
__output.flush();
__output = null;
} }
}
/***
* Writes part of a String to the output starting from a given offset.
*
* @param string The String to write.
* @param offset The offset into the String at which to start copying data.
* @param length The number of characters to write.
* @throws IOException If an error occurs while writing to the underlying
* output.
***/
@Override
public void write(String string, int offset, int length) throws IOException
{
write(string.toCharArray(), offset, length);
}
/***
* Flushes the underlying output, writing all buffered output.
*
* @throws IOException If an error occurs while writing to the underlying
* output.
***/
@Override
public void flush() throws IOException
{
synchronized (lock)
{
__output.flush();
}
}
/***
* Flushes the underlying output, writing all buffered output, but doesn't
* actually close the underlying stream. The underlying stream may still
* be used for communicating with the server and therefore is not closed.
*
* @throws IOException If an error occurs while writing to the underlying
* output or closing the Writer.
***/
@Override
public void close() throws IOException
{
synchronized (lock)
{
if (__output == null) {
return ;
}
if (__state == __LAST_WAS_CR_STATE) {
__output.write('\n');
} else if (__state != __LAST_WAS_NL_STATE) {
__output.write("\r\n");
}
__output.write(".\r\n");
__output.flush();
__output = null;
}
}
} }

@ -32,190 +32,165 @@ import java.io.UnsupportedEncodingException;
* *
***/ ***/
public final class FromNetASCIIInputStream extends PushbackInputStream public final class FromNetASCIIInputStream extends PushbackInputStream {
{ static final boolean _noConversionRequired;
static final boolean _noConversionRequired; static final String _lineSeparator;
static final String _lineSeparator; static final byte[] _lineSeparatorBytes;
static final byte[] _lineSeparatorBytes;
static {
static { _lineSeparator = System.getProperty("line.separator");
_lineSeparator = System.getProperty("line.separator"); _noConversionRequired = _lineSeparator.equals("\r\n");
_noConversionRequired = _lineSeparator.equals("\r\n"); try {
try { _lineSeparatorBytes = _lineSeparator.getBytes("US-ASCII");
_lineSeparatorBytes = _lineSeparator.getBytes("US-ASCII"); } catch (UnsupportedEncodingException e) {
} catch (UnsupportedEncodingException e) { throw new RuntimeException("Broken JVM - cannot find US-ASCII charset!", e);
throw new RuntimeException("Broken JVM - cannot find US-ASCII charset!",e);
}
}
private int __length = 0;
/***
* Returns true if the NetASCII line separator differs from the system
* line separator, false if they are the same. This method is useful
* to determine whether or not you need to instantiate a
* FromNetASCIIInputStream object.
*
* @return True if the NETASCII line separator differs from the local
* system line separator, false if they are the same.
***/
public static final boolean isConversionRequired()
{
return !_noConversionRequired;
}
/***
* Creates a FromNetASCIIInputStream instance that wraps an existing
* InputStream.
* @param input the stream to wrap
***/
public FromNetASCIIInputStream(InputStream input)
{
super(input, _lineSeparatorBytes.length + 1);
} }
}
private int __read() throws IOException private int __length = 0;
{
int ch; /***
* Returns true if the NetASCII line separator differs from the system
* line separator, false if they are the same. This method is useful
* to determine whether or not you need to instantiate a
* FromNetASCIIInputStream object.
*
* @return True if the NETASCII line separator differs from the local
* system line separator, false if they are the same.
***/
public static final boolean isConversionRequired() {
return !_noConversionRequired;
}
/***
* Creates a FromNetASCIIInputStream instance that wraps an existing
* InputStream.
* @param input the stream to wrap
***/
public FromNetASCIIInputStream(InputStream input) {
super(input, _lineSeparatorBytes.length + 1);
}
private int __read() throws IOException {
int ch;
ch = super.read();
if (ch == '\r') {
ch = super.read();
if (ch == '\n') {
unread(_lineSeparatorBytes);
ch = super.read(); ch = super.read();
// This is a kluge for read(byte[], ...) to read the right amount
if (ch == '\r') --__length;
{ } else {
ch = super.read(); if (ch != -1) {
if (ch == '\n') unread(ch);
{
unread(_lineSeparatorBytes);
ch = super.read();
// This is a kluge for read(byte[], ...) to read the right amount
--__length;
}
else
{
if (ch != -1) {
unread(ch);
}
return '\r';
}
} }
return '\r';
return ch; }
} }
return ch;
/*** }
* Reads and returns the next byte in the stream. If the end of the
* message has been reached, returns -1. Note that a call to this method /***
* may result in multiple reads from the underlying input stream in order * Reads and returns the next byte in the stream. If the end of the
* to convert NETASCII line separators to the local line separator format. * message has been reached, returns -1. Note that a call to this method
* This is transparent to the programmer and is only mentioned for * may result in multiple reads from the underlying input stream in order
* completeness. * to convert NETASCII line separators to the local line separator format.
* * This is transparent to the programmer and is only mentioned for
* @return The next character in the stream. Returns -1 if the end of the * completeness.
* stream has been reached. *
* @throws IOException If an error occurs while reading the underlying * @return The next character in the stream. Returns -1 if the end of the
* stream. * stream has been reached.
***/ * @throws IOException If an error occurs while reading the underlying
@Override * stream.
public int read() throws IOException ***/
{ @Override public int read() throws IOException {
if (_noConversionRequired) { if (_noConversionRequired) {
return super.read(); return super.read();
}
return __read();
} }
return __read();
/*** }
* Reads the next number of bytes from the stream into an array and
* returns the number of bytes read. Returns -1 if the end of the /***
* stream has been reached. * Reads the next number of bytes from the stream into an array and
* * returns the number of bytes read. Returns -1 if the end of the
* @param buffer The byte array in which to store the data. * stream has been reached.
* @return The number of bytes read. Returns -1 if the *
* end of the message has been reached. * @param buffer The byte array in which to store the data.
* @throws IOException If an error occurs in reading the underlying * @return The number of bytes read. Returns -1 if the
* stream. * end of the message has been reached.
***/ * @throws IOException If an error occurs in reading the underlying
@Override * stream.
public int read(byte buffer[]) throws IOException ***/
{ @Override public int read(byte buffer[]) throws IOException {
return read(buffer, 0, buffer.length); return read(buffer, 0, buffer.length);
}
/***
* Reads the next number of bytes from the stream into an array and returns
* the number of bytes read. Returns -1 if the end of the
* message has been reached. The characters are stored in the array
* starting from the given offset and up to the length specified.
*
* @param buffer The byte array in which to store the data.
* @param offset The offset into the array at which to start storing data.
* @param length The number of bytes to read.
* @return The number of bytes read. Returns -1 if the
* end of the stream has been reached.
* @throws IOException If an error occurs while reading the underlying
* stream.
***/
@Override public int read(byte buffer[], int offset, int length) throws IOException {
if (_noConversionRequired) {
return super.read(buffer, offset, length);
} }
if (length < 1) {
return 0;
}
/*** int ch, off;
* Reads the next number of bytes from the stream into an array and returns
* the number of bytes read. Returns -1 if the end of the
* message has been reached. The characters are stored in the array
* starting from the given offset and up to the length specified.
*
* @param buffer The byte array in which to store the data.
* @param offset The offset into the array at which to start storing data.
* @param length The number of bytes to read.
* @return The number of bytes read. Returns -1 if the
* end of the stream has been reached.
* @throws IOException If an error occurs while reading the underlying
* stream.
***/
@Override
public int read(byte buffer[], int offset, int length) throws IOException
{
if (_noConversionRequired) {
return super.read(buffer, offset, length);
}
if (length < 1) {
return 0;
}
int ch, off;
ch = available();
__length = (length > ch ? ch : length);
// If nothing is available, block to read only one character ch = available();
if (__length < 1) {
__length = 1;
}
__length = (length > ch ? ch : length);
if ((ch = __read()) == -1) { // If nothing is available, block to read only one character
return -1; if (__length < 1) {
} __length = 1;
}
off = offset; if ((ch = __read()) == -1) {
return -1;
}
do off = offset;
{
buffer[offset++] = (byte)ch;
}
while (--__length > 0 && (ch = __read()) != -1);
do {
buffer[offset++] = (byte) ch;
} while (--__length > 0 && (ch = __read()) != -1);
return (offset - off); return (offset - off);
} }
// PushbackInputStream in JDK 1.1.3 returns the wrong thing
// TODO - can we delete this override now?
// PushbackInputStream in JDK 1.1.3 returns the wrong thing /***
// TODO - can we delete this override now? * Returns the number of bytes that can be read without blocking EXCEPT
/*** * when newline conversions have to be made somewhere within the
* Returns the number of bytes that can be read without blocking EXCEPT * available block of bytes. In other words, you really should not
* when newline conversions have to be made somewhere within the * rely on the value returned by this method if you are trying to avoid
* available block of bytes. In other words, you really should not * blocking.
* rely on the value returned by this method if you are trying to avoid ***/
* blocking. @Override public int available() throws IOException {
***/ if (in == null) {
@Override throw new IOException("Stream closed");
public int available() throws IOException
{
if (in == null) {
throw new IOException("Stream closed");
}
return (buf.length - pos) + in.available();
} }
return (buf.length - pos) + in.available();
}
} }

@ -36,140 +36,115 @@ import java.io.OutputStream;
* *
***/ ***/
public final class FromNetASCIIOutputStream extends FilterOutputStream public final class FromNetASCIIOutputStream extends FilterOutputStream {
{ private boolean __lastWasCR;
private boolean __lastWasCR;
/***
/*** * Creates a FromNetASCIIOutputStream instance that wraps an existing
* Creates a FromNetASCIIOutputStream instance that wraps an existing * OutputStream.
* OutputStream. *
* * @param output The OutputStream to wrap.
* @param output The OutputStream to wrap. ***/
***/ public FromNetASCIIOutputStream(OutputStream output) {
public FromNetASCIIOutputStream(OutputStream output) super(output);
{ __lastWasCR = false;
super(output); }
private void __write(int ch) throws IOException {
switch (ch) {
case '\r':
__lastWasCR = true;
// Don't write anything. We need to see if next one is linefeed
break;
case '\n':
if (__lastWasCR) {
out.write(FromNetASCIIInputStream._lineSeparatorBytes);
__lastWasCR = false;
break;
}
__lastWasCR = false; __lastWasCR = false;
} out.write('\n');
break;
default:
private void __write(int ch) throws IOException if (__lastWasCR) {
{ out.write('\r');
switch (ch) __lastWasCR = false;
{
case '\r':
__lastWasCR = true;
// Don't write anything. We need to see if next one is linefeed
break;
case '\n':
if (__lastWasCR)
{
out.write(FromNetASCIIInputStream._lineSeparatorBytes);
__lastWasCR = false;
break;
}
__lastWasCR = false;
out.write('\n');
break;
default:
if (__lastWasCR)
{
out.write('\r');
__lastWasCR = false;
}
out.write(ch);
break;
} }
out.write(ch);
break;
} }
}
/*** /***
* Writes a byte to the stream. Note that a call to this method * Writes a byte to the stream. Note that a call to this method
* might not actually write a byte to the underlying stream until a * might not actually write a byte to the underlying stream until a
* subsequent character is written, from which it can be determined if * subsequent character is written, from which it can be determined if
* a NETASCII line separator was encountered. * a NETASCII line separator was encountered.
* This is transparent to the programmer and is only mentioned for * This is transparent to the programmer and is only mentioned for
* completeness. * completeness.
* *
* @param ch The byte to write. * @param ch The byte to write.
* @throws IOException If an error occurs while writing to the underlying * @throws IOException If an error occurs while writing to the underlying
* stream. * stream.
***/ ***/
@Override @Override public synchronized void write(int ch) throws IOException {
public synchronized void write(int ch) if (FromNetASCIIInputStream._noConversionRequired) {
throws IOException out.write(ch);
{ return;
if (FromNetASCIIInputStream._noConversionRequired)
{
out.write(ch);
return ;
}
__write(ch);
} }
__write(ch);
/*** }
* Writes a byte array to the stream.
* /***
* @param buffer The byte array to write. * Writes a byte array to the stream.
* @throws IOException If an error occurs while writing to the underlying *
* stream. * @param buffer The byte array to write.
***/ * @throws IOException If an error occurs while writing to the underlying
@Override * stream.
public synchronized void write(byte buffer[]) ***/
throws IOException @Override public synchronized void write(byte buffer[]) throws IOException {
{ write(buffer, 0, buffer.length);
write(buffer, 0, buffer.length); }
/***
* Writes a number of bytes from a byte array to the stream starting from
* a given offset.
*
* @param buffer The byte array to write.
* @param offset The offset into the array at which to start copying data.
* @param length The number of bytes to write.
* @throws IOException If an error occurs while writing to the underlying
* stream.
***/
@Override public synchronized void write(byte buffer[], int offset, int length)
throws IOException {
if (FromNetASCIIInputStream._noConversionRequired) {
// FilterOutputStream method is very slow.
//super.write(buffer, offset, length);
out.write(buffer, offset, length);
return;
} }
while (length-- > 0) {
/*** __write(buffer[offset++]);
* Writes a number of bytes from a byte array to the stream starting from }
* a given offset. }
*
* @param buffer The byte array to write. /***
* @param offset The offset into the array at which to start copying data. * Closes the stream, writing all pending data.
* @param length The number of bytes to write. *
* @throws IOException If an error occurs while writing to the underlying * @throws IOException If an error occurs while closing the stream.
* stream. ***/
***/ @Override public synchronized void close() throws IOException {
@Override if (FromNetASCIIInputStream._noConversionRequired) {
public synchronized void write(byte buffer[], int offset, int length) super.close();
throws IOException return;
{
if (FromNetASCIIInputStream._noConversionRequired)
{
// FilterOutputStream method is very slow.
//super.write(buffer, offset, length);
out.write(buffer, offset, length);
return ;
}
while (length-- > 0) {
__write(buffer[offset++]);
}
} }
if (__lastWasCR) {
/*** out.write('\r');
* Closes the stream, writing all pending data.
*
* @throws IOException If an error occurs while closing the stream.
***/
@Override
public synchronized void close()
throws IOException
{
if (FromNetASCIIInputStream._noConversionRequired)
{
super.close();
return ;
}
if (__lastWasCR) {
out.write('\r');
}
super.close();
} }
super.close();
}
} }

@ -34,35 +34,31 @@ import java.net.Socket;
* @see SocketOutputStream * @see SocketOutputStream
***/ ***/
public class SocketInputStream extends FilterInputStream public class SocketInputStream extends FilterInputStream {
{ private final Socket __socket;
private final Socket __socket;
/*** /***
* Creates a SocketInputStream instance wrapping an input stream and * Creates a SocketInputStream instance wrapping an input stream and
* storing a reference to a socket that should be closed on closing * storing a reference to a socket that should be closed on closing
* the stream. * the stream.
* *
* @param socket The socket to close on closing the stream. * @param socket The socket to close on closing the stream.
* @param stream The input stream to wrap. * @param stream The input stream to wrap.
***/ ***/
public SocketInputStream(Socket socket, InputStream stream) public SocketInputStream(Socket socket, InputStream stream) {
{ super(stream);
super(stream); __socket = socket;
__socket = socket; }
}
/*** /***
* Closes the stream and immediately afterward closes the referenced * Closes the stream and immediately afterward closes the referenced
* socket. * socket.
* *
* @throws IOException If there is an error in closing the stream * @throws IOException If there is an error in closing the stream
* or socket. * or socket.
***/ ***/
@Override @Override public void close() throws IOException {
public void close() throws IOException super.close();
{ __socket.close();
super.close(); }
__socket.close();
}
} }

@ -34,55 +34,47 @@ import java.net.Socket;
* @see SocketInputStream * @see SocketInputStream
***/ ***/
public class SocketOutputStream extends FilterOutputStream public class SocketOutputStream extends FilterOutputStream {
{ private final Socket __socket;
private final Socket __socket;
/*** /***
* Creates a SocketOutputStream instance wrapping an output stream and * Creates a SocketOutputStream instance wrapping an output stream and
* storing a reference to a socket that should be closed on closing * storing a reference to a socket that should be closed on closing
* the stream. * the stream.
* *
* @param socket The socket to close on closing the stream. * @param socket The socket to close on closing the stream.
* @param stream The input stream to wrap. * @param stream The input stream to wrap.
***/ ***/
public SocketOutputStream(Socket socket, OutputStream stream) public SocketOutputStream(Socket socket, OutputStream stream) {
{ super(stream);
super(stream); __socket = socket;
__socket = socket; }
}
/***
* Writes a number of bytes from a byte array to the stream starting from
* a given offset. This method bypasses the equivalent method in
* FilterOutputStream because the FilterOutputStream implementation is
* very inefficient.
*
* @param buffer The byte array to write.
* @param offset The offset into the array at which to start copying data.
* @param length The number of bytes to write.
* @throws IOException If an error occurs while writing to the underlying
* stream.
***/
@Override public void write(byte buffer[], int offset, int length) throws IOException {
out.write(buffer, offset, length);
}
/*** /***
* Writes a number of bytes from a byte array to the stream starting from * Closes the stream and immediately afterward closes the referenced
* a given offset. This method bypasses the equivalent method in * socket.
* FilterOutputStream because the FilterOutputStream implementation is *
* very inefficient. * @throws IOException If there is an error in closing the stream
* * or socket.
* @param buffer The byte array to write. ***/
* @param offset The offset into the array at which to start copying data. @Override public void close() throws IOException {
* @param length The number of bytes to write. super.close();
* @throws IOException If an error occurs while writing to the underlying __socket.close();
* stream. }
***/
@Override
public void write(byte buffer[], int offset, int length) throws IOException
{
out.write(buffer, offset, length);
}
/***
* Closes the stream and immediately afterward closes the referenced
* socket.
*
* @throws IOException If there is an error in closing the stream
* or socket.
***/
@Override
public void close() throws IOException
{
super.close();
__socket.close();
}
} }

@ -32,154 +32,134 @@ import java.io.InputStream;
* *
***/ ***/
public final class ToNetASCIIInputStream extends FilterInputStream public final class ToNetASCIIInputStream extends FilterInputStream {
{ private static final int __NOTHING_SPECIAL = 0;
private static final int __NOTHING_SPECIAL = 0; private static final int __LAST_WAS_CR = 1;
private static final int __LAST_WAS_CR = 1; private static final int __LAST_WAS_NL = 2;
private static final int __LAST_WAS_NL = 2; private int __status;
private int __status;
/***
/*** * Creates a ToNetASCIIInputStream instance that wraps an existing
* Creates a ToNetASCIIInputStream instance that wraps an existing * InputStream.
* InputStream. *
* * @param input The InputStream to wrap.
* @param input The InputStream to wrap. ***/
***/ public ToNetASCIIInputStream(InputStream input) {
public ToNetASCIIInputStream(InputStream input) super(input);
{ __status = __NOTHING_SPECIAL;
super(input); }
__status = __NOTHING_SPECIAL;
/***
* Reads and returns the next byte in the stream. If the end of the
* message has been reached, returns -1.
*
* @return The next character in the stream. Returns -1 if the end of the
* stream has been reached.
* @throws IOException If an error occurs while reading the underlying
* stream.
***/
@Override public int read() throws IOException {
int ch;
if (__status == __LAST_WAS_NL) {
__status = __NOTHING_SPECIAL;
return '\n';
} }
ch = in.read();
/*** switch (ch) {
* Reads and returns the next byte in the stream. If the end of the case '\r':
* message has been reached, returns -1. __status = __LAST_WAS_CR;
* return '\r';
* @return The next character in the stream. Returns -1 if the end of the case '\n':
* stream has been reached. if (__status != __LAST_WAS_CR) {
* @throws IOException If an error occurs while reading the underlying __status = __LAST_WAS_NL;
* stream. return '\r';
***/
@Override
public int read() throws IOException
{
int ch;
if (__status == __LAST_WAS_NL)
{
__status = __NOTHING_SPECIAL;
return '\n';
}
ch = in.read();
switch (ch)
{
case '\r':
__status = __LAST_WAS_CR;
return '\r';
case '\n':
if (__status != __LAST_WAS_CR)
{
__status = __LAST_WAS_NL;
return '\r';
}
//$FALL-THROUGH$
default:
__status = __NOTHING_SPECIAL;
return ch;
} }
// statement not reached //$FALL-THROUGH$
//return ch; default:
__status = __NOTHING_SPECIAL;
return ch;
}
// statement not reached
//return ch;
}
/***
* Reads the next number of bytes from the stream into an array and
* returns the number of bytes read. Returns -1 if the end of the
* stream has been reached.
*
* @param buffer The byte array in which to store the data.
* @return The number of bytes read. Returns -1 if the
* end of the message has been reached.
* @throws IOException If an error occurs in reading the underlying
* stream.
***/
@Override public int read(byte buffer[]) throws IOException {
return read(buffer, 0, buffer.length);
}
/***
* Reads the next number of bytes from the stream into an array and returns
* the number of bytes read. Returns -1 if the end of the
* message has been reached. The characters are stored in the array
* starting from the given offset and up to the length specified.
*
* @param buffer The byte array in which to store the data.
* @param offset The offset into the array at which to start storing data.
* @param length The number of bytes to read.
* @return The number of bytes read. Returns -1 if the
* end of the stream has been reached.
* @throws IOException If an error occurs while reading the underlying
* stream.
***/
@Override public int read(byte buffer[], int offset, int length) throws IOException {
int ch, off;
if (length < 1) {
return 0;
} }
ch = available();
/*** if (length > ch) {
* Reads the next number of bytes from the stream into an array and length = ch;
* returns the number of bytes read. Returns -1 if the end of the
* stream has been reached.
*
* @param buffer The byte array in which to store the data.
* @return The number of bytes read. Returns -1 if the
* end of the message has been reached.
* @throws IOException If an error occurs in reading the underlying
* stream.
***/
@Override
public int read(byte buffer[]) throws IOException
{
return read(buffer, 0, buffer.length);
} }
// If nothing is available, block to read only one character
if (length < 1) {
length = 1;
}
/*** if ((ch = read()) == -1) {
* Reads the next number of bytes from the stream into an array and returns return -1;
* the number of bytes read. Returns -1 if the end of the }
* message has been reached. The characters are stored in the array
* starting from the given offset and up to the length specified.
*
* @param buffer The byte array in which to store the data.
* @param offset The offset into the array at which to start storing data.
* @param length The number of bytes to read.
* @return The number of bytes read. Returns -1 if the
* end of the stream has been reached.
* @throws IOException If an error occurs while reading the underlying
* stream.
***/
@Override
public int read(byte buffer[], int offset, int length) throws IOException
{
int ch, off;
if (length < 1) {
return 0;
}
ch = available(); off = offset;
if (length > ch) { do {
length = ch; buffer[offset++] = (byte) ch;
} } while (--length > 0 && (ch = read()) != -1);
// If nothing is available, block to read only one character return (offset - off);
if (length < 1) { }
length = 1;
}
if ((ch = read()) == -1) { /*** Returns false. Mark is not supported. ***/
return -1; @Override public boolean markSupported() {
} return false;
}
off = offset; @Override public int available() throws IOException {
int result;
do result = in.available();
{
buffer[offset++] = (byte)ch;
}
while (--length > 0 && (ch = read()) != -1);
return (offset - off); if (__status == __LAST_WAS_NL) {
return (result + 1);
} }
/*** Returns false. Mark is not supported. ***/ return result;
@Override }
public boolean markSupported()
{
return false;
}
@Override
public int available() throws IOException
{
int result;
result = in.available();
if (__status == __LAST_WAS_NL) {
return (result + 1);
}
return result;
}
} }

@ -32,89 +32,74 @@ import java.io.OutputStream;
* *
***/ ***/
public final class ToNetASCIIOutputStream extends FilterOutputStream public final class ToNetASCIIOutputStream extends FilterOutputStream {
{ private boolean __lastWasCR;
private boolean __lastWasCR;
/***
* Creates a ToNetASCIIOutputStream instance that wraps an existing
* OutputStream.
*
* @param output The OutputStream to wrap.
***/
public ToNetASCIIOutputStream(OutputStream output)
{
super(output);
__lastWasCR = false;
}
/***
* Creates a ToNetASCIIOutputStream instance that wraps an existing
* OutputStream.
*
* @param output The OutputStream to wrap.
***/
public ToNetASCIIOutputStream(OutputStream output) {
super(output);
__lastWasCR = false;
}
/*** /***
* Writes a byte to the stream. Note that a call to this method * Writes a byte to the stream. Note that a call to this method
* may result in multiple writes to the underlying input stream in order * may result in multiple writes to the underlying input stream in order
* to convert naked newlines to NETASCII line separators. * to convert naked newlines to NETASCII line separators.
* This is transparent to the programmer and is only mentioned for * This is transparent to the programmer and is only mentioned for
* completeness. * completeness.
* *
* @param ch The byte to write. * @param ch The byte to write.
* @throws IOException If an error occurs while writing to the underlying * @throws IOException If an error occurs while writing to the underlying
* stream. * stream.
***/ ***/
@Override @Override public synchronized void write(int ch) throws IOException {
public synchronized void write(int ch) switch (ch) {
throws IOException case '\r':
{ __lastWasCR = true;
switch (ch) out.write('\r');
{ return;
case '\r': case '\n':
__lastWasCR = true; if (!__lastWasCR) {
out.write('\r'); out.write('\r');
return ;
case '\n':
if (!__lastWasCR) {
out.write('\r');
}
//$FALL-THROUGH$
default:
__lastWasCR = false;
out.write(ch);
return ;
} }
//$FALL-THROUGH$
default:
__lastWasCR = false;
out.write(ch);
return;
} }
}
/***
* Writes a byte array to the stream.
*
* @param buffer The byte array to write.
* @throws IOException If an error occurs while writing to the underlying
* stream.
***/
@Override public synchronized void write(byte buffer[]) throws IOException {
write(buffer, 0, buffer.length);
}
/*** /***
* Writes a byte array to the stream. * Writes a number of bytes from a byte array to the stream starting from
* * a given offset.
* @param buffer The byte array to write. *
* @throws IOException If an error occurs while writing to the underlying * @param buffer The byte array to write.
* stream. * @param offset The offset into the array at which to start copying data.
***/ * @param length The number of bytes to write.
@Override * @throws IOException If an error occurs while writing to the underlying
public synchronized void write(byte buffer[]) * stream.
throws IOException ***/
{ @Override public synchronized void write(byte buffer[], int offset, int length)
write(buffer, 0, buffer.length); throws IOException {
} while (length-- > 0) {
write(buffer[offset++]);
/***
* Writes a number of bytes from a byte array to the stream starting from
* a given offset.
*
* @param buffer The byte array to write.
* @param offset The offset into the array at which to start copying data.
* @param length The number of bytes to write.
* @throws IOException If an error occurs while writing to the underlying
* stream.
***/
@Override
public synchronized void write(byte buffer[], int offset, int length)
throws IOException
{
while (length-- > 0) {
write(buffer[offset++]);
}
} }
}
} }

@ -35,356 +35,317 @@ import java.net.Socket;
* @see CopyStreamAdapter * @see CopyStreamAdapter
***/ ***/
public final class Util public final class Util {
{ /**
/** * The default buffer size ({@value}) used by
* The default buffer size ({@value}) used by * {@link #copyStream copyStream } and {@link #copyReader copyReader}
* {@link #copyStream copyStream } and {@link #copyReader copyReader} * and by the copyReader/copyStream methods if a zero or negative buffer size is supplied.
* and by the copyReader/copyStream methods if a zero or negative buffer size is supplied. */
*/ public static final int DEFAULT_COPY_BUFFER_SIZE = 1024;
public static final int DEFAULT_COPY_BUFFER_SIZE = 1024;
// Cannot be instantiated // Cannot be instantiated
private Util() private Util() {
{ } }
/***
* Copies the contents of an InputStream to an OutputStream using a
* copy buffer of a given size and notifies the provided
* CopyStreamListener of the progress of the copy operation by calling
* its bytesTransferred(long, int) method after each write to the
* destination. If you wish to notify more than one listener you should
* use a CopyStreamAdapter as the listener and register the additional
* listeners with the CopyStreamAdapter.
* <p>
* The contents of the InputStream are
* read until the end of the stream is reached, but neither the
* source nor the destination are closed. You must do this yourself
* outside of the method call. The number of bytes read/written is
* returned.
*
* @param source The source InputStream.
* @param dest The destination OutputStream.
* @param bufferSize The number of bytes to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @param streamSize The number of bytes in the stream being copied.
* Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown.
* Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
* @param listener The CopyStreamListener to notify of progress. If
* this parameter is null, notification is not attempted.
* @param flush Whether to flush the output stream after every
* write. This is necessary for interactive sessions that rely on
* buffered streams. If you don't flush, the data will stay in
* the stream buffer.
* @return number of bytes read/written
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyStream(InputStream source, OutputStream dest, int bufferSize,
long streamSize, CopyStreamListener listener, boolean flush) throws CopyStreamException {
int numBytes;
long total = 0;
byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];
/*** try {
* Copies the contents of an InputStream to an OutputStream using a while ((numBytes = source.read(buffer)) != -1) {
* copy buffer of a given size and notifies the provided // Technically, some read(byte[]) methods may return 0 and we cannot
* CopyStreamListener of the progress of the copy operation by calling // accept that as an indication of EOF.
* its bytesTransferred(long, int) method after each write to the
* destination. If you wish to notify more than one listener you should
* use a CopyStreamAdapter as the listener and register the additional
* listeners with the CopyStreamAdapter.
* <p>
* The contents of the InputStream are
* read until the end of the stream is reached, but neither the
* source nor the destination are closed. You must do this yourself
* outside of the method call. The number of bytes read/written is
* returned.
*
* @param source The source InputStream.
* @param dest The destination OutputStream.
* @param bufferSize The number of bytes to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @param streamSize The number of bytes in the stream being copied.
* Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown.
* Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
* @param listener The CopyStreamListener to notify of progress. If
* this parameter is null, notification is not attempted.
* @param flush Whether to flush the output stream after every
* write. This is necessary for interactive sessions that rely on
* buffered streams. If you don't flush, the data will stay in
* the stream buffer.
* @return number of bytes read/written
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyStream(InputStream source, OutputStream dest,
int bufferSize, long streamSize,
CopyStreamListener listener,
boolean flush)
throws CopyStreamException
{
int numBytes;
long total = 0;
byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];
try if (numBytes == 0) {
{ int singleByte = source.read();
while ((numBytes = source.read(buffer)) != -1) if (singleByte < 0) {
{ break;
// Technically, some read(byte[]) methods may return 0 and we cannot }
// accept that as an indication of EOF. dest.write(singleByte);
if (flush) {
if (numBytes == 0) dest.flush();
{ }
int singleByte = source.read(); ++total;
if (singleByte < 0) { if (listener != null) {
break; listener.bytesTransferred(total, 1, streamSize);
} }
dest.write(singleByte); continue;
if(flush) { }
dest.flush();
}
++total;
if (listener != null) {
listener.bytesTransferred(total, 1, streamSize);
}
continue;
}
dest.write(buffer, 0, numBytes); dest.write(buffer, 0, numBytes);
if(flush) { if (flush) {
dest.flush(); dest.flush();
}
total += numBytes;
if (listener != null) {
listener.bytesTransferred(total, numBytes, streamSize);
}
}
} }
catch (IOException e) total += numBytes;
{ if (listener != null) {
throw new CopyStreamException("IOException caught while copying.", listener.bytesTransferred(total, numBytes, streamSize);
total, e);
} }
}
return total; } catch (IOException e) {
} throw new CopyStreamException("IOException caught while copying.", total, e);
/***
* Copies the contents of an InputStream to an OutputStream using a
* copy buffer of a given size and notifies the provided
* CopyStreamListener of the progress of the copy operation by calling
* its bytesTransferred(long, int) method after each write to the
* destination. If you wish to notify more than one listener you should
* use a CopyStreamAdapter as the listener and register the additional
* listeners with the CopyStreamAdapter.
* <p>
* The contents of the InputStream are
* read until the end of the stream is reached, but neither the
* source nor the destination are closed. You must do this yourself
* outside of the method call. The number of bytes read/written is
* returned.
*
* @param source The source InputStream.
* @param dest The destination OutputStream.
* @param bufferSize The number of bytes to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @param streamSize The number of bytes in the stream being copied.
* Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown.
* Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
* @param listener The CopyStreamListener to notify of progress. If
* this parameter is null, notification is not attempted.
* @return number of bytes read/written
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyStream(InputStream source, OutputStream dest,
int bufferSize, long streamSize,
CopyStreamListener listener)
throws CopyStreamException
{
return copyStream(source, dest, bufferSize, streamSize, listener,
true);
} }
return total;
/*** }
* Copies the contents of an InputStream to an OutputStream using a
* copy buffer of a given size. The contents of the InputStream are
* read until the end of the stream is reached, but neither the
* source nor the destination are closed. You must do this yourself
* outside of the method call. The number of bytes read/written is
* returned.
*
* @param source The source InputStream.
* @param dest The destination OutputStream.
* @param bufferSize The number of bytes to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @return The number of bytes read/written in the copy operation.
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyStream(InputStream source, OutputStream dest,
int bufferSize)
throws CopyStreamException
{
return copyStream(source, dest, bufferSize,
CopyStreamEvent.UNKNOWN_STREAM_SIZE, null);
}
/*** /***
* Same as <code> copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code> * Copies the contents of an InputStream to an OutputStream using a
* @param source where to copy from * copy buffer of a given size and notifies the provided
* @param dest where to copy to * CopyStreamListener of the progress of the copy operation by calling
* @return number of bytes copied * its bytesTransferred(long, int) method after each write to the
* @throws CopyStreamException on error * destination. If you wish to notify more than one listener you should
***/ * use a CopyStreamAdapter as the listener and register the additional
public static final long copyStream(InputStream source, OutputStream dest) * listeners with the CopyStreamAdapter.
throws CopyStreamException * <p>
{ * The contents of the InputStream are
return copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); * read until the end of the stream is reached, but neither the
} * source nor the destination are closed. You must do this yourself
* outside of the method call. The number of bytes read/written is
* returned.
*
* @param source The source InputStream.
* @param dest The destination OutputStream.
* @param bufferSize The number of bytes to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @param streamSize The number of bytes in the stream being copied.
* Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown.
* Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
* @param listener The CopyStreamListener to notify of progress. If
* this parameter is null, notification is not attempted.
* @return number of bytes read/written
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyStream(InputStream source, OutputStream dest, int bufferSize,
long streamSize, CopyStreamListener listener) throws CopyStreamException {
return copyStream(source, dest, bufferSize, streamSize, listener, true);
}
/***
* Copies the contents of an InputStream to an OutputStream using a
* copy buffer of a given size. The contents of the InputStream are
* read until the end of the stream is reached, but neither the
* source nor the destination are closed. You must do this yourself
* outside of the method call. The number of bytes read/written is
* returned.
*
* @param source The source InputStream.
* @param dest The destination OutputStream.
* @param bufferSize The number of bytes to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @return The number of bytes read/written in the copy operation.
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyStream(InputStream source, OutputStream dest, int bufferSize)
throws CopyStreamException {
return copyStream(source, dest, bufferSize, CopyStreamEvent.UNKNOWN_STREAM_SIZE, null);
}
/*** /***
* Copies the contents of a Reader to a Writer using a * Same as <code> copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code>
* copy buffer of a given size and notifies the provided * @param source where to copy from
* CopyStreamListener of the progress of the copy operation by calling * @param dest where to copy to
* its bytesTransferred(long, int) method after each write to the * @return number of bytes copied
* destination. If you wish to notify more than one listener you should * @throws CopyStreamException on error
* use a CopyStreamAdapter as the listener and register the additional ***/
* listeners with the CopyStreamAdapter. public static final long copyStream(InputStream source, OutputStream dest)
* <p> throws CopyStreamException {
* The contents of the Reader are return copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE);
* read until its end is reached, but neither the source nor the }
* destination are closed. You must do this yourself outside of the
* method call. The number of characters read/written is returned.
*
* @param source The source Reader.
* @param dest The destination writer.
* @param bufferSize The number of characters to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @param streamSize The number of characters in the stream being copied.
* Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown.
* Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
* @param listener The CopyStreamListener to notify of progress. If
* this parameter is null, notification is not attempted.
* @return The number of characters read/written in the copy operation.
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyReader(Reader source, Writer dest,
int bufferSize, long streamSize,
CopyStreamListener listener)
throws CopyStreamException
{
int numChars;
long total = 0;
char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];
try /***
{ * Copies the contents of a Reader to a Writer using a
while ((numChars = source.read(buffer)) != -1) * copy buffer of a given size and notifies the provided
{ * CopyStreamListener of the progress of the copy operation by calling
// Technically, some read(char[]) methods may return 0 and we cannot * its bytesTransferred(long, int) method after each write to the
// accept that as an indication of EOF. * destination. If you wish to notify more than one listener you should
if (numChars == 0) * use a CopyStreamAdapter as the listener and register the additional
{ * listeners with the CopyStreamAdapter.
int singleChar = source.read(); * <p>
if (singleChar < 0) { * The contents of the Reader are
break; * read until its end is reached, but neither the source nor the
} * destination are closed. You must do this yourself outside of the
dest.write(singleChar); * method call. The number of characters read/written is returned.
dest.flush(); *
++total; * @param source The source Reader.
if (listener != null) { * @param dest The destination writer.
listener.bytesTransferred(total, 1, streamSize); * @param bufferSize The number of characters to buffer during the copy.
} * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
continue; * @param streamSize The number of characters in the stream being copied.
} * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown.
* Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)}
* @param listener The CopyStreamListener to notify of progress. If
* this parameter is null, notification is not attempted.
* @return The number of characters read/written in the copy operation.
* @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been
* transferred before an
* IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods.
***/
public static final long copyReader(Reader source, Writer dest, int bufferSize, long streamSize,
CopyStreamListener listener) throws CopyStreamException {
int numChars;
long total = 0;
char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE];
dest.write(buffer, 0, numChars); try {
dest.flush(); while ((numChars = source.read(buffer)) != -1) {
total += numChars; // Technically, some read(char[]) methods may return 0 and we cannot
if (listener != null) { // accept that as an indication of EOF.
listener.bytesTransferred(total, numChars, streamSize); if (numChars == 0) {
} int singleChar = source.read();
} if (singleChar < 0) {
} break;
catch (IOException e) }
{ dest.write(singleChar);
throw new CopyStreamException("IOException caught while copying.", dest.flush();
total, e); ++total;
if (listener != null) {
listener.bytesTransferred(total, 1, streamSize);
}
continue;
} }
return total; dest.write(buffer, 0, numChars);
dest.flush();
total += numChars;
if (listener != null) {
listener.bytesTransferred(total, numChars, streamSize);
}
}
} catch (IOException e) {
throw new CopyStreamException("IOException caught while copying.", total, e);
} }
return total;
}
/*** /***
* Copies the contents of a Reader to a Writer using a * Copies the contents of a Reader to a Writer using a
* copy buffer of a given size. The contents of the Reader are * copy buffer of a given size. The contents of the Reader are
* read until its end is reached, but neither the source nor the * read until its end is reached, but neither the source nor the
* destination are closed. You must do this yourself outside of the * destination are closed. You must do this yourself outside of the
* method call. The number of characters read/written is returned. * method call. The number of characters read/written is returned.
* *
* @param source The source Reader. * @param source The source Reader.
* @param dest The destination writer. * @param dest The destination writer.
* @param bufferSize The number of characters to buffer during the copy. * @param bufferSize The number of characters to buffer during the copy.
* A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}.
* @return The number of characters read/written in the copy operation. * @return The number of characters read/written in the copy operation.
* @throws CopyStreamException If an error occurs while reading from the * @throws CopyStreamException If an error occurs while reading from the
* source or writing to the destination. The CopyStreamException * source or writing to the destination. The CopyStreamException
* will contain the number of bytes confirmed to have been * will contain the number of bytes confirmed to have been
* transferred before an * transferred before an
* IOException occurred, and it will also contain the IOException * IOException occurred, and it will also contain the IOException
* that caused the error. These values can be retrieved with * that caused the error. These values can be retrieved with
* the CopyStreamException getTotalBytesTransferred() and * the CopyStreamException getTotalBytesTransferred() and
* getIOException() methods. * getIOException() methods.
***/ ***/
public static final long copyReader(Reader source, Writer dest, public static final long copyReader(Reader source, Writer dest, int bufferSize)
int bufferSize) throws CopyStreamException {
throws CopyStreamException return copyReader(source, dest, bufferSize, CopyStreamEvent.UNKNOWN_STREAM_SIZE, null);
{ }
return copyReader(source, dest, bufferSize,
CopyStreamEvent.UNKNOWN_STREAM_SIZE, null);
}
/*** /***
* Same as <code> copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code> * Same as <code> copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code>
* @param source where to copy from * @param source where to copy from
* @param dest where to copy to * @param dest where to copy to
* @return number of bytes copied * @return number of bytes copied
* @throws CopyStreamException on error * @throws CopyStreamException on error
***/ ***/
public static final long copyReader(Reader source, Writer dest) public static final long copyReader(Reader source, Writer dest) throws CopyStreamException {
throws CopyStreamException return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE);
{ }
return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE);
}
/** /**
* Closes the object quietly, catching rather than throwing IOException. * Closes the object quietly, catching rather than throwing IOException.
* Intended for use from finally blocks. * Intended for use from finally blocks.
* *
* @param closeable the object to close, may be {@code null} * @param closeable the object to close, may be {@code null}
* @since 3.0 * @since 3.0
*/ */
public static void closeQuietly(Closeable closeable) { public static void closeQuietly(Closeable closeable) {
if (closeable != null) { if (closeable != null) {
try { try {
closeable.close(); closeable.close();
} catch (IOException e) { } catch (IOException e) {
// Ignored // Ignored
} }
}
} }
}
/** /**
* Closes the socket quietly, catching rather than throwing IOException. * Closes the socket quietly, catching rather than throwing IOException.
* Intended for use from finally blocks. * Intended for use from finally blocks.
* *
* @param socket the socket to close, may be {@code null} * @param socket the socket to close, may be {@code null}
* @since 3.0 * @since 3.0
*/ */
public static void closeQuietly(Socket socket) { public static void closeQuietly(Socket socket) {
if (socket != null) { if (socket != null) {
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
// Ignored // Ignored
} }
}
} }
}
} }

@ -26,29 +26,28 @@ import java.nio.charset.Charset;
*/ */
public class Charsets { public class Charsets {
/** /**
* Returns a charset object for the given charset name. * Returns a charset object for the given charset name.
* *
* @param charsetName * @param charsetName The name of the requested charset; may be a canonical name, an alias, or
* The name of the requested charset; may be a canonical name, an alias, or null. If null, return the * null. If null, return the
* default charset. * default charset.
* @return A charset object for the named charset * @return A charset object for the named charset
*/ */
public static Charset toCharset(String charsetName) { public static Charset toCharset(String charsetName) {
return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName);
} }
/** /**
* Returns a charset object for the given charset name. * Returns a charset object for the given charset name.
* *
* @param charsetName * @param charsetName The name of the requested charset; may be a canonical name, an alias, or
* The name of the requested charset; may be a canonical name, an alias, or null. * null.
* If null, return the default charset. * If null, return the default charset.
* @param defaultCharsetName the charset name to use if the requested charset is null * @param defaultCharsetName the charset name to use if the requested charset is null
* * @return A charset object for the named charset
* @return A charset object for the named charset */
*/ public static Charset toCharset(String charsetName, String defaultCharsetName) {
public static Charset toCharset(String charsetName, String defaultCharsetName) { return charsetName == null ? Charset.forName(defaultCharsetName) : Charset.forName(charsetName);
return charsetName == null ? Charset.forName(defaultCharsetName) : Charset.forName(charsetName); }
}
} }

@ -64,183 +64,174 @@ import org.apache.commons.net.io.Util;
*/ */
public final class KeyManagerUtils { public final class KeyManagerUtils {
private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType(); private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType();
private KeyManagerUtils(){ private KeyManagerUtils() {
// Not instantiable // Not instantiable
} }
/** /**
* Create a client key manager which returns a particular key. * Create a client key manager which returns a particular key.
* Does not handle server keys. * Does not handle server keys.
* *
* @param ks the keystore to use * @param ks the keystore to use
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key
* @param keyPass the password of the key to use * entry alias is used
* @return the customised KeyManager * @param keyPass the password of the key to use
* @throws GeneralSecurityException if there is a problem creating the keystore * @return the customised KeyManager
*/ * @throws GeneralSecurityException if there is a problem creating the keystore
public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass) */
throws GeneralSecurityException public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass)
{ throws GeneralSecurityException {
ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass); ClientKeyStore cks =
return new X509KeyManager(cks); new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass);
} return new X509KeyManager(cks);
}
/**
* Create a client key manager which returns a particular key. /**
* Does not handle server keys. * Create a client key manager which returns a particular key.
* * Does not handle server keys.
* @param storeType the type of the keyStore, e.g. "JKS" *
* @param storePath the path to the keyStore * @param storeType the type of the keyStore, e.g. "JKS"
* @param storePass the keyStore password * @param storePath the path to the keyStore
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used * @param storePass the keyStore password
* @param keyPass the password of the key to use * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key
* @return the customised KeyManager * entry alias is used
* @throws GeneralSecurityException if there is a problem creating the keystore * @param keyPass the password of the key to use
* @throws IOException if there is a problem creating the keystore * @return the customised KeyManager
*/ * @throws GeneralSecurityException if there is a problem creating the keystore
public static KeyManager createClientKeyManager( * @throws IOException if there is a problem creating the keystore
String storeType, File storePath, String storePass, String keyAlias, String keyPass) */
throws IOException, GeneralSecurityException public static KeyManager createClientKeyManager(String storeType, File storePath,
{ String storePass, String keyAlias, String keyPass)
KeyStore ks = loadStore(storeType, storePath, storePass); throws IOException, GeneralSecurityException {
return createClientKeyManager(ks, keyAlias, keyPass); KeyStore ks = loadStore(storeType, storePath, storePass);
return createClientKeyManager(ks, keyAlias, keyPass);
}
/**
* Create a client key manager which returns a particular key.
* Does not handle server keys.
* Uses the default store type and assumes the key password is the same as the store password
*
* @param storePath the path to the keyStore
* @param storePass the keyStore password
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key
* entry alias is used
* @return the customised KeyManager
* @throws IOException if there is a problem creating the keystore
* @throws GeneralSecurityException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias)
throws IOException, GeneralSecurityException {
return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
}
/**
* Create a client key manager which returns a particular key.
* Does not handle server keys.
* Uses the default store type and assumes the key password is the same as the store password.
* The key alias is found by searching the keystore for the first private key entry
*
* @param storePath the path to the keyStore
* @param storePass the keyStore password
* @return the customised KeyManager
* @throws IOException if there is a problem creating the keystore
* @throws GeneralSecurityException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(File storePath, String storePass)
throws IOException, GeneralSecurityException {
return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass);
}
private static KeyStore loadStore(String storeType, File storePath, String storePass)
throws KeyStoreException, IOException, GeneralSecurityException {
KeyStore ks = KeyStore.getInstance(storeType);
FileInputStream stream = null;
try {
stream = new FileInputStream(storePath);
ks.load(stream, storePass.toCharArray());
} finally {
Util.closeQuietly(stream);
} }
return ks;
/** }
* Create a client key manager which returns a particular key.
* Does not handle server keys. private static String findAlias(KeyStore ks) throws KeyStoreException {
* Uses the default store type and assumes the key password is the same as the store password Enumeration<String> e = ks.aliases();
* while (e.hasMoreElements()) {
* @param storePath the path to the keyStore String entry = e.nextElement();
* @param storePass the keyStore password if (ks.isKeyEntry(entry)) {
* @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used return entry;
* @return the customised KeyManager }
* @throws IOException if there is a problem creating the keystore
* @throws GeneralSecurityException if there is a problem creating the keystore
*/
public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias)
throws IOException, GeneralSecurityException
{
return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass);
} }
throw new KeyStoreException("Cannot find a private key entry");
/** }
* Create a client key manager which returns a particular key.
* Does not handle server keys. private static class ClientKeyStore {
* Uses the default store type and assumes the key password is the same as the store password.
* The key alias is found by searching the keystore for the first private key entry private final X509Certificate[] certChain;
* private final PrivateKey key;
* @param storePath the path to the keyStore private final String keyAlias;
* @param storePass the keyStore password
* @return the customised KeyManager ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException {
* @throws IOException if there is a problem creating the keystore this.keyAlias = keyAlias;
* @throws GeneralSecurityException if there is a problem creating the keystore this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray());
*/ Certificate[] certs = ks.getCertificateChain(this.keyAlias);
public static KeyManager createClientKeyManager(File storePath, String storePass) X509Certificate[] X509certs = new X509Certificate[certs.length];
throws IOException, GeneralSecurityException for (int i = 0; i < certs.length; i++) {
{ X509certs[i] = (X509Certificate) certs[i];
return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass); }
this.certChain = X509certs;
} }
private static KeyStore loadStore(String storeType, File storePath, String storePass) final X509Certificate[] getCertificateChain() {
throws KeyStoreException, IOException, GeneralSecurityException { return this.certChain;
KeyStore ks = KeyStore.getInstance(storeType);
FileInputStream stream = null;
try {
stream = new FileInputStream(storePath);
ks.load(stream, storePass.toCharArray());
} finally {
Util.closeQuietly(stream);
}
return ks;
} }
private static String findAlias(KeyStore ks) throws KeyStoreException { final PrivateKey getPrivateKey() {
Enumeration<String> e = ks.aliases(); return this.key;
while(e.hasMoreElements()) {
String entry = e.nextElement();
if (ks.isKeyEntry(entry)) {
return entry;
}
}
throw new KeyStoreException("Cannot find a private key entry");
} }
private static class ClientKeyStore { final String getAlias() {
return this.keyAlias;
private final X509Certificate[] certChain;
private final PrivateKey key;
private final String keyAlias;
ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException
{
this.keyAlias = keyAlias;
this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray());
Certificate[] certs = ks.getCertificateChain(this.keyAlias);
X509Certificate[] X509certs = new X509Certificate[certs.length];
for (int i=0; i < certs.length; i++) {
X509certs[i] = (X509Certificate) certs[i];
}
this.certChain = X509certs;
}
final X509Certificate[] getCertificateChain() {
return this.certChain;
}
final PrivateKey getPrivateKey() {
return this.key;
}
final String getAlias() {
return this.keyAlias;
}
} }
}
private static class X509KeyManager extends X509ExtendedKeyManager { private static class X509KeyManager extends X509ExtendedKeyManager {
private final ClientKeyStore keyStore;
X509KeyManager(final ClientKeyStore keyStore) { private final ClientKeyStore keyStore;
this.keyStore = keyStore;
}
// Call sequence: 1 X509KeyManager(final ClientKeyStore keyStore) {
@Override this.keyStore = keyStore;
public String chooseClientAlias(String[] keyType, Principal[] issuers, }
Socket socket) {
return keyStore.getAlias();
}
// Call sequence: 2
@Override
public X509Certificate[] getCertificateChain(String alias) {
return keyStore.getCertificateChain();
}
@Override // Call sequence: 1
public String[] getClientAliases(String keyType, Principal[] issuers) { @Override public String chooseClientAlias(String[] keyType, Principal[] issuers,
return new String[]{ keyStore.getAlias()}; Socket socket) {
} return keyStore.getAlias();
}
// Call sequence: 3 // Call sequence: 2
@Override @Override public X509Certificate[] getCertificateChain(String alias) {
public PrivateKey getPrivateKey(String alias) { return keyStore.getCertificateChain();
return keyStore.getPrivateKey(); }
}
@Override @Override public String[] getClientAliases(String keyType, Principal[] issuers) {
public String[] getServerAliases(String keyType, Principal[] issuers) { return new String[] { keyStore.getAlias() };
return null; }
}
@Override // Call sequence: 3
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { @Override public PrivateKey getPrivateKey(String alias) {
return null; return keyStore.getPrivateKey();
} }
@Override public String[] getServerAliases(String keyType, Principal[] issuers) {
return null;
} }
@Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return null;
}
}
} }

@ -25,42 +25,35 @@ import java.util.concurrent.CopyOnWriteArrayList;
/** /**
*/ */
public class ListenerList implements Serializable, Iterable<EventListener> public class ListenerList implements Serializable, Iterable<EventListener> {
{ private static final long serialVersionUID = -1934227607974228213L;
private static final long serialVersionUID = -1934227607974228213L;
private final CopyOnWriteArrayList<EventListener> __listeners;
private final CopyOnWriteArrayList<EventListener> __listeners;
public ListenerList() {
public ListenerList() __listeners = new CopyOnWriteArrayList<EventListener>();
{ }
__listeners = new CopyOnWriteArrayList<EventListener>();
} public void addListener(EventListener listener) {
__listeners.add(listener);
public void addListener(EventListener listener) }
{
__listeners.add(listener); public void removeListener(EventListener listener) {
} __listeners.remove(listener);
}
public void removeListener(EventListener listener)
{ public int getListenerCount() {
__listeners.remove(listener); return __listeners.size();
} }
public int getListenerCount() /**
{ * Return an {@link Iterator} for the {@link EventListener} instances.
return __listeners.size(); *
} * @return an {@link Iterator} for the {@link EventListener} instances
* @since 2.0
/** * TODO Check that this is a good defensive strategy
* Return an {@link Iterator} for the {@link EventListener} instances. */
* @Override public Iterator<EventListener> iterator() {
* @return an {@link Iterator} for the {@link EventListener} instances return __listeners.iterator();
* @since 2.0 }
* TODO Check that this is a good defensive strategy
*/
@Override
public Iterator<EventListener> iterator() {
return __listeners.iterator();
}
} }

@ -26,48 +26,52 @@ import javax.net.ssl.TrustManager;
/** /**
* General utilities for SSLContext. * General utilities for SSLContext.
*
* @since 3.0 * @since 3.0
*/ */
public class SSLContextUtils { public class SSLContextUtils {
private SSLContextUtils() { private SSLContextUtils() {
// Not instantiable // Not instantiable
} }
/** /**
* Create and initialise an SSLContext. * Create and initialise an SSLContext.
* @param protocol the protocol used to instatiate the context *
* @param keyManager the key manager, may be {@code null} * @param protocol the protocol used to instatiate the context
* @param trustManager the trust manager, may be {@code null} * @param keyManager the key manager, may be {@code null}
* @return the initialised context. * @param trustManager the trust manager, may be {@code null}
* @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs * @return the initialised context.
*/ * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs
public static SSLContext createSSLContext(String protocol, KeyManager keyManager, TrustManager trustManager) */
throws IOException { public static SSLContext createSSLContext(String protocol, KeyManager keyManager,
return createSSLContext(protocol, TrustManager trustManager) throws IOException {
keyManager == null ? null : new KeyManager[] { keyManager }, return createSSLContext(protocol, keyManager == null ? null : new KeyManager[] { keyManager },
trustManager == null ? null : new TrustManager[] { trustManager }); trustManager == null ? null : new TrustManager[] { trustManager });
} }
/** /**
* Create and initialise an SSLContext. * Create and initialise an SSLContext.
* @param protocol the protocol used to instatiate the context *
* @param keyManagers the array of key managers, may be {@code null} but array entries must not be {@code null} * @param protocol the protocol used to instatiate the context
* @param trustManagers the array of trust managers, may be {@code null} but array entries must not be {@code null} * @param keyManagers the array of key managers, may be {@code null} but array entries must not be
* @return the initialised context. * {@code null}
* @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs * @param trustManagers the array of trust managers, may be {@code null} but array entries must
*/ * not be {@code null}
public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers, TrustManager[] trustManagers) * @return the initialised context.
throws IOException { * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs
SSLContext ctx; */
try { public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers,
ctx = SSLContext.getInstance(protocol); TrustManager[] trustManagers) throws IOException {
ctx.init(keyManagers, trustManagers, /*SecureRandom*/ null); SSLContext ctx;
} catch (GeneralSecurityException e) { try {
IOException ioe = new IOException("Could not initialize SSL context"); ctx = SSLContext.getInstance(protocol);
ioe.initCause(e); ctx.init(keyManagers, trustManagers, /*SecureRandom*/ null);
throw ioe; } catch (GeneralSecurityException e) {
} IOException ioe = new IOException("Could not initialize SSL context");
return ctx; ioe.initCause(e);
throw ioe;
} }
return ctx;
}
} }

@ -25,39 +25,44 @@ import javax.net.ssl.SSLSocket;
/** /**
* General utilities for SSLSocket. * General utilities for SSLSocket.
*
* @since 3.4 * @since 3.4
*/ */
public class SSLSocketUtils { public class SSLSocketUtils {
private SSLSocketUtils() { private SSLSocketUtils() {
// Not instantiable // Not instantiable
} }
/** /**
* Enable the HTTPS endpoint identification algorithm on an SSLSocket. * Enable the HTTPS endpoint identification algorithm on an SSLSocket.
* @param socket the SSL socket *
* @return {@code true} on success (this is only supported on Java 1.7+) * @param socket the SSL socket
*/ * @return {@code true} on success (this is only supported on Java 1.7+)
public static boolean enableEndpointNameVerification(SSLSocket socket) { */
try { public static boolean enableEndpointNameVerification(SSLSocket socket) {
Class<?> cls = Class.forName("javax.net.ssl.SSLParameters"); try {
Method setEndpointIdentificationAlgorithm = cls.getDeclaredMethod("setEndpointIdentificationAlgorithm", String.class); Class<?> cls = Class.forName("javax.net.ssl.SSLParameters");
Method getSSLParameters = SSLSocket.class.getDeclaredMethod("getSSLParameters"); Method setEndpointIdentificationAlgorithm =
Method setSSLParameters = SSLSocket.class.getDeclaredMethod("setSSLParameters", cls); cls.getDeclaredMethod("setEndpointIdentificationAlgorithm", String.class);
if (setEndpointIdentificationAlgorithm != null && getSSLParameters != null && setSSLParameters != null) { Method getSSLParameters = SSLSocket.class.getDeclaredMethod("getSSLParameters");
Object sslParams = getSSLParameters.invoke(socket); Method setSSLParameters = SSLSocket.class.getDeclaredMethod("setSSLParameters", cls);
if (sslParams != null) { if (setEndpointIdentificationAlgorithm != null
setEndpointIdentificationAlgorithm.invoke(sslParams, "HTTPS"); && getSSLParameters != null
setSSLParameters.invoke(socket, sslParams); && setSSLParameters != null) {
return true; Object sslParams = getSSLParameters.invoke(socket);
} if (sslParams != null) {
} setEndpointIdentificationAlgorithm.invoke(sslParams, "HTTPS");
} catch (SecurityException e) { // Ignored setSSLParameters.invoke(socket, sslParams);
} catch (ClassNotFoundException e) { // Ignored return true;
} catch (NoSuchMethodException e) { // Ignored
} catch (IllegalArgumentException e) { // Ignored
} catch (IllegalAccessException e) { // Ignored
} catch (InvocationTargetException e) { // Ignored
} }
return false; }
} catch (SecurityException e) { // Ignored
} catch (ClassNotFoundException e) { // Ignored
} catch (NoSuchMethodException e) { // Ignored
} catch (IllegalArgumentException e) { // Ignored
} catch (IllegalAccessException e) { // Ignored
} catch (InvocationTargetException e) { // Ignored
} }
return false;
}
} }

@ -21,342 +21,375 @@ import java.util.regex.Pattern;
/** /**
* A class that performs some subnet calculations given a network address and a subnet mask. * A class that performs some subnet calculations given a network address and a subnet mask.
*
* @see "http://www.faqs.org/rfcs/rfc1519.html" * @see "http://www.faqs.org/rfcs/rfc1519.html"
* @since 2.0 * @since 2.0
*/ */
public class SubnetUtils { public class SubnetUtils {
private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})"; private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})";
private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS); private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS);
private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT); private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT);
private static final int NBITS = 32; private static final int NBITS = 32;
private int netmask = 0; private int netmask = 0;
private int address = 0; private int address = 0;
private int network = 0; private int network = 0;
private int broadcast = 0; private int broadcast = 0;
/** Whether the broadcast/network address are included in host count */ /** Whether the broadcast/network address are included in host count */
private boolean inclusiveHostCount = false; private boolean inclusiveHostCount = false;
/**
* Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16"
*
* @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
* @throws IllegalArgumentException if the parameter is invalid,
* i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32
*/
public SubnetUtils(String cidrNotation) {
calculate(cidrNotation);
}
/**
* Constructor that takes a dotted decimal address and a dotted decimal mask.
*
* @param address An IP address, e.g. "192.168.0.1"
* @param mask A dotted decimal netmask e.g. "255.255.0.0"
* @throws IllegalArgumentException if the address or mask is invalid,
* i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros
*/
public SubnetUtils(String address, String mask) {
calculate(toCidrNotation(address, mask));
}
/**
* Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()}
* includes the network and broadcast addresses.
*
* @return true if the hostcount includes the network and broadcast addresses
* @since 2.2
*/
public boolean isInclusiveHostCount() {
return inclusiveHostCount;
}
/**
* Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()}
* to include the network and broadcast addresses.
*
* @param inclusiveHostCount true if network and broadcast addresses are to be included
* @since 2.2
*/
public void setInclusiveHostCount(boolean inclusiveHostCount) {
this.inclusiveHostCount = inclusiveHostCount;
}
/**
* Convenience container for subnet summary information.
*/
public final class SubnetInfo {
/* Mask to convert unsigned int to a long (i.e. keep 32 bits) */
private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
private SubnetInfo() {
}
private int netmask() {
return netmask;
}
/** private int network() {
* Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16" return network;
* @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16"
* @throws IllegalArgumentException if the parameter is invalid,
* i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32
*/
public SubnetUtils(String cidrNotation) {
calculate(cidrNotation);
} }
/** private int address() {
* Constructor that takes a dotted decimal address and a dotted decimal mask. return address;
* @param address An IP address, e.g. "192.168.0.1"
* @param mask A dotted decimal netmask e.g. "255.255.0.0"
* @throws IllegalArgumentException if the address or mask is invalid,
* i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros
*/
public SubnetUtils(String address, String mask) {
calculate(toCidrNotation(address, mask));
} }
private int broadcast() {
return broadcast;
}
/** // long versions of the values (as unsigned int) which are more suitable for range checking
* Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()} private long networkLong() {
* includes the network and broadcast addresses. return network & UNSIGNED_INT_MASK;
* @since 2.2
* @return true if the hostcount includes the network and broadcast addresses
*/
public boolean isInclusiveHostCount() {
return inclusiveHostCount;
} }
/** private long broadcastLong() {
* Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()} return broadcast & UNSIGNED_INT_MASK;
* to include the network and broadcast addresses.
* @param inclusiveHostCount true if network and broadcast addresses are to be included
* @since 2.2
*/
public void setInclusiveHostCount(boolean inclusiveHostCount) {
this.inclusiveHostCount = inclusiveHostCount;
} }
private int low() {
return (isInclusiveHostCount() ? network()
: broadcastLong() - networkLong() > 1 ? network() + 1 : 0);
}
private int high() {
return (isInclusiveHostCount() ? broadcast()
: broadcastLong() - networkLong() > 1 ? broadcast() - 1 : 0);
}
/** /**
* Convenience container for subnet summary information. * Returns true if the parameter <code>address</code> is in the
* range of usable endpoint addresses for this subnet. This excludes the
* network and broadcast adresses.
* *
* @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
* @return True if in range, false otherwise
*/ */
public final class SubnetInfo { public boolean isInRange(String address) {
/* Mask to convert unsigned int to a long (i.e. keep 32 bits) */ return isInRange(toInteger(address));
private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
private SubnetInfo() {}
private int netmask() { return netmask; }
private int network() { return network; }
private int address() { return address; }
private int broadcast() { return broadcast; }
// long versions of the values (as unsigned int) which are more suitable for range checking
private long networkLong() { return network & UNSIGNED_INT_MASK; }
private long broadcastLong(){ return broadcast & UNSIGNED_INT_MASK; }
private int low() {
return (isInclusiveHostCount() ? network() :
broadcastLong() - networkLong() > 1 ? network() + 1 : 0);
}
private int high() {
return (isInclusiveHostCount() ? broadcast() :
broadcastLong() - networkLong() > 1 ? broadcast() -1 : 0);
}
/**
* Returns true if the parameter <code>address</code> is in the
* range of usable endpoint addresses for this subnet. This excludes the
* network and broadcast adresses.
* @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
* @return True if in range, false otherwise
*/
public boolean isInRange(String address) {
return isInRange(toInteger(address));
}
/**
*
* @param address the address to check
* @return true if it is in range
* @since 3.4 (made public)
*/
public boolean isInRange(int address) {
long addLong = address & UNSIGNED_INT_MASK;
long lowLong = low() & UNSIGNED_INT_MASK;
long highLong = high() & UNSIGNED_INT_MASK;
return addLong >= lowLong && addLong <= highLong;
}
public String getBroadcastAddress() {
return format(toArray(broadcast()));
}
public String getNetworkAddress() {
return format(toArray(network()));
}
public String getNetmask() {
return format(toArray(netmask()));
}
public String getAddress() {
return format(toArray(address()));
}
/**
* Return the low address as a dotted IP address.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
*
* @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
*/
public String getLowAddress() {
return format(toArray(low()));
}
/**
* Return the high address as a dotted IP address.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
*
* @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
*/
public String getHighAddress() {
return format(toArray(high()));
}
/**
* Get the count of available addresses.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
* @return the count of addresses, may be zero.
* @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE}
* @deprecated (3.4) use {@link #getAddressCountLong()} instead
*/
@Deprecated
public int getAddressCount() {
long countLong = getAddressCountLong();
if (countLong > Integer.MAX_VALUE) {
throw new RuntimeException("Count is larger than an integer: " + countLong);
}
// N.B. cannot be negative
return (int)countLong;
}
/**
* Get the count of available addresses.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
* @return the count of addresses, may be zero.
* @since 3.4
*/
public long getAddressCountLong() {
long b = broadcastLong();
long n = networkLong();
long count = b - n + (isInclusiveHostCount() ? 1 : -1);
return count < 0 ? 0 : count;
}
public int asInteger(String address) {
return toInteger(address);
}
public String getCidrSignature() {
return toCidrNotation(
format(toArray(address())),
format(toArray(netmask()))
);
}
public String[] getAllAddresses() {
int ct = getAddressCount();
String[] addresses = new String[ct];
if (ct == 0) {
return addresses;
}
for (int add = low(), j=0; add <= high(); ++add, ++j) {
addresses[j] = format(toArray(add));
}
return addresses;
}
/**
* {@inheritDoc}
* @since 2.2
*/
@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]")
.append(" Netmask: [").append(getNetmask()).append("]\n")
.append("Network:\t[").append(getNetworkAddress()).append("]\n")
.append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n")
.append("First Address:\t[").append(getLowAddress()).append("]\n")
.append("Last Address:\t[").append(getHighAddress()).append("]\n")
.append("# Addresses:\t[").append(getAddressCount()).append("]\n");
return buf.toString();
}
} }
/** /**
* Return a {@link SubnetInfo} instance that contains subnet-specific statistics * @param address the address to check
* @return new instance * @return true if it is in range
* @since 3.4 (made public)
*/ */
public final SubnetInfo getInfo() { return new SubnetInfo(); } public boolean isInRange(int address) {
long addLong = address & UNSIGNED_INT_MASK;
/* long lowLong = low() & UNSIGNED_INT_MASK;
* Initialize the internal fields from the supplied CIDR mask long highLong = high() & UNSIGNED_INT_MASK;
*/ return addLong >= lowLong && addLong <= highLong;
private void calculate(String mask) { }
Matcher matcher = cidrPattern.matcher(mask);
if (matcher.matches()) { public String getBroadcastAddress() {
address = matchAddress(matcher); return format(toArray(broadcast()));
}
/* Create a binary netmask from the number of bits specification /x */ public String getNetworkAddress() {
int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); return format(toArray(network()));
for (int j = 0; j < cidrPart; ++j) { }
netmask |= (1 << 31 - j);
}
/* Calculate base network address */ public String getNetmask() {
network = (address & netmask); return format(toArray(netmask()));
}
/* Calculate broadcast address */ public String getAddress() {
broadcast = network | ~(netmask); return format(toArray(address()));
} else {
throw new IllegalArgumentException("Could not parse [" + mask + "]");
}
} }
/* /**
* Convert a dotted decimal format address to a packed integer format * Return the low address as a dotted IP address.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
*
* @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
*/ */
private int toInteger(String address) { public String getLowAddress() {
Matcher matcher = addressPattern.matcher(address); return format(toArray(low()));
if (matcher.matches()) {
return matchAddress(matcher);
} else {
throw new IllegalArgumentException("Could not parse [" + address + "]");
}
} }
/* /**
* Convenience method to extract the components of a dotted decimal address and * Return the high address as a dotted IP address.
* pack into an integer using a regex match * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
*
* @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address
*/ */
private int matchAddress(Matcher matcher) { public String getHighAddress() {
int addr = 0; return format(toArray(high()));
for (int i = 1; i <= 4; ++i) {
int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255));
addr |= ((n & 0xff) << 8*(4-i));
}
return addr;
} }
/* /**
* Convert a packed integer address into a 4-element array * Get the count of available addresses.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
*
* @return the count of addresses, may be zero.
* @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE}
* @deprecated (3.4) use {@link #getAddressCountLong()} instead
*/ */
private int[] toArray(int val) { @Deprecated public int getAddressCount() {
int ret[] = new int[4]; long countLong = getAddressCountLong();
for (int j = 3; j >= 0; --j) { if (countLong > Integer.MAX_VALUE) {
ret[j] |= ((val >>> 8*(3-j)) & (0xff)); throw new RuntimeException("Count is larger than an integer: " + countLong);
} }
return ret; // N.B. cannot be negative
return (int) countLong;
} }
/* /**
* Convert a 4-element array into dotted decimal format * Get the count of available addresses.
* Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false.
*
* @return the count of addresses, may be zero.
* @since 3.4
*/ */
private String format(int[] octets) { public long getAddressCountLong() {
StringBuilder str = new StringBuilder(); long b = broadcastLong();
for (int i =0; i < octets.length; ++i){ long n = networkLong();
str.append(octets[i]); long count = b - n + (isInclusiveHostCount() ? 1 : -1);
if (i != octets.length - 1) { return count < 0 ? 0 : count;
str.append(".");
}
}
return str.toString();
} }
/* public int asInteger(String address) {
* Convenience function to check integer boundaries. return toInteger(address);
* Checks if a value x is in the range [begin,end]. }
* Returns x if it is in range, throws an exception otherwise.
*/
private int rangeCheck(int value, int begin, int end) {
if (value >= begin && value <= end) { // (begin,end]
return value;
}
throw new IllegalArgumentException("Value [" + value + "] not in range ["+begin+","+end+"]"); public String getCidrSignature() {
return toCidrNotation(format(toArray(address())), format(toArray(netmask())));
} }
/* public String[] getAllAddresses() {
* Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy int ct = getAddressCount();
* see Hacker's Delight section 5.1 String[] addresses = new String[ct];
*/ if (ct == 0) {
int pop(int x) { return addresses;
x = x - ((x >>> 1) & 0x55555555); }
x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); for (int add = low(), j = 0; add <= high(); ++add, ++j) {
x = (x + (x >>> 4)) & 0x0F0F0F0F; addresses[j] = format(toArray(add));
x = x + (x >>> 8); }
x = x + (x >>> 16); return addresses;
return x & 0x0000003F;
} }
/* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format /**
* by counting the 1-bit population in the mask address. (It may be better to count * {@inheritDoc}
* NBITS-#trailing zeroes for this case) *
* @since 2.2
*/ */
private String toCidrNotation(String addr, String mask) { @Override public String toString() {
return addr + "/" + pop(toInteger(mask)); final StringBuilder buf = new StringBuilder();
buf.append("CIDR Signature:\t[")
.append(getCidrSignature())
.append("]")
.append(" Netmask: [")
.append(getNetmask())
.append("]\n")
.append("Network:\t[")
.append(getNetworkAddress())
.append("]\n")
.append("Broadcast:\t[")
.append(getBroadcastAddress())
.append("]\n")
.append("First Address:\t[")
.append(getLowAddress())
.append("]\n")
.append("Last Address:\t[")
.append(getHighAddress())
.append("]\n")
.append("# Addresses:\t[")
.append(getAddressCount())
.append("]\n");
return buf.toString();
} }
}
/**
* Return a {@link SubnetInfo} instance that contains subnet-specific statistics
*
* @return new instance
*/
public final SubnetInfo getInfo() {
return new SubnetInfo();
}
/*
* Initialize the internal fields from the supplied CIDR mask
*/
private void calculate(String mask) {
Matcher matcher = cidrPattern.matcher(mask);
if (matcher.matches()) {
address = matchAddress(matcher);
/* Create a binary netmask from the number of bits specification /x */
int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS);
for (int j = 0; j < cidrPart; ++j) {
netmask |= (1 << 31 - j);
}
/* Calculate base network address */
network = (address & netmask);
/* Calculate broadcast address */
broadcast = network | ~(netmask);
} else {
throw new IllegalArgumentException("Could not parse [" + mask + "]");
}
}
/*
* Convert a dotted decimal format address to a packed integer format
*/
private int toInteger(String address) {
Matcher matcher = addressPattern.matcher(address);
if (matcher.matches()) {
return matchAddress(matcher);
} else {
throw new IllegalArgumentException("Could not parse [" + address + "]");
}
}
/*
* Convenience method to extract the components of a dotted decimal address and
* pack into an integer using a regex match
*/
private int matchAddress(Matcher matcher) {
int addr = 0;
for (int i = 1; i <= 4; ++i) {
int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255));
addr |= ((n & 0xff) << 8 * (4 - i));
}
return addr;
}
/*
* Convert a packed integer address into a 4-element array
*/
private int[] toArray(int val) {
int ret[] = new int[4];
for (int j = 3; j >= 0; --j) {
ret[j] |= ((val >>> 8 * (3 - j)) & (0xff));
}
return ret;
}
/*
* Convert a 4-element array into dotted decimal format
*/
private String format(int[] octets) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < octets.length; ++i) {
str.append(octets[i]);
if (i != octets.length - 1) {
str.append(".");
}
}
return str.toString();
}
/*
* Convenience function to check integer boundaries.
* Checks if a value x is in the range [begin,end].
* Returns x if it is in range, throws an exception otherwise.
*/
private int rangeCheck(int value, int begin, int end) {
if (value >= begin && value <= end) { // (begin,end]
return value;
}
throw new IllegalArgumentException(
"Value [" + value + "] not in range [" + begin + "," + end + "]");
}
/*
* Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy
* see Hacker's Delight section 5.1
*/
int pop(int x) {
x = x - ((x >>> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
x = (x + (x >>> 4)) & 0x0F0F0F0F;
x = x + (x >>> 8);
x = x + (x >>> 16);
return x & 0x0000003F;
}
/* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format
* by counting the 1-bit population in the mask address. (It may be better to count
* NBITS-#trailing zeroes for this case)
*/
private String toCidrNotation(String addr, String mask) {
return addr + "/" + pop(toInteger(mask));
}
} }

@ -30,88 +30,81 @@ import javax.net.ssl.X509TrustManager;
* *
* @since 3.0 * @since 3.0
*/ */
public final class TrustManagerUtils public final class TrustManagerUtils {
{ private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[] {};
private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{};
private static class TrustManager implements X509TrustManager { private static class TrustManager implements X509TrustManager {
private final boolean checkServerValidity; private final boolean checkServerValidity;
TrustManager(boolean checkServerValidity) { TrustManager(boolean checkServerValidity) {
this.checkServerValidity = checkServerValidity; this.checkServerValidity = checkServerValidity;
}
/**
* Never generates a CertificateException.
*/
@Override
public void checkClientTrusted(X509Certificate[] certificates, String authType)
{
return;
}
@Override
public void checkServerTrusted(X509Certificate[] certificates, String authType)
throws CertificateException
{
if (checkServerValidity) {
for (X509Certificate certificate : certificates)
{
certificate.checkValidity();
}
}
}
/**
* @return an empty array of certificates
*/
@Override
public X509Certificate[] getAcceptedIssuers()
{
return EMPTY_X509CERTIFICATE_ARRAY;
}
} }
private static final X509TrustManager ACCEPT_ALL=new TrustManager(false);
private static final X509TrustManager CHECK_SERVER_VALIDITY=new TrustManager(true);
/** /**
* Generate a TrustManager that performs no checks. * Never generates a CertificateException.
*
* @return the TrustManager
*/ */
public static X509TrustManager getAcceptAllTrustManager(){ @Override public void checkClientTrusted(X509Certificate[] certificates, String authType) {
return ACCEPT_ALL; return;
} }
/** @Override public void checkServerTrusted(X509Certificate[] certificates, String authType)
* Generate a TrustManager that checks server certificates for validity, throws CertificateException {
* but otherwise performs no checks. if (checkServerValidity) {
* for (X509Certificate certificate : certificates) {
* @return the validating TrustManager certificate.checkValidity();
*/ }
public static X509TrustManager getValidateServerCertificateTrustManager(){ }
return CHECK_SERVER_VALIDITY;
} }
/** /**
* Return the default TrustManager provided by the JVM. * @return an empty array of certificates
* <p>
* This should be the same as the default used by
* {@link javax.net.ssl.SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom)
* SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}
* when the TrustManager parameter is set to {@code null}
* @param keyStore the KeyStore to use, may be {@code null}
* @return the default TrustManager
* @throws GeneralSecurityException if an error occurs
*/ */
public static X509TrustManager getDefaultTrustManager(KeyStore keyStore) throws GeneralSecurityException { @Override public X509Certificate[] getAcceptedIssuers() {
String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); return EMPTY_X509CERTIFICATE_ARRAY;
TrustManagerFactory instance = TrustManagerFactory.getInstance(defaultAlgorithm);
instance.init(keyStore);
return (X509TrustManager) instance.getTrustManagers()[0];
} }
}
private static final X509TrustManager ACCEPT_ALL = new TrustManager(false);
private static final X509TrustManager CHECK_SERVER_VALIDITY = new TrustManager(true);
/**
* Generate a TrustManager that performs no checks.
*
* @return the TrustManager
*/
public static X509TrustManager getAcceptAllTrustManager() {
return ACCEPT_ALL;
}
/**
* Generate a TrustManager that checks server certificates for validity,
* but otherwise performs no checks.
*
* @return the validating TrustManager
*/
public static X509TrustManager getValidateServerCertificateTrustManager() {
return CHECK_SERVER_VALIDITY;
}
/**
* Return the default TrustManager provided by the JVM.
* <p>
* This should be the same as the default used by
* {@link javax.net.ssl.SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], * java.security.SecureRandom)
* SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}
* when the TrustManager parameter is set to {@code null}
*
* @param keyStore the KeyStore to use, may be {@code null}
* @return the default TrustManager
* @throws GeneralSecurityException if an error occurs
*/
public static X509TrustManager getDefaultTrustManager(KeyStore keyStore)
throws GeneralSecurityException {
String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory instance = TrustManagerFactory.getInstance(defaultAlgorithm);
instance.init(keyStore);
return (X509TrustManager) instance.getTrustManagers()[0];
}
} }

@ -35,7 +35,7 @@ import java.io.File;
* Ftp下载测试 * Ftp下载测试
*/ */
public class FtpDownloadActivity extends BaseActivity<ActivityFtpDownloadBinding> { public class FtpDownloadActivity extends BaseActivity<ActivityFtpDownloadBinding> {
private final String URL = "ftp://172.18.104.147:21/haha/ftp_test.apk"; private final String URL = "ftp://172.18.104.229:21/haha/测试ss123/ftp_test.apk";
@Override protected void init(Bundle savedInstanceState) { @Override protected void init(Bundle savedInstanceState) {
super.init(savedInstanceState); super.init(savedInstanceState);

@ -34,8 +34,8 @@ import com.arialyy.simple.databinding.ActivityFtpUploadBinding;
* Ftp 文件上传demo * Ftp 文件上传demo
*/ */
public class FtpUploadActivity extends BaseActivity<ActivityFtpUploadBinding> { public class FtpUploadActivity extends BaseActivity<ActivityFtpUploadBinding> {
private final String FILE_PATH = "/mnt/sdcard/成都.mp3"; private final String FILE_PATH = "/mnt/sdcard/Download/jd.jpg";
private final String URL = "ftp://172.18.104.229:21/upload/测试"; private final String URL = "ftp://192.168.1.9:21/upload/测试";
@Override protected void init(Bundle savedInstanceState) { @Override protected void init(Bundle savedInstanceState) {
setTile("FTP 文件上传"); setTile("FTP 文件上传");

Loading…
Cancel
Save