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 package net.sf.hermesftp.client;
26
27 import java.io.BufferedOutputStream;
28 import java.io.BufferedReader;
29 import java.io.BufferedWriter;
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.OutputStream;
35 import java.io.OutputStreamWriter;
36 import java.io.PrintWriter;
37 import java.net.InetAddress;
38 import java.net.ServerSocket;
39 import java.net.Socket;
40 import java.util.StringTokenizer;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43
44 import javax.net.ServerSocketFactory;
45
46 import net.sf.hermesftp.common.FtpConstants;
47 import net.sf.hermesftp.utils.NetUtils;
48
49 import org.apache.commons.logging.Log;
50 import org.apache.commons.logging.LogFactory;
51
52
53 /***
54 * FTP test client for test purposes.
55 *
56 * @author Lars Behnke
57 */
58 public class FtpTestClient {
59
60 /***
61 * On Linux ports below 1024 can only be bound by root.
62 */
63 private static final int TEST_FTP_PORT = 2121;
64
65 private static final int LOG_LINE_LENGTH = 80;
66
67 private static Log log = LogFactory.getLog(FtpTestClient.class);
68
69 private PrintWriter out;
70
71 private BufferedReader in;
72
73 private String server;
74
75 private InputStream transIs;
76
77 private OutputStream transOut;
78
79 private Socket passiveModeSocket;
80
81 private ServerSocket activeModeServerSocket;
82
83 private Socket serverSocket;
84
85 private StringBuffer textBuffer;
86
87 private byte[] rawBuffer;
88
89 private Object lock = new Object();
90
91 /***
92 * Returns the text data.
93 *
94 * @return The text data.
95 */
96 public String getTextData() {
97 return textBuffer.toString();
98 }
99
100 /***
101 * Returns the raw data.
102 *
103 * @return The text data.
104 */
105 public byte[] getRawData() {
106 return rawBuffer;
107 }
108
109 /***
110 * Opens a anonymous FTP connection.
111 *
112 * @throws IOException Error on connection.
113 */
114 public void openConnection() throws IOException {
115 openConnection("anonymous", "my@mail");
116 }
117
118 /***
119 * Opens a FTP connection.
120 *
121 * @param user The user name.
122 * @param pass The user password.
123 * @throws IOException Error on connection.
124 */
125 public void openConnection(String user, String pass) throws IOException {
126 openConnection(null, user, pass, TEST_FTP_PORT);
127
128 }
129
130 /***
131 * Closes the FTP connection.
132 */
133 public void closeConnection() {
134 try {
135 if (serverSocket != null) {
136 serverSocket.close();
137 }
138
139 } catch (IOException e) {
140 log.debug(e.toString());
141 }
142 try {
143 if (passiveModeSocket != null) {
144 passiveModeSocket.close();
145 }
146
147 } catch (IOException e) {
148 log.debug(e.toString());
149 }
150 }
151
152 /***
153 * Opening a connection to the FTP server.
154 *
155 * @param svr The server name. If null is passed, the local machine is used.
156 * @param user The user name.
157 * @param pass The user password.
158 * @throws IOException Error on connection.
159 */
160 public void openConnection(String svr, String user, String pass) throws IOException {
161 openConnection(svr, user, pass, TEST_FTP_PORT);
162 }
163
164 /***
165 * Opening a connection to the FTP server.
166 *
167 * @param svr The server name. If null is passed, the local machine is used.
168 * @param user The user name.
169 * @param pass The user password.
170 * @param port FTP port.
171 * @throws IOException Error on connection.
172 */
173 public void openConnection(String svr, String user, String pass, int port) throws IOException {
174 this.server = svr;
175
176 if (server == null || server.startsWith("127.0.0.")) {
177 this.server = NetUtils.getMachineAddress(true).getHostAddress();
178 }
179 serverSocket = new Socket(server, port);
180 in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
181 out = new PrintWriter(serverSocket.getOutputStream(), true);
182 getResponse();
183 sendAndReceive("USER " + user);
184 sendAndReceive("PASS " + pass);
185 }
186
187 public String openPassiveMode() throws IOException {
188 sendCommand("PASV");
189 String response = getResponse();
190 int parentStart = response.lastIndexOf('(');
191 int parentEnd = response.lastIndexOf(')');
192
193 String pasv = response.substring(parentStart + 1, parentEnd);
194 StringTokenizer st = new StringTokenizer(pasv, ",");
195 int[] iPs = new int[8];
196 for (int i = 0; st.hasMoreTokens(); i++) {
197 iPs[i] = Integer.valueOf(st.nextToken()).intValue();
198 }
199 int port = (iPs[4] << FtpConstants.BYTE_LENGTH) + iPs[5];
200
201 resetDataSockets();
202 passiveModeSocket = new Socket(server, port);
203 return response;
204 }
205
206 public String openActiveMode() throws IOException {
207 InetAddress addr = NetUtils.getMachineAddress(true);
208 String addrStr = addr != null ? addr.getHostAddress() : "127.0.0.1";
209 ServerSocket sock = ServerSocketFactory.getDefault().createServerSocket(0, 1, addr);
210 sock.setSoTimeout(10000);
211
212 Pattern pattern = Pattern.compile("^([0-9]+)//.([0-9]+)//.([0-9]+)//.([0-9]+)$");
213 Matcher matcher = pattern.matcher(addrStr);
214 if (!matcher.matches()) {
215 throw new IOException("Invalid address: " + addrStr);
216 }
217 int p1 = (sock.getLocalPort() >>> FtpConstants.BYTE_LENGTH) & FtpConstants.BYTE_MASK;
218 int p2 = sock.getLocalPort() & FtpConstants.BYTE_MASK;
219
220 StringBuffer sb = new StringBuffer();
221 sb.append("PORT ");
222 sb.append(matcher.group(1));
223 sb.append(",");
224 sb.append(matcher.group(2));
225 sb.append(",");
226 sb.append(matcher.group(3));
227 sb.append(",");
228 sb.append(matcher.group(4));
229 sb.append(",");
230 sb.append(p1);
231 sb.append(",");
232 sb.append(p2);
233
234 resetDataSockets();
235 activeModeServerSocket = sock;
236
237 sendCommand(sb.toString());
238
239
240
241
242
243
244
245
246 String response = getResponse();
247
248
249
250
251
252
253 return response;
254
255 }
256
257 public String openExtendedPassiveMode() throws IOException {
258 sendCommand("EPSV");
259 String response = getResponse();
260
261 Pattern pattern = Pattern.compile("^.*//(//|//|//|([0-9]+)//|//).*$");
262 Matcher matcher = pattern.matcher(response);
263 int port = 0;
264 if (matcher.matches()) {
265 port = Integer.parseInt(matcher.group(1));
266 }
267
268 resetDataSockets();
269 passiveModeSocket = new Socket(server, port);
270 return response;
271 }
272
273 private void initializeIOStreams() throws IOException {
274 if (passiveModeSocket != null) {
275 transIs = passiveModeSocket.getInputStream();
276 transOut = passiveModeSocket.getOutputStream();
277 } else if (activeModeServerSocket != null) {
278 Socket socket = activeModeServerSocket.accept();
279 transIs = socket.getInputStream();
280 transOut = socket.getOutputStream();
281 } else {
282 throw new IOException("IO streams have not been initialized");
283 }
284 }
285
286 private boolean isServerSocketAvailable() {
287 return passiveModeSocket != null || activeModeServerSocket != null;
288 }
289
290 private void resetDataSockets() throws IOException {
291 if (passiveModeSocket != null) {
292 passiveModeSocket.close();
293 passiveModeSocket = null;
294 }
295 if (activeModeServerSocket != null) {
296 activeModeServerSocket.close();
297 activeModeServerSocket = null;
298 }
299 }
300
301 public String openExtendedActiveMode() throws IOException {
302 StringBuffer params = new StringBuffer();
303
304 params.append("|1|");
305 InetAddress addr = NetUtils.getMachineAddress(true);
306 ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0, 1, addr);
307 params.append(addr.getHostAddress());
308 params.append("|");
309 params.append(serverSocket.getLocalPort());
310 params.append("|");
311
312 sendCommand("EPRT " + params.toString());
313 String response = getResponse();
314
315 if (passiveModeSocket != null) {
316 passiveModeSocket.close();
317 }
318 activeModeServerSocket = serverSocket;
319 return response;
320 }
321
322 /***
323 * Lists the content of the passed path.
324 *
325 * @param f The path.
326 * @return The content as string.
327 * @throws IOException Error on data transfer.
328 */
329 public String list(String f) throws IOException {
330 String response = null;
331 if (!isServerSocketAvailable()) {
332 openPassiveMode();
333 }
334 textBuffer = new StringBuffer();
335 if (f == null) {
336 sendCommand("LIST");
337 } else {
338 sendCommand("LIST " + f);
339 }
340 String res = getResponse();
341
342 if (res.startsWith("150")) {
343 initializeIOStreams();
344 } else {
345 return res;
346 }
347
348 BufferedReader in = new BufferedReader(new InputStreamReader(transIs, "ISO-8859-1"));
349 TextReceiver l = new TextReceiver(in, false);
350 executeReceive(l);
351 resetDataSockets();
352 response = getResponse();
353 return response;
354
355 }
356
357 /***
358 * Lists the current folder.
359 *
360 * @return The content.
361 * @throws IOException Error on data transfer.
362 */
363 public String list() throws IOException {
364 return list(null);
365 }
366
367 /***
368 * Retrieves a text file.
369 *
370 * @param filename The filename.
371 * @return The content of the text file.
372 * @throws IOException Error on data transfer.
373 */
374 public String retrieveText(String filename) throws IOException {
375 String response = null;
376 sendAndReceive("TYPE A");
377 if (!isServerSocketAvailable()) {
378 openPassiveMode();
379 }
380 textBuffer = new StringBuffer();
381 response = sendAndReceive("RETR " + filename);
382 if (!response.startsWith("150")) {
383 return response;
384 }
385 initializeIOStreams();
386 BufferedReader in = new BufferedReader(new InputStreamReader(transIs, "ISO-8859-1"));
387 TextReceiver l = new TextReceiver(in, false);
388 executeReceive(l);
389
390 response = getResponse();
391 resetDataSockets();
392 return response;
393 }
394
395 /***
396 * Retrieves a text file.
397 *
398 * @param filename The filename.
399 * @return Size of file.
400 * @throws IOException Error on data transfer.
401 */
402 public int retrieveBigText(String filename) throws IOException {
403
404 String response;
405 sendAndReceive("TYPE A");
406 if (!isServerSocketAvailable()) {
407 openPassiveMode();
408 }
409 textBuffer = new StringBuffer();
410 response = sendAndReceive("RETR " + filename);
411 if (response.startsWith("150")) {
412 initializeIOStreams();
413 } else {
414 return 0;
415 }
416
417 BufferedReader in = new BufferedReader(new InputStreamReader(transIs, "ISO-8859-1"));
418 TextReceiver l = new TextReceiver(in, true);
419 executeReceive(l);
420
421 getResponse();
422 resetDataSockets();
423 return l.getCount();
424 }
425
426 private void executeReceive(TextReceiver l) {
427 Thread t = new Thread(l);
428 t.start();
429 while (t.isAlive()) {
430 Thread.yield();
431 }
432 }
433
434 /***
435 * Retrieves a raw data file.
436 *
437 * @param filename The filename.
438 * @return The content of the data file.
439 * @throws IOException Error on data transfer.
440 */
441 public String retrieveRaw(String filename) throws IOException {
442 String response;
443 if (!isServerSocketAvailable()) {
444 openPassiveMode();
445 }
446 rawBuffer = null;
447 response = sendAndReceive("RETR " + filename);
448 if (!response.startsWith("150")) {
449 return response;
450 }
451 initializeIOStreams();
452 RawReceiver l = new RawReceiver(transIs);
453 executeReceive(l);
454
455 response = getResponse();
456 resetDataSockets();
457 return response;
458 }
459
460 private void executeReceive(RawReceiver l) {
461 Thread t = new Thread(l);
462 t.start();
463 while (t.isAlive()) {
464 Thread.yield();
465 }
466 }
467
468 /***
469 * Stores a text file on the remote system.
470 *
471 * @param filename The filename.
472 * @param textToStore The text to be stored.
473 * @return The response.
474 * @throws IOException Error on data transfer.
475 */
476 public String storeText(String filename, String textToStore) throws IOException {
477 return storeText(filename, textToStore, false);
478 }
479
480 /***
481 * Appends text to an text file.
482 *
483 * @param filename The filename.
484 * @param textToStore The text to append.
485 * @return The server response.
486 * @throws IOException Error on data transfer.
487 */
488 public String appendText(String filename, String textToStore) throws IOException {
489 return storeText(filename, textToStore, true);
490 }
491
492 private String storeText(String filename, String textToStore, boolean append) throws IOException {
493 String response = null;
494
495 sendAndReceive("TYPE A");
496 if (!isServerSocketAvailable()) {
497 openPassiveMode();
498 }
499
500 textBuffer = new StringBuffer();
501 if (append) {
502 sendCommand("APPE" + filename);
503 } else {
504 sendCommand("STOR " + filename);
505 }
506 response = getResponse();
507 if (!response.startsWith("150")) {
508 return response;
509 }
510 initializeIOStreams();
511 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(transOut, "ISO-8859-1"));
512 TextSender l = new TextSender(out, textToStore);
513 executeSend(l);
514
515 response = getResponse();
516 resetDataSockets();
517 return response;
518
519 }
520
521 /***
522 * Stores a file of a given size. The content is arbitrary.
523 *
524 * @param filename Filename.
525 * @param size Size of the file.
526 * @return Response.
527 * @throws IOException
528 */
529 public String storeBigText(String filename, int size) throws IOException {
530 String response = null;
531
532 response = sendAndReceive("TYPE A");
533 if (!isServerSocketAvailable()) {
534 openPassiveMode();
535 }
536 textBuffer = new StringBuffer();
537 sendCommand("STOR " + filename);
538
539 response = getResponse();
540 if (!response.startsWith("150")) {
541 return response;
542 }
543 initializeIOStreams();
544 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(transOut, "ISO-8859-1"));
545 TextSender l = new TextSender(out, size);
546 executeSend(l);
547 response = getResponse();
548 resetDataSockets();
549 return response;
550
551 }
552
553 private void executeSend(TextSender l) {
554 Thread t = new Thread(l);
555 t.start();
556 while (t.isAlive()) {
557 Thread.yield();
558 }
559 }
560
561 /***
562 * Stores a data file on the remote system.
563 *
564 * @param filename The filename.
565 * @param data The Data to be stored.
566 * @return The response.
567 * @throws IOException Error on data transfer.
568 */
569 public String storeRaw(String filename, byte[] data) throws IOException {
570 return storeRaw(filename, data, false);
571 }
572
573 /***
574 * Appends text to an data file.
575 *
576 * @param filename The filename.
577 * @param data The data to append.
578 * @return The server response.
579 * @throws IOException Error on data transfer.
580 */
581 public String appendRaw(String filename, byte[] data) throws IOException {
582 return storeRaw(filename, data, true);
583 }
584
585 private String storeRaw(String filename, byte[] data, boolean append) throws IOException {
586 String response = null;
587
588 if (!isServerSocketAvailable()) {
589 openPassiveMode();
590 }
591 if (append) {
592 sendCommand("APPE" + filename);
593 } else {
594 sendCommand("STOR " + filename);
595 }
596 response = getResponse();
597 if (!response.startsWith("150")) {
598 return response;
599 }
600 initializeIOStreams();
601
602 BufferedOutputStream out = new BufferedOutputStream(transOut);
603 RawSender l = new RawSender(out, data);
604 executeSend(l);
605 response = getResponse();
606 resetDataSockets();
607 return response;
608
609 }
610
611 private void executeSend(RawSender l) {
612 Thread t = new Thread(l);
613 t.start();
614 while (t.isAlive()) {
615 Thread.yield();
616 }
617 }
618
619 /***
620 * Sends a command string to the server.
621 *
622 * @param cmd The command.
623 * @return The server response.
624 * @throws IOException Error on data transfer.
625 */
626 public String sendAndReceive(String cmd) throws IOException {
627 sendCommand(cmd);
628 return getResponse();
629 }
630
631 private String getResponse() throws IOException {
632 return getResponse(in);
633 }
634
635 private String getResponse(BufferedReader in) throws IOException {
636 StringBuffer sb = new StringBuffer();
637 boolean done;
638 do {
639 String line = in.readLine();
640 sb.append(line + "\n");
641 int idx = 0;
642 done = Character.isDigit(line.charAt(idx++)) && Character.isDigit(line.charAt(idx++))
643 && Character.isDigit(line.charAt(idx++)) && line.charAt(idx++) == ' ';
644 } while (!done);
645 return sb.toString().trim();
646 }
647
648 /***
649 * Send command and wait for resonse.
650 */
651 private void sendCommand(String command) throws IOException {
652
653 out.println(command);
654 }
655
656 /***
657 * Listens to server socket.
658 *
659 * @author Lars Behnke
660 */
661 private class TextReceiver implements Runnable {
662
663 private BufferedReader reader;
664
665 private int count;
666
667 private boolean countOnly;
668
669 /***
670 * Constructor.
671 *
672 * @param reader The reader.
673 * @param countOnly True, if only the text size matters.
674 */
675 public TextReceiver(BufferedReader reader, boolean countOnly) {
676 this.countOnly = countOnly;
677 this.reader = reader;
678 }
679
680 /***
681 * {@inheritDoc}
682 */
683 public void run() {
684 synchronized (lock) {
685 try {
686 char[] buffer = new char[4096];
687 int count;
688
689 while ((count = reader.read(buffer)) != -1) {
690
691 if (!countOnly) {
692 String logLine = new String(buffer, 0, count);
693 if (textBuffer.length() > 0) {
694 textBuffer.append(System.getProperty("line.separator"));
695 }
696 textBuffer.append(logLine);
697 if (log.isTraceEnabled()) {
698 if (count >= LOG_LINE_LENGTH) {
699
700 logLine = logLine.substring(0, LOG_LINE_LENGTH) + " ["
701 + (logLine.length() - LOG_LINE_LENGTH) + " chars more]";
702 }
703
704 log.trace("<==: " + logLine.trim());
705 }
706 }
707 this.count += count;
708 }
709 if (countOnly) {
710 log.trace("<==: " + this.count + " characters read");
711 }
712 reader.close();
713
714 } catch (IOException e) {
715 log.error(e);
716 }
717 lock.notifyAll();
718 }
719 }
720
721 /***
722 * @return Size of file.
723 */
724 public int getCount() {
725 return count;
726 }
727 }
728
729 /***
730 * Sends data to server socket.
731 *
732 * @author Lars Behnke
733 */
734 private class TextSender implements Runnable {
735
736 private BufferedWriter writer;
737
738 private String textToSend;
739
740 private int textSize;
741
742 /***
743 * Constructor.
744 *
745 * @param writer The writer.
746 * @param textToSend Text to send.
747 */
748 public TextSender(BufferedWriter writer, String textToSend) {
749 this.writer = writer;
750 this.textToSend = textToSend;
751 this.textSize = -1;
752 }
753
754 /***
755 * Constructor.
756 *
757 * @param writer The writer.
758 * @param size Size of the test string.
759 */
760 public TextSender(BufferedWriter writer, int size) {
761 this.writer = writer;
762 this.textSize = size;
763 }
764
765 /***
766 * @see java.lang.Runnable#run()
767 */
768 public void run() {
769 synchronized (lock) {
770 try {
771 if (textSize == -1 && textToSend != null) {
772 if (log.isTraceEnabled()) {
773 String x;
774 if (textToSend.length() >= LOG_LINE_LENGTH) {
775 x = textToSend.substring(0, LOG_LINE_LENGTH) + " ["
776 + (textToSend.length() - LOG_LINE_LENGTH) + " chars more]";
777 } else {
778 x = textToSend;
779 }
780 log.trace("==>: " + x.trim());
781 }
782 writer.write(textToSend);
783 writer.flush();
784 } else if (textSize >= 0) {
785 if (log.isTraceEnabled()) {
786 log.trace("==>: Sending test text, length: " + textSize);
787 }
788 for (int i = 0; i < textSize; i++) {
789 writer.write('X');
790 }
791
792 }
793
794 writer.close();
795 } catch (IOException e) {
796 log.error(e);
797 }
798 lock.notifyAll();
799 }
800 }
801 }
802
803 /***
804 * Sends data to server socket.
805 *
806 * @author Lars Behnke
807 */
808 private class RawSender implements Runnable {
809
810 private BufferedOutputStream os;
811
812 private byte[] dataToSend;
813
814 /***
815 * Constructor.
816 *
817 * @param os The output stream.
818 * @param dataToSend Data to send.
819 */
820 public RawSender(BufferedOutputStream os, byte[] dataToSend) {
821 this.os = os;
822 this.dataToSend = dataToSend;
823 }
824
825 /***
826 * @see java.lang.Runnable#run()
827 */
828 public void run() {
829 synchronized (lock) {
830 try {
831 log.trace("==>: " + dataToSend.length + " bytes");
832 os.write(dataToSend);
833 os.flush();
834 os.close();
835 } catch (IOException e) {
836 log.error(e);
837 } finally {
838 lock.notifyAll();
839 }
840 }
841 }
842 }
843
844 /***
845 * Raw data receiver that listens to server socket.
846 *
847 * @author Lars Behnke
848 */
849 private class RawReceiver implements Runnable {
850
851 private InputStream is;
852
853 /***
854 * Constructor.
855 *
856 * @param is The input stream.
857 */
858 public RawReceiver(InputStream is) {
859 this.is = is;
860 }
861
862 /***
863 * {@inheritDoc}
864 */
865 public void run() {
866 ByteArrayOutputStream baos = new ByteArrayOutputStream();
867 byte[] buffer = new byte[1024];
868 int count = 0;
869 synchronized (lock) {
870 try {
871
872 while ((count = is.read(buffer)) >= 0) {
873 log.trace("<==: " + count + " bytes");
874 baos.write(buffer, 0, count);
875 }
876 rawBuffer = baos.toByteArray();
877 } catch (IOException e) {
878 log.error(e);
879 } finally {
880 lock.notifyAll();
881 }
882 }
883 }
884 }
885
886 }
887
888