KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > enterprisedt > net > ftp > FTPControlSocket


1 /**
2  *
3  * Java FTP client library.
4  *
5  * Copyright (C) 2000-2003 Enterprise Distributed Technologies Ltd
6  *
7  * www.enterprisedt.com
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  *
23  * Bug fixes, suggestions and comments should be sent to bruce@enterprisedt.com
24  *
25  * Change Log:
26  *
27  * $Log: FTPControlSocket.java,v $
28  * Revision 1.1.1.1 2005/06/23 15:22:59 smontoro
29  * hipergate backend
30  *
31  * Revision 1.1 2004/02/07 03:15:20 hipergate
32  * v2.0 pre-alpha
33  *
34  * Revision 1.6 2003/05/31 14:53:44 bruceb
35  * 1.2.2 changes
36  *
37  * Revision 1.5 2003/01/29 22:46:08 bruceb
38  * minor changes
39  *
40  * Revision 1.4 2002/11/19 22:01:25 bruceb
41  * changes for 1.2
42  *
43  * Revision 1.3 2001/10/09 20:53:46 bruceb
44  * Active mode changes
45  *
46  * Revision 1.1 2001/10/05 14:42:04 bruceb
47  * moved from old project
48  *
49  *
50  */

51
52 package com.enterprisedt.net.ftp;
53
54 import java.io.IOException JavaDoc;
55 import java.io.PrintWriter JavaDoc;
56 import java.io.BufferedReader JavaDoc;
57 import java.io.InputStream JavaDoc;
58 import java.io.OutputStream JavaDoc;
59 import java.io.InputStreamReader JavaDoc;
60 import java.io.OutputStreamWriter JavaDoc;
61 import java.io.Writer JavaDoc;
62
63 import java.net.Socket JavaDoc;
64 import java.net.ServerSocket JavaDoc;
65 import java.net.InetAddress JavaDoc;
66
67 /**
68  * Supports client-side FTP operations
69  *
70  * @author Bruce Blackshaw
71  * @version $Revision: 1.1.1.1 $
72  *
73  */

74  public class FTPControlSocket {
75
76      /**
77       * Revision control id
78       */

79      private static String JavaDoc cvsId = "@(#)$Id: FTPControlSocket.java,v 1.1.1.1 2005/06/23 15:22:59 smontoro Exp $";
80
81      /**
82       * Standard FTP end of line sequence
83       */

84      static final String JavaDoc EOL = "\r\n";
85
86      /**
87       * The control port number for FTP
88       */

89      static final int CONTROL_PORT = 21;
90      
91      /**
92       * Used to flag messages
93       */

94      private static final String JavaDoc DEBUG_ARROW = "---> ";
95      
96      /**
97       * Start of password message
98       */

99      private static final String JavaDoc PASSWORD_MESSAGE = DEBUG_ARROW + "PASS";
100
101      /**
102       * Controls if responses sent back by the
103       * server are sent to assigned output stream
104       */

105      private boolean debugResponses = false;
106
107      /**
108       * Output stream debug is written to,
109       * stdout by default
110       */

111      private PrintWriter JavaDoc log = new PrintWriter JavaDoc(System.out);
112
113      /**
114       * The underlying socket.
115       */

116      private Socket JavaDoc controlSock = null;
117
118      /**
119       * The write that writes to the control socket
120       */

121      private Writer JavaDoc writer = null;
122
123      /**
124       * The reader that reads control data from the
125       * control socket
126       */

127      private BufferedReader JavaDoc reader = null;
128
129      /**
130       * Constructor. Performs TCP connection and
131       * sets up reader/writer. Allows different control
132       * port to be used
133       *
134       * @param remoteHost Remote hostname
135       * @param controlPort port for control stream
136       * @param millis the length of the timeout, in milliseconds
137       * @param log the new logging stream
138       */

139      public FTPControlSocket(String JavaDoc remoteHost, int controlPort,
140                              PrintWriter JavaDoc log, int timeout)
141          throws IOException JavaDoc, FTPException {
142
143          setLogStream(log);
144
145          // ensure we get debug from initial connection sequence
146
debugResponses(true);
147          controlSock = new Socket JavaDoc(remoteHost, controlPort);
148          setTimeout(timeout);
149          initStreams();
150          validateConnection();
151
152          // switch off debug - user can switch on from this point
153
debugResponses(false);
154      }
155
156      /**
157       * Constructor. Performs TCP connection and
158       * sets up reader/writer. Allows different control
159       * port to be used
160       *
161       * @param remoteAddr Remote inet address
162       * @param controlPort port for control stream
163       * @param millis the length of the timeout, in milliseconds
164       * @param log the new logging stream
165       */

166      public FTPControlSocket(InetAddress JavaDoc remoteAddr, int controlPort,
167                              PrintWriter JavaDoc log, int timeout)
168          throws IOException JavaDoc, FTPException {
169          
170          setLogStream(log);
171
172          // ensure we get debug from initial connection sequence
173
debugResponses(true);
174          controlSock = new Socket JavaDoc(remoteAddr, controlPort);
175          setTimeout(timeout);
176          initStreams();
177          validateConnection();
178
179          // switch off debug - user can switch on from this point
180
debugResponses(false);
181      }
182
183
184      /**
185       * Checks that the standard 220 reply is returned
186       * following the initiated connection
187       */

188      private void validateConnection()
189          throws IOException JavaDoc, FTPException {
190
191          String JavaDoc reply = readReply();
192          validateReply(reply, "220");
193      }
194
195
196      /**
197       * Obtain the reader/writer streams for this
198       * connection
199       */

200      private void initStreams()
201          throws IOException JavaDoc {
202
203          // input stream
204
InputStream JavaDoc is = controlSock.getInputStream();
205          reader = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(is));
206
207          // output stream
208
OutputStream JavaDoc os = controlSock.getOutputStream();
209          writer = new OutputStreamWriter JavaDoc(os);
210      }
211
212
213      /**
214       * Get the name of the remote host
215       *
216       * @return remote host name
217       */

218      String JavaDoc getRemoteHostName() {
219          InetAddress JavaDoc addr = controlSock.getInetAddress();
220          return addr.getHostName();
221      }
222
223
224     /**
225      * Set the TCP timeout on the underlying control socket.
226      *
227      * If a timeout is set, then any operation which
228      * takes longer than the timeout value will be
229      * killed with a java.io.InterruptedException.
230      *
231      * @param millis The length of the timeout, in milliseconds
232      */

233     void setTimeout(int millis)
234         throws IOException JavaDoc {
235
236         if (controlSock == null)
237             throw new IllegalStateException JavaDoc(
238                         "Failed to set timeout - no control socket");
239
240         controlSock.setSoTimeout(millis);
241     }
242
243
244      /**
245       * Quit this FTP session and clean up.
246       */

247      public void logout()
248          throws IOException JavaDoc {
249
250          if (log != null) {
251             log.flush();
252             log = null;
253          }
254
255          IOException JavaDoc ex = null;
256          try {
257              writer.close();
258          }
259          catch (IOException JavaDoc e) {
260              ex = e;
261          }
262          try {
263              reader.close();
264          }
265          catch (IOException JavaDoc e) {
266              ex = e;
267          }
268          try {
269              controlSock.close();
270          }
271          catch (IOException JavaDoc e) {
272              ex = e;
273          }
274          if (ex != null)
275              throw ex;
276      }
277
278
279      /**
280       * Request a data socket be created on the
281       * server, connect to it and return our
282       * connected socket.
283       *
284       * @param active if true, create in active mode, else
285       * in passive mode
286       * @return connected data socket
287       */

288      FTPDataSocket createDataSocket(FTPConnectMode connectMode)
289          throws IOException JavaDoc, FTPException {
290
291         if (connectMode == FTPConnectMode.ACTIVE) {
292             return new FTPDataSocket(createDataSocketActive());
293         }
294         else { // PASV
295
return new FTPDataSocket(createDataSocketPASV());
296         }
297      }
298
299
300      /**
301       * Request a data socket be created on the Client
302       * client on any free port, do not connect it to yet.
303       *
304       * @return not connected data socket
305       */

306      ServerSocket JavaDoc createDataSocketActive()
307          throws IOException JavaDoc, FTPException {
308
309         // use any available port
310
ServerSocket JavaDoc socket = new ServerSocket JavaDoc(0);
311
312         // get the local address to which the control socket is bound.
313
InetAddress JavaDoc localhost = controlSock.getLocalAddress();
314
315         // send the PORT command to the server
316
setDataPort(localhost, (short)socket.getLocalPort());
317
318         return socket;
319      }
320
321
322
323     /**
324      * Helper method to convert a byte into an unsigned short value
325      *
326      * @param value value to convert
327      * @return the byte value as an unsigned short
328      */

329     private short toUnsignedShort(byte value) {
330         return ( value < 0 )
331             ? (short) (value + 256)
332             : (short) value;
333      }
334
335     /**
336      * Convert a short into a byte array
337      *
338      * @param value value to convert
339      * @return a byte array
340      */

341     protected byte[] toByteArray (short value) {
342
343         byte[] bytes = new byte[2];
344         bytes[0] = (byte) (value >> 8); // bits 1- 8
345
bytes[1] = (byte) (value & 0x00FF); // bits 9-16
346
return bytes;
347     }
348
349
350     /**
351      * Sets the data port on the server, i.e. sends a PORT
352      * command
353      *
354      * @param host the local host the server will connect to
355      * @param portNo the port number to connect to
356      */

357     private void setDataPort(InetAddress JavaDoc host, short portNo)
358         throws IOException JavaDoc, FTPException {
359
360         byte[] hostBytes = host.getAddress();
361         byte[] portBytes = toByteArray(portNo);
362
363         // assemble the PORT command
364
String JavaDoc cmd = new StringBuffer JavaDoc ("PORT ")
365             .append (toUnsignedShort (hostBytes[0])) .append (",")
366             .append (toUnsignedShort (hostBytes[1])) .append (",")
367             .append (toUnsignedShort (hostBytes[2])) .append (",")
368             .append (toUnsignedShort (hostBytes[3])) .append (",")
369             .append (toUnsignedShort (portBytes[0])) .append (",")
370             .append (toUnsignedShort (portBytes[1])) .toString ();
371
372         // send command and check reply
373
String JavaDoc reply = sendCommand(cmd);
374         validateReply(reply, "200");
375      }
376
377
378      /**
379       * Request a data socket be created on the
380       * server, connect to it and return our
381       * connected socket.
382       *
383       * @return connected data socket
384       */

385      Socket JavaDoc createDataSocketPASV()
386          throws IOException JavaDoc, FTPException {
387
388          // PASSIVE command - tells the server to listen for
389
// a connection attempt rather than initiating it
390
String JavaDoc reply = sendCommand("PASV");
391          validateReply(reply, "227");
392
393          // The reply to PASV is in the form:
394
// 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
395
// where h1..h4 are the IP address to connect and
396
// p1,p2 the port number
397
// Example:
398
// 227 Entering Passive Mode (128,3,122,1,15,87).
399
// NOTE: PASV command in IBM/Mainframe returns the string
400
// 227 Entering Passive Mode 128,3,122,1,15,87 (missing
401
// brackets)
402

403          // extract the IP data string from between the brackets
404
int startIP = reply.indexOf('(');
405          int endIP = reply.indexOf(')');
406
407          // allow for IBM missing brackets around IP address
408
if (startIP < 0 && endIP < 0) {
409              startIP = reply.toUpperCase().lastIndexOf("MODE") + 4;
410              endIP = reply.length();
411          }
412                   
413          String JavaDoc ipData = reply.substring(startIP+1,endIP);
414          int parts[] = new int[6];
415
416          int len = ipData.length();
417          int partCount = 0;
418          StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
419
420          // loop thru and examine each char
421
for (int i = 0; i < len && partCount <= 6; i++) {
422
423              char ch = ipData.charAt(i);
424              if (Character.isDigit(ch))
425                  buf.append(ch);
426              else if (ch != ',') {
427                  throw new FTPException("Malformed PASV reply: " + reply);
428              }
429
430              // get the part
431
if (ch == ',' || i+1 == len) { // at end or at separator
432
try {
433                      parts[partCount++] = Integer.parseInt(buf.toString());
434                      buf.setLength(0);
435                  }
436                  catch (NumberFormatException JavaDoc ex) {
437                      throw new FTPException("Malformed PASV reply: " + reply);
438                  }
439              }
440          }
441
442          // assemble the IP address
443
// we try connecting, so we don't bother checking digits etc
444
String JavaDoc ipAddress = parts[0] + "."+ parts[1]+ "." +
445              parts[2] + "." + parts[3];
446
447          // assemble the port number
448
int port = (parts[4] << 8) + parts[5];
449
450          // create the socket
451
return new Socket JavaDoc(ipAddress, port);
452      }
453
454
455
456      /**
457       * Send a command to the FTP server and
458       * return the server's reply
459       *
460       * @return reply to the supplied command
461       */

462      String JavaDoc sendCommand(String JavaDoc command)
463          throws IOException JavaDoc {
464
465          log(DEBUG_ARROW + command);
466
467          // send it
468
writer.write(command + EOL);
469          writer.flush();
470
471          // and read the result
472
return readReply();
473      }
474
475      /**
476       * Read the FTP server's reply to a previously
477       * issued command. RFC 959 states that a reply
478       * consists of the 3 digit code followed by text.
479       * The 3 digit code is followed by a hyphen if it
480       * is a muliline response, and the last line starts
481       * with the same 3 digit code.
482       *
483       * @return reply string
484       */

485      String JavaDoc readReply()
486          throws IOException JavaDoc {
487
488          String JavaDoc firstLine = reader.readLine();
489          if (firstLine == null || firstLine.length() == 0)
490              throw new IOException JavaDoc("Unexpected null reply received");
491
492          StringBuffer JavaDoc reply = new StringBuffer JavaDoc(firstLine);
493
494          log(reply.toString());
495  
496          String JavaDoc replyCode = reply.toString().substring(0, 3);
497
498          // check for multiline response and build up
499
// the reply
500
if (reply.charAt(3) == '-') {
501
502              boolean complete = false;
503              while (!complete) {
504                  String JavaDoc line = reader.readLine();
505                  if (line == null)
506                      throw new IOException JavaDoc("Unexpected null reply received");
507
508                  log(line);
509
510                  if (line.length() > 3 &&
511                      line.substring(0, 3).equals(replyCode) &&
512                      line.charAt(3) == ' ') {
513                      reply.append(line.substring(3));
514                      complete = true;
515                  }
516                  else { // not the last line
517
reply.append(" ");
518                      reply.append(line);
519                  }
520              } // end while
521
} // end if
522
return reply.toString();
523      }
524
525
526      /**
527       * Validate the response the host has supplied against the
528       * expected reply. If we get an unexpected reply we throw an
529       * exception, setting the message to that returned by the
530       * FTP server
531       *
532       * @param reply the entire reply string we received
533       * @param expectedReplyCode the reply we expected to receive
534       *
535       */

536      FTPReply validateReply(String JavaDoc reply, String JavaDoc expectedReplyCode)
537          throws IOException JavaDoc, FTPException {
538
539          // all reply codes are 3 chars long
540
String JavaDoc replyCode = reply.substring(0, 3);
541          String JavaDoc replyText = reply.substring(4);
542          FTPReply replyObj = new FTPReply(replyCode, replyText);
543          
544          if (replyCode.equals(expectedReplyCode))
545              return replyObj;
546
547          // if unexpected reply, throw an exception
548
throw new FTPException(replyText, replyCode);
549      }
550
551      /**
552       * Validate the response the host has supplied against the
553       * expected reply. If we get an unexpected reply we throw an
554       * exception, setting the message to that returned by the
555       * FTP server
556       *
557       * @param reply the entire reply string we received
558       * @param expectedReplyCodes array of expected replies
559       * @return an object encapsulating the server's reply
560       *
561       */

562      FTPReply validateReply(String JavaDoc reply, String JavaDoc[] expectedReplyCodes)
563          throws IOException JavaDoc, FTPException {
564
565          // all reply codes are 3 chars long
566
String JavaDoc replyCode = reply.substring(0, 3);
567          String JavaDoc replyText = reply.substring(4);
568
569          FTPReply replyObj = new FTPReply(replyCode, replyText);
570
571          for (int i = 0; i < expectedReplyCodes.length; i++)
572              if (replyCode.equals(expectedReplyCodes[i]))
573                  return replyObj;
574
575          // got this far, not recognised
576
throw new FTPException(replyText, replyCode);
577      }
578
579
580      /**
581       * Switch debug of responses on or off
582       *
583       * @param on true if you wish to have responses to
584       * stdout, false otherwise
585       */

586      void debugResponses(boolean on) {
587          debugResponses = on;
588      }
589
590      /**
591       * Set the logging stream, replacing
592       * stdout. If null log supplied, logging is
593       * switched off
594       *
595       * @param log the new logging stream
596       */

597      void setLogStream(PrintWriter JavaDoc log) {
598          if (log != null)
599              this.log = log;
600          else
601              debugResponses = false;
602      }
603      
604      
605      /**
606       * Log a message, if logging is set up
607       *
608       * @param msg message to log
609       */

610      void log(String JavaDoc msg) {
611          if (debugResponses && log != null) {
612              if (!msg.startsWith(PASSWORD_MESSAGE))
613                  log.println(msg);
614              else
615                  log.println(PASSWORD_MESSAGE + " ********");
616          }
617      }
618           
619 }
620
621
622
Popular Tags