001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.config; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.jar.Attributes; 027import java.util.jar.Manifest; 028import java.util.zip.ZipEntry; 029import java.util.zip.ZipException; 030import java.util.zip.ZipFile; 031import java.util.zip.ZipInputStream; 032 033import lucee.commons.io.IOUtil; 034import lucee.commons.io.SystemUtil; 035import lucee.commons.io.compress.ZipUtil; 036import lucee.commons.io.log.Log; 037import lucee.commons.io.log.LogUtil; 038import lucee.commons.io.res.Resource; 039import lucee.commons.io.res.filter.ExtensionResourceFilter; 040import lucee.commons.io.res.filter.ResourceFilter; 041import lucee.commons.io.res.util.FileWrapper; 042import lucee.commons.io.res.util.ResourceUtil; 043import lucee.commons.lang.ExceptionUtil; 044import lucee.commons.lang.StringUtil; 045import lucee.commons.lang.SystemOut; 046import lucee.runtime.Info; 047import lucee.runtime.extension.RHExtension; 048import lucee.runtime.op.Caster; 049import lucee.runtime.op.Decision; 050import lucee.runtime.type.util.ListUtil; 051 052public class DeployHandler { 053 054 055 056 private static final ResourceFilter ALL_EXT = new ExtensionResourceFilter(new String[]{".lex",".lar"}); 057 058 059 public static void deploy(Config config){ 060 synchronized (config) { 061 Resource dir = getDeployDirectory(config); 062 int ma = Info.getMajorVersion(); 063 int mi = Info.getMinorVersion(); 064 if(!dir.exists()) { 065 if(ma>4 || ma==4 && mi>1) {// FUTURE remove the if contition 066 dir.mkdir(); // TODO we have set this to mkdir to avoid that delted context are recreated (the file structure), but the question is, why is this executed on a deleted context 067 } 068 return; 069 } 070 071 Resource[] children = dir.listResources(ALL_EXT); 072 Resource child; 073 String ext; 074 for(int i=0;i<children.length;i++){ 075 child=children[i]; 076 try { 077 // Lucee archives 078 ext=ResourceUtil.getExtension(child, null); 079 if("lar".equalsIgnoreCase(ext)) { 080 deployArchive(config,child); 081 } 082 083 // Lucee Extensions 084 else if("lex".equalsIgnoreCase(ext)) 085 deployExtension(config, child); 086 } 087 catch (ZipException e) { 088 SystemOut.printDate(config.getErrWriter(),ExceptionUtil.getStacktrace(e, true)); 089 } 090 catch (IOException e) { 091 SystemOut.printDate(config.getErrWriter(),ExceptionUtil.getStacktrace(e, true)); 092 } 093 } 094 } 095 } 096 097 public static Resource getDeployDirectory(Config config) { 098 return config.getConfigDir().getRealResource("deploy"); 099 } 100 101 private static void deployArchive(Config config,Resource archive) throws ZipException, IOException { 102 Log logger = ((ConfigImpl)config).getLog("deploy"); 103 String type=null,virtual=null,name=null; 104 boolean readOnly,topLevel,hidden,physicalFirst; 105 short inspect; 106 InputStream is = null; 107 ZipFile file=null; 108 try { 109 file=new ZipFile(FileWrapper.toFile(archive)); 110 ZipEntry entry = file.getEntry("META-INF/MANIFEST.MF"); 111 112 // no manifest 113 if(entry==null) { 114 logger.log(Log.LEVEL_ERROR,"archive","cannot deploy Lucee Archive ["+archive+"], file is to old, the file does not have a MANIFEST."); 115 moveToFailedFolder(archive); 116 return; 117 } 118 119 is = file.getInputStream(entry); 120 Manifest manifest = new Manifest(is); 121 Attributes attr = manifest.getMainAttributes(); 122 123 //id = unwrap(attr.getValue("mapping-id")); 124 type = unwrap(attr.getValue("mapping-type")); 125 virtual = unwrap(attr.getValue("mapping-virtual-path")); 126 name = ListUtil.trim(virtual, "/"); 127 readOnly = Caster.toBooleanValue(unwrap(attr.getValue("mapping-readonly")),false); 128 topLevel = Caster.toBooleanValue(unwrap(attr.getValue("mapping-top-level")),false); 129 inspect = ConfigWebUtil.inspectTemplate(unwrap(attr.getValue("mapping-inspect")), ConfigImpl.INSPECT_UNDEFINED); 130 if(inspect==ConfigImpl.INSPECT_UNDEFINED) { 131 Boolean trusted = Caster.toBoolean(unwrap(attr.getValue("mapping-trusted")),null); 132 if(trusted!=null) { 133 if(trusted.booleanValue()) inspect=ConfigImpl.INSPECT_NEVER; 134 else inspect=ConfigImpl.INSPECT_ALWAYS; 135 } 136 } 137 hidden = Caster.toBooleanValue(unwrap(attr.getValue("mapping-hidden")),false); 138 physicalFirst = Caster.toBooleanValue(unwrap(attr.getValue("mapping-physical-first")),false); 139 } 140 finally{ 141 IOUtil.closeEL(is); 142 ZipUtil.close(file); 143 } 144 Resource trgDir = config.getConfigDir().getRealResource("archives").getRealResource(type).getRealResource(name); 145 Resource trgFile = trgDir.getRealResource(archive.getName()); 146 trgDir.mkdirs(); 147 148 // delete existing files 149 150 try { 151 ResourceUtil.deleteContent(trgDir, null); 152 ResourceUtil.moveTo(archive, trgFile,true); 153 154 logger.log(Log.LEVEL_INFO,"archive","add "+type+" mapping ["+virtual+"] with archive ["+trgFile.getAbsolutePath()+"]"); 155 if("regular".equalsIgnoreCase(type)) 156 ConfigWebAdmin.updateMapping((ConfigImpl)config,virtual, null, trgFile.getAbsolutePath(), "archive", inspect, topLevel); 157 else if("cfc".equalsIgnoreCase(type)) 158 ConfigWebAdmin.updateComponentMapping((ConfigImpl)config,virtual, null, trgFile.getAbsolutePath(), "archive", inspect); 159 else if("ct".equalsIgnoreCase(type)) 160 ConfigWebAdmin.updateCustomTagMapping((ConfigImpl)config,virtual, null, trgFile.getAbsolutePath(), "archive", inspect); 161 162 163 } 164 catch (Throwable t) { 165 ExceptionUtil.rethrowIfNecessary(t); 166 moveToFailedFolder(archive); 167 LogUtil.log(logger, Log.LEVEL_ERROR,"archive",t); 168 } 169 } 170 171 172 private static void deployExtension(Config config, Resource ext) { 173 ConfigImpl ci = (ConfigImpl)config; 174 boolean isWeb=config instanceof ConfigWeb; 175 String type=isWeb?"web":"server"; 176 Log logger = ((ConfigImpl)config).getLog("deploy"); 177 178 // Manifest 179 Manifest manifest = null; 180 ZipInputStream zis=null; 181 try { 182 zis = new ZipInputStream( IOUtil.toBufferedInputStream(ext.getInputStream()) ) ; 183 ZipEntry entry; 184 String name; 185 while ( ( entry = zis.getNextEntry()) != null ) { 186 name=entry.getName(); 187 if(!entry.isDirectory() && name.equalsIgnoreCase("META-INF/MANIFEST.MF")) { 188 manifest = toManifest(config,zis,null); 189 } 190 zis.closeEntry() ; 191 } 192 } 193 catch(Throwable t){ 194 ExceptionUtil.rethrowIfNecessary(t); 195 LogUtil.log(logger, Log.LEVEL_ERROR,"extension", t); 196 moveToFailedFolder(ext); 197 return; 198 } 199 finally { 200 IOUtil.closeEL(zis); 201 } 202 203 int minCoreVersion=0; 204 double minLoaderVersion=0; 205 String strMinCoreVersion="",strMinLoaderVersion="",version=null,name=null,id=null; 206 207 if(manifest!=null) { 208 Attributes attr = manifest.getMainAttributes(); 209 // version 210 version=unwrap(attr.getValue("version")); 211 212 213 // id 214 id=unwrap(attr.getValue("id")); 215 216 // name 217 name=unwrap(attr.getValue("name")); 218 219 // core version 220 strMinCoreVersion=unwrap(attr.getValue("lucee-core-version")); 221 if(StringUtil.isEmpty(strMinCoreVersion)) 222 strMinCoreVersion=unwrap(attr.getValue("railo-core-version")); 223 minCoreVersion=Info.toIntVersion(strMinCoreVersion,minCoreVersion); 224 225 // loader version 226 strMinLoaderVersion=unwrap(attr.getValue("lucee-loader-version")); 227 if(StringUtil.isEmpty(strMinLoaderVersion)) 228 strMinLoaderVersion=unwrap(attr.getValue("railo-loader-version")); 229 minLoaderVersion=Caster.toDoubleValue(strMinLoaderVersion,minLoaderVersion); 230 231 } 232 if(StringUtil.isEmpty(name,true)) { 233 name=ext.getName(); 234 int index=name.lastIndexOf('.'); 235 name=name.substring(0,index-1); 236 } 237 name=name.trim(); 238 239 240 // check core version 241 if(minCoreVersion>Info.getVersionAsInt()) { 242 logger.log(Log.LEVEL_ERROR,"extension", "cannot deploy Lucee Extension ["+ext+"], Lucee Version must be at least ["+strMinCoreVersion+"]."); 243 moveToFailedFolder(ext); 244 return; 245 } 246 247 // check loader version 248 if(minLoaderVersion>SystemUtil.getLoaderVersion()) { 249 logger.log(Log.LEVEL_ERROR,"extension", "cannot deploy Lucee Extension ["+ext+"], Lucee Loader Version must be at least ["+strMinLoaderVersion+"], update the lucee.jar first."); 250 moveToFailedFolder(ext); 251 return; 252 } 253 // check id 254 if(!Decision.isUUId(id)) { 255 logger.log(Log.LEVEL_ERROR,"extension", "cannot deploy Lucee Extension ["+ext+"], this Extension has no valid id ["+id+"],id must be a valid UUID."); 256 moveToFailedFolder(ext); 257 return; 258 } 259 260 261 262 263 Resource trgFile=null; 264 try{ 265 ConfigWebAdmin.removeRHExtension(ci,id); 266 267 Resource trgDir = config.getConfigDir().getRealResource("extensions").getRealResource(type).getRealResource(name); 268 trgFile = trgDir.getRealResource(ext.getName()); 269 trgDir.mkdirs(); 270 ResourceUtil.moveTo(ext, trgFile,true); 271 } 272 catch(Throwable t){ 273 ExceptionUtil.rethrowIfNecessary(t); 274 LogUtil.log(logger, Log.LEVEL_ERROR,"extension", t); 275 moveToFailedFolder(ext); 276 return; 277 } 278 279 try { 280 zis = new ZipInputStream( IOUtil.toBufferedInputStream(trgFile.getInputStream()) ) ; 281 ZipEntry entry; 282 String path; 283 String fileName; 284 List<String> jars=new ArrayList<String>(), flds=new ArrayList<String>(), tlds=new ArrayList<String>(), contexts=new ArrayList<String>(), applications=new ArrayList<String>(); 285 while ( ( entry = zis.getNextEntry()) != null ) { 286 path=entry.getName(); 287 fileName=fileName(entry); 288 // jars 289 if(!entry.isDirectory() && (startsWith(path,type,"jars") || startsWith(path,type,"jar") || startsWith(path,type,"lib") || startsWith(path,type,"libs")) && StringUtil.endsWithIgnoreCase(path, ".jar")) { 290 logger.log(Log.LEVEL_INFO,"extension","deploy jar "+fileName); 291 ConfigWebAdmin.updateJar(config,zis,fileName,false); 292 jars.add(fileName); 293 } 294 295 // flds 296 if(!entry.isDirectory() && startsWith(path,type,"flds") && StringUtil.endsWithIgnoreCase(path, ".fld")) { 297 logger.log(Log.LEVEL_INFO,"extension","deploy fld "+fileName); 298 ConfigWebAdmin.updateFLD(config, zis, fileName,false); 299 flds.add(fileName); 300 } 301 302 // tlds 303 if(!entry.isDirectory() && startsWith(path,type,"tlds") && (StringUtil.endsWithIgnoreCase(path, ".tld") || StringUtil.endsWithIgnoreCase(path, ".tldx"))) { 304 logger.log(Log.LEVEL_INFO,"extension","deploy tld "+fileName); 305 ConfigWebAdmin.updateTLD(config, zis, fileName,false); 306 tlds.add(fileName); 307 } 308 309 // context 310 String relpath; 311 if(!entry.isDirectory() && startsWith(path,type,"context") && !StringUtil.startsWith(fileName(entry), '.')) { 312 relpath=path.substring(8); 313 //log.info("extension","deploy context "+relpath); 314 logger.log(Log.LEVEL_INFO,"extension","deploy context "+relpath); 315 ConfigWebAdmin.updateContext(ci, zis, relpath,false); 316 contexts.add(relpath); 317 } 318 319 // applications 320 if(!entry.isDirectory() && startsWith(path,type,"applications") && !StringUtil.startsWith(fileName(entry), '.')) { 321 relpath=path.substring(13); 322 //log.info("extension","deploy context "+relpath); 323 logger.log(Log.LEVEL_INFO,"extension","deploy application "+relpath); 324 ConfigWebAdmin.updateApplication(ci, zis, relpath,false); 325 applications.add(relpath); 326 } 327 328 329 zis.closeEntry() ; 330 } 331 332 //installation successfull 333 334 ConfigWebAdmin.updateRHExtension(ci, 335 new RHExtension(id,name,version, 336 jars.toArray(new String[jars.size()]), 337 flds.toArray(new String[flds.size()]), 338 tlds.toArray(new String[tlds.size()]), 339 contexts.toArray(new String[contexts.size()]), 340 applications.toArray(new String[applications.size()]))); 341 342 } 343 catch(Throwable t){ 344 ExceptionUtil.rethrowIfNecessary(t); 345 // installation failed 346 347 LogUtil.log(logger, Log.LEVEL_ERROR,"extension",t); 348 moveToFailedFolder(trgFile); 349 return; 350 } 351 finally { 352 IOUtil.closeEL(zis); 353 } 354 } 355 356 private static Manifest toManifest(Config config,InputStream is, Manifest defaultValue) { 357 try { 358 String cs = config.getResourceCharset(); 359 String str = IOUtil.toString(is,cs); 360 if(StringUtil.isEmpty(str,true)) return defaultValue; 361 str=str.trim()+"\n"; 362 return new Manifest(new ByteArrayInputStream(str.getBytes(cs))); 363 } 364 catch (Throwable t) { 365 ExceptionUtil.rethrowIfNecessary(t); 366 return defaultValue; 367 } 368 } 369 370 private static boolean startsWith(String path,String type, String name) { 371 return StringUtil.startsWithIgnoreCase(path, name+"/") || StringUtil.startsWithIgnoreCase(path, type+"/"+name+"/"); 372 } 373 374 private static String fileName(ZipEntry entry) { 375 String name = entry.getName(); 376 int index=name.lastIndexOf('/'); 377 if(index==-1) return name; 378 return name.substring(index+1); 379 } 380 381 private static void moveToFailedFolder(Resource archive) { 382 Resource dir = archive.getParentResource().getRealResource("failed-to-deploy"); 383 Resource dst = dir.getRealResource(archive.getName()); 384 dir.mkdirs(); 385 386 try { 387 if(dst.exists()) dst.remove(true); 388 ResourceUtil.moveTo(archive, dst,true); 389 } 390 catch (Throwable t) { 391 ExceptionUtil.rethrowIfNecessary(t); 392 } 393 394 // TODO Auto-generated method stub 395 396 } 397 398 private static String unwrap(String value) { 399 if(value==null) return ""; 400 String res = unwrap(value,'"'); 401 if(res!=null) return res; // was double quote 402 403 return unwrap(value,'\''); // try single quote unwrap, when there is no double quote. 404 } 405 406 private static String unwrap(String value, char del) { 407 value=value.trim(); 408 if(StringUtil.startsWith(value, del) && StringUtil.endsWith(value, del)) { 409 return value.substring(1, value.length()-1); 410 } 411 return value; 412 } 413 414}