diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..9f369166363
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+在线诊断利器Arthas
+
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 00000000000..3c0e2cb4fd2
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,23 @@
+
+* 代码还是很乱,需要继续重构
+* 依赖需要清理,几个问题:
+ * 所有 apache 的 common 库应当不需要
+ * json 库有好几份
+ * `jopt-simple` 看下能不能用 `cli` 取代
+ * `cli`, `termd` 的 artifactId, version 需要想下。是不是应该直接拿进来。他们的依赖也需要仔细看一下
+* termd 依赖 netty,感觉有点重,而且第一次 attach 比较慢,不确定是 netty 的问题还是 attach 的问题
+* 目前 web console 依赖 termd 中自带的 term.js 和 css,需要美化,需要想下如何集成到研发门户上
+* 因为现在没有 Java 客户端了,所以 batch mode 也就没有了
+* `com.taobao.arthas.core.shell.session.Session` 的能力需要和以前的 session 的实现对标。其中:
+ * 真的需要 textmode 吗?我觉得这个应该是 option 的事情
+ * 真的需要 encoding 吗?我觉得仍然应该在 option 中定义,就算是真的需要,因为我觉得就应该是 UTF-8
+ * duration 是应当展示的,session 的列表也许也应当展示
+ * 需要仔细看下 session 过期是否符合预期
+ * 多人协作的时候 session 原来是在多人之间共享的吗?
+* 所有的命令现在实现的是 AnnotatedCommand,需要继续增强的是:
+ * Help 中的格式化输出被删除。需要为 `@Description` 定义一套统一的格式
+ * 命令的输入以及输出的日志 (record logger) 被删除,需要重新实现,因为现在是用 `CommandProcess` 来输出,所以,需要在 `CommandProcess` 的实现里打日志
+* `com.taobao.arthas.core.GlobalOptions` 看上去好奇怪,感觉是 OptionCommand 应当做的事情
+* `com.taobao.arthas.core.config.Configure` 需要清理,尤其是和 http 相关的
+* 需要合并 develop 分支上后续的修复
+* 代码中的 TODO/FIXME
\ No newline at end of file
diff --git a/agent/pom.xml b/agent/pom.xml
new file mode 100644
index 00000000000..c7a9a0addba
--- /dev/null
+++ b/agent/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ com.taobao.arthas
+ arthas-all
+ 3.0.0-SNAPSHOT
+
+ arthas-agent
+ arthas-agent
+
+
+
+ com.taobao.arthas
+ arthas-spy
+ ${project.version}
+ provided
+
+
+ junit
+ junit
+ test
+
+
+
+
+ arthas-agent
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.6
+ UTF-8
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+ attached
+
+ package
+
+
+ jar-with-dependencies
+
+
+
+ com.taobao.arthas.agent.AgentBootstrap
+ com.taobao.arthas.agent.AgentBootstrap
+ true
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/agent/src/main/java/com/taobao/arthas/agent/AgentBootstrap.java b/agent/src/main/java/com/taobao/arthas/agent/AgentBootstrap.java
new file mode 100644
index 00000000000..5fa6bdf09a5
--- /dev/null
+++ b/agent/src/main/java/com/taobao/arthas/agent/AgentBootstrap.java
@@ -0,0 +1,168 @@
+package com.taobao.arthas.agent;
+
+import java.arthas.Spy;
+import java.io.*;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.jar.JarFile;
+
+/**
+ * 代理启动类
+ *
+ * @author vlinux on 15/5/19.
+ */
+public class AgentBootstrap {
+
+ private static final String ADVICEWEAVER = "com.taobao.arthas.core.advisor.AdviceWeaver";
+ private static final String ON_BEFORE = "methodOnBegin";
+ private static final String ON_RETURN = "methodOnReturnEnd";
+ private static final String ON_THROWS = "methodOnThrowingEnd";
+ private static final String BEFORE_INVOKE = "methodOnInvokeBeforeTracing";
+ private static final String AFTER_INVOKE = "methodOnInvokeAfterTracing";
+ private static final String THROW_INVOKE = "methodOnInvokeThrowTracing";
+ private static final String RESET = "resetArthasClassLoader";
+ private static final String ARTHAS_SPY_JAR = "arthas-spy.jar";
+ private static final String ARTHAS_CONFIGURE = "com.taobao.arthas.core.config.Configure";
+ private static final String ARTHAS_BOOTSTRAP = "com.taobao.arthas.core.server.ArthasBootstrap";
+ private static final String TO_CONFIGURE = "toConfigure";
+ private static final String GET_JAVA_PID = "getJavaPid";
+ private static final String GET_INSTANCE = "getInstance";
+ private static final String IS_BIND = "isBind";
+ private static final String BIND = "bind";
+
+ private static PrintStream ps = System.err;
+ static {
+ try {
+ File log = new File(System.getProperty("user.home") + File.separator + "logs" + File.separator
+ + "arthas" + File.separator + "arthas.log");
+ if (!log.exists()) {
+ log.getParentFile().mkdir();
+ log.createNewFile();
+ }
+ ps = new PrintStream(new FileOutputStream(log, true));
+ } catch (Throwable t) {
+ t.printStackTrace(ps);
+ }
+ }
+
+ // 全局持有classloader用于隔离 Arthas 实现
+ private static volatile ClassLoader arthasClassLoader;
+
+ public static void premain(String args, Instrumentation inst) {
+ main(args, inst);
+ }
+
+ public static void agentmain(String args, Instrumentation inst) {
+ main(args, inst);
+ }
+
+ /**
+ * 让下次再次启动时有机会重新加载
+ */
+ public synchronized static void resetArthasClassLoader() {
+ arthasClassLoader = null;
+ }
+
+ private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File agentJarFile) throws Throwable {
+ // 将Spy添加到BootstrapClassLoader
+ inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));
+
+ // 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
+ return loadOrDefineClassLoader(agentJarFile);
+ }
+
+ private static ClassLoader loadOrDefineClassLoader(File agentJar) throws Throwable {
+ if (arthasClassLoader == null) {
+ arthasClassLoader = new ArthasClassloader(new URL[]{agentJar.toURI().toURL()});
+ }
+ return arthasClassLoader;
+ }
+
+ private static void initSpy(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
+ Class> adviceWeaverClass = classLoader.loadClass(ADVICEWEAVER);
+ Method onBefore = adviceWeaverClass.getMethod(ON_BEFORE, int.class, ClassLoader.class, String.class,
+ String.class, String.class, Object.class, Object[].class);
+ Method onReturn = adviceWeaverClass.getMethod(ON_RETURN, Object.class);
+ Method onThrows = adviceWeaverClass.getMethod(ON_THROWS, Throwable.class);
+ Method beforeInvoke = adviceWeaverClass.getMethod(BEFORE_INVOKE, int.class, String.class, String.class, String.class);
+ Method afterInvoke = adviceWeaverClass.getMethod(AFTER_INVOKE, int.class, String.class, String.class, String.class);
+ Method throwInvoke = adviceWeaverClass.getMethod(THROW_INVOKE, int.class, String.class, String.class, String.class);
+ Method reset = AgentBootstrap.class.getMethod(RESET);
+ Spy.initForAgentLauncher(classLoader, onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke, reset);
+ }
+
+ private static synchronized void main(final String args, final Instrumentation inst) {
+ try {
+ ps.println("Arthas server agent start...");
+ // 传递的args参数分两个部分:agentJar路径和agentArgs, 分别是Agent的JAR包路径和期望传递到服务端的参数
+ int index = args.indexOf(';');
+ String agentJar = args.substring(0, index);
+ final String agentArgs = args.substring(index, args.length());
+
+ File agentJarFile = new File(agentJar);
+ if (!agentJarFile.exists()) {
+ ps.println("Agent jar file does not exist: " + agentJarFile);
+ return;
+ }
+
+ File spyJarFile = new File(agentJarFile.getParentFile(), ARTHAS_SPY_JAR);
+ if (!spyJarFile.exists()) {
+ ps.println("Spy jar file does not exist: " + spyJarFile);
+ return;
+ }
+
+ /**
+ * Use a dedicated thread to run the binding logic to prevent possible memory leak. #195
+ */
+ final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, agentJarFile);
+ initSpy(agentLoader);
+
+ Thread bindingThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ bind(inst, agentLoader, agentArgs);
+ } catch (Throwable throwable) {
+ throwable.printStackTrace(ps);
+ }
+ }
+ };
+
+ bindingThread.setName("arthas-binding-thread");
+ bindingThread.start();
+ bindingThread.join();
+ } catch (Throwable t) {
+ t.printStackTrace(ps);
+ try {
+ if (ps != System.err) {
+ ps.close();
+ }
+ } catch (Throwable tt) {
+ // ignore
+ }
+ throw new RuntimeException(t);
+ }
+ }
+
+ private static void bind(Instrumentation inst, ClassLoader agentLoader, String args) throws Throwable {
+ Class> classOfConfigure = agentLoader.loadClass(ARTHAS_CONFIGURE);
+ Object configure = classOfConfigure.getMethod(TO_CONFIGURE, String.class).invoke(null, args);
+ int javaPid = (Integer) classOfConfigure.getMethod(GET_JAVA_PID).invoke(configure);
+ Class> bootstrapClass = agentLoader.loadClass(ARTHAS_BOOTSTRAP);
+ Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, int.class, Instrumentation.class).invoke(null, javaPid, inst);
+ boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
+ if (!isBind) {
+ try {
+ ps.println("Arthas start to bind...");
+ bootstrapClass.getMethod(BIND, classOfConfigure).invoke(bootstrap, configure);
+ ps.println("Arthas server bind success.");
+ return;
+ } catch (Exception e) {
+ ps.println("Arthas server port binding failed! Please check $HOME/logs/arthas/arthas.log for more details.");
+ throw e;
+ }
+ }
+ ps.println("Arthas server already bind.");
+ }
+}
diff --git a/agent/src/main/java/com/taobao/arthas/agent/ArthasClassloader.java b/agent/src/main/java/com/taobao/arthas/agent/ArthasClassloader.java
new file mode 100644
index 00000000000..4d94bc8e15a
--- /dev/null
+++ b/agent/src/main/java/com/taobao/arthas/agent/ArthasClassloader.java
@@ -0,0 +1,36 @@
+package com.taobao.arthas.agent;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * @author beiwei30 on 09/12/2016.
+ */
+public class ArthasClassloader extends URLClassLoader {
+ public ArthasClassloader(URL[] urls) {
+ super(urls, ClassLoader.getSystemClassLoader().getParent());
+ }
+
+ @Override
+ protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ final Class> loadedClass = findLoadedClass(name);
+ if (loadedClass != null) {
+ return loadedClass;
+ }
+
+ // 优先从parent(SystemClassLoader)里加载系统类,避免抛出ClassNotFoundException
+ if (name != null && (name.startsWith("sun.") || name.startsWith("java."))) {
+ return super.loadClass(name, resolve);
+ }
+ try {
+ Class> aClass = findClass(name);
+ if (resolve) {
+ resolveClass(aClass);
+ }
+ return aClass;
+ } catch (Exception e) {
+ // ignore
+ }
+ return super.loadClass(name, resolve);
+ }
+}
diff --git a/as-package.sh b/as-package.sh
new file mode 100755
index 00000000000..84b290236d3
--- /dev/null
+++ b/as-package.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+CUR_VERSION="3.0"
+
+# arthas's target dir
+ARTHAS_TARGET_DIR=$DIR/target/arthas
+
+# arthas's version
+DATE=$(date '+%Y%m%d%H%M%S')
+
+ARTHAS_VERSION="${CUR_VERSION}.${DATE}"
+
+echo ${ARTHAS_VERSION} > $DIR/core/src/main/resources/com/taobao/arthas/core/res/version
+
+# define newset arthas lib home
+NEWEST_ARTHAS_LIB_HOME=${HOME}/.arthas/lib/${ARTHAS_VERSION}/arthas
+
+
+# exit shell with err_code
+# $1 : err_code
+# $2 : err_msg
+exit_on_err()
+{
+ [[ ! -z "${2}" ]] && echo "${2}" 1>&2
+ exit ${1}
+}
+
+# maven package the arthas
+mvn clean package -Dmaven.test.skip=true -f $DIR/pom.xml \
+|| exit_on_err 1 "package arthas failed."
+
+rm -r $DIR/core/src/main/resources/com/taobao/arthas/core/res/version
+
+# reset the target dir
+mkdir -p ${ARTHAS_TARGET_DIR}
+
+# copy jar to TARGET_DIR
+cp $DIR/spy/target/arthas-spy.jar ${ARTHAS_TARGET_DIR}/arthas-spy.jar
+cp $DIR/core/target/arthas-core-jar-with-dependencies.jar ${ARTHAS_TARGET_DIR}/arthas-core.jar
+cp $DIR/agent/target/arthas-agent-jar-with-dependencies.jar ${ARTHAS_TARGET_DIR}/arthas-agent.jar
+cp $DIR/client/target/arthas-client-jar-with-dependencies.jar ${ARTHAS_TARGET_DIR}/arthas-client.jar
+
+# copy shell to TARGET_DIR
+cat $DIR/bin/install-local.sh|sed "s/ARTHAS_VERSION=0.0/ARTHAS_VERSION=${ARTHAS_VERSION}/g" > ${ARTHAS_TARGET_DIR}/install-local.sh
+chmod +x ${ARTHAS_TARGET_DIR}/install-local.sh
+cp $DIR/bin/as.sh ${ARTHAS_TARGET_DIR}/as.sh
+cp $DIR/bin/as.bat ${ARTHAS_TARGET_DIR}/as.bat
+
+# zip the arthas
+cd $DIR/target/
+zip -r arthas-${ARTHAS_VERSION}-bin.zip arthas/
+cd -
+
+# install to local
+mkdir -p ${NEWEST_ARTHAS_LIB_HOME}
+cp $DIR/target/arthas/* ${NEWEST_ARTHAS_LIB_HOME}/
+
+if [ $# -gt 0 ] && [ "$1" = "-release" ]; then
+ echo "creating git tag ${ARTHAS_VERSION}..."
+ git tag -a ${ARTHAS_VERSION} -m "release ${ARTHAS_VERSION}"
+ if [ $? -eq 0 ]; then
+ echo "A local git tag ${ARTHAS_VERSION} has been created, please use 'git tag -l' to verify it."
+ echo "To commit the tag to remote repo, please run 'git push --tags' manually. "
+ fi
+fi
diff --git a/batch.as b/batch.as
new file mode 100644
index 00000000000..bc4426793a1
--- /dev/null
+++ b/batch.as
@@ -0,0 +1,3 @@
+dashboard -n 1
+sysprop
+watch arthas.Test test "@com.alibaba.arthas.Test@n.entrySet().iterator.{? #this.key.name()=='STOP' }" -n 2
diff --git a/bin/as.bat b/bin/as.bat
new file mode 100644
index 00000000000..71b4c3a6322
--- /dev/null
+++ b/bin/as.bat
@@ -0,0 +1,90 @@
+@echo off
+
+REM ----------------------------------------------------------------------------
+REM program : Arthas
+REM author : Core Engine @ Taobao.com
+REM date : 2015-11-11
+REM version : 3.0
+REM ----------------------------------------------------------------------------
+
+
+
+set ERROR_CODE=0
+
+:init
+REM Decide how to startup depending on the version of windows
+
+REM -- Win98ME
+if NOT "%OS%"=="Windows_NT" goto Win9xArg
+
+REM set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+goto WinNTGetScriptDir
+
+:Win9xArg
+REM Slurp the command line arguments. This loop allows for an unlimited number
+REM of arguments (up to the command line limit, anyway).
+set BASEDIR=%CD%
+goto repoSetup
+
+:WinNTGetScriptDir
+set BASEDIR=%~dp0
+
+:repoSetup
+set AGENT_JAR=%BASEDIR%\arthas-agent.jar
+set CORE_JAR=%BASEDIR%\arthas-core.jar
+
+set PID=%1
+
+REM Setup JAVA_HOME
+if "%JAVA_HOME%" == "" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+if not exist "%JAVA_HOME%\lib\tools.jar" goto noJavaHome
+set JAVACMD="%JAVA_HOME%\bin\java"
+set BOOT_CLASSPATH="-Xbootclasspath/a:%JAVA_HOME%\lib\tools.jar"
+goto okJava
+
+:noJavaHome
+echo The JAVA_HOME environment variable is not defined correctly.
+echo It is needed to run this program.
+echo NB: JAVA_HOME should point to a JDK not a JRE.
+goto exit
+
+:okJava
+set JAVACMD="%JAVA_HOME%"\bin\java
+
+REM Reaching here means variables are defined and arguments have been captured
+:endInit
+
+%JAVACMD% -Dfile.encoding=UTF-8 %BOOT_CLASSPATH% -jar "%CORE_JAR%" -pid "%PID%" -target-ip 127.0.0.1 -telnet-port 3658 -http-port 8563 -core "%CORE_JAR%" -agent "%AGENT_JAR%"
+if %ERRORLEVEL% NEQ 0 goto error
+goto attachSuccess
+
+:error
+if "%OS%"=="Windows_NT" endlocal
+set ERROR_CODE=%ERRORLEVEL%
+goto endNT
+
+:attachSuccess
+REM %JAVACMD% -Dfile.encoding=UTF-8 -Djava.awt.headless=true -cp "%CORE_JAR%" com.taobao.arthas.core.ArthasConsole 127.0.0.1 3658
+telnet 127.0.0.1 3658
+
+REM set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" goto endNT
+
+REM For old DOS remove the set variables from ENV - we assume they were not set
+REM before we started - at least we don't leave any baggage around
+goto postExec
+
+:endNT
+REM If error code is set to 1 then the endlocal was done already in :error.
+if %ERROR_CODE% EQU 0 endlocal
+
+
+:postExec
+
+if "%FORCE_EXIT_ON_ERROR%" == "on" (
+ if %ERROR_CODE% NEQ 0 exit %ERROR_CODE%
+)
+
+exit /B %ERROR_CODE%
diff --git a/bin/as.sh b/bin/as.sh
new file mode 100755
index 00000000000..a3269af6029
--- /dev/null
+++ b/bin/as.sh
@@ -0,0 +1,482 @@
+#!/bin/sh
+
+# program : Arthas
+# author : Core Engine @ Taobao.com
+# date : 2017-3-27
+# version : 3.0.3
+
+# define arthas's home
+ARTHAS_HOME=${HOME}/.arthas
+
+# define arthas's lib
+ARTHAS_LIB_DIR=${ARTHAS_HOME}/lib
+
+# define arthas's temp dir
+TMP_DIR=/tmp
+
+# last update arthas version
+ARTHAS_VERSION=
+
+# current arthas script version
+ARTHAS_SCRIPT_VERSION=3.0.2
+
+# arthas remote url
+ARTHAS_REMOTE_VERSION_URL="http://arthas.io/api/arthas/newestVersion.do"
+ARTHAS_REMOTE_DOWNLOAD_URL="http://arthas.io/mdtool"
+
+# update timeout(sec)
+SO_TIMEOUT=5
+
+# define default target ip
+DEFAULT_TARGET_IP="127.0.0.1"
+
+# define default target port
+DEFAULT_TELNET_PORT="3658"
+DEFAULT_HTTP_PORT="8563"
+
+# define JVM's OPS
+JVM_OPTS=""
+
+# define default batch mode
+BATCH_MODE=false
+
+# if true, the script will only attach the agent to target jvm.
+ATTACH_ONLY=false
+
+# define batch script location
+BATCH_SCRIPT=
+
+ARTHAS_OPTS="-Djava.awt.headless=true"
+
+# exit shell with err_code
+# $1 : err_code
+# $2 : err_msg
+exit_on_err()
+{
+ [[ ! -z "${2}" ]] && echo "${2}" 1>&2
+ exit ${1}
+}
+
+
+# get with default value
+# $1 : target value
+# $2 : default value
+default()
+{
+ [[ ! -z "${1}" ]] && echo "${1}" || echo "${2}"
+}
+
+
+# check arthas permission
+check_permission()
+{
+ [ ! -w ${HOME} ] \
+ && exit_on_err 1 "permission denied, ${HOME} is not writeable."
+}
+
+
+# reset arthas work environment
+# reset some options for env
+reset_for_env()
+{
+
+ # init ARTHAS' lib
+ mkdir -p ${ARTHAS_LIB_DIR} \
+ || exit_on_err 1 "create ${ARTHAS_LIB_DIR} fail."
+
+ # if env define the JAVA_HOME, use it first
+ # if is alibaba opts, use alibaba ops's default JAVA_HOME
+ [ -z ${JAVA_HOME} ] && JAVA_HOME=/opt/taobao/java
+
+ # iterater throught candidates to find a proper JAVA_HOME at least contains tools.jar which is required by arthas.
+ if [ ! -d ${JAVA_HOME} ]; then
+ JAVA_HOME_CANDIDATES=($(ps aux | grep java | grep -v 'grep java' | awk '{print $11}' | sed -n 's/\/bin\/java$//p'))
+ for JAVA_HOME_TEMP in ${JAVA_HOME_CANDIDATES[@]}; do
+ if [ -f ${JAVA_HOME_TEMP}/lib/tools.jar ]; then
+ JAVA_HOME=${JAVA_HOME_TEMP}
+ break
+ fi
+ done
+ fi
+
+ # check the jvm version, we need 1.6+
+ local JAVA_VERSION=$(${JAVA_HOME}/bin/java -version 2>&1|awk -F '"' '$2>"1.5"{print $2}')
+ [[ ! -x ${JAVA_HOME} || -z ${JAVA_VERSION} ]] && exit_on_err 1 "illegal ENV, please set \$JAVA_HOME to JDK6+"
+
+ # when java version greater than 9, there is no tools.jar
+ if [[ ! "$JAVA_VERSION" =~ ^9 ]];then
+ # check tools.jar exists
+ if [ ! -f ${JAVA_HOME}/lib/tools.jar ]; then
+ exit_on_err 1 "${JAVA_HOME}/lib/tools.jar does not exist, arthas could not be launched!"
+ else
+ BOOT_CLASSPATH=-Xbootclasspath/a:${JAVA_HOME}/lib/tools.jar
+ fi
+ fi
+
+ # reset CHARSET for alibaba opts, we use GBK
+ [[ -x /opt/taobao/java ]] && JVM_OPTS="-Dinput.encoding=GBK ${JVM_OPTS} "
+
+}
+
+# get latest version from local
+get_local_version()
+{
+ ls ${ARTHAS_LIB_DIR} | sort | tail -1
+}
+
+# get latest version from remote
+get_remote_version()
+{
+ curl -sLk --connect-timeout ${SO_TIMEOUT} "${ARTHAS_REMOTE_VERSION_URL}"
+}
+
+# make version format to comparable format like 000.000.(0){15}
+# $1 : version
+to_comparable_version()
+{
+ echo ${1}|awk -F "." '{printf("%d.%d.%d\n",$1,$2,$3)}'
+}
+
+# update arthas if necessary
+update_if_necessary()
+{
+ local update_version=$1
+
+ if [ ! -d ${ARTHAS_LIB_DIR}/${update_version} ]; then
+ echo "updating version ${update_version} ..."
+
+ local temp_target_lib_dir="$TMP_DIR/temp_${update_version}_$$"
+ local temp_target_lib_zip="${temp_target_lib_dir}/arthas-${update_version}-bin.zip"
+ local target_lib_dir="${ARTHAS_LIB_DIR}/${update_version}"
+
+ # clean
+ rm -rf ${temp_target_lib_dir}
+ rm -rf ${target_lib_dir}
+
+ mkdir -p "${temp_target_lib_dir}" \
+ || exit_on_err 1 "create ${temp_target_lib_dir} fail."
+
+ # download current arthas version
+ curl \
+ -#Lk \
+ --connect-timeout ${SO_TIMEOUT} \
+ -o ${temp_target_lib_zip} \
+ "${ARTHAS_REMOTE_DOWNLOAD_URL}/${update_version}/arthas-${update_version}-bin.zip" \
+ || return 1
+
+ # unzip arthas lib
+ unzip ${temp_target_lib_zip} -d ${temp_target_lib_dir} || (rm -rf ${temp_target_lib_dir} && return 1)
+
+ # rename
+ mv ${temp_target_lib_dir} ${target_lib_dir} || return 1
+
+ # print success
+ echo "update completed."
+ fi
+}
+
+# the usage
+usage()
+{
+ echo "
+Usage:
+ $0 [-b [-f SCRIPT_FILE]] [debug] [--use-version VERSION] [--attach-only] [@IP:TELNET_PORT:HTTP_PORT]
+ [debug] : start the agent in debug mode
+ : the target Java Process ID
+ [IP] : the target's IP
+ [TELNET_PORT] : the target's PORT for telnet
+ [HTTP_PORT] : the target's PORT for http
+ [-b] : batch mode, which will disable interactive process selection.
+ [-f] : specify the path to batch script file.
+ [--attach-only] : only attach the arthas agent to target jvm.
+ [--use-version] : use the specified arthas version to attach.
+
+Example:
+ ./as.sh
+ ./as.sh @[IP]
+ ./as.sh @[IP:PORT]
+ ./as.sh debug
+ ./as.sh -b
+ ./as.sh -b -f /path/to/script
+ ./as.sh --attach-only
+ ./as.sh --use-version 2.0.20161221142407
+
+Here is the list of possible java process(es) to attatch:
+
+$(${JAVA_HOME}/bin/jps -l | grep -v sun.tools.jps.Jps)
+"
+}
+
+# parse the argument
+parse_arguments()
+{
+ if [ "$1" = "-h" ]; then
+ usage
+ exit 0
+ fi
+
+ if [ "$1" = "-b" ]; then
+ BATCH_MODE=true
+ shift
+ if [ "$1" = "-f" ]; then
+ if [ "x$2" != "x" ] && [ -f $2 ]; then
+ BATCH_SCRIPT=$2
+ echo "Using script file for batch mode: $BATCH_SCRIPT"
+ shift # -f
+ shift # /path/to/script
+ else
+ echo "Invalid script file $2."
+ return 1
+ fi
+ fi
+ fi
+
+ if [ "$1" = "debug" ] ; then
+ if [ -z "$JPDA_TRANSPORT" ]; then
+ JPDA_TRANSPORT="dt_socket"
+ fi
+ if [ -z "$JPDA_ADDRESS" ]; then
+ JPDA_ADDRESS="8888"
+ fi
+ if [ -z "$JPDA_SUSPEND" ]; then
+ JPDA_SUSPEND="n"
+ fi
+ if [ -z "$JPDA_OPTS" ]; then
+ JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND"
+ fi
+ ARTHAS_OPTS="$JPDA_OPTS $ARTHAS_OPTS"
+ shift
+ fi
+
+ # use custom version
+ if [ "$1" = "--use-version" ]; then
+ shift
+ ARTHAS_VERSION=$1
+ shift
+ fi
+
+ # attach only mode
+ if [ "$1" = "--attach-only" ]; then
+ ATTACH_ONLY=true
+ shift
+ fi
+
+ TARGET_PID=$(echo ${1}|awk -F "@" '{print $1}');
+ TARGET_IP=$(echo ${1}|awk -F "@|:" '{print $2}');
+ TELNET_PORT=$(echo ${1}|awk -F ":" '{print $2}');
+ HTTP_PORT=$(echo ${1}|awk -F ":" '{print $3}');
+
+ # check pid
+ if [ -z ${TARGET_PID} ] && [ ${BATCH_MODE} = false ]; then
+ # interactive mode
+ IFS=$'\n'
+ CANDIDATES=($(${JAVA_HOME}/bin/jps -l | grep -v sun.tools.jps.Jps | awk '{print $0}'))
+
+ if [ ${#CANDIDATES[@]} -eq 0 ]; then
+ echo "Error: no available java process to attach."
+ return 1
+ fi
+
+ echo "Found existing java process, please choose one and hit RETURN."
+
+ index=0
+ suggest=1
+ # auto select tomcat/pandora-boot process
+ for process in "${CANDIDATES[@]}"; do
+ index=$(($index+1))
+ if [ $(echo ${process} | grep -c org.apache.catalina.startup.Bootstrap) -eq 1 ] \
+ || [ $(echo ${process} | grep -c com.taobao.pandora.boot.loader.SarLauncher) -eq 1 ]
+ then
+ suggest=${index}
+ break
+ fi
+ done
+
+ index=0
+ for process in "${CANDIDATES[@]}"; do
+ index=$(($index+1))
+ if [ ${index} -eq ${suggest} ]; then
+ echo "* [$index]: ${process}"
+ else
+ echo " [$index]: ${process}"
+ fi
+ done
+
+ read choice
+
+ if [ -z ${choice} ]; then
+ choice=${suggest}
+ fi
+
+ TARGET_PID=`echo ${CANDIDATES[$(($choice-1))]} | cut -d ' ' -f 1`
+
+ elif [ -z ${TARGET_PID} ]; then
+ # batch mode is enabled, no interactive process selection.
+ echo "Illegal arguments, the is required." 1>&2
+ return 1
+ fi
+
+ # reset ${ip} to default if empty
+ [ -z ${TARGET_IP} ] && TARGET_IP=${DEFAULT_TARGET_IP}
+
+ # reset ${port} to default if empty
+ [ -z ${TELNET_PORT} ] && TELNET_PORT=${DEFAULT_TELNET_PORT}
+ [ -z ${HTTP_PORT} ] && HTTP_PORT=${DEFAULT_HTTP_PORT}
+
+ return 0
+
+}
+
+
+# attach arthas to target jvm
+# $1 : arthas_local_version
+attach_jvm()
+{
+ local arthas_version=$1
+ local arthas_lib_dir=${ARTHAS_LIB_DIR}/${arthas_version}/arthas
+
+ echo "Attaching to ${TARGET_PID} using version ${1}..."
+
+ if [ ${TARGET_IP} = ${DEFAULT_TARGET_IP} ]; then
+ if [[ "${arthas_version}" > "3.0" ]]; then
+ ${JAVA_HOME}/bin/java \
+ ${ARTHAS_OPTS} ${BOOT_CLASSPATH} ${JVM_OPTS} \
+ -jar ${arthas_lib_dir}/arthas-core.jar \
+ -pid ${TARGET_PID} \
+ -target-ip ${TARGET_IP} \
+ -telnet-port ${TELNET_PORT} \
+ -http-port ${HTTP_PORT} \
+ -core "${arthas_lib_dir}/arthas-core.jar" \
+ -agent "${arthas_lib_dir}/arthas-agent.jar"
+ else
+ # for compatibility
+ ${JAVA_HOME}/bin/java \
+ ${ARTHAS_OPTS} ${BOOT_CLASSPATH} ${JVM_OPTS} \
+ -jar ${arthas_lib_dir}/arthas-core.jar \
+ -pid ${TARGET_PID} \
+ -target ${TARGET_IP}":"${TELNET_PORT} \
+ -core "${arthas_lib_dir}/arthas-core.jar" \
+ -agent "${arthas_lib_dir}/arthas-agent.jar"
+
+ # verify_pid
+ echo "help" > /tmp/command
+ PID=`${JAVA_HOME}/bin/java -cp ${arthas_lib_dir}/arthas-core.jar ${ARTHAS_OPTS}\
+ com.taobao.arthas.core.ArthasConsole ${TARGET_IP} ${TELNET_PORT} -b -f /tmp/command \
+ | grep PID | awk '{print $2}'`
+ rm /tmp/command
+ if [ ! -z ${PID} ] && [ "${PID}" != "${TARGET_PID}" ]; then
+ echo "WARNING: Arthas server is running on ${PID} instead of ${TARGET_PID}, exiting."
+ exit 1
+ fi
+ fi
+ fi
+}
+
+sanity_check() {
+ # 0 check whether the pid exist
+ local pid=$(ps -p ${TARGET_PID} -o pid=)
+ if [ -z ${pid} ]; then
+ exit_on_err 1 "The target pid (${TARGET_PID}) does not exist!"
+ fi
+
+ # 1 check the current user matches the process owner
+ local current_user=$(id -u -n)
+ # the last '=' after 'user' eliminates the column header
+ local target_user=$(ps -p "${TARGET_PID}" -o user=)
+ if [ "$current_user" != "$target_user" ]; then
+ echo "The current user ($current_user) does not match with the owner of process ${TARGET_PID} ($target_user)."
+ echo "To solve this, choose one of the following command:"
+ echo " 1) sudo su $target_user && ./as.sh"
+ echo " 2) sudo -u $target_user -EH ./as.sh"
+ exit_on_err 1
+ fi
+}
+
+# active console
+# $1 : arthas_local_version
+active_console()
+{
+ local arthas_version=$1
+ local arthas_lib_dir=${ARTHAS_LIB_DIR}/${arthas_version}/arthas
+
+ if [[ "${arthas_version}" > "3.0" ]]; then
+ if [ "${BATCH_MODE}" = "true" ]; then
+ ${JAVA_HOME}/bin/java ${ARTHAS_OPTS} ${JVM_OPTS} \
+ -jar ${arthas_lib_dir}/arthas-client.jar \
+ ${TARGET_IP} \
+ -p ${TELNET_PORT} \
+ -f ${BATCH_SCRIPT}
+ elif type telnet 2>&1 >> /dev/null; then
+ # use telnet
+ telnet ${TARGET_IP} ${TELNET_PORT}
+ else
+ echo "'telnet' is required." 1>&2
+ return 1
+ fi
+ else
+ # for compatibility
+ # use default console
+ ARGS="${TARGET_IP} ${TELNET_PORT}"
+ if [ ${BATCH_MODE} = true ]; then
+ ARGS="$ARGS -b"
+ fi
+ if [ ! -z ${BATCH_SCRIPT} ]; then
+ ARGS="$ARGS -f $BATCH_SCRIPT"
+ fi
+ eval ${JAVA_HOME}/bin/java ${ARTHAS_OPTS} \
+ -cp ${arthas_lib_dir}/arthas-core.jar \
+ com.taobao.arthas.core.ArthasConsole \
+ ${ARGS}
+ fi
+}
+
+# the main
+main()
+{
+ echo "Arthas script version: $ARTHAS_SCRIPT_VERSION"
+ echo "Try out Arthas online: /arthas/web-console"
+
+ check_permission
+ reset_for_env
+
+ parse_arguments "${@}" \
+ || exit_on_err 1 "$(usage)"
+
+ local remote_version=$(get_remote_version)
+
+ if [ -z ${ARTHAS_VERSION} ]; then
+ update_if_necessary ${remote_version} || echo "update fail, ignore this update." 1>&2
+ else
+ update_if_necessary ${ARTHAS_VERSION} || echo "update fail, ignore this update." 1>&2
+ fi
+
+ local arthas_local_version=$(get_local_version)
+
+ if [ ! -z ${ARTHAS_VERSION} ]; then
+ arthas_local_version=${ARTHAS_VERSION}
+ fi
+
+ if [ ! -d ${ARTHAS_LIB_DIR}/${arthas_local_version} ]; then
+ exit_on_err 1 "arthas not found, please check your network."
+ fi
+
+ sanity_check
+
+ echo "Calculating attach execution time..."
+ time (attach_jvm ${arthas_local_version} || exit 1)
+
+ if [ $? -ne 0 ]; then
+ exit_on_err 1 "attach to target jvm (${TARGET_PID}) failed, check ${HOME}/logs/arthas/arthas.log or stderr of target jvm for any exceptions."
+ fi
+
+ echo "Attach success."
+
+ if [ ${ATTACH_ONLY} = false ]; then
+ echo "Connecting to arthas server... current timestamp is `date +%s`"
+ active_console ${arthas_local_version}
+ fi
+}
+
+
+
+main "${@}"
diff --git a/bin/install-local.sh b/bin/install-local.sh
new file mode 100644
index 00000000000..0d709abcf58
--- /dev/null
+++ b/bin/install-local.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# define newset arthas's version
+# need ../arthas-packages.sh replace the version number
+ARTHAS_VERSION=0.0
+
+# define newset arthas lib home
+ARTHAS_LIB_HOME=${HOME}/.arthas/lib/${ARTHAS_VERSION}/arthas
+
+# exit shell with err_code
+# $1 : err_code
+# $2 : err_msg
+exit_on_err()
+{
+ [[ ! -z "${2}" ]] && echo "${2}" 1>&2
+ exit ${1}
+}
+
+# install to local if necessary
+if [[ ! -x ${ARTHAS_LIB_HOME} ]]; then
+
+ # install to local
+ mkdir -p ${ARTHAS_LIB_HOME} \
+ || exit_on_err 1 "create target directory ${ARTHAS_LIB_HOME} failed."
+
+ # copy jar files
+ cp *.jar ${ARTHAS_LIB_HOME}/
+
+ # make it -x
+ chmod +x ./as.sh
+
+fi
+
+echo "install to local successed."
+
diff --git a/bin/install.sh b/bin/install.sh
new file mode 100644
index 00000000000..f8524017563
--- /dev/null
+++ b/bin/install.sh
@@ -0,0 +1,48 @@
+#! /bin/bash
+
+# temp file of as.sh
+TEMP_ARTHAS_FILE="./as.sh.$$"
+
+# target file of as.sh
+TARGET_ARTHAS_FILE="./as.sh"
+
+# update timeout(sec)
+SO_TIMEOUT=60
+
+# default downloading url
+ARTHAS_FILE_URL="http://arthas.io/arthas/as.sh"
+
+# exit shell with err_code
+# $1 : err_code
+# $2 : err_msg
+exit_on_err()
+{
+ [[ ! -z "${2}" ]] && echo "${2}" 1>&2
+ exit ${1}
+}
+
+# check permission to download && install
+[ ! -w ./ ] && exit_on_err 1 "permission denied, target directory ./ was not writable."
+
+if [ $# -gt 1 ] && [ $1 = "--url" ]; then
+ shift
+ ARTHAS_FILE_URL=$1
+ shift
+fi
+
+# download from aliyunos
+echo "downloading... ${TEMP_ARTHAS_FILE}"
+curl \
+ -sLk \
+ --connect-timeout ${SO_TIMEOUT} \
+ $ARTHAS_FILE_URL \
+ -o ${TEMP_ARTHAS_FILE} \
+|| exit_on_err 1 "download failed!"
+
+# wirte or overwrite local file
+rm -rf as.sh
+mv ${TEMP_ARTHAS_FILE} ${TARGET_ARTHAS_FILE}
+chmod +x ${TARGET_ARTHAS_FILE}
+
+# done
+echo "Arthas install successed."
diff --git a/bin/jps.sh b/bin/jps.sh
new file mode 100644
index 00000000000..fdc0f9b4c44
--- /dev/null
+++ b/bin/jps.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# jps.sh version 1.0.2
+
+# there might be multiple java processes, e.g. log-agent
+JPS_CMDS=($(ps aux | grep java | grep -v 'grep java' | awk '{print $11}' | sed -n 's/java$/jps/p'))
+
+# find the first executable jps command
+JPS_CMD=""
+for jps in ${JPS_CMDS[@]}; do
+ if [ -x $jps ]; then
+ JPS_CMD=$jps
+ break
+ fi
+done
+
+if [ "$JPS_CMD" == "" ]; then
+ echo "No Java Process Found on this Machine."
+ exit 1
+else
+ result=`$JPS_CMD -lmv | grep -v jps`
+ if [ "$result" == "" ]; then
+ ps aux | grep -E '^admin.*java.*' | grep -v grep | awk 'BEGIN{ORS=""}{print $2" ";for(j=NF;j>=12;j--){if(match($j, /^\-[a-zA-Z0-9]/)) {break;} } for(i=j+1;i<=NF;i++) {print $i" "} for(i=12;i<=j;i++) {print $i" "} print "\n" }'
+ else
+ echo "$result"
+ fi
+fi
diff --git a/client/pom.xml b/client/pom.xml
new file mode 100644
index 00000000000..a68a48ad32c
--- /dev/null
+++ b/client/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+ arthas-all
+ com.taobao.arthas
+ 3.0.0-SNAPSHOT
+
+ 4.0.0
+
+ arthas-client
+ arthas-client
+
+
+ arthas-client
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.6
+ UTF-8
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+ attached
+
+ package
+
+
+ jar-with-dependencies
+
+
+
+ com.taobao.arthas.client.TelnetConsole
+
+
+ core engine team, middleware group, alibaba inc.
+
+
+
+
+
+
+
+
+
+
+
+ com.alibaba.middleware
+ cli
+
+
+
+
\ No newline at end of file
diff --git a/client/src/main/java/com/taobao/arthas/client/TelnetConsole.java b/client/src/main/java/com/taobao/arthas/client/TelnetConsole.java
new file mode 100644
index 00000000000..e331f0dcc47
--- /dev/null
+++ b/client/src/main/java/com/taobao/arthas/client/TelnetConsole.java
@@ -0,0 +1,213 @@
+package com.taobao.arthas.client;
+
+import com.taobao.middleware.cli.Argument;
+import com.taobao.middleware.cli.CLI;
+import com.taobao.middleware.cli.CLIs;
+import com.taobao.middleware.cli.CommandLine;
+import com.taobao.middleware.cli.Option;
+import com.taobao.middleware.cli.TypedOption;
+import org.apache.commons.net.telnet.TelnetClient;
+import org.apache.commons.net.telnet.WindowSizeOptionHandler;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author ralf0131 2016-12-29 11:55.
+ */
+public class TelnetConsole{
+
+ private static final String PROMPT = "$";
+ private static final String DEFAULT_TELNET_PORT = "3658";
+ private static final int DEFAULT_CONNECTION_TIMEOUT = 5000; // 5000 ms
+ private static final String DEFAULT_WINDOW_WIDTH = "120";
+ private static final String DEFAULT_WINDOW_HEIGHT = "40";
+ private static final int DEFAULT_BUFFER_SIZE = 1024;
+
+ private TelnetClient telnet;
+ private String address;
+ private int port;
+ private InputStream in;
+ private PrintStream out;
+
+ public TelnetConsole(String address, int port, int width, int height) {
+ this.telnet = new TelnetClient();
+ this.address = address;
+ this.port = port;
+ try {
+ telnet.addOptionHandler(new WindowSizeOptionHandler(width, height, true, false, true, false));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ telnet.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
+ }
+
+ public void connect() {
+ try {
+ // Connect to the specified server
+ telnet.connect(address, port);
+ // Get input and output stream references
+ in = telnet.getInputStream();
+ out = new PrintStream(telnet.getOutputStream());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public String readUntil(String prompt) {
+ try {
+ StringBuilder sBuffer = new StringBuilder();
+ byte[] b = new byte[DEFAULT_BUFFER_SIZE];
+ while(true) {
+ int size = in.read(b);
+ if(-1 != size) {
+ sBuffer.append(new String(b,0,size));
+ String data = sBuffer.toString();
+ if(data.trim().endsWith(prompt)) {
+ break;
+ }
+ }
+ }
+ return sBuffer.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public String readUntilPrompt() {
+ return readUntil(PROMPT);
+ }
+
+ public void write(String value) {
+ try {
+ out.println(value);
+ out.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void sendCommand(String command) {
+ try {
+ write(command);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void disconnect() {
+ try {
+ telnet.disconnect();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 批处理模式
+ */
+ public void batchModeRun(File batchFile) {
+ if (batchFile == null || !batchFile.exists()) {
+ return;
+ }
+ batchModeRun(readLines(batchFile));
+ }
+
+ private void batchModeRun(List commands) {
+ for (String command: commands) {
+ // send command to server
+ sendCommand(command + " | plaintext");
+ // read result from server and output
+ String response = readUntilPrompt();
+ System.out.print(response);
+ }
+ }
+
+ private List readLines(File batchFile) {
+ List list = new ArrayList();
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new FileReader(batchFile));
+ String line = br.readLine();
+ while (line != null) {
+ list.add(line);
+ line = br.readLine();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ return list;
+ }
+
+ public static void main(String[] args) {
+ try {
+ if (args.length < 1) {
+ System.err.println("Usage: TelnetConsole [-p PORT] [-c COMMAND] [-f BATCH_FILE] [-w WIDTH] [-h HEIGHT]");
+ System.exit(1);
+ }
+
+ CommandLine commandLine = parseArguments(args);
+
+ TelnetConsole console = new TelnetConsole(
+ (String)commandLine.getArgumentValue("target-ip"),
+ (Integer)commandLine.getOptionValue("p"),
+ (Integer)commandLine.getOptionValue("w"),
+ (Integer)commandLine.getOptionValue("h"));
+
+ console.connect();
+ String logo = console.readUntilPrompt();
+ System.out.print(logo);
+
+ String cmd = commandLine.getOptionValue("c");
+ if (cmd != null) {
+ List cmds = new ArrayList();
+ for (String c: cmd.split(";")) {
+ cmds.add(c.trim());
+ }
+ console.batchModeRun(cmds);
+ }
+
+ String filePath = commandLine.getOptionValue("f");
+ if (filePath != null) {
+ File batchFile = new File(filePath);
+ console.batchModeRun(batchFile);
+ }
+
+ console.disconnect();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static CommandLine parseArguments(String[] args) {
+ Argument addr = new Argument().setArgName("target-ip").setIndex(0).setRequired(true);
+ Option port = new TypedOption().setType(Integer.class).setShortName("p")
+ .setDefaultValue(DEFAULT_TELNET_PORT);
+ Option command = new TypedOption().setType(String.class).setShortName("c");
+ Option batchFileOption = new TypedOption().setType(String.class).setShortName("f");
+ Option width = new TypedOption().setType(Integer.class).setShortName("w")
+ .setDefaultValue(DEFAULT_WINDOW_WIDTH);
+ Option height = new TypedOption().setType(Integer.class).setShortName("h")
+ .setDefaultValue(DEFAULT_WINDOW_HEIGHT);
+ CLI cli = CLIs.create("TelnetConsole").addArgument(addr).addOption(port)
+ .addOption(command).addOption(batchFileOption).addOption(width).addOption(height);
+ return cli.parse(Arrays.asList(args));
+ }
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/DatagramSocketClient.java b/client/src/main/java/org/apache/commons/net/DatagramSocketClient.java
new file mode 100644
index 00000000000..5ba059fe283
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/DatagramSocketClient.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.nio.charset.Charset;
+
+/***
+ * The DatagramSocketClient provides the basic operations that are required
+ * of client objects accessing datagram sockets. It is meant to be
+ * subclassed to avoid having to rewrite the same code over and over again
+ * to open a socket, close a socket, set timeouts, etc. Of special note
+ * is the {@link #setDatagramSocketFactory setDatagramSocketFactory }
+ * method, which allows you to control the type of DatagramSocket the
+ * DatagramSocketClient creates for network communications. This is
+ * especially useful for adding things like proxy support as well as better
+ * support for applets. For
+ * example, you could create a
+ * {@link org.apache.commons.net.DatagramSocketFactory}
+ * that
+ * requests browser security capabilities before creating a socket.
+ * All classes derived from DatagramSocketClient should use the
+ * {@link #_socketFactory_ _socketFactory_ } member variable to
+ * create DatagramSocket instances rather than instantiating
+ * them by directly invoking a constructor. By honoring this contract
+ * you guarantee that a user will always be able to provide his own
+ * Socket implementations by substituting his own SocketFactory.
+ *
+ *
+ * @see DatagramSocketFactory
+ ***/
+
+public abstract class DatagramSocketClient
+{
+ /***
+ * The default DatagramSocketFactory shared by all DatagramSocketClient
+ * instances.
+ ***/
+ private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY =
+ new DefaultDatagramSocketFactory();
+
+ /**
+ * Charset to use for byte IO.
+ */
+ private Charset charset = Charset.defaultCharset();
+
+ /*** The timeout to use after opening a socket. ***/
+ protected int _timeout_;
+
+ /*** The datagram socket used for the connection. ***/
+ protected DatagramSocket _socket_;
+
+ /***
+ * A status variable indicating if the client's socket is currently open.
+ ***/
+ protected boolean _isOpen_;
+
+ /*** The datagram socket's DatagramSocketFactory. ***/
+ protected DatagramSocketFactory _socketFactory_;
+
+ /***
+ * Default constructor for DatagramSocketClient. Initializes
+ * _socket_ to null, _timeout_ to 0, and _isOpen_ to false.
+ ***/
+ public DatagramSocketClient()
+ {
+ _socket_ = null;
+ _timeout_ = 0;
+ _isOpen_ = false;
+ _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
+ }
+
+
+ /***
+ * Opens a DatagramSocket on the local host at the first available port.
+ * Also sets the timeout on the socket to the default timeout set
+ * by {@link #setDefaultTimeout setDefaultTimeout() }.
+ *
+ * _isOpen_ is set to true after calling this method and _socket_
+ * is set to the newly opened socket.
+ *
+ * @exception SocketException If the socket could not be opened or the
+ * timeout could not be set.
+ ***/
+ public void open() throws SocketException
+ {
+ _socket_ = _socketFactory_.createDatagramSocket();
+ _socket_.setSoTimeout(_timeout_);
+ _isOpen_ = true;
+ }
+
+
+ /***
+ * Opens a DatagramSocket on the local host at a specified port.
+ * Also sets the timeout on the socket to the default timeout set
+ * by {@link #setDefaultTimeout setDefaultTimeout() }.
+ *
+ * _isOpen_ is set to true after calling this method and _socket_
+ * is set to the newly opened socket.
+ *
+ * @param port The port to use for the socket.
+ * @exception SocketException If the socket could not be opened or the
+ * timeout could not be set.
+ ***/
+ public void open(int port) throws SocketException
+ {
+ _socket_ = _socketFactory_.createDatagramSocket(port);
+ _socket_.setSoTimeout(_timeout_);
+ _isOpen_ = true;
+ }
+
+
+ /***
+ * Opens a DatagramSocket at the specified address on the local host
+ * at a specified port.
+ * Also sets the timeout on the socket to the default timeout set
+ * by {@link #setDefaultTimeout setDefaultTimeout() }.
+ *
+ * _isOpen_ is set to true after calling this method and _socket_
+ * is set to the newly opened socket.
+ *
+ * @param port The port to use for the socket.
+ * @param laddr The local address to use.
+ * @exception SocketException If the socket could not be opened or the
+ * timeout could not be set.
+ ***/
+ public void open(int port, InetAddress laddr) throws SocketException
+ {
+ _socket_ = _socketFactory_.createDatagramSocket(port, laddr);
+ _socket_.setSoTimeout(_timeout_);
+ _isOpen_ = true;
+ }
+
+
+
+ /***
+ * Closes the DatagramSocket used for the connection.
+ * You should call this method after you've finished using the class
+ * instance and also before you call {@link #open open() }
+ * again. _isOpen_ is set to false and _socket_ is set to null.
+ * If you call this method when the client socket is not open,
+ * a NullPointerException is thrown.
+ ***/
+ public void close()
+ {
+ 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 curerntly open socket, false otherwise.
+ ***/
+ public boolean isOpen()
+ {
+ return _isOpen_;
+ }
+
+
+ /***
+ * Set the default timeout in milliseconds to use when opening a socket.
+ * After a call to open, the timeout for the socket is set using this value.
+ * This method should be used prior to a call to {@link #open open()}
+ * and should not be confused with {@link #setSoTimeout setSoTimeout()}
+ * which operates on the currently open socket. _timeout_ contains
+ * the new timeout value.
+ *
+ * @param timeout The timeout in milliseconds to use for the datagram socket
+ * connection.
+ ***/
+ public void setDefaultTimeout(int timeout)
+ {
+ _timeout_ = timeout;
+ }
+
+
+ /***
+ * Returns the default timeout in milliseconds that is used when
+ * opening a socket.
+ *
+ * @return The default timeout in milliseconds that is used when
+ * opening a socket.
+ ***/
+ public int getDefaultTimeout()
+ {
+ return _timeout_;
+ }
+
+
+ /***
+ * Set the timeout in milliseconds of a currently open connection.
+ * Only call this method after a connection has been opened
+ * by {@link #open open()}.
+ *
+ * @param timeout The timeout in milliseconds to use for the currently
+ * open datagram socket connection.
+ * @throws SocketException if an error setting the timeout
+ ***/
+ public void setSoTimeout(int timeout) throws SocketException
+ {
+ _socket_.setSoTimeout(timeout);
+ }
+
+
+ /***
+ * Returns the timeout in milliseconds of the currently opened socket.
+ * If you call this method when the client socket is not open,
+ * a NullPointerException is thrown.
+ *
+ * @return The timeout in milliseconds of the currently opened socket.
+ * @throws SocketException if an error getting the timeout
+ ***/
+ public int getSoTimeout() throws SocketException
+ {
+ return _socket_.getSoTimeout();
+ }
+
+
+ /***
+ * Returns the port number of the open socket on the local host used
+ * for the connection. If you call this method when the client socket
+ * is not open, a NullPointerException is thrown.
+ *
+ * @return The port number of the open socket on the local host used
+ * for the connection.
+ ***/
+ public int getLocalPort()
+ {
+ return _socket_.getLocalPort();
+ }
+
+
+ /***
+ * Returns the local address to which the client's socket is bound.
+ * If you call this method when the client socket is not open, a
+ * NullPointerException is thrown.
+ *
+ * @return The local address to which the client's socket is bound.
+ ***/
+ public InetAddress getLocalAddress()
+ {
+ return _socket_.getLocalAddress();
+ }
+
+
+ /***
+ * Sets the DatagramSocketFactory used by the DatagramSocketClient
+ * to open DatagramSockets. If the factory value is null, then a default
+ * factory is used (only do this to reset the factory after having
+ * previously altered it).
+ *
+ * @param factory The new DatagramSocketFactory the DatagramSocketClient
+ * should use.
+ ***/
+ public void setDatagramSocketFactory(DatagramSocketFactory factory)
+ {
+ if (factory == null) {
+ _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
+ } else {
+ _socketFactory_ = factory;
+ }
+ }
+
+ /**
+ * Gets the charset name.
+ *
+ * @return the charset name.
+ * @since 3.3
+ * TODO Will be deprecated once the code requires Java 1.6 as a mininmum
+ */
+ public String getCharsetName() {
+ return charset.name();
+ }
+
+ /**
+ * Gets the charset.
+ *
+ * @return the charset.
+ * @since 3.3
+ */
+ public Charset getCharset() {
+ return charset;
+ }
+
+ /**
+ * Sets the charset.
+ *
+ * @param charset the charset.
+ * @since 3.3
+ */
+ public void setCharset(Charset charset) {
+ this.charset = charset;
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/DatagramSocketFactory.java b/client/src/main/java/org/apache/commons/net/DatagramSocketFactory.java
new file mode 100644
index 00000000000..47c26aba9b5
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/DatagramSocketFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+
+/***
+ * The DatagramSocketFactory interface provides a means for the
+ * programmer to control the creation of datagram sockets and
+ * provide his own DatagramSocket implementations for use by all
+ * classes derived from
+ * {@link org.apache.commons.net.DatagramSocketClient}
+ * .
+ * This allows you to provide your own DatagramSocket implementations and
+ * to perform security checks or browser capability requests before
+ * creating a DatagramSocket.
+ *
+ *
+ ***/
+
+public interface DatagramSocketFactory
+{
+
+ /***
+ * Creates a DatagramSocket on the local host at the first available port.
+ * @return the socket
+ *
+ * @exception SocketException If the socket could not be created.
+ ***/
+ public DatagramSocket createDatagramSocket() throws SocketException;
+
+ /***
+ * Creates a DatagramSocket on the local host at a specified port.
+ *
+ * @param port The port to use for the socket.
+ * @return the socket
+ * @exception SocketException If the socket could not be created.
+ ***/
+ public DatagramSocket createDatagramSocket(int port) throws SocketException;
+
+ /***
+ * Creates a DatagramSocket at the specified address on the local host
+ * at a specified port.
+ *
+ * @param port The port to use for the socket.
+ * @param laddr The local address to use.
+ * @return the socket
+ * @exception SocketException If the socket could not be created.
+ ***/
+ public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
+ throws SocketException;
+}
diff --git a/client/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java b/client/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java
new file mode 100644
index 00000000000..6f46619c98a
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+
+/***
+ * DefaultDatagramSocketFactory implements the DatagramSocketFactory
+ * interface by simply wrapping the java.net.DatagramSocket
+ * constructors. It is the default DatagramSocketFactory used by
+ * {@link org.apache.commons.net.DatagramSocketClient}
+ * implementations.
+ *
+ *
+ * @see DatagramSocketFactory
+ * @see DatagramSocketClient
+ * @see DatagramSocketClient#setDatagramSocketFactory
+ ***/
+
+public class DefaultDatagramSocketFactory implements DatagramSocketFactory
+{
+
+ /***
+ * Creates a DatagramSocket on the local host at the first available port.
+ * @return a new DatagramSocket
+ * @exception SocketException If the socket could not be created.
+ ***/
+ @Override
+ public DatagramSocket createDatagramSocket() throws SocketException
+ {
+ return new DatagramSocket();
+ }
+
+ /***
+ * Creates a DatagramSocket on the local host at a specified port.
+ *
+ * @param port The port to use for the socket.
+ * @return a new DatagramSocket
+ * @exception SocketException If the socket could not be created.
+ ***/
+ @Override
+ public DatagramSocket createDatagramSocket(int port) throws SocketException
+ {
+ return new DatagramSocket(port);
+ }
+
+ /***
+ * Creates a DatagramSocket at the specified address on the local host
+ * at a specified port.
+ *
+ * @param port The port to use for the socket.
+ * @param laddr The local address to use.
+ * @return a new DatagramSocket
+ * @exception SocketException If the socket could not be created.
+ ***/
+ @Override
+ public DatagramSocket createDatagramSocket(int port, InetAddress laddr)
+ throws SocketException
+ {
+ return new DatagramSocket(port, laddr);
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/DefaultSocketFactory.java b/client/src/main/java/org/apache/commons/net/DefaultSocketFactory.java
new file mode 100644
index 00000000000..2f0d6583081
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/DefaultSocketFactory.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import javax.net.SocketFactory;
+
+/***
+ * DefaultSocketFactory implements the SocketFactory interface by
+ * simply wrapping the java.net.Socket and java.net.ServerSocket
+ * constructors. It is the default SocketFactory used by
+ * {@link org.apache.commons.net.SocketClient}
+ * implementations.
+ *
+ *
+ * @see SocketFactory
+ * @see SocketClient
+ * @see SocketClient#setSocketFactory
+ ***/
+
+public class DefaultSocketFactory extends SocketFactory
+{
+ /** The proxy to use when creating new sockets. */
+ private final Proxy connProxy;
+
+ /**
+ * The default constructor.
+ */
+ public DefaultSocketFactory()
+ {
+ this(null);
+ }
+
+ /**
+ * A constructor for sockets with proxy support.
+ *
+ * @param proxy The Proxy to use when creating new Sockets.
+ * @since 3.2
+ */
+ public DefaultSocketFactory(Proxy proxy)
+ {
+ connProxy = proxy;
+ }
+
+ /**
+ * Creates an unconnected Socket.
+ *
+ * @return A new unconnected Socket.
+ * @exception IOException If an I/O error occurs while creating the Socket.
+ * @since 3.2
+ */
+ @Override
+ public Socket createSocket() throws IOException
+ {
+ if (connProxy != null)
+ {
+ return new Socket(connProxy);
+ }
+ return new Socket();
+ }
+
+ /***
+ * Creates a Socket connected to the given host and port.
+ *
+ * @param host The hostname to connect to.
+ * @param port The port to connect to.
+ * @return A Socket connected to the given host and port.
+ * @exception UnknownHostException If the hostname cannot be resolved.
+ * @exception IOException If an I/O error occurs while creating the Socket.
+ ***/
+ @Override
+ public Socket createSocket(String host, int port)
+ throws UnknownHostException, IOException
+ {
+ if (connProxy != null)
+ {
+ Socket s = new Socket(connProxy);
+ s.connect(new InetSocketAddress(host, port));
+ return s;
+ }
+ return new Socket(host, port);
+ }
+
+ /***
+ * Creates a Socket connected to the given host and port.
+ *
+ * @param address The address of the host to connect to.
+ * @param port The port to connect to.
+ * @return A Socket connected to the given host and port.
+ * @exception IOException If an I/O error occurs while creating the Socket.
+ ***/
+ @Override
+ public Socket createSocket(InetAddress address, int port)
+ throws IOException
+ {
+ if (connProxy != null)
+ {
+ Socket s = new Socket(connProxy);
+ s.connect(new InetSocketAddress(address, port));
+ return s;
+ }
+ return new Socket(address, port);
+ }
+
+ /***
+ * Creates a Socket connected to the given host and port and
+ * originating from the specified local address and port.
+ *
+ * @param host The hostname to connect to.
+ * @param port The port to connect to.
+ * @param localAddr The local address to use.
+ * @param localPort The local port to use.
+ * @return A Socket connected to the given host and port.
+ * @exception UnknownHostException If the hostname cannot be resolved.
+ * @exception IOException If an I/O error occurs while creating the Socket.
+ ***/
+ @Override
+ public Socket createSocket(String host, int port,
+ InetAddress localAddr, int localPort)
+ throws UnknownHostException, IOException
+ {
+ if (connProxy != null)
+ {
+ Socket s = new Socket(connProxy);
+ s.bind(new InetSocketAddress(localAddr, localPort));
+ s.connect(new InetSocketAddress(host, port));
+ return s;
+ }
+ return new Socket(host, port, localAddr, localPort);
+ }
+
+ /***
+ * Creates a Socket connected to the given host and port and
+ * originating from the specified local address and port.
+ *
+ * @param address The address of the host to connect to.
+ * @param port The port to connect to.
+ * @param localAddr The local address to use.
+ * @param localPort The local port to use.
+ * @return A Socket connected to the given host and port.
+ * @exception IOException If an I/O error occurs while creating the Socket.
+ ***/
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddr, int localPort)
+ throws IOException
+ {
+ if (connProxy != null)
+ {
+ Socket s = new Socket(connProxy);
+ s.bind(new InetSocketAddress(localAddr, localPort));
+ s.connect(new InetSocketAddress(address, port));
+ return s;
+ }
+ return new Socket(address, port, localAddr, localPort);
+ }
+
+ /***
+ * Creates a ServerSocket bound to a specified port. A port
+ * of 0 will create the ServerSocket on a system-determined free port.
+ *
+ * @param port The port on which to listen, or 0 to use any free port.
+ * @return A ServerSocket that will listen on a specified port.
+ * @exception IOException If an I/O error occurs while creating
+ * the ServerSocket.
+ ***/
+ public ServerSocket createServerSocket(int port) throws IOException
+ {
+ return new ServerSocket(port);
+ }
+
+ /***
+ * Creates a ServerSocket bound to a specified port with a given
+ * maximum queue length for incoming connections. A port of 0 will
+ * create the ServerSocket on a system-determined free port.
+ *
+ * @param port The port on which to listen, or 0 to use any free port.
+ * @param backlog The maximum length of the queue for incoming connections.
+ * @return A ServerSocket that will listen on a specified port.
+ * @exception IOException If an I/O error occurs while creating
+ * the ServerSocket.
+ ***/
+ public ServerSocket createServerSocket(int port, int backlog)
+ throws IOException
+ {
+ return new ServerSocket(port, backlog);
+ }
+
+ /***
+ * Creates a ServerSocket bound to a specified port on a given local
+ * address with a given maximum queue length for incoming connections.
+ * A port of 0 will
+ * create the ServerSocket on a system-determined free port.
+ *
+ * @param port The port on which to listen, or 0 to use any free port.
+ * @param backlog The maximum length of the queue for incoming connections.
+ * @param bindAddr The local address to which the ServerSocket should bind.
+ * @return A ServerSocket that will listen on a specified port.
+ * @exception IOException If an I/O error occurs while creating
+ * the ServerSocket.
+ ***/
+ public ServerSocket createServerSocket(int port, int backlog,
+ InetAddress bindAddr)
+ throws IOException
+ {
+ return new ServerSocket(port, backlog, bindAddr);
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/MalformedServerReplyException.java b/client/src/main/java/org/apache/commons/net/MalformedServerReplyException.java
new file mode 100644
index 00000000000..e5ade4e3939
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/MalformedServerReplyException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.io.IOException;
+
+/***
+ * This exception is used to indicate that the reply from a server
+ * could not be interpreted. Most of the NetComponents classes attempt
+ * to be as lenient as possible when receiving server replies. Many
+ * server implementations deviate from IETF protocol specifications, making
+ * it necessary to be as flexible as possible. However, there will be
+ * certain situations where it is not possible to continue an operation
+ * because the server reply could not be interpreted in a meaningful manner.
+ * In these cases, a MalformedServerReplyException should be thrown.
+ *
+ *
+ ***/
+
+public class MalformedServerReplyException extends IOException
+{
+
+ private static final long serialVersionUID = 6006765264250543945L;
+
+ /*** Constructs a MalformedServerReplyException with no message ***/
+ public MalformedServerReplyException()
+ {
+ super();
+ }
+
+ /***
+ * Constructs a MalformedServerReplyException with a specified message.
+ *
+ * @param message The message explaining the reason for the exception.
+ ***/
+ public MalformedServerReplyException(String message)
+ {
+ super(message);
+ }
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/PrintCommandListener.java b/client/src/main/java/org/apache/commons/net/PrintCommandListener.java
new file mode 100644
index 00000000000..8fff4ed17b5
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/PrintCommandListener.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/***
+ * This is a support class for some of the example programs. It is
+ * a sample implementation of the ProtocolCommandListener interface
+ * which just prints out to a specified stream all command/reply traffic.
+ *
+ * @since 2.0
+ ***/
+
+public class PrintCommandListener implements ProtocolCommandListener
+{
+ private final PrintWriter __writer;
+ private final boolean __nologin;
+ private final char __eolMarker;
+ private final boolean __directionMarker;
+
+ /**
+ * Create the default instance which prints everything.
+ *
+ * @param stream where to write the commands and responses
+ * e.g. System.out
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintStream stream)
+ {
+ this(new PrintWriter(stream));
+ }
+
+ /**
+ * Create an instance which optionally suppresses login command text
+ * and indicates where the EOL starts with the specified character.
+ *
+ * @param stream where to write the commands and responses
+ * @param suppressLogin if {@code true}, only print command name for login
+ *
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintStream stream, boolean suppressLogin) {
+ this(new PrintWriter(stream), suppressLogin);
+ }
+
+ /**
+ * Create an instance which optionally suppresses login command text
+ * and indicates where the EOL starts with the specified character.
+ *
+ * @param stream where to write the commands and responses
+ * @param suppressLogin if {@code true}, only print command name for login
+ * @param eolMarker if non-zero, add a marker just before the EOL.
+ *
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker) {
+ this(new PrintWriter(stream), suppressLogin, eolMarker);
+ }
+
+ /**
+ * Create an instance which optionally suppresses login command text
+ * and indicates where the EOL starts with the specified character.
+ *
+ * @param stream where to write the commands and responses
+ * @param suppressLogin if {@code true}, only print command name for login
+ * @param eolMarker if non-zero, add a marker just before the EOL.
+ * @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output
+ *
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker, boolean showDirection) {
+ this(new PrintWriter(stream), suppressLogin, eolMarker, showDirection);
+ }
+
+ /**
+ * Create the default instance which prints everything.
+ *
+ * @param writer where to write the commands and responses
+ */
+ public PrintCommandListener(PrintWriter writer)
+ {
+ this(writer, false);
+ }
+
+ /**
+ * Create an instance which optionally suppresses login command text.
+ *
+ * @param writer where to write the commands and responses
+ * @param suppressLogin if {@code true}, only print command name for login
+ *
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintWriter writer, boolean suppressLogin)
+ {
+ this(writer, suppressLogin, (char) 0);
+ }
+
+ /**
+ * Create an instance which optionally suppresses login command text
+ * and indicates where the EOL starts with the specified character.
+ *
+ * @param writer where to write the commands and responses
+ * @param suppressLogin if {@code true}, only print command name for login
+ * @param eolMarker if non-zero, add a marker just before the EOL.
+ *
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker)
+ {
+ this(writer, suppressLogin, eolMarker, false);
+ }
+
+ /**
+ * Create an instance which optionally suppresses login command text
+ * and indicates where the EOL starts with the specified character.
+ *
+ * @param writer where to write the commands and responses
+ * @param suppressLogin if {@code true}, only print command name for login
+ * @param eolMarker if non-zero, add a marker just before the EOL.
+ * @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output
+ *
+ * @since 3.0
+ */
+ public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker, boolean showDirection)
+ {
+ __writer = writer;
+ __nologin = suppressLogin;
+ __eolMarker = eolMarker;
+ __directionMarker = showDirection;
+ }
+
+ @Override
+ public void protocolCommandSent(ProtocolCommandEvent event)
+ {
+ if (__directionMarker) {
+ __writer.print("> ");
+ }
+ if (__nologin) {
+ String cmd = event.getCommand();
+ if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) {
+ __writer.print(cmd);
+ __writer.println(" *******"); // Don't bother with EOL marker for this!
+ } else {
+ final String IMAP_LOGIN = "LOGIN";
+ if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP
+ String msg = event.getMessage();
+ msg=msg.substring(0, msg.indexOf(IMAP_LOGIN)+IMAP_LOGIN.length());
+ __writer.print(msg);
+ __writer.println(" *******"); // Don't bother with EOL marker for this!
+ } else {
+ __writer.print(getPrintableString(event.getMessage()));
+ }
+ }
+ } else {
+ __writer.print(getPrintableString(event.getMessage()));
+ }
+ __writer.flush();
+ }
+
+ private String getPrintableString(String msg){
+ if (__eolMarker == 0) {
+ return msg;
+ }
+ int pos = msg.indexOf(SocketClient.NETASCII_EOL);
+ if (pos > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(msg.substring(0,pos));
+ sb.append(__eolMarker);
+ sb.append(msg.substring(pos));
+ return sb.toString();
+ }
+ return msg;
+ }
+
+ @Override
+ public void protocolReplyReceived(ProtocolCommandEvent event)
+ {
+ if (__directionMarker) {
+ __writer.print("< ");
+ }
+ __writer.print(event.getMessage());
+ __writer.flush();
+ }
+}
+
diff --git a/client/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java b/client/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java
new file mode 100644
index 00000000000..d921c615be8
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+import java.util.EventObject;
+
+/***
+ * There exists a large class of IETF protocols that work by sending an
+ * ASCII text command and arguments to a server, and then receiving an
+ * ASCII text reply. For debugging and other purposes, it is extremely
+ * useful to log or keep track of the contents of the protocol messages.
+ * The ProtocolCommandEvent class coupled with the
+ * {@link org.apache.commons.net.ProtocolCommandListener}
+ * interface facilitate this process.
+ *
+ *
+ * @see ProtocolCommandListener
+ * @see ProtocolCommandSupport
+ ***/
+
+public class ProtocolCommandEvent extends EventObject
+{
+ private static final long serialVersionUID = 403743538418947240L;
+
+ private final int __replyCode;
+ private final boolean __isCommand;
+ private final String __message, __command;
+
+ /***
+ * Creates a ProtocolCommandEvent signalling a command was sent to
+ * the server. ProtocolCommandEvents created with this constructor
+ * should only be sent after a command has been sent, but before the
+ * reply has been received.
+ *
+ * @param source The source of the event.
+ * @param command The string representation of the command type sent, not
+ * including the arguments (e.g., "STAT" or "GET").
+ * @param message The entire command string verbatim as sent to the server,
+ * including all arguments.
+ ***/
+ public ProtocolCommandEvent(Object source, String command, String message)
+ {
+ super(source);
+ __replyCode = 0;
+ __message = message;
+ __isCommand = true;
+ __command = command;
+ }
+
+
+ /***
+ * Creates a ProtocolCommandEvent signalling a reply to a command was
+ * received. ProtocolCommandEvents created with this constructor
+ * should only be sent after a complete command reply has been received
+ * fromt a server.
+ *
+ * @param source The source of the event.
+ * @param replyCode The integer code indicating the natureof the reply.
+ * This will be the protocol integer value for protocols
+ * that use integer reply codes, or the reply class constant
+ * corresponding to the reply for protocols like POP3 that use
+ * strings like OK rather than integer codes (i.e., POP3Repy.OK).
+ * @param message The entire reply as received from the server.
+ ***/
+ public ProtocolCommandEvent(Object source, int replyCode, String message)
+ {
+ super(source);
+ __replyCode = replyCode;
+ __message = message;
+ __isCommand = false;
+ __command = null;
+ }
+
+ /***
+ * Returns the string representation of the command type sent (e.g., "STAT"
+ * or "GET"). If the ProtocolCommandEvent is a reply event, then null
+ * is returned.
+ *
+ * @return The string representation of the command type sent, or null
+ * if this is a reply event.
+ ***/
+ public String getCommand()
+ {
+ return __command;
+ }
+
+
+ /***
+ * Returns the reply code of the received server reply. Undefined if
+ * this is not a reply event.
+ *
+ * @return The reply code of the received server reply. Undefined if
+ * not a reply event.
+ ***/
+ public int getReplyCode()
+ {
+ return __replyCode;
+ }
+
+ /***
+ * Returns true if the ProtocolCommandEvent was generated as a result
+ * of sending a command.
+ *
+ * @return true If the ProtocolCommandEvent was generated as a result
+ * of sending a command. False otherwise.
+ ***/
+ public boolean isCommand()
+ {
+ return __isCommand;
+ }
+
+ /***
+ * Returns true if the ProtocolCommandEvent was generated as a result
+ * of receiving a reply.
+ *
+ * @return true If the ProtocolCommandEvent was generated as a result
+ * of receiving a reply. False otherwise.
+ ***/
+ public boolean isReply()
+ {
+ return !isCommand();
+ }
+
+ /***
+ * Returns the entire message sent to or received from the server.
+ * Includes the line terminator.
+ *
+ * @return The entire message sent to or received from the server.
+ ***/
+ public String getMessage()
+ {
+ return __message;
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/ProtocolCommandListener.java b/client/src/main/java/org/apache/commons/net/ProtocolCommandListener.java
new file mode 100644
index 00000000000..f6d4231d5ad
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/ProtocolCommandListener.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+import java.util.EventListener;
+
+/***
+ * There exists a large class of IETF protocols that work by sending an
+ * ASCII text command and arguments to a server, and then receiving an
+ * ASCII text reply. For debugging and other purposes, it is extremely
+ * useful to log or keep track of the contents of the protocol messages.
+ * The ProtocolCommandListener interface coupled with the
+ * {@link ProtocolCommandEvent} class facilitate this process.
+ *
+ * To receive ProtocolCommandEvents, you merely implement the
+ * ProtocolCommandListener interface and register the class as a listener
+ * with a ProtocolCommandEvent source such as
+ * {@link org.apache.commons.net.ftp.FTPClient}.
+ *
+ *
+ * @see ProtocolCommandEvent
+ * @see ProtocolCommandSupport
+ ***/
+
+public interface ProtocolCommandListener extends EventListener
+{
+
+ /***
+ * This method is invoked by a ProtocolCommandEvent source after
+ * sending a protocol command to a server.
+ *
+ * @param event The ProtocolCommandEvent fired.
+ ***/
+ public void protocolCommandSent(ProtocolCommandEvent event);
+
+ /***
+ * This method is invoked by a ProtocolCommandEvent source after
+ * receiving a reply from a server.
+ *
+ * @param event The ProtocolCommandEvent fired.
+ ***/
+ public void protocolReplyReceived(ProtocolCommandEvent event);
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java b/client/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java
new file mode 100644
index 00000000000..95611f01e6e
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.io.Serializable;
+import java.util.EventListener;
+
+import org.apache.commons.net.util.ListenerList;
+
+/***
+ * ProtocolCommandSupport is a convenience class for managing a list of
+ * ProtocolCommandListeners and firing ProtocolCommandEvents. You can
+ * simply delegate ProtocolCommandEvent firing and listener
+ * registering/unregistering tasks to this class.
+ *
+ *
+ * @see ProtocolCommandEvent
+ * @see ProtocolCommandListener
+ ***/
+
+public class ProtocolCommandSupport implements Serializable
+{
+ private static final long serialVersionUID = -8017692739988399978L;
+
+ private final Object __source;
+ private final ListenerList __listeners;
+
+ /***
+ * Creates a ProtocolCommandSupport instance using the indicated source
+ * as the source of ProtocolCommandEvents.
+ *
+ * @param source The source to use for all generated ProtocolCommandEvents.
+ ***/
+ public ProtocolCommandSupport(Object source)
+ {
+ __listeners = new ListenerList();
+ __source = source;
+ }
+
+
+ /***
+ * Fires a ProtocolCommandEvent signalling the sending of a command to all
+ * registered listeners, invoking their
+ * {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() }
+ * methods.
+ *
+ * @param command The string representation of the command type sent, not
+ * including the arguments (e.g., "STAT" or "GET").
+ * @param message The entire command string verbatim as sent to the server,
+ * including all arguments.
+ ***/
+ public void fireCommandSent(String command, String message)
+ {
+ ProtocolCommandEvent event;
+
+ event = new ProtocolCommandEvent(__source, command, message);
+
+ for (EventListener listener : __listeners)
+ {
+ ((ProtocolCommandListener)listener).protocolCommandSent(event);
+ }
+ }
+
+ /***
+ * Fires a ProtocolCommandEvent signalling the reception of a command reply
+ * to all registered listeners, invoking their
+ * {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() }
+ * methods.
+ *
+ * @param replyCode The integer code indicating the natureof the reply.
+ * This will be the protocol integer value for protocols
+ * that use integer reply codes, or the reply class constant
+ * corresponding to the reply for protocols like POP3 that use
+ * strings like OK rather than integer codes (i.e., POP3Repy.OK).
+ * @param message The entire reply as received from the server.
+ ***/
+ public void fireReplyReceived(int replyCode, String message)
+ {
+ ProtocolCommandEvent event;
+ event = new ProtocolCommandEvent(__source, replyCode, message);
+
+ for (EventListener listener : __listeners)
+ {
+ ((ProtocolCommandListener)listener).protocolReplyReceived(event);
+ }
+ }
+
+ /***
+ * Adds a ProtocolCommandListener.
+ *
+ * @param listener The ProtocolCommandListener to add.
+ ***/
+ public void addProtocolCommandListener(ProtocolCommandListener listener)
+ {
+ __listeners.addListener(listener);
+ }
+
+ /***
+ * Removes a ProtocolCommandListener.
+ *
+ * @param listener The ProtocolCommandListener to remove.
+ ***/
+ public void removeProtocolCommandListener(ProtocolCommandListener listener)
+ {
+ __listeners.removeListener(listener);
+ }
+
+
+ /***
+ * Returns the number of ProtocolCommandListeners currently registered.
+ *
+ * @return The number of ProtocolCommandListeners currently registered.
+ ***/
+ public int getListenerCount()
+ {
+ return __listeners.getListenerCount();
+ }
+
+}
+
diff --git a/client/src/main/java/org/apache/commons/net/SocketClient.java b/client/src/main/java/org/apache/commons/net/SocketClient.java
new file mode 100644
index 00000000000..e23b985dbab
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/SocketClient.java
@@ -0,0 +1,888 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.charset.Charset;
+
+import javax.net.ServerSocketFactory;
+import javax.net.SocketFactory;
+
+
+/**
+ * The SocketClient provides the basic operations that are required of
+ * client objects accessing sockets. It is meant to be
+ * subclassed to avoid having to rewrite the same code over and over again
+ * to open a socket, close a socket, set timeouts, etc. Of special note
+ * is the {@link #setSocketFactory setSocketFactory }
+ * method, which allows you to control the type of Socket the SocketClient
+ * creates for initiating network connections. This is especially useful
+ * for adding SSL or proxy support as well as better support for applets. For
+ * example, you could create a
+ * {@link javax.net.SocketFactory} that
+ * requests browser security capabilities before creating a socket.
+ * All classes derived from SocketClient should use the
+ * {@link #_socketFactory_ _socketFactory_ } member variable to
+ * create Socket and ServerSocket instances rather than instantiating
+ * them by directly invoking a constructor. By honoring this contract
+ * you guarantee that a user will always be able to provide his own
+ * Socket implementations by substituting his own SocketFactory.
+ * @see SocketFactory
+ */
+public abstract class SocketClient
+{
+ /**
+ * The end of line character sequence used by most IETF protocols. That
+ * is a carriage return followed by a newline: "\r\n"
+ */
+ public static final String NETASCII_EOL = "\r\n";
+
+ /** The default SocketFactory shared by all SocketClient instances. */
+ private static final SocketFactory __DEFAULT_SOCKET_FACTORY =
+ SocketFactory.getDefault();
+
+ /** The default {@link ServerSocketFactory} */
+ private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY =
+ ServerSocketFactory.getDefault();
+
+ /**
+ * A ProtocolCommandSupport object used to manage the registering of
+ * ProtocolCommandListeners and the firing of ProtocolCommandEvents.
+ */
+ private ProtocolCommandSupport __commandSupport;
+
+ /** The timeout to use after opening a socket. */
+ protected int _timeout_;
+
+ /** The socket used for the connection. */
+ protected Socket _socket_;
+
+ /** The hostname used for the connection (null = no hostname supplied). */
+ protected String _hostname_;
+
+ /** The default port the client should connect to. */
+ protected int _defaultPort_;
+
+ /** The socket's InputStream. */
+ protected InputStream _input_;
+
+ /** The socket's OutputStream. */
+ protected OutputStream _output_;
+
+ /** The socket's SocketFactory. */
+ protected SocketFactory _socketFactory_;
+
+ /** The socket's ServerSocket Factory. */
+ protected ServerSocketFactory _serverSocketFactory_;
+
+ /** The socket's connect timeout (0 = infinite timeout) */
+ private static final int DEFAULT_CONNECT_TIMEOUT = 0;
+ protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+
+ /** Hint for SO_RCVBUF size */
+ private int receiveBufferSize = -1;
+
+ /** Hint for SO_SNDBUF size */
+ private int sendBufferSize = -1;
+
+ /** The proxy to use when connecting. */
+ private Proxy connProxy;
+
+ /**
+ * Charset to use for byte IO.
+ */
+ private Charset charset = Charset.defaultCharset();
+
+ /**
+ * Default constructor for SocketClient. Initializes
+ * _socket_ to null, _timeout_ to 0, _defaultPort to 0,
+ * _isConnected_ to false, charset to {@code Charset.defaultCharset()}
+ * and _socketFactory_ to a shared instance of
+ * {@link org.apache.commons.net.DefaultSocketFactory}.
+ */
+ public SocketClient()
+ {
+ _socket_ = null;
+ _hostname_ = null;
+ _input_ = null;
+ _output_ = null;
+ _timeout_ = 0;
+ _defaultPort_ = 0;
+ _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
+ _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
+ }
+
+
+ /**
+ * Because there are so many connect() methods, the _connectAction_()
+ * method is provided as a means of performing some action immediately
+ * after establishing a connection, rather than reimplementing all
+ * of the connect() methods. The last action performed by every
+ * connect() method after opening a socket is to call this method.
+ *
+ * This method sets the timeout on the just opened socket to the default
+ * timeout set by {@link #setDefaultTimeout setDefaultTimeout() },
+ * sets _input_ and _output_ to the socket's InputStream and OutputStream
+ * respectively, and sets _isConnected_ to true.
+ *
+ * Subclasses overriding this method should start by calling
+ * super._connectAction_() first to ensure the
+ * initialization of the aforementioned protected variables.
+ * @throws IOException (SocketException) if a problem occurs with the socket
+ */
+ protected void _connectAction_() throws IOException
+ {
+ _socket_.setSoTimeout(_timeout_);
+ _input_ = _socket_.getInputStream();
+ _output_ = _socket_.getOutputStream();
+ }
+
+
+ /**
+ * Opens a Socket connected to a remote host at the specified port and
+ * originating from the current host at a system assigned port.
+ * Before returning, {@link #_connectAction_ _connectAction_() }
+ * is called to perform connection initialization actions.
+ *
+ * @param host The remote host.
+ * @param port The port to connect to on the remote host.
+ * @exception SocketException If the socket timeout could not be set.
+ * @exception IOException If the socket could not be opened. In most
+ * cases you will only want to catch IOException since SocketException is
+ * derived from it.
+ */
+ public void connect(InetAddress host, int port)
+ throws SocketException, IOException
+ {
+ _hostname_ = null;
+ _socket_ = _socketFactory_.createSocket();
+ if (receiveBufferSize != -1) {
+ _socket_.setReceiveBufferSize(receiveBufferSize);
+ }
+ if (sendBufferSize != -1) {
+ _socket_.setSendBufferSize(sendBufferSize);
+ }
+ _socket_.connect(new InetSocketAddress(host, port), connectTimeout);
+ _connectAction_();
+ }
+
+ /**
+ * Opens a Socket connected to a remote host at the specified port and
+ * originating from the current host at a system assigned port.
+ * Before returning, {@link #_connectAction_ _connectAction_() }
+ * is called to perform connection initialization actions.
+ *
+ * @param hostname The name of the remote host.
+ * @param port The port to connect to on the remote host.
+ * @exception SocketException If the socket timeout could not be set.
+ * @exception IOException If the socket could not be opened. In most
+ * cases you will only want to catch IOException since SocketException is
+ * derived from it.
+ * @exception java.net.UnknownHostException If the hostname cannot be resolved.
+ */
+ public void connect(String hostname, int port)
+ throws SocketException, IOException
+ {
+ connect(InetAddress.getByName(hostname), port);
+ _hostname_ = hostname;
+ }
+
+
+ /**
+ * Opens a Socket connected to a remote host at the specified port and
+ * originating from the specified local address and port.
+ * Before returning, {@link #_connectAction_ _connectAction_() }
+ * is called to perform connection initialization actions.
+ *
+ * @param host The remote host.
+ * @param port The port to connect to on the remote host.
+ * @param localAddr The local address to use.
+ * @param localPort The local port to use.
+ * @exception SocketException If the socket timeout could not be set.
+ * @exception IOException If the socket could not be opened. In most
+ * cases you will only want to catch IOException since SocketException is
+ * derived from it.
+ */
+ public void connect(InetAddress host, int port,
+ InetAddress localAddr, int localPort)
+ throws SocketException, IOException
+ {
+ _hostname_ = null;
+ _socket_ = _socketFactory_.createSocket();
+ if (receiveBufferSize != -1) {
+ _socket_.setReceiveBufferSize(receiveBufferSize);
+ }
+ if (sendBufferSize != -1) {
+ _socket_.setSendBufferSize(sendBufferSize);
+ }
+ _socket_.bind(new InetSocketAddress(localAddr, localPort));
+ _socket_.connect(new InetSocketAddress(host, port), connectTimeout);
+ _connectAction_();
+ }
+
+
+ /**
+ * Opens a Socket connected to a remote host at the specified port and
+ * originating from the specified local address and port.
+ * Before returning, {@link #_connectAction_ _connectAction_() }
+ * is called to perform connection initialization actions.
+ *
+ * @param hostname The name of the remote host.
+ * @param port The port to connect to on the remote host.
+ * @param localAddr The local address to use.
+ * @param localPort The local port to use.
+ * @exception SocketException If the socket timeout could not be set.
+ * @exception IOException If the socket could not be opened. In most
+ * cases you will only want to catch IOException since SocketException is
+ * derived from it.
+ * @exception java.net.UnknownHostException If the hostname cannot be resolved.
+ */
+ public void connect(String hostname, int port,
+ InetAddress localAddr, int localPort)
+ throws SocketException, IOException
+ {
+ connect(InetAddress.getByName(hostname), port, localAddr, localPort);
+ _hostname_ = hostname;
+ }
+
+
+ /**
+ * Opens a Socket connected to a remote host at the current default port
+ * and originating from the current host at a system assigned port.
+ * Before returning, {@link #_connectAction_ _connectAction_() }
+ * is called to perform connection initialization actions.
+ *
+ * @param host The remote host.
+ * @exception SocketException If the socket timeout could not be set.
+ * @exception IOException If the socket could not be opened. In most
+ * cases you will only want to catch IOException since SocketException is
+ * derived from it.
+ */
+ public void connect(InetAddress host) throws SocketException, IOException
+ {
+ _hostname_ = null;
+ connect(host, _defaultPort_);
+ }
+
+
+ /**
+ * Opens a Socket connected to a remote host at the current default
+ * port and originating from the current host at a system assigned port.
+ * Before returning, {@link #_connectAction_ _connectAction_() }
+ * is called to perform connection initialization actions.
+ *
+ * @param hostname The name of the remote host.
+ * @exception SocketException If the socket timeout could not be set.
+ * @exception IOException If the socket could not be opened. In most
+ * cases you will only want to catch IOException since SocketException is
+ * derived from it.
+ * @exception java.net.UnknownHostException If the hostname cannot be resolved.
+ */
+ public void connect(String hostname) throws SocketException, IOException
+ {
+ connect(hostname, _defaultPort_);
+ _hostname_ = hostname;
+ }
+
+
+ /**
+ * Disconnects the socket connection.
+ * You should call this method after you've finished using the class
+ * instance and also before you call
+ * {@link #connect connect() }
+ * again. _isConnected_ is set to false, _socket_ is set to null,
+ * _input_ is set to null, and _output_ is set to null.
+ *
+ * @exception IOException If there is an error closing the socket.
+ */
+ public void disconnect() throws IOException
+ {
+ closeQuietly(_socket_);
+ closeQuietly(_input_);
+ closeQuietly(_output_);
+ _socket_ = null;
+ _hostname_ = null;
+ _input_ = null;
+ _output_ = null;
+ }
+
+ private void closeQuietly(Socket socket) {
+ if (socket != null){
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // Ignored
+ }
+ }
+ }
+
+ private void closeQuietly(Closeable close){
+ if (close != null){
+ try {
+ close.close();
+ } catch (IOException e) {
+ // Ignored
+ }
+ }
+ }
+ /**
+ * Returns true if the client is currently connected to a server.
+ *
+ * Delegates to {@link Socket#isConnected()}
+ * @return True if the client is currently connected to a server,
+ * false otherwise.
+ */
+ public boolean isConnected()
+ {
+ if (_socket_ == null) {
+ return false;
+ }
+
+ return _socket_.isConnected();
+ }
+
+ /**
+ * Make various checks on the socket to test if it is available for use.
+ * Note that the only sure test is to use it, but these checks may help
+ * in some cases.
+ * @see NET-350
+ * @return {@code true} if the socket appears to be available for use
+ * @since 3.0
+ */
+ public boolean isAvailable(){
+ if (isConnected()) {
+ try
+ {
+ if (_socket_.getInetAddress() == null) {
+ return false;
+ }
+ if (_socket_.getPort() == 0) {
+ return false;
+ }
+ if (_socket_.getRemoteSocketAddress() == null) {
+ return false;
+ }
+ if (_socket_.isClosed()) {
+ return false;
+ }
+ /* these aren't exact checks (a Socket can be half-open),
+ but since we usually require two-way data transfer,
+ we check these here too: */
+ if (_socket_.isInputShutdown()) {
+ return false;
+ }
+ if (_socket_.isOutputShutdown()) {
+ return false;
+ }
+ /* ignore the result, catch exceptions: */
+ _socket_.getInputStream();
+ _socket_.getOutputStream();
+ }
+ catch (IOException ioex)
+ {
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets the default port the SocketClient should connect to when a port
+ * is not specified. The {@link #_defaultPort_ _defaultPort_ }
+ * variable stores this value. If never set, the default port is equal
+ * to zero.
+ *
+ * @param port The default port to set.
+ */
+ public void setDefaultPort(int port)
+ {
+ _defaultPort_ = port;
+ }
+
+ /**
+ * Returns the current value of the default port (stored in
+ * {@link #_defaultPort_ _defaultPort_ }).
+ *
+ * @return The current value of the default port.
+ */
+ public int getDefaultPort()
+ {
+ return _defaultPort_;
+ }
+
+
+ /**
+ * Set the default timeout in milliseconds to use when opening a socket.
+ * This value is only used previous to a call to
+ * {@link #connect connect()}
+ * and should not be confused with {@link #setSoTimeout setSoTimeout()}
+ * which operates on an the currently opened socket. _timeout_ contains
+ * the new timeout value.
+ *
+ * @param timeout The timeout in milliseconds to use for the socket
+ * connection.
+ */
+ public void setDefaultTimeout(int timeout)
+ {
+ _timeout_ = timeout;
+ }
+
+
+ /**
+ * Returns the default timeout in milliseconds that is used when
+ * opening a socket.
+ *
+ * @return The default timeout in milliseconds that is used when
+ * opening a socket.
+ */
+ public int getDefaultTimeout()
+ {
+ return _timeout_;
+ }
+
+
+ /**
+ * Set the timeout in milliseconds of a currently open connection.
+ * Only call this method after a connection has been opened
+ * by {@link #connect connect()}.
+ *
+ * To set the initial timeout, use {@link #setDefaultTimeout(int)} instead.
+ *
+ * @param timeout The timeout in milliseconds to use for the currently
+ * open socket connection.
+ * @exception SocketException If the operation fails.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public void setSoTimeout(int timeout) throws SocketException
+ {
+ _socket_.setSoTimeout(timeout);
+ }
+
+
+ /**
+ * Set the underlying socket send buffer size.
+ *
+ * @param size The size of the buffer in bytes.
+ * @throws SocketException never thrown, but subclasses might want to do so
+ * @since 2.0
+ */
+ public void setSendBufferSize(int size) throws SocketException {
+ sendBufferSize = size;
+ }
+
+ /**
+ * Get the current sendBuffer size
+ * @return the size, or -1 if not initialised
+ * @since 3.0
+ */
+ protected int getSendBufferSize(){
+ return sendBufferSize;
+ }
+
+ /**
+ * Sets the underlying socket receive buffer size.
+ *
+ * @param size The size of the buffer in bytes.
+ * @throws SocketException never (but subclasses may wish to do so)
+ * @since 2.0
+ */
+ public void setReceiveBufferSize(int size) throws SocketException {
+ receiveBufferSize = size;
+ }
+
+ /**
+ * Get the current receivedBuffer size
+ * @return the size, or -1 if not initialised
+ * @since 3.0
+ */
+ protected int getReceiveBufferSize(){
+ return receiveBufferSize;
+ }
+
+ /**
+ * Returns the timeout in milliseconds of the currently opened socket.
+ *
+ * @return The timeout in milliseconds of the currently opened socket.
+ * @exception SocketException If the operation fails.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public int getSoTimeout() throws SocketException
+ {
+ return _socket_.getSoTimeout();
+ }
+
+ /**
+ * Enables or disables the Nagle's algorithm (TCP_NODELAY) on the
+ * currently opened socket.
+ *
+ * @param on True if Nagle's algorithm is to be enabled, false if not.
+ * @exception SocketException If the operation fails.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public void setTcpNoDelay(boolean on) throws SocketException
+ {
+ _socket_.setTcpNoDelay(on);
+ }
+
+
+ /**
+ * Returns true if Nagle's algorithm is enabled on the currently opened
+ * socket.
+ *
+ * @return True if Nagle's algorithm is enabled on the currently opened
+ * socket, false otherwise.
+ * @exception SocketException If the operation fails.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public boolean getTcpNoDelay() throws SocketException
+ {
+ return _socket_.getTcpNoDelay();
+ }
+
+ /**
+ * Sets the SO_KEEPALIVE flag on the currently opened socket.
+ *
+ * From the Javadocs, the default keepalive time is 2 hours (although this is
+ * implementation dependent). It looks as though the Windows WSA sockets implementation
+ * allows a specific keepalive value to be set, although this seems not to be the case on
+ * other systems.
+ * @param keepAlive If true, keepAlive is turned on
+ * @throws SocketException if there is a problem with the socket
+ * @throws NullPointerException if the socket is not currently open
+ * @since 2.2
+ */
+ public void setKeepAlive(boolean keepAlive) throws SocketException {
+ _socket_.setKeepAlive(keepAlive);
+ }
+
+ /**
+ * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket.
+ * Delegates to {@link Socket#getKeepAlive()}
+ * @return True if SO_KEEPALIVE is enabled.
+ * @throws SocketException if there is a problem with the socket
+ * @throws NullPointerException if the socket is not currently open
+ * @since 2.2
+ */
+ public boolean getKeepAlive() throws SocketException {
+ return _socket_.getKeepAlive();
+ }
+
+ /**
+ * Sets the SO_LINGER timeout on the currently opened socket.
+ *
+ * @param on True if linger is to be enabled, false if not.
+ * @param val The linger timeout (in hundredths of a second?)
+ * @exception SocketException If the operation fails.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public void setSoLinger(boolean on, int val) throws SocketException
+ {
+ _socket_.setSoLinger(on, val);
+ }
+
+
+ /**
+ * Returns the current SO_LINGER timeout of the currently opened socket.
+ *
+ * @return The current SO_LINGER timeout. If SO_LINGER is disabled returns
+ * -1.
+ * @exception SocketException If the operation fails.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public int getSoLinger() throws SocketException
+ {
+ return _socket_.getSoLinger();
+ }
+
+
+ /**
+ * Returns the port number of the open socket on the local host used
+ * for the connection.
+ * Delegates to {@link Socket#getLocalPort()}
+ *
+ * @return The port number of the open socket on the local host used
+ * for the connection.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public int getLocalPort()
+ {
+ return _socket_.getLocalPort();
+ }
+
+
+ /**
+ * Returns the local address to which the client's socket is bound.
+ * Delegates to {@link Socket#getLocalAddress()}
+ *
+ * @return The local address to which the client's socket is bound.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public InetAddress getLocalAddress()
+ {
+ return _socket_.getLocalAddress();
+ }
+
+ /**
+ * Returns the port number of the remote host to which the client is
+ * connected.
+ * Delegates to {@link Socket#getPort()}
+ *
+ * @return The port number of the remote host to which the client is
+ * connected.
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public int getRemotePort()
+ {
+ return _socket_.getPort();
+ }
+
+
+ /**
+ * @return The remote address to which the client is connected.
+ * Delegates to {@link Socket#getInetAddress()}
+ * @throws NullPointerException if the socket is not currently open
+ */
+ public InetAddress getRemoteAddress()
+ {
+ return _socket_.getInetAddress();
+ }
+
+
+ /**
+ * Verifies that the remote end of the given socket is connected to the
+ * the same host that the SocketClient is currently connected to. This
+ * is useful for doing a quick security check when a client needs to
+ * accept a connection from a server, such as an FTP data connection or
+ * a BSD R command standard error stream.
+ *
+ * @param socket the item to check against
+ * @return True if the remote hosts are the same, false if not.
+ */
+ public boolean verifyRemote(Socket socket)
+ {
+ InetAddress host1, host2;
+
+ host1 = socket.getInetAddress();
+ host2 = getRemoteAddress();
+
+ return host1.equals(host2);
+ }
+
+
+ /**
+ * Sets the SocketFactory used by the SocketClient to open socket
+ * connections. If the factory value is null, then a default
+ * factory is used (only do this to reset the factory after having
+ * previously altered it).
+ * Any proxy setting is discarded.
+ *
+ * @param factory The new SocketFactory the SocketClient should use.
+ */
+ public void setSocketFactory(SocketFactory factory)
+ {
+ if (factory == null) {
+ _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
+ } else {
+ _socketFactory_ = factory;
+ }
+ // re-setting the socket factory makes the proxy setting useless,
+ // so set the field to null so that getProxy() doesn't return a
+ // Proxy that we're actually not using.
+ connProxy = null;
+ }
+
+ /**
+ * Sets the ServerSocketFactory used by the SocketClient to open ServerSocket
+ * connections. If the factory value is null, then a default
+ * factory is used (only do this to reset the factory after having
+ * previously altered it).
+ *
+ * @param factory The new ServerSocketFactory the SocketClient should use.
+ * @since 2.0
+ */
+ public void setServerSocketFactory(ServerSocketFactory factory) {
+ if (factory == null) {
+ _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
+ } else {
+ _serverSocketFactory_ = factory;
+ }
+ }
+
+ /**
+ * Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's
+ * connect() method.
+ * @param connectTimeout The connection timeout to use (in ms)
+ * @since 2.0
+ */
+ public void setConnectTimeout(int connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ }
+
+ /**
+ * Get the underlying socket connection timeout.
+ * @return timeout (in ms)
+ * @since 2.0
+ */
+ public int getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ /**
+ * Get the underlying {@link ServerSocketFactory}
+ * @return The server socket factory
+ * @since 2.2
+ */
+ public ServerSocketFactory getServerSocketFactory() {
+ return _serverSocketFactory_;
+ }
+
+
+ /**
+ * Adds a ProtocolCommandListener.
+ *
+ * @param listener The ProtocolCommandListener to add.
+ * @since 3.0
+ */
+ public void addProtocolCommandListener(ProtocolCommandListener listener) {
+ getCommandSupport().addProtocolCommandListener(listener);
+ }
+
+ /**
+ * Removes a ProtocolCommandListener.
+ *
+ * @param listener The ProtocolCommandListener to remove.
+ * @since 3.0
+ */
+ public void removeProtocolCommandListener(ProtocolCommandListener listener) {
+ getCommandSupport().removeProtocolCommandListener(listener);
+ }
+
+ /**
+ * If there are any listeners, send them the reply details.
+ *
+ * @param replyCode the code extracted from the reply
+ * @param reply the full reply text
+ * @since 3.0
+ */
+ protected void fireReplyReceived(int replyCode, String reply) {
+ if (getCommandSupport().getListenerCount() > 0) {
+ getCommandSupport().fireReplyReceived(replyCode, reply);
+ }
+ }
+
+ /**
+ * If there are any listeners, send them the command details.
+ *
+ * @param command the command name
+ * @param message the complete message, including command name
+ * @since 3.0
+ */
+ protected void fireCommandSent(String command, String message) {
+ if (getCommandSupport().getListenerCount() > 0) {
+ getCommandSupport().fireCommandSent(command, message);
+ }
+ }
+
+ /**
+ * Create the CommandSupport instance if required
+ */
+ protected void createCommandSupport(){
+ __commandSupport = new ProtocolCommandSupport(this);
+ }
+
+ /**
+ * Subclasses can override this if they need to provide their own
+ * instance field for backwards compatibilty.
+ *
+ * @return the CommandSupport instance, may be {@code null}
+ * @since 3.0
+ */
+ protected ProtocolCommandSupport getCommandSupport() {
+ return __commandSupport;
+ }
+
+ /**
+ * Sets the proxy for use with all the connections.
+ * The proxy is used for connections established after the
+ * call to this method.
+ *
+ * @param proxy the new proxy for connections.
+ * @since 3.2
+ */
+ public void setProxy(Proxy proxy) {
+ setSocketFactory(new DefaultSocketFactory(proxy));
+ connProxy = proxy;
+ }
+
+ /**
+ * Gets the proxy for use with all the connections.
+ * @return the current proxy for connections.
+ */
+ public Proxy getProxy() {
+ return connProxy;
+ }
+
+ /**
+ * Gets the charset name.
+ *
+ * @return the charset.
+ * @since 3.3
+ * @deprecated Since the code now requires Java 1.6 as a mininmum
+ */
+ @Deprecated
+ public String getCharsetName() {
+ return charset.name();
+ }
+
+ /**
+ * Gets the charset.
+ *
+ * @return the charset.
+ * @since 3.3
+ */
+ public Charset getCharset() {
+ return charset;
+ }
+
+ /**
+ * Sets the charset.
+ *
+ * @param charset the charset.
+ * @since 3.3
+ */
+ public void setCharset(Charset charset) {
+ this.charset = charset;
+ }
+
+ /*
+ * N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility,
+ * so the abstract method is needed to pass the instance to the methods which were moved here.
+ */
+}
+
+
diff --git a/client/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java b/client/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java
new file mode 100644
index 00000000000..a48f59c2a95
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * Implements the telnet echo option RFC 857.
+ ***/
+public class EchoOptionHandler extends TelnetOptionHandler
+{
+ /***
+ * Constructor for the EchoOptionHandler. Allows defining desired
+ * initial setting for local/remote activation of this option and
+ * behaviour in case a local/remote activation request for this
+ * option is received.
+ *
+ * @param initlocal - if set to true, a WILL is sent upon connection.
+ * @param initremote - if set to true, a DO is sent upon connection.
+ * @param acceptlocal - if set to true, any DO request is accepted.
+ * @param acceptremote - if set to true, any WILL request is accepted.
+ ***/
+ public EchoOptionHandler(boolean initlocal, boolean initremote,
+ boolean acceptlocal, boolean acceptremote)
+ {
+ super(TelnetOption.ECHO, initlocal, initremote,
+ acceptlocal, acceptremote);
+ }
+
+ /***
+ * Constructor for the EchoOptionHandler. Initial and accept
+ * behaviour flags are set to false
+ ***/
+ public EchoOptionHandler()
+ {
+ super(TelnetOption.ECHO, false, false, false, false);
+ }
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java b/client/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java
new file mode 100644
index 00000000000..f5954403d2c
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * The InvalidTelnetOptionException is the exception that is
+ * thrown whenever a TelnetOptionHandler with an invlaid
+ * option code is registered in TelnetClient with addOptionHandler.
+ ***/
+public class InvalidTelnetOptionException extends Exception
+{
+
+ private static final long serialVersionUID = -2516777155928793597L;
+
+ /***
+ * Option code
+ ***/
+ private final int optionCode;
+
+ /***
+ * Error message
+ ***/
+ private final String msg;
+
+ /***
+ * Constructor for the exception.
+ *
+ * @return the error message.
+ ***/
+ @Override
+ public String getMessage()
+ {
+ return (msg + ": " + optionCode);
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java b/client/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java
new file mode 100644
index 00000000000..8a88e0f25cb
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * Simple option handler that can be used for options
+ * that don't require subnegotiation.
+ ***/
+public class SimpleOptionHandler extends TelnetOptionHandler
+{
+ /***
+ * Constructor for the SimpleOptionHandler. Allows defining desired
+ * initial setting for local/remote activation of this option and
+ * behaviour in case a local/remote activation request for this
+ * option is received.
+ *
+ * @param optcode - option code.
+ * @param initlocal - if set to true, a WILL is sent upon connection.
+ * @param initremote - if set to true, a DO is sent upon connection.
+ * @param acceptlocal - if set to true, any DO request is accepted.
+ * @param acceptremote - if set to true, any WILL request is accepted.
+ ***/
+ public SimpleOptionHandler(int optcode,
+ boolean initlocal,
+ boolean initremote,
+ boolean acceptlocal,
+ boolean acceptremote)
+ {
+ super(optcode, initlocal, initremote,
+ acceptlocal, acceptremote);
+ }
+
+ /***
+ * Constructor for the SimpleOptionHandler. Initial and accept
+ * behaviour flags are set to false
+ *
+ * @param optcode - option code.
+ ***/
+ public SimpleOptionHandler(int optcode)
+ {
+ super(optcode, false, false, false, false);
+ }
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java b/client/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java
new file mode 100644
index 00000000000..b353d48154a
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * Implements the telnet suppress go ahead option RFC 858.
+ ***/
+public class SuppressGAOptionHandler extends TelnetOptionHandler
+{
+ /***
+ * Constructor for the SuppressGAOptionHandler. Allows defining desired
+ * initial setting for local/remote activation of this option and
+ * behaviour in case a local/remote activation request for this
+ * option is received.
+ *
+ * @param initlocal - if set to true, a WILL is sent upon connection.
+ * @param initremote - if set to true, a DO is sent upon connection.
+ * @param acceptlocal - if set to true, any DO request is accepted.
+ * @param acceptremote - if set to true, any WILL request is accepted.
+ ***/
+ public SuppressGAOptionHandler(boolean initlocal, boolean initremote,
+ boolean acceptlocal, boolean acceptremote)
+ {
+ super(TelnetOption.SUPPRESS_GO_AHEAD, initlocal, initremote,
+ acceptlocal, acceptremote);
+ }
+
+ /***
+ * Constructor for the SuppressGAOptionHandler. Initial and accept
+ * behaviour flags are set to false
+ ***/
+ public SuppressGAOptionHandler()
+ {
+ super(TelnetOption.SUPPRESS_GO_AHEAD, false, false, false, false);
+ }
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/Telnet.java b/client/src/main/java/org/apache/commons/net/telnet/Telnet.java
new file mode 100644
index 00000000000..c66f891d6ae
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/Telnet.java
@@ -0,0 +1,1265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.commons.net.SocketClient;
+
+class Telnet extends SocketClient
+{
+ static final boolean debug = /*true;*/ false;
+
+ static final boolean debugoptions = /*true;*/ false;
+
+ static final byte[] _COMMAND_DO = {
+ (byte)TelnetCommand.IAC, (byte)TelnetCommand.DO
+ };
+
+ static final byte[] _COMMAND_DONT = {
+ (byte)TelnetCommand.IAC, (byte)TelnetCommand.DONT
+ };
+
+ static final byte[] _COMMAND_WILL = {
+ (byte)TelnetCommand.IAC, (byte)TelnetCommand.WILL
+ };
+
+ static final byte[] _COMMAND_WONT = {
+ (byte)TelnetCommand.IAC, (byte)TelnetCommand.WONT
+ };
+
+ static final byte[] _COMMAND_SB = {
+ (byte)TelnetCommand.IAC, (byte)TelnetCommand.SB
+ };
+
+ static final byte[] _COMMAND_SE = {
+ (byte)TelnetCommand.IAC, (byte)TelnetCommand.SE
+ };
+
+ static final int _WILL_MASK = 0x01, _DO_MASK = 0x02,
+ _REQUESTED_WILL_MASK = 0x04, _REQUESTED_DO_MASK = 0x08;
+
+ /* public */
+ static final int DEFAULT_PORT = 23;
+
+ int[] _doResponse, _willResponse, _options;
+
+ /* TERMINAL-TYPE option (start)*/
+ /***
+ * Terminal type option
+ ***/
+ protected static final int TERMINAL_TYPE = 24;
+
+ /***
+ * Send (for subnegotiation)
+ ***/
+ protected static final int TERMINAL_TYPE_SEND = 1;
+
+ /***
+ * Is (for subnegotiation)
+ ***/
+ protected static final int TERMINAL_TYPE_IS = 0;
+
+ /***
+ * Is sequence (for subnegotiation)
+ ***/
+ static final byte[] _COMMAND_IS = {
+ (byte) TERMINAL_TYPE, (byte) TERMINAL_TYPE_IS
+ };
+
+ /***
+ * Terminal type
+ ***/
+ private String terminalType = null;
+ /* TERMINAL-TYPE option (end)*/
+
+ /* open TelnetOptionHandler functionality (start)*/
+ /***
+ * Array of option handlers
+ ***/
+ private final TelnetOptionHandler optionHandlers[];
+
+ /* open TelnetOptionHandler functionality (end)*/
+
+ /* Code Section added for supporting AYT (start)*/
+ /***
+ * AYT sequence
+ ***/
+ static final byte[] _COMMAND_AYT = {
+ (byte) TelnetCommand.IAC, (byte) TelnetCommand.AYT
+ };
+
+ /***
+ * monitor to wait for AYT
+ ***/
+ private final Object aytMonitor = new Object();
+
+ /***
+ * flag for AYT
+ ***/
+ private volatile boolean aytFlag = true;
+ /* Code Section added for supporting AYT (end)*/
+
+ /***
+ * The stream on which to spy
+ ***/
+ private volatile OutputStream spyStream = null;
+
+ /***
+ * The notification handler
+ ***/
+ private TelnetNotificationHandler __notifhand = null;
+ /***
+ * Empty Constructor
+ ***/
+ Telnet()
+ {
+ setDefaultPort(DEFAULT_PORT);
+ _doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
+ _willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
+ _options = new int[TelnetOption.MAX_OPTION_VALUE + 1];
+ optionHandlers =
+ new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1];
+ }
+
+ /* TERMINAL-TYPE option (start)*/
+ /***
+ * This constructor lets you specify the terminal type.
+ *
+ * @param termtype - terminal type to be negotiated (ej. VT100)
+ ***/
+ Telnet(String termtype)
+ {
+ setDefaultPort(DEFAULT_PORT);
+ _doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
+ _willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1];
+ _options = new int[TelnetOption.MAX_OPTION_VALUE + 1];
+ terminalType = termtype;
+ optionHandlers =
+ new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1];
+ }
+ /* TERMINAL-TYPE option (end)*/
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a will has been acknowledged
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _stateIsWill(int option)
+ {
+ return ((_options[option] & _WILL_MASK) != 0);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a wont has been acknowledged
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _stateIsWont(int option)
+ {
+ return !_stateIsWill(option);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a do has been acknowledged
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _stateIsDo(int option)
+ {
+ return ((_options[option] & _DO_MASK) != 0);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a dont has been acknowledged
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _stateIsDont(int option)
+ {
+ return !_stateIsDo(option);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a will has been reuqested
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _requestedWill(int option)
+ {
+ return ((_options[option] & _REQUESTED_WILL_MASK) != 0);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a wont has been reuqested
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _requestedWont(int option)
+ {
+ return !_requestedWill(option);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a do has been reuqested
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _requestedDo(int option)
+ {
+ return ((_options[option] & _REQUESTED_DO_MASK) != 0);
+ }
+
+ /***
+ * Looks for the state of the option.
+ *
+ * @return returns true if a dont has been reuqested
+ *
+ * @param option - option code to be looked up.
+ ***/
+ boolean _requestedDont(int option)
+ {
+ return !_requestedDo(option);
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ * @throws IOException
+ ***/
+ void _setWill(int option) throws IOException
+ {
+ _options[option] |= _WILL_MASK;
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (_requestedWill(option))
+ {
+ if (optionHandlers[option] != null)
+ {
+ optionHandlers[option].setWill(true);
+
+ int subneg[] =
+ optionHandlers[option].startSubnegotiationLocal();
+
+ if (subneg != null)
+ {
+ _sendSubnegotiation(subneg);
+ }
+ }
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ * @throws IOException
+ ***/
+ void _setDo(int option) throws IOException
+ {
+ _options[option] |= _DO_MASK;
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (_requestedDo(option))
+ {
+ if (optionHandlers[option] != null)
+ {
+ optionHandlers[option].setDo(true);
+
+ int subneg[] =
+ optionHandlers[option].startSubnegotiationRemote();
+
+ if (subneg != null)
+ {
+ _sendSubnegotiation(subneg);
+ }
+ }
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ ***/
+ void _setWantWill(int option)
+ {
+ _options[option] |= _REQUESTED_WILL_MASK;
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ ***/
+ void _setWantDo(int option)
+ {
+ _options[option] |= _REQUESTED_DO_MASK;
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ ***/
+ void _setWont(int option)
+ {
+ _options[option] &= ~_WILL_MASK;
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (optionHandlers[option] != null)
+ {
+ optionHandlers[option].setWill(false);
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ ***/
+ void _setDont(int option)
+ {
+ _options[option] &= ~_DO_MASK;
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (optionHandlers[option] != null)
+ {
+ optionHandlers[option].setDo(false);
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ ***/
+ void _setWantWont(int option)
+ {
+ _options[option] &= ~_REQUESTED_WILL_MASK;
+ }
+
+ /***
+ * Sets the state of the option.
+ *
+ * @param option - option code to be set.
+ ***/
+ void _setWantDont(int option)
+ {
+ _options[option] &= ~_REQUESTED_DO_MASK;
+ }
+
+ /**
+ * Processes a COMMAND.
+ *
+ * @param command - option code to be set.
+ **/
+ void _processCommand(int command)
+ {
+ if (debugoptions)
+ {
+ System.err.println("RECEIVED COMMAND: " + command);
+ }
+
+ if (__notifhand != null)
+ {
+ __notifhand.receivedNegotiation(
+ TelnetNotificationHandler.RECEIVED_COMMAND, command);
+ }
+ }
+
+ /**
+ * Processes a DO request.
+ *
+ * @param option - option code to be set.
+ * @throws IOException - Exception in I/O.
+ **/
+ void _processDo(int option) throws IOException
+ {
+ if (debugoptions)
+ {
+ System.err.println("RECEIVED DO: "
+ + TelnetOption.getOption(option));
+ }
+
+ if (__notifhand != null)
+ {
+ __notifhand.receivedNegotiation(
+ TelnetNotificationHandler.RECEIVED_DO,
+ option);
+ }
+
+ boolean acceptNewState = false;
+
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (optionHandlers[option] != null)
+ {
+ acceptNewState = optionHandlers[option].getAcceptLocal();
+ }
+ else
+ {
+ /* open TelnetOptionHandler functionality (end)*/
+ /* TERMINAL-TYPE option (start)*/
+ if (option == TERMINAL_TYPE)
+ {
+ if ((terminalType != null) && (terminalType.length() > 0))
+ {
+ acceptNewState = true;
+ }
+ }
+ /* TERMINAL-TYPE option (end)*/
+ /* open TelnetOptionHandler functionality (start)*/
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+
+ if (_willResponse[option] > 0)
+ {
+ --_willResponse[option];
+ if (_willResponse[option] > 0 && _stateIsWill(option))
+ {
+ --_willResponse[option];
+ }
+ }
+
+ if (_willResponse[option] == 0)
+ {
+ if (_requestedWont(option))
+ {
+
+ switch (option)
+ {
+
+ default:
+ break;
+
+ }
+
+
+ if (acceptNewState)
+ {
+ _setWantWill(option);
+ _sendWill(option);
+ }
+ else
+ {
+ ++_willResponse[option];
+ _sendWont(option);
+ }
+ }
+ else
+ {
+ // Other end has acknowledged option.
+
+ switch (option)
+ {
+
+ default:
+ break;
+
+ }
+
+ }
+ }
+
+ _setWill(option);
+ }
+
+ /**
+ * Processes a DONT request.
+ *
+ * @param option - option code to be set.
+ * @throws IOException - Exception in I/O.
+ **/
+ void _processDont(int option) throws IOException
+ {
+ if (debugoptions)
+ {
+ System.err.println("RECEIVED DONT: "
+ + TelnetOption.getOption(option));
+ }
+ if (__notifhand != null)
+ {
+ __notifhand.receivedNegotiation(
+ TelnetNotificationHandler.RECEIVED_DONT,
+ option);
+ }
+ if (_willResponse[option] > 0)
+ {
+ --_willResponse[option];
+ if (_willResponse[option] > 0 && _stateIsWont(option))
+ {
+ --_willResponse[option];
+ }
+ }
+
+ if (_willResponse[option] == 0 && _requestedWill(option))
+ {
+
+ switch (option)
+ {
+
+ default:
+ break;
+
+ }
+
+ /* FIX for a BUG in the negotiation (start)*/
+ if ((_stateIsWill(option)) || (_requestedWill(option)))
+ {
+ _sendWont(option);
+ }
+
+ _setWantWont(option);
+ /* FIX for a BUG in the negotiation (end)*/
+ }
+
+ _setWont(option);
+ }
+
+
+ /**
+ * Processes a WILL request.
+ *
+ * @param option - option code to be set.
+ * @throws IOException - Exception in I/O.
+ **/
+ void _processWill(int option) throws IOException
+ {
+ if (debugoptions)
+ {
+ System.err.println("RECEIVED WILL: "
+ + TelnetOption.getOption(option));
+ }
+
+ if (__notifhand != null)
+ {
+ __notifhand.receivedNegotiation(
+ TelnetNotificationHandler.RECEIVED_WILL,
+ option);
+ }
+
+ boolean acceptNewState = false;
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (optionHandlers[option] != null)
+ {
+ acceptNewState = optionHandlers[option].getAcceptRemote();
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+
+ if (_doResponse[option] > 0)
+ {
+ --_doResponse[option];
+ if (_doResponse[option] > 0 && _stateIsDo(option))
+ {
+ --_doResponse[option];
+ }
+ }
+
+ if (_doResponse[option] == 0 && _requestedDont(option))
+ {
+
+ switch (option)
+ {
+
+ default:
+ break;
+
+ }
+
+
+ if (acceptNewState)
+ {
+ _setWantDo(option);
+ _sendDo(option);
+ }
+ else
+ {
+ ++_doResponse[option];
+ _sendDont(option);
+ }
+ }
+
+ _setDo(option);
+ }
+
+ /**
+ * Processes a WONT request.
+ *
+ * @param option - option code to be set.
+ * @throws IOException - Exception in I/O.
+ **/
+ void _processWont(int option) throws IOException
+ {
+ if (debugoptions)
+ {
+ System.err.println("RECEIVED WONT: "
+ + TelnetOption.getOption(option));
+ }
+
+ if (__notifhand != null)
+ {
+ __notifhand.receivedNegotiation(
+ TelnetNotificationHandler.RECEIVED_WONT,
+ option);
+ }
+
+ if (_doResponse[option] > 0)
+ {
+ --_doResponse[option];
+ if (_doResponse[option] > 0 && _stateIsDont(option))
+ {
+ --_doResponse[option];
+ }
+ }
+
+ if (_doResponse[option] == 0 && _requestedDo(option))
+ {
+
+ switch (option)
+ {
+
+ default:
+ break;
+
+ }
+
+ /* FIX for a BUG in the negotiation (start)*/
+ if ((_stateIsDo(option)) || (_requestedDo(option)))
+ {
+ _sendDont(option);
+ }
+
+ _setWantDont(option);
+ /* FIX for a BUG in the negotiation (end)*/
+ }
+
+ _setDont(option);
+ }
+
+ /* TERMINAL-TYPE option (start)*/
+ /**
+ * Processes a suboption negotiation.
+ *
+ * @param suboption - subnegotiation data received
+ * @param suboptionLength - length of data received
+ * @throws IOException - Exception in I/O.
+ **/
+ void _processSuboption(int suboption[], int suboptionLength)
+ throws IOException
+ {
+ if (debug)
+ {
+ System.err.println("PROCESS SUBOPTION.");
+ }
+
+ /* open TelnetOptionHandler functionality (start)*/
+ if (suboptionLength > 0)
+ {
+ if (optionHandlers[suboption[0]] != null)
+ {
+ int responseSuboption[] =
+ optionHandlers[suboption[0]].answerSubnegotiation(suboption,
+ suboptionLength);
+ _sendSubnegotiation(responseSuboption);
+ }
+ else
+ {
+ if (suboptionLength > 1)
+ {
+ if (debug)
+ {
+ for (int ii = 0; ii < suboptionLength; ii++)
+ {
+ System.err.println("SUB[" + ii + "]: "
+ + suboption[ii]);
+ }
+ }
+ if ((suboption[0] == TERMINAL_TYPE)
+ && (suboption[1] == TERMINAL_TYPE_SEND))
+ {
+ _sendTerminalType();
+ }
+ }
+ }
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+ }
+
+ /***
+ * Sends terminal type information.
+ *
+ * @throws IOException - Exception in I/O.
+ ***/
+ final synchronized void _sendTerminalType()
+ throws IOException
+ {
+ if (debug)
+ {
+ System.err.println("SEND TERMINAL-TYPE: " + terminalType);
+ }
+ if (terminalType != null)
+ {
+ _output_.write(_COMMAND_SB);
+ _output_.write(_COMMAND_IS);
+ _output_.write(terminalType.getBytes(getCharset()));
+ _output_.write(_COMMAND_SE);
+ _output_.flush();
+ }
+ }
+
+ /* TERMINAL-TYPE option (end)*/
+
+ /* open TelnetOptionHandler functionality (start)*/
+ /**
+ * Manages subnegotiation for Terminal Type.
+ *
+ * @param subn - subnegotiation data to be sent
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _sendSubnegotiation(int subn[])
+ throws IOException
+ {
+ if (debug)
+ {
+ System.err.println("SEND SUBNEGOTIATION: ");
+ if (subn != null)
+ {
+ System.err.println(Arrays.toString(subn));
+ }
+ }
+ if (subn != null)
+ {
+ _output_.write(_COMMAND_SB);
+ // Note _output_ is buffered, so might as well simplify by writing single bytes
+ for (int element : subn)
+ {
+ byte b = (byte) element;
+ if (b == (byte) TelnetCommand.IAC) { // cast is necessary because IAC is outside the signed byte range
+ _output_.write(b); // double any IAC bytes
+ }
+ _output_.write(b);
+ }
+ _output_.write(_COMMAND_SE);
+
+ /* Code Section added for sending the negotiation ASAP (start)*/
+ _output_.flush();
+ /* Code Section added for sending the negotiation ASAP (end)*/
+ }
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+
+ /**
+ * Sends a command, automatically adds IAC prefix and flushes the output.
+ *
+ * @param cmd - command data to be sent
+ * @throws IOException - Exception in I/O.
+ * @since 3.0
+ */
+ final synchronized void _sendCommand(byte cmd) throws IOException
+ {
+ _output_.write(TelnetCommand.IAC);
+ _output_.write(cmd);
+ _output_.flush();
+ }
+
+ /* Code Section added for supporting AYT (start)*/
+ /***
+ * Processes the response of an AYT
+ ***/
+ final synchronized void _processAYTResponse()
+ {
+ if (!aytFlag)
+ {
+ synchronized (aytMonitor)
+ {
+ aytFlag = true;
+ aytMonitor.notifyAll();
+ }
+ }
+ }
+ /* Code Section added for supporting AYT (end)*/
+
+ /***
+ * Called upon connection.
+ *
+ * @throws IOException - Exception in I/O.
+ ***/
+ @Override
+ protected void _connectAction_() throws IOException
+ {
+ /* (start). BUGFIX: clean the option info for each connection*/
+ for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++)
+ {
+ _doResponse[ii] = 0;
+ _willResponse[ii] = 0;
+ _options[ii] = 0;
+ if (optionHandlers[ii] != null)
+ {
+ optionHandlers[ii].setDo(false);
+ optionHandlers[ii].setWill(false);
+ }
+ }
+ /* (end). BUGFIX: clean the option info for each connection*/
+
+ super._connectAction_();
+ _input_ = new BufferedInputStream(_input_);
+ _output_ = new BufferedOutputStream(_output_);
+
+ /* open TelnetOptionHandler functionality (start)*/
+ for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++)
+ {
+ if (optionHandlers[ii] != null)
+ {
+ if (optionHandlers[ii].getInitLocal())
+ {
+ _requestWill(optionHandlers[ii].getOptionCode());
+ }
+
+ if (optionHandlers[ii].getInitRemote())
+ {
+ _requestDo(optionHandlers[ii].getOptionCode());
+ }
+ }
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+ }
+
+ /**
+ * Sends a DO.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _sendDo(int option)
+ throws IOException
+ {
+ if (debug || debugoptions)
+ {
+ System.err.println("DO: " + TelnetOption.getOption(option));
+ }
+ _output_.write(_COMMAND_DO);
+ _output_.write(option);
+
+ /* Code Section added for sending the negotiation ASAP (start)*/
+ _output_.flush();
+ /* Code Section added for sending the negotiation ASAP (end)*/
+ }
+
+ /**
+ * Requests a DO.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _requestDo(int option)
+ throws IOException
+ {
+ if ((_doResponse[option] == 0 && _stateIsDo(option))
+ || _requestedDo(option))
+ {
+ return ;
+ }
+ _setWantDo(option);
+ ++_doResponse[option];
+ _sendDo(option);
+ }
+
+ /**
+ * Sends a DONT.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _sendDont(int option)
+ throws IOException
+ {
+ if (debug || debugoptions)
+ {
+ System.err.println("DONT: " + TelnetOption.getOption(option));
+ }
+ _output_.write(_COMMAND_DONT);
+ _output_.write(option);
+
+ /* Code Section added for sending the negotiation ASAP (start)*/
+ _output_.flush();
+ /* Code Section added for sending the negotiation ASAP (end)*/
+ }
+
+ /**
+ * Requests a DONT.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _requestDont(int option)
+ throws IOException
+ {
+ if ((_doResponse[option] == 0 && _stateIsDont(option))
+ || _requestedDont(option))
+ {
+ return ;
+ }
+ _setWantDont(option);
+ ++_doResponse[option];
+ _sendDont(option);
+ }
+
+
+ /**
+ * Sends a WILL.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _sendWill(int option)
+ throws IOException
+ {
+ if (debug || debugoptions)
+ {
+ System.err.println("WILL: " + TelnetOption.getOption(option));
+ }
+ _output_.write(_COMMAND_WILL);
+ _output_.write(option);
+
+ /* Code Section added for sending the negotiation ASAP (start)*/
+ _output_.flush();
+ /* Code Section added for sending the negotiation ASAP (end)*/
+ }
+
+ /**
+ * Requests a WILL.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _requestWill(int option)
+ throws IOException
+ {
+ if ((_willResponse[option] == 0 && _stateIsWill(option))
+ || _requestedWill(option))
+ {
+ return ;
+ }
+ _setWantWill(option);
+ ++_doResponse[option];
+ _sendWill(option);
+ }
+
+ /**
+ * Sends a WONT.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _sendWont(int option)
+ throws IOException
+ {
+ if (debug || debugoptions)
+ {
+ System.err.println("WONT: " + TelnetOption.getOption(option));
+ }
+ _output_.write(_COMMAND_WONT);
+ _output_.write(option);
+
+ /* Code Section added for sending the negotiation ASAP (start)*/
+ _output_.flush();
+ /* Code Section added for sending the negotiation ASAP (end)*/
+ }
+
+ /**
+ * Requests a WONT.
+ *
+ * @param option - Option code.
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _requestWont(int option)
+ throws IOException
+ {
+ if ((_willResponse[option] == 0 && _stateIsWont(option))
+ || _requestedWont(option))
+ {
+ return ;
+ }
+ _setWantWont(option);
+ ++_doResponse[option];
+ _sendWont(option);
+ }
+
+ /**
+ * Sends a byte.
+ *
+ * @param b - byte to send
+ * @throws IOException - Exception in I/O.
+ **/
+ final synchronized void _sendByte(int b)
+ throws IOException
+ {
+ _output_.write(b);
+
+ /* Code Section added for supporting spystreams (start)*/
+ _spyWrite(b);
+ /* Code Section added for supporting spystreams (end)*/
+
+ }
+
+ /* Code Section added for supporting AYT (start)*/
+ /**
+ * Sends an Are You There sequence and waits for the result.
+ *
+ * @param timeout - Time to wait for a response (millis.)
+ * @throws IOException - Exception in I/O.
+ * @throws IllegalArgumentException - Illegal argument
+ * @throws InterruptedException - Interrupted during wait.
+ * @return true if AYT received a response, false otherwise
+ **/
+ final boolean _sendAYT(long timeout)
+ throws IOException, IllegalArgumentException, InterruptedException
+ {
+ boolean retValue = false;
+ synchronized (aytMonitor)
+ {
+ synchronized (this)
+ {
+ aytFlag = false;
+ _output_.write(_COMMAND_AYT);
+ _output_.flush();
+ }
+ aytMonitor.wait(timeout);
+ if (aytFlag == false)
+ {
+ retValue = false;
+ aytFlag = true;
+ }
+ else
+ {
+ retValue = true;
+ }
+ }
+
+ return (retValue);
+ }
+ /* Code Section added for supporting AYT (end)*/
+
+ /* open TelnetOptionHandler functionality (start)*/
+
+ /**
+ * Registers a new TelnetOptionHandler for this telnet to use.
+ *
+ * @param opthand - option handler to be registered.
+ * @throws InvalidTelnetOptionException - The option code is invalid.
+ * @throws IOException on error
+ **/
+ void addOptionHandler(TelnetOptionHandler opthand)
+ throws InvalidTelnetOptionException, IOException
+ {
+ int optcode = opthand.getOptionCode();
+ if (TelnetOption.isValidOption(optcode))
+ {
+ if (optionHandlers[optcode] == null)
+ {
+ optionHandlers[optcode] = opthand;
+ if (isConnected())
+ {
+ if (opthand.getInitLocal())
+ {
+ _requestWill(optcode);
+ }
+
+ if (opthand.getInitRemote())
+ {
+ _requestDo(optcode);
+ }
+ }
+ }
+ else
+ {
+ throw (new InvalidTelnetOptionException(
+ "Already registered option", optcode));
+ }
+ }
+ else
+ {
+ throw (new InvalidTelnetOptionException(
+ "Invalid Option Code", optcode));
+ }
+ }
+
+ /**
+ * Unregisters a TelnetOptionHandler.
+ *
+ * @param optcode - Code of the option to be unregistered.
+ * @throws InvalidTelnetOptionException - The option code is invalid.
+ * @throws IOException on error
+ **/
+ void deleteOptionHandler(int optcode)
+ throws InvalidTelnetOptionException, IOException
+ {
+ if (TelnetOption.isValidOption(optcode))
+ {
+ if (optionHandlers[optcode] == null)
+ {
+ throw (new InvalidTelnetOptionException(
+ "Unregistered option", optcode));
+ }
+ else
+ {
+ TelnetOptionHandler opthand = optionHandlers[optcode];
+ optionHandlers[optcode] = null;
+
+ if (opthand.getWill())
+ {
+ _requestWont(optcode);
+ }
+
+ if (opthand.getDo())
+ {
+ _requestDont(optcode);
+ }
+ }
+ }
+ else
+ {
+ throw (new InvalidTelnetOptionException(
+ "Invalid Option Code", optcode));
+ }
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+
+ /* Code Section added for supporting spystreams (start)*/
+ /***
+ * Registers an OutputStream for spying what's going on in
+ * the Telnet session.
+ *
+ * @param spystream - OutputStream on which session activity
+ * will be echoed.
+ ***/
+ void _registerSpyStream(OutputStream spystream)
+ {
+ spyStream = spystream;
+ }
+
+ /***
+ * Stops spying this Telnet.
+ *
+ ***/
+ void _stopSpyStream()
+ {
+ spyStream = null;
+ }
+
+ /***
+ * Sends a read char on the spy stream.
+ *
+ * @param ch - character read from the session
+ ***/
+ void _spyRead(int ch)
+ {
+ OutputStream spy = spyStream;
+ if (spy != null)
+ {
+ try
+ {
+ if (ch != '\r') // never write '\r' on its own
+ {
+ if (ch == '\n')
+ {
+ spy.write('\r'); // add '\r' before '\n'
+ }
+ spy.write(ch); // write original character
+ spy.flush();
+ }
+ }
+ catch (IOException e)
+ {
+ spyStream = null;
+ }
+ }
+ }
+
+ /***
+ * Sends a written char on the spy stream.
+ *
+ * @param ch - character written to the session
+ ***/
+ void _spyWrite(int ch)
+ {
+ if (!(_stateIsDo(TelnetOption.ECHO)
+ && _requestedDo(TelnetOption.ECHO)))
+ {
+ OutputStream spy = spyStream;
+ if (spy != null)
+ {
+ try
+ {
+ spy.write(ch);
+ spy.flush();
+ }
+ catch (IOException e)
+ {
+ spyStream = null;
+ }
+ }
+ }
+ }
+ /* Code Section added for supporting spystreams (end)*/
+
+ /***
+ * Registers a notification handler to which will be sent
+ * notifications of received telnet option negotiation commands.
+ *
+ * @param notifhand - TelnetNotificationHandler to be registered
+ ***/
+ public void registerNotifHandler(TelnetNotificationHandler notifhand)
+ {
+ __notifhand = notifhand;
+ }
+
+ /***
+ * Unregisters the current notification handler.
+ *
+ ***/
+ public void unregisterNotifHandler()
+ {
+ __notifhand = null;
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetClient.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetClient.java
new file mode 100644
index 00000000000..d53067c68d2
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetClient.java
@@ -0,0 +1,414 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/***
+ * The TelnetClient class implements the simple network virtual
+ * terminal (NVT) for the Telnet protocol according to RFC 854. It
+ * does not implement any of the extra Telnet options because it
+ * is meant to be used within a Java program providing automated
+ * access to Telnet accessible resources.
+ *
+ * The class can be used by first connecting to a server using the
+ * SocketClient
+ * {@link org.apache.commons.net.SocketClient#connect connect}
+ * method. Then an InputStream and OutputStream for sending and
+ * receiving data over the Telnet connection can be obtained by
+ * using the {@link #getInputStream getInputStream() } and
+ * {@link #getOutputStream getOutputStream() } methods.
+ * When you finish using the streams, you must call
+ * {@link #disconnect disconnect } rather than simply
+ * closing the streams.
+ ***/
+
+public class TelnetClient extends Telnet
+{
+ private InputStream __input;
+ private OutputStream __output;
+ protected boolean readerThread = true;
+ private TelnetInputListener inputListener;
+
+ /***
+ * Default TelnetClient constructor, sets terminal-type {@code VT100}.
+ ***/
+ public TelnetClient()
+ {
+ /* TERMINAL-TYPE option (start)*/
+ super ("VT100");
+ /* TERMINAL-TYPE option (end)*/
+ __input = null;
+ __output = null;
+ }
+
+ /**
+ * Construct an instance with the specified terminal type.
+ *
+ * @param termtype the terminal type to use, e.g. {@code VT100}
+ */
+ /* TERMINAL-TYPE option (start)*/
+ public TelnetClient(String termtype)
+ {
+ super (termtype);
+ __input = null;
+ __output = null;
+ }
+ /* TERMINAL-TYPE option (end)*/
+
+ void _flushOutputStream() throws IOException
+ {
+ _output_.flush();
+ }
+ void _closeOutputStream() throws IOException
+ {
+ _output_.close();
+ }
+
+ /***
+ * Handles special connection requirements.
+ *
+ * @exception IOException If an error occurs during connection setup.
+ ***/
+ @Override
+ protected void _connectAction_() throws IOException
+ {
+ super._connectAction_();
+ TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread);
+ if(readerThread)
+ {
+ tmp._start();
+ }
+ // __input CANNOT refer to the TelnetInputStream. We run into
+ // blocking problems when some classes use TelnetInputStream, so
+ // we wrap it with a BufferedInputStream which we know is safe.
+ // This blocking behavior requires further investigation, but right
+ // now it looks like classes like InputStreamReader are not implemented
+ // in a safe manner.
+ __input = new BufferedInputStream(tmp);
+ __output = new TelnetOutputStream(this);
+ }
+
+ /***
+ * Disconnects the telnet session, closing the input and output streams
+ * as well as the socket. If you have references to the
+ * input and output streams of the telnet connection, you should not
+ * close them yourself, but rather call disconnect to properly close
+ * the connection.
+ ***/
+ @Override
+ public void disconnect() throws IOException
+ {
+ if (__input != null) {
+ __input.close();
+ }
+ if (__output != null) {
+ __output.close();
+ }
+ super.disconnect();
+ }
+
+ /***
+ * Returns the telnet connection output stream. You should not close the
+ * stream when you finish with it. Rather, you should call
+ * {@link #disconnect disconnect }.
+ *
+ * @return The telnet connection output stream.
+ ***/
+ public OutputStream getOutputStream()
+ {
+ return __output;
+ }
+
+ /***
+ * Returns the telnet connection input stream. You should not close the
+ * stream when you finish with it. Rather, you should call
+ * {@link #disconnect disconnect }.
+ *
+ * @return The telnet connection input stream.
+ ***/
+ public InputStream getInputStream()
+ {
+ return __input;
+ }
+
+ /***
+ * Returns the state of the option on the local side.
+ *
+ * @param option - Option to be checked.
+ *
+ * @return The state of the option on the local side.
+ ***/
+ public boolean getLocalOptionState(int option)
+ {
+ /* BUG (option active when not already acknowledged) (start)*/
+ return (_stateIsWill(option) && _requestedWill(option));
+ /* BUG (option active when not already acknowledged) (end)*/
+ }
+
+ /***
+ * Returns the state of the option on the remote side.
+ *
+ * @param option - Option to be checked.
+ *
+ * @return The state of the option on the remote side.
+ ***/
+ public boolean getRemoteOptionState(int option)
+ {
+ /* BUG (option active when not already acknowledged) (start)*/
+ return (_stateIsDo(option) && _requestedDo(option));
+ /* BUG (option active when not already acknowledged) (end)*/
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+
+ /* Code Section added for supporting AYT (start)*/
+
+ /***
+ * Sends an Are You There sequence and waits for the result.
+ *
+ * @param timeout - Time to wait for a response (millis.)
+ *
+ * @return true if AYT received a response, false otherwise
+ *
+ * @throws InterruptedException on error
+ * @throws IllegalArgumentException on error
+ * @throws IOException on error
+ ***/
+ public boolean sendAYT(long timeout)
+ throws IOException, IllegalArgumentException, InterruptedException
+ {
+ return (_sendAYT(timeout));
+ }
+ /* Code Section added for supporting AYT (start)*/
+
+ /***
+ * Sends a protocol-specific subnegotiation message to the remote peer.
+ * {@link TelnetClient} will add the IAC SB & IAC SE framing bytes;
+ * the first byte in {@code message} should be the appropriate telnet
+ * option code.
+ *
+ *
+ * This method does not wait for any response. Subnegotiation messages
+ * sent by the remote end can be handled by registering an approrpriate
+ * {@link TelnetOptionHandler}.
+ *
+ *
+ * @param message option code followed by subnegotiation payload
+ * @throws IllegalArgumentException if {@code message} has length zero
+ * @throws IOException if an I/O error occurs while writing the message
+ * @since 3.0
+ ***/
+ public void sendSubnegotiation(int[] message)
+ throws IOException, IllegalArgumentException
+ {
+ if (message.length < 1) {
+ throw new IllegalArgumentException("zero length message");
+ }
+ _sendSubnegotiation(message);
+ }
+
+ /***
+ * Sends a command byte to the remote peer, adding the IAC prefix.
+ *
+ *
+ * This method does not wait for any response. Messages
+ * sent by the remote end can be handled by registering an approrpriate
+ * {@link TelnetOptionHandler}.
+ *
+ *
+ * @param command the code for the command
+ * @throws IOException if an I/O error occurs while writing the message
+ * @throws IllegalArgumentException on error
+ * @since 3.0
+ ***/
+ public void sendCommand(byte command)
+ throws IOException, IllegalArgumentException
+ {
+ _sendCommand(command);
+ }
+
+ /* open TelnetOptionHandler functionality (start)*/
+
+ /***
+ * Registers a new TelnetOptionHandler for this telnet client to use.
+ *
+ * @param opthand - option handler to be registered.
+ *
+ * @throws InvalidTelnetOptionException on error
+ * @throws IOException on error
+ ***/
+ @Override
+ public void addOptionHandler(TelnetOptionHandler opthand)
+ throws InvalidTelnetOptionException, IOException
+ {
+ super.addOptionHandler(opthand);
+ }
+ /* open TelnetOptionHandler functionality (end)*/
+
+ /***
+ * Unregisters a TelnetOptionHandler.
+ *
+ * @param optcode - Code of the option to be unregistered.
+ *
+ * @throws InvalidTelnetOptionException on error
+ * @throws IOException on error
+ ***/
+ @Override
+ public void deleteOptionHandler(int optcode)
+ throws InvalidTelnetOptionException, IOException
+ {
+ super.deleteOptionHandler(optcode);
+ }
+
+ /* Code Section added for supporting spystreams (start)*/
+ /***
+ * Registers an OutputStream for spying what's going on in
+ * the TelnetClient session.
+ *
+ * @param spystream - OutputStream on which session activity
+ * will be echoed.
+ ***/
+ public void registerSpyStream(OutputStream spystream)
+ {
+ super._registerSpyStream(spystream);
+ }
+
+ /***
+ * Stops spying this TelnetClient.
+ *
+ ***/
+ public void stopSpyStream()
+ {
+ super._stopSpyStream();
+ }
+ /* Code Section added for supporting spystreams (end)*/
+
+ /***
+ * Registers a notification handler to which will be sent
+ * notifications of received telnet option negotiation commands.
+ *
+ * @param notifhand - TelnetNotificationHandler to be registered
+ ***/
+ @Override
+ public void registerNotifHandler(TelnetNotificationHandler notifhand)
+ {
+ super.registerNotifHandler(notifhand);
+ }
+
+ /***
+ * Unregisters the current notification handler.
+ *
+ ***/
+ @Override
+ public void unregisterNotifHandler()
+ {
+ super.unregisterNotifHandler();
+ }
+
+ /***
+ * Sets the status of the reader thread.
+ *
+ *
+ * When enabled, a seaparate internal reader thread is created for new
+ * connections to read incoming data as it arrives. This results in
+ * immediate handling of option negotiation, notifications, etc.
+ * (at least until the fixed-size internal buffer fills up).
+ * Otherwise, no thread is created an all negotiation and option
+ * handling is deferred until a read() is performed on the
+ * {@link #getInputStream input stream}.
+ *
+ *
+ *
+ * The reader thread must be enabled for {@link TelnetInputListener}
+ * support.
+ *
+ *
+ *
+ * When this method is invoked, the reader thread status will apply to all
+ * subsequent connections; the current connection (if any) is not affected.
+ *
+ *
+ * @param flag true to enable the reader thread, false to disable
+ * @see #registerInputListener
+ ***/
+ public void setReaderThread(boolean flag)
+ {
+ readerThread = flag;
+ }
+
+ /***
+ * Gets the status of the reader thread.
+ *
+ * @return true if the reader thread is enabled, false otherwise
+ ***/
+ public boolean getReaderThread()
+ {
+ return (readerThread);
+ }
+
+ /***
+ * Register a listener to be notified when new incoming data is
+ * available to be read on the {@link #getInputStream input stream}.
+ * Only one listener is supported at a time.
+ *
+ *
+ * More precisely, notifications are issued whenever the number of
+ * bytes available for immediate reading (i.e., the value returned
+ * by {@link InputStream#available}) transitions from zero to non-zero.
+ * Note that (in general) multiple reads may be required to empty the
+ * buffer and reset this notification, because incoming bytes are being
+ * added to the internal buffer asynchronously.
+ *
+ *
+ *
+ * Notifications are only supported when a {@link #setReaderThread
+ * reader thread} is enabled for the connection.
+ *
+ *
+ * @param listener listener to be registered; replaces any previous
+ * @since 3.0
+ ***/
+ public synchronized void registerInputListener(TelnetInputListener listener)
+ {
+ this.inputListener = listener;
+ }
+
+ /***
+ * Unregisters the current {@link TelnetInputListener}, if any.
+ *
+ * @since 3.0
+ ***/
+ public synchronized void unregisterInputListener()
+ {
+ this.inputListener = null;
+ }
+
+ // Notify input listener
+ void notifyInputListener() {
+ TelnetInputListener listener;
+ synchronized (this) {
+ listener = this.inputListener;
+ }
+ if (listener != null) {
+ listener.telnetInputAvailable();
+ }
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java
new file mode 100644
index 00000000000..6e65f302111
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/**
+ * The TelnetCommand class cannot be instantiated and only serves as a
+ * storehouse for telnet command constants.
+ * @see org.apache.commons.net.telnet.Telnet
+ * @see org.apache.commons.net.telnet.TelnetClient
+ */
+
+public final class TelnetCommand
+{
+ /*** The maximum value a command code can have. This value is 255. ***/
+ public static final int MAX_COMMAND_VALUE = 255;
+
+ /*** Interpret As Command code. Value is 255 according to RFC 854. ***/
+ public static final int IAC = 255;
+
+ /*** Don't use option code. Value is 254 according to RFC 854. ***/
+ public static final int DONT = 254;
+
+ /*** Request to use option code. Value is 253 according to RFC 854. ***/
+ public static final int DO = 253;
+
+ /*** Refuse to use option code. Value is 252 according to RFC 854. ***/
+ public static final int WONT = 252;
+
+ /*** Agree to use option code. Value is 251 according to RFC 854. ***/
+ public static final int WILL = 251;
+
+ /*** Start subnegotiation code. Value is 250 according to RFC 854. ***/
+ public static final int SB = 250;
+
+ /*** Go Ahead code. Value is 249 according to RFC 854. ***/
+ public static final int GA = 249;
+
+ /*** Erase Line code. Value is 248 according to RFC 854. ***/
+ public static final int EL = 248;
+
+ /*** Erase Character code. Value is 247 according to RFC 854. ***/
+ public static final int EC = 247;
+
+ /*** Are You There code. Value is 246 according to RFC 854. ***/
+ public static final int AYT = 246;
+
+ /*** Abort Output code. Value is 245 according to RFC 854. ***/
+ public static final int AO = 245;
+
+ /*** Interrupt Process code. Value is 244 according to RFC 854. ***/
+ public static final int IP = 244;
+
+ /*** Break code. Value is 243 according to RFC 854. ***/
+ public static final int BREAK = 243;
+
+ /*** Data mark code. Value is 242 according to RFC 854. ***/
+ public static final int DM = 242;
+
+ /*** No Operation code. Value is 241 according to RFC 854. ***/
+ public static final int NOP = 241;
+
+ /*** End subnegotiation code. Value is 240 according to RFC 854. ***/
+ public static final int SE = 240;
+
+ /*** End of record code. Value is 239. ***/
+ public static final int EOR = 239;
+
+ /*** Abort code. Value is 238. ***/
+ public static final int ABORT = 238;
+
+ /*** Suspend process code. Value is 237. ***/
+ public static final int SUSP = 237;
+
+ /*** End of file code. Value is 236. ***/
+ public static final int EOF = 236;
+
+ /*** Synchronize code. Value is 242. ***/
+ public static final int SYNCH = 242;
+
+ /*** String representations of commands. ***/
+ private static final String __commandString[] = {
+ "IAC", "DONT", "DO", "WONT", "WILL", "SB", "GA", "EL", "EC", "AYT",
+ "AO", "IP", "BRK", "DMARK", "NOP", "SE", "EOR", "ABORT", "SUSP", "EOF"
+ };
+
+ private static final int __FIRST_COMMAND = IAC;
+ private static final int __LAST_COMMAND = EOF;
+
+ /***
+ * Returns the string representation of the telnet protocol command
+ * corresponding to the given command code.
+ *
+ * @param code The command code of the telnet protocol command.
+ * @return The string representation of the telnet protocol command.
+ ***/
+ public static final String getCommand(int code)
+ {
+ return __commandString[__FIRST_COMMAND - code];
+ }
+
+ /***
+ * Determines if a given command code is valid. Returns true if valid,
+ * false if not.
+ *
+ * @param code The command code to test.
+ * @return True if the command code is valid, false if not.
+ **/
+ public static final boolean isValidCommand(int code)
+ {
+ return (code <= __FIRST_COMMAND && code >= __LAST_COMMAND);
+ }
+
+ // Cannot be instantiated
+ private TelnetCommand()
+ { }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java
new file mode 100644
index 00000000000..34bb2b6e259
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * Listener interface used for notification that incoming data is
+ * available to be read.
+ *
+ * @see TelnetClient
+ * @since 3.0
+ ***/
+public interface TelnetInputListener
+{
+
+ /***
+ * Callback method invoked when new incoming data is available on a
+ * {@link TelnetClient}'s {@link TelnetClient#getInputStream input stream}.
+ *
+ * @see TelnetClient#registerInputListener
+ ***/
+ public void telnetInputAvailable();
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java
new file mode 100644
index 00000000000..082bbea14de
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java
@@ -0,0 +1,680 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+
+final class TelnetInputStream extends BufferedInputStream implements Runnable
+{
+ /** End of file has been reached */
+ private static final int EOF = -1;
+
+ /** Read would block */
+ private static final int WOULD_BLOCK = -2;
+
+ // TODO should these be private enums?
+ static final int _STATE_DATA = 0, _STATE_IAC = 1, _STATE_WILL = 2,
+ _STATE_WONT = 3, _STATE_DO = 4, _STATE_DONT = 5,
+ _STATE_SB = 6, _STATE_SE = 7, _STATE_CR = 8, _STATE_IAC_SB = 9;
+
+ private boolean __hasReachedEOF; // @GuardedBy("__queue")
+ private volatile boolean __isClosed;
+ private boolean __readIsWaiting;
+ private int __receiveState, __queueHead, __queueTail, __bytesAvailable;
+ private final int[] __queue;
+ private final TelnetClient __client;
+ private final Thread __thread;
+ private IOException __ioException;
+
+ /* TERMINAL-TYPE option (start)*/
+ private final int __suboption[] = new int[512];
+ private int __suboption_count = 0;
+ /* TERMINAL-TYPE option (end)*/
+
+ private volatile boolean __threaded;
+
+ TelnetInputStream(InputStream input, TelnetClient client,
+ boolean readerThread)
+ {
+ super(input);
+ __client = client;
+ __receiveState = _STATE_DATA;
+ __isClosed = true;
+ __hasReachedEOF = false;
+ // Make it 2049, because when full, one slot will go unused, and we
+ // want a 2048 byte buffer just to have a round number (base 2 that is)
+ __queue = new int[2049];
+ __queueHead = 0;
+ __queueTail = 0;
+ __bytesAvailable = 0;
+ __ioException = null;
+ __readIsWaiting = false;
+ __threaded = false;
+ if(readerThread) {
+ __thread = new Thread(this);
+ } else {
+ __thread = null;
+ }
+ }
+
+ TelnetInputStream(InputStream input, TelnetClient client) {
+ this(input, client, true);
+ }
+
+ void _start()
+ {
+ if(__thread == null) {
+ return;
+ }
+
+ int priority;
+ __isClosed = false;
+ // TODO remove this
+ // Need to set a higher priority in case JVM does not use pre-emptive
+ // threads. This should prevent scheduler induced deadlock (rather than
+ // deadlock caused by a bug in this code).
+ priority = Thread.currentThread().getPriority() + 1;
+ if (priority > Thread.MAX_PRIORITY) {
+ priority = Thread.MAX_PRIORITY;
+ }
+ __thread.setPriority(priority);
+ __thread.setDaemon(true);
+ __thread.start();
+ __threaded = true; // tell _processChar that we are running threaded
+ }
+
+
+ // synchronized(__client) critical sections are to protect against
+ // TelnetOutputStream writing through the telnet client at same time
+ // as a processDo/Will/etc. command invoked from TelnetInputStream
+ // tries to write.
+ /**
+ * Get the next byte of data.
+ * IAC commands are processed internally and do not return data.
+ *
+ * @param mayBlock true if method is allowed to block
+ * @return the next byte of data,
+ * or -1 (EOF) if end of stread reached,
+ * or -2 (WOULD_BLOCK) if mayBlock is false and there is no data available
+ */
+ private int __read(boolean mayBlock) throws IOException
+ {
+ int ch;
+
+ while (true)
+ {
+
+ // If there is no more data AND we were told not to block,
+ // just return WOULD_BLOCK (-2). (More efficient than exception.)
+ if(!mayBlock && super.available() == 0) {
+ return WOULD_BLOCK;
+ }
+
+ // Otherwise, exit only when we reach end of stream.
+ if ((ch = super.read()) < 0) {
+ return EOF;
+ }
+
+ ch = (ch & 0xff);
+
+ /* Code Section added for supporting AYT (start)*/
+ synchronized (__client)
+ {
+ __client._processAYTResponse();
+ }
+ /* Code Section added for supporting AYT (end)*/
+
+ /* Code Section added for supporting spystreams (start)*/
+ __client._spyRead(ch);
+ /* Code Section added for supporting spystreams (end)*/
+
+ switch (__receiveState)
+ {
+
+ case _STATE_CR:
+ if (ch == '\0')
+ {
+ // Strip null
+ continue;
+ }
+ // How do we handle newline after cr?
+ // else if (ch == '\n' && _requestedDont(TelnetOption.ECHO) &&
+
+ // Handle as normal data by falling through to _STATE_DATA case
+
+ //$FALL-THROUGH$
+ case _STATE_DATA:
+ if (ch == TelnetCommand.IAC)
+ {
+ __receiveState = _STATE_IAC;
+ continue;
+ }
+
+
+ if (ch == '\r')
+ {
+ synchronized (__client)
+ {
+ if (__client._requestedDont(TelnetOption.BINARY)) {
+ __receiveState = _STATE_CR;
+ } else {
+ __receiveState = _STATE_DATA;
+ }
+ }
+ } else {
+ __receiveState = _STATE_DATA;
+ }
+ break;
+
+ case _STATE_IAC:
+ switch (ch)
+ {
+ case TelnetCommand.WILL:
+ __receiveState = _STATE_WILL;
+ continue;
+ case TelnetCommand.WONT:
+ __receiveState = _STATE_WONT;
+ continue;
+ case TelnetCommand.DO:
+ __receiveState = _STATE_DO;
+ continue;
+ case TelnetCommand.DONT:
+ __receiveState = _STATE_DONT;
+ continue;
+ /* TERMINAL-TYPE option (start)*/
+ case TelnetCommand.SB:
+ __suboption_count = 0;
+ __receiveState = _STATE_SB;
+ continue;
+ /* TERMINAL-TYPE option (end)*/
+ case TelnetCommand.IAC:
+ __receiveState = _STATE_DATA;
+ break; // exit to enclosing switch to return IAC from read
+ case TelnetCommand.SE: // unexpected byte! ignore it (don't send it as a command)
+ __receiveState = _STATE_DATA;
+ continue;
+ default:
+ __receiveState = _STATE_DATA;
+ __client._processCommand(ch); // Notify the user
+ continue; // move on the next char
+ }
+ break; // exit and return from read
+ case _STATE_WILL:
+ synchronized (__client)
+ {
+ __client._processWill(ch);
+ __client._flushOutputStream();
+ }
+ __receiveState = _STATE_DATA;
+ continue;
+ case _STATE_WONT:
+ synchronized (__client)
+ {
+ __client._processWont(ch);
+ __client._flushOutputStream();
+ }
+ __receiveState = _STATE_DATA;
+ continue;
+ case _STATE_DO:
+ synchronized (__client)
+ {
+ __client._processDo(ch);
+ __client._flushOutputStream();
+ }
+ __receiveState = _STATE_DATA;
+ continue;
+ case _STATE_DONT:
+ synchronized (__client)
+ {
+ __client._processDont(ch);
+ __client._flushOutputStream();
+ }
+ __receiveState = _STATE_DATA;
+ continue;
+ /* TERMINAL-TYPE option (start)*/
+ case _STATE_SB:
+ switch (ch)
+ {
+ case TelnetCommand.IAC:
+ __receiveState = _STATE_IAC_SB;
+ continue;
+ default:
+ // store suboption char
+ if (__suboption_count < __suboption.length) {
+ __suboption[__suboption_count++] = ch;
+ }
+ break;
+ }
+ __receiveState = _STATE_SB;
+ continue;
+ case _STATE_IAC_SB: // IAC received during SB phase
+ switch (ch)
+ {
+ case TelnetCommand.SE:
+ synchronized (__client)
+ {
+ __client._processSuboption(__suboption, __suboption_count);
+ __client._flushOutputStream();
+ }
+ __receiveState = _STATE_DATA;
+ continue;
+ case TelnetCommand.IAC: // De-dup the duplicated IAC
+ if (__suboption_count < __suboption.length) {
+ __suboption[__suboption_count++] = ch;
+ }
+ break;
+ default: // unexpected byte! ignore it
+ break;
+ }
+ __receiveState = _STATE_SB;
+ continue;
+ /* TERMINAL-TYPE option (end)*/
+ }
+
+ break;
+ }
+
+ return ch;
+ }
+
+ // synchronized(__client) critical sections are to protect against
+ // TelnetOutputStream writing through the telnet client at same time
+ // as a processDo/Will/etc. command invoked from TelnetInputStream
+ // tries to write. Returns true if buffer was previously empty.
+ private boolean __processChar(int ch) throws InterruptedException
+ {
+ // Critical section because we're altering __bytesAvailable,
+ // __queueTail, and the contents of _queue.
+ boolean bufferWasEmpty;
+ synchronized (__queue)
+ {
+ bufferWasEmpty = (__bytesAvailable == 0);
+ while (__bytesAvailable >= __queue.length - 1)
+ {
+ // The queue is full. We need to wait before adding any more data to it. Hopefully the stream owner
+ // will consume some data soon!
+ if(__threaded)
+ {
+ __queue.notify();
+ try
+ {
+ __queue.wait();
+ }
+ catch (InterruptedException e)
+ {
+ throw e;
+ }
+ }
+ else
+ {
+ // We've been asked to add another character to the queue, but it is already full and there's
+ // no other thread to drain it. This should not have happened!
+ throw new IllegalStateException("Queue is full! Cannot process another character.");
+ }
+ }
+
+ // Need to do this in case we're not full, but block on a read
+ if (__readIsWaiting && __threaded)
+ {
+ __queue.notify();
+ }
+
+ __queue[__queueTail] = ch;
+ ++__bytesAvailable;
+
+ if (++__queueTail >= __queue.length) {
+ __queueTail = 0;
+ }
+ }
+ return bufferWasEmpty;
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ // Critical section because we're altering __bytesAvailable,
+ // __queueHead, and the contents of _queue in addition to
+ // testing value of __hasReachedEOF.
+ synchronized (__queue)
+ {
+
+ while (true)
+ {
+ if (__ioException != null)
+ {
+ IOException e;
+ e = __ioException;
+ __ioException = null;
+ throw e;
+ }
+
+ if (__bytesAvailable == 0)
+ {
+ // Return EOF if at end of file
+ if (__hasReachedEOF) {
+ return EOF;
+ }
+
+ // Otherwise, we have to wait for queue to get something
+ if(__threaded)
+ {
+ __queue.notify();
+ try
+ {
+ __readIsWaiting = true;
+ __queue.wait();
+ __readIsWaiting = false;
+ }
+ catch (InterruptedException e)
+ {
+ throw new InterruptedIOException("Fatal thread interruption during read.");
+ }
+ }
+ else
+ {
+ //__alreadyread = false;
+ __readIsWaiting = true;
+ int ch;
+ boolean mayBlock = true; // block on the first read only
+
+ do
+ {
+ try
+ {
+ if ((ch = __read(mayBlock)) < 0) { // must be EOF
+ if(ch != WOULD_BLOCK) {
+ return (ch);
+ }
+ }
+ }
+ catch (InterruptedIOException e)
+ {
+ synchronized (__queue)
+ {
+ __ioException = e;
+ __queue.notifyAll();
+ try
+ {
+ __queue.wait(100);
+ }
+ catch (InterruptedException interrupted)
+ {
+ // Ignored
+ }
+ }
+ return EOF;
+ }
+
+
+ try
+ {
+ if(ch != WOULD_BLOCK)
+ {
+ __processChar(ch);
+ }
+ }
+ catch (InterruptedException e)
+ {
+ if (__isClosed) {
+ return EOF;
+ }
+ }
+
+ // Reads should not block on subsequent iterations. Potentially, this could happen if the
+ // remaining buffered socket data consists entirely of Telnet command sequence and no "user" data.
+ mayBlock = false;
+
+ }
+ // Continue reading as long as there is data available and the queue is not full.
+ while (super.available() > 0 && __bytesAvailable < __queue.length - 1);
+
+ __readIsWaiting = false;
+ }
+ continue;
+ }
+ else
+ {
+ int ch;
+
+ ch = __queue[__queueHead];
+
+ if (++__queueHead >= __queue.length) {
+ __queueHead = 0;
+ }
+
+ --__bytesAvailable;
+
+ // Need to explicitly notify() so available() works properly
+ if(__bytesAvailable == 0 && __threaded) {
+ __queue.notify();
+ }
+
+ return ch;
+ }
+ }
+ }
+ }
+
+
+ /***
+ * Reads the next number of bytes from the stream into an array and
+ * returns the number of bytes read. Returns -1 if the end of the
+ * stream has been reached.
+ *
+ * @param buffer The byte array in which to store the data.
+ * @return The number of bytes read. Returns -1 if the
+ * end of the message has been reached.
+ * @exception IOException If an error occurs in reading the underlying
+ * stream.
+ ***/
+ @Override
+ public int read(byte buffer[]) throws IOException
+ {
+ return read(buffer, 0, buffer.length);
+ }
+
+
+ /***
+ * Reads the next number of bytes from the stream into an array and returns
+ * the number of bytes read. Returns -1 if the end of the
+ * message has been reached. The characters are stored in the array
+ * starting from the given offset and up to the length specified.
+ *
+ * @param buffer The byte array in which to store the data.
+ * @param offset The offset into the array at which to start storing data.
+ * @param length The number of bytes to read.
+ * @return The number of bytes read. Returns -1 if the
+ * end of the stream has been reached.
+ * @exception IOException If an error occurs while reading the underlying
+ * stream.
+ ***/
+ @Override
+ public int read(byte buffer[], int offset, int length) throws IOException
+ {
+ int ch, off;
+
+ if (length < 1) {
+ return 0;
+ }
+
+ // Critical section because run() may change __bytesAvailable
+ synchronized (__queue)
+ {
+ if (length > __bytesAvailable) {
+ length = __bytesAvailable;
+ }
+ }
+
+ if ((ch = read()) == EOF) {
+ return EOF;
+ }
+
+ off = offset;
+
+ do
+ {
+ buffer[offset++] = (byte)ch;
+ }
+ while (--length > 0 && (ch = read()) != EOF);
+
+ //__client._spyRead(buffer, off, offset - off);
+ return (offset - off);
+ }
+
+
+ /*** Returns false. Mark is not supported. ***/
+ @Override
+ public boolean markSupported()
+ {
+ return false;
+ }
+
+ @Override
+ public int available() throws IOException
+ {
+ // Critical section because run() may change __bytesAvailable
+ synchronized (__queue)
+ {
+ if (__threaded) { // Must not call super.available when running threaded: NET-466
+ return __bytesAvailable;
+ } else {
+ return __bytesAvailable + super.available();
+ }
+ }
+ }
+
+
+ // Cannot be synchronized. Will cause deadlock if run() is blocked
+ // in read because BufferedInputStream read() is synchronized.
+ @Override
+ public void close() throws IOException
+ {
+ // Completely disregard the fact thread may still be running.
+ // We can't afford to block on this close by waiting for
+ // thread to terminate because few if any JVM's will actually
+ // interrupt a system read() from the interrupt() method.
+ super.close();
+
+ synchronized (__queue)
+ {
+ __hasReachedEOF = true;
+ __isClosed = true;
+
+ if (__thread != null && __thread.isAlive())
+ {
+ __thread.interrupt();
+ }
+
+ __queue.notifyAll();
+ }
+
+ }
+
+ @Override
+ public void run()
+ {
+ int ch;
+
+ try
+ {
+_outerLoop:
+ while (!__isClosed)
+ {
+ try
+ {
+ if ((ch = __read(true)) < 0) {
+ break;
+ }
+ }
+ catch (InterruptedIOException e)
+ {
+ synchronized (__queue)
+ {
+ __ioException = e;
+ __queue.notifyAll();
+ try
+ {
+ __queue.wait(100);
+ }
+ catch (InterruptedException interrupted)
+ {
+ if (__isClosed) {
+ break _outerLoop;
+ }
+ }
+ continue;
+ }
+ } catch(RuntimeException re) {
+ // We treat any runtime exceptions as though the
+ // stream has been closed. We close the
+ // underlying stream just to be sure.
+ super.close();
+ // Breaking the loop has the effect of setting
+ // the state to closed at the end of the method.
+ break _outerLoop;
+ }
+
+ // Process new character
+ boolean notify = false;
+ try
+ {
+ notify = __processChar(ch);
+ }
+ catch (InterruptedException e)
+ {
+ if (__isClosed) {
+ break _outerLoop;
+ }
+ }
+
+ // Notify input listener if buffer was previously empty
+ if (notify) {
+ __client.notifyInputListener();
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ synchronized (__queue)
+ {
+ __ioException = ioe;
+ }
+ __client.notifyInputListener();
+ }
+
+ synchronized (__queue)
+ {
+ __isClosed = true; // Possibly redundant
+ __hasReachedEOF = true;
+ __queue.notify();
+ }
+
+ __threaded = false;
+ }
+}
+
+/* Emacs configuration
+ * Local variables: **
+ * mode: java **
+ * c-basic-offset: 4 **
+ * indent-tabs-mode: nil **
+ * End: **
+ */
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java
new file mode 100644
index 00000000000..19446dd1d67
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * The TelnetNotificationHandler interface can be used to handle
+ * notification of options negotiation commands received on a telnet
+ * session.
+ *
+ * The user can implement this interface and register a
+ * TelnetNotificationHandler by using the registerNotificationHandler()
+ * of TelnetClient to be notified of option negotiation commands.
+ ***/
+
+public interface TelnetNotificationHandler
+{
+ /***
+ * The remote party sent a DO command.
+ ***/
+ public static final int RECEIVED_DO = 1;
+
+ /***
+ * The remote party sent a DONT command.
+ ***/
+ public static final int RECEIVED_DONT = 2;
+
+ /***
+ * The remote party sent a WILL command.
+ ***/
+ public static final int RECEIVED_WILL = 3;
+
+ /***
+ * The remote party sent a WONT command.
+ ***/
+ public static final int RECEIVED_WONT = 4;
+
+ /***
+ * The remote party sent a COMMAND.
+ * @since 2.2
+ ***/
+ public static final int RECEIVED_COMMAND = 5;
+
+ /***
+ * Callback method called when TelnetClient receives an
+ * command or option negotiation command
+ *
+ * @param negotiation_code - type of (negotiation) command received
+ * (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT, RECEIVED_COMMAND)
+ *
+ * @param option_code - code of the option negotiated, or the command code itself (e.g. NOP).
+ ***/
+ public void receivedNegotiation(int negotiation_code, int option_code);
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetOption.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetOption.java
new file mode 100644
index 00000000000..5fa7d5d0990
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetOption.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * The TelnetOption class cannot be instantiated and only serves as a
+ * storehouse for telnet option constants.
+ *
+ * Details regarding Telnet option specification can be found in RFC 855.
+ *
+ *
+ * @see org.apache.commons.net.telnet.Telnet
+ * @see org.apache.commons.net.telnet.TelnetClient
+ ***/
+
+public class TelnetOption
+{
+ /*** The maximum value an option code can have. This value is 255. ***/
+ public static final int MAX_OPTION_VALUE = 255;
+
+ public static final int BINARY = 0;
+
+ public static final int ECHO = 1;
+
+ public static final int PREPARE_TO_RECONNECT = 2;
+
+ public static final int SUPPRESS_GO_AHEAD = 3;
+
+ public static final int APPROXIMATE_MESSAGE_SIZE = 4;
+
+ public static final int STATUS = 5;
+
+ public static final int TIMING_MARK = 6;
+
+ public static final int REMOTE_CONTROLLED_TRANSMISSION = 7;
+
+ public static final int NEGOTIATE_OUTPUT_LINE_WIDTH = 8;
+
+ public static final int NEGOTIATE_OUTPUT_PAGE_SIZE = 9;
+
+ public static final int NEGOTIATE_CARRIAGE_RETURN = 10;
+
+ public static final int NEGOTIATE_HORIZONTAL_TAB_STOP = 11;
+
+ public static final int NEGOTIATE_HORIZONTAL_TAB = 12;
+
+ public static final int NEGOTIATE_FORMFEED = 13;
+
+ public static final int NEGOTIATE_VERTICAL_TAB_STOP = 14;
+
+ public static final int NEGOTIATE_VERTICAL_TAB = 15;
+
+ public static final int NEGOTIATE_LINEFEED = 16;
+
+ public static final int EXTENDED_ASCII = 17;
+
+ public static final int FORCE_LOGOUT = 18;
+
+ public static final int BYTE_MACRO = 19;
+
+ public static final int DATA_ENTRY_TERMINAL = 20;
+
+ public static final int SUPDUP = 21;
+
+ public static final int SUPDUP_OUTPUT = 22;
+
+ public static final int SEND_LOCATION = 23;
+
+ public static final int TERMINAL_TYPE = 24;
+
+ public static final int END_OF_RECORD = 25;
+
+ public static final int TACACS_USER_IDENTIFICATION = 26;
+
+ public static final int OUTPUT_MARKING = 27;
+
+ public static final int TERMINAL_LOCATION_NUMBER = 28;
+
+ public static final int REGIME_3270 = 29;
+
+ public static final int X3_PAD = 30;
+
+ public static final int WINDOW_SIZE = 31;
+
+ public static final int TERMINAL_SPEED = 32;
+
+ public static final int REMOTE_FLOW_CONTROL = 33;
+
+ public static final int LINEMODE = 34;
+
+ public static final int X_DISPLAY_LOCATION = 35;
+
+ public static final int OLD_ENVIRONMENT_VARIABLES = 36;
+
+ public static final int AUTHENTICATION = 37;
+
+ public static final int ENCRYPTION = 38;
+
+ public static final int NEW_ENVIRONMENT_VARIABLES = 39;
+
+ public static final int EXTENDED_OPTIONS_LIST = 255;
+
+ @SuppressWarnings("unused")
+ private static final int __FIRST_OPTION = BINARY;
+ private static final int __LAST_OPTION = EXTENDED_OPTIONS_LIST;
+
+ private static final String __optionString[] = {
+ "BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", "NAME", "STATUS",
+ "TIMING MARK", "RCTE", "NAOL", "NAOP", "NAOCRD", "NAOHTS", "NAOHTD",
+ "NAOFFD", "NAOVTS", "NAOVTD", "NAOLFD", "EXTEND ASCII", "LOGOUT",
+ "BYTE MACRO", "DATA ENTRY TERMINAL", "SUPDUP", "SUPDUP OUTPUT",
+ "SEND LOCATION", "TERMINAL TYPE", "END OF RECORD", "TACACS UID",
+ "OUTPUT MARKING", "TTYLOC", "3270 REGIME", "X.3 PAD", "NAWS", "TSPEED",
+ "LFLOW", "LINEMODE", "XDISPLOC", "OLD-ENVIRON", "AUTHENTICATION",
+ "ENCRYPT", "NEW-ENVIRON", "TN3270E", "XAUTH", "CHARSET", "RSP",
+ "Com Port Control", "Suppress Local Echo", "Start TLS",
+ "KERMIT", "SEND-URL", "FORWARD_X", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "TELOPT PRAGMA LOGON", "TELOPT SSPI LOGON",
+ "TELOPT PRAGMA HEARTBEAT", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "", "", "",
+ "Extended-Options-List"
+ };
+
+
+ /***
+ * Returns the string representation of the telnet protocol option
+ * corresponding to the given option code.
+ *
+ * @param code The option code of the telnet protocol option
+ * @return The string representation of the telnet protocol option.
+ ***/
+ public static final String getOption(int code)
+ {
+ if(__optionString[code].length() == 0)
+ {
+ return "UNASSIGNED";
+ }
+ else
+ {
+ return __optionString[code];
+ }
+ }
+
+
+ /***
+ * Determines if a given option code is valid. Returns true if valid,
+ * false if not.
+ *
+ * @param code The option code to test.
+ * @return True if the option code is valid, false if not.
+ **/
+ public static final boolean isValidOption(int code)
+ {
+ return (code <= __LAST_OPTION);
+ }
+
+ // Cannot be instantiated
+ private TelnetOption()
+ { }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java
new file mode 100644
index 00000000000..14b4f015714
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * The TelnetOptionHandler class is the base class to be used
+ * for implementing handlers for telnet options.
+ *
+ * TelnetOptionHandler implements basic option handling
+ * functionality and defines abstract methods that must be
+ * implemented to define subnegotiation behaviour.
+ ***/
+public abstract class TelnetOptionHandler
+{
+ /***
+ * Option code
+ ***/
+ private int optionCode = -1;
+
+ /***
+ * true if the option should be activated on the local side
+ ***/
+ private boolean initialLocal = false;
+
+ /***
+ * true if the option should be activated on the remote side
+ ***/
+ private boolean initialRemote = false;
+
+ /***
+ * true if the option should be accepted on the local side
+ ***/
+ private boolean acceptLocal = false;
+
+ /***
+ * true if the option should be accepted on the remote side
+ ***/
+ private boolean acceptRemote = false;
+
+ /***
+ * true if the option is active on the local side
+ ***/
+ private boolean doFlag = false;
+
+ /***
+ * true if the option is active on the remote side
+ ***/
+ private boolean willFlag = false;
+
+ /***
+ * Constructor for the TelnetOptionHandler. Allows defining desired
+ * initial setting for local/remote activation of this option and
+ * behaviour in case a local/remote activation request for this
+ * option is received.
+ *
+ * @param optcode - Option code.
+ * @param initlocal - if set to true, a WILL is sent upon connection.
+ * @param initremote - if set to true, a DO is sent upon connection.
+ * @param acceptlocal - if set to true, any DO request is accepted.
+ * @param acceptremote - if set to true, any WILL request is accepted.
+ ***/
+ public TelnetOptionHandler(int optcode,
+ boolean initlocal,
+ boolean initremote,
+ boolean acceptlocal,
+ boolean acceptremote)
+ {
+ optionCode = optcode;
+ initialLocal = initlocal;
+ initialRemote = initremote;
+ acceptLocal = acceptlocal;
+ acceptRemote = acceptremote;
+ }
+
+
+ /***
+ * Returns the option code for this option.
+ *
+ * @return Option code.
+ ***/
+ public int getOptionCode()
+ {
+ return (optionCode);
+ }
+
+ /***
+ * Returns a boolean indicating whether to accept a DO
+ * request coming from the other end.
+ *
+ * @return true if a DO request shall be accepted.
+ ***/
+ public boolean getAcceptLocal()
+ {
+ return (acceptLocal);
+ }
+
+ /***
+ * Returns a boolean indicating whether to accept a WILL
+ * request coming from the other end.
+ *
+ * @return true if a WILL request shall be accepted.
+ ***/
+ public boolean getAcceptRemote()
+ {
+ return (acceptRemote);
+ }
+
+ /***
+ * Set behaviour of the option for DO requests coming from
+ * the other end.
+ *
+ * @param accept - if true, subsequent DO requests will be accepted.
+ ***/
+ public void setAcceptLocal(boolean accept)
+ {
+ acceptLocal = accept;
+ }
+
+ /***
+ * Set behaviour of the option for WILL requests coming from
+ * the other end.
+ *
+ * @param accept - if true, subsequent WILL requests will be accepted.
+ ***/
+ public void setAcceptRemote(boolean accept)
+ {
+ acceptRemote = accept;
+ }
+
+ /***
+ * Returns a boolean indicating whether to send a WILL request
+ * to the other end upon connection.
+ *
+ * @return true if a WILL request shall be sent upon connection.
+ ***/
+ public boolean getInitLocal()
+ {
+ return (initialLocal);
+ }
+
+ /***
+ * Returns a boolean indicating whether to send a DO request
+ * to the other end upon connection.
+ *
+ * @return true if a DO request shall be sent upon connection.
+ ***/
+ public boolean getInitRemote()
+ {
+ return (initialRemote);
+ }
+
+ /***
+ * Tells this option whether to send a WILL request upon connection.
+ *
+ * @param init - if true, a WILL request will be sent upon subsequent
+ * connections.
+ ***/
+ public void setInitLocal(boolean init)
+ {
+ initialLocal = init;
+ }
+
+ /***
+ * Tells this option whether to send a DO request upon connection.
+ *
+ * @param init - if true, a DO request will be sent upon subsequent
+ * connections.
+ ***/
+ public void setInitRemote(boolean init)
+ {
+ initialRemote = init;
+ }
+
+ /***
+ * Method called upon reception of a subnegotiation for this option
+ * coming from the other end.
+ *
+ * This implementation returns null, and
+ * must be overridden by the actual TelnetOptionHandler to specify
+ * which response must be sent for the subnegotiation request.
+ *
+ * @param suboptionData - the sequence received, without IAC SB & IAC SE
+ * @param suboptionLength - the length of data in suboption_data
+ *
+ * @return response to be sent to the subnegotiation sequence. TelnetClient
+ * will add IAC SB & IAC SE. null means no response
+ ***/
+ public int[] answerSubnegotiation(int suboptionData[], int suboptionLength) {
+ return null;
+ }
+
+ /***
+ * This method is invoked whenever this option is acknowledged active on
+ * the local end (TelnetClient sent a WILL, remote side sent a DO).
+ * The method is used to specify a subnegotiation sequence that will be
+ * sent by TelnetClient when the option is activated.
+ *
+ * This implementation returns null, and must be overriden by
+ * the actual TelnetOptionHandler to specify
+ * which response must be sent for the subnegotiation request.
+ * @return subnegotiation sequence to be sent by TelnetClient. TelnetClient
+ * will add IAC SB & IAC SE. null means no subnegotiation.
+ ***/
+ public int[] startSubnegotiationLocal() {
+ return null;
+ }
+
+ /***
+ * This method is invoked whenever this option is acknowledged active on
+ * the remote end (TelnetClient sent a DO, remote side sent a WILL).
+ * The method is used to specify a subnegotiation sequence that will be
+ * sent by TelnetClient when the option is activated.
+ *
+ * This implementation returns null, and must be overriden by
+ * the actual TelnetOptionHandler to specify
+ * which response must be sent for the subnegotiation request.
+ * @return subnegotiation sequence to be sent by TelnetClient. TelnetClient
+ * will add IAC SB & IAC SE. null means no subnegotiation.
+ ***/
+ public int[] startSubnegotiationRemote() {
+ return null;
+ }
+
+ /***
+ * Returns a boolean indicating whether a WILL request sent to the other
+ * side has been acknowledged.
+ *
+ * @return true if a WILL sent to the other side has been acknowledged.
+ ***/
+ boolean getWill()
+ {
+ return willFlag;
+ }
+
+ /***
+ * Tells this option whether a WILL request sent to the other
+ * side has been acknowledged (invoked by TelnetClient).
+ *
+ * @param state - if true, a WILL request has been acknowledged.
+ ***/
+ void setWill(boolean state)
+ {
+ willFlag = state;
+ }
+
+ /***
+ * Returns a boolean indicating whether a DO request sent to the other
+ * side has been acknowledged.
+ *
+ * @return true if a DO sent to the other side has been acknowledged.
+ ***/
+ boolean getDo()
+ {
+ return doFlag;
+ }
+
+
+ /***
+ * Tells this option whether a DO request sent to the other
+ * side has been acknowledged (invoked by TelnetClient).
+ *
+ * @param state - if true, a DO request has been acknowledged.
+ ***/
+ void setDo(boolean state)
+ {
+ doFlag = state;
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java b/client/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java
new file mode 100644
index 00000000000..2f99f3ee93b
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Wraps an output stream.
+ *
+ * In binary mode, the only conversion is to double IAC.
+ *
+ * In ASCII mode, if convertCRtoCRLF is true (currently always true), any CR is converted to CRLF.
+ * IACs are doubled.
+ * Also a bare LF is converted to CRLF and a bare CR is converted to CR\0
+ *
+ ***/
+
+
+final class TelnetOutputStream extends OutputStream
+{
+ private final TelnetClient __client;
+ // TODO there does not appear to be any way to change this value - should it be a ctor parameter?
+ private final boolean __convertCRtoCRLF = true;
+ private boolean __lastWasCR = false;
+
+ TelnetOutputStream(TelnetClient client)
+ {
+ __client = client;
+ }
+
+
+ /***
+ * Writes a byte to the stream.
+ *
+ * @param ch The byte to write.
+ * @exception IOException If an error occurs while writing to the underlying
+ * stream.
+ ***/
+ @Override
+ public void write(int ch) throws IOException
+ {
+
+ synchronized (__client)
+ {
+ ch &= 0xff;
+
+ if (__client._requestedWont(TelnetOption.BINARY)) // i.e. ASCII
+ {
+ if (__lastWasCR)
+ {
+ if (__convertCRtoCRLF)
+ {
+ __client._sendByte('\n');
+ if (ch == '\n') // i.e. was CRLF anyway
+ {
+ __lastWasCR = false;
+ return ;
+ }
+ } // __convertCRtoCRLF
+ else if (ch != '\n')
+ {
+ __client._sendByte('\0'); // RFC854 requires CR NUL for bare CR
+ }
+ }
+
+ switch (ch)
+ {
+ case '\r':
+ __client._sendByte('\r');
+ __lastWasCR = true;
+ break;
+ case '\n':
+ if (!__lastWasCR) { // convert LF to CRLF
+ __client._sendByte('\r');
+ }
+ __client._sendByte(ch);
+ __lastWasCR = false;
+ break;
+ case TelnetCommand.IAC:
+ __client._sendByte(TelnetCommand.IAC);
+ __client._sendByte(TelnetCommand.IAC);
+ __lastWasCR = false;
+ break;
+ default:
+ __client._sendByte(ch);
+ __lastWasCR = false;
+ break;
+ }
+ } // end ASCII
+ else if (ch == TelnetCommand.IAC)
+ {
+ __client._sendByte(ch);
+ __client._sendByte(TelnetCommand.IAC);
+ } else {
+ __client._sendByte(ch);
+ }
+ }
+ }
+
+
+ /***
+ * Writes a byte array to the stream.
+ *
+ * @param buffer The byte array to write.
+ * @exception IOException If an error occurs while writing to the underlying
+ * stream.
+ ***/
+ @Override
+ public void write(byte buffer[]) throws IOException
+ {
+ write(buffer, 0, buffer.length);
+ }
+
+
+ /***
+ * Writes a number of bytes from a byte array to the stream starting from
+ * a given offset.
+ *
+ * @param buffer The byte array to write.
+ * @param offset The offset into the array at which to start copying data.
+ * @param length The number of bytes to write.
+ * @exception IOException If an error occurs while writing to the underlying
+ * stream.
+ ***/
+ @Override
+ public void write(byte buffer[], int offset, int length) throws IOException
+ {
+ synchronized (__client)
+ {
+ while (length-- > 0) {
+ write(buffer[offset++]);
+ }
+ }
+ }
+
+ /*** Flushes the stream. ***/
+ @Override
+ public void flush() throws IOException
+ {
+ __client._flushOutputStream();
+ }
+
+ /*** Closes the stream. ***/
+ @Override
+ public void close() throws IOException
+ {
+ __client._closeOutputStream();
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java b/client/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java
new file mode 100644
index 00000000000..49e0fa3c857
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * Implements the telnet terminal type option RFC 1091.
+ ***/
+public class TerminalTypeOptionHandler extends TelnetOptionHandler
+{
+ /***
+ * Terminal type
+ ***/
+ private final String termType;
+
+ /***
+ * Terminal type option
+ ***/
+ protected static final int TERMINAL_TYPE = 24;
+
+ /***
+ * Send (for subnegotiation)
+ ***/
+ protected static final int TERMINAL_TYPE_SEND = 1;
+
+ /***
+ * Is (for subnegotiation)
+ ***/
+ protected static final int TERMINAL_TYPE_IS = 0;
+
+ /***
+ * Constructor for the TerminalTypeOptionHandler. Allows defining desired
+ * initial setting for local/remote activation of this option and
+ * behaviour in case a local/remote activation request for this
+ * option is received.
+ *
+ * @param termtype - terminal type that will be negotiated.
+ * @param initlocal - if set to true, a WILL is sent upon connection.
+ * @param initremote - if set to true, a DO is sent upon connection.
+ * @param acceptlocal - if set to true, any DO request is accepted.
+ * @param acceptremote - if set to true, any WILL request is accepted.
+ ***/
+ public TerminalTypeOptionHandler(String termtype,
+ boolean initlocal,
+ boolean initremote,
+ boolean acceptlocal,
+ boolean acceptremote)
+ {
+ super(TelnetOption.TERMINAL_TYPE, initlocal, initremote,
+ acceptlocal, acceptremote);
+ termType = termtype;
+ }
+
+ /***
+ * Constructor for the TerminalTypeOptionHandler. Initial and accept
+ * behaviour flags are set to false
+ *
+ * @param termtype - terminal type that will be negotiated.
+ ***/
+ public TerminalTypeOptionHandler(String termtype)
+ {
+ super(TelnetOption.TERMINAL_TYPE, false, false, false, false);
+ termType = termtype;
+ }
+
+ /***
+ * Implements the abstract method of TelnetOptionHandler.
+ *
+ * @param suboptionData - the sequence received, without IAC SB & IAC SE
+ * @param suboptionLength - the length of data in suboption_data
+ *
+ * @return terminal type information
+ ***/
+ @Override
+ public int[] answerSubnegotiation(int suboptionData[], int suboptionLength)
+ {
+ if ((suboptionData != null) && (suboptionLength > 1)
+ && (termType != null))
+ {
+ if ((suboptionData[0] == TERMINAL_TYPE)
+ && (suboptionData[1] == TERMINAL_TYPE_SEND))
+ {
+ int response[] = new int[termType.length() + 2];
+
+ response[0] = TERMINAL_TYPE;
+ response[1] = TERMINAL_TYPE_IS;
+
+ for (int ii = 0; ii < termType.length(); ii++)
+ {
+ response[ii + 2] = termType.charAt(ii);
+ }
+
+ return response;
+ }
+ }
+ return null;
+ }
+}
diff --git a/client/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java b/client/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java
new file mode 100644
index 00000000000..e1ba769537b
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.telnet;
+
+/***
+ * Implements the telnet window size option RFC 1073.
+ * @version $Id: WindowSizeOptionHandler.java 1697293 2015-08-24 01:01:00Z sebb $
+ * @since 2.0
+ ***/
+public class WindowSizeOptionHandler extends TelnetOptionHandler
+{
+ /***
+ * Horizontal Size
+ ***/
+ private int m_nWidth = 80;
+
+ /***
+ * Vertical Size
+ ***/
+ private int m_nHeight = 24;
+
+ /***
+ * Window size option
+ ***/
+ protected static final int WINDOW_SIZE = 31;
+
+ /***
+ * Constructor for the WindowSizeOptionHandler. Allows defining desired
+ * initial setting for local/remote activation of this option and
+ * behaviour in case a local/remote activation request for this
+ * option is received.
+ *
+ * @param nWidth - Window width.
+ * @param nHeight - Window Height
+ * @param initlocal - if set to true, a WILL is sent upon connection.
+ * @param initremote - if set to true, a DO is sent upon connection.
+ * @param acceptlocal - if set to true, any DO request is accepted.
+ * @param acceptremote - if set to true, any WILL request is accepted.
+ ***/
+ public WindowSizeOptionHandler(
+ int nWidth,
+ int nHeight,
+ boolean initlocal,
+ boolean initremote,
+ boolean acceptlocal,
+ boolean acceptremote
+ ) {
+ super (
+ TelnetOption.WINDOW_SIZE,
+ initlocal,
+ initremote,
+ acceptlocal,
+ acceptremote
+ );
+
+ m_nWidth = nWidth;
+ m_nHeight = nHeight;
+ }
+
+ /***
+ * Constructor for the WindowSizeOptionHandler. Initial and accept
+ * behaviour flags are set to false
+ *
+ * @param nWidth - Window width.
+ * @param nHeight - Window Height
+ ***/
+ public WindowSizeOptionHandler(
+ int nWidth,
+ int nHeight
+ ) {
+ super (
+ TelnetOption.WINDOW_SIZE,
+ false,
+ false,
+ false,
+ false
+ );
+
+ m_nWidth = nWidth;
+ m_nHeight = nHeight;
+ }
+
+ /***
+ * Implements the abstract method of TelnetOptionHandler.
+ * This will send the client Height and Width to the server.
+ *
+ * @return array to send to remote system
+ ***/
+ @Override
+ public int[] startSubnegotiationLocal()
+ {
+ int nCompoundWindowSize = m_nWidth * 0x10000 + m_nHeight;
+ int nResponseSize = 5;
+ int nIndex;
+ int nShift;
+ int nTurnedOnBits;
+
+ if ((m_nWidth % 0x100) == 0xFF) {
+ nResponseSize += 1;
+ }
+
+ if ((m_nWidth / 0x100) == 0xFF) {
+ nResponseSize += 1;
+ }
+
+ if ((m_nHeight % 0x100) == 0xFF) {
+ nResponseSize += 1;
+ }
+
+ if ((m_nHeight / 0x100) == 0xFF) {
+ nResponseSize += 1;
+ }
+
+ //
+ // allocate response array
+ //
+ int response[] = new int[nResponseSize];
+
+ //
+ // Build response array.
+ // ---------------------
+ // 1. put option name.
+ // 2. loop through Window size and fill the values,
+ // 3. duplicate 'ff' if needed.
+ //
+
+ response[0] = WINDOW_SIZE; // 1 //
+
+ for ( // 2 //
+ nIndex=1, nShift = 24;
+ nIndex < nResponseSize;
+ nIndex++, nShift -=8
+ ) {
+ nTurnedOnBits = 0xFF;
+ nTurnedOnBits <<= nShift;
+ response[nIndex] = (nCompoundWindowSize & nTurnedOnBits) >>> nShift;
+
+ if (response[nIndex] == 0xff) { // 3 //
+ nIndex++;
+ response[nIndex] = 0xff;
+ }
+ }
+
+ return response;
+ }
+
+}
diff --git a/client/src/main/java/org/apache/commons/net/util/ListenerList.java b/client/src/main/java/org/apache/commons/net/util/ListenerList.java
new file mode 100644
index 00000000000..10a3ffce35f
--- /dev/null
+++ b/client/src/main/java/org/apache/commons/net/util/ListenerList.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.util;
+
+import java.io.Serializable;
+import java.util.EventListener;
+import java.util.Iterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ */
+
+public class ListenerList implements Serializable, Iterable
+{
+ private static final long serialVersionUID = -1934227607974228213L;
+
+ private final CopyOnWriteArrayList __listeners;
+
+ public ListenerList()
+ {
+ __listeners = new CopyOnWriteArrayList();
+ }
+
+ public void addListener(EventListener listener)
+ {
+ __listeners.add(listener);
+ }
+
+ public void removeListener(EventListener listener)
+ {
+ __listeners.remove(listener);
+ }
+
+ public int getListenerCount()
+ {
+ return __listeners.size();
+ }
+
+ /**
+ * Return an {@link Iterator} for the {@link EventListener} instances.
+ *
+ * @return an {@link Iterator} for the {@link EventListener} instances
+ * @since 2.0
+ * TODO Check that this is a good defensive strategy
+ */
+ @Override
+ public Iterator iterator() {
+ return __listeners.iterator();
+ }
+
+}
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 00000000000..4c1132deaad
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,125 @@
+
+
+ 4.0.0
+
+ com.taobao.arthas
+ arthas-all
+ 3.0.0-SNAPSHOT
+
+ arthas-core
+ arthas-core
+
+
+ arthas-core
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.6
+ UTF-8
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+ attached
+
+ package
+
+
+ jar-with-dependencies
+
+
+
+ com.taobao.arthas.core.Arthas
+
+
+ core engine team, middleware group, alibaba inc.
+
+
+
+
+
+
+
+
+
+
+
+
+ org.ow2.asm
+ asm
+
+
+ org.ow2.asm
+ asm-commons
+
+
+
+
+
+
+
+
+ com.alibaba.middleware
+ termd-core
+
+
+ com.alibaba.middleware
+ cli
+
+
+ com.taobao.text
+ text.ui
+
+
+ com.fifesoft
+ rsyntaxtextarea
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ ch.qos.logback
+ logback-core
+
+
+ com.taobao.middleware
+ logger.api
+
+
+ com.alibaba
+ fastjson
+
+
+ ognl
+ ognl
+
+
+ junit
+ junit
+
+
+ org.benf
+ cfr
+
+
+
+
diff --git a/core/src/main/java/com/taobao/arthas/core/Arthas.java b/core/src/main/java/com/taobao/arthas/core/Arthas.java
new file mode 100644
index 00000000000..f5d4d90cb63
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/Arthas.java
@@ -0,0 +1,93 @@
+package com.taobao.arthas.core;
+
+import com.taobao.arthas.core.config.Configure;
+import com.taobao.middleware.cli.CLI;
+import com.taobao.middleware.cli.CLIs;
+import com.taobao.middleware.cli.CommandLine;
+import com.taobao.middleware.cli.Option;
+import com.taobao.middleware.cli.TypedOption;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Arthas启动器
+ */
+public class Arthas {
+
+ private static final String DEFAULT_TELNET_PORT = "3658";
+ private static final String DEFAULT_HTTP_PORT = "8563";
+
+ private Arthas(String[] args) throws Exception {
+ attachAgent(parse(args));
+ }
+
+ private Configure parse(String[] args) {
+ Option pid = new TypedOption().setType(Integer.class).setShortName("pid").setRequired(true);
+ Option core = new TypedOption().setType(String.class).setShortName("core").setRequired(true);
+ Option agent = new TypedOption().setType(String.class).setShortName("agent").setRequired(true);
+ Option target = new TypedOption().setType(String.class).setShortName("target-ip");
+ Option telnetPort = new TypedOption().setType(Integer.class)
+ .setShortName("telnet-port").setDefaultValue(DEFAULT_TELNET_PORT);
+ Option httpPort = new TypedOption().setType(Integer.class)
+ .setShortName("http-port").setDefaultValue(DEFAULT_HTTP_PORT);
+ CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target)
+ .addOption(telnetPort).addOption(httpPort);
+ CommandLine commandLine = cli.parse(Arrays.asList(args));
+
+ Configure configure = new Configure();
+ configure.setJavaPid((Integer) commandLine.getOptionValue("pid"));
+ configure.setArthasAgent((String) commandLine.getOptionValue("agent"));
+ configure.setArthasCore((String) commandLine.getOptionValue("core"));
+ if (commandLine.getOptionValue("target-ip") == null) {
+ throw new IllegalStateException("as.sh is too old to support web console, " +
+ "please run the following command to upgrade to latest version:" +
+ "\ncurl -sLk http://arthas.io/arthas/install.sh | sh");
+ }
+ configure.setIp((String) commandLine.getOptionValue("target-ip"));
+ configure.setTelnetPort((Integer) commandLine.getOptionValue("telnet-port"));
+ configure.setHttpPort((Integer) commandLine.getOptionValue("http-port"));
+ return configure;
+ }
+
+ private void attachAgent(Configure configure) throws Exception {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class> vmdClass = loader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
+ Class> vmClass = loader.loadClass("com.sun.tools.attach.VirtualMachine");
+
+ Object attachVmdObj = null;
+ for (Object obj : (List>) vmClass.getMethod("list", (Class>[]) null).invoke(null, (Object[]) null)) {
+ Object pid = vmdClass.getMethod("id", (Class>[]) null).invoke(obj, (Object[]) null);
+ if (pid.equals(Integer.toString(configure.getJavaPid()))) {
+ attachVmdObj = obj;
+ }
+ }
+
+ Object vmObj = null;
+ try {
+ if (null == attachVmdObj) { // 使用 attach(String pid) 这种方式
+ vmObj = vmClass.getMethod("attach", String.class).invoke(null, "" + configure.getJavaPid());
+ } else {
+ vmObj = vmClass.getMethod("attach", vmdClass).invoke(null, attachVmdObj);
+ }
+ Method loadAgent = vmClass.getMethod("loadAgent", String.class, String.class);
+ loadAgent.invoke(vmObj, configure.getArthasAgent(), configure.getArthasCore() + ";" + configure.toString());
+ } finally {
+ if (null != vmObj) {
+ vmClass.getMethod("detach", (Class>[]) null).invoke(vmObj, (Object[]) null);
+ }
+ }
+ }
+
+
+ public static void main(String[] args) {
+ try {
+ new Arthas(args);
+ } catch (Throwable t) {
+ System.err.println("Start arthas failed, exception stack trace: ");
+ t.printStackTrace();
+ System.exit(-1);
+ }
+ }
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java b/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
new file mode 100644
index 00000000000..2905a1d9709
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
@@ -0,0 +1,102 @@
+package com.taobao.arthas.core;
+
+/**
+ * 全局开关
+ * Created by vlinux on 15/6/4.
+ */
+public class GlobalOptions {
+
+ /**
+ * 是否支持系统类
+ * 这个开关打开之后将能代理到来自JVM的部分类,由于有非常强的安全风险可能会引起系统崩溃
+ * 所以这个开关默认是关闭的,除非你非常了解你要做什么,否则请不要打开
+ */
+ @Option(level = 0,
+ name = "unsafe",
+ summary = "Option to support system-level class",
+ description =
+ "This option enables to proxy functionality of JVM classes."
+ + " Due to serious security risk a JVM crash is possibly be introduced."
+ + " Do not activate it unless you are able to manage."
+ )
+ public static volatile boolean isUnsafe = false;
+
+ /**
+ * 是否支持dump被增强的类
+ * 这个开关打开这后,每次增强类的时候都将会将增强的类dump到文件中,以便于进行反编译分析
+ */
+ @Option(level = 1,
+ name = "dump",
+ summary = "Option to dump the enhanced classes",
+ description =
+ "This option enables the enhanced classes to be dumped to external file " +
+ "for further de-compilation and analysis."
+ )
+ public static volatile boolean isDump = false;
+
+ /**
+ * 是否支持批量增强
+ * 这个开关打开后,每次均是批量增强类
+ */
+ @Option(level = 1,
+ name = "batch-re-transform",
+ summary = "Option to support batch reTransform Class",
+ description = "This options enables to reTransform classes with batch mode."
+ )
+ public static volatile boolean isBatchReTransform = true;
+
+ /**
+ * 是否支持json格式化输出
+ * 这个开关打开后,使用json格式输出目标对象,配合-x参数使用
+ */
+ @Option(level = 2,
+ name = "json-format",
+ summary = "Option to support JSON format of object output",
+ description = "This option enables to format object output with JSON when -x option selected."
+ )
+ public static volatile boolean isUsingJson = false;
+
+ /**
+ * 是否关闭子类
+ */
+ @Option(
+ level = 1,
+ name = "disable-sub-class",
+ summary = "Option to control include sub class when class matching",
+ description = "This option disable to include sub class when matching class."
+ )
+ public static volatile boolean isDisableSubClass = false;
+
+ /**
+ * 是否在asm中输出
+ */
+ @Option(level = 1,
+ name = "debug-for-asm",
+ summary = "Option to print DEBUG message if ASM is involved",
+ description = "This option enables to print DEBUG message of ASM for each method invocation."
+ )
+ public static volatile boolean isDebugForAsm = false;
+
+ /**
+ * 是否日志中保存命令执行结果
+ */
+ @Option(level = 1,
+ name = "save-result",
+ summary = "Option to print command's result to log file",
+ description = "This option enables to save each command's result to log file, " +
+ "which path is ${user.home}/logs/arthas/result.log."
+ )
+ public static volatile boolean isSaveResult = false;
+
+ /**
+ * job的超时时间
+ */
+ @Option(level = 2,
+ name = "job-timeout",
+ summary = "Option to job timeout",
+ description = "This option setting job timeout,The unit can be d, h, m, s for day, hour, minute, second. "
+ + "1d is one day in default"
+ )
+ public static volatile String jobTimeout = "1d";
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/Option.java b/core/src/main/java/com/taobao/arthas/core/Option.java
new file mode 100644
index 00000000000..613c945065d
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/Option.java
@@ -0,0 +1,35 @@
+package com.taobao.arthas.core;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Arthas全局选项
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Option {
+
+ /*
+ * 选项级别,数字越小级别越高
+ */
+ int level();
+
+ /*
+ * 选项名称
+ */
+ String name();
+
+ /*
+ * 选项摘要说明
+ */
+ String summary();
+
+ /*
+ * 命令描述
+ */
+ String description();
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java b/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java
new file mode 100644
index 00000000000..55cca86c359
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java
@@ -0,0 +1,150 @@
+package com.taobao.arthas.core.advisor;
+
+/**
+ * 通知点 Created by vlinux on 15/5/20.
+ */
+public class Advice {
+
+ private final ClassLoader loader;
+ private final Class> clazz;
+ private final ArthasMethod method;
+ private final Object target;
+ private final Object[] params;
+ private final Object returnObj;
+ private final Throwable throwExp;
+
+ private final static int ACCESS_BEFORE = 1;
+ private final static int ACCESS_AFTER_RETUNING = 1 << 1;
+ private final static int ACCESS_AFTER_THROWING = 1 << 2;
+
+ private final boolean isBefore;
+ private final boolean isThrow;
+ private final boolean isReturn;
+
+ public boolean isBefore() {
+ return isBefore;
+ }
+
+ public boolean isAfterReturning() {
+ return isReturn;
+ }
+
+ public boolean isAfterThrowing() {
+ return isThrow;
+ }
+
+ public ClassLoader getLoader() {
+ return loader;
+ }
+
+ public Object getTarget() {
+ return target;
+ }
+
+ public Object[] getParams() {
+ return params;
+ }
+
+ public Object getReturnObj() {
+ return returnObj;
+ }
+
+ public Throwable getThrowExp() {
+ return throwExp;
+ }
+
+ public Class> getClazz() {
+ return clazz;
+ }
+
+ public ArthasMethod getMethod() {
+ return method;
+ }
+
+ /**
+ * for finish
+ *
+ * @param loader 类加载器
+ * @param clazz 类
+ * @param method 方法
+ * @param target 目标类
+ * @param params 调用参数
+ * @param returnObj 返回值
+ * @param throwExp 抛出异常
+ * @param access 进入场景
+ */
+ private Advice(
+ ClassLoader loader,
+ Class> clazz,
+ ArthasMethod method,
+ Object target,
+ Object[] params,
+ Object returnObj,
+ Throwable throwExp,
+ int access) {
+ this.loader = loader;
+ this.clazz = clazz;
+ this.method = method;
+ this.target = target;
+ this.params = params;
+ this.returnObj = returnObj;
+ this.throwExp = throwExp;
+ isBefore = (access & ACCESS_BEFORE) == ACCESS_BEFORE;
+ isThrow = (access & ACCESS_AFTER_THROWING) == ACCESS_AFTER_THROWING;
+ isReturn = (access & ACCESS_AFTER_RETUNING) == ACCESS_AFTER_RETUNING;
+ }
+
+ public static Advice newForBefore(ClassLoader loader,
+ Class> clazz,
+ ArthasMethod method,
+ Object target,
+ Object[] params) {
+ return new Advice(
+ loader,
+ clazz,
+ method,
+ target,
+ params,
+ null, //returnObj
+ null, //throwExp
+ ACCESS_BEFORE
+ );
+ }
+
+ public static Advice newForAfterRetuning(ClassLoader loader,
+ Class> clazz,
+ ArthasMethod method,
+ Object target,
+ Object[] params,
+ Object returnObj) {
+ return new Advice(
+ loader,
+ clazz,
+ method,
+ target,
+ params,
+ returnObj,
+ null, //throwExp
+ ACCESS_AFTER_RETUNING
+ );
+ }
+
+ public static Advice newForAfterThrowing(ClassLoader loader,
+ Class> clazz,
+ ArthasMethod method,
+ Object target,
+ Object[] params,
+ Throwable throwExp) {
+ return new Advice(
+ loader,
+ clazz,
+ method,
+ target,
+ params,
+ null, //returnObj
+ throwExp,
+ ACCESS_AFTER_THROWING
+ );
+ }
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java
new file mode 100644
index 00000000000..28d322edfd8
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java
@@ -0,0 +1,74 @@
+package com.taobao.arthas.core.advisor;
+
+/**
+ * 通知监听器
+ * Created by vlinux on 15/5/17.
+ */
+public interface AdviceListener {
+
+ /**
+ * 监听器创建
+ * 监听器被注册时触发
+ */
+ void create();
+
+ /**
+ * 监听器销毁
+ * 监听器被销毁时触发
+ */
+ void destroy();
+
+ /**
+ * 前置通知
+ *
+ * @param loader 类加载器
+ * @param className 类名
+ * @param methodName 方法名
+ * @param methodDesc 方法描述
+ * @param target 目标类实例
+ * 若目标为静态方法,则为null
+ * @param args 参数列表
+ * @throws Throwable 通知过程出错
+ */
+ void before(
+ ClassLoader loader, String className, String methodName, String methodDesc,
+ Object target, Object[] args) throws Throwable;
+
+ /**
+ * 返回通知
+ *
+ * @param loader 类加载器
+ * @param className 类名
+ * @param methodName 方法名
+ * @param methodDesc 方法描述
+ * @param target 目标类实例
+ * 若目标为静态方法,则为null
+ * @param args 参数列表
+ * @param returnObject 返回结果
+ * 若为无返回值方法(void),则为null
+ * @throws Throwable 通知过程出错
+ */
+ void afterReturning(
+ ClassLoader loader, String className, String methodName, String methodDesc,
+ Object target, Object[] args,
+ Object returnObject) throws Throwable;
+
+ /**
+ * 异常通知
+ *
+ * @param loader 类加载器
+ * @param className 类名
+ * @param methodName 方法名
+ * @param methodDesc 方法描述
+ * @param target 目标类实例
+ * 若目标为静态方法,则为null
+ * @param args 参数列表
+ * @param throwable 目标异常
+ * @throws Throwable 通知过程出错
+ */
+ void afterThrowing(
+ ClassLoader loader, String className, String methodName, String methodDesc,
+ Object target, Object[] args,
+ Throwable throwable) throws Throwable;
+
+}
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java
new file mode 100644
index 00000000000..d291c4b31af
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java
@@ -0,0 +1,43 @@
+package com.taobao.arthas.core.advisor;
+
+/**
+ * 通知监听适配器
+ */
+public class AdviceListenerAdapter implements AdviceListener {
+
+
+ @Override
+ public void create() {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ @Override
+ public void before(
+ ClassLoader loader, String className, String methodName, String methodDesc,
+ Object target, Object[] args) throws Throwable {
+
+ }
+
+ @Override
+ public void afterReturning(
+ ClassLoader loader, String className, String methodName, String methodDesc,
+ Object target, Object[] args,
+ Object returnObject) throws Throwable {
+
+ }
+
+ @Override
+ public void afterThrowing(
+ ClassLoader loader, String className, String methodName, String methodDesc,
+ Object target, Object[] args,
+ Throwable throwable) throws Throwable {
+
+ }
+
+}
+
diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java
new file mode 100644
index 00000000000..8ab3a307112
--- /dev/null
+++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java
@@ -0,0 +1,984 @@
+package com.taobao.arthas.core.advisor;
+
+import com.taobao.arthas.core.GlobalOptions;
+import com.taobao.arthas.core.util.matcher.Matcher;
+import com.taobao.arthas.core.util.*;
+import com.taobao.arthas.core.util.affect.EnhancerAffect;
+import com.taobao.arthas.core.util.collection.GaStack;
+import com.taobao.arthas.core.util.collection.ThreadUnsafeFixGaStack;
+import com.taobao.arthas.core.util.collection.ThreadUnsafeGaStack;
+import com.taobao.middleware.logger.Logger;
+import org.objectweb.asm.*;
+import org.objectweb.asm.commons.AdviceAdapter;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
+import org.objectweb.asm.commons.Method;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 通知编织者
+ *
+ *