1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package gnu.inet.ftp;
31
32 import java.io.BufferedReader;
33 import java.io.FileNotFoundException;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.OutputStreamWriter;
38 import java.net.InetAddress;
39 import java.net.InetSocketAddress;
40 import java.net.Socket;
41 import java.net.SocketAddress;
42 import java.net.SocketException;
43 import java.net.UnknownHostException;
44 import java.text.ParseException;
45 import java.text.SimpleDateFormat;
46 import java.util.Calendar;
47 import java.util.Date;
48 import java.util.StringTokenizer;
49 import java.util.TimeZone;
50 import java.util.Vector;
51
52 import org.apache.commons.logging.Log;
53 import org.apache.commons.logging.LogFactory;
54
55 /***
56 * This is the core implementation of the FTP client protocol, RFC0959. You
57 * should be able to find the document via searches on the World Wide Web. At
58 * the time of this writing, RFCs could be found at <A
59 * HREF="http://www.faqs.org">www.faqs.org</A>.
60 * <P>
61 * The purpose of this class is to implement the FTP client protocol as simply
62 * and straight-forward as possible.
63 * <P>
64 * This package was started as an implementation of the HylaFAX client protocol
65 * so the features that overlap with the HylaFAX protocol will be the first
66 * implemented and most tested.
67 * <P>
68 * Method names are not my choosing for the most part. They have been largely
69 * pulled straight from the protocol and man pages. I expect that convenience
70 * classes and methods, with more developer friendly names will be built on top
71 * of this raw protocol implementation as time passes.
72 * <P>
73 * Most developers should use the higher-level FtpClient to perform some actions
74 * rather than this class directly.
75 *
76 * @author <a href="mailto:jaiger@net-foundry.com">Joe Phillips</a>
77 * @author <a href="mailto:steve@mjnservices.com">Steven Jardine</a>
78 * @author <a href="mailto:dorpsidioot@gmail.com">Lieven Poelman</a>
79 *
80 * @version $Id:FtpClientProtocol.java 77 2008-02-20 21:54:51Z sjardine $
81 */
82 public class FtpClientProtocol extends Object {
83
84 private final static Log log = LogFactory.getLog(FtpClientProtocol.class);
85
86 protected Socket sock;
87
88 protected int port;
89
90 protected BufferedReader istream;
91
92 protected OutputStreamWriter ostream;
93
94 protected String greeting;
95
96 protected char fileType;
97
98 protected int socketTimeout = 10000;
99
100 protected String hylafaxServerHost = null;
101
102 protected int hylafaxServerPort = -1;
103
104 protected String hylafaxServerUsername = null;
105
106
107
108 /***
109 * @param socketTimeout
110 * timout in milliseconds.
111 */
112 public void setSocketTimeout(int socketTimeout) {
113 this.socketTimeout = socketTimeout;
114 }
115
116 /***
117 * @return the timout in milliseconds.
118 */
119 public int getSocketTimeout() {
120 return socketTimeout;
121 }
122
123
124
125 /***
126 * default FTP server port.
127 */
128 public static int DEFAULT_PORT = 21;
129
130 /***
131 * default constructor. sets up the initial class state.
132 */
133 public FtpClientProtocol() {
134 fileType = TYPE_ASCII;
135 }
136
137
138
139 /***
140 * get the local ip address of the control connection socket.
141 *
142 * @return control connection ip address
143 */
144 public synchronized InetAddress getInetAddress() {
145 return sock.getLocalAddress();
146 }
147
148 /***
149 * open a connection to a given server and port this is an alias for
150 * connect()
151 *
152 * @param host
153 * the hostname of the HylaFAX server
154 * @param portnumber
155 * the port the server is listening on
156 * @exception UnknownHostException
157 * cannot resolve the given hostname
158 * @exception IOException
159 * IO error occurred
160 * @exception ServerResponseException
161 * the server replied with an error code
162 */
163 public synchronized void open(String host, int portnumber)
164 throws UnknownHostException, IOException, ServerResponseException {
165 connect(host, portnumber);
166 }
167
168 /***
169 * open a connection to a given server at default port this is an alias for
170 * connect()
171 *
172 * @param host
173 * the hostname of the HylaFAX server
174 * @exception UnknownHostException
175 * cannot resolve the given hostname
176 * @exception IOException
177 * IO error occurred
178 * @exception ServerResponseException
179 * the server replied with an error code
180 */
181 public synchronized void open(String host) throws UnknownHostException,
182 IOException, ServerResponseException {
183 connect(host, DEFAULT_PORT);
184 }
185
186 /***
187 * open a connection to the localhost on the default port
188 *
189 * @exception UnknownHostException
190 * cannot resolve the given hostname
191 * @exception IOException
192 * IO error occurred
193 * @exception ServerResponseException
194 * the server replied with an error code
195 */
196 public synchronized void open() throws UnknownHostException, IOException,
197 ServerResponseException {
198 connect("localhost", DEFAULT_PORT);
199 }
200
201 /***
202 * send the user name for this session
203 *
204 * @param username
205 * name of the user to login as
206 * @exception IOException
207 * io error occurred
208 * @exception ServerResponseException
209 * server replied with an error code
210 * @return true if a password is required, false if no password is required
211 */
212 public synchronized boolean user(String username) throws IOException,
213 ServerResponseException {
214
215 hylafaxServerUsername = username;
216
217
218 ostream.write("user " + username + "\r\n");
219 ostream.flush();
220 log.debug("-> user " + username);
221
222
223 String response = readResponse(istream);
224
225 log.debug(response);
226 String temp = new String(response.substring(0, 3));
227 if (temp.equals("230")) {
228 return false;
229 } else if (temp.equals("331")) {
230 return true;
231 } else {
232 throw (new ServerResponseException(response));
233 }
234 }
235
236 /***
237 * send the password for this username and session
238 *
239 * @param password
240 * the password to login with
241 * @exception IOException
242 * io error occurred
243 * @exception ServerResponseException
244 * server replied with an error code
245 */
246 public synchronized void pass(String password) throws IOException,
247 ServerResponseException {
248
249 ostream.write("pass " + password + "\r\n");
250 ostream.flush();
251
252 log.debug("-> pass");
253
254
255 String response = readResponse(istream);
256
257 log.debug(response);
258
259 StringTokenizer st = new StringTokenizer(response, " -");
260 if (!st.nextToken().equals("230")) {
261 throw (new ServerResponseException(response));
262 }
263 }
264
265 /***
266 * perform server No Operation could be used as a keep-alive
267 *
268 * @exception IOException
269 * io error occurred
270 * @exception ServerResponseException
271 * server replied with an error code
272 */
273 public synchronized void noop() throws IOException, ServerResponseException {
274
275 ostream.write("noop\r\n");
276 ostream.flush();
277 log.debug("-> noop");
278
279
280 String response = readResponse(istream);
281 log.debug(response);
282
283 String temp = new String(response.substring(0, 3));
284 if (!temp.equals("200")) {
285 throw (new ServerResponseException(response));
286 }
287 }
288
289 /***
290 * return current directory
291 *
292 * @exception IOException
293 * io error occurred
294 * @exception ServerResponseException
295 * server replied with an error code
296 */
297 public synchronized String pwd() throws IOException,
298 ServerResponseException {
299
300 ostream.write("pwd\r\n");
301 ostream.flush();
302 log.debug("-> pwd");
303
304
305 String response = readResponse(istream);
306 log.debug(response);
307
308
309 String temp = new String(response.substring(0, 3));
310 if (!temp.equals("257")) {
311
312 throw (new ServerResponseException(response));
313 }
314
315 StringTokenizer st = new StringTokenizer(response, "\"");
316 st.nextToken();
317 return st.nextToken();
318 }
319
320 /***
321 * change current working directory
322 *
323 * @param value
324 * directory to set to current working directory
325 * @exception IOException
326 * io error occurred
327 * @exception ServerResponseException
328 * server replied with an error code
329 */
330 public synchronized void cwd(String value) throws IOException,
331 ServerResponseException {
332
333 ostream.write("cwd " + value + "\r\n");
334 ostream.flush();
335 log.debug("-> cwd " + value);
336
337
338 String response = readResponse(istream);
339 log.debug(response);
340
341
342 String temp = response.substring(0, 3);
343 if (!"250".equals(temp)) {
344
345 throw (new ServerResponseException(response));
346 }
347 }
348
349 /***
350 * change to parent of current working directory
351 *
352 * @exception IOException
353 * io error occurred
354 * @exception ServerResponseException
355 * server replied with error code
356 */
357 public synchronized void cdup() throws IOException, ServerResponseException {
358
359 ostream.write("cdup\r\n");
360 ostream.flush();
361 log.debug("-> cdup");
362
363
364 String response = readResponse(istream);
365 log.debug(response);
366
367
368 StringTokenizer st = new StringTokenizer(response);
369 if (!st.nextToken().equals("250")) {
370 throw (new ServerResponseException(response));
371 }
372 }
373
374 /***
375 * get the current idle timeout in seconds
376 *
377 * @exception IOException
378 * io error occurred
379 * @exception ServerResponseException
380 * server replied with an error code
381 * @return server's idle timeout in seconds
382 */
383 public synchronized long idle() throws IOException, ServerResponseException {
384
385 ostream.write("idle\r\n");
386 ostream.flush();
387 log.debug("-> idle");
388
389
390 String response = readResponse(istream);
391 log.debug(response);
392
393
394 StringTokenizer st = new StringTokenizer(response);
395 if (!st.nextToken().equals("213")) {
396
397 throw (new ServerResponseException(response));
398 }
399
400 Long l = new Long(st.nextToken());
401 return l.longValue();
402 }
403
404 /***
405 * set the idle timeout value to the given number of seconds
406 *
407 * @param timeout
408 * new timeout value in seconds (MAX = 7200)
409 * @exception IOException
410 * io error occurred
411 * @exception ServerResponseException
412 * server replied with an error code
413 */
414 public synchronized void idle(long timeout) throws IOException,
415 ServerResponseException {
416
417 ostream.write("idle " + timeout + "\r\n");
418 ostream.flush();
419 log.debug("-> idle " + timeout);
420
421
422 String response = readResponse(istream);
423 log.debug(response);
424
425
426 StringTokenizer st = new StringTokenizer(response);
427 if (!st.nextToken().equals("213")) {
428
429 throw (new ServerResponseException(response));
430 }
431 }
432
433 /***
434 * delete the given file.
435 *
436 * @param pathname
437 * the name of the file to delete
438 * @exception IOException
439 * a socket IO error happened
440 * @exception ServerResponseException
441 * the server replied with an error code
442 */
443 public synchronized void dele(String pathname) throws IOException,
444 ServerResponseException {
445 ostream.write("dele " + pathname + "\r\n");
446 ostream.flush();
447 log.debug("-> dele " + pathname);
448
449 String response = readResponse(istream);
450 log.debug(response);
451
452 if (!response.substring(0, 3).equals("250")) {
453 throw (new ServerResponseException(response));
454 }
455 }
456
457 /***
458 * the file type is ASCII
459 */
460 public static final char TYPE_ASCII = 'A';
461
462 /***
463 * the file type is EBCDIC
464 */
465 public static final char TYPE_EBCDIC = 'E';
466
467 /***
468 * the file type is an 'image'. the file is binary data.
469 */
470 public static final char TYPE_IMAGE = 'I';
471
472 /***
473 * the file type is a local type
474 */
475 public static final char TYPE_LOCAL = 'L';
476
477 /***
478 * Set the file type for transfer. File types can be TYPE_ASCII,
479 * TYPE_EBCDIC, TYPE_IMAGE or TYPE_LOCAL. This may affect how the client and
480 * server interpret file data during transfers. The default type is
481 * TYPE_ASCII per RFC 959.
482 *
483 * @param value
484 * new type
485 * @exception IOException
486 * io error occurred
487 * @exception ServerResponseException
488 * server replied with error code
489 */
490 public synchronized void type(char value) throws IOException,
491 ServerResponseException {
492 ostream.write("type " + value + "\r\n");
493 ostream.flush();
494 log.debug("-> type " + value);
495
496 String response = readResponse(istream);
497 log.debug(response);
498
499 StringTokenizer st = new StringTokenizer(response);
500 if (!st.nextToken().equals("200")) {
501 throw (new ServerResponseException(response));
502 }
503 fileType = value;
504 }
505
506 /***
507 * Return the file type for transfer. File types can be TYPE_ASCII,
508 * TYPE_EBCDIC, TYPE_IMAGE, or TYPE_LOCAL.
509 *
510 * @return current file transfer type
511 */
512 public char getType() {
513 return fileType;
514 }
515
516
517
518 /***
519 * data transfer mode is STREAM
520 */
521 public static final char MODE_STREAM = 'S';
522
523 /***
524 * data transfer mode is BLOCK mode
525 */
526 public static final char MODE_BLOCK = 'B';
527
528 /***
529 * data transfer mode is COMPRESSED, as in UNIX compress.
530 */
531 public static final char MODE_COMPRESSED = 'C';
532
533 /***
534 * data transfer mode is ZLIB, as in zlib compression.
535 */
536 public static final char MODE_ZLIB = 'Z';
537
538 /***
539 * set the data transfer mode. Valid modes are MODE_STREAM, MODE_BLOCK,
540 * MODE_COMPRESSED and MODE_ZLIB. NOTE: only MODE_STREAM (default) has been
541 * used to date.
542 *
543 * @param value
544 * new data transfer mode
545 * @exception IOException
546 * io error occurred
547 * @exception ServerResponseException
548 * server replied with an error code
549 */
550 public synchronized void mode(char value) throws IOException,
551 ServerResponseException {
552 ostream.write("mode " + value + "\r\n");
553 ostream.flush();
554 log.debug("-> mode " + value);
555
556 String response = readResponse(istream);
557 log.debug(response);
558
559 StringTokenizer st = new StringTokenizer(response);
560 if (!st.nextToken().equals("200")) {
561 throw (new ServerResponseException(response));
562 }
563 }
564
565 /***
566 * abort the last command
567 *
568 * @exception IOException
569 * a socket IO error occurred
570 * @exception ServerResponseException
571 * the server responded with an error code
572 */
573 public synchronized void abor() throws IOException, ServerResponseException {
574 ostream.write("abor\r\n");
575 ostream.flush();
576 log.debug("-> abor");
577
578 String response = readResponse(istream);
579 log.debug(response);
580
581 StringTokenizer st = new StringTokenizer(response);
582 if (!st.nextToken().equals("225")) {
583 throw (new ServerResponseException(response));
584 }
585 }
586
587 /***
588 * abort the last command on the given modem. experience shows that this
589 * requires ADMIN priviledges.
590 *
591 * @param modem
592 * the modem to abort the command on
593 * @exception IOException
594 * a socket IO error occurred
595 * @exception ServerResponseException
596 * the server responded with an error
597 */
598 public synchronized void abor(String modem) throws IOException,
599 ServerResponseException {
600 ostream.write("abor " + modem + "\r\n");
601 ostream.flush();
602 log.debug("-> abor " + modem);
603
604 String response = readResponse(istream);
605 log.debug(response);
606
607 StringTokenizer st = new StringTokenizer(response);
608 if (!st.nextToken().equals("225")) {
609 throw (new ServerResponseException(response));
610 }
611 }
612
613 /***
614 * tell the server which address/port we will listen on
615 *
616 * @param address
617 * address that we'll be listening on
618 * @param port
619 * port on given address we'll be listening on
620 * @exception IOException
621 * io error occurred
622 * @exception ServerResponseException
623 * server replied with an error code
624 */
625 public synchronized void port(InetAddress address, int newPort)
626 throws IOException, ServerResponseException {
627
628
629
630
631
632
633
634
635
636
637
638 String addr = address.getHostAddress();
639
640
641 addr = addr.replace('.', ',');
642
643 String str = new String("port " + addr + ","
644 + ((newPort & 0xff00) >> 8) + "," + (newPort & 0x00ff));
645
646 ostream.write(str + "\r\n");
647 ostream.flush();
648 log.debug("-> " + str + " (" + addr.replace(',', '.') + ":" + newPort
649 + ")");
650
651 String response = readResponse(istream);
652 log.debug(response);
653
654 StringTokenizer st = new StringTokenizer(response);
655 if (!st.nextToken().equals("200")) {
656 throw (new ServerResponseException(response));
657 }
658 }
659
660 /***
661 * store temp file, the file is stored in a uniquely named file on the
662 * server. The remote temp file is deleted when the connection is closed.
663 *
664 * @exception IOException
665 * io error occurred talking to the server
666 * @exception ServerResponseException
667 * server replied with error code
668 * @return the filename of the temp file
669 */
670 public synchronized String stot(InputStream data) throws IOException,
671 ServerResponseException {
672 String filename;
673 String response;
674 StringTokenizer st;
675
676
677 ostream.write("stot\r\n");
678 ostream.flush();
679
680 log.debug("-> stot");
681
682
683 response = readResponse(istream);
684 log.debug(response);
685
686 st = new StringTokenizer(response);
687 if (!st.nextToken().equals("150")) {
688 throw (new ServerResponseException(response));
689 }
690 st.nextToken();
691 filename = new String(st.nextToken());
692
693
694
695
696 response = readResponse(istream);
697 log.debug(response);
698
699 st = new StringTokenizer(response);
700 if (!st.nextToken().equals("226")) {
701
702 throw (new ServerResponseException(response));
703 }
704
705 return filename;
706 }
707
708 /***
709 * store a file with a unique name.
710 *
711 * @exception IOException
712 * a socket IO error occurred
713 * @exception ServerResponseException
714 * the server responded with an error code
715 * @return the name of the file created
716 */
717 public synchronized String stou(InputStream in) throws IOException,
718 ServerResponseException {
719 String filename;
720 String response;
721 StringTokenizer st;
722
723
724 ostream.write("stou\r\n");
725 ostream.flush();
726
727 log.debug("-> stou");
728
729
730 response = readResponse(istream);
731 log.debug(response);
732
733 st = new StringTokenizer(response);
734 if (!st.nextToken().equals("150")) {
735 throw (new ServerResponseException(response));
736 }
737 st.nextToken();
738 filename = new String(st.nextToken());
739
740
741
742
743 response = readResponse(istream);
744 log.debug(response);
745
746 st = new StringTokenizer(response);
747 if (!st.nextToken().equals("226")) {
748
749 throw (new ServerResponseException(response));
750 }
751
752 return filename;
753 }
754
755 /***
756 * store a file.
757 *
758 * @param pathname
759 * name of file to store on server (where to put the file on
760 * the server)
761 * @exception IOException
762 * a socket IO error occurred
763 * @exception ServerResponseException
764 * the server responded with an error
765 */
766 public synchronized void stor(InputStream in, String pathname)
767 throws IOException, ServerResponseException {
768
769
770 ostream.write("stor " + pathname + "\r\n");
771 ostream.flush();
772 log.debug("-> stor " + pathname);
773
774 String response = readResponse(istream);
775 log.debug(response);
776
777 if (!response.substring(0, 3).equals("150")) {
778 throw (new ServerResponseException(response));
779 }
780
781
782
783
784 response = readResponse(istream);
785 log.debug(response);
786
787 if (!response.substring(0, 3).equals("226")) {
788 throw (new ServerResponseException(response));
789 }
790
791
792 }
793
794 /***
795 * get system type returns the string that the server sends (not sure how to
796 * handle it yet) on a Debian GNU/Linux 2.1 (slink) system, the result
797 * string is: "UNIX Type: L8 Version: SVR4"
798 *
799 * @exception IOException
800 * a socket IO error occurred
801 * @exception ServerResponseException
802 * the server replied with an error code
803 * @return the system type string
804 */
805 public synchronized String syst() throws IOException,
806 ServerResponseException {
807 ostream.write("syst\r\n");
808 ostream.flush();
809 log.debug("-> syst");
810
811
812 String response = readResponse(istream);
813 log.debug(response);
814
815 StringTokenizer st = new StringTokenizer(response);
816 if (!st.nextToken().equals("215")) {
817 throw (new ServerResponseException(response));
818 }
819
820
821 return response.substring(4);
822 }
823
824
825
826 /***
827 * the file structure is FILE.
828 */
829 public static final char STRU_FILE = 'F';
830
831 /***
832 * the file structure is RECORD based.
833 */
834 public static final char STRU_RECORD = 'R';
835
836 /***
837 * the file structure is PAGE based.
838 */
839 public static final char STRU_PAGE = 'P';
840
841 /***
842 * the file structure is TIFF.
843 */
844 public static final char STRU_TIFF = 'T';
845
846 /***
847 * set the file structure. valid file structure settings are STRU_FILE,
848 * STRU_RECORD, STRU_PAGE and STRU_TIFF. NOTE: only STRU_FILE has been
849 * tested to date. I have no idea when you would use the other settings.
850 *
851 * @param value
852 * file structure setting
853 * @exception IOException
854 * io error occurred
855 * @exception ServerResponseException
856 * server replied with an error code
857 */
858 public synchronized void stru(char value) throws IOException,
859 ServerResponseException {
860 ostream.write("stru " + value + "\r\n");
861 ostream.flush();
862 log.debug("-> stru " + value);
863
864
865 String response = readResponse(istream);
866 log.debug(response);
867
868 StringTokenizer st = new StringTokenizer(response);
869 if (!st.nextToken().equals("200")) {
870 throw (new ServerResponseException(response));
871 }
872 }
873
874 /***
875 * get a list of files in a directory. Like nlst() but with more information
876 * per line. The difference is more like ls vs. ls -l. With FTP servers, I
877 * know the output is very system dependant but since there aren't too many
878 * different implementations of the HylaFAX server, it's probably not much
879 * of an issue here. Still, beware.
880 * <P>
881 * This method relies on a data port (passive or active) to receive the list
882 * data. it is recommended that you use the getList() wrapper method rather
883 * than this method directly.
884 *
885 * @param path
886 * path of file or directory to get listing. A <i>null</i>
887 * path will get the listing of the current directory.
888 * @exception IOException
889 * io error occurred
890 * @exception ServerResponseException
891 * server replied with an error code
892 * @exception FileNotFoundException
893 * the given path does not exist
894 */
895 public synchronized void list(String path) throws IOException,
896 FileNotFoundException, ServerResponseException {
897
898 String command;
899 if (path == null) {
900 command = "list";
901 } else {
902 command = "list " + path;
903 }
904 ostream.write(command + "\r\n");
905 ostream.flush();
906 log.debug("-> " + command);
907
908
909 String response = readResponse(istream);
910 log.debug(response);
911
912
913 StringTokenizer st = new StringTokenizer(response);
914 String result_code = new String(st.nextToken());
915 if (!result_code.equals("150")) {
916 if (result_code.equals("550")) {
917
918 throw (new FileNotFoundException(response));
919 }
920 throw (new ServerResponseException(response));
921 }
922
923
924
925
926 response = readResponse(istream);
927 log.debug(response);
928
929 st = new StringTokenizer(response);
930 if (!st.nextToken().equals("226")) {
931 throw (new ServerResponseException(response));
932 }
933
934 }
935
936 /***
937 * get list of files in current directory.
938 * <P>
939 * NOTE: uses list() with the <i>null</i> path.
940 * <P>
941 * This method relies on a data socket of some sort to receive the list
942 * data. It is recommended that you use the getList() convenience method
943 * rather than this one directly.
944 *
945 * @exception IOException
946 * io error occurred
947 * @exception ServerResponseException
948 * server replied with an error code
949 * @exception FileNotFoundException
950 * server could not find the specified file
951 */
952 public synchronized void list() throws IOException, FileNotFoundException,
953 ServerResponseException {
954 list(null);
955 }
956
957 /***
958 * get name list of files in directory. Similar to list() but names only.
959 * <P>
960 * This method requires a data socket to receive the name list data. It is
961 * recommended that you use the getNameList() method instead of this method
962 * directly.
963 *
964 * @param path
965 * path of the file or directory to list, passing in <i>null</i>
966 * will result in listing the current directory
967 * @exception IOException
968 * io error occurred
969 * @exception ServerResponseException
970 * server replied with an error code
971 * @exception FileNotFoundException
972 * server could not find the specified file
973 */
974 public synchronized void nlst(String path) throws IOException,
975 ServerResponseException, FileNotFoundException {
976
977
978 String command;
979 if (path == null) {
980 command = "nlst";
981 } else {
982 command = "nlst " + path;
983 }
984 ostream.write(command + "\r\n");
985 ostream.flush();
986 log.debug("-> " + command);
987
988
989 String response = readResponse(istream);
990 log.debug(response);
991
992
993 String result_code = response.substring(0, 3);
994 if (!"150".equals(result_code)) {
995 if ("550".equals(result_code)) {
996
997 throw (new FileNotFoundException(response));
998 }
999 throw (new ServerResponseException(response));
1000 }
1001
1002
1003
1004
1005 response = readResponse(istream);
1006 log.debug(response);
1007
1008 result_code = response.substring(0, 3);
1009 if (!"226".equals(result_code)) {
1010 throw (new ServerResponseException(response));
1011 }
1012
1013
1014
1015 }
1016
1017 /***
1018 * get name list of files in directory. Similar to list() but names only.
1019 * NOTE: uses the <i>null</i> path as the argument to nlst(String)
1020 *
1021 * @exception IOException
1022 * io error occurred
1023 * @exception ServerResponseException
1024 * server replied with an error code
1025 * @exception FileNotFoundException
1026 * server could not find the specified file
1027 */
1028 public synchronized void nlst() throws IOException, FileNotFoundException,
1029 ServerResponseException {
1030 nlst(null);
1031 }
1032
1033 /***
1034 * retrieve the given file
1035 *
1036 * @param path
1037 * the relative or absolute path to the file to retrieve
1038 * @exception IOException
1039 * caused by a socket IO error
1040 * @exception ServerResponseException
1041 * caused by a server response indicating an error
1042 * @exception FileNotFoundException
1043 * the given path does not exist
1044 */
1045 public synchronized void retr(String path) throws IOException,
1046 FileNotFoundException, ServerResponseException {
1047 String response;
1048 StringTokenizer st;
1049
1050
1051 ostream.write("retr " + path + "\r\n");
1052 ostream.flush();
1053
1054 log.debug("-> retr " + path);
1055
1056
1057 response = readResponse(istream);
1058 log.debug(response);
1059
1060 st = new StringTokenizer(response);
1061 String code = st.nextToken();
1062 if (!code.equals("150")) {
1063 if (code.equals("550")) {
1064
1065 throw (new FileNotFoundException(response));
1066 }
1067 throw (new ServerResponseException(response));
1068 }
1069
1070
1071
1072
1073 response = readResponse(istream);
1074 log.debug(response);
1075
1076 st = new StringTokenizer(response);
1077 if (!st.nextToken().equals("226")) {
1078
1079 throw (new ServerResponseException(response));
1080 }
1081 }
1082
1083 /***
1084 * Returns the size (in bytes) of the given regular file. This is the size
1085 * on the server and may not accurately represent the file size once the
1086 * file has been transferred (particularly via ASCII mode)
1087 *
1088 * @param pathname
1089 * the name of the file to get the size for
1090 * @exception IOException
1091 * caused by a socket IO error
1092 * @exception ServerResponseException
1093 * caused by a server response indicating an error
1094 * @exception FileNotFoundException
1095 * the given path does not exist
1096 */
1097 public synchronized long size(String pathname) throws IOException,
1098 FileNotFoundException, ServerResponseException {
1099 ostream.write("size " + pathname + "\r\n");
1100 ostream.flush();
1101 log.debug("-> size " + pathname);
1102
1103 String response = readResponse(istream);
1104 log.debug(response);
1105
1106 StringTokenizer st = new StringTokenizer(response);
1107 String return_code = st.nextToken();
1108 if (!return_code.equals("213")) {
1109 if (return_code.equals("550")) {
1110 throw (new FileNotFoundException(response));
1111 }
1112 throw (new ServerResponseException(response));
1113 }
1114
1115 return Long.parseLong(st.nextToken());
1116 }
1117
1118 public final static String MDTM_TIME_FORMAT1 = "yyyyMMddHHmmss.SSS";
1119
1120 public final static String MDTM_TIME_FORMAT2 = "yyyyMMddHHmmss";
1121
1122 /***
1123 * Returns the last modified time of the given file. This command is
1124 * specified in the FTPEXT Working Group draft currently available at <code>
1125 * http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-15.txt</code>.
1126 * <p>
1127 * The date is returned in GMT in a string of the following format:
1128 * <code>YYYYMMDDhhmmss.ddd</code> where <code>.ddd</code> is an
1129 * optional suffix reporting milliseconds. This method attempts to parse the
1130 * string returned by the server and present the caller with a
1131 * java.util.Date instance.
1132 *
1133 * @param pathname
1134 * the name of the file to get the last-modified time for
1135 * @exception IOException
1136 * caused by a socket IO error
1137 * @exception ServerResponseException
1138 * caused by a server response indicating an error
1139 * @exception FileNotFoundException
1140 * the given path does not exist
1141 * @exception ParseException
1142 * the server returned an unrecognized date format
1143 */
1144 public synchronized Date mdtm(String pathname) throws IOException,
1145 FileNotFoundException, ServerResponseException, ParseException {
1146 ostream.write("mdtm " + pathname + "\r\n");
1147 ostream.flush();
1148 log.debug("-> mdtm " + pathname);
1149
1150 String response = readResponse(istream);
1151 log.debug(response);
1152
1153 StringTokenizer st = new StringTokenizer(response);
1154 String return_code = st.nextToken();
1155 if (!return_code.equals("213")) {
1156 if (return_code.equals("550")) {
1157 throw new FileNotFoundException(response);
1158 }
1159 throw new ServerResponseException(response);
1160 }
1161
1162 String time = st.nextToken();
1163 SimpleDateFormat sdf;
1164 if (time.indexOf('.') == -1) {
1165
1166 sdf = new SimpleDateFormat(MDTM_TIME_FORMAT2);
1167 } else {
1168
1169 sdf = new SimpleDateFormat(MDTM_TIME_FORMAT1);
1170 }
1171 TimeZone tz = TimeZone.getTimeZone("GMT");
1172 Calendar c = sdf.getCalendar();
1173 c.setTimeZone(tz);
1174 sdf.setCalendar(c);
1175 Date d = sdf.parse(time);
1176 return d;
1177 }
1178
1179 /***
1180 * Specifies a file to be renamed. This command must be immediately followed
1181 * by a RNTO command. It is recommended that you use the rename()
1182 * convenience method rather than this one directly.
1183 *
1184 * @param pathname
1185 * the name of the file to be renamed
1186 * @exception IOException
1187 * caused by a socket IO error
1188 * @exception ServerResponseException
1189 * caused by a server response indicating an error
1190 * @exception FileNotFoundException
1191 * the given path does not exist
1192 */
1193 public synchronized void rnfr(String pathname) throws IOException,
1194 FileNotFoundException, ServerResponseException {
1195 ostream.write("rnfr " + pathname + "\r\n");
1196 ostream.flush();
1197 log.debug("-> rnfr " + pathname);
1198
1199 String response = readResponse(istream);
1200 log.debug(response);
1201
1202 StringTokenizer st = new StringTokenizer(response);
1203 String return_code = st.nextToken();
1204 if (!return_code.equals("350")) {
1205 if (return_code.equals("550")) {
1206 throw new FileNotFoundException(response);
1207 }
1208 throw new ServerResponseException(response);
1209 }
1210 return;
1211 }
1212
1213 /***
1214 * Renames a previously specified file to the given name. This command must
1215 * be immediately preceded by a RNFR command. It is recommended that you use
1216 * the rename() convenience method rather than this one directly.
1217 *
1218 * @param pathname
1219 * the name of the file to be renamed
1220 * @exception IOException
1221 * caused by a socket IO error
1222 * @exception ServerResponseException
1223 * caused by a server response indicating an error
1224 */
1225 public synchronized void rnto(String pathname) throws IOException,
1226 ServerResponseException {
1227 ostream.write("rnto " + pathname + "\r\n");
1228 ostream.flush();
1229 log.debug("-> rnto " + pathname);
1230
1231 String response = readResponse(istream);
1232 log.debug(response);
1233
1234 StringTokenizer st = new StringTokenizer(response);
1235 String return_code = st.nextToken();
1236 if (!return_code.equals("250")) {
1237 throw new ServerResponseException(response);
1238 }
1239 return;
1240 }
1241
1242 /***
1243 * Returns the status of the named file or directory.
1244 *
1245 * @param path
1246 * the directory or file to get the status of, using a null
1247 * path will cause this method to return the server status
1248 * information
1249 * @exception IOException
1250 * caused by a socket IO error
1251 * @exception FileNotFoundException
1252 * the given path or file does not exist
1253 * @exception ServerResponseException
1254 * caused by a server response indicating an error
1255 * @return a Vector of String objects, each String being a single line of
1256 * the status message from the server
1257 */
1258 public synchronized Vector stat(String path) throws IOException,
1259 FileNotFoundException, ServerResponseException {
1260 String command;
1261 if (path == null) {
1262 command = "stat\r\n";
1263 } else {
1264 command = "stat " + path + "\r\n";
1265 }
1266 ostream.write(command);
1267 ostream.flush();
1268 log.debug("-> " + command);
1269
1270 String response = istream.readLine();
1271 log.debug(response);
1272
1273 StringTokenizer st = new StringTokenizer(response, "- ");
1274 String return_code = st.nextToken();
1275 String remainder = response.substring(4);
1276 if (return_code.equals("550")) {
1277 throw new FileNotFoundException(remainder);
1278 }
1279 if (!return_code.equals("211")) {
1280 if (response.charAt(3) == '-') {
1281
1282 String first_token;
1283 boolean done = false;
1284 while (!done) {
1285 response = istream.readLine();
1286 if (response == null) {
1287
1288 done = true;
1289 } else {
1290 log.debug(response);
1291 remainder = remainder + "\n" + response;
1292 st = new StringTokenizer(response, " ");
1293 first_token = st.nextToken();
1294 if (return_code.equals(first_token)) {
1295
1296 done = true;
1297 }
1298 }
1299 }
1300 }
1301
1302 throw new ServerResponseException(remainder);
1303 }
1304
1305 Vector status = new Vector();
1306 if (response.charAt(3) == '-') {
1307
1308 String first_token;
1309 boolean done = false;
1310 while (!done) {
1311 response = istream.readLine();
1312 if (response == null) {
1313
1314 done = true;
1315 } else {
1316 log.debug(response);
1317 st = new StringTokenizer(response, " ");
1318 first_token = st.nextToken();
1319 if (return_code.equals(first_token)) {
1320
1321 done = true;
1322 } else {
1323 status.addElement(response);
1324 }
1325 }
1326 }
1327 }
1328 return status;
1329 }
1330
1331 /***
1332 * Returns the server status message. This is equivalent to using
1333 * stat(null).
1334 *
1335 * @exception IOException
1336 * caused by a socket IO error
1337 * @exception ServerResponseException
1338 * caused by a server response indicating an error
1339 * @return a Vector of String objects, each String being a single line of
1340 * the status message from the server
1341 */
1342 public synchronized Vector stat() throws IOException,
1343 ServerResponseException {
1344 return stat(null);
1345 }
1346
1347 /***
1348 * prepare for server-to-server transfer (passive mode)
1349 *
1350 * @exception IOException
1351 * io error occurred
1352 * @exception ServerResponseException
1353 * server replied with an error code
1354 * @return tuple containing the server IP address and port number
1355 */
1356 public synchronized PassiveParameters pasv() throws IOException,
1357 ServerResponseException {
1358 ostream.write("pasv\r\n");
1359 ostream.flush();
1360 log.debug("-> pasv");
1361
1362
1363 String response = readResponse(istream);
1364 log.debug(response);
1365
1366
1367 StringTokenizer st = new StringTokenizer(response);
1368 if (!st.nextToken().equals("227")) {
1369 throw (new ServerResponseException(response));
1370 }
1371
1372
1373 st.nextToken("(,)");
1374 String addr = new String(st.nextToken());
1375 addr = addr + "." + st.nextToken();
1376 addr = addr + "." + st.nextToken();
1377 addr = addr + "." + st.nextToken();
1378
1379 int p = (((Integer.parseInt(st.nextToken()) << 8) & 0xff00) | (Integer
1380 .parseInt(st.nextToken()) & 0x00ff));
1381 return new PassiveParameters(addr, p);
1382
1383 }
1384
1385 /***
1386 * end session
1387 *
1388 * @exception IOException
1389 * io error occurred
1390 * @exception ServerResponseException
1391 * server replied with an error code
1392 */
1393 public synchronized void quit() throws IOException, ServerResponseException {
1394 try {
1395
1396 ostream.write("quit\r\n");
1397 ostream.flush();
1398 log.debug("-> quit");
1399
1400
1401 String response = readResponse(istream);
1402 log.debug(response);
1403
1404 String temp = new String(response.substring(0, 3));
1405 if (!temp.equals("221")) {
1406 throw (new ServerResponseException(response));
1407 }
1408 } finally {
1409 if (sock != null && !sock.isClosed()) {
1410 try {
1411 log.debug("Setting socket to 0 lingering");
1412 sock.setSoLinger(true, 0);
1413 sock.close();
1414 } catch (SocketException e) {
1415
1416 }
1417 }
1418 }
1419 }
1420
1421
1422
1423
1424 public String getGreeting() {
1425 return greeting;
1426 }
1427
1428
1429
1430 /***
1431 * read a (multi-line) response
1432 *
1433 * @param input
1434 * the BufferedReader to read data from
1435 * @exception IOException
1436 * an IO error occurred
1437 * @return the response in a Vector of Strings
1438 */
1439 protected synchronized String readResponse(BufferedReader input)
1440 throws IOException {
1441 String rc = "";
1442 String response = "";
1443
1444 boolean done = false;
1445 boolean first = true;
1446
1447 String tmp = null;
1448 while (!done && (tmp = input.readLine()) != null) {
1449 response += tmp + '\n';
1450 if (tmp.length() >= 4) {
1451 if (first == true) {
1452
1453 rc = response.substring(0, 3);
1454 first = false;
1455 }
1456
1457
1458
1459
1460 if ((rc.equals(tmp.substring(0, 3))) && (tmp.charAt(3) == ' ')) {
1461 done = true;
1462 }
1463 } else {
1464
1465
1466
1467 }
1468 }
1469 return response;
1470 }
1471
1472
1473
1474 /***
1475 * connect to the given host at the given port number.
1476 *
1477 * @param host
1478 * the hostname of the server to connect to
1479 * @param portnumber
1480 * the port that the server is listening on
1481 * @exception UnknownHostException
1482 * the given host name cannot be resolved
1483 * @exception IOException
1484 * an IO error occurred
1485 * @exception ServerResponseException
1486 * the server responded with an error
1487 */
1488 protected void connect(String host, int portnumber)
1489 throws UnknownHostException, IOException, ServerResponseException {
1490
1491 hylafaxServerHost = host;
1492 hylafaxServerPort = portnumber;
1493
1494 SocketAddress sockAddr = new InetSocketAddress(host, portnumber);
1495 sock = new Socket();
1496 sock.connect(sockAddr, socketTimeout);
1497 sock.setSoTimeout(socketTimeout);
1498
1499
1500 istream = new BufferedReader(new InputStreamReader(sock
1501 .getInputStream()));
1502
1503
1504
1505
1506 ostream = new OutputStreamWriter(sock.getOutputStream());
1507
1508
1509 greeting = readResponse(istream);
1510
1511
1512 String temp = new String(greeting.substring(0, 3));
1513
1514
1515
1516
1517
1518 while (temp.equals("130")) {
1519 log.warn(greeting);
1520 greeting = readResponse(istream);
1521 temp = new String(greeting.substring(0, 3));
1522 }
1523
1524 if (!temp.equals("220")) {
1525 throw (new ServerResponseException(greeting));
1526 }
1527
1528 port = portnumber;
1529 }
1530
1531 }
1532
1533