0001: /**
0002: *
0003: * edtFTPj
0004: *
0005: * Copyright (C) 2000-2003 Enterprise Distributed Technologies Ltd
0006: *
0007: * www.enterprisedt.com
0008: *
0009: * This library is free software; you can redistribute it and/or
0010: * modify it under the terms of the GNU Lesser General Public
0011: * License as published by the Free Software Foundation; either
0012: * version 2.1 of the License, or (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0017: * Lesser General Public License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0022: *
0023: * Bug fixes, suggestions and comments should be should posted on
0024: * http://www.enterprisedt.com/forums/index.php
0025: *
0026: * Change Log:
0027: *
0028: * $Log: FTPControlSocket.java,v $
0029: * Revision 1.40 2007-11-13 07:14:04 bruceb
0030: * ListenOnAllInterfaces
0031: *
0032: * Revision 1.39 2007-11-07 23:53:14 bruceb
0033: * refactoring for FXP
0034: *
0035: * Revision 1.38 2007-10-23 07:20:42 bruceb
0036: * fixed doco spelling mistake
0037: *
0038: * Revision 1.37 2007/02/07 23:03:10 bruceb
0039: * added close()
0040: *
0041: * Revision 1.36 2007/02/04 23:03:30 bruceb
0042: * extra codes and extra debug
0043: *
0044: * Revision 1.35 2007/01/10 02:36:53 bruceb
0045: * sendPORTCommand takes a port number now
0046: *
0047: * Revision 1.34 2006/10/27 15:43:23 bruceb
0048: * added connect with timeout
0049: *
0050: * Revision 1.33 2006/10/17 10:28:15 bruceb
0051: * refactored to get sendPORTCommand()
0052: *
0053: * Revision 1.32 2006/10/11 08:51:44 hans
0054: * made cvsId final
0055: *
0056: * Revision 1.31 2006/08/25 20:40:54 hans
0057: * Fixed documentation.
0058: *
0059: * Revision 1.30 2006/07/27 14:11:00 bruceb
0060: * IPV6 changes (for subclass)
0061: *
0062: * Revision 1.29 2006/05/23 00:17:42 bruceb
0063: * apply timeout to active data socket
0064: *
0065: * Revision 1.28 2006/03/09 21:44:24 bruceb
0066: * made PASV parsing cleaner
0067: *
0068: * Revision 1.27 2006/02/16 19:47:57 hans
0069: * Changed comment
0070: *
0071: * Revision 1.26 2006/02/09 09:00:44 bruceb
0072: * fix to allow for missing end bracket re PASV response
0073: *
0074: * Revision 1.25 2005/09/21 08:38:53 bruceb
0075: * allow 230 to be initial server response
0076: *
0077: * Revision 1.24 2005/09/02 21:02:44 bruceb
0078: * bug fix in readreply
0079: *
0080: * Revision 1.23 2005/08/26 17:48:26 bruceb
0081: * passive ip address setting
0082: *
0083: * Revision 1.22 2005/08/04 22:08:42 hans
0084: * Remember encoding so that it can be reused when initStreams is called in places other than the constructor
0085: *
0086: * Revision 1.21 2005/06/10 15:43:59 bruceb
0087: * message length check
0088: *
0089: * Revision 1.20 2005/06/03 11:26:25 bruceb
0090: * comment change
0091: *
0092: * Revision 1.21 2005/05/15 20:44:15 bruceb
0093: * removed debug
0094: *
0095: * Revision 1.20 2005/05/15 19:46:28 bruceb
0096: * changes for testing setActivePortRange + STOR accepting 350 nonstrict
0097: *
0098: * Revision 1.19 2005/03/26 12:35:45 bruceb
0099: * allow for blank lines in server replies
0100: *
0101: * Revision 1.18 2004/11/19 08:28:10 bruceb
0102: * added setPORTIP()
0103: *
0104: * Revision 1.17 2004/10/18 15:56:46 bruceb
0105: * set encoding for sock, remove sendCommandOld etc
0106: *
0107: * Revision 1.16 2004/09/18 09:33:47 bruceb
0108: * 1.1.8 tweaks
0109: *
0110: * Revision 1.15 2004/08/31 10:46:59 bruceb
0111: * restructured reply code
0112: *
0113: * Revision 1.14 2004/07/23 23:29:57 bruceb
0114: * sendcommand public again
0115: *
0116: * Revision 1.13 2004/07/23 08:30:40 bruceb
0117: * restructured re non-strict replies
0118: *
0119: * Revision 1.12 2004/05/22 16:52:57 bruceb
0120: * message listener
0121: *
0122: * Revision 1.11 2004/05/01 17:05:15 bruceb
0123: * Logger stuff added
0124: *
0125: * Revision 1.10 2004/03/23 20:25:47 bruceb
0126: * added US-ASCII to control stream constructor
0127: *
0128: * Revision 1.9 2003/11/15 11:23:55 bruceb
0129: * changes required for ssl subclasses
0130: *
0131: * Revision 1.6 2003/05/31 14:53:44 bruceb
0132: * 1.2.2 changes
0133: *
0134: * Revision 1.5 2003/01/29 22:46:08 bruceb
0135: * minor changes
0136: *
0137: * Revision 1.4 2002/11/19 22:01:25 bruceb
0138: * changes for 1.2
0139: *
0140: * Revision 1.3 2001/10/09 20:53:46 bruceb
0141: * Active mode changes
0142: *
0143: * Revision 1.1 2001/10/05 14:42:04 bruceb
0144: * moved from old project
0145: *
0146: *
0147: */package com.enterprisedt.net.ftp;
0148:
0149: import java.io.BufferedReader;
0150: import java.io.IOException;
0151: import java.io.InputStream;
0152: import java.io.InputStreamReader;
0153: import java.io.OutputStream;
0154: import java.io.OutputStreamWriter;
0155: import java.io.Writer;
0156: import java.net.InetAddress;
0157: import java.net.ServerSocket;
0158: import java.net.Socket;
0159: import java.util.Vector;
0160:
0161: import com.enterprisedt.util.debug.Logger;
0162:
0163: /**
0164: * Supports client-side FTP operations
0165: *
0166: * @author Bruce Blackshaw
0167: * @version $Revision: 1.40 $
0168: *
0169: */
0170: public class FTPControlSocket {
0171:
0172: /**
0173: * Revision control id
0174: */
0175: public static final String cvsId = "@(#)$Id: FTPControlSocket.java,v 1.40 2007-11-13 07:14:04 bruceb Exp $";
0176:
0177: /**
0178: * Standard FTP end of line sequence
0179: */
0180: static final String EOL = "\r\n";
0181:
0182: /**
0183: * The default and standard control port number for FTP
0184: */
0185: public static final int CONTROL_PORT = 21;
0186:
0187: /**
0188: * Used to flag messages
0189: */
0190: private static final String DEBUG_ARROW = "---> ";
0191:
0192: /**
0193: * Start of password message
0194: */
0195: private static final String PASSWORD_MESSAGE = DEBUG_ARROW + "PASS";
0196:
0197: /**
0198: * Logging object
0199: */
0200: private static Logger log = Logger.getLogger("FTPControlSocket");
0201:
0202: /**
0203: * Use strict return codes if true
0204: */
0205: private boolean strictReturnCodes = true;
0206:
0207: /**
0208: * Listen to all interfaces in active mode
0209: */
0210: protected boolean listenOnAllInterfaces = true;
0211:
0212: /**
0213: * The underlying socket.
0214: */
0215: protected Socket controlSock = null;
0216:
0217: /**
0218: * The write that writes to the control socket
0219: */
0220: protected Writer writer = null;
0221:
0222: /**
0223: * The reader that reads control data from the
0224: * control socket
0225: */
0226: protected BufferedReader reader = null;
0227:
0228: /**
0229: * Message listener
0230: */
0231: private FTPMessageListener messageListener = null;
0232:
0233: /**
0234: * IP address we force PORT to send - useful with certain
0235: * NAT configurations
0236: */
0237: protected String forcedActiveIP;
0238:
0239: /**
0240: * Lowest port in active mode port range
0241: */
0242: private int lowPort = -1;
0243:
0244: /**
0245: * Highest port in active mode port range
0246: */
0247: private int highPort = -1;
0248:
0249: /**
0250: * Next port number to use. 0 indicates let Java decide
0251: */
0252: private int nextPort = 0;
0253:
0254: /**
0255: * Character encoding.
0256: */
0257: private String encoding;
0258:
0259: /**
0260: * The remote address to connect to
0261: */
0262: protected InetAddress remoteAddr;
0263:
0264: /**
0265: * If true, uses the original host IP if an internal IP address
0266: * is returned by the server in PASV mode
0267: */
0268: protected boolean autoPassiveIPSubstitution = false;
0269:
0270: /**
0271: * Constructor. Performs TCP connection and
0272: * sets up reader/writer. Allows different control
0273: * port to be used
0274: *
0275: * @param remoteAddr Remote inet address
0276: * @param controlPort port for control stream
0277: * @param timeout the length of the timeout, in milliseconds
0278: * @param encoding character encoding used for data
0279: * @param messageListener listens for messages
0280: */
0281: protected FTPControlSocket(InetAddress remoteAddr, int controlPort,
0282: int timeout, String encoding,
0283: FTPMessageListener messageListener) throws IOException,
0284: FTPException {
0285:
0286: this (remoteAddr, SocketUtils.createSocket(remoteAddr,
0287: controlPort, timeout), timeout, encoding,
0288: messageListener);
0289: }
0290:
0291: /**
0292: * Constructs a new <code>FTPControlSocket</code> using the given
0293: * <code>Socket</code> object.
0294: *
0295: * @param remoteAddr the remote address
0296: * @param controlSock Socket to be used.
0297: * @param timeout Timeout to be used.
0298: * @param encoding character encoding used for data
0299: * @param messageListener listens for messages
0300: *
0301: * @throws IOException Thrown if no connection response could be read from the server.
0302: * @throws FTPException Thrown if the incorrect connection response was sent by the server.
0303: */
0304: protected FTPControlSocket(InetAddress remoteAddr,
0305: Socket controlSock, int timeout, String encoding,
0306: FTPMessageListener messageListener) throws IOException,
0307: FTPException {
0308:
0309: this .remoteAddr = remoteAddr;
0310: this .controlSock = controlSock;
0311: this .messageListener = messageListener;
0312: this .encoding = encoding;
0313:
0314: setTimeout(timeout);
0315: initStreams();
0316: validateConnection();
0317: }
0318:
0319: /**
0320: * Set automatic substitution of the remote host IP on if
0321: * in passive mode
0322: *
0323: * @param autoPassiveIPSubstitution true if set to on, false otherwise
0324: */
0325: protected void setAutoPassiveIPSubstitution(
0326: boolean autoPassiveIPSubstitution) {
0327: this .autoPassiveIPSubstitution = autoPassiveIPSubstitution;
0328: }
0329:
0330: /**
0331: * Checks that the standard 220 reply is returned
0332: * following the initiated connection. Allow 230 as some
0333: * proxy servers return it
0334: */
0335: private void validateConnection() throws IOException, FTPException {
0336:
0337: FTPReply reply = readReply();
0338: String[] validCodes = { "220", "230" };
0339: validateReply(reply, validCodes);
0340: }
0341:
0342: /**
0343: * Initialize the reader/writer streams for this connection.
0344: */
0345: protected void initStreams() throws IOException {
0346:
0347: // input stream
0348: InputStream is = controlSock.getInputStream();
0349: reader = new BufferedReader(new InputStreamReader(is, encoding));
0350:
0351: // output stream
0352: OutputStream os = controlSock.getOutputStream();
0353: writer = new OutputStreamWriter(os, encoding);
0354: }
0355:
0356: /**
0357: * Get the name of the remote host
0358: *
0359: * @return remote host name
0360: */
0361: String getRemoteHostName() {
0362: InetAddress addr = controlSock.getInetAddress();
0363: return addr.getHostName();
0364: }
0365:
0366: /**
0367: * Set strict checking of FTP return codes. If strict
0368: * checking is on (the default) code must exactly match the expected
0369: * code. If strict checking is off, only the first digit must match.
0370: *
0371: * @param strict true for strict checking, false for loose checking
0372: */
0373: void setStrictReturnCodes(boolean strict) {
0374: this .strictReturnCodes = strict;
0375: }
0376:
0377: /**
0378: * Listen on all interfaces for active mode transfers (the default).
0379: *
0380: * @param listenOnAll true if listen on all interfaces, false to listen on the control interface
0381: */
0382: void setListenOnAllInterfaces(boolean listenOnAll) {
0383: this .listenOnAllInterfaces = listenOnAll;
0384: }
0385:
0386: /**
0387: * Are we listening on all interfaces in active mode, which is the default?
0388: *
0389: * @return true if listening on all interfaces, false if listening just on the control interface
0390: */
0391: boolean getListenOnAllInterfaces() {
0392: return listenOnAllInterfaces;
0393: }
0394:
0395: /**
0396: * Set the TCP timeout on the underlying control socket.
0397: *
0398: * If a timeout is set, then any operation which
0399: * takes longer than the timeout value will be
0400: * killed with a java.io.InterruptedException.
0401: *
0402: * @param millis The length of the timeout, in milliseconds
0403: */
0404: void setTimeout(int millis) throws IOException {
0405:
0406: if (controlSock == null)
0407: throw new IllegalStateException(
0408: "Failed to set timeout - no control socket");
0409:
0410: controlSock.setSoTimeout(millis);
0411: }
0412:
0413: /**
0414: * Set a listener that handles all FTP messages
0415: *
0416: * @param listener message listener
0417: */
0418: void setMessageListener(FTPMessageListener listener) {
0419: this .messageListener = listener;
0420: }
0421:
0422: /**
0423: * Close the socket
0424: *
0425: * @throws IOException
0426: */
0427: public void close() throws IOException {
0428: controlSock.close();
0429: }
0430:
0431: /**
0432: * Quit this FTP session and clean up.
0433: */
0434: public void logout() throws IOException {
0435:
0436: IOException ex = null;
0437: try {
0438: writer.close();
0439: } catch (IOException e) {
0440: ex = e;
0441: }
0442: try {
0443: reader.close();
0444: } catch (IOException e) {
0445: ex = e;
0446: }
0447: try {
0448: controlSock.close();
0449: } catch (IOException e) {
0450: ex = e;
0451: }
0452: if (ex != null)
0453: throw ex;
0454: }
0455:
0456: /**
0457: * Request a data socket be created on the
0458: * server, connect to it and return our
0459: * connected socket.
0460: *
0461: * @param active if true, create in active mode, else
0462: * in passive mode
0463: * @return connected data socket
0464: */
0465: FTPDataSocket createDataSocket(FTPConnectMode connectMode)
0466: throws IOException, FTPException {
0467:
0468: if (connectMode == FTPConnectMode.ACTIVE) {
0469: return createDataSocketActive();
0470: } else { // PASV
0471: return createDataSocketPASV();
0472: }
0473: }
0474:
0475: /**
0476: * Request a data socket be created on the Client
0477: * client on any free port, do not connect it to yet.
0478: *
0479: * @return not connected data socket
0480: */
0481: FTPDataSocket createDataSocketActive() throws IOException,
0482: FTPException {
0483:
0484: // use the next port in list (or 0 by default, indicating any port number)
0485: FTPDataSocket socket = newActiveDataSocket(nextPort);
0486:
0487: // increment port number to use to next in range, or else recycle
0488: // from lowPort again
0489: if (lowPort >= 0 && highPort >= 0) {
0490: if (nextPort < highPort)
0491: nextPort++;
0492: else
0493: nextPort = lowPort;
0494: }
0495:
0496: short port = (short) socket.getLocalPort();
0497: sendPORTCommand(port);
0498:
0499: return socket;
0500: }
0501:
0502: /**
0503: * Send the PORT command to the server
0504: *
0505: * @param socket data socket
0506: * @throws IOException
0507: * @throws FTPException
0508: */
0509: void sendPORTCommand(short port) throws IOException, FTPException {
0510:
0511: // get the local address to which the control socket is bound.
0512: InetAddress localhost = controlSock.getLocalAddress();
0513:
0514: // send the PORT command to the server
0515: setDataPort(localhost, port);
0516: }
0517:
0518: /**
0519: * Helper method to convert a byte into an unsigned short value
0520: *
0521: * @param value value to convert
0522: * @return the byte value as an unsigned short
0523: */
0524: private short toUnsignedShort(byte value) {
0525: return (value < 0) ? (short) (value + 256) : (short) value;
0526: }
0527:
0528: /**
0529: * Convert a short into a byte array
0530: *
0531: * @param value value to convert
0532: * @return a byte array
0533: */
0534: protected byte[] toByteArray(short value) {
0535:
0536: byte[] bytes = new byte[2];
0537: bytes[0] = (byte) (value >> 8); // bits 1- 8
0538: bytes[1] = (byte) (value & 0x00FF); // bits 9-16
0539: return bytes;
0540: }
0541:
0542: /**
0543: * We can force PORT to send a fixed IP address, which can be useful with certain
0544: * NAT configurations. Must be connected to the remote host to call this method.
0545: *
0546: * @param forcedActiveIP IP address to force
0547: */
0548: void setActivePortIPAddress(String forcedActiveIP) {
0549: this .forcedActiveIP = forcedActiveIP;
0550: }
0551:
0552: /**
0553: * Set the port number range for active mode
0554: *
0555: * @param lowest lowest port number in range
0556: * @param highest highest port number in range
0557: */
0558: public void setActivePortRange(int lowest, int highest) {
0559: this .lowPort = lowest;
0560: this .highPort = highest;
0561: this .nextPort = lowPort;
0562: }
0563:
0564: /**
0565: * Gets the IP address bytes from an IPV4 address that is
0566: * a string
0567: *
0568: * @param IPAddress ip address such as 192.168.10.0
0569: * @return
0570: * @throws FTPException
0571: */
0572: private byte[] getIPAddressBytes(String IPAddress)
0573: throws FTPException {
0574:
0575: byte ipbytes[] = new byte[4];
0576: int len = IPAddress.length();
0577: int partCount = 0;
0578: StringBuffer buf = new StringBuffer();
0579:
0580: // loop thru and examine each char
0581: for (int i = 0; i < len && partCount <= 4; i++) {
0582:
0583: char ch = IPAddress.charAt(i);
0584: if (Character.isDigit(ch))
0585: buf.append(ch);
0586: else if (ch != '.') {
0587: throw new FTPException(
0588: "Incorrectly formatted IP address: "
0589: + IPAddress);
0590: }
0591:
0592: // get the part
0593: if (ch == '.' || i + 1 == len) { // at end or at separator
0594: try {
0595: ipbytes[partCount++] = (byte) Integer.parseInt(buf
0596: .toString());
0597: buf.setLength(0);
0598: } catch (NumberFormatException ex) {
0599: throw new FTPException(
0600: "Incorrectly formatted IP address: "
0601: + IPAddress);
0602: }
0603: }
0604: }
0605: return ipbytes;
0606: }
0607:
0608: /**
0609: * Sets the data port on the server, that is, sends a PORT
0610: * command.
0611: *
0612: * @param host the local host the server will connect to
0613: * @param portNo the port number to connect to
0614: */
0615: protected void setDataPort(InetAddress host, short portNo)
0616: throws IOException, FTPException {
0617:
0618: byte[] hostBytes = host.getAddress();
0619: byte[] portBytes = toByteArray(portNo);
0620:
0621: if (forcedActiveIP != null) {
0622: log.info("Forcing use of fixed IP for PORT command");
0623: hostBytes = getIPAddressBytes(forcedActiveIP);
0624: }
0625:
0626: // assemble the PORT command
0627: String cmd = new StringBuffer("PORT ").append(
0628: toUnsignedShort(hostBytes[0])).append(",").append(
0629: toUnsignedShort(hostBytes[1])).append(",").append(
0630: toUnsignedShort(hostBytes[2])).append(",").append(
0631: toUnsignedShort(hostBytes[3])).append(",").append(
0632: toUnsignedShort(portBytes[0])).append(",").append(
0633: toUnsignedShort(portBytes[1])).toString();
0634:
0635: // send command and check reply
0636: // CoreFTP returns 250 incorrectly
0637: FTPReply reply = sendCommand(cmd);
0638: String[] validCodes = { "200", "250" };
0639: validateReply(reply, validCodes);
0640: }
0641:
0642: /**
0643: * Request a data socket be created on the
0644: * server, connect to it and return our
0645: * connected socket.
0646: *
0647: * @return connected data socket
0648: */
0649: protected FTPDataSocket createDataSocketPASV() throws IOException,
0650: FTPException {
0651:
0652: // PASSIVE command - tells the server to listen for
0653: // a connection attempt rather than initiating it
0654: FTPReply replyObj = sendCommand("PASV");
0655: validateReply(replyObj, "227");
0656: String reply = replyObj.getReplyText();
0657:
0658: int[] parts = getPASVParts(reply);
0659:
0660: // assemble the IP address
0661: // we try connecting, so we don't bother checking digits etc
0662: String ipAddress = parts[0] + "." + parts[1] + "." + parts[2]
0663: + "." + parts[3];
0664:
0665: // assemble the port number
0666: int port = (parts[4] << 8) + parts[5];
0667:
0668: String hostIP = ipAddress;
0669: if (autoPassiveIPSubstitution) {
0670: hostIP = remoteAddr.getHostAddress();
0671: StringBuffer msg = new StringBuffer(
0672: "Substituting server supplied IP (");
0673: msg.append(ipAddress).append(") with remote host IP (")
0674: .append(hostIP).append(")");
0675: log.debug(msg.toString());
0676: }
0677:
0678: // create the socket
0679: return newPassiveDataSocket(hostIP, port);
0680: }
0681:
0682: /**
0683: * Get the parts that make up the PASV reply
0684: *
0685: * @param reply reply string
0686: * @return
0687: * @throws FTPException
0688: */
0689: int[] getPASVParts(String reply) throws FTPException {
0690:
0691: // The reply to PASV is in the form:
0692: // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
0693: // where h1..h4 are the IP address to connect and
0694: // p1,p2 the port number
0695: // Example:
0696: // 227 Entering Passive Mode (128,3,122,1,15,87).
0697: // NOTE: PASV command in IBM/Mainframe returns the string
0698: // 227 Entering Passive Mode 128,3,122,1,15,87 (missing
0699: // brackets)
0700:
0701: // extract the IP data string from between the brackets
0702: int startIP = reply.indexOf('(');
0703: int endIP = reply.indexOf(')');
0704:
0705: // if didn't find start bracket, figure out where it should have been
0706: if (startIP < 0) {
0707: startIP = 0;
0708: while (startIP < reply.length()
0709: && !Character.isDigit(reply.charAt(startIP)))
0710: startIP++;
0711: startIP--; // go back so this is where the '(' should be
0712: }
0713:
0714: // if didn't find end bracket, set to end of reply
0715: if (endIP < 0) {
0716: endIP = reply.length() - 1;
0717: while (endIP > 0 && !Character.isDigit(reply.charAt(endIP)))
0718: endIP--;
0719: endIP++; // go forward so this is where the ')' should be
0720: if (endIP >= reply.length())
0721: reply += ")";
0722: }
0723:
0724: String ipData = reply.substring(startIP + 1, endIP).trim();
0725: int parts[] = new int[6];
0726:
0727: int len = ipData.length();
0728: int partCount = 0;
0729: StringBuffer buf = new StringBuffer();
0730:
0731: // loop thru and examine each char
0732: for (int i = 0; i < len && partCount <= 6; i++) {
0733:
0734: char ch = ipData.charAt(i);
0735: if (Character.isDigit(ch))
0736: buf.append(ch);
0737: else if (ch != ',' && ch != ' ') {
0738: throw new FTPException("Malformed PASV reply: " + reply);
0739: }
0740:
0741: // get the part
0742: if (ch == ',' || i + 1 == len) { // at end or at separator
0743: try {
0744: parts[partCount++] = Integer.parseInt(buf
0745: .toString());
0746: buf.setLength(0);
0747: } catch (NumberFormatException ex) {
0748: throw new FTPException("Malformed PASV reply: "
0749: + reply);
0750: }
0751: }
0752: }
0753: return parts;
0754: }
0755:
0756: /**
0757: * Constructs a new <code>FTPDataSocket</code> object (client mode) and connect
0758: * to the given remote host and port number.
0759: *
0760: * @param remoteHost Remote host to connect to.
0761: * @param port Remote port to connect to.
0762: * @return A new <code>FTPDataSocket</code> object (client mode) which is
0763: * connected to the given server.
0764: * @throws IOException Thrown if no TCP/IP connection could be made.
0765: */
0766: protected FTPDataSocket newPassiveDataSocket(String remoteHost,
0767: int port) throws IOException {
0768:
0769: return new FTPPassiveDataSocket(new Socket(remoteHost, port));
0770: }
0771:
0772: /**
0773: * Constructs a new <code>FTPDataSocket</code> object (server mode) which will
0774: * listen on the given port number.
0775: *
0776: * @param port Remote port to listen on.
0777: * @return A new <code>FTPDataSocket</code> object (server mode) which is
0778: * configured to listen on the given port.
0779: * @throws IOException Thrown if an error occurred when creating the socket.
0780: */
0781: protected FTPDataSocket newActiveDataSocket(int port)
0782: throws IOException {
0783:
0784: // ensure server sock gets the timeout
0785: ServerSocket sock = listenOnAllInterfaces ? new ServerSocket(
0786: port) : new ServerSocket(port, 0, controlSock
0787: .getLocalAddress());
0788: log.debug("ListenOnAllInterfaces=" + listenOnAllInterfaces);
0789: sock.setSoTimeout(controlSock.getSoTimeout());
0790: return new FTPActiveDataSocket(sock);
0791: }
0792:
0793: /**
0794: * Send a command to the FTP server and
0795: * return the server's reply as a structured
0796: * reply object
0797: *
0798: * @param command command to send
0799: *
0800: * @return reply to the supplied command
0801: */
0802: public FTPReply sendCommand(String command) throws IOException {
0803:
0804: writeCommand(command);
0805:
0806: // and read the result
0807: return readReply();
0808: }
0809:
0810: /**
0811: * Send a command to the FTP server. Don't
0812: * read the reply
0813: *
0814: * @param command command to send
0815: */
0816: void writeCommand(String command) throws IOException {
0817:
0818: log(DEBUG_ARROW + command, true);
0819:
0820: // send it
0821: writer.write(command + EOL);
0822: writer.flush();
0823: }
0824:
0825: /**
0826: * Read the FTP server's reply to a previously
0827: * issued command. RFC 959 states that a reply
0828: * consists of the 3 digit code followed by text.
0829: * The 3 digit code is followed by a hyphen if it
0830: * is a multiline response, and the last line starts
0831: * with the same 3 digit code.
0832: *
0833: * @return structured reply object
0834: */
0835: FTPReply readReply() throws IOException {
0836:
0837: String line = reader.readLine();
0838: while (line != null && line.length() == 0)
0839: line = reader.readLine();
0840:
0841: if (line == null)
0842: throw new IOException("Unexpected null reply received");
0843:
0844: log(line, false);
0845:
0846: if (line.length() < 3)
0847: throw new IOException("Short reply received");
0848:
0849: String replyCode = line.substring(0, 3);
0850: StringBuffer reply = new StringBuffer("");
0851: if (line.length() > 3)
0852: reply.append(line.substring(4));
0853:
0854: Vector dataLines = null;
0855:
0856: // check for multiline response and build up
0857: // the reply
0858: if (line.charAt(3) == '-') {
0859: dataLines = new Vector();
0860: boolean complete = false;
0861: while (!complete) {
0862: line = reader.readLine();
0863: if (line == null)
0864: throw new IOException(
0865: "Unexpected null reply received");
0866:
0867: if (line.length() == 0)
0868: continue;
0869:
0870: log(line, false);
0871:
0872: if (line.length() > 3
0873: && line.substring(0, 3).equals(replyCode)
0874: && line.charAt(3) == ' ') {
0875: reply.append(line.substring(3));
0876: complete = true;
0877: } else { // not the last line
0878: reply.append(" ").append(line);
0879: dataLines.addElement(line);
0880: }
0881: } // end while
0882: } // end if
0883:
0884: if (dataLines != null) {
0885: String[] data = new String[dataLines.size()];
0886: dataLines.copyInto(data);
0887: return new FTPReply(replyCode, reply.toString(), data);
0888: } else {
0889: return new FTPReply(replyCode, reply.toString());
0890: }
0891: }
0892:
0893: /**
0894: * Validate the response the host has supplied against the
0895: * expected reply. If we get an unexpected reply we throw an
0896: * exception, setting the message to that returned by the
0897: * FTP server
0898: *
0899: * @param reply the entire reply string we received
0900: * @param expectedReplyCode the reply we expected to receive
0901: *
0902: */
0903: FTPReply validateReply(String reply, String expectedReplyCode)
0904: throws FTPException {
0905:
0906: FTPReply replyObj = new FTPReply(reply);
0907:
0908: if (validateReplyCode(replyObj, expectedReplyCode))
0909: return replyObj;
0910:
0911: // if unexpected reply, throw an exception
0912: throw new FTPException(replyObj);
0913: }
0914:
0915: /**
0916: * Validate the response the host has supplied against the
0917: * expected reply. If we get an unexpected reply we throw an
0918: * exception, setting the message to that returned by the
0919: * FTP server
0920: *
0921: * @param reply the entire reply string we received
0922: * @param expectedReplyCodes array of expected replies
0923: * @return an object encapsulating the server's reply
0924: *
0925: */
0926: public FTPReply validateReply(String reply,
0927: String[] expectedReplyCodes) throws IOException,
0928: FTPException {
0929:
0930: FTPReply replyObj = new FTPReply(reply);
0931: return validateReply(replyObj, expectedReplyCodes);
0932: }
0933:
0934: /**
0935: * Validate the response the host has supplied against the
0936: * expected reply. If we get an unexpected reply we throw an
0937: * exception, setting the message to that returned by the
0938: * FTP server
0939: *
0940: * @param reply reply object
0941: * @param expectedReplyCodes array of expected replies
0942: * @return reply object
0943: *
0944: */
0945: public FTPReply validateReply(FTPReply reply,
0946: String[] expectedReplyCodes) throws FTPException {
0947:
0948: for (int i = 0; i < expectedReplyCodes.length; i++)
0949: if (validateReplyCode(reply, expectedReplyCodes[i]))
0950: return reply;
0951:
0952: // got this far, not recognised
0953: StringBuffer buf = new StringBuffer("[");
0954: for (int i = 0; i < expectedReplyCodes.length; i++) {
0955: buf.append(expectedReplyCodes[i]);
0956: if (i + 1 < expectedReplyCodes.length)
0957: buf.append(",");
0958: }
0959: buf.append("]");
0960: log.info("Expected reply codes = " + buf.toString());
0961: throw new FTPException(reply);
0962: }
0963:
0964: /**
0965: * Validate the response the host has supplied against the
0966: * expected reply. If we get an unexpected reply we throw an
0967: * exception, setting the message to that returned by the
0968: * FTP server
0969: *
0970: * @param reply reply object
0971: * @param expectedReplyCode expected reply
0972: * @return reply object
0973: *
0974: */
0975: public FTPReply validateReply(FTPReply reply,
0976: String expectedReplyCode) throws FTPException {
0977:
0978: if (validateReplyCode(reply, expectedReplyCode))
0979: return reply;
0980:
0981: // got this far, not recognised
0982: log.info("Expected reply code = [" + expectedReplyCode + "]");
0983: throw new FTPException(reply);
0984: }
0985:
0986: /**
0987: * Validate reply object
0988: *
0989: * @param reply reference to reply object
0990: * @param expectedReplyCode expect reply code
0991: * @return true if valid, false if invalid
0992: */
0993: private boolean validateReplyCode(FTPReply reply,
0994: String expectedReplyCode) {
0995:
0996: String replyCode = reply.getReplyCode();
0997: if (strictReturnCodes) {
0998: if (replyCode.equals(expectedReplyCode))
0999: return true;
1000: else
1001: return false;
1002: } else { // non-strict - match first char
1003: if (replyCode.charAt(0) == expectedReplyCode.charAt(0))
1004: return true;
1005: else
1006: return false;
1007: }
1008: }
1009:
1010: /**
1011: * Log a message, checking for passwords
1012: *
1013: * @param msg message to log
1014: * @param reply true if a response, false otherwise
1015: */
1016: void log(String msg, boolean command) {
1017: if (msg.startsWith(PASSWORD_MESSAGE))
1018: msg = PASSWORD_MESSAGE + " ********";
1019: log.debug(msg);
1020: if (messageListener != null)
1021: if (command)
1022: messageListener.logCommand(msg);
1023: else
1024: messageListener.logReply(msg);
1025:
1026: }
1027: }
|