diff --git a/pom.xml b/pom.xml index 0d45b2f8..62dfa4e5 100644 --- a/pom.xml +++ b/pom.xml @@ -385,6 +385,13 @@ commons-lang3 3.3.2 + + + + com.googlecode.owasp-java-html-sanitizer + owasp-java-html-sanitizer + 20211018.2 + diff --git a/src/main/java/nlib/cmm/fileupload/FileUploadController.java b/src/main/java/nlib/cmm/fileupload/FileUploadController.java index f90e20a1..af2d3b6a 100644 --- a/src/main/java/nlib/cmm/fileupload/FileUploadController.java +++ b/src/main/java/nlib/cmm/fileupload/FileUploadController.java @@ -1,327 +1,322 @@ -package nlib.cmm.fileupload; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.annotation.Secured; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; -import org.springframework.web.servlet.ModelAndView; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import nlib.bbs.service.AttachFileService; -import nlib.bbs.service.AttachFileVO; -import nlib.cmm.NlibCommonController; -import nlib.cmm.exception.ErrorMessage; -import nlib.cmm.service.NlibProperty; -import nlib.util.FileUtil; -import nlib.util.StringUtil; -import nlib.util.UUID; - -/** - *
- * @Class Name : FileUploadController.java
- * 
- * @Description : 파일 업로드를 처리하는 컨트롤러  
- * 
- * 
- * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
- *
- * 
- * - * @ ------------ -------- --------------------------- - * @ 수정일 수정자 수정내용 - * @ ------------ -------- --------------------------- - * @ 2021. 7. 15. KNKIM 최초 생성 - * - * - * @author 이씨플라자 * DIGITALSHIP KNKIM - * @since 2021. 7. 15. - * @version 1.0 - * - */ -@Controller -public class FileUploadController extends NlibCommonController { - - private static final Logger log = LoggerFactory.getLogger(FileUploadController.class); - - - /** - * 첨부파일이 저장되는 최상위 위치 - */ - private String FILEUPLOAD_BASE_PATH = NlibProperty.getProperty("fileupload.base.path"); - - /** - * 첨부파일 임시 저장 위치 - */ - private String FILEUPLOAD_TEMP_SUBPATH = NlibProperty.getProperty("fileupload.temp.subpath"); - - /** - * 경로 부적합 오류 메시지 - */ - private static final String MSG_NOT_VALID_FILE_PATH = "파일 경로가 부적합합니다. 관리자에게 문의하여 주시기 바랍니다."; - - - @Resource(name="attachFileService") - private AttachFileService attachFileService; - - /** - * 파일업로드(Ajax) 처리 - * - 화면 : Javascript Free Open Framework DropZone 사용 - * - * @param multiRequest - * @param subPathKey - * @param response : 파일정보를 담은 JSON String - * @return - */ - @Secured("ROLE_USER") - @ResponseBody - @RequestMapping(value="/fileupload/uploadFilesAjax.do", produces="application/json") - public ResponseEntity uploadFilesAjax( - final MultipartHttpServletRequest multiRequest - , @RequestParam(name="subPathKey") String subPathKey - , HttpServletResponse response) { - - String message = null; - - // TODO : 권한 체크 추가할 것 - List result = new ArrayList(); - - try { - - // 최상위 위치 - if(FileUtil.isEmpty(FILEUPLOAD_BASE_PATH)) { - message = "첨부파일이 저장되는 최상위 위치정보값이 정확하지 않습니다."; - log.error(message + " : FILEUPLOAD_BASE_PATH 값 부재"); - ErrorMessage errorMessage = new ErrorMessage("ERROR", message); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } - - // 서브 위치 - if(StringUtil.isEmpty(subPathKey)) { - message = "첨부파일이 저장되는 하위 위치정보값이 정확하지 않습니다."; - log.error(message + " : 매개변수 subPathKey 값 부재"); - ErrorMessage errorMessage = new ErrorMessage("ERROR", message); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } - String subPath = NlibProperty.getProperty(subPathKey); - if(StringUtil.isEmpty(subPath)) { - message = "첨부파일이 저장되는 하위 위치정보값을 찾을 수 없습니다."; - log.error(message + " : 매개변수 subPathKey의 속성값 부재"); - ErrorMessage errorMessage = new ErrorMessage("ERROR", message); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } - - // 위치정보 적합 확인 - if(FileUtil.isNotValid(FILEUPLOAD_BASE_PATH + subPath)) { - message = MSG_NOT_VALID_FILE_PATH; - log.error(message + " : " + (FILEUPLOAD_BASE_PATH + subPath)); - ErrorMessage errorMessage = new ErrorMessage("ERROR", message); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } - - // 파일 업로드 처리 - final Map files = multiRequest.getFileMap(); - log.debug("uploadFilesAjax : 진입 > 파일수 = " + (files == null ? " files null " : files.size())); - - File dir = new File(FILEUPLOAD_BASE_PATH + subPath); - if(!dir.exists() || !dir.isDirectory()) dir.mkdirs(); - - if(!files.isEmpty()) { - - Iterator> itr = files.entrySet().iterator(); - MultipartFile file; - String filePath = ""; - AttachFileVO atvo; - - // 허용 파일 확장자 - String acceptableExtList = StringUtil.getString(NlibProperty.getString("fileupload.acceptable.ext"), null); - if(acceptableExtList != null) acceptableExtList = "," + acceptableExtList.trim().toLowerCase(); - boolean acceptableExt = false; - - // 최대 파일크기 - int maxFileSize = NlibProperty.getInt("fileupload.max.mbsize", 10) * 1024 * 1024; - - // 최대 개수 - int maxFileCount = NlibProperty.getInt("fileupload.max.files", 5); - - while (itr.hasNext()) { - - if(result.size() >= maxFileCount) { - log.error("최대 개수를 초과하여 이후 파일은 처리되지 않습니다."); - break; - } - - acceptableExt = false; - - Entry entry = itr.next(); - - file = entry.getValue(); - String orignlFileNm = file.getOriginalFilename(); - - //-------------------------------------- - // 원 파일명이 없는 경우 처리 SKIP - // (첨부가 되지 않은 input file type) - //-------------------------------------- - if (orignlFileNm == null || "".equals(orignlFileNm)) { - continue; - } - - // 파일 확장자 - int index = orignlFileNm.lastIndexOf("."); - String fileExt = (index < 1 ? "" : orignlFileNm.substring(index + 1)).toLowerCase(); - - if(acceptableExtList != null) { - - if(acceptableExtList.contains("." + fileExt)) { - acceptableExt = true; - } else if(acceptableExtList.contains("image/*")) { - acceptableExt = ".jpg,.jpe,.jpge,.bmp,.gif,.png".contains("." + fileExt); - } - } - if(!acceptableExt) { - log.error("허용되지 않는 파일 확장자 입니다. : " + orignlFileNm); - continue; - } - - int size = (int)file.getSize(); - if(size > maxFileSize) { - log.error("첨부파일이 허용 크기를 초과합니다. : " + orignlFileNm); - continue; - } - - String streFileNm = UUID.getNewStreFileNm(); - - filePath = FILEUPLOAD_BASE_PATH + subPath + "/" + streFileNm; - log.debug("FILE UPLOAD : filePath=" + filePath); - - file.transferTo(new File(FileUtil.filePathBlackList(filePath))); - - atvo = new AttachFileVO(); - atvo.setSubPathKey(subPathKey); - //atvo.setAttachFileId(attachFileId); // 실제 DB 저장 처리될때 설정함 - atvo.setStreFileNm(streFileNm); - atvo.setOrignlFileNm(orignlFileNm); - atvo.setFileExt(fileExt); - atvo.setFileSize(size); - - result.add(atvo); - } - } - } catch(SecurityException e) { - log.error("uploadFilesAjax SecurityException : " + e.toString()); - ErrorMessage errorMessage = new ErrorMessage("ERROR", "처리에 실패하였습니다(1)"); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } catch(IllegalStateException e) { - log.error("uploadFilesAjax IllegalStateException : " + e.toString()); - ErrorMessage errorMessage = new ErrorMessage("ERROR", "처리에 실패하였습니다(1)"); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } catch(IOException e) { - log.error("uploadFilesAjax IOException : " + e.toString()); - ErrorMessage errorMessage = new ErrorMessage("ERROR", "처리에 실패하였습니다(1)"); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } - - // Convert Json - ObjectMapper mapper = new ObjectMapper(); - String json; - try { - json = mapper.writeValueAsString(result); - log.debug("RETURN DATA > json:" + json); - } catch (JsonProcessingException e) { - json = null; - log.error("uploadFilesAjax Error : " + e.toString()); - ErrorMessage errorMessage = new ErrorMessage("ERROR", "JSON 변환 처리에 실패하였습니다 : " + e.toString()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); - } - - HttpHeaders responseHeaders = new HttpHeaders(); - ResponseEntity ret = ResponseEntity.ok().headers(responseHeaders).body(json); - return ret; - } - - /** - * 파일 다운로드를 처리한다. - * - * @param params - * @param mv - * @return - * @throws Exception - */ - @RequestMapping(value="/fileupload/downloadAttachFile.do") - public ModelAndView downloadAttachFile(HttpServletRequest request, AttachFileVO attachFileVO, ModelAndView mv) throws Exception { - - //--------------------------------- - // 권한체크 - //--------------------------------- - // 공지사항, FAQ : 모두 공개 - // QnA : 공개된 글인 경우, 모두 공개 - // QnA : 비밀글 (본인에 한정) - - attachFileVO.setMbInfoId(getMbInfoId(request)); - boolean hasAuth = attachFileService.checkAttachFileDownload(attachFileVO); - - if(!hasAuth) { - throw new Exception("잘못된 접근입니다. 파일다운로드를 할 수 있는 권한이 없습니다."); - } - - AttachFileVO retAttachFileVO = attachFileService.selectAttachFile(attachFileVO); - - if(retAttachFileVO == null || StringUtil.isEmpty(retAttachFileVO.getAttachFileId())) { - throw new Exception("잘못된 접근이므로 파일다운로드를 할 수 없습니다."); - } - - if(StringUtil.isEmpty(retAttachFileVO.getFileStrePath()) || StringUtil.isEmpty(retAttachFileVO.getStreFileNm())) { - throw new Exception("파일정보가 올바르지 않습니다."); - } - - if(StringUtil.isEmpty(retAttachFileVO.getOrignlFileNm())) { - retAttachFileVO.setOrignlFileNm("첨부파일." + retAttachFileVO.getFileExt()); - } - - if(StringUtil.isEmpty(retAttachFileVO.getOrignlFileNm())) { - retAttachFileVO.setOrignlFileNm("첨부파일." + retAttachFileVO.getFileExt()); - } - - String fullPath = retAttachFileVO.getFileStrePath() + retAttachFileVO.getStreFileNm(); - - log.debug("downloadAttachFile > fullPath = " + fullPath); - - if(FileUtil.isNotValid(fullPath)) { - throw new Exception(MSG_NOT_VALID_FILE_PATH); - } - - File file = new File(FileUtil.filePathBlackList(fullPath)); - if(!file.isFile()) { - throw new Exception("해당 파일이 존재하지 않습니다. 관리자에게 문의하여 주시기 바랍니다."); - } - - mv.setViewName("downloadView"); // dispatcher-servlet.xml내 BeanNameViewResolver 정의 - mv.addObject("downloadFile", file); - mv.addObject("fname", retAttachFileVO.getOrignlFileNm()); - - return mv; - } - -} - +package nlib.cmm.fileupload; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.servlet.ModelAndView; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import nlib.bbs.service.AttachFileService; +import nlib.bbs.service.AttachFileVO; +import nlib.cmm.NlibCommonController; +import nlib.cmm.exception.ErrorMessage; +import nlib.cmm.service.NlibProperty; +import nlib.util.FileUtil; +import nlib.util.StringUtil; +import nlib.util.UUID; + +/** + *
+ * @Class Name : FileUploadController.java
+ * 
+ * @Description : 파일 업로드를 처리하는 컨트롤러  
+ * 
+ * 
+ * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
+ *
+ * 
+ * + * @ ------------ -------- --------------------------- + * @ 수정일 수정자 수정내용 + * @ ------------ -------- --------------------------- + * @ 2021. 7. 15. KNKIM 최초 생성 + * + * + * @author 이씨플라자 * DIGITALSHIP KNKIM + * @since 2021. 7. 15. + * @version 1.0 + * + */ +@Controller +public class FileUploadController extends NlibCommonController { + + private static final Logger log = LoggerFactory.getLogger(FileUploadController.class); + + + /** + * 첨부파일이 저장되는 최상위 위치 + */ + private String FILEUPLOAD_BASE_PATH = NlibProperty.getProperty("fileupload.base.path"); + + /** + * 경로 부적합 오류 메시지 + */ + private static final String MSG_NOT_VALID_FILE_PATH = "파일 경로가 부적합합니다. 관리자에게 문의하여 주시기 바랍니다."; + + + @Resource(name="attachFileService") + private AttachFileService attachFileService; + + /** + * 파일업로드(Ajax) 처리 + * - 화면 : Javascript Free Open Framework DropZone 사용 + * + * @param multiRequest + * @param subPathKey + * @param response : 파일정보를 담은 JSON String + * @return + */ + @Secured("ROLE_USER") + @ResponseBody + @RequestMapping(value="/fileupload/uploadFilesAjax.do", produces="application/json") + public ResponseEntity uploadFilesAjax( + final MultipartHttpServletRequest multiRequest + , @RequestParam(name="subPathKey") String subPathKey + , HttpServletResponse response) { + + String message = null; + + // TODO : 권한 체크 추가할 것 + List result = new ArrayList(); + + try { + + // 최상위 위치 + if(FileUtil.isEmpty(FILEUPLOAD_BASE_PATH)) { + message = "첨부파일이 저장되는 최상위 위치정보값이 정확하지 않습니다."; + log.error(message + " : FILEUPLOAD_BASE_PATH 값 부재"); + ErrorMessage errorMessage = new ErrorMessage("ERROR", message); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + + // 서브 위치 + if(StringUtil.isEmpty(subPathKey)) { + message = "첨부파일이 저장되는 하위 위치정보값이 정확하지 않습니다."; + log.error(message + " : 매개변수 subPathKey 값 부재"); + ErrorMessage errorMessage = new ErrorMessage("ERROR", message); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + String subPath = NlibProperty.getProperty(subPathKey); + if(StringUtil.isEmpty(subPath)) { + message = "첨부파일이 저장되는 하위 위치정보값을 찾을 수 없습니다."; + log.error(message + " : 매개변수 subPathKey의 속성값 부재"); + ErrorMessage errorMessage = new ErrorMessage("ERROR", message); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + + // 위치정보 적합 확인 + if(FileUtil.isNotValid(FILEUPLOAD_BASE_PATH + subPath)) { + message = MSG_NOT_VALID_FILE_PATH; + log.error(message + " : " + (FILEUPLOAD_BASE_PATH + subPath)); + ErrorMessage errorMessage = new ErrorMessage("ERROR", message); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + + // 파일 업로드 처리 + final Map files = multiRequest.getFileMap(); + log.debug("uploadFilesAjax : 진입 > 파일수 = " + (files == null ? " files null " : files.size())); + + File dir = new File(FILEUPLOAD_BASE_PATH + subPath); + if(!dir.exists() || !dir.isDirectory()) dir.mkdirs(); + + if(!files.isEmpty()) { + + Iterator> itr = files.entrySet().iterator(); + MultipartFile file; + String filePath = ""; + AttachFileVO atvo; + + // 허용 파일 확장자 + String acceptableExtList = StringUtil.getString(NlibProperty.getString("fileupload.acceptable.ext"), null); + if(acceptableExtList != null) acceptableExtList = "," + acceptableExtList.trim().toLowerCase(); + boolean acceptableExt = false; + + // 최대 파일크기 + int maxFileSize = NlibProperty.getInt("fileupload.max.mbsize", 10) * 1024 * 1024; + + // 최대 개수 + int maxFileCount = NlibProperty.getInt("fileupload.max.files", 5); + + while (itr.hasNext()) { + + if(result.size() >= maxFileCount) { + log.error("최대 개수를 초과하여 이후 파일은 처리되지 않습니다."); + break; + } + + acceptableExt = false; + + Entry entry = itr.next(); + + file = entry.getValue(); + String orignlFileNm = file.getOriginalFilename(); + + //-------------------------------------- + // 원 파일명이 없는 경우 처리 SKIP + // (첨부가 되지 않은 input file type) + //-------------------------------------- + if (orignlFileNm == null || "".equals(orignlFileNm)) { + continue; + } + + // 파일 확장자 + int index = orignlFileNm.lastIndexOf("."); + String fileExt = (index < 1 ? "" : orignlFileNm.substring(index + 1)).toLowerCase(); + + if(acceptableExtList != null) { + + if(acceptableExtList.contains("." + fileExt)) { + acceptableExt = true; + } else if(acceptableExtList.contains("image/*")) { + acceptableExt = ".jpg,.jpe,.jpge,.bmp,.gif,.png".contains("." + fileExt); + } + } + if(!acceptableExt) { + log.error("허용되지 않는 파일 확장자 입니다. : " + orignlFileNm); + continue; + } + + int size = (int)file.getSize(); + if(size > maxFileSize) { + log.error("첨부파일이 허용 크기를 초과합니다. : " + orignlFileNm); + continue; + } + + String streFileNm = UUID.getNewStreFileNm(); + + filePath = FILEUPLOAD_BASE_PATH + subPath + "/" + streFileNm; + log.debug("FILE UPLOAD : filePath=" + filePath); + + file.transferTo(new File(FileUtil.filePathBlackList(filePath))); + + atvo = new AttachFileVO(); + atvo.setSubPathKey(subPathKey); + //atvo.setAttachFileId(attachFileId); // 실제 DB 저장 처리될때 설정함 + atvo.setStreFileNm(streFileNm); + atvo.setOrignlFileNm(orignlFileNm); + atvo.setFileExt(fileExt); + atvo.setFileSize(size); + + result.add(atvo); + } + } + } catch(SecurityException e) { + log.error("uploadFilesAjax SecurityException : " + e.toString()); + ErrorMessage errorMessage = new ErrorMessage("ERROR", "처리에 실패하였습니다(1)"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } catch(IllegalStateException e) { + log.error("uploadFilesAjax IllegalStateException : " + e.toString()); + ErrorMessage errorMessage = new ErrorMessage("ERROR", "처리에 실패하였습니다(1)"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } catch(IOException e) { + log.error("uploadFilesAjax IOException : " + e.toString()); + ErrorMessage errorMessage = new ErrorMessage("ERROR", "처리에 실패하였습니다(1)"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + + // Convert Json + ObjectMapper mapper = new ObjectMapper(); + String json; + try { + json = mapper.writeValueAsString(result); + log.debug("RETURN DATA > json:" + json); + } catch (JsonProcessingException e) { + json = null; + log.error("uploadFilesAjax Error : " + e.toString()); + ErrorMessage errorMessage = new ErrorMessage("ERROR", "JSON 변환 처리에 실패하였습니다 : " + e.toString()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage); + } + + HttpHeaders responseHeaders = new HttpHeaders(); + ResponseEntity ret = ResponseEntity.ok().headers(responseHeaders).body(json); + return ret; + } + + /** + * 파일 다운로드를 처리한다. + * + * @param params + * @param mv + * @return + * @throws Exception + */ + @RequestMapping(value="/fileupload/downloadAttachFile.do") + public ModelAndView downloadAttachFile(HttpServletRequest request, AttachFileVO attachFileVO, ModelAndView mv) throws Exception { + + //--------------------------------- + // 권한체크 + //--------------------------------- + // 공지사항, FAQ : 모두 공개 + // QnA : 공개된 글인 경우, 모두 공개 + // QnA : 비밀글 (본인에 한정) + + attachFileVO.setMbInfoId(getMbInfoId(request)); + boolean hasAuth = attachFileService.checkAttachFileDownload(attachFileVO); + + if(!hasAuth) { + throw new Exception("잘못된 접근입니다. 파일다운로드를 할 수 있는 권한이 없습니다."); + } + + AttachFileVO retAttachFileVO = attachFileService.selectAttachFile(attachFileVO); + + if(retAttachFileVO == null || StringUtil.isEmpty(retAttachFileVO.getAttachFileId())) { + throw new Exception("잘못된 접근이므로 파일다운로드를 할 수 없습니다."); + } + + if(StringUtil.isEmpty(retAttachFileVO.getFileStrePath()) || StringUtil.isEmpty(retAttachFileVO.getStreFileNm())) { + throw new Exception("파일정보가 올바르지 않습니다."); + } + + if(StringUtil.isEmpty(retAttachFileVO.getOrignlFileNm())) { + retAttachFileVO.setOrignlFileNm("첨부파일." + retAttachFileVO.getFileExt()); + } + + if(StringUtil.isEmpty(retAttachFileVO.getOrignlFileNm())) { + retAttachFileVO.setOrignlFileNm("첨부파일." + retAttachFileVO.getFileExt()); + } + + String fullPath = retAttachFileVO.getFileStrePath() + retAttachFileVO.getStreFileNm(); + + log.debug("downloadAttachFile > fullPath = " + fullPath); + + if(FileUtil.isNotValid(fullPath)) { + throw new Exception(MSG_NOT_VALID_FILE_PATH); + } + + File file = new File(FileUtil.filePathBlackList(fullPath)); + if(!file.isFile()) { + throw new Exception("해당 파일이 존재하지 않습니다. 관리자에게 문의하여 주시기 바랍니다."); + } + + mv.setViewName("downloadView"); // dispatcher-servlet.xml내 BeanNameViewResolver 정의 + mv.addObject("downloadFile", file); + mv.addObject("fname", retAttachFileVO.getOrignlFileNm()); + + return mv; + } + +} + diff --git a/src/main/java/nlib/util/StringUtil.java b/src/main/java/nlib/util/StringUtil.java index bc84b24d..24959c4d 100644 --- a/src/main/java/nlib/util/StringUtil.java +++ b/src/main/java/nlib/util/StringUtil.java @@ -13,6 +13,8 @@ import java.util.Base64.Encoder; import java.util.Locale; import org.apache.commons.lang3.StringUtils; +import org.owasp.html.HtmlPolicyBuilder; +import org.owasp.html.PolicyFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -383,6 +385,33 @@ public class StringUtil extends StringUtils { return value; } + /** + * XSS 등의 공격으로 부터 보안성 유지를 위해서 HTML 허용된 태그와 속성만으로 HTML을 재구성하여 리턴한다. + * + * @param html + * @return + */ + private static String sanitizeHtml(String html) { + + PolicyFactory policy = new HtmlPolicyBuilder() + .allowAttributes("src", "align", "title").onElements("img") + .allowAttributes("href", "title").onElements("a") + .allowAttributes("class", "height", "width", "style").globally() + .allowUrlProtocols("http","https","mailto","tel") + .allowElements( + "a", "label", + "h1", "h2", "h3", "h4", "h5", "h6", + "p", "i", "b", "u", "strong", "em", "small", "big", "pre", "code", + "cite", "samp", "sub", "sup", "strike", "center", "blockquote", + "hr", "br", "col", "font", "span", "div", "img", + "ul", "ol", "li", "dd", "dt", "dl", "tbody", "thead", "tfoot", + "table", "td", "th", "tr", "colgroup", "fieldset", "legend" + ) + .toFactory(); + + return policy.sanitize(html); + } + // Tag 화이트 리스트 ( 허용할 태그 등록 ) static String[] whiteListTag = { "

","

","
" }; diff --git a/src/main/resources/nlib/nlib.properties b/src/main/resources/nlib/nlib.properties index 0c3100d9..4dd5e266 100644 --- a/src/main/resources/nlib/nlib.properties +++ b/src/main/resources/nlib/nlib.properties @@ -108,6 +108,9 @@ fileupload.base.path = /nfs_nas_dev/uac/nlib # \uc6b4\uc601\uc11c\ubc84 #fileupload.base.path = /nfs_nas/uac/nlib +# \ubb3b\uace0\ub2f5\ud558\uae30 \uac8c\uc2dc\ud310 \uc11c\ube0c\uc704\uce58 : \ucca8\ubd80\ud30c\uc77c \ucd5c\uc0c1\uc704 \uc704\uce58 \uc774\ud558\uc758 \uc704\uce58 \uc815\ubcf4\ub97c \uc124\uc815 +fileupload.bbs.qna.subpath = /qna + # \ucd5c\ub300 \ud30c\uc77c \ud06c\uae30 (MB) fileupload.max.mbsize = 10 # \ucd5c\ub300 \ud30c\uc77c \uac1c\uc218 @@ -116,12 +119,6 @@ fileupload.max.files = 5 fileupload.acceptable.ext = .jpg,.jpe,.jpge,.bmp,.gif,.png,.hwp,.hwpx,.pdf,.xls,.xlsx,.ppt,.pptx,.doc,.docx,.txt -# \uc784\uc2dc \uc800\uc7a5 \uc704\uce58 -fileupload.temp.subpath = /temp - -# \ubb3b\uace0\ub2f5\ud558\uae30 \uac8c\uc2dc\ud310 \uc11c\ube0c\uc704\uce58 : \ucca8\ubd80\ud30c\uc77c \ucd5c\uc0c1\uc704 \uc704\uce58 \uc774\ud558\uc758 \uc704\uce58 \uc815\ubcf4\ub97c \uc124\uc815 -fileupload.bbs.qna.subpath = /qna - #---------------------------------------- # \uc774\uba54\uc77c \ud15c\ud50c\ub9bf \uacbd\ub85c #----------------------------------------