增加访问文件的api , 调整xss的判断逻辑

This commit is contained in:
iizmg
2020-05-27 16:31:36 +08:00
parent 5008e877bd
commit 080e1d7859
10 changed files with 181 additions and 245 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,65 @@
package org.itkk.udf.api.general.web;
import org.itkk.udf.api.common.CommonConstant;
import org.itkk.udf.starter.core.exception.ParameterValidException;
import org.itkk.udf.starter.file.db.DbFileConstant;
import org.itkk.udf.starter.file.db.dto.DbFileInfoDto;
import org.itkk.udf.starter.file.db.web.DbFileBase;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@RestController
@RequestMapping(value = CommonConstant.URL_ROOT_WEB_PUBLIC + "file/")
public class FilePublicController extends DbFileBase {
/**
* 文件预览
*
* @param id id
* @param width width
* @param height height
* @throws IOException IOException
*/
@GetMapping("preview/{id}")
public void preview(@PathVariable String id, @RequestParam(name = "width", required = false) Integer width, @RequestParam(name = "height", required = false) Integer height) throws IOException {
//获得文件信息
DbFileInfoDto dbFileInfoDto = dbFileService.get(id);
//判空
if (dbFileInfoDto == null) {
throw new ParameterValidException("文件ID:" + id + "对应的文件不存在");
}
//判断是否公共文件
if (DbFileConstant.PUBLIC_ROOT_PATH_CODE.equals(dbFileInfoDto.getRootPathCode())) {
throw new ParameterValidException("文件ID:" + id + "不是公共文件");
}
//设置头信息
this.setResponseHeader(id, true);
//下载
dbFileService.download(id, width, height, response.getOutputStream());
}
/**
* 文件下载
*
* @param id id
* @throws IOException IOException
*/
@GetMapping("download/{id}")
public void download(@PathVariable String id) throws IOException {
//获得文件信息
DbFileInfoDto dbFileInfoDto = dbFileService.get(id);
//判空
if (dbFileInfoDto == null) {
throw new ParameterValidException("文件ID:" + id + "对应的文件不存在");
}
//判断是否公共文件
if (DbFileConstant.PUBLIC_ROOT_PATH_CODE.equals(dbFileInfoDto.getRootPathCode())) {
throw new ParameterValidException("文件ID:" + id + "不是公共文件");
}
//设置头信息
this.setResponseHeader(id, false);
//下载
dbFileService.download(id, null, null, response.getOutputStream());
}
}
@@ -11,6 +11,7 @@ logging.file.max-size=100MB
logging.file.max-history=31
# FILE DB
org.itkk.udf.starter.db.file.properties.mapping.common=D:/app/file/common/
org.itkk.udf.starter.db.file.properties.mapping.public=D:/app/file/public/
org.itkk.udf.starter.db.file.properties.mapping.temp=D:/app/file/temp/
org.itkk.udf.starter.db.file.properties.fileUploadSupportType=pdf,jpg,jpeg,png
org.itkk.udf.starter.db.file.properties.previewSupportContentType=application/pdf,image/jpeg,image/png
@@ -11,6 +11,7 @@ logging.file.max-size=100MB
logging.file.max-history=31
# FILE DB
org.itkk.udf.starter.db.file.properties.mapping.common=/usr/local/share/nginx/file/common/
org.itkk.udf.starter.db.file.properties.mapping.public=/usr/local/share/nginx/file/public/
org.itkk.udf.starter.db.file.properties.mapping.temp=/usr/local/share/nginx/file/temp/
org.itkk.udf.starter.db.file.properties.fileUploadSupportType=pdf,jpg,jpeg,png
org.itkk.udf.starter.db.file.properties.previewSupportContentType=application/pdf,image/jpeg,image/png
@@ -90,8 +90,12 @@ public class CoreUtil {
* @return 包含中文标点 true , 不包含中文标点 false
*/
public static boolean isChinesePunctuation(String s) {
final char[] skipArr = new char[]{'>', '<'};
boolean rv = false;
for (char c : s.toCharArray()) {
if (ArrayUtils.contains(skipArr, c)) {
continue;
}
if (isChinesePunctuation(c)) {
rv = true;
break;
@@ -18,8 +18,6 @@ public class XssUtil {
public static String xssEncode(String s) {
if (s == null || s.isEmpty()) {
return s;
} else {
s = stripXSSAndSql(s);
}
StringBuilder sb = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
@@ -31,13 +29,13 @@ public class XssUtil {
case '<':
sb.append("<");// 转义小于号
break;
case '\'':
/*case '\'':
sb.append("'");// 转义单引号
break;
case '\"':
sb.append(""");// 转义双引号
break;
/*case '&':
case '&':
sb.append("&");// 转义&
break;
case '#':
@@ -48,7 +46,7 @@ public class XssUtil {
break;
}
}
return sb.toString();
return stripXSSAndSql(sb.toString());
}
/**
@@ -64,7 +62,7 @@ public class XssUtil {
scriptPattern7 = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE),
scriptPattern8 = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE),
scriptPattern9 = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
scriptPattern10 = Pattern.compile("(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|and|or|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute|union|where|limit|group by|hex|set)\\b)", Pattern.CASE_INSENSITIVE),
scriptPattern10 = Pattern.compile("(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(update|delete|select|insert|trancate|char|into|substr|ascii|declare|exec|count|master|drop|execute|union|where|limit|group by|hex)\\b)", Pattern.CASE_INSENSITIVE),
scriptPattern11 = Pattern.compile("\\*\\/(\\s|.)*?\\/\\*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
private static String stripXSSAndSql(String value) {
@@ -88,7 +86,7 @@ public class XssUtil {
// Avoid onload= expressions
value = scriptPattern9.matcher(value).replaceAll(REPLACE_STRING);
// Avoid sql= expressions
value = scriptPattern10.matcher(value).replaceAll(REPLACE_STRING);
//value = scriptPattern10.matcher(value).replaceAll(REPLACE_STRING);
// Avoid */xxx/*(...) expressions
value = scriptPattern11.matcher(value).replaceAll(REPLACE_STRING);
}
@@ -7,10 +7,17 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class DbFileConstant {
/**
* 公共根路径代码
*/
public static final String PUBLIC_ROOT_PATH_CODE = "public";
/**
* JPEG上下文类型
*/
public static final String CONTENT_TYPE_JPEG = "image/jpeg";
/**
* PNG上下文类型
*/
@@ -0,0 +1,97 @@
package org.itkk.udf.starter.file.db.web;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.itkk.udf.starter.core.CoreConstant;
import org.itkk.udf.starter.core.CoreProperties;
import org.itkk.udf.starter.core.exception.ParameterValidException;
import org.itkk.udf.starter.file.db.DbFileProperties;
import org.itkk.udf.starter.file.db.dto.DbFileInfoDto;
import org.itkk.udf.starter.file.db.service.DbFileService;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public abstract class DbFileBase {
/**
* dbFileService
*/
@Autowired
protected DbFileService dbFileService;
/**
* dbFileProperties
*/
@Autowired
protected DbFileProperties dbFileProperties;
/**
* coreProperties
*/
@Autowired
protected CoreProperties coreProperties;
/**
* response
*/
@Autowired
protected HttpServletResponse response;
/**
* request
*/
@Autowired
protected HttpServletRequest request;
/**
* 设置头信息
*
* @param id id
* @param preview preview
* @throws UnsupportedEncodingException
*/
protected void setResponseHeader(String id, Boolean preview) throws UnsupportedEncodingException {
//获得文件信息
DbFileInfoDto fileInfo = dbFileService.get(id);
//判空
if (fileInfo == null) {
throw new ParameterValidException("文件" + id + "不存在");
}
//rootPath判空
if (MapUtils.isEmpty(dbFileProperties.getMapping()) || !dbFileProperties.getMapping().containsKey(fileInfo.getRootPathCode())) {
throw new ParameterValidException("文件路径映射" + fileInfo.getRootPathCode() + "不存在");
}
//清空response
response.reset();
//设置response
response.setContentLengthLong(fileInfo.getFileSize());
response.setCharacterEncoding(CoreConstant.CHARACTER_SET);
response.setContentType(fileInfo.getContentType());
response.setDateHeader("Expires", 0L);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
//response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
//判断是否预览 (预览则要限定一下上下文类型){
if (preview) {
if (StringUtils.isNotBlank(dbFileProperties.getPreviewSupportContentType())) {
if (!dbFileProperties.getPreviewSupportContentType().contains(fileInfo.getContentType())) {
throw new ParameterValidException("不支持[" + fileInfo.getContentType() + "]此上下文类型的预览");
}
}
}
//判断是否预览 (预览则不用设置文件名)
if (!preview) {
//设置文件名
String fileName = URLEncoder.encode(fileInfo.getFileName(), CoreConstant.CHARACTER_SET);
if (request.getHeader("User-Agent").toUpperCase().indexOf("MSIE") > 0) {
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
} else {
response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'" + fileName);
}
}
}
}
@@ -1,57 +1,16 @@
package org.itkk.udf.starter.file.db.web;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.itkk.udf.starter.core.CoreConstant;
import org.itkk.udf.starter.core.CoreProperties;
import org.itkk.udf.starter.core.RestResponse;
import org.itkk.udf.starter.core.exception.ParameterValidException;
import org.itkk.udf.starter.file.db.DbFileProperties;
import org.itkk.udf.starter.file.db.dto.DbFileInfoDto;
import org.itkk.udf.starter.file.db.service.DbFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
public abstract class DbFileBaseController {
/**
* dbFileService
*/
@Autowired
private DbFileService dbFileService;
/**
* dbFileProperties
*/
@Autowired
private DbFileProperties dbFileProperties;
/**
* coreProperties
*/
@Autowired
private CoreProperties coreProperties;
/**
* response
*/
@Autowired
private HttpServletResponse response;
/**
* request
*/
@Autowired
private HttpServletRequest request;
public abstract class DbFileBaseController extends DbFileBase {
/**
* 获得当前用户ID
@@ -144,52 +103,4 @@ public abstract class DbFileBaseController {
dbFileService.delete(id);
return RestResponse.success();
}
/**
* 设置头信息
*
* @param id id
* @param preview preview
* @throws UnsupportedEncodingException
*/
private void setResponseHeader(String id, Boolean preview) throws UnsupportedEncodingException {
//获得文件信息
DbFileInfoDto fileInfo = dbFileService.get(id);
//判空
if (fileInfo == null) {
throw new ParameterValidException("文件" + id + "不存在");
}
//rootPath判空
if (MapUtils.isEmpty(dbFileProperties.getMapping()) || !dbFileProperties.getMapping().containsKey(fileInfo.getRootPathCode())) {
throw new ParameterValidException("文件路径映射" + fileInfo.getRootPathCode() + "不存在");
}
//清空response
response.reset();
//设置response
response.setContentLengthLong(fileInfo.getFileSize());
response.setCharacterEncoding(CoreConstant.CHARACTER_SET);
response.setContentType(fileInfo.getContentType());
response.setDateHeader("Expires", 0L);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
//response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
//判断是否预览 (预览则要限定一下上下文类型){
if (preview) {
if (StringUtils.isNotBlank(dbFileProperties.getPreviewSupportContentType())) {
if (!dbFileProperties.getPreviewSupportContentType().contains(fileInfo.getContentType())) {
throw new ParameterValidException("不支持[" + fileInfo.getContentType() + "]此上下文类型的预览");
}
}
}
//判断是否预览 (预览则不用设置文件名)
if (!preview) {
//设置文件名
String fileName = URLEncoder.encode(fileInfo.getFileName(), CoreConstant.CHARACTER_SET);
if (request.getHeader("User-Agent").toUpperCase().indexOf("MSIE") > 0) {
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
} else {
response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'" + fileName);
}
}
}
}
@@ -1,148 +0,0 @@
package org.itkk.udf.starter.file.db.web;
import org.itkk.udf.starter.core.exception.SystemRuntimeException;
import org.itkk.udf.starter.file.db.dto.WopiFileInfo;
import org.itkk.udf.starter.file.db.service.DbFileService;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.MessageDigest;
public abstract class WopiBaseContrller {
/**
* request
*/
@Autowired
private HttpServletRequest request;
/**
* response
*/
@Autowired
private HttpServletResponse response;
/**
* dbFileService
*/
@Autowired
private DbFileService dbFileService;
/**
* 获取文件流
*
* @param name
*/
@GetMapping("files/{name}/contents")
public void getFile(@PathVariable String name) {
final String utf8 = "UTF-8";
final String iso88591 = "ISO-8859-1";
InputStream fis = null;
OutputStream toClient = null;
try {
// 获得文件
File file = dbFileService.getFile(name);
// 取得文件名
String filename = file.getName();
// 以流的形式下载文件
fis = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
// 清空response
response.reset();
// 设置response的Header
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(utf8), iso88591));
response.addHeader("Content-Length", "" + file.length());
toClient = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
toClient.write(buffer);
toClient.flush();
} catch (Exception e) {
throw new SystemRuntimeException(e);
} finally {
try {
if (fis != null) {
fis.close();
}
if (toClient != null) {
toClient.close();
}
} catch (Exception e) {
throw new SystemRuntimeException(e);
}
}
}
/**
* 获取文件信息
*
* @param name name
* @return WopiFileInfo
*/
@RequestMapping("files/{name}")
public WopiFileInfo getFileInfo(@PathVariable String name) {
//定义返回值
WopiFileInfo info = null;
//获得文件
File file = dbFileService.getFile(name);
//判空
if (file.exists()) {
//实例化
info = new WopiFileInfo();
//填充文件信息
info.setBaseFileName(file.getName());
info.setSize(file.length());
info.setOwnerId("admin");
info.setVersion(file.lastModified());
info.setSha256(getHash256(file));
info.setAllowExternalMarketplace(false);
info.setUserCanWrite(false);
info.setSupportsUpdate(false);
info.setSupportsLocks(false);
info.setDisablePrint(true);
info.setDisableTranslation(true);
info.setUserCanNotWriteRelative(true);
}
//返回
return info;
}
/**
* 获取文件的SHA-256值
*
* @param file file
* @return String
*/
private String getHash256(File file) {
String value = "";
// 获取hash值
try {
byte[] buffer = new byte[1024];
int numRead;
InputStream fis = new FileInputStream(file);
//如果想使用SHA-1或SHA-256,则传入SHA-1,SHA-256
MessageDigest complete = MessageDigest.getInstance("SHA-256");
do {
//从文件读到buffer
numRead = fis.read(buffer);
if (numRead > 0) {
//用读到的字节进行MD5的计算,第二个参数是偏移量
complete.update(buffer, 0, numRead);
}
} while (numRead != -1);
fis.close();
value = new String(Base64.encodeBase64(complete.digest()));
} catch (Exception e) {
throw new SystemRuntimeException(e);
}
return value;
}
}