0001: /*
0002: * FtpSession.java
0003: *
0004: * Copyright (C) 1998-2003 Peter Graves
0005: * $Id: FtpSession.java,v 1.4 2003/05/25 13:43:17 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j;
0023:
0024: import java.io.BufferedReader;
0025: import java.io.IOException;
0026: import java.io.InputStream;
0027: import java.io.InputStreamReader;
0028: import java.io.OutputStream;
0029: import java.io.OutputStreamWriter;
0030: import java.io.StringReader;
0031: import java.net.InetAddress;
0032: import java.net.ServerSocket;
0033: import java.net.Socket;
0034: import java.net.SocketException;
0035: import java.util.Random;
0036: import java.util.StringTokenizer;
0037: import java.util.Vector;
0038: import javax.swing.SwingUtilities;
0039:
0040: public class FtpSession implements Constants {
0041: private static final boolean echo = true;
0042: private static final Vector sessionList = new Vector();
0043:
0044: private static CleanupThread cleanupThread;
0045:
0046: private String host;
0047: private int port;
0048: private String user;
0049: private String password;
0050: private String loginDirectory;
0051: private String currentDirectory;
0052: private Socket controlSocket;
0053: private BufferedReader controlIn;
0054: private OutputStreamWriter controlOut;
0055: private ServerSocket serverSocket;
0056: private Socket dataSocket;
0057: private InputStream dataIn;
0058: private OutputStream dataOut;
0059: private boolean connected;
0060: private boolean usePassiveMode = true;
0061: private String errorText;
0062: private ProgressNotifier progressNotifier;
0063: private boolean locked;
0064:
0065: private FtpSession() {
0066: register(this );
0067: }
0068:
0069: private FtpSession(Login login, int port) {
0070: host = login.host;
0071: user = login.user;
0072: password = login.password;
0073: this .port = port;
0074: usePassiveMode = Editor.preferences().getBooleanProperty(
0075: Property.FTP_USE_PASSIVE_MODE);
0076: register(this );
0077: }
0078:
0079: private static synchronized void register(FtpSession session) {
0080: sessionList.add(session);
0081: if (cleanupThread == null) {
0082: cleanupThread = new CleanupThread(cleanupRunnable);
0083: cleanupThread.start();
0084: }
0085: }
0086:
0087: private static synchronized void unregister(FtpSession session) {
0088: if (!sessionList.contains(session))
0089: Debug.bug();
0090: sessionList.remove(session);
0091: }
0092:
0093: protected Object clone() {
0094: FtpSession session = new FtpSession();
0095: session.host = host;
0096: session.user = user;
0097: session.password = password;
0098: session.port = port;
0099: session.usePassiveMode = usePassiveMode;
0100: return session;
0101: }
0102:
0103: public final String getHost() {
0104: return host;
0105: }
0106:
0107: public final String getErrorText() {
0108: return errorText;
0109: }
0110:
0111: public final String getLoginDirectory() {
0112: return loginDirectory;
0113: }
0114:
0115: public final void setProgressNotifier(
0116: ProgressNotifier progressNotifier) {
0117: this .progressNotifier = progressNotifier;
0118: }
0119:
0120: public final boolean isLocked() {
0121: return locked;
0122: }
0123:
0124: private synchronized boolean lock() {
0125: if (locked)
0126: return false;
0127: locked = true;
0128: return true;
0129: }
0130:
0131: public synchronized void unlock() {
0132: if (locked) {
0133: progressNotifier = null;
0134: locked = false;
0135: } else
0136: Debug.bug("FtpSession.unlock session was not locked");
0137: }
0138:
0139: private boolean changeDirectory(String dirname) {
0140: if (dirname.equals(currentDirectory))
0141: return true;
0142: command("CWD " + dirname);
0143: String s = getReplyString();
0144: int code = getCode(s);
0145: if (code == 421) {
0146: connect();
0147: if (connected) {
0148: command("CWD " + dirname);
0149: s = getReplyString();
0150: } else
0151: return false;
0152: }
0153: if (getCode(s) == 250) {
0154: currentDirectory = dirname;
0155: return true;
0156: }
0157: return false;
0158: }
0159:
0160: public boolean isDirectory(String remotePath) {
0161: if (changeDirectory(remotePath))
0162: return true;
0163:
0164: return false;
0165: }
0166:
0167: public boolean isFile(String remotePath) {
0168: command("SIZE " + remotePath);
0169: String s = getReplyString();
0170: int code = getCode(s);
0171: if (code == 213)
0172: return true;
0173: if (code == 500) {
0174: // "SIZE" command was not recognized.
0175: String listing = getDirectoryListingForFile(remotePath);
0176: if (listing != null && listing.length() > 0) {
0177: char c = listing.charAt(0);
0178: if (c == '-' || c == 'l')
0179: return true;
0180: }
0181: }
0182: return false;
0183: }
0184:
0185: public boolean exists(String remotePath) {
0186: if (isDirectory(remotePath))
0187: return true;
0188: else
0189: return isFile(remotePath);
0190: }
0191:
0192: private long getFileSize(String remotePath) {
0193: long fileSize = -1;
0194: command("SIZE " + remotePath);
0195: String s = getReplyString();
0196: if (getCode(s) == 213) {
0197: s = s.substring(3).trim();
0198: try {
0199: fileSize = Long.parseLong(s);
0200: } catch (NumberFormatException e) {
0201: Log.error(e);
0202: }
0203: }
0204: return fileSize;
0205: }
0206:
0207: boolean deleteFile(String remotePath) {
0208: command("DELE " + remotePath);
0209: return getReply() == 250;
0210: }
0211:
0212: boolean removeDirectory(String remotePath) {
0213: command("RMD " + remotePath);
0214: return getReply() == 250;
0215: }
0216:
0217: public boolean chmod(FtpFile file, int permissions) {
0218: if (permissions != 0) {
0219: FastStringBuffer sb = new FastStringBuffer("SITE CHMOD ");
0220: sb.append(Integer.toString(permissions, 8));
0221: sb.append(' ');
0222: sb.append(file.canonicalPath());
0223: command(sb.toString());
0224: return getReply() == 200;
0225: } else
0226: return false;
0227: }
0228:
0229: public int getPermissions(FtpFile file) {
0230: int permissions = 0;
0231: String listing = getDirectoryListingForFile(file
0232: .canonicalPath());
0233: if (listing != null) {
0234: String s = listing.substring(1, 10);
0235: Log.debug("s = |" + s + "|");
0236: if (s.length() == 9) {
0237: if (s.charAt(0) == 'r')
0238: permissions += 0400;
0239: if (s.charAt(1) == 'w')
0240: permissions += 0200;
0241: if (s.charAt(2) == 'x')
0242: permissions += 0100;
0243: if (s.charAt(3) == 'r')
0244: permissions += 040;
0245: if (s.charAt(4) == 'w')
0246: permissions += 020;
0247: if (s.charAt(5) == 'x')
0248: permissions += 010;
0249: if (s.charAt(6) == 'r')
0250: permissions += 4;
0251: if (s.charAt(7) == 'w')
0252: permissions += 2;
0253: if (s.charAt(8) == 'x')
0254: permissions += 1;
0255: }
0256: }
0257: return permissions;
0258: }
0259:
0260: int getFileStatus(String filename) {
0261: if (!connected) {
0262: connect();
0263: if (!connected)
0264: return -1;
0265: }
0266: if (changeDirectory(filename))
0267: return 2; // It's a directory.
0268: // Not a directory.
0269: if (!openDataSocket())
0270: return -1;
0271: int status = -1; // Not found or unknown status.
0272: command("LIST " + filename);
0273: int code = getReply();
0274: // 125 Data connection already open, transfer starting
0275: // 150 Opening ASCII mode data connection for file list
0276: // 226 Transfer complete
0277: if (code == 125 || code == 150 || code == 226) {
0278: String s = getData();
0279: closeDataSocket();
0280: // Look for "drwxrwxrwx"...
0281: if (s != null && s.length() > 10) {
0282: char c = s.charAt(0);
0283: if (c == 'd')
0284: status = 2; // Shouldn't happen, since changeDirectory failed.
0285: else if (c == '-')
0286: status = 1;
0287: if (status != -1) {
0288: // Sanity checking.
0289: boolean valid = true;
0290: c = s.charAt(1);
0291: if (c != 'r' && c != 'w' && c != '-')
0292: valid = false;
0293: else {
0294: c = s.charAt(2);
0295: if (c != 'r' && c != 'w' && c != '-')
0296: valid = false;
0297: }
0298: if (!valid)
0299: status = -1;
0300: }
0301: }
0302: // 125 Data connection already open, transfer starting
0303: // 150 Opening ASCII mode data connection for file list
0304: // 226 Transfer complete
0305: if (code == 125 || code == 150)
0306: getReply(226);
0307: }
0308: return status;
0309: }
0310:
0311: public String getDirectoryListing(File file) {
0312: if (!(file instanceof FtpFile)) {
0313: Debug.bug();
0314: return null;
0315: }
0316: Debug.assertTrue(isLocked());
0317: String listing = DirectoryCache.getDirectoryCache().getListing(
0318: file);
0319: if (listing == null) {
0320: listing = getDirectoryListing(file.canonicalPath());
0321: if (listing != null)
0322: DirectoryCache.getDirectoryCache().put(file, listing);
0323: }
0324: return listing;
0325: }
0326:
0327: public String getDirectoryListing(String dirname) {
0328: Debug.assertTrue(isLocked());
0329: String listing = null;
0330: if (verifyConnected()) {
0331: if (changeDirectory(dirname)) {
0332: if (openDataSocket()) {
0333: command("LIST -la");
0334: int code = getReply();
0335:
0336: // 125 Data connection already open, transfer starting
0337: // 150 Opening ASCII mode data connection for file list
0338: // 226 Transfer complete
0339: if (code == 125 || code == 150 || code == 226)
0340: listing = getData();
0341:
0342: closeDataSocket();
0343: if (code == 125 || code == 150)
0344: getReply(226);
0345: }
0346: }
0347: }
0348: return listing;
0349: }
0350:
0351: public String getDirectoryListingForFile(String filename) {
0352: String listing = null;
0353: if (connected) {
0354: if (openDataSocket()) {
0355: command("LIST -la " + filename);
0356: int code = getReply();
0357:
0358: // 125 Data connection already open, transfer starting
0359: // 150 Opening ASCII mode data connection for file list
0360: // 226 Transfer complete
0361: if (code == 125 || code == 150 || code == 226)
0362: listing = getData();
0363:
0364: closeDataSocket();
0365: if (code == 125 || code == 150)
0366: getReply(226);
0367: }
0368: }
0369:
0370: // With at least some FTP servers (ProFTP 1.2.0pre9) the listing
0371: // will be empty if any part of the filename contains any space
0372: // characters, although CWD works fine.
0373:
0374: // If that happens we CWD into the parent directory, list all the
0375: // files, and pick out the line of interest with a regular expression.
0376:
0377: // We check the listing again to make sure the file hasn't changed
0378: // on the server when we go to write out a modified version.
0379:
0380: if (listing == null || listing.length() == 0) {
0381: int index = filename.lastIndexOf('/');
0382: if (index >= 0) {
0383: String parent = index == 0 ? "/" : filename.substring(
0384: 0, index);
0385: String name = filename.substring(index + 1);
0386: String parentListing = getDirectoryListing(parent);
0387: if (parentListing != null) {
0388: BufferedReader reader = new BufferedReader(
0389: new StringReader(parentListing));
0390: String entry;
0391: try {
0392: while ((entry = reader.readLine()) != null) {
0393: if (name.equals(DirectoryEntry
0394: .getName(entry))) {
0395: // Found it!
0396: listing = entry;
0397: break;
0398: }
0399: }
0400: } catch (IOException e) {
0401: Log.error(e);
0402: }
0403: }
0404: }
0405: }
0406:
0407: Log.debug("getDirectoryListingForFile |" + listing + "|");
0408: return listing;
0409: }
0410:
0411: public int put(File localFile, File remoteFile, long fileSize,
0412: boolean saveInPlace) {
0413: boolean succeeded = false;
0414: boolean cancelled = false;
0415: String tempName = null;
0416:
0417: // Change directory to prove parent directory exists.
0418: if (changeDirectory(remoteFile.getParent())) {
0419: OutputStream out = null;
0420: if (saveInPlace) {
0421: out = getOutputStreamForFile(remoteFile.canonicalPath());
0422: } else {
0423: // It would be nice to use STOU here, but most servers don't
0424: // implement it.
0425: tempName = getUniqueName(remoteFile.getParentFile());
0426: Log.debug("tempName = |" + tempName + "|");
0427: if (tempName != null)
0428: out = getOutputStreamForFile(tempName);
0429: }
0430: if (out != null) {
0431: try {
0432: InputStream in = localFile.getInputStream();
0433: byte[] bytes = new byte[16384];
0434: long totalBytes = 0;
0435: int bytesRead;
0436: if (progressNotifier != null)
0437: progressNotifier.progressStart();
0438: while ((bytesRead = in.read(bytes)) > 0) {
0439: out.write(bytes, 0, bytesRead);
0440: totalBytes += bytesRead;
0441: if (progressNotifier != null) {
0442: if (progressNotifier.cancelled()) {
0443: cancelled = true;
0444: break;
0445: }
0446: progressNotifier.progress("Sent ",
0447: totalBytes, fileSize);
0448: }
0449: // Slow things down for debugging, maybe.
0450: Debug.throttle();
0451: }
0452: if (progressNotifier != null)
0453: progressNotifier.progressStop();
0454: out.flush();
0455: out.close();
0456: closeDataSocket();
0457: in.close();
0458: succeeded = getReply(226);
0459: if (cancelled && tempName != null) {
0460: command("DELE " + tempName);
0461: getReplyString(); // Ignore reply.
0462: }
0463: } catch (Exception e) {
0464: Log.error(e);
0465: }
0466: }
0467: }
0468:
0469: if (succeeded && !cancelled && tempName != null) {
0470: do {
0471: if (progressNotifier != null)
0472: progressNotifier.setText("Renaming temporary file");
0473: command("RNFR " + tempName);
0474: if (!getReply(350)) {
0475: succeeded = false;
0476: break;
0477: }
0478: command("RNTO " + remoteFile.canonicalPath());
0479: if (!getReply(250))
0480: succeeded = false;
0481: break;
0482: } while (false);
0483: }
0484:
0485: if (progressNotifier != null) {
0486: if (cancelled)
0487: progressNotifier.setText("Transfer cancelled");
0488: else if (succeeded)
0489: progressNotifier.setText("Transfer completed");
0490: else
0491: progressNotifier.setText("Transfer failed");
0492: }
0493:
0494: if (cancelled)
0495: return CANCELLED;
0496: else if (succeeded)
0497: return SUCCESS;
0498: else
0499: return ERROR;
0500: }
0501:
0502: private static Random random;
0503:
0504: private String getUniqueName(File dir) {
0505: long now = System.currentTimeMillis();
0506: if (random == null) {
0507: // Use uptime as seed.
0508: random = new Random(now - Editor.getStartTimeMillis());
0509: }
0510: long n = now + Math.abs(random.nextLong() % now);
0511: for (int i = 0; i < 100; i++) {
0512: File file = File.getInstance(dir, String.valueOf(n));
0513: String name = file.canonicalPath();
0514: if (!exists(name)) {
0515: Log.debug("unique name = |" + name + "|");
0516: return name;
0517: }
0518: n += Math.abs(random.nextLong() % now);
0519: }
0520: return null;
0521: }
0522:
0523: public int get(File remoteFile, File localFile, long fileSize) {
0524: boolean succeeded = false;
0525: boolean cancelled = false;
0526: if (fileSize == 0) {
0527: fileSize = getFileSize(remoteFile.canonicalPath());
0528: if (fileSize < 0) // Error.
0529: fileSize = 0;
0530: }
0531: InputStream in = getInputStreamForFile(remoteFile
0532: .canonicalPath());
0533: if (in == null)
0534: return ERROR;
0535: try {
0536: OutputStream out = localFile.getOutputStream();
0537: byte[] bytes = new byte[16384];
0538: long totalBytes = 0;
0539:
0540: if (progressNotifier != null)
0541: progressNotifier.progressStart();
0542:
0543: while (true) {
0544: int bytesRead = 0;
0545: try {
0546: // We may get an exception here if user cancels.
0547: bytesRead = in.read(bytes);
0548: } catch (Exception e) {
0549: if (progressNotifier == null
0550: || !progressNotifier.cancelled())
0551: Log.error(e);
0552: else
0553: // Exception is expected.
0554: Log.debug("FtpSession.get cancelled");
0555: }
0556: if (bytesRead <= 0)
0557: break;
0558: out.write(bytes, 0, bytesRead);
0559: totalBytes += bytesRead;
0560: if (progressNotifier != null) {
0561: if (progressNotifier.cancelled()) {
0562: cancelled = true;
0563: break;
0564: }
0565: progressNotifier.progress("Received ", totalBytes,
0566: fileSize);
0567: }
0568: // Slow things down for debugging, maybe.
0569: Debug.throttle();
0570: }
0571: if (progressNotifier != null)
0572: progressNotifier.progressStop();
0573: // If the user cancels, just close the data connection.
0574: // A bit rude, but it seems to work.
0575: out.flush();
0576: out.close();
0577: in.close();
0578: closeDataSocket();
0579: if (cancelled)
0580: localFile.delete();
0581: succeeded = getReply(226);
0582: } catch (SocketException e) {
0583: if (cancelled)
0584: ; // Exception is expected in this case.
0585: else
0586: Log.error(e);
0587: } catch (Exception e) {
0588: Log.error(e);
0589: }
0590: if (progressNotifier != null) {
0591: if (cancelled)
0592: progressNotifier.setText("Transfer cancelled");
0593: else if (succeeded)
0594: progressNotifier.setText("Transfer completed");
0595: else
0596: progressNotifier.setText("Transfer failed");
0597: }
0598: if (cancelled)
0599: return CANCELLED;
0600: else if (succeeded)
0601: return SUCCESS;
0602: else
0603: return ERROR;
0604: }
0605:
0606: public synchronized boolean verifyConnected() {
0607: if (connected) {
0608: command("NOOP");
0609: if (getReply() == 421) {
0610: Log.debug("verifyConnected calling connect");
0611: connect();
0612: }
0613: } else
0614: connect();
0615: if (connected && progressNotifier != null)
0616: progressNotifier.setText("Connected to " + host);
0617: return connected;
0618: }
0619:
0620: private synchronized void connect() {
0621: if (progressNotifier != null)
0622: progressNotifier.setText("Connecting to " + host);
0623: Log.debug("connecting to " + host);
0624:
0625: connected = false;
0626: loginDirectory = null;
0627: currentDirectory = null;
0628: errorText = null;
0629:
0630: SocketConnection sc = new SocketConnection(host, port, 30000,
0631: 200, progressNotifier);
0632: controlSocket = sc.connect();
0633: if (controlSocket == null) {
0634: errorText = sc.getErrorText();
0635: return;
0636: }
0637:
0638: try {
0639: controlIn = new BufferedReader(new InputStreamReader(
0640: controlSocket.getInputStream()));
0641: controlOut = new OutputStreamWriter(controlSocket
0642: .getOutputStream());
0643: } catch (IOException e) {
0644: Log.error(e);
0645: disconnect();
0646: return;
0647: }
0648: getReplyString();
0649: command("USER " + user);
0650: int code = getReply();
0651: if (code == 331) {
0652: // Password required.
0653: if (password != null) {
0654: command("PASS " + password);
0655: code = getReply();
0656: if (code == 530) {
0657: errorText = "Login incorrect";
0658: user = null;
0659: password = null;
0660: }
0661: }
0662: }
0663: if (code != 230) {
0664: if (errorText == null || errorText.length() == 0) {
0665: if (lastReply != null)
0666: errorText = lastReply.substring(3).trim();
0667: if (errorText == null || errorText.length() == 0)
0668: errorText = "Unable to connect to " + host;
0669: }
0670: disconnect();
0671: return;
0672: }
0673:
0674: // Determine login directory.
0675: command("PWD");
0676: String s = getReplyString();
0677: int begin = s.indexOf('"');
0678: if (begin >= 0) {
0679: int end = s.indexOf('"', ++begin);
0680: if (end >= 0)
0681: loginDirectory = currentDirectory = s.substring(begin,
0682: end);
0683: }
0684:
0685: command("TYPE I");
0686: code = getReply();
0687: if (code != 200) {
0688: Log.error("connect didn't get 200");
0689: disconnect();
0690: return;
0691: }
0692:
0693: connected = true;
0694: Log.debug("connected!");
0695: }
0696:
0697: private void command(String s) {
0698: boolean reconnect = false;
0699: if (echo)
0700: Log.debug("==> " + (s.startsWith("PASS ") ? "PASS" : s));
0701: try {
0702: controlOut.write(s + "\r\n");
0703: controlOut.flush();
0704: return;
0705: } catch (Exception e) {
0706: Log.error("exception command " + s);
0707: reconnect = true;
0708: }
0709: if (reconnect) {
0710: // Exception may mean we were disconnected by remote host because
0711: // of inactivity. Try to reconnect.
0712: Log.debug("trying to reconnect...");
0713: connect();
0714: if (connected) {
0715: if (echo)
0716: Log.debug("==> "
0717: + (s.startsWith("PASS ") ? "PASS" : s));
0718: try {
0719: controlOut.write(s + "\r\n");
0720: controlOut.flush();
0721: return;
0722: } catch (Exception e) {
0723: Log.error("2nd exception command " + s);
0724: reconnect = true;
0725: }
0726: }
0727: }
0728: }
0729:
0730: private String lastReply;
0731:
0732: // Returns final line of reply.
0733: private String getReplyString() {
0734: String s = null;
0735: try {
0736: do {
0737: s = controlIn.readLine();
0738: if (echo && s != null)
0739: Log.debug("<== " + s);
0740: } while (s != null && !isEndOfReply(s));
0741: } catch (Exception e) {
0742: }
0743: lastReply = s;
0744: return s;
0745: }
0746:
0747: private static boolean isEndOfReply(String s) {
0748: // If it's not the last line of the reply, the 4th char will be '-'.
0749: if (s != null && s.length() >= 4 && s.charAt(3) == ' '
0750: && Character.isDigit(s.charAt(0))
0751: && Character.isDigit(s.charAt(1))
0752: && Character.isDigit(s.charAt(2))) {
0753: return true;
0754: }
0755:
0756: return false;
0757: }
0758:
0759: private boolean getReply(int required) {
0760: String s = getReplyString();
0761: if (s == null)
0762: return false;
0763: return getCode(s) == required;
0764: }
0765:
0766: private int getReply() {
0767: String s = getReplyString();
0768: if (s == null)
0769: return 421;
0770: return getCode(s);
0771: }
0772:
0773: private static int getCode(String reply) {
0774: int code = -1;
0775: if (reply != null) {
0776: // We do sanity checking when we get the reply string, so we don't
0777: // need it here.
0778: try {
0779: code = Integer.parseInt(reply.substring(0, 3));
0780: } catch (NumberFormatException e) {
0781: Log.error(e);
0782: }
0783: }
0784: return code;
0785: }
0786:
0787: private String getData() {
0788: if (!usePassiveMode)
0789: acceptConnectionFromServer();
0790: byte[] buf = new byte[16384];
0791: FastStringBuffer sb = new FastStringBuffer();
0792: try {
0793: while (true) {
0794: int bytesRead = dataIn.read(buf); // Blocks.
0795: if (bytesRead < 0)
0796: break;
0797: sb.append(new String(buf, 0, bytesRead));
0798: }
0799: } catch (IOException e) {
0800: Log.error(e);
0801: }
0802: return sb.toString();
0803: }
0804:
0805: private void closeDataSocket() {
0806: if (dataSocket != null) {
0807: if (dataIn != null) {
0808: try {
0809: dataIn.close();
0810: } catch (IOException e) {
0811: Log.error(e);
0812: }
0813: }
0814: if (dataOut != null) {
0815: try {
0816: dataOut.close();
0817: } catch (IOException e) {
0818: Log.error(e);
0819: }
0820: }
0821: try {
0822: dataSocket.close();
0823: } catch (IOException e) {
0824: Log.error(e);
0825: }
0826: dataSocket = null;
0827: dataIn = null;
0828: dataOut = null;
0829: }
0830: }
0831:
0832: private boolean openDataSocket() {
0833: if (!connected) {
0834: connect();
0835: if (!connected)
0836: return false;
0837: }
0838: closeDataSocket();
0839: if (usePassiveMode) {
0840: command("PASV");
0841: String reply;
0842: while (true) {
0843: reply = getReplyString();
0844: if (reply == null) {
0845: Log.error("openDataSocket null reply to PASV");
0846: return false;
0847: }
0848: int code = getCode(reply);
0849: if (code >= 400)
0850: return false; // Error.
0851: if (code == 227)
0852: break; // The one we want.
0853: // Otherwise the reply we received is not a reply to the PASV command.
0854: // Loop back and try again.
0855: }
0856: int begin = reply.indexOf('(');
0857: if (begin < 0)
0858: return false;
0859: int end = reply.indexOf(')');
0860: if (end < 0)
0861: return false;
0862: String s = reply.substring(begin + 1, end);
0863: StringTokenizer st = new StringTokenizer(s, ",", false);
0864: if (st.countTokens() != 6)
0865: return false;
0866: String[] addr = new String[4];
0867: for (int i = 0; i < 4; i++)
0868: addr[i] = st.nextToken();
0869: String address = addr[0] + "." + addr[1] + "." + addr[2]
0870: + "." + addr[3];
0871: try {
0872: int hibyte = Integer.parseInt(st.nextToken());
0873: int lobyte = Integer.parseInt(st.nextToken());
0874: int dataPort = hibyte * 256 + lobyte;
0875: Log.debug("opening data socket");
0876: dataSocket = new Socket(address, dataPort);
0877: dataIn = dataSocket.getInputStream();
0878: dataOut = dataSocket.getOutputStream();
0879: } catch (NumberFormatException e) {
0880: Log.error(e);
0881: } catch (IOException ex) {
0882: Log.error(ex);
0883: }
0884: return dataSocket != null;
0885: } else {
0886: // We're not using passive mode.
0887: try {
0888: InetAddress localHost = controlSocket.getLocalAddress();
0889: serverSocket = new ServerSocket(0);
0890: int dataPort = serverSocket.getLocalPort();
0891: int lobyte = (dataPort & 0x00ff);
0892: int hibyte = ((dataPort & 0xff00) >> 8);
0893: byte[] addrBytes = localHost.getAddress();
0894: int[] addr = new int[4];
0895: for (int i = 0; i < 4; i++) {
0896: addr[i] = addrBytes[i];
0897: if (addr[i] < 0)
0898: addr[i] += 256;
0899: }
0900: String s = "PORT " + addr[0] + "," + addr[1] + ","
0901: + addr[2] + "," + addr[3] + "," + hibyte + ","
0902: + lobyte;
0903: command(s);
0904: return getReply(200);
0905: } catch (Exception e) {
0906: Log.error(e);
0907: }
0908: return false;
0909: }
0910: }
0911:
0912: private void acceptConnectionFromServer() {
0913: try {
0914: dataSocket = serverSocket.accept();
0915: if (dataSocket != null) {
0916: dataIn = dataSocket.getInputStream();
0917: dataOut = dataSocket.getOutputStream();
0918: }
0919: } catch (Exception e) {
0920: Log.error(e);
0921: }
0922: }
0923:
0924: private InputStream getInputStreamForFile(String filename) {
0925: if (openDataSocket()) {
0926: command("RETR " + filename);
0927: int code = getCode(getReplyString());
0928: if (code > -1 && code < 400) {
0929: if (!usePassiveMode)
0930: acceptConnectionFromServer();
0931: return dataIn;
0932: }
0933: }
0934: return null;
0935: }
0936:
0937: private OutputStream getOutputStreamForFile(String filename) {
0938: if (filename == null)
0939: return null;
0940: if (openDataSocket()) {
0941: command("STOR " + filename);
0942: int code = getCode(getReplyString());
0943: if (code > -1 && code < 400) {
0944: if (!usePassiveMode)
0945: acceptConnectionFromServer();
0946: return dataOut;
0947: }
0948: if (code == 550 || code == 553)
0949: errorText = "Access denied";
0950: }
0951: return null;
0952: }
0953:
0954: private static synchronized void cleanup() {
0955: // Walk buffer list in event dispatch thread.
0956: if (!SwingUtilities.isEventDispatchThread()) {
0957: Debug.bug();
0958: return;
0959: }
0960: if (sessionList.size() == 0)
0961: return; // Nothing to do.
0962: for (int i = sessionList.size(); i-- > 0;) {
0963: FtpSession session = (FtpSession) sessionList.get(i);
0964: String host = session.getHost();
0965: boolean inUse = false;
0966: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
0967: Buffer buf = it.nextBuffer();
0968: if (buf.getFile() instanceof FtpFile) {
0969: if (host.equals(buf.getFile().getHostName())) {
0970: inUse = true;
0971: break;
0972: }
0973: }
0974: }
0975: if (!inUse) {
0976: session.close();
0977: unregister(session);
0978: }
0979: }
0980: if (sessionList.size() == 0) {
0981: if (cleanupThread != null) {
0982: cleanupThread.cancel();
0983: cleanupThread = null;
0984: }
0985: }
0986: }
0987:
0988: private static final Runnable cleanupRunnable = new Runnable() {
0989: public void run() {
0990: cleanup();
0991: }
0992: };
0993:
0994: public synchronized void disconnect() {
0995: Log.debug("disconnect");
0996: if (controlSocket != null) {
0997: Log.debug("closing control socket...");
0998: try {
0999: controlSocket.close();
1000: } catch (IOException e) {
1001: Log.error(e);
1002: }
1003: controlSocket = null;
1004: controlIn = null;
1005: controlOut = null;
1006: }
1007: if (dataSocket != null) {
1008: Log.debug("closing data socket...");
1009: try {
1010: dataSocket.close();
1011: } catch (IOException e) {
1012: Log.error(e);
1013: }
1014: dataSocket = null;
1015: dataIn = null;
1016: dataOut = null;
1017: }
1018: connected = false;
1019: }
1020:
1021: private void close() {
1022: Log.debug("FtpSession.close");
1023: if (connected) {
1024: final Editor editor = Editor.currentEditor();
1025: editor.setWaitCursor();
1026: Runnable r = new Runnable() {
1027: public void run() {
1028: try {
1029: if (echo)
1030: Log.debug("==> QUIT");
1031: controlOut.write("QUIT\r\n");
1032: controlOut.flush();
1033: getReply();
1034: } catch (IOException e) {
1035: }
1036: }
1037: };
1038: Thread t = new Thread(r);
1039: t.start();
1040: try {
1041: t.join(3000);
1042: } catch (InterruptedException e) {
1043: Log.error(e);
1044: }
1045: if (t.isAlive()) {
1046: Log.debug("stopping QUIT thread");
1047: t.stop();
1048: }
1049: disconnect();
1050: editor.setDefaultCursor();
1051: }
1052: Log.debug("leaving close");
1053: }
1054:
1055: public static synchronized FtpSession getSession(FtpFile file) {
1056: if (file == null)
1057: return null;
1058: Login login = new Login(file.getHostName(), file.getUserName(),
1059: file.getPassword());
1060: if (login == null)
1061: return null; // Invalid URL.
1062: // Try to find an idle session for the required host.
1063: FtpSession session = lockSession(login.host, file.getPort());
1064: if (session != null) {
1065: if (session.checkLogin())
1066: return session;
1067:
1068: session.unlock();
1069: return null;
1070: }
1071: // No idle session for this host. Try to find a session to clone.
1072: session = findSession(login.host, file.getPort());
1073: if (session != null) {
1074: session = (FtpSession) session.clone();
1075: if (session.lock()) {
1076: if (session.checkLogin())
1077: return session;
1078: session.unlock();
1079: }
1080: return null;
1081: }
1082: if (login.user == null || login.password == null) {
1083: // The URL lacked either a user name or a password or both.
1084: final Editor editor = Editor.currentEditor();
1085: if (login.user == null) {
1086: // No user name in URL. We'll take the first entry in ~/.netrc
1087: // for the host in question.
1088: Login maybe = Netrc.getLogin(login.host);
1089: if (maybe != null) {
1090: login.user = maybe.user;
1091: login.password = maybe.password;
1092: }
1093: if (login.user == null) {
1094: // Nothing relevant in ~/.netrc. Ask the user.
1095: login.user = InputDialog.showInputDialog(editor,
1096: "Login:", "Login on " + login.host);
1097: editor.repaintNow();
1098: }
1099: }
1100: if (login.user == null)
1101: return null; // User cancelled.
1102: if (login.user.length() == 0)
1103: login.user = "anonymous";
1104: if (login.password == null) {
1105: // We have host and user name but no password.
1106: login.password = Netrc.getPassword(login.host,
1107: login.user);
1108: if (login.password == null) {
1109: if (login.user.equals("anonymous"))
1110: login.password = Editor
1111: .preferences()
1112: .getStringProperty(
1113: Property.FTP_ANONYMOUS_PASSWORD);
1114: if (login.password == null) {
1115: // Ask the user.
1116: login.password = PasswordDialog
1117: .showPasswordDialog(editor,
1118: "Password:", "Password");
1119: editor.repaintNow();
1120: }
1121: }
1122: }
1123: // If we don't have a password, give up (it can be blank, but not
1124: // null).
1125: if (login.password == null)
1126: return null;
1127: }
1128: // Final sanity check.
1129: if (login.user.length() == 0)
1130: return null;
1131: // At this point we have non-empty strings for host and user name, and
1132: // a non-null password.
1133: session = new FtpSession(login, file.getPort());
1134: if (session.lock())
1135: return session;
1136: return null;
1137: }
1138:
1139: // Make sure the login is comnplete. Get the user to enter the username
1140: // and/or password if missing. Don't look in .netrc or preferences; we may
1141: // be here because the information in .netrc or preferences didn't work.
1142: private boolean checkLogin() {
1143: final Editor editor = Editor.currentEditor();
1144: if (user == null) {
1145: // Nothing relevant in ~/.netrc. Ask the user.
1146: user = InputDialog.showInputDialog(editor, "Login:",
1147: "Login on " + host);
1148: editor.repaintNow();
1149: if (user == null)
1150: return false; // User cancelled.
1151: }
1152: if (user.length() == 0)
1153: user = "anonymous";
1154: if (password == null) {
1155: // Ask the user.
1156: password = PasswordDialog.showPasswordDialog(editor,
1157: "Password:", "Password");
1158: editor.repaintNow();
1159: // If we don't have a password, give up.
1160: if (password == null)
1161: return false;
1162: }
1163: // Final sanity check.
1164: if (user.length() == 0)
1165: return false;
1166: return true;
1167: }
1168:
1169: private static FtpSession lockSession(String host, int port) {
1170: for (int i = 0; i < sessionList.size(); i++) {
1171: FtpSession session = (FtpSession) sessionList.get(i);
1172: if (session.host.equals(host) && session.port == port) {
1173: if (session.lock())
1174: return session;
1175: }
1176: }
1177: return null;
1178: }
1179:
1180: private static FtpSession findSession(String host, int port) {
1181: for (int i = 0; i < sessionList.size(); i++) {
1182: FtpSession session = (FtpSession) sessionList.get(i);
1183: if (session.host.equals(host) && session.port == port)
1184: return session;
1185: }
1186: return null;
1187: }
1188: }
|