자바 스프링 프레임워크 예제

주의

서버 보안을 위해 파일 업로드 및 문서 임포트 처리를 구현할 때 아래 가이드를 참고합니다.

소프트웨어 보안 취약점 진단 가이드

환경별 통합 가이드

  • Java Spring Framework 예제
  • Java Servlet 예제
  • ASP.NET (C#) 예제
  • ASP (Classic) 예제
  • PHP 예제
  • PHP4 예제
  • Django 예제
  • Ruby on Rails 예제
  • WordPress 플러그인 예제

1. 이미지 / Video / 파일 업로드

주의

아래 샘플 코드의 파일 업로드 부분은 의도적으로 최소화되어 있으며, 적절한 보안 처리가 적용되어 있지 않습니다.

파일 업로드는 프로젝트에 이미 구현된 기능을 사용하고, 아래 코드는 에디터 연동 부분만 참고합니다.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;

@Controller
public class UploadController {
    static String IMAGE_UPLOAD_DIR_REL_PATH = "uploads";

    @RequestMapping(value = "/uploadFile.do", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> uploadFile(HttpServletRequest request,
                                          @RequestParam("file") MultipartFile file) throws IOException {
        String ROOT_ABS_PATH       = request.getSession().getServletContext().getRealPath("");
        String UPLOAD_DIR_ABS_PATH = ROOT_ABS_PATH + File.separator + IMAGE_UPLOAD_DIR_REL_PATH;
        makeDirectory(UPLOAD_DIR_ABS_PATH);

        String fileName    = file.getOriginalFilename();
        String contentType = file.getContentType();
        String ext = "";
        if (contentType != null) {
            ext = "." + contentType.substring(contentType.lastIndexOf('/') + 1);
        } else if (fileName.lastIndexOf('.') > 0) {
            ext = fileName.substring(fileName.lastIndexOf('.'));
        }
        if (ext.contains(".jpeg")) ext = ".jpg";   // normalize jpeg → jpg

        String saveFileName    = UUID.randomUUID().toString() + ext;
        String saveFileAbsPath = UPLOAD_DIR_ABS_PATH + File.separator + saveFileName;
        writeFile(saveFileAbsPath, file.getBytes());

        Map<String, Object> map = new HashMap<>();
        map.put("uploadPath", "uploads/" + saveFileName);   // browser-accessible path
        return map;
    }

    private static void writeFile(String path, byte[] bytes) throws IOException {
        try (OutputStream os = new FileOutputStream(path)) { os.write(bytes); }
    }
    private static void makeDirectory(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.exists()) dir.mkdir();
    }
}

2. HWP / Word / 엑셀 문서 임포트

주의

아래 샘플 코드의 파일 업로드 부분은 의도적으로 최소화되어 있으며, 적절한 보안 처리가 적용되어 있지 않습니다.

파일 업로드는 프로젝트에 이미 구현된 기능을 사용하고, 아래 코드는 에디터 연동 부분만 참고합니다.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;
import java.util.zip.InflaterInputStream;

@Controller
public class ImportController {
    static String DOC_UPLOAD_DIR_REL_PATH = "uploads" + File.separator + "docs";
    static String OUTPUT_DIR_REL_PATH     = "uploads" + File.separator + "output";

    @RequestMapping(value = "/importDoc.do", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> importDoc(HttpServletRequest request,
                                         @RequestParam("file") MultipartFile importFile) throws IOException {
        String ROOT_ABS_PATH       = request.getSession().getServletContext().getRealPath("");
        String UPLOAD_DIR_ABS_PATH = ROOT_ABS_PATH + File.separator + DOC_UPLOAD_DIR_REL_PATH;
        makeDirectory(UPLOAD_DIR_ABS_PATH);

        String fileName         = importFile.getOriginalFilename();
        String inputFileAbsPath = UPLOAD_DIR_ABS_PATH + File.separator + fileName;
        writeFile(inputFileAbsPath, importFile.getBytes());

        // Per-file conversion output directory
        Calendar cal = Calendar.getInstance();
        String yearMonth = String.format("%04d%02d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1);
        String uuid             = UUID.randomUUID().toString();
        String worksDirAbsPath  = ROOT_ABS_PATH + File.separator + OUTPUT_DIR_REL_PATH
                                + File.separator + yearMonth + File.separator + uuid;
        makeDirectory(worksDirAbsPath);

        // Convert
        executeConverter(inputFileAbsPath, worksDirAbsPath);
        deleteFile(inputFileAbsPath);

        // Read and serialize the .pb output
        // From v2.3.0 the filename is "document.pb" (was "document.word.pb").
        String pbAbsPath = worksDirAbsPath + File.separator + "document.pb";
        Integer[] serializedData = serializePbData(pbAbsPath);
        deleteFile(pbAbsPath);

        Map<String, Object> map = new HashMap<>();
        map.put("serializedData", serializedData);
        map.put("importPath", "uploads/output/" + yearMonth + "/" + uuid);   // browser-accessible
        return map;
    }

    /** Invoke the sedocConverter process. */
    public static int executeConverter(String inputFilePath, String outputFilePath) {
        String SEDOC_CONVERTER_DIR_ABS_PATH = "absolute/path/to/converter/dir";
        String FONT_DIR_ABS_PATH            = SEDOC_CONVERTER_DIR_ABS_PATH + File.separator + "fonts";
        String TEMP_DIR_ABS_PATH            = SEDOC_CONVERTER_DIR_ABS_PATH + File.separator + "temp";
        String SEDOC_CONVERTER_ABS_PATH     = SEDOC_CONVERTER_DIR_ABS_PATH + File.separator + "sedocConverter_exe";
        // Windows: "sedocConverter.exe"

        makeDirectory(TEMP_DIR_ABS_PATH);
        makeDirectory(FONT_DIR_ABS_PATH);

        String[] cmd = { SEDOC_CONVERTER_ABS_PATH, "-f", FONT_DIR_ABS_PATH,
                         inputFilePath, outputFilePath, TEMP_DIR_ABS_PATH };
        try {
            Timer t = new Timer();
            ProcessBuilder builder = new ProcessBuilder(cmd);
            builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
            builder.redirectError(ProcessBuilder.Redirect.INHERIT);
            Process proc = builder.start();

            TimerTask killer = new TimeoutProcessKiller(proc);
            t.schedule(killer, 20000);   // 20 s timeout
            int exitValue = proc.waitFor();
            killer.cancel();
            return exitValue;
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }

    /** Inflate the .pb file and serialize to int[]. */
    public static Integer[] serializePbData(String pbFilePath) throws IOException {
        List<Integer> serialized = new ArrayList<>();
        try (FileInputStream fis = new FileInputStream(pbFilePath)) {
            fis.skip(16);
            try (InflaterInputStream ifis = new InflaterInputStream(fis)) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = ifis.read(buffer)) != -1) {
                    for (int i = 0; i < len; i++) serialized.add(buffer[i] & 0xFF);
                }
            }
        }
        return serialized.toArray(new Integer[0]);
    }

    private static void writeFile(String path, byte[] bytes) throws IOException {
        try (OutputStream os = new FileOutputStream(path)) { os.write(bytes); }
    }
    private static void deleteFile(String path) {
        File f = new File(path);
        if (f.exists()) f.delete();
    }
    private static void makeDirectory(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.exists()) dir.mkdir();
    }

    private static class TimeoutProcessKiller extends TimerTask {
        private final Process p;
        public TimeoutProcessKiller(Process p) { this.p = p; }
        @Override public void run() { p.destroy(); }
    }
}

3. WMF를 PNG로 변환

3.0.2409 버전부터 지원, 2.18.2409 버전부터 지원합니다.

HWP나 MS Word에서 붙여넣은 내용에 포함된 WMF 파일은 PNG로 변환됩니다.

주의

아래 샘플 코드의 파일 업로드 부분은 의도적으로 최소화되어 있으며, 적절한 보안 처리가 적용되어 있지 않습니다.

파일 업로드는 프로젝트에 이미 구현된 기능을 사용하고, 아래 코드는 에디터 연동 부분만 참고합니다.

import org.springframework.http.*;
import org.springframework.stereotype.Controller;
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.*;
import java.util.*;

@Controller
public class WmfToPngController {
    static String IMAGE_UPLOAD_DIR_REL_PATH = "uploads" + File.separator + "image";
    static String OUTPUT_DIR_REL_PATH       = "uploads" + File.separator + "output";

    @PostMapping(value = "/wmfToPng", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ResponseBody
    public ResponseEntity<Map<String, String>> convertWmfToPng(HttpServletRequest request,
                                                              @RequestParam("file") MultipartFile wmfImageFile) throws IOException {
        String ROOT_ABS_PATH       = request.getSession().getServletContext().getRealPath("");
        String UPLOAD_DIR_ABS_PATH = ROOT_ABS_PATH + File.separator + IMAGE_UPLOAD_DIR_REL_PATH;
        makeDirectory(UPLOAD_DIR_ABS_PATH);

        String fileName         = wmfImageFile.getOriginalFilename();
        String inputFileAbsPath = UPLOAD_DIR_ABS_PATH + File.separator + fileName;
        writeFile(inputFileAbsPath, wmfImageFile.getBytes());

        Calendar cal = Calendar.getInstance();
        String yearMonth = String.format("%04d%02d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1);
        String uuid           = UUID.randomUUID().toString();
        String worksDirAbsPath = ROOT_ABS_PATH + OUTPUT_DIR_REL_PATH
                              + File.separator + yearMonth + File.separator + uuid;
        makeDirectory(worksDirAbsPath);

        executeConverter(inputFileAbsPath, worksDirAbsPath + File.separator + "output.png");
        deleteFile(inputFileAbsPath);

        String downloadUrl = request.getScheme() + "://" + request.getServerName() + ":"
                          + request.getServerPort() + "/download?fileName=" + uuid + "/output.png";

        Map<String, String> response = new HashMap<>();
        response.put("uploadPath", downloadUrl);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return ResponseEntity.status(HttpStatus.OK).headers(headers).body(response);
    }

    @GetMapping("/download")
    public void downloadFile(@RequestParam("fileName") String fileName,
                             HttpServletRequest request, HttpServletResponse response) throws IOException {
        String ROOT_ABS_PATH    = request.getSession().getServletContext().getRealPath("");
        Calendar cal = Calendar.getInstance();
        String yearMonth = String.format("%04d%02d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1);
        String downloadDirPath  = ROOT_ABS_PATH + OUTPUT_DIR_REL_PATH + File.separator + yearMonth + File.separator;

        File file = new File(downloadDirPath + File.separator + fileName);
        if (!file.exists()) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; }

        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
        response.setContentLengthLong(file.length());

        try (InputStream is = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {
            byte[] buffer = new byte[8192];
            int n;
            while ((n = is.read(buffer)) != -1) os.write(buffer, 0, n);
        }
    }

    public static int executeConverter(String inputFilePath, String outputFilePath) {
        String SEDOC_CONVERTER_DIR_ABS_PATH = "absolute/path/to/converter/dir";
        String TEMP_DIR_ABS_PATH            = SEDOC_CONVERTER_DIR_ABS_PATH + File.separator + "temp";
        String SEDOC_CONVERTER_ABS_PATH     = SEDOC_CONVERTER_DIR_ABS_PATH + File.separator + "sedocConverter_exe";
        makeDirectory(TEMP_DIR_ABS_PATH);
        String[] cmd = { SEDOC_CONVERTER_ABS_PATH, inputFilePath, outputFilePath, TEMP_DIR_ABS_PATH };
        try {
            Timer t = new Timer();
            ProcessBuilder builder = new ProcessBuilder(cmd);
            builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
            builder.redirectError(ProcessBuilder.Redirect.INHERIT);
            Process proc = builder.start();
            TimerTask killer = new TimeoutProcessKiller(proc);
            t.schedule(killer, 20000);
            int exitValue = proc.waitFor();
            killer.cancel();
            return exitValue;
        } catch (Exception e) { e.printStackTrace(); return -1; }
    }

    private static void writeFile(String path, byte[] bytes) throws IOException {
        try (OutputStream os = new FileOutputStream(path)) { os.write(bytes); }
    }
    private static void deleteFile(String path) {
        File file = new File(path);
        if (file.exists()) file.delete();
    }
    private static void makeDirectory(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.exists()) dir.mkdirs();
    }
    private static class TimeoutProcessKiller extends TimerTask {
        private final Process p;
        public TimeoutProcessKiller(Process p) { this.p = p; }
        @Override public void run() { p.destroy(); }
    }
}

관련 정보