This commit is contained in:
Looly 2020-11-13 17:54:42 +08:00
parent 1a0c9a3b51
commit 99eeb30ffe
13 changed files with 222 additions and 116 deletions

View File

@ -306,6 +306,7 @@ public class FileUtil extends PathUtil {
/**
* 创建File对象<br>
* 根据的路径构建文件在Win下直接构建在Linux下拆分路径单独构建
* 此方法会检查slip漏洞漏洞说明见http://blog.nsfocus.net/zip-slip-2/
*
* @param parent 父文件对象
@ -316,7 +317,7 @@ public class FileUtil extends PathUtil {
if (StrUtil.isBlank(path)) {
throw new NullPointerException("File path is blank!");
}
return checkSlip(parent, new File(parent, path));
return checkSlip(parent, buildFile(parent, path));
}
/**
@ -3277,4 +3278,34 @@ public class FileUtil extends PathUtil {
public static void tail(File file, Charset charset) {
tail(file, charset, Tailer.CONSOLE_HANDLER);
}
/**
* 根据压缩包中的路径构建目录结构在Win下直接构建在Linux下拆分路径单独构建
*
* @param outFile 最外部路径
* @param fileName 文件名可以包含路径
* @return 文件或目录
* @since 5.0.5
*/
private static File buildFile(File outFile, String fileName) {
// 替换Windows路径分隔符为Linux路径分隔符便于统一处理
fileName = fileName.replace('\\', '/');
if (false == FileUtil.isWindows()
// 检查文件名中是否包含"/"不考虑以"/"结尾的情况
&& fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
// 在Linux下多层目录创建存在问题/会被当成文件名的一部分此处做处理
// 使用/拆分路径zip中无\级联创建父目录
final List<String> pathParts = StrUtil.split(fileName, '/', false, true);
final int lastPartIndex = pathParts.size() - 1;//目录个数
for (int i = 0; i < lastPartIndex; i++) {
//由于路径拆分slip不检查在最后一步检查
outFile = new File(outFile, pathParts.get(i));
}
//noinspection ResultOfMethodCallIgnored
outFile.mkdirs();
// 最后一个部分如果非空作为文件名
fileName = pathParts.get(lastPartIndex);
}
return new File(outFile, fileName);
}
}

View File

@ -471,7 +471,7 @@ public class ZipUtil {
while (em.hasMoreElements()) {
zipEntry = em.nextElement();
// FileUtil.file会检查slip漏洞漏洞说明见http://blog.nsfocus.net/zip-slip-2/
outItemFile = buildFile(outFile, zipEntry.getName());
outItemFile = FileUtil.file(outFile, zipEntry.getName());
if (zipEntry.isDirectory()) {
// 创建对应目录
//noinspection ResultOfMethodCallIgnored
@ -1106,36 +1106,6 @@ public class ZipUtil {
throw new IORuntimeException(e);
}
}
/**
* 根据压缩包中的路径构建目录结构在Win下直接构建在Linux下拆分路径单独构建
*
* @param outFile 最外部路径
* @param fileName 文件名可以包含路径
* @return 文件或目录
* @since 5.0.5
*/
private static File buildFile(File outFile, String fileName) {
// 替换Windows路径分隔符为Linux路径分隔符便于统一处理
fileName = fileName.replace('\\', '/');
if (false == FileUtil.isWindows()
// 检查文件名中是否包含"/"不考虑以"/"结尾的情况
&& fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
// 在Linux下多层目录创建存在问题/会被当成文件名的一部分此处做处理
// 使用/拆分路径zip中无\级联创建父目录
final List<String> pathParts = StrUtil.split(fileName, '/', false, true);
final int lastPartIndex = pathParts.size() - 1;//目录个数
for (int i = 0; i < lastPartIndex; i++) {
//由于路径拆分slip不检查在最后一步检查
outFile = new File(outFile, pathParts.get(i));
}
//noinspection ResultOfMethodCallIgnored
outFile.mkdirs();
// 最后一个部分如果非空作为文件名
fileName = pathParts.get(lastPartIndex);
}
return FileUtil.file(outFile, fileName);
}
// ---------------------------------------------------------------------------------------------- Private method end
}

View File

@ -1,4 +0,0 @@
package cn.hutool.extra.compress;
public class Extractor {
}

View File

@ -7,7 +7,7 @@ import java.io.File;
import java.io.FileFilter;
/**
* 数据归档封装归档即将几个文件或目录打成一个压缩包<br>
* 数据归档封装归档即将几个文件或目录打成一个压缩包
*
* @author looly
*/

View File

@ -56,7 +56,7 @@ public class StreamArchiver implements Archiver {
return new StreamArchiver(charset, archiverName, out);
}
private ArchiveOutputStream out;
private final ArchiveOutputStream out;
/**
* 构造

View File

@ -0,0 +1,11 @@
/**
* 基于commons-compress的打包压缩封装
*
* <p>
* https://commons.apache.org/proper/commons-compress/
* </p>
*
* @author looly
*
*/
package cn.hutool.extra.compress.archiver;

View File

@ -0,0 +1,11 @@
package cn.hutool.extra.compress.extractor;
/**
* 数据解包封装用于将ziptar等包解包为文件
*
* @author looly
* @since 5.5.0
*/
public interface Extractor {
}

View File

@ -0,0 +1,69 @@
package cn.hutool.extra.compress.extractor;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.lang.Assert;
import cn.hutool.extra.compress.CompressException;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
public class StreamExtractor {
private final ArchiveInputStream in;
public StreamExtractor(Charset charset, InputStream in) {
final ArchiveStreamFactory factory = new ArchiveStreamFactory(charset.name());
try {
this.in = factory.createArchiveInputStream(in);
} catch (ArchiveException e) {
throw new CompressException(e);
}
}
/**
* 释放解压到指定目录
*
* @param targetDir 目标目录
*/
public void extract(File targetDir) {
try {
extractInternal(targetDir);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 释放解压到指定目录
*
* @param targetDir 目标目录
* @throws IOException IO异常
*/
private void extractInternal(File targetDir) throws IOException {
Assert.isTrue(null != targetDir && targetDir.isDirectory(), "target must be dir.");
final ArchiveInputStream in = this.in;
ArchiveEntry entry;
File outItemFile;
while(null != (entry = this.in.getNextEntry())){
if(false == in.canReadEntryData(entry)){
// 无法读取的文件直接跳过
continue;
}
outItemFile = FileUtil.file(targetDir, entry.getName());
if(entry.isDirectory()){
// 创建对应目录
//noinspection ResultOfMethodCallIgnored
outItemFile.mkdirs();
} else {
FileUtil.writeFromStream(in, outItemFile);
}
}
}
}

View File

@ -0,0 +1,11 @@
/**
* 基于commons-compress的解包解压缩封装
*
* <p>
* https://commons.apache.org/proper/commons-compress/
* </p>
*
* @author looly
*
*/
package cn.hutool.extra.compress.extractor;

View File

@ -1,7 +1,5 @@
package cn.hutool.extra.template.engine.freemarker;
import java.io.IOException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ClassUtil;
@ -13,9 +11,12 @@ import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.template.Configuration;
import java.io.IOException;
/**
* Beetl模板引擎封装
*
* FreeMarker模板引擎封装<br>
* https://freemarker.apache.org/
*
* @author looly
*/
public class FreemarkerEngine implements TemplateEngine {
@ -23,14 +24,16 @@ public class FreemarkerEngine implements TemplateEngine {
private Configuration cfg;
// --------------------------------------------------------------------------------- Constructor start
/**
* 默认构造
*/
public FreemarkerEngine() {}
public FreemarkerEngine() {
}
/**
* 构造
*
*
* @param config 模板配置
*/
public FreemarkerEngine(TemplateConfig config) {
@ -39,7 +42,7 @@ public class FreemarkerEngine implements TemplateEngine {
/**
* 构造
*
*
* @param freemarkerCfg {@link Configuration}
*/
public FreemarkerEngine(Configuration freemarkerCfg) {
@ -49,7 +52,7 @@ public class FreemarkerEngine implements TemplateEngine {
@Override
public TemplateEngine init(TemplateConfig config) {
if(null == config){
if (null == config) {
config = TemplateConfig.DEFAULT;
}
init(createCfg(config));
@ -58,29 +61,30 @@ public class FreemarkerEngine implements TemplateEngine {
/**
* 初始化引擎
*
* @param freemarkerCfg Configuration
*/
private void init(Configuration freemarkerCfg){
private void init(Configuration freemarkerCfg) {
this.cfg = freemarkerCfg;
}
@Override
public Template getTemplate(String resource) {
if(null == this.cfg){
if (null == this.cfg) {
init(TemplateConfig.DEFAULT);
}
try {
return FreemarkerTemplate.wrap(this.cfg.getTemplate(resource));
} catch(IOException e) {
} catch (IOException e) {
throw new IORuntimeException(e);
}catch (Exception e) {
} catch (Exception e) {
throw new TemplateException(e);
}
}
/**
* 创建配置项
*
*
* @param config 模板配置
* @return {@link Configuration }
*/
@ -94,30 +98,30 @@ public class FreemarkerEngine implements TemplateEngine {
cfg.setDefaultEncoding(config.getCharset().toString());
switch (config.getResourceMode()) {
case CLASSPATH:
cfg.setTemplateLoader(new ClassTemplateLoader(ClassUtil.getClassLoader(), config.getPath()));
break;
case FILE:
try {
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(config.getPath())));
} catch (IOException e) {
throw new IORuntimeException(e);
}
break;
case WEB_ROOT:
try {
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(FileUtil.getWebRoot(), config.getPath())));
} catch (IOException e) {
throw new IORuntimeException(e);
}
break;
case STRING:
cfg.setTemplateLoader(new SimpleStringTemplateLoader());
break;
default:
break;
case CLASSPATH:
cfg.setTemplateLoader(new ClassTemplateLoader(ClassUtil.getClassLoader(), config.getPath()));
break;
case FILE:
try {
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(config.getPath())));
} catch (IOException e) {
throw new IORuntimeException(e);
}
break;
case WEB_ROOT:
try {
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(FileUtil.getWebRoot(), config.getPath())));
} catch (IOException e) {
throw new IORuntimeException(e);
}
break;
case STRING:
cfg.setTemplateLoader(new SimpleStringTemplateLoader());
break;
default:
break;
}
return cfg;
}
}

View File

@ -1,7 +1,7 @@
/**
* Freemarker实现
*
* @author looly
* Freemarker实现<br>
* https://freemarker.apache.org/
*
* @author looly
*/
package cn.hutool.extra.template.engine.freemarker;

View File

@ -7,10 +7,10 @@ import cn.hutool.extra.template.TemplateEngine;
import org.apache.velocity.app.Velocity;
/**
* Velocity模板引擎
*
* @author looly
* Velocity模板引擎<br>
* http://velocity.apache.org/
*
* @author looly
*/
public class VelocityEngine implements TemplateEngine {
@ -18,14 +18,16 @@ public class VelocityEngine implements TemplateEngine {
private TemplateConfig config;
// --------------------------------------------------------------------------------- Constructor start
/**
* 默认构造
*/
public VelocityEngine() {}
public VelocityEngine() {
}
/**
* 构造
*
*
* @param config 模板配置
*/
public VelocityEngine(TemplateConfig config) {
@ -34,7 +36,7 @@ public class VelocityEngine implements TemplateEngine {
/**
* 构造
*
*
* @param engine {@link org.apache.velocity.app.VelocityEngine}
*/
public VelocityEngine(org.apache.velocity.app.VelocityEngine engine) {
@ -44,7 +46,7 @@ public class VelocityEngine implements TemplateEngine {
@Override
public TemplateEngine init(TemplateConfig config) {
if(null == config){
if (null == config) {
config = TemplateConfig.DEFAULT;
}
this.config = config;
@ -54,15 +56,16 @@ public class VelocityEngine implements TemplateEngine {
/**
* 初始化引擎
*
* @param engine 引擎
*/
private void init(org.apache.velocity.app.VelocityEngine engine){
private void init(org.apache.velocity.app.VelocityEngine engine) {
this.engine = engine;
}
/**
* 获取原始的引擎对象
*
*
* @return 原始引擎对象
* @since 4.3.0
*/
@ -72,7 +75,7 @@ public class VelocityEngine implements TemplateEngine {
@Override
public Template getTemplate(String resource) {
if(null == this.engine){
if (null == this.engine) {
init(TemplateConfig.DEFAULT);
}
@ -80,15 +83,15 @@ public class VelocityEngine implements TemplateEngine {
String root;
// 自定义编码
String charsetStr = null;
if(null != this.config){
if (null != this.config) {
root = this.config.getPath();
charsetStr = this.config.getCharsetStr();
// 修正template目录在classpath或者web_root模式下按照配置添加默认前缀
// 如果用户已经自行添加了前缀则忽略之
final TemplateConfig.ResourceMode resourceMode = this.config.getResourceMode();
if(TemplateConfig.ResourceMode.CLASSPATH == resourceMode
|| TemplateConfig.ResourceMode.WEB_ROOT == resourceMode){
if (TemplateConfig.ResourceMode.CLASSPATH == resourceMode
|| TemplateConfig.ResourceMode.WEB_ROOT == resourceMode) {
resource = StrUtil.addPrefixIfNot(resource, StrUtil.addSuffixIfNot(root, "/"));
}
}
@ -98,7 +101,7 @@ public class VelocityEngine implements TemplateEngine {
/**
* 创建引擎
*
*
* @param config 模板配置
* @return {@link org.apache.velocity.app.VelocityEngine}
*/
@ -116,29 +119,29 @@ public class VelocityEngine implements TemplateEngine {
// loader
switch (config.getResourceMode()) {
case CLASSPATH:
// 新版Velocity弃用
case CLASSPATH:
// 新版Velocity弃用
// ve.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
ve.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
break;
case FILE:
// path
final String path = config.getPath();
if (null != path) {
ve.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);
}
break;
case WEB_ROOT:
ve.setProperty(Velocity.RESOURCE_LOADER, "webapp");
ve.setProperty("webapp.resource.loader.class", "org.apache.velocity.tools.view.servlet.WebappLoader");
ve.setProperty("webapp.resource.loader.path", StrUtil.nullToDefault(config.getPath(), StrUtil.SLASH));
break;
case STRING:
ve.setProperty(Velocity.RESOURCE_LOADER, "str");
ve.setProperty("str.resource.loader.class", SimpleStringResourceLoader.class.getName());
break;
default:
break;
ve.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
break;
case FILE:
// path
final String path = config.getPath();
if (null != path) {
ve.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);
}
break;
case WEB_ROOT:
ve.setProperty(Velocity.RESOURCE_LOADER, "webapp");
ve.setProperty("webapp.resource.loader.class", "org.apache.velocity.tools.view.servlet.WebappLoader");
ve.setProperty("webapp.resource.loader.path", StrUtil.nullToDefault(config.getPath(), StrUtil.SLASH));
break;
case STRING:
ve.setProperty(Velocity.RESOURCE_LOADER, "str");
ve.setProperty("str.resource.loader.class", SimpleStringResourceLoader.class.getName());
break;
default:
break;
}
ve.init();

View File

@ -1,7 +1,7 @@
/**
* Velocity实现
*
* @author looly
* Velocity实现<br>
* http://velocity.apache.org/
*
* @author looly
*/
package cn.hutool.extra.template.engine.velocity;