ASP.NET (C#) 예제

주의

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

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

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

주의

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

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

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;

// Absolute path to WEB ROOT
private const string WEB_ROOT_ABS_PATH = @"C:\workspace\SynapEditor_C#\SynapEditor\wwwroot";

// Where uploaded files go
private readonly string UPLOAD_DIR_ABS_PATH = Path.Combine(WEB_ROOT_ABS_PATH, "uploads");

[HttpPost("api/uploadFile")]
public IActionResult UploadFile(IFormFile file)
{
    string uploadFileAbsPath = SaveFileToDisk(file).Result;
    string uploadFileRelPath = Path.GetRelativePath(UPLOAD_DIR_ABS_PATH, uploadFileAbsPath);
    return Ok(new { uploadPath = uploadFileRelPath });
}

private async Task<string> SaveFileToDisk(IFormFile file)
{
    string extension         = Path.GetExtension(file.FileName);
    string uploadFileName    = $"{Guid.NewGuid()}{extension}";
    string uploadFileAbsPath = Path.Combine(UPLOAD_DIR_ABS_PATH, uploadFileName);

    using (var fileStream = new FileStream(uploadFileAbsPath, FileMode.Create))
    {
        await file.CopyToAsync(fileStream);
    }
    return uploadFileAbsPath;
}

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

주의

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

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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

private const string WEB_ROOT_ABS_PATH = @"C:\workspace\SynapEditor_C#\SynapEditor\wwwroot";

// Temporary workspace for decompressing converted documents
private readonly string WORK_DIR_ABS_PATH   = Path.Combine(WEB_ROOT_ABS_PATH, "works");
private readonly string UPLOAD_DIR_ABS_PATH = Path.Combine(WEB_ROOT_ABS_PATH, "uploads");

[HttpPost("api/importDoc")]
public async Task<IActionResult> ImportDoc(IFormFile file)
{
    // 1. Save the document to disk
    string uploadFileAbsPath = SaveFileToDisk(file).Result;

    // 2. Convert it to model data
    string importAbsPath = ExecuteConverter(uploadFileAbsPath).Result;
    string importRelPath = Path.GetRelativePath(WEB_ROOT_ABS_PATH, importAbsPath);

    // 3. Read the model data
    // From v2.3.0 the filename is "document.pb" (was "document.word.pb")
    string pbFileAbsPath = Path.Combine(importAbsPath, "document.pb");
    int[] serializedData = ReadPbData(pbFileAbsPath);

    // 4. Return
    return Ok(new { serializedData, importPath = importRelPath });
}

private async Task<string> SaveFileToDisk(IFormFile file)
{
    string extension         = Path.GetExtension(file.FileName);
    string uploadFileName    = $"{Guid.NewGuid()}{extension}";
    string uploadFileAbsPath = Path.Combine(UPLOAD_DIR_ABS_PATH, uploadFileName);

    using (var fileStream = new FileStream(uploadFileAbsPath, FileMode.Create))
    {
        await file.CopyToAsync(fileStream);
    }
    return uploadFileAbsPath;
}

private async Task<string> ExecuteConverter(string docFileAbsPath)
{
    const string CONVERTER_ABS_PATH  = @"C:\workspace\SynapEditor_C#\sedocConverter.exe";
    const string FONTS_DIR_ABS_PATH  = @"C:\workspace\SynapEditor_C#\fonts";
    const string TEMP_DIR_ABS_PATH   = @"C:\workspace\SynapEditor_C#\tmp";

    string outputAbsPath = Path.Combine(WORK_DIR_ABS_PATH, Path.GetFileName(docFileAbsPath));

    var startInfo = new ProcessStartInfo
    {
        FileName        = CONVERTER_ABS_PATH,
        Arguments       = $"-f {FONTS_DIR_ABS_PATH} {docFileAbsPath} {outputAbsPath} {TEMP_DIR_ABS_PATH}",
        CreateNoWindow  = false,
        UseShellExecute = false,
        WindowStyle     = ProcessWindowStyle.Hidden
    };

    using (Process exeProcess = Process.Start(startInfo))
    {
        // Treat conversions taking more than 30 seconds as a failure
        if (exeProcess.WaitForExit(30000)) return outputAbsPath;
        exeProcess.Kill();
        return "";
    }
}

/*
 * Serialize document model data so the Synap Editor front-end module can consume it.
 * (Important: import will fail if the body of this function is modified.)
 */
private static int[] ReadPbData(string pbFilePath)
{
    var pbData = new List<int>();
    using (var fileStream = new FileStream(pbFilePath, FileMode.Open, FileAccess.Read))
    {
        fileStream.Seek(16, SeekOrigin.Begin);
        using (var inflater = new InflaterInputStream(fileStream))
        {
            byte[] buffer = new byte[1024];
            int count;
            while ((count = inflater.Read(buffer, 0, buffer.Length)) > 0)
            {
                for (int i = 0; i < count; i++) pbData.Add(buffer[i] & 0xFF);
            }
        }
    }
    return pbData.ToArray();
}

관련 정보