001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.net;
019
020import java.io.PrintStream;
021import java.io.PrintWriter;
022import java.util.Locale;
023import java.util.Objects;
024
025import org.apache.commons.net.io.Util;
026
027/**
028 * This is a support class for some example programs. It is a sample implementation of the ProtocolCommandListener interface which just prints out to a
029 * specified stream all command/reply traffic.
030 *
031 * @since 2.0
032 */
033public class PrintCommandListener implements ProtocolCommandListener {
034
035    private static final String DIRECTION_MARKER_RECEIVE = "< ";
036    private static final String DIRECTION_MARKER_SEND = "> ";
037    private static final String HIDDEN_MARKER = " *******";
038    private static final String CMD_LOGIN = "LOGIN";
039    private static final String CMD_USER = "USER";
040    private static final String CMD_PASS = "PASS";
041    private final PrintWriter writer;
042    private final boolean noLogin;
043    private final char eolMarker;
044    private final boolean showDirection;
045
046    /**
047     * Constructs an instance which prints everything using the default Charset.
048     *
049     * @param printStream where to write the commands and responses e.g. System.out
050     * @since 3.0
051     */
052    @SuppressWarnings("resource")
053    public PrintCommandListener(final PrintStream printStream) {
054        this(Util.newPrintWriter(printStream));
055    }
056
057    /**
058     * Constructs an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character.
059     *
060     * @param printStream        where to write the commands and responses
061     * @param suppressLogin if {@code true}, only print command name for login
062     * @since 3.0
063     */
064    @SuppressWarnings("resource")
065    public PrintCommandListener(final PrintStream printStream, final boolean suppressLogin) {
066        this(Util.newPrintWriter(printStream), suppressLogin);
067    }
068
069    /**
070     * Constructs an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character.
071     *
072     * @param printStream        where to write the commands and responses
073     * @param suppressLogin if {@code true}, only print command name for login
074     * @param eolMarker     if non-zero, add a marker just before the EOL.
075     * @since 3.0
076     */
077    @SuppressWarnings("resource")
078    public PrintCommandListener(final PrintStream printStream, final boolean suppressLogin, final char eolMarker) {
079        this(Util.newPrintWriter(printStream), suppressLogin, eolMarker);
080    }
081
082    /**
083     * Constructs an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character.
084     *
085     * @param printStream        where to write the commands and responses
086     * @param suppressLogin if {@code true}, only print command name for login
087     * @param eolMarker     if non-zero, add a marker just before the EOL.
088     * @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output
089     * @since 3.0
090     */
091    @SuppressWarnings("resource")
092    public PrintCommandListener(final PrintStream printStream, final boolean suppressLogin, final char eolMarker, final boolean showDirection) {
093        this(Util.newPrintWriter(printStream), suppressLogin, eolMarker, showDirection);
094    }
095
096    /**
097     * Constructs the default instance which prints everything.
098     *
099     * @param writer where to write the commands and responses
100     */
101    public PrintCommandListener(final PrintWriter writer) {
102        this(writer, false);
103    }
104
105    /**
106     * Constructs an instance which optionally suppresses login command text.
107     *
108     * @param writer        where to write the commands and responses
109     * @param suppressLogin if {@code true}, only print command name for login
110     * @since 3.0
111     */
112    public PrintCommandListener(final PrintWriter writer, final boolean suppressLogin) {
113        this(writer, suppressLogin, (char) 0);
114    }
115
116    /**
117     * Constructs an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character.
118     *
119     * @param writer        where to write the commands and responses
120     * @param suppressLogin if {@code true}, only print command name for login
121     * @param eolMarker     if non-zero, add a marker just before the EOL.
122     * @since 3.0
123     */
124    public PrintCommandListener(final PrintWriter writer, final boolean suppressLogin, final char eolMarker) {
125        this(writer, suppressLogin, eolMarker, false);
126    }
127
128    /**
129     * Constructs an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character.
130     *
131     * @param writer        where to write the commands and responses, not null.
132     * @param suppressLogin if {@code true}, only print command name for login
133     * @param eolMarker     if non-zero, add a marker just before the EOL.
134     * @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output
135     * @since 3.0
136     */
137    public PrintCommandListener(final PrintWriter writer, final boolean suppressLogin, final char eolMarker, final boolean showDirection) {
138        this.writer = Objects.requireNonNull(writer, "writer");
139        this.noLogin = suppressLogin;
140        this.eolMarker = eolMarker;
141        this.showDirection = showDirection;
142    }
143
144    private String getCommand(final ProtocolCommandEvent event) {
145        return Objects.toString(event.getCommand()).toUpperCase(Locale.ROOT);
146    }
147
148    private String getMessage(final ProtocolCommandEvent event) {
149        return Objects.toString(event.getMessage());
150    }
151
152    private String getPrintableString(final String msg) {
153        if (eolMarker == 0) {
154            return msg;
155        }
156        final int pos = msg.indexOf(SocketClient.NETASCII_EOL);
157        if (pos > 0) {
158            final StringBuilder sb = new StringBuilder(msg + 1);
159            sb.append(msg.substring(0, pos));
160            sb.append(eolMarker);
161            sb.append(msg.substring(pos));
162            return sb.toString();
163        }
164        return msg;
165    }
166
167    @Override
168    public void protocolCommandSent(final ProtocolCommandEvent event) {
169        if (showDirection) {
170            writer.print(DIRECTION_MARKER_SEND);
171        }
172        if (noLogin) {
173            final String cmd = getCommand(event);
174            if (CMD_PASS.equals(cmd) || CMD_USER.equals(cmd)) {
175                writer.print(cmd);
176                writer.println(HIDDEN_MARKER); // Don't bother with EOL marker for this!
177            } else if (CMD_LOGIN.equals(cmd)) { // IMAP
178                String msg = getMessage(event);
179                msg = msg.substring(0, msg.indexOf(CMD_LOGIN) + CMD_LOGIN.length());
180                writer.print(msg);
181                writer.println(HIDDEN_MARKER); // Don't bother with EOL marker for this!
182            } else {
183                writer.print(getPrintableString(getMessage(event)));
184            }
185        } else {
186            writer.print(getPrintableString(getMessage(event)));
187        }
188        writer.flush();
189    }
190
191    @Override
192    public void protocolReplyReceived(final ProtocolCommandEvent event) {
193        if (showDirection) {
194            writer.print(DIRECTION_MARKER_RECEIVE);
195        }
196        final String message = getMessage(event);
197        final char last = message.charAt(message.length() - 1);
198        writer.print(message);
199        if (last != '\r' && last != '\n') {
200            writer.println();
201        }
202        writer.flush();
203    }
204}