001/* 002 * Version 0.70 01/04/2002 003 * 004 * Visit my url for update: http://www.geocities.com/beapetrovicova/ 005 * 006 * jFtp was developed by Bea Petrovicova <beapetrovicova@yahoo.com>. 007 * The design and implementation of jFtp are available for royalty-free 008 * adoption and use. This software is provided 'as is' without any 009 * guarantees. Copyright is retained by Bea Petrovicova. Redistribution 010 * of any part of jFtp or any derivative works must include this notice. 011 * 012 */ 013 014package cz.dhl.ftp; 015 016import cz.dhl.io.CoFile; 017import cz.dhl.io.CoFilenameFilter; 018import cz.dhl.io.CoOrder; 019import cz.dhl.ui.CoConsole; 020import java.io.BufferedReader; 021import java.io.InputStreamReader; 022import java.io.InputStream; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.io.Reader; 026import java.text.DateFormat; 027import java.util.Calendar; 028import java.util.Date; 029import java.util.Locale; 030import java.util.NoSuchElementException; 031import java.util.StringTokenizer; 032import java.util.Vector; 033 034/** 035 * Allows uniform manipulation with FTP files. 036 * Equivalent for File object. 037 * 038 * <P><B>Only absolute pathnames are supported!</B></P> 039 * 040 * @Version 0.70 01/04/2002 041 * @author Bea Petrovicova <beapetrovicova@yahoo.com> 042 * 043 * @see Ftp 044 * @see cz.dhl.io.CoFile 045 * @see java.io.File 046 */ 047public final class FtpFile 048 implements CoFile 049{ 050 /* CoOrder Implementation. */ 051 052 private String name=null, ext=null; 053 054 private void sortSetup(String name) 055 { this.name=name.toUpperCase(); int index = this.name.lastIndexOf("."); 056 if(index != -1 && index < this.name.length()) 057 ext = this.name.substring(index); else ext = " " +this.name; } 058 059 public int compareNameToIgnoreCase(CoOrder file) 060 { if(file instanceof FtpFile) 061 { FtpFile l2 = (FtpFile)file; 062 return name.compareTo(l2.name); } 063 else throw new ClassCastException(); } 064 065 public int compareExtToIgnoreCase(CoOrder file) 066 { if(file instanceof FtpFile) 067 { FtpFile l2 = (FtpFile)file; 068 int result=ext.compareTo(l2.ext); 069 if(result==0) 070 result=name.compareTo(l2.name); 071 return result; } 072 else throw new ClassCastException(); } 073 074 public boolean startsWithIgnoreCase(char ch) 075 { return (name.charAt(0) == Character.toUpperCase(ch)); } 076 077 public boolean equalsExtTo(String filter) 078 { return (ext.compareTo(filter)==0); } 079 080 public boolean equalsExtTo(String filter[]) 081 { boolean done = false; 082 for(int j=0;j<filter.length;j++) 083 if(ext.compareTo(filter[j])==0) 084 { done = true; break; } 085 return done; } 086 087 public boolean equals(Object o) 088 { if(o==null) return false; else return(compareTo(o)==0); } 089 090 public int compareTo(Object o) 091 { String s1 = getHost()+getAbsolutePath(), s2; 092 if(o instanceof CoFile) 093 { CoFile f2 = (CoFile)o; 094 s2 = f2.getHost()+f2.getAbsolutePath(); } 095 else if(o instanceof String) 096 s2 = (String)o; 097 else throw new ClassCastException(); 098 return s1.compareTo(s2); } 099 100 public boolean isConnected() { return client.isConnected(); } 101 102 /* CoOrder Implementation. */ 103 104 public char getDataType() 105 { if(client.getContext().getFileTransferMode() == 'S') 106 if(equalsExtTo(client.getContext().getTextFilter())) 107 return 'A'; else return 'I'; 108 else return client.getContext().getFileTransferMode(); } 109 110 public InputStream getInputStream() throws IOException 111 { return new FtpInputStream(this); } 112 113 public OutputStream getOutputStream() throws IOException 114 { return new FtpOutputStream(this,false); } 115 116 public OutputStream getOutputStream(boolean append) throws IOException 117 { return new FtpOutputStream(this,append); } 118 119 public CoFile newFileChild(String child) 120 { return new FtpFile(this,child,this.client); } 121 122 public CoFile newFileRename(String name) 123 { return new FtpFile(this.getParent(),name,this.client); } 124 125 public CoConsole getConsole() 126 { return client.getContext().getConsole(); } 127 128 /* CoFile Implementation. */ 129 130 private static final String months[] = {"JAN", "FEB", "MAR", 131 "APR", "MAY", "JUN", 132 "JUL", "AUG", "SEP", 133 "OCT", "NOV", "DEC"}; 134 135 private static final char LINK = 'l'; 136 private static final char SPECIAL = 'c'; 137 private static final char FOLDER = 'd'; 138 private static final char FILE = '-'; 139 140 Ftp client = null; 141 142 private String access = null; 143 private String owner = null; 144 private String group = null; 145 private long size = 0L; 146 private long date = 0L; 147 String path = null; 148 149 private FtpFile() {} 150 151 /** Creates a new FtpFile instance by converting the 152 * given pathname string into an abstract pathname. */ 153 public FtpFile(String path, Ftp client) 154 { access = "d?????????"; 155 owner = ""; group = ""; 156 size = -1; date = 0L; 157 if(path.compareTo("/")!=0 && path.endsWith("/")) 158 this.path = path.substring(0,path.length()-1); 159 else this.path = path; 160 this.client = client; 161 sortSetup(getName()); 162 } 163 164 /** Creates a new FtpFile instance from a parent 165 * pathname string and a child pathname string. */ 166 public FtpFile(String path, String name, Ftp client) 167 { access = "f?????????"; 168 owner = ""; group = ""; 169 size = -1; date = 0L; 170 if(path.endsWith("/")) 171 this.path = path+name; 172 else this.path = path+"/"+name; 173 this.client = client; 174 sortSetup(name); 175 } 176 177 /** Creates a new FtpFile instance from a parent 178 * abstract pathname and a child pathname string. */ 179 public FtpFile(FtpFile dir, String name, Ftp client) 180 { this(dir.toString(),name,client); } 181 182 public String getHost() 183 { String system = ""; 184 try 185 { system = client.host(); } 186 catch(IOException e) 187 { client.getContext().printlog("< File: Can't obtain host name. >\n"+e); } 188 return system; 189 } 190 191 public String getAbsolutePath() { return path; } 192 193 public int getPathDepth() 194 { int depth = -1; int length = -1; 195 while((length = path.indexOf('/',length + 1)) >= 0) 196 depth++; 197 if(!path.endsWith("/")) 198 depth++; 199 return depth; 200 } 201 202 public CoFile getPathFragment(int depth) 203 { if(depth>0) 204 { int length = -1; 205 for(int n=0;n<=depth;n++) 206 if((length = path.indexOf('/',length + 1)) < 0) 207 break; 208 if(length>0) 209 return new FtpFile(path.substring(0,length),client); 210 else return this; 211 } else return new FtpFile("/",client); 212 } 213 214 public String[] getPathArray() 215 { Vector dv = new Vector(); 216 dv.addElement(getHost()); 217 StringTokenizer toker = new StringTokenizer(path,"/"); 218 while (true) 219 try 220 { String d = toker.nextToken(); 221 dv.addElement(d); 222 } catch(NoSuchElementException e) 223 { break; } 224 String[] ds = new String[dv.size()]; 225 dv.copyInto(ds); 226 return ds; 227 } 228 229 public String getName() 230 { if(path.lastIndexOf('/')>=0) 231 return path.substring(path.lastIndexOf('/')+1); 232 else return path; } 233 234 public String getParent() 235 { if(path.lastIndexOf('/')>0) 236 return path.substring(0,path.lastIndexOf('/')); 237 else return new String("/"); } 238 239 public boolean delete() throws SecurityException 240 { if(isDirectory()) 241 { /* release dir if current */ 242 client.cd(getParent()); 243 if(client.rmdir(path)) 244 return true; 245 /* When using NAME_LIST, files/dirs 246 are distinguished by guessing. */ 247 else return client.rm(path); 248 } else if(client.rm(path)) 249 return true; 250 else 251 { /* release dir if current */ 252 client.cd(getParent()); 253 /* When using NAME_LIST, files/dirs 254 are distinguished by guessing. */ 255 return client.rmdir(path); } 256 } 257 258 public boolean mkdir() throws SecurityException 259 { return client.mkdir(path); } 260 public boolean mkdirs() throws SecurityException 261 { boolean done = true; 262 int depth = getPathDepth(); 263 for(int i=0; i<depth;i++) 264 if(!((FtpFile)getPathFragment(depth)).mkdir()) 265 done = false; 266 return done; 267 } 268 public boolean renameTo(CoFile dest) throws SecurityException 269 { return client.mv(path,dest.getAbsolutePath()); } 270 271 public long length() { return size; } 272 public long lastModified() { return date; } 273 public String lastModifiedString() 274 { return (DateFormat.getDateTimeInstance( 275 DateFormat.SHORT,DateFormat.SHORT) 276 ).format(new Date(lastModified())); } 277 278 public boolean isAbsolute() { return (path.charAt(0) == '/'); } 279 public boolean isDirectory() { return (access.charAt(0) == FOLDER); } 280 public boolean isFile() { return (access.charAt(0) == FILE); } 281 public boolean isSpecial() { return (access.charAt(0) == SPECIAL); } 282 public boolean isLink() { return (access.charAt(0) == LINK); } 283 /** Tests if the file represented by this File object is executable. */ 284 public boolean isExecutable() { return (access.indexOf('x') != -1); } 285 public boolean isHidden() { return false; } 286 public boolean canRead() { return true; } 287 public boolean canWrite() { return true; } 288 public boolean exists() { return false; } 289 290 public String getAccess() { return access; }; 291 /** Get owner of this file object. */ 292 public String getOwner() { return owner; }; 293 /** Get group of users for this file object. */ 294 public String getGroup() { return group; }; 295 public String propertyString() 296 { String desc = getAccess()+" "+getOwner()+" "+getGroup(); 297 return (isFile()?""+length()+" "+desc:desc); } 298 299 public CoFile[] listCoRoots() 300 { CoFile fs[] = new CoFile[1]; 301 fs[0] = getPathFragment(0); 302 return fs; } 303 304 public CoFile[] listCoFiles() 305 throws SecurityException 306 { FtpFile[] fs = null; 307 String line; 308 BufferedReader ibuf = null; 309 310 int listtype = client.getContext().getListCommandMode(); 311 try 312 { boolean error=false; 313 Vector fv = new Vector(); 314 315 ibuf = new BufferedReader(new InputStreamReader(new FtpListInputStream(this))); 316 while ((line = ibuf.readLine()) != null) 317 { try 318 { switch(listtype) 319 { 320 case FtpContext.LIST: 321 fv.addElement(FtpFile.examineListLine(this,line)); 322 break; 323 case FtpContext.NAME_LIST: 324 if(line.startsWith("/") && line.endsWith(":")) 325 break; 326 if(line.indexOf("//")!=-1) 327 line = line.substring(line.lastIndexOf('/')+1); 328 case FtpContext.NAME_LIST_LS_F: 329 case FtpContext.NAME_LIST_LS_P: 330 if(line.length()>0) 331 fv.addElement(FtpFile.examineNameListLine(this,line,listtype)); 332 break; 333 case FtpContext.NAME_LIST_LS_LA: 334 fv.addElement(FtpFile.examineUnixListLine(this,line)); 335 break; 336 } 337 } 338 catch(NoSuchElementException e) 339 { if(!error && (e.getMessage()==null || !e.getMessage().equals("skip"))) 340 { client.printlog("\n < File: Invalid List Format ! >" + 341 (e.getMessage()!=null?"\n "+e.getMessage():"")+ 342 "\n Line: "+line+ 343 "\n Try: 'NAME_LIST' List Command"); 344 error = true; } } 345 } 346 fs = new FtpFile[fv.size()]; 347 fv.copyInto(fs); 348 } 349 catch(IOException e) 350 { client.printlog("< File: Can't list directory! >\n"+e); } 351 finally 352 { try 353 { Reader r; 354 if(ibuf!=null) { r=ibuf; ibuf=null; r.close(); } 355 } catch(IOException e) 356 { client.printerr(e); } 357 } 358 return fs; 359 } 360 361 public CoFile[] listCoFiles(CoFilenameFilter filter) 362 throws SecurityException 363 { FtpFile[] fs = (FtpFile[])listCoFiles(); 364 if(filter!=null) 365 { Vector fv = new Vector(); 366 for(int i=0;i<fs.length;i++) 367 if(filter.accept(this,fs[i].getName())) 368 fv.addElement(fs[i]); 369 fs = new FtpFile[fv.size()]; 370 fv.copyInto(fs); } 371 return fs; 372 } 373 374 private static FtpFile examineListLine(FtpFile path,String line) 375 throws NoSuchElementException 376 { if("0123456789".indexOf(line.charAt(0))<0) 377 /* unix list format never strarts with number */ 378 return FtpFile.examineUnixListLine(path,line); 379 /* windows list format always starts with number */ 380 else return FtpFile.examineWinListLine(path,line); } 381 382 private static FtpFile examineNameListLine(FtpFile path,String line,int listtype) 383 throws NoSuchElementException 384 { FtpFile ff = new FtpFile(); 385 ff.client = path.client; 386 switch(listtype) 387 { 388 case FtpContext.NAME_LIST: 389 /************************************** 390 * file folder executable link special * 391 **************************************/ 392 if(line.indexOf('.') != -1) 393 ff.access = "-?????????"; 394 else ff.access = "d?????????"; 395 break; 396 case FtpContext.NAME_LIST_LS_P: 397 /*************************************** 398 * file folder/ executable link special * 399 ***************************************/ 400 if(line.endsWith("/")) 401 { ff.access = "d?????????"; 402 line = line.substring(0,line.length()-1); 403 } else ff.access = "-?????????"; 404 break; 405 case FtpContext.NAME_LIST_LS_F: 406 /***************************************** 407 * file folder/ executable* link@ special * 408 *****************************************/ 409 if(line.endsWith("/")) 410 { ff.access = "d?????????"; 411 line = line.substring(0,line.length()-1); 412 } else 413 if(line.endsWith("*")) 414 { ff.access = "-??x??x??x"; 415 line = line.substring(0,line.length()-1); 416 } else 417 if(line.endsWith("@")) 418 { ff.access = "l?????????"; 419 line = line.substring(0,line.length()-1); 420 } else 421 ff.access = "-?????????"; 422 break; 423 } 424 ff.owner = ""; 425 ff.group = ""; 426 ff.size = -1; 427 ff.date = 0L; 428 ff.path = path.getAbsolutePath(); 429 430 if(!ff.path.endsWith("/")) 431 ff.path = ff.path + '/' + line; 432 else ff.path = ff.path + line; 433 434 ff.sortSetup(line); 435 436 /* Skip current '.' and parent '..' directory aliases. */ 437 if(ff.getName().compareTo(".")==0 || ff.getName().compareTo("..")==0) 438 throw new NoSuchElementException("skip"); 439 return ff; 440 } 441 442 private static FtpFile examineWinListLine(FtpFile path,String line) 443 throws NoSuchElementException 444 { FtpFile ff = new FtpFile(); 445 ff.client = path.client; 446 /********************************************** 447 * 10-16-01 11:35PM 1479 file * 448 * 10-16-01 11:37PM <DIR> awt * 449 **********************************************/ 450 try 451 { StringTokenizer toker = new StringTokenizer(line); 452 ff.date = examineWinListDate 453 (toker.nextToken(), /* date */ 454 toker.nextToken()); /* time */ 455 String size2dir = toker.nextToken(); /* size or dir */ 456 if(size2dir.equals("<DIR>")) 457 { ff.access = "d?????????"; 458 ff.size = -1; 459 } else { 460 ff.access = "-?????????"; 461 ff.size = Long.parseLong(size2dir); } 462 String name = toker.nextToken("").trim(); /* name */ 463 464 ff.owner = ""; 465 ff.group = ""; 466 467 ff.path = path.toString(); 468 469 if(!ff.path.endsWith("/")) 470 ff.path = ff.path + '/' + name; 471 else ff.path = ff.path + name; 472 473 ff.sortSetup(name); 474 } 475 catch(NumberFormatException e) 476 { throw new NoSuchElementException("Win-List: Invalid Number Format"); } 477 /* Skip current '.' and parent '..' directory aliases. */ 478 if(ff.getName().compareTo(".")==0 || ff.getName().compareTo("..")==0) 479 throw new NoSuchElementException("skip"); 480 return ff; 481 } 482 483 private static long examineWinListDate(String date, String time) 484 { /********************** 485 * 10-16-01 11:35PM * 486 * 10-16-2001 11:35PM * 487 **********************/ 488 Calendar c = Calendar.getInstance(); 489 try 490 { StringTokenizer toker = new StringTokenizer(date,"-"); 491 int m = Integer.parseInt(toker.nextToken()), 492 d = Integer.parseInt(toker.nextToken()), 493 y = Integer.parseInt(toker.nextToken()); 494 if(y>=70) y+=1900; else y+=2000; 495 toker = new StringTokenizer(time,":APM"); 496 c.set(y,m,d,(time.endsWith("PM")?12:0)+ 497 Integer.parseInt(toker.nextToken()), 498 Integer.parseInt(toker.nextToken())); 499 } 500 catch(NumberFormatException e) 501 { throw new NoSuchElementException("Win-List: Invalid Date Format"); } 502 return c.getTime().getTime(); 503 } 504 505 private static FtpFile examineUnixListLine(FtpFile path,String line) 506 throws NoSuchElementException 507 { FtpFile ff = new FtpFile(); 508 ff.client = path.client; 509 510 /********************************************************************* 511 * -rw-r--r-- 1 owner group 239 Nov 9 1998 file * 512 * crw-rw-rw- 1 root sys 11, 42 Aug 3 2000 sun_device * 513 * crw------- 1 root sys 137 0x0089 Nov 25 11:39 hpux_device * 514 * drw-r--r-- 1 owner group 58 Nov 12 13:51 folder * 515 * lrw-r--r-- 1 owner group 58 Nov 12 13:51 link -> source * 516 * -rw-r--r-- 1 4 58 Nov 12 13:51 uu_file * 517 * crw------- 1 4 137 0x0089 Nov 25 11:39 uu_device * 518 * drw-r--r-- 1 4 58 Nov 12 13:51 uu_folder * 519 * lrw-r--r-- 1 4 58 Nov 12 13:51 uu_link -> src * 520 **********************************************************************/ 521 try 522 { if(line.indexOf("->")>=0) 523 line = line.substring(0,line.indexOf("->")); 524 StringTokenizer toker = new StringTokenizer(line); 525 ff.access = toker.nextToken(); /* access */ 526 toker.nextToken(); /* links */ 527 ff.owner = toker.nextToken(); /* owner */ 528 ff.group = toker.nextToken(); /* group */ 529 String size = toker.nextToken(); /* size */ 530 if(size.endsWith(",")) 531 size = size.substring(0,size.indexOf(",")); 532 String uu = size; 533 if(ff.access.startsWith("c")) 534 uu = toker.nextToken(); /* device */ 535 /* if uu.charAt(0) is not digit try uu_file format */ 536 if("0123456789".indexOf(uu.charAt(0))<0) 537 { size = ff.group; ff.group = ""; } 538 ff.size = Integer.parseInt(size); 539 ff.date = examineUnixListDate 540 (("0123456789".indexOf(uu.charAt(0))<0?uu 541 :toker.nextToken()), /* month */ 542 toker.nextToken(), /* day */ 543 toker.nextToken()); /* time or year */ 544 String name = toker.nextToken("").trim(); /* name */ 545 546 ff.path = path.toString(); 547 if(!ff.path.endsWith("/")) 548 ff.path = ff.path + '/' + name; 549 else ff.path = ff.path + name; 550 ff.sortSetup(name); 551 } 552 catch(NumberFormatException e) 553 { throw new NoSuchElementException("Unix-List: Invalid Number Format"); } 554 catch(NoSuchElementException e) 555 { /* Skip 'total n' message. */ 556 try 557 { StringTokenizer toker = new StringTokenizer(line); 558 if(!toker.nextToken().equals("total")) 559 throw e; 560 Long.parseLong(toker.nextToken()); 561 if(!toker.hasMoreTokens()) 562 throw new NoSuchElementException("skip"); 563 else throw e; 564 } catch(NumberFormatException x) 565 { throw e; } 566 } 567 /* Skip current '.' and parent '..' directory aliases. */ 568 if(ff.getName().compareTo(".")==0 || ff.getName().compareTo("..")==0) 569 throw new NoSuchElementException("skip"); 570 return ff; 571 } 572 573 private static long examineUnixListDate(String month, String day, String year2time) 574 { /*************** 575 * Nov 9 1998 * 576 * Nov 12 13:51 * 577 ***************/ 578 Calendar c = Calendar.getInstance(); 579 month = month.toUpperCase(); 580 try 581 { for(int m=0;m<12;m++) 582 if(month.equals(months[m])) 583 { if(year2time.indexOf(':')!= -1) 584 { /* current year */ 585 c.setTime(new Date(System.currentTimeMillis())); 586 StringTokenizer toker 587 = new StringTokenizer(year2time,":"); 588 /* date and time */ 589 c.set(c.get(Calendar.YEAR), m, 590 Integer.parseInt(day), 591 Integer.parseInt(toker.nextToken()), 592 Integer.parseInt(toker.nextToken())); 593 } else 594 /* date */ 595 c.set(Integer.parseInt(year2time),m, 596 Integer.parseInt(day),0,0); 597 break; } 598 } 599 catch(NumberFormatException e) 600 { throw new NoSuchElementException("Unix-List: Invalid Date Format"); } 601 return c.getTime().getTime(); 602 } 603 604 public String toString() 605 { return getAbsolutePath(); } 606}