001 package railo.runtime.tag; 002 003 import java.io.ByteArrayInputStream; 004 import java.io.ByteArrayOutputStream; 005 import java.io.IOException; 006 import java.io.InputStream; 007 import java.util.ArrayList; 008 import java.util.Enumeration; 009 import java.util.HashSet; 010 import java.util.List; 011 import java.util.Set; 012 import java.util.zip.ZipEntry; 013 import java.util.zip.ZipException; 014 import java.util.zip.ZipFile; 015 import java.util.zip.ZipInputStream; 016 import java.util.zip.ZipOutputStream; 017 018 import railo.commons.io.IOUtil; 019 import railo.commons.io.compress.ZipUtil; 020 import railo.commons.io.res.Resource; 021 import railo.commons.io.res.filter.DirectoryResourceFilter; 022 import railo.commons.io.res.filter.FileResourceFilter; 023 import railo.commons.io.res.filter.OrResourceFilter; 024 import railo.commons.io.res.filter.ResourceFilter; 025 import railo.commons.io.res.util.FileWrapper; 026 import railo.commons.io.res.util.ResourceUtil; 027 import railo.commons.io.res.util.WildcardPatternFilter; 028 import railo.commons.lang.StringUtil; 029 import railo.runtime.exp.ApplicationException; 030 import railo.runtime.exp.ExpressionException; 031 import railo.runtime.exp.PageException; 032 import railo.runtime.ext.tag.BodyTagImpl; 033 import railo.runtime.op.Caster; 034 import railo.runtime.op.Decision; 035 import railo.runtime.type.QueryImpl; 036 import railo.runtime.type.dt.DateTimeImpl; 037 038 public final class Zip extends BodyTagImpl { 039 040 private String action="zip"; 041 private String charset; 042 private Resource destination; 043 private String entryPath; 044 private Resource file; 045 private WildcardPatternFilter filter; 046 private String name; 047 private boolean overwrite; 048 private String prefix; 049 private boolean recurse=true; 050 private boolean showDirectory; 051 private boolean storePath=true; 052 private String variable; 053 private List<Object> params; 054 private Set<String> alreadyUsed; 055 private Resource source; 056 private static int id=0; 057 058 059 @Override 060 public void release() { 061 super.release(); 062 action="zip"; 063 charset=null; 064 destination=null; 065 entryPath=null; 066 file=null; 067 filter=null; 068 name=null; 069 overwrite=false; 070 prefix=null; 071 recurse=true; 072 showDirectory=false; 073 source=null; 074 storePath=true; 075 variable=null; 076 077 if(params!=null)params.clear(); 078 if(alreadyUsed!=null)alreadyUsed.clear(); 079 } 080 081 082 083 /** 084 * @param action the action to set 085 */ 086 public void setAction(String action) { 087 this.action = action.trim().toLowerCase(); 088 } 089 090 091 092 /** 093 * @param charset the charset to set 094 */ 095 public void setCharset(String charset) { 096 this.charset = charset; 097 } 098 099 100 101 /** 102 * @param destination the destination to set 103 * @throws ExpressionException 104 * @throws PageException 105 */ 106 public void setDestination(String strDestination) throws PageException { 107 this.destination = ResourceUtil.toResourceExistingParent(pageContext, strDestination); 108 if(!destination.exists())destination.mkdirs(); 109 110 if(!destination.isDirectory()) 111 throw new ApplicationException("destination ["+strDestination+"] is not a existing directory"); 112 113 114 } 115 116 117 118 /** 119 * @param entryPath the entryPath to set 120 */ 121 public void setEntrypath(String entryPath) { 122 if(StringUtil.isEmpty(entryPath,true)) return; 123 124 entryPath=entryPath.trim(); 125 entryPath=entryPath.replace('\\','/'); 126 127 if(StringUtil.startsWith(entryPath,'/'))entryPath=entryPath.substring(1); 128 if(StringUtil.endsWith(entryPath,'/'))entryPath=entryPath.substring(0,entryPath.length()-1); 129 this.entryPath = entryPath; 130 } 131 132 133 134 /** 135 * @param file the file to set 136 * @throws ExpressionException 137 */ 138 public void setFile(String file) { 139 this.file = ResourceUtil.toResourceNotExisting(pageContext, file); 140 } 141 142 /** 143 * @param filter the filter to set 144 */ 145 public void setFilter(String filter) { 146 147 if ( !filter.isEmpty() ) 148 this.filter = new WildcardPatternFilter( filter ); 149 } 150 151 152 153 /** 154 * @param name the name to set 155 */ 156 public void setName(String name) { 157 this.name = name; 158 } 159 160 161 162 /** 163 * @param overwrite the overwrite to set 164 */ 165 public void setOverwrite(boolean overwrite) { 166 this.overwrite = overwrite; 167 } 168 169 170 171 /** 172 * @param prefix the prefix to set 173 */ 174 public void setPrefix(String prefix) { 175 this.prefix = prefix; 176 } 177 178 179 180 /** 181 * @param recurse the recurse to set 182 */ 183 public void setRecurse(boolean recurse) { 184 this.recurse = recurse; 185 } 186 187 188 189 /** 190 * @param showDirectory the showDirectory to set 191 */ 192 public void setShowdirectory(boolean showDirectory) { 193 this.showDirectory = showDirectory; 194 } 195 196 197 198 /** 199 * @param source the source to set 200 * @throws PageException 201 */ 202 public void setSource(String strSource) throws PageException { 203 source = ResourceUtil.toResourceExisting(pageContext, strSource); 204 205 } 206 207 208 209 /** 210 * @param storePath the storePath to set 211 */ 212 public void setStorepath(boolean storePath) { 213 this.storePath = storePath; 214 } 215 216 217 218 /** 219 * @param variable the variable to set 220 */ 221 public void setVariable(String variable) { 222 this.variable = variable; 223 } 224 225 226 227 @Override 228 public int doStartTag() throws PageException { 229 return EVAL_BODY_INCLUDE; 230 } 231 232 private void actionDelete() throws ApplicationException, IOException { 233 required("file",file,true); 234 235 236 Resource existing = pageContext.getConfig().getTempDirectory().getRealResource(getTempName()); 237 IOUtil.copy(file, existing); 238 239 ZipInputStream zis = null; 240 ZipOutputStream zos = null; 241 try { 242 zis = new ZipInputStream( IOUtil.toBufferedInputStream(existing.getInputStream()) ); 243 zos = new ZipOutputStream(IOUtil.toBufferedOutputStream(file.getOutputStream())); 244 245 ZipEntry entry; 246 String path,name; 247 int index; 248 boolean accept; 249 250 if(filter==null && recurse && entryPath==null) 251 throw new ApplicationException("define at least one restriction, can't delete all the entries from a zip file"); 252 253 while ( ( entry = zis.getNextEntry()) != null ) { 254 accept=false; 255 path = entry.getName().replace('\\', '/'); 256 index=path.lastIndexOf('/'); 257 258 if(!recurse && index>0) accept=true; 259 260 //dir=index==-1?"":path.substring(0,index); 261 name=path.substring(index+1); 262 263 if(filter!=null && !filter.accept(name)) accept=true; 264 if(!entryPathMatch(path)) accept=true; 265 266 if(!accept) continue; 267 268 add(zos, entry, zis, false); 269 zis.closeEntry(); 270 } 271 } 272 finally { 273 IOUtil.closeEL(zis); 274 IOUtil.closeEL(zos); 275 existing.delete(); 276 } 277 278 } 279 280 281 282 private void actionList() throws PageException, IOException { 283 required("file",file,true); 284 required("name",name); 285 286 287 288 289 railo.runtime.type.Query query=new QueryImpl( 290 new String[]{"name","size","type","dateLastModified","directory","crc","compressedSize","comment"}, 291 0,"query"); 292 pageContext.setVariable(name, query); 293 294 ZipFile zip = getZip(file); 295 Enumeration entries = zip.entries(); 296 297 try { 298 String path,name,dir; 299 ZipEntry ze; 300 int row=0,index; 301 while(entries.hasMoreElements()) { 302 ze = (ZipEntry)entries.nextElement(); 303 if(!showDirectory && ze.isDirectory()) continue; 304 305 path = ze.getName().replace('\\', '/'); 306 index=path.lastIndexOf('/'); 307 if(!recurse && index>0) continue; 308 309 dir=index==-1?"":path.substring(0,index); 310 name=path.substring(index+1); 311 312 if(filter!=null && !filter.accept(null, name)) continue; 313 314 315 if(!entryPathMatch(dir)) continue; 316 //if(entryPath!=null && !(dir.equalsIgnoreCase(entryPath) || StringUtil.startsWithIgnoreCase(dir,entryPath+"/"))) ;///continue; 317 318 row++; 319 query.addRow(); 320 query.setAt("name", row, path); 321 query.setAt("size", row, Caster.toDouble(ze.getSize())); 322 query.setAt("type", row, ze.isDirectory()?"Directory":"File"); 323 query.setAt("dateLastModified", row, new DateTimeImpl(pageContext,ze.getTime(),false)); 324 query.setAt("crc", row, Caster.toDouble(ze.getCrc())); 325 query.setAt("compressedSize", row, Caster.toDouble(ze.getCompressedSize())); 326 query.setAt("comment", row, ze.getComment()); 327 query.setAt("directory", row, dir); 328 //zis.closeEntry(); 329 330 } 331 } 332 finally { 333 IOUtil.closeEL(zip); 334 } 335 } 336 337 private boolean entryPathMatch(String dir) { 338 if(entryPath==null) return true; 339 340 return dir.equalsIgnoreCase(entryPath) || StringUtil.startsWithIgnoreCase(dir,entryPath+"/"); 341 } 342 343 344 345 private void actionRead(boolean binary) throws ZipException, IOException, PageException { 346 required("file",file,true); 347 required("variable",variable); 348 required("entrypath",variable); 349 ZipFile zip = getZip(file); 350 351 try { 352 ZipEntry ze = zip.getEntry(entryPath); 353 if(ze==null)ze = zip.getEntry(entryPath+"/"); 354 if(ze==null) throw new ApplicationException("zip file ["+file+"] has no entry with name ["+entryPath+"]"); 355 356 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 357 358 InputStream is = zip.getInputStream(ze); 359 IOUtil.copy(is, baos,true,false); 360 zip.close(); 361 362 if(binary) 363 pageContext.setVariable(variable, baos.toByteArray()); 364 else { 365 if(charset==null)charset=pageContext.getConfig().getResourceCharset(); 366 pageContext.setVariable(variable, new String(baos.toByteArray(),charset)); 367 } 368 } 369 finally { 370 IOUtil.closeEL(zip); 371 } 372 373 } 374 375 376 377 private void actionUnzip() throws ApplicationException, IOException { 378 required("file",file,true); 379 required("destination",destination,false); 380 381 ZipInputStream zis=null; 382 String path; 383 Resource target,parent; 384 int index; 385 try { 386 387 zis = new ZipInputStream( IOUtil.toBufferedInputStream(file.getInputStream()) ) ; 388 ZipEntry entry; 389 while ( ( entry = zis.getNextEntry()) != null ) { 390 391 path = entry.getName().replace('\\', '/'); 392 index=path.lastIndexOf('/'); 393 394 // recurse 395 if(!recurse && index!=-1) { 396 zis.closeEntry(); 397 continue; 398 } 399 // filter 400 if(filter!=null && !filter.accept(path.substring(index+1))) { 401 zis.closeEntry(); 402 continue; 403 } 404 // entrypath 405 if(!entryPathMatch(path)) { 406 zis.closeEntry(); 407 continue; 408 } 409 target=destination.getRealResource(entry.getName()); 410 if(!storePath) target=destination.getRealResource(target.getName()); 411 if(entry.isDirectory()) { 412 target.mkdirs(); 413 } 414 else { 415 if(storePath){ 416 parent=target.getParentResource(); 417 if(!parent.exists())parent.mkdirs(); 418 } 419 if(overwrite || !target.exists())IOUtil.copy(zis,target,false); 420 } 421 target.setLastModified(entry.getTime()); 422 zis.closeEntry() ; 423 } 424 } 425 finally { 426 IOUtil.closeEL(zis); 427 } 428 } 429 430 431 432 private void actionZip() throws PageException, IOException { 433 required("file",file,false); 434 Resource dir = file.getParentResource(); 435 436 if(!dir.exists()) { 437 throw new ApplicationException("directory ["+dir.toString()+"] doesn't exist"); 438 } 439 440 441 442 if((params==null || params.isEmpty()) && source!=null) { 443 setParam(new ZipParamSource(source,entryPath,filter,prefix,recurse)); 444 } 445 446 if((params==null || params.isEmpty())) { 447 throw new ApplicationException("No source/content specified"); 448 } 449 450 451 452 453 ZipOutputStream zos=null; 454 Resource existing=null; 455 try { 456 457 // existing 458 if(!overwrite && file.exists()) { 459 existing = pageContext.getConfig().getTempDirectory().getRealResource(getTempName()); 460 IOUtil.copy(file, existing); 461 } 462 463 zos = new ZipOutputStream(IOUtil.toBufferedOutputStream(file.getOutputStream())); 464 465 Object[] arr = params.toArray(); 466 for(int i=arr.length-1;i>=0;i--) { 467 if(arr[i] instanceof ZipParamSource) 468 actionZip(zos,(ZipParamSource)arr[i]); 469 else if(arr[i] instanceof ZipParamContent) 470 actionZip(zos,(ZipParamContent)arr[i]); 471 } 472 473 if(existing!=null) { 474 ZipInputStream zis = new ZipInputStream( IOUtil.toBufferedInputStream(existing.getInputStream()) ); 475 try { 476 ZipEntry entry; 477 while ( ( entry = zis.getNextEntry()) != null ) { 478 add(zos, entry, zis, false); 479 zis.closeEntry(); 480 } 481 } 482 finally { 483 zis.close(); 484 } 485 } 486 } 487 finally { 488 ZipUtil.close(zos); 489 if(existing!=null)existing.delete(); 490 491 } 492 493 } 494 495 496 497 private String getTempName() { 498 return "tmp-"+(id++)+".zip"; 499 } 500 501 502 503 private void actionZip(ZipOutputStream zos, ZipParamContent zpc) throws PageException, IOException { 504 Object content = zpc.getContent(); 505 if(Decision.isBinary(content)) { 506 add(zos, new ByteArrayInputStream(Caster.toBinary(content)), zpc.getEntryPath(), System.currentTimeMillis(), true); 507 508 } 509 else { 510 String charset=zpc.getCharset(); 511 if(StringUtil.isEmpty(charset))charset=pageContext.getConfig().getResourceCharset(); 512 add(zos, new ByteArrayInputStream(content.toString().getBytes(charset)), zpc.getEntryPath(), System.currentTimeMillis(), true); 513 } 514 } 515 516 517 518 private void actionZip(ZipOutputStream zos, ZipParamSource zps) throws IOException { 519 if(zps.getSource().isFile()){ 520 521 String ep = zps.getEntryPath(); 522 if(ep==null)ep=zps.getSource().getName(); 523 add(zos,zps.getSource().getInputStream(),ep,zps.getSource().lastModified(),true); 524 } 525 else { 526 527 // prefix 528 String p=zps.getPrefix(); 529 if(StringUtil.isEmpty(p)) 530 p=this.prefix; 531 532 if(!StringUtil.isEmpty(p)){ 533 if(!StringUtil.endsWith(p, '/'))p+="/"; 534 } 535 else 536 p=""; 537 538 // recurse 539 540 541 // filter 542 ResourceFilter f = zps.getFilter(); 543 if(f==null)f=this.filter; 544 if(zps.isRecurse()) { 545 if(f!=null)f=new OrResourceFilter(new ResourceFilter[]{DirectoryResourceFilter.FILTER,f}); 546 } 547 else { 548 if(f==null)f=FileResourceFilter.FILTER; 549 } 550 551 addDir(zos,zps.getSource(),p,f); 552 } 553 } 554 555 556 557 private void addDir(ZipOutputStream zos, Resource dir, String parent, ResourceFilter filter) throws IOException { 558 Resource[] children = filter==null?dir.listResources():dir.listResources(filter); 559 560 for(int i=0;i<children.length;i++) { 561 562 563 if(children[i].isDirectory()) addDir(zos, children[i], parent+children[i].getName()+"/",filter); 564 else { 565 add(zos, children[i].getInputStream(), parent+children[i].getName(), children[i].lastModified(), true); 566 } 567 } 568 } 569 570 571 572 private void add(ZipOutputStream zos, InputStream is, String path, long lastMod, boolean closeInput) throws IOException { 573 ZipEntry ze=new ZipEntry(path); 574 ze.setTime(lastMod); 575 add(zos, ze, is, closeInput); 576 } 577 578 private void add(ZipOutputStream zos, ZipEntry entry,InputStream is, boolean closeInput) throws IOException { 579 if(alreadyUsed==null)alreadyUsed=new HashSet<String>(); 580 else if(alreadyUsed.contains(entry.getName())) return; 581 zos.putNextEntry(entry); 582 try { 583 IOUtil.copy(is,zos,closeInput,false); 584 } 585 finally { 586 zos.closeEntry(); 587 } 588 alreadyUsed.add(entry.getName()); 589 } 590 591 592 593 @Override 594 public void doInitBody() { 595 596 } 597 598 @Override 599 public int doAfterBody() { 600 return SKIP_BODY; 601 } 602 603 @Override 604 public int doEndTag() throws PageException {//print.out("doEndTag"+doCaching+"-"+body); 605 try { 606 if(action.equals("delete")) actionDelete(); 607 else if(action.equals("list")) actionList(); 608 else if(action.equals("read")) actionRead(false); 609 else if(action.equals("readbinary")) actionRead(true); 610 else if(action.equals("unzip")) actionUnzip(); 611 else if(action.equals("zip")) actionZip(); 612 else 613 throw new ApplicationException("invalid value ["+action+"] for attribute action","values for attribute action are:info,move,rename,copy,delete,read,readbinary,write,append,upload"); 614 } 615 catch(IOException ioe) { 616 throw Caster.toPageException(ioe); 617 } 618 619 return EVAL_PAGE; 620 } 621 622 /** 623 * sets if tag has a body or not 624 * @param hasBody 625 */ 626 public void hasBody(boolean hasBody) { 627 ///this.hasBody=hasBody; 628 } 629 630 private ZipFile getZip(Resource file) throws ZipException, IOException { 631 return new ZipFile(FileWrapper.toFile(file)); 632 } 633 634 /** 635 * throw a error if the value is empty (null) 636 * @param attributeName 637 * @param atttributValue 638 * @throws ApplicationException 639 */ 640 private void required(String attributeName, String attributValue) throws ApplicationException { 641 if(StringUtil.isEmpty(attributValue)) 642 throw new ApplicationException( 643 "invalid attribute constellation for the tag zip", 644 "attribute ["+attributeName+"] is required, if action is ["+action+"]"); 645 } 646 647 /** 648 * throw a error if the value is empty (null) 649 * @param attributeName 650 * @param atttributValue 651 * @throws ApplicationException 652 */ 653 private void required(String attributeName, Resource attributValue, boolean exists) throws ApplicationException { 654 if(attributValue==null) 655 throw new ApplicationException( 656 "invalid attribute constellation for the tag zip", 657 "attribute ["+attributeName+"] is required, if action is ["+action+"]"); 658 659 if(exists && !attributValue.exists()) 660 throw new ApplicationException(attributeName+" resource ["+attributValue+"] doesn't exist"); 661 else if(exists && !attributValue.canRead()) 662 throw new ApplicationException("no access to "+attributeName+" resource ["+attributValue+"]"); 663 664 665 } 666 667 668 669 670 public void setParam(Object param) { 671 if(params==null) { 672 params=new ArrayList<Object>(); 673 alreadyUsed=new HashSet<String>(); 674 } 675 params.add(param); 676 } 677 678 679 680 /** 681 * @return the source 682 */ 683 public Resource getSource() { 684 return source; 685 } 686 687 688 }