1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package gnu.inet.ftp;
22
23 import java.io.BufferedReader;
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.OutputStream;
33 import java.io.RandomAccessFile;
34 import java.util.Enumeration;
35 import java.util.Vector;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40 /***
41 * This class implements convenience methods that wrapper the FtpClientProtocol
42 * methods for common functionality.
43 * <P>
44 * Most developers will want to use this class rather than the lower-level
45 * FtpClientProtocol methods directly.
46 *
47 * @see FtpClientProtocol
48 */
49 public class FtpClient extends FtpClientProtocol implements
50 ConnectionEventSource, TransferEventSource {
51
52 private boolean passive;
53
54 private final static Log log = LogFactory.getLog(FtpClient.class);
55
56
57 private char mode;
58
59 private Vector connectionListeners;
60
61 private Vector transferListeners;
62
63 /***
64 * This is a cached PassiveConnection instance. After some hair-pulling I
65 * came to realize that in order to avoid an annoying bug, I needed to cache
66 * the last PassiveConnection instance in case an error was encountered in
67 * the last passive transfer. In my test cases the error condition that
68 * triggered the wierd bug could be duplicated by attempting to
69 * getList(some-non-exising-file). This caused an exception during the LIST
70 * transfer. The next PASV command would return the same IP/Port values.
71 * Opening a second socket to the same IP/Port would yield an exception or
72 * other errors. Caching the last PassiveConnection (and by extension the
73 * Socket) I could reuse the last PassiveConnection values avoiding the
74 * bug/error-condition. This is not very pretty but it works. Unfortunately,
75 * I'm worried that it may not be very portable since it relies on the
76 * behavior of the HylaFAX server. I'll have to see if this behavior is in
77 * the FTP RFC (RFC0959.)
78 * <P>
79 * Whenever a successful passive transfer occurrs, this variable should be
80 * set to null, thereby invalidating the cached value.
81 */
82 protected PassiveConnection connection = null;
83
84 /***
85 * default constructor. initialize class state.
86 */
87 public FtpClient() {
88 passive = false;
89 mode = MODE_STREAM;
90 transferListeners = new Vector();
91 connectionListeners = new Vector();
92 }
93
94 /***
95 * enable or disable passive transfers
96 *
97 * @param passive
98 * indicates whether passive transfers should be used
99 */
100 public synchronized void setPassive(boolean passive) {
101 this.passive = passive;
102 }
103
104 /***
105 * check whether we're using passive transfers or not.
106 *
107 * @return true if passive transfers are enabled, false otherwise
108 */
109 public synchronized boolean getPassive() {
110 return passive;
111 }
112
113 /***
114 * set the transfer mode. valid mode values are MODE_* listed in the
115 * FtpClientProtocol class.
116 *
117 * @param mode
118 * the new mode setting
119 * @exception IOException
120 * an io error occurred talking to the server
121 * @exception ServerResponseException
122 * the server replied with an error code
123 */
124 public synchronized void mode(char newMode) throws IOException,
125 ServerResponseException {
126 super.mode(newMode);
127 this.mode = newMode;
128 }
129
130 /***
131 * Register a connection listener with the event source.
132 *
133 * @param listener
134 * the listener to register with the event source
135 */
136 public void addConnectionListener(ConnectionListener listener) {
137 connectionListeners.addElement(listener);
138 }
139
140 /***
141 * Register a set of connection listeners with the event source.
142 *
143 * @param listeners
144 * the listeners to register with the event source
145 */
146 public void addConnectionListeners(Vector listeners) {
147 Enumeration e = listeners.elements();
148 while (e.hasMoreElements()) {
149 ConnectionListener listener = (ConnectionListener) e.nextElement();
150 connectionListeners.addElement(listener);
151 }
152 }
153
154 /***
155 * De-register a connection listener with the event source.
156 *
157 * @param listener
158 * the listener to de-register with the event source
159 */
160 public void removeConnectionListener(ConnectionListener listener) {
161 connectionListeners.removeElement(listener);
162 }
163
164 /***
165 * Register a transfer listener with the event source.
166 *
167 * @param listener
168 * the listener to register with the event source
169 */
170 public void addTransferListener(TransferListener listener) {
171 transferListeners.addElement(listener);
172 }
173
174 /***
175 * Register a set of transfer listeners with the event source.
176 *
177 * @param listeners
178 * the listeners to register with the event source
179 */
180 public void addTransferListeners(Vector listeners) {
181 Enumeration e = listeners.elements();
182 while (e.hasMoreElements()) {
183 TransferListener listener = (TransferListener) e.nextElement();
184 transferListeners.addElement(listener);
185 }
186 }
187
188 /***
189 * De-register a transfer listener with the event source.
190 *
191 * @param listener
192 * the listener to de-register with the event source
193 */
194 public void removeTransferListener(TransferListener listener) {
195 transferListeners.removeElement(listener);
196 }
197
198 /***
199 * put a temp file, the data is stored in a uniquely named file on the
200 * server. The remote temp file is deleted when the connection is closed.
201 * NOTE: this calls stot() internally.
202 *
203 * @exception IOException
204 * io error occurred talking to the server
205 * @exception ServerResponseException
206 * server replied with error code
207 * @return the filename of the temp file
208 */
209 public synchronized String putTemporary(InputStream data)
210 throws IOException, ServerResponseException {
211 String filename;
212
213 Putter put;
214 if (passive == true) {
215
216 if (connection == null) {
217 connection = new PassiveConnection(pasv());
218 }
219 put = new PassivePutter(data, connection);
220 } else {
221
222 ActivePutter aput = new ActivePutter(data);
223 put = aput;
224 port(getInetAddress(), aput.getPort());
225 }
226 put.setMode(mode);
227 put.setType(fileType);
228 put.addConnectionListeners(connectionListeners);
229 put.addTransferListeners(transferListeners);
230 put.start();
231
232
233 try {
234 filename = stot(data);
235 } catch (IOException ioe) {
236 put.cancel();
237 throw ioe;
238 } catch (ServerResponseException sree) {
239 put.cancel();
240 throw sree;
241 } finally {
242
243 try {
244 put.join();
245 } catch (InterruptedException ie) {
246 }
247 }
248
249 connection = null;
250
251 return filename;
252 }
253
254 /***
255 * put a file with a unique name. NOTE: this calls stou() internally.
256 *
257 * @exception IOException
258 * a socket IO error occurred
259 * @exception ServerResponseException
260 * the server responded with an error code
261 * @return the name of the file created
262 */
263 public synchronized String put(InputStream in) throws IOException,
264 ServerResponseException {
265 String filename;
266
267
268 Putter put;
269 if (passive == true) {
270
271 if (connection == null) {
272 connection = new PassiveConnection(pasv());
273 }
274 put = new PassivePutter(in, connection);
275 } else {
276
277 ActivePutter aput = new ActivePutter(in);
278 put = aput;
279 port(getInetAddress(), aput.getPort());
280
281 }
282 put.setMode(mode);
283 put.setType(fileType);
284 put.addConnectionListeners(connectionListeners);
285 put.addTransferListeners(transferListeners);
286 put.start();
287
288
289 try {
290 filename = stou(in);
291 } catch (IOException ioe) {
292 put.cancel();
293 throw ioe;
294 } catch (ServerResponseException sree) {
295 put.cancel();
296 throw sree;
297 } finally {
298
299 try {
300 put.join();
301 } catch (InterruptedException ie) {
302 }
303 }
304
305 connection = null;
306
307 return filename;
308 }
309
310 /***
311 * store a file. NOTE: this calls stor() internally.
312 *
313 * @param pathname
314 * name of file to store on server (where to put the file on
315 * the server)
316 * @exception IOException
317 * a socket IO error occurred
318 * @exception ServerResponseException
319 * the server responded with an error
320 */
321 public synchronized void put(InputStream in, String pathname)
322 throws IOException, ServerResponseException {
323
324
325 Putter put;
326 if (passive == true) {
327
328 if (connection == null) {
329 connection = new PassiveConnection(pasv());
330 }
331 put = new PassivePutter(in, connection);
332 } else {
333
334 ActivePutter aput = new ActivePutter(in);
335 put = aput;
336 port(getInetAddress(), aput.getPort());
337
338 }
339 put.setMode(mode);
340 put.setType(fileType);
341 put.addConnectionListeners(connectionListeners);
342 put.addTransferListeners(transferListeners);
343 put.start();
344
345
346 try {
347 stor(in, pathname);
348 } catch (IOException ioe) {
349 put.cancel();
350 throw ioe;
351 } catch (ServerResponseException sree) {
352 put.cancel();
353 throw sree;
354 } finally {
355
356 try {
357 put.join();
358 } catch (InterruptedException ie) {
359 }
360 }
361
362 connection = null;
363
364
365 }
366
367 /***
368 * get a long-style listing of files in the given directory. NOTE: this
369 * calls the list() method internally.
370 *
371 * @param path
372 * the path that we're interested in finding the contents of
373 * @exception IOException
374 * an IO error occurred
375 * @exception FileNotFoundException
376 * the given path doesn't exist
377 * @exception ServerResponseException
378 * the server reported an error
379 * @return a Vector of Strings containing the list information
380 */
381 public synchronized Vector getList(String path) throws IOException,
382 FileNotFoundException, ServerResponseException {
383 Vector filenames = new Vector();
384 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
385 Getter spot;
386
387 if (passive == true) {
388
389 if (connection == null) {
390 connection = new PassiveConnection(pasv());
391 }
392 spot = new PassiveGetter(buffer, connection);
393 } else {
394 ActiveGetter aget = new ActiveGetter(buffer);
395
396 port(getInetAddress(), aget.getPort());
397 spot = aget;
398 }
399
400
401 spot.addConnectionListeners(connectionListeners);
402 spot.addTransferListeners(transferListeners);
403 spot.start();
404
405
406 try {
407 list(path);
408 } catch (FileNotFoundException fnfe) {
409 spot.cancel();
410 throw fnfe;
411 } catch (IOException ioe) {
412 spot.cancel();
413 throw ioe;
414 } catch (ServerResponseException sree) {
415 spot.cancel();
416 throw sree;
417 } finally {
418
419 try {
420 spot.join();
421 } catch (InterruptedException ie) {
422 }
423 }
424
425 connection = null;
426
427
428 BufferedReader data = new BufferedReader(new InputStreamReader(
429 new ByteArrayInputStream(buffer.toByteArray())));
430 boolean done = false;
431 while (!done) {
432 String line = data.readLine();
433 if (line == null) {
434 done = true;
435 } else {
436 filenames.addElement(line);
437 }
438 }
439
440 return filenames;
441 }
442
443 /***
444 * get a long-style listing of files in the current directory. NOTE: this
445 * calls the list() method internally with the "." path.
446 *
447 * @exception IOException
448 * an IO error occurred
449 * @exception FileNotFoundException
450 * the "." path doesn't exist
451 * @exception ServerResponseException
452 * the server reported an error
453 * @return a Vector of Strings containing the list information
454 */
455 public synchronized Vector getList() throws IOException,
456 FileNotFoundException, ServerResponseException {
457 return getList(null);
458 }
459
460 /***
461 * get name list of files in the given directory. Similar to getList() but
462 * returns filenames only where getList() returns other, system dependant
463 * information.
464 *
465 * @param path
466 * the path of the directory that we want the name list of
467 * @exception IOException
468 * an IO error occurred
469 * @exception ServerResponseException
470 * the server reported an error
471 * @exception FileNotFoundException
472 * the requested path does not exist
473 * @return Vector of Strings containing filenames
474 */
475 public synchronized Vector getNameList(String path) throws IOException,
476 ServerResponseException, FileNotFoundException {
477 Vector filenames = new Vector();
478 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
479 Getter sparky;
480
481 if (passive == true) {
482
483 if (connection == null) {
484 connection = new PassiveConnection(pasv());
485 }
486 sparky = new PassiveGetter(buffer, connection);
487 } else {
488 ActiveGetter aget = new ActiveGetter(buffer);
489
490 port(getInetAddress(), aget.getPort());
491 sparky = aget;
492 }
493
494
495 sparky.addConnectionListeners(connectionListeners);
496 sparky.addTransferListeners(transferListeners);
497 sparky.start();
498
499
500 try {
501 nlst(path);
502 } catch (FileNotFoundException fnfe) {
503 sparky.cancel();
504 throw fnfe;
505 } catch (IOException ioe) {
506 sparky.cancel();
507 throw ioe;
508 } catch (ServerResponseException sree) {
509 sparky.cancel();
510 throw sree;
511 } catch (Exception e) {
512 sparky.cancel();
513 throw new ServerResponseException(e.getMessage());
514 } finally {
515
516 try {
517 sparky.join();
518 } catch (InterruptedException ie) {
519 }
520 }
521
522 connection = null;
523
524
525 BufferedReader data = new BufferedReader(new InputStreamReader(
526 new ByteArrayInputStream(buffer.toByteArray())));
527 boolean done = false;
528 while (!done) {
529 String line = data.readLine();
530 if (line == null) {
531 done = true;
532 } else {
533 filenames.addElement(line);
534 }
535 }
536
537 return filenames;
538 }
539
540 /***
541 * get name list of files in the current directory. Similar to getList() but
542 * returns filenames only where getList() returns other, system dependant
543 * information.
544 *
545 * @exception IOException
546 * an IO error occurred
547 * @exception ServerResponseException
548 * the server reported an error
549 * @exception FileNotFoundException
550 * the requested path does not exist
551 * @return Vector of Strings containing filenames
552 */
553 public synchronized Vector getNameList() throws IOException,
554 ServerResponseException, FileNotFoundException {
555 return getNameList(null);
556 }
557
558 /***
559 * GET the named file, FTP style.
560 *
561 * @param path
562 * the name of the file to GET. This can be a full or partial
563 * path.
564 * @param out
565 * the OutputStream to write the file data to
566 * @exception IOException
567 * an IO error occurred
568 * @exception ServerResponseException
569 * the server reported an error
570 * @exception FileNotFoundException
571 * the given path does not exist
572 */
573 public synchronized void get(String path, OutputStream out)
574 throws IOException, FileNotFoundException, ServerResponseException {
575 Getter get;
576 if (passive == true) {
577
578 if (connection == null) {
579 connection = new PassiveConnection(pasv());
580 }
581 get = new PassiveGetter(out, connection);
582 } else {
583
584 ActiveGetter aget = new ActiveGetter(out);
585 get = aget;
586 port(getInetAddress(), aget.getPort());
587 }
588 get.setMode(mode);
589 get.setType(fileType);
590 get.addConnectionListeners(connectionListeners);
591 get.addTransferListeners(transferListeners);
592 get.start();
593
594
595 try {
596 retr(path);
597 } catch (FileNotFoundException fnfe) {
598 get.cancel();
599 throw fnfe;
600 } catch (IOException ioe) {
601 get.cancel();
602 throw ioe;
603 } catch (ServerResponseException sree) {
604 get.cancel();
605 throw sree;
606 } finally {
607
608 try {
609 get.join();
610 } catch (InterruptedException ie) {
611 }
612 }
613
614 connection = null;
615
616 }
617
618 /***
619 * Renames remote file from source to target. Uses rnfr and rnto internally.
620 *
621 * @param source
622 * file to rename
623 * @param target
624 * new filename
625 * @exception IOException
626 * an IO error occurred
627 * @exception ServerResponseException
628 * the server reported an error
629 * @exception FileNotFoundException
630 * the source file does not exist
631 */
632 public synchronized void rename(String source, String target)
633 throws IOException, ServerResponseException, FileNotFoundException {
634 rnfr(source);
635 rnto(target);
636 return;
637 }
638
639 /***
640 * run some basic tests. eventually this method should be removed in favor
641 * of a decent testing framework.
642 *
643 * @param Arguments
644 * an array of command-line-argument Strings
645 */
646 public static void main(String Arguments[]) {
647 FtpClient c = new FtpClient();
648 try {
649 c.open("localhost");
650 c.noop();
651
652 c.setPassive(true);
653
654 c.user("jaiger");
655
656 c.type(TYPE_IMAGE);
657
658
659 System.out.println("current directory is: " + c.pwd());
660
661 c.cwd("docq");
662
663 System.out.println("current directory is: " + c.pwd());
664
665 c.cdup();
666 System.out.println("current directory is: " + c.pwd());
667
668
669 System.out.println("idle timer set to " + c.idle() + " seconds.");
670 c.idle(1800);
671 System.out.println("idle timer set to " + c.idle() + " seconds.");
672
673
674 c.stru(STRU_FILE);
675
676 c.stru(STRU_TIFF);
677
678 c.stru(STRU_FILE);
679
680
681 {
682 String filename = "test.ps";
683 FileInputStream file = new FileInputStream(filename);
684
685 String f = c.putTemporary(file);
686 System.out.println("filename= " + f);
687
688
689 long local_size, remote_size;
690 local_size = (new RandomAccessFile(filename, "r").length());
691 remote_size = c.size(f);
692 System.out.println(filename + " local size is " + local_size);
693 System.out.println(f + " remote size is " + remote_size);
694
695
696 FileOutputStream out_file = new FileOutputStream(filename
697 + ".retr");
698 c.get(f, out_file);
699 local_size = (new RandomAccessFile(filename + ".retr", "r")
700 .length());
701 System.out.println(filename + ".retr size is " + local_size);
702
703
704 FileOutputStream zip_file = new FileOutputStream(filename
705 + ".gz");
706 c.mode(MODE_ZLIB);
707 c.get(f, zip_file);
708 local_size = (new RandomAccessFile(filename + ".gz", "r")
709 .length());
710 System.out.println(filename + ".gz size is " + local_size);
711 c.mode(MODE_STREAM);
712
713 }
714
715
716
717 {
718 Vector files;
719 int counter;
720
721
722 files = c.getList();
723 for (counter = 0; counter < files.size(); counter++) {
724 System.out.println((String) files.elementAt(counter));
725 }
726
727
728 files = c.getList("/tmp");
729 for (counter = 0; counter < files.size(); counter++) {
730 System.out.println((String) files.elementAt(counter));
731 }
732
733
734 c.mode(MODE_ZLIB);
735 files = c.getList("/tmp");
736 for (counter = 0; counter < files.size(); counter++) {
737 System.out.println((String) files.elementAt(counter));
738 }
739 c.mode(MODE_STREAM);
740
741 try {
742
743 c.getList("/joey-joe-joe-jr.shabba-do");
744
745
746
747 System.out.println("ERROR: file not found was expected");
748 } catch (FileNotFoundException fnfe) {
749
750 System.out.println("GOOD: file not found, as expected");
751 }
752
753
754 files = c.getList();
755 for (counter = 0; counter < files.size(); counter++) {
756 System.out.println((String) files.elementAt(counter));
757 }
758 }
759
760
761
762 {
763 Vector files;
764 int counter;
765
766
767 files = c.getNameList("/tmp");
768 for (counter = 0; counter < files.size(); counter++) {
769 System.out.println((String) files.elementAt(counter));
770 }
771
772 files = c.getNameList("/tmp");
773 for (counter = 0; counter < files.size(); counter++) {
774 System.out.println((String) files.elementAt(counter));
775 }
776
777
778 c.mode(MODE_ZLIB);
779 files = c.getNameList("/tmp");
780 for (counter = 0; counter < files.size(); counter++) {
781 System.out.println((String) files.elementAt(counter));
782 }
783 c.mode(MODE_STREAM);
784
785
786 files = c.getNameList();
787 for (counter = 0; counter < files.size(); counter++) {
788 System.out.println((String) files.elementAt(counter));
789 }
790
791 }
792
793
794
795 String system = c.syst();
796 System.out.println("system type: " + system + ".");
797
798 c.noop();
799
800
801 {
802
803 Vector status = c.stat();
804 int counter;
805 for (counter = 0; counter < status.size(); counter++) {
806 System.out.println(status.elementAt(counter));
807 }
808
809
810 status = c.stat("docq");
811 for (counter = 0; counter < status.size(); counter++) {
812 System.out.println(status.elementAt(counter));
813 }
814
815
816 try {
817 status = c.stat("joey-joe-joe-junior-shabba-do");
818 for (counter = 0; counter < status.size(); counter++) {
819 System.out.println(status.elementAt(counter));
820 }
821 } catch (FileNotFoundException fnfe) {
822 System.out
823 .println("GOOD: file not found. this is what we expected");
824 }
825 }
826
827 c.noop();
828
829 c.quit();
830
831 } catch (Exception e) {
832 log.error(e.getMessage(), e);
833 }
834 System.out.println("main: end");
835 }
836 }
837
838