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