Source code: org/finj/FTPResponse.java
1 package org.finj;
2
3 import java.util.Locale;
4 import java.util.MissingResourceException;
5 import java.util.ResourceBundle;
6 import java.util.StringTokenizer;
7
8 /**
9 * This class contains constants and methods that simplify handling
10 * and internationalization of FTP server replies.
11 *
12 * Codes according to RFC959-4.2 <tt>http://www.rfc.net/rfc959.html</tt>
13 * (October 1995).
14 *
15 * Copyright (C)
16 *
17 * This library is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public
19 * License as published by the Free Software Foundation; either
20 * version 2.1 of the License, or (at your option) any later version.
21 *
22 * This library is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this library; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 *
31 * @author Javier Iglesias -- jiglesias@users.sourceforge.net
32 * @version $Id: FTPResponse.java,v 1.2 2003/10/22 08:14:57 jiglesia Exp $
33 */
34 public class FTPResponse extends Object {
35
36 /**
37 * No response will ever (as RFC959 is concerned...) match this code !
38 *
39 * Used for comparisions purposes.
40 *
41 * @since v1.0.2
42 */
43 private static final int NOT_A_REAL_RESPONSE_CODE = -1;
44
45 // ----- 100 SERIES CODES -----
46 // Requested action is being initiated, expect
47 // another response before proceeding with a new command
48 /**
49 * Restart marker response.
50 *
51 * In this case, the text is exact and not
52 * left to the particular implementation; it must read :
53 * <code>MARK yyyy = mmmm</code>
54 * Where <code>yyyy</code> is User-process data stream marker,
55 * and <code>mmmm</code> server's equivalent marker
56 * (note the spaces between markers and '=`).
57 *
58 * @see "RFC959-4:2:'110'"
59 * @since v1.0
60 */
61 public static final int RESTART_MARKER_RESPONSE_CODE = 110;
62 /**
63 * Service ready in <code>nnn</code> minutes.
64 *
65 * @see "RFC959-4:2:'120'"
66 * @since v1.0
67 */
68 public static final int WILL_BE_READY_IN_MINUTES_CODE = 120;
69 /**
70 * Data Connection already open; transfer starting.
71 *
72 * @see "RFC959-4:2:'125'"
73 * @since v1.0
74 */
75 public static final int DATA_CONNECTION_ALREADY_OPENED_CODE = 125;
76 /**
77 * File status okay; about to open data connection.
78 *
79 * @see "RFC959-4:2:'150'"
80 * @since v1.0
81 */
82 public static final int DATA_CONNECTION_ABOUT_TO_BE_OPENED_CODE = 150;
83
84 // ----- 200 SERIES CODES -----
85 // Requested action has been successfully completed
86 /**
87 * Command okay.
88 *
89 * @see "RFC959-4:2:'200'"
90 * @since v1.0
91 */
92 public static final int POSITIVE_COMPLETION_RESPONSE_CODE = 200;
93 /**
94 * Command not implemented, superfluous at this site.
95 *
96 * @see "RFC959-4:2:'202'"
97 * @since v1.0
98 */
99 public static final int SUPERFLOUS_COMMAND_CODE = 202;
100 /**
101 * System status, or system help response.
102 *
103 * @see "RFC959-4:2:'211'"
104 * @since v1.0
105 */
106 public static final int SYSTEM_STATUS_OR_HELP_RESPONSE_CODE = 211;
107 /**
108 * Directory status
109 *
110 * @see "RFC959-4:2:'212'"
111 * @since v1.0
112 */
113 public static final int DIRECTORY_STATUS_CODE = 212;
114 /**
115 * File status.
116 *
117 * @see "RFC959-4:2:'213'"
118 * @since v1.0
119 */
120 public static final int FILE_STATUS_CODE = 213;
121 /**
122 * Help message.
123 *
124 * On how to use the server or the meaning of a particular
125 * non-standard command. This response is useful only to the
126 * human user.
127 *
128 * @see "RFC959-4:2:'214'"
129 * @since v1.0
130 */
131 public static final int HELP_MESSAGE_CODE = 214;
132 /**
133 * <code>NAME</code> system type.
134 *
135 * Where <code>NAME</code> is an official system name from
136 * the list in the Assigned Numbers document.
137 *
138 * @see "RFC959-4:2:'215'"
139 * @see "RFC1700"
140 * @since v1.0
141 */
142 public static final int SYSTEM_TYPE_NAME_CODE = 215;
143 /**
144 * Service ready for new user.
145 *
146 * @see "RFC959-4:2:'220'"
147 * @since v1.0
148 */
149 public static final int CONTROL_CONNECTION_OPENED_CODE = 220;
150 /**
151 * Service closing control connection.
152 *
153 * Logged out if appropriate.
154 *
155 * @see "RFC959-4:2:'221'"
156 * @since v1.0
157 */
158 public static final int CONTROL_CONNECTION_CLOSED_CODE = 221;
159 /**
160 * Data connection open; no transfer in progress.
161 *
162 * @see "RFC959-4:2:'225'"
163 * @since v1.0
164 */
165 public static final int DATA_CONNECTION_OPENED_CODE = 225;
166 /**
167 * Closing data connection.
168 *
169 * Requested file action successful (e.g., file transfer or
170 * file abort).
171 *
172 * @see "RFC959-4:2:'226'"
173 * @since v1.0
174 */
175 public static final int DATA_CONNECTION_CLOSED_CODE = 226;
176 /**
177 * Entering Passive Mode (h1,h2,h3,h4,p1,p2).
178 *
179 * @see "RFC959-4:2:'227'"
180 * @since v1.0
181 */
182 public static final int ENTERING_PASSIVE_MODE_CODE = 227;
183 /**
184 * User logged in, proceed.
185 *
186 * @see "RFC959-4:2:'230'"
187 * @since v1.0
188 */
189 public static final int USER_LOGGED_IN_CODE = 230;
190 /**
191 * Requested file action okay, completed.
192 *
193 * @see "RFC959-4:2:'250'"
194 * @since v1.0
195 */
196 public static final int REQUESTED_FILE_ACTION_OK_CODE = 250;
197 /**
198 * "PATHNAME" created.
199 *
200 * @see "RFC959-4:2:'257'"
201 * @since v1.0
202 */
203 public static final int PATHNAME_CREATED_CODE = 257;
204
205 // ----- 300 SERIES CODES -----
206 // Command has been accepted, but requested action
207 // is being held abeyance, pending receipt of
208 // further information
209 /**
210 * User name okay, need password.
211 *
212 * @see "RFC959-4:2;'331'"
213 * @since v1.0
214 */
215 public static final int USERNAME_OK_NEED_PASSWORD_CODE = 331;
216 /**
217 * Need account for login.
218 *
219 * @see "RFC959-4:2:'332'"
220 * @since v1.0
221 */
222 public static final int NEED_ACCOUNT_FOR_LOGIN_CODE = 332;
223 /**
224 * Requested file action pending further information.
225 *
226 * @see "RFC959-4:2:'350'"
227 * @since v1.0
228 */
229 public static final int REQUESTED_FILE_ACTION_PENDING_CODE = 350;
230
231 // ----- 400 SERIES CODES -----
232 // Command was not accepted and the requested action
233 // did NOT take place, but the error condition is
234 // temporary and the action may be requested again
235 /**
236 * Service not available, closing control connection.
237 *
238 * This may be a response to any command if the service
239 * knows it must shut down.
240 *
241 * @see "RFC959-4:2:'421'"
242 * @since v1.0
243 */
244 public static final int SERVICE_NOT_AVAILABLE_CODE = 421;
245 /**
246 * Can't open data connection.
247 *
248 * @see "RFC959-4:2:'425'"
249 * @since v1.0
250 */
251 public static final int CAN_T_OPEN_DATA_CONNECTION_CODE = 425;
252 /**
253 * Connection closed; transfer aborted.
254 *
255 * @see "RFC959-4:2:'426'"
256 * @since v1.0
257 */
258 public static final int DATA_CONNECTION_ABORTED_CODE = 426;
259 /**
260 * Requested file action not taken.
261 *
262 * File unavailable (e.g., file busy).
263 *
264 * @see "RFC959-4:2:'450'"
265 * @since v1.0
266 */
267 public static final int TRANSIENT_UNAVAILABLE_FILE_CODE = 450;
268 /**
269 * Requested action aborted: local error in processing.
270 *
271 * @see "RFC959-4:2:'451'"
272 * @since v1.0
273 */
274 public static final int TRANSIENT_LOCAL_PROCESSING_ERROR_CODE = 451;
275 /**
276 * Requested action not taken.
277 *
278 * Insufficient storage space in system.
279 *
280 * @see "RFC959-4:2:'452'"
281 * @since v1.0
282 */
283 public static final int TRANSIENT_INSUFFICIENT_STORAGE_SPACE_CODE = 452;
284
285 // ----- 500 SERIES CODES -----
286 // Command was not accepted and the requested action
287 // did NOT take place
288 /**
289 * Syntax error, command unrecognized.
290 *
291 * This may include errors such as command line too long.
292 *
293 * @see "RFC959-4:2:'500'"
294 * @since v1.0
295 */
296 public static final int PERMANENT_NEGATIVE_COMPLETION_RESPONSE_CODE = 500;
297 /**
298 * Syntax error n parameters or arguments.
299 *
300 * @see "RFC959-4:2:'501'"
301 * @since v1.0
302 */
303 public static final int SYNTAX_ERROR_IN_ARGUMENTS_CODE = 501;
304 /**
305 * Command not implemented.
306 *
307 * @see "RFC959-4:2:'502'"
308 * @since v1.0
309 */
310 public static final int COMMAND_NOT_IMPLEMENTED_CODE = 502;
311 /**
312 * Bad sequence of commands.
313 *
314 * @see "RFC959-4:2:'503'"
315 * @since v1.0
316 */
317 public static final int BAD_COMMAND_SEQUENCE_CODE = 503;
318 /**
319 * Command not implemented for that parameter.
320 *
321 * @see "RFC959-4:2:'504'"
322 * @since v1.0
323 */
324 public static final int COMMAND_NOT_IMPLEMENTED_FOR_THAT_PARAMETER_CODE = 504;
325 /**
326 * Not logged in.
327 *
328 * @see "RFC959-4:2:'530'"
329 * @since v1.0
330 */
331 public static final int NOT_LOGGED_IN_CODE = 530;
332 /**
333 * Need account for storing files.
334 *
335 * @see "RFC959-4:2:'532'"
336 * @since v1.0
337 */
338 public static final int NEED_ACCOUNT_FOR_STORING_FILES_CODE = 532;
339 /**
340 * Requested action not taken.
341 *
342 * File unavailable (e.g., file not found, no access).
343 *
344 * @see "RFC959-4:2:'550'"
345 * @since v1.0
346 */
347 public static final int PERMANENTLY_UNAVAILABLE_FILE_CODE = 550;
348 /**
349 * Requested action aborted: page type unknown.
350 *
351 * @see "RFC959-4:2:'551'"
352 * @since v1.0
353 */
354 public static final int PAGE_TYPE_UNKNOWN_CODE = 551;
355 /**
356 * Requested file action aborted.
357 *
358 * Exceed storage allocation (for current directory or dataset).
359 *
360 * @see "RFC959-4:2:'552'"
361 * @since v1.0
362 */
363 public static final int EXCEEDED_STORAGE_ALLOCATION_CODE = 552;
364 /**
365 * Requested action not taken.
366 *
367 * File name not allowed.
368 *
369 * @see "RFC959-4:2:'553'"
370 * @since v1.0
371 */
372 public static final int FINE_NAME_NOT_ALLOWED_CODE = 553;
373
374
375 // Messages are stored in resource files
376 private static final String MESSAGE_BUNDLE_NAME = "org/finj/i18n/Responses";
377 private static final String MESSAGE_NAME_PREFIX = "code_";
378 private static final String DEFAULT_MESSAGE_NAME = "code_default";
379 private ResourceBundle resources = null;
380
381 private int code = 0;
382 private String text = null;
383 private String[] multi = null;
384
385 /**
386 * Defeat parameterless constructor.
387 *
388 * @since v1.0
389 */
390 private FTPResponse ( ) {;}
391
392 /**
393 * Constructs a new instance of this class that will
394 * use <code>FTPConstants.DEFAULT_LOCALE</code> as
395 * default locale.
396 *
397 * @param response original response message received on the
398 * control connection.
399 * @since v1.0
400 */
401 public FTPResponse ( String response ) {
402 this(response, null, FTPConstants.DEFAULT_LOCALE);
403 }
404
405 /**
406 * Constructs a new instance of this class that will
407 * use <code>FTPConstants.DEFAULT_LOCALE</code> as
408 * default locale, and will store a multiline answer.
409 *
410 * @param response original response message received on the
411 * control connection.
412 * @param multiline text received with this response.
413 * @since v1.0
414 */
415 public FTPResponse ( String response,
416 String[] multiline ) {
417 this(response, multiline, FTPConstants.DEFAULT_LOCALE);
418 }
419
420 /**
421 * Constructs a new instance of this class
422 * that will use <code>locale</code> as
423 * message language. If not available,
424 * messages for default language
425 * (<code>Locale.US</code>) will be used.
426 *
427 * @param response original response message received on the
428 * control connection.
429 * @param locale language to use for message.
430 * @since v1.0
431 */
432 public FTPResponse ( String response,
433 Locale locale ) throws IllegalArgumentException {
434 this(response, null, locale);
435 }
436
437 /**
438 * Constructs a new instance of this class
439 * that will use <code>locale</code> as
440 * message language. If not available,
441 * messages for default language
442 * (<code>Locale.US</code>) will be used.
443 *
444 * @param response original response message received on the
445 * control connection.
446 * @param multiline text received with this response.
447 * @param locale language to use for message.
448 * @since v1.0
449 */
450 public FTPResponse ( String response,
451 String[] multiline,
452 Locale locale ) throws IllegalArgumentException {
453 this(parseCode(response),
454 parseOriginalMessage(response),
455 multiline,
456 locale);
457 }
458
459 /**
460 * Initializes the current instance with the info provided.
461 *
462 * @param code valid FTP code.
463 * @param text original message found in the response.
464 * @param multi multiline info received with this message.
465 * @param locale language to use for message.
466 * @since v1.0
467 */
468 public FTPResponse ( int code,
469 String text,
470 String[] multi,
471 Locale locale ) {
472 this.code = code;
473 this.multi = multi;
474 this.text = text;
475 resources = ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME, locale);
476 }
477
478
479
480 /**
481 * Returns the FTP code of this response.
482 *
483 * @return FTP code
484 * @since v1.0
485 */
486 public int getCode ( ) {
487 return code;
488 }
489
490 /**
491 * Returns a localized version of the message corresponding
492 * to the original one's code. This will only work if a
493 * localized version of the messages are provided.
494 *
495 * @return code of the response sent by FTP server.
496 * @since v1.0
497 */
498 public String getMessage ( ) {
499 return getMessageFromCode(code);
500 }
501
502 /**
503 * Returns the original message received from the FTP server
504 * and passed at construction time.
505 *
506 * @return original message text.
507 * @since v1.0
508 */
509 public String getOriginalMessage ( ) {
510 return text;
511 }
512
513 /**
514 * Returns true if this response anounces a multi line response,
515 * e.g., <code>STAT</code> command.
516 *
517 * @return <code>true</code> if more lines are coming next
518 * the one used to construct this instance.
519 * @see "RFC959-4:2"
520 * @since v1.0
521 */
522 public boolean isMultiline ( ) {
523 return (multi != null);
524 }
525
526 /**
527 * Returns the multiline answer received with this response.
528 *
529 * @return multiline answer if exists, <code>null</code> else.
530 * @since v1.0
531 */
532 public String[] getMultilineText ( ) {
533 return multi;
534 }
535
536
537
538
539
540
541 /**
542 * Returns the message that corresponds to the
543 * <code>code</code> given as parameter.
544 *
545 * @param code one of the codes defined in this class.
546 * @since v1.0
547 */
548 protected String getMessageFromCode ( int code ) {
549 try {
550 return resources.getString(new StringBuffer(MESSAGE_NAME_PREFIX).append(code).toString());
551 } catch ( MissingResourceException mre ) {
552 return resources.getString(DEFAULT_MESSAGE_NAME);
553 }
554 }
555
556
557
558
559
560
561
562 /**
563 * Tests the first line of a response to know if it opens
564 * a multiline answer.
565 *
566 * @param response FTP server response to interpret.
567 * @since v1.0.2
568 */
569 public static boolean opensMultilineResponse ( String response ) {
570 return (response.charAt (3) == '-');
571 } // DONE
572
573 /**
574 * Tests a line of a response to know if it closes
575 * a multiline answer.
576 *
577 * @param response FTP server response to interpret.
578 * @since v1.0.2
579 */
580 public static boolean closesMultilineResponse ( String response,
581 int code ) {
582 int responseCode = NOT_A_REAL_RESPONSE_CODE;
583 try {
584 responseCode = parseCode(response);
585 } catch ( IllegalArgumentException iae ) {;}
586 return (code == responseCode &&
587 response.charAt(3) == ' ');
588 } // DONE
589
590
591 /**
592 * Returns the code contained in the
593 * <code>response</code> passed as parameter.
594 * This code are in fact the 3 first characters
595 * of the <code>response</code>.
596 *
597 * @param response FTP server response to interpret.
598 * @exception IllegalArgumentException
599 * when <code>response</code>'s 3 first characters
600 * are not a valid three digit number
601 * @since v1.0
602 */
603 public static int parseCode ( String response ) throws IllegalArgumentException {
604 try {
605 return Integer.parseInt(response.substring(0, 3));
606 } catch ( NumberFormatException nfe ) {
607 throw new IllegalArgumentException(response.substring (0, 3) + " is obviously not a valid FTP error code !"); // FIXME : internationalize
608 } catch ( Exception e ) {
609 throw new IllegalArgumentException("There is obviously no valid FTP error code on line : '"+response+"'"); // FIXME : internationalize
610 }
611 }
612
613 /**
614 * Returns the message contained in the
615 * <code>response</code> passed as parameter.
616 *
617 * @param response FTP server response to interpret.
618 * @exception IllegalArgumentException
619 * when <code>response</code> isn't parsable.
620 * @since v1.0
621 */
622 public static String parseOriginalMessage ( String response ) throws IllegalArgumentException {
623 try {
624 return (response.substring(4, response.length()).trim());
625 } catch ( Exception e ) {
626 throw new IllegalArgumentException("Impossible to extract the message part of : '" + response + "' !"); // FIXME : internationalize
627 }
628 }
629
630
631
632
633
634
635
636 /**
637 * Returns the host passive port parsed from the original text of response.
638 * The author of this method is Syed Meerkasim.
639 *
640 * @return a <code>java.lang.String[]</code>
641 * @exception FTPException Response doesn't contain the required information
642 * @since v1.0
643 */
644 // FIXME : change method name public String[] getHostNPortFromMessage ( ) throws FTPException {
645 // FIXME : is it really the right place to do that ? are there no exception to check ?
646 public String[] parseServerPassivePort ( ) throws FTPException {
647 int infoStartIndex = 0;
648 int infoStopIndex = 0;
649 String answer[] = null;
650
651 infoStartIndex = text.indexOf("(");
652 infoStopIndex = text.indexOf(")");
653
654 if ( infoStartIndex == -1 ||
655 infoStopIndex == -1 ) {
656 throw new FTPException(this, -1, "This is not a valid answer to the PASV command."); // FIXME : what kind of exception ?? + internationalize
657 }
658
659 // ok : info is in the message.
660 // let's parse it
661 String tmp1 = "";
662 answer = new String[2];
663
664 String tmp = text.substring(infoStartIndex + 1, infoStopIndex);
665
666 StringTokenizer tokenizer = new StringTokenizer(tmp,",");
667
668 int tokenIndex = 0;
669 while ( tokenizer.hasMoreTokens() ) {
670 if ( tokenIndex == 4 ) {
671 answer[0] = tmp1; // first half of answer : ??
672 try{
673 tmp1 = Integer.toString(Integer.parseInt(tokenizer.nextToken())*256);
674 } catch ( Exception e ) {
675 tmp1="0";
676 }
677 }
678 if ( tokenIndex > 0 && tokenIndex < 4 ) {
679 tmp1 = new StringBuffer(tmp1).append(".").toString();
680 }
681 if ( tokenIndex < 4 ) {
682 tmp1 = new StringBuffer(tmp1).append(tokenizer.nextToken()).toString();
683 } else {
684 if ( tokenIndex == 5 ) {
685 try {
686 tmp1 = Integer.toString(Integer.parseInt(tmp1) + Integer.parseInt(tokenizer.nextToken()));
687 } catch ( Exception e ) {;}
688 }
689 }
690 tokenIndex++;
691 }
692 answer[1] = tmp1; // second half of answer : ??
693 return answer;
694 }
695
696 /**
697 * This command causes the name of the current working directory to be
698 * returned.
699 *
700 * @see "RFC959-4:1:3:'PWD'"
701 * @return current working directory pathname.
702 * @since v1.0
703 */
704 // FIXME : is it really the right place to do that ? are there no exception to check ?
705 public String parseWorkingDirectory ( ) {
706 String answer = text;
707 answer = answer.substring(answer.indexOf('"')+1, answer.length());
708 answer = answer.substring(0, answer.indexOf('"'));
709 return answer;
710 }
711 }