반응형

클릭한 곳에 사각형 생성하기

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Canvas tutorial</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script type="text/javascript">
        $(function () {
            draw();
        });

        function draw() {
            var ctx;
            var canvas = document.getElementById("canvas");
            if (canvas.getContext) {
                ctx = canvas.getContext("2d");
            }

            $(canvas).on("click", function (e) {
                if (canvas.getContext) {
                    var l = $(this).offset().left;
                    var t = $(this).offset().top;
                    var x = e.pageX - l;
                    var y = e.pageY - t;
                    var w = 15;
                    var h = 15;

                    ctx.fillStyle = "rgb(255, 187, 0)";
                    ctx.fillRect(x, y, w, h);
                }
            })
        }
    </script>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }

        canvas {
            border: 1px solid black;
        }

        #canvas {
            position: relative;
            top: 150px;
            left: 600px;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="750" height="750"></canvas>
</body>
</html>

canvas를 생성하고, 생성한 캔버스를 클릭하면 현재의 좌표를 받고 지정한 사이즈와 색상으로 사각형을 생성하는 예제입니다.

 

사각형 생성

 

 

클릭한 곳에 동그라미 생성하기

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Canvas tutorial</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script type="text/javascript">
        $(function () {
            draw();
        });

        function draw() {
            var ctx;
            var canvas = document.getElementById("canvas");
            if (canvas.getContext) {
                ctx = canvas.getContext("2d");
            }

            $(canvas).on("click", function (e) {
                if (canvas.getContext) {
                    var l = $(this).offset().left;
                    var t = $(this).offset().top;
                    var x = e.pageX - l;
                    var y = e.pageY - t;
                    var r = 5;
                    console.log("canvas click" + x + "    " + y);

                    ctx.beginPath();
                    ctx.fillStyle = "rgb(54, 138, 255)";
                    ctx.arc(x, y, r, 0, Math.PI * 2, true);
                    ctx.fill();
                }
            })
        }
    </script>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }

        canvas {
            border: 1px solid black;
        }

        #canvas {
            position: relative;
            top: 150px;
            left: 600px;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="750" height="750"></canvas>
</body>
</html>

사각형과 원리는 동일하며 생성하는 데이터각 사각형이 아닌 동그라미를 생성하고 그립니다.

 

동그라미 생성

 

반응형
반응형

Javascript 기술은 점점 발전하여 단순 JS만으로도 많은 기능을 사용할 수 있는데, 이번엔 화면을 캡처하는 방법을 알아보겠습니다.

 

요청사항 중 특정 요소를 캡처하여 이미지 데이터를 서버에 전송하고 저장을 원하였습니다.

화면 캡처 후 다운로드 기능은 사실 이미 많이 공유되고 있는데, 제가 겪었던 문제점에 대한 대응책이나 이런부분이 잘 보이질 않아 추가적으로 적어볼까합니다.

 

일단 html2canvas를 사용하여 동작시킬 예정이므로 해당 라이브러리를 추가합니다.

 

 

html2canvas를 통한 캡처 후 전송하기

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

아래는 공식사이트입니다.

https://html2canvas.hertzen.com/

 

html2canvas - Screenshots with JavaScript

Try out html2canvas Test out html2canvas by rendering the viewport from the current page. Capture

html2canvas.hertzen.com

 

이제 전송을 위해 ajax를 활용하여 전송 예제를 만들어보겠습니다.

-HTML

<section id="chart_box">
    <div class="chart">
     ...//차트에 해당하는 요소들
    </div>
</section>

 

 

-JS

$("#btn").on("click", function() {
	sreenShot($("#chart_box"));
});

function sreenShot(target) {
	if (target != null && target.length > 0) {
		var t = target[0];
		html2canvas(t).then(function(canvas) {
			var myImg = canvas.toDataURL("image/png");
			myImg = myImg.replace("data:image/png;base64,", "");

			$.ajax({
				type : "POST",
				data : {
					"imgSrc" : myImg
				},
				dataType : "text",
				url : contextPath + "/public/ImgSaveTest.do",
				success : function(data) {
					console.log(data);
				},
				error : function(a, b, c) {
					alert("error");
				}
			});
		});
	}
}

넣고 싶은 요소 $("#chart_box")를 매개변수로 집어넣고 0번째 데이터를 html2canvas 첫번째 파라미터에 처리합니다.

html2canvas는 Promise를 사용하는것으로 보이는데, 요소 처리가 완료되면 다음 이행동작을 정의합니다.

 

매개변수로 나온 canvas의 toDataURL을 통해 이미지 바이너리 데이터를 받을 수 있습니다.

거기서 앞에 붙는 데이터타입과 인코딩타입을 제거하고 서버로 전송하면 js부분은 끝입니다.

전송은 ajax를 통해 처리하였습니다.

 

 

-Server(java)

@ResponseBody
@RequestMapping(value = { "ImgSaveTest" }, method = RequestMethod.POST)
public ModelMap ImgSaveTest(@RequestParam HashMap<Object, Object> param, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
	ModelMap map = new ModelMap();
	
	String binaryData = request.getParameter("imgSrc");
	FileOutputStream stream = null;
	try{
		System.out.println("binary file   "  + binaryData);
		if(binaryData == null || binaryData.trim().equals("")) {
		    throw new Exception();
		}
		binaryData = binaryData.replaceAll("data:image/png;base64,", "");
		byte[] file = Base64.decodeBase64(binaryData);
		String fileName=  UUID.randomUUID().toString();
		
		stream = new FileOutputStream("E:/test2/"+fileName+".png");
		stream.write(file);
		stream.close();
		System.out.println("캡처 저장");
	    
	}catch(Exception e){
		e.printStackTrace();
		System.out.println("에러 발생");
	}finally{
		if(stream != null) {
			stream.close();
		}
	}
	
	map.addAttribute("resultMap", "");
	return map;
}

파라미터로 넘긴 데이터를 문자열로 전달받고 바이너리 데이터를 스트림을 통해 읽어서 파일쓰기를 통해 파일을 남깁니다.

 

 

사용예시

html Page

전송버튼을 누르고 지정한 test2 디렉토리에 저장되는지 확인해보겠습니다.

 

 

정상적으로 파일이 써진 모습을 볼 수 있습니다.

 

 

개발시 주의사항

*아래 기재한 주의사항은 개인적으로 적용과 테스트를 해보면서 겪은 문제들입니다.

 

1. html2canvas에 넣는 첫번째 요소가 jQuery 선택자인 경우 id값일지라도 배열의 인덱스가 필요합니다.

ex) $("#test")[0]

 

2. 다운받으려는 특정요소는 width, height등 요소의 영역이 정확하게 지정되어 있어야합니다.

지정되지 않은 요소를 받게되면 브라우저상에서는 잘 보일지라도 전송된 데이터는 비어있거나 잘렸거나 등등 여러가지 문제가 발생할 수 있습니다.

 

3. 다운받으려는 특정요소의 위치가 position등을 통해 억지로 맞춰진 경우 정상출력이 되지 않을 수 있습니다.

 

4. div태그 내부의 img태그등 정보를 받아오는 데이터가 외부인 경우 정상적으로 출력되지 않습니다.

다른 예시 블로그 글들을 보면 http~를 통해 이미지를 가져오고 다운로드 받는다고 되어있는데, 막상 따라해보면 흰색 화면만 받아졌습니다. 서버 내부에 존재하는 이미지를 넣고 다운로드시 정상 출력되었습니다.

 

5. 인터셉터 관련 서버 코드 문제

저같은 경우는 XSS악성 스크립트 방지를 위해 서버코드로 넘어가는 모든 파라미터를 검사하는 로직을 추가해놨습니다.

html2canvas로 읽는 데이터가 얼마 안되는 경우에는 큰 문제가 없지만 복잡한 이미지를 만들게되면 그만큼 바이너리 데이터 또한 매우 커집니다. 캡처의 바이너리만 해도 문자열 길이가 약 7만5천개정도가 되었습니다. 여기서 로직을 체크하는 함수와 문자열이 겹치면서 서버로 전송이 안되는 이슈가 있어서, 해당 파라미터만 검사를 안하거나 url에 따라 예외처리등 약간의 극복(?)이 필요했습니다.

 

6. 바이너리 데이터를 넘기다보니 더욱 복잡한 이미지를 이미지화하여 전송하게되면 데이터는 더욱 커지게 되고 tomcat의 maxPortSize를 강제로 키우거나 -1처리를 통해 무제한으로 변경해야 할 수도 있습니다.

 

7. 높이가 body보다 큰 영역을 처리할 때 잘릴 수가 있습니다. 처리하는동안 body태그의 값을 제어하여 영역과 동일하게 또는 그 보다 크게 처리하였다가 원래 사이즈로 처리하는 로직이 필요합니다. - 20201126 추가

반응형
반응형

new Date객체의 getDate()메소드를 통해 마지막 일자를 구할 수 있다.

var date = new Date(년, 월, 0);
date.getDate(); //마지막 날짜


//사용예
new Date(2020, 1, 0).getDate(); //31
new Date(2020, 2, 0).getDate(); //29
new Date(2020, 3, 0).getDate(); //31
new Date(2020, 4, 0).getDate(); //30
new Date(2020, 5, 0).getDate(); //31
new Date(2020, 6, 0).getDate(); //30

 

반응형
반응형

이미 작업이 끝난 페이지가 오류가 발생하거나 분석을 위해 찾다보면 사용한 함수 -> 함수 형태로 찾을일이 많은데,

웹에서 디버깅은 관리자도구만 잘 사용하면 빠르게 디버깅이 가능하고 위치를 찾아서 분석하고 수정을 할 수 있습니다.

 

예를 들어 함수가 JSP에도 정의되어 있고 따로 빼놓은 js에도 있고, include처리된 jsp에도 있고 그 jsp를 물고 있는 js에도 정의 되어있고... 직접 짠사람이라면 금방 알고 수정하겠지만, 처음보거나 다른사람이 보면 정의된 내용을 찾는게 여간 고역이 아닙니다. 여러사람이 개발하면서 제대로 된 개발 약속이 없다면 이런식으로 중구난방으로 막 들어가 있을텐데 관리자도구를 사용하면 해당 함수가 어디 있는지 바로바로 찾을 수 있습니다.

 

관리자 도구를 통해 함수가 적용된 페이지 찾기

1. F12버튼을 누르고 관리자도구를 열어줍니다.

 

2. 관리자도구 Console창에 해당하는 함수명을 입력합니다.

function test() {
    console.log("저를 찾았나요?");
}

라는 내용의 test함수를 찾을 경우 "test"만 입력합니다.

 

함수명만 입력

 

3. 그럼 관리자도구는 해당 함수가 존재할 경우 타입이 무엇인지 정의된 내용이 무엇인지 간단하게 알려줍니다.

이때 출력된 함수 내용을 클릭해주면 해당하는 페이지로 관리자 도구가 이동을 시켜줍니다.

 

반응형
반응형

특정 영역을 지정하여 해당 영역에 달력을 생성하고 클릭에 따라 선택 이벤트, 이전달, 다음달로 이동되는 달력 code를 작성해보겠습니다.

아래는 동작 달력입니다.

 

 

 

 

 

 

 

달력 만들기

 

 

 

달력만들기

calendar.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>달력 만들기</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
    <div id="calendarForm"></div>
</body>
</html>

 

calendar.css

* {
    margin: 0;
    padding: 0
}

.custom_calendar_table td {
    text-align: center;
}

.custom_calendar_table thead.cal_date th {
    font-size: 1.5rem;
}

.custom_calendar_table thead.cal_date th button {
    font-size: 1.5rem;
    background: none;
    border: none;
}

.custom_calendar_table thead.cal_week th {
    background-color: #288CFF;
    color: #fff;
}

.custom_calendar_table tbody td {
    cursor: pointer;
}

.custom_calendar_table tbody td:nth-child(1) {
    color: red;
}

.custom_calendar_table tbody td:nth-child(7) {
    color: #288CFF;
}

.custom_calendar_table tbody td.select_day {
    background-color: #288CFF;
    color: #fff;
}

 

calendar.js

(function () {
    calendarMaker($("#calendarForm"), new Date());
})();

var nowDate = new Date();
function calendarMaker(target, date) {
    if (date == null || date == undefined) {
        date = new Date();
    }
    nowDate = date;
    if ($(target).length > 0) {
        var year = nowDate.getFullYear();
        var month = nowDate.getMonth() + 1;
        $(target).empty().append(assembly(year, month));
    } else {
        console.error("custom_calendar Target is empty!!!");
        return;
    }

    var thisMonth = new Date(nowDate.getFullYear(), nowDate.getMonth(), 1);
    var thisLastDay = new Date(nowDate.getFullYear(), nowDate.getMonth() + 1, 0);


    var tag = "<tr>";
    var cnt = 0;
    //빈 공백 만들어주기
    for (i = 0; i < thisMonth.getDay(); i++) {
        tag += "<td></td>";
        cnt++;
    }

    //날짜 채우기
    for (i = 1; i <= thisLastDay.getDate(); i++) {
        if (cnt % 7 == 0) { tag += "<tr>"; }

        tag += "<td>" + i + "</td>";
        cnt++;
        if (cnt % 7 == 0) {
            tag += "</tr>";
        }
    }
    $(target).find("#custom_set_date").append(tag);
    calMoveEvtFn();

    function assembly(year, month) {
        var calendar_html_code =
            "<table class='custom_calendar_table'>" +
            "<colgroup>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "</colgroup>" +
            "<thead class='cal_date'>" +
            "<th><button type='button' class='prev'><</button></th>" +
            "<th colspan='5'><p><span>" + year + "</span>년 <span>" + month + "</span>월</p></th>" +
            "<th><button type='button' class='next'>></button></th>" +
            "</thead>" +
            "<thead  class='cal_week'>" +
            "<th>일</th><th>월</th><th>화</th><th>수</th><th>목</th><th>금</th><th>토</th>" +
            "</thead>" +
            "<tbody id='custom_set_date'>" +
            "</tbody>" +
            "</table>";
        return calendar_html_code;
    }

    function calMoveEvtFn() {
        //전달 클릭
        $(".custom_calendar_table").on("click", ".prev", function () {
            nowDate = new Date(nowDate.getFullYear(), nowDate.getMonth() - 1, nowDate.getDate());
            calendarMaker($(target), nowDate);
        });
        //다음날 클릭
        $(".custom_calendar_table").on("click", ".next", function () {
            nowDate = new Date(nowDate.getFullYear(), nowDate.getMonth() + 1, nowDate.getDate());
            calendarMaker($(target), nowDate);
        });
        //일자 선택 클릭
        $(".custom_calendar_table").on("click", "td", function () {
            $(".custom_calendar_table .select_day").removeClass("select_day");
            $(this).removeClass("select_day").addClass("select_day");
        });
    }
}

 

반응형
반응형

checkbox는 자주사용하는 html tag이지만 브라우저별로 약간의 사이즈 차이와 이미지가 차이가 있다보니 css를 통해 통일화를 시키고 싶지만 CSS를 통해 checkbox를 꾸미는것은 불가능하기 때문에 이미지로 대체하여 동작시키는 경우가 많다.

 

label태그를 사용하면 checkbox를 꾸밀 수 있는데 코드는 아래와 같다.

 

checkbox.html

<input type="checkbox" id="myCheck">
<label for="myCheck"></label>

id를 지정해주고 label태그는 바라볼 checkbox와 연결해준다.

 

 

css

input[type="checkbox"]+label {
    display: block;
    width: 24px;
    height: 24px;
    background: url('./images/check-off.png') no-repeat 0 0px / contain;
}

input[type='checkbox']:checked+label {
    background: url('./images/check-on.png') no-repeat 0 1px / contain;
}

input[type="checkbox"] {
    display: none;
}

+를 통해 바로 뒤에 오는 label태그를 같이 선택시킨다.

그런 다음 기본이미지("체크되지 않은 이미지")를 배경으로 처리하고 :checked속성을 추가하여 선택된 이미지("체크 이미지")를 넣는다.

 

마지막에 원래 이미지인 checkbox는 숨긴다.

 

동작정보

이미지로 대체된 기본 체크박스

변경된 체크박스

반응형
반응형

이번 포스팅에선 달력 라이브러리를 하나 다뤄볼까 합니다.

 

air - datepicker라는 녀석이고 jqeury가 필요합니다.

아래는 공식 홈페이지입니다. 다양한 예제와 사용법이 정리되어 있습니다.

http://t1m0n.name/air-datepicker/docs/

 

Air Datepicker

Datepicker's language. If string is passed, then language will be searched inDatepicker.languageobject. If object is passed, then data will be taken from this object directly. If some fields are missing, they will be taken from default localization object

t1m0n.name

 

웹 페이지를 구성하다보면 달력은 굉장이 자주 집어넣게 되는 요소인데, jquery ui의 기본 datepicker 아무래도 너무 단순하고 디자인을 잘 모르는 제가 봐도 족히 10년전쯤에 사용했을 것 같은 디자인이라 꺼려지게 되었는데, air-datepicker는 jquery ui datepicker에 비하면 굉장히 깔끔합니다.

 

색상도 많이 들어가지 않아 깔끔하고 어지간한 페이지에 조화로운 느낌이며 옵션 또한 많은 기능을 제공하고 있어서 큰 문제없이 기능을 제어하고 동작시킬 수 있고, 달력 + 시간조합도 가능하기 때문에 일자와 시분초까지 표현해야 하는 경우에도 유용하게 사용할 수 있습니다.

 

사용법을 알아보겠습니다.

 

Air-datepicker

먼저 동작을 위해 js와 css를 다운받아야합니다.

https://github.com/t1m0n/air-datepicker/tree/master/dist

 

t1m0n/air-datepicker

Cool jQuery datepicker. Contribute to t1m0n/air-datepicker development by creating an account on GitHub.

github.com

개발자님의 github로 가셔서 다운로드 받습니다.

 

귀찮으신분들은 아래의 제가올린 압축파일 받습니다.

datepicker.zip
0.07MB

 

단일 달력

<html>

<head>
    <title>datepicker example</title>
    <script src="./jquery-3.1.1.min.js"></script> <!-- 값 제어를 위해 jquery -->
    <link href="./datepicker/css/datepicker.min.css" rel="stylesheet" type="text/css" media="all">
    <!-- Air datepicker css -->
    <script src="./datepicker/js/datepicker.js"></script> <!-- Air datepicker js -->
    <script src="./datepicker/js/datepicker.ko.js"></script> <!-- 달력 한글 추가를 위해 커스텀 -->
</head>

<body>
    <div>
        단일 달력<br />
        <input type="text" id="datepicker">
    </div><br /><br /><br />
</body>
<script>
    $("#datepicker").datepicker({
    	language: 'ko'
    }); 
</script>

</html>

추가한 input text박스를 클릭하면 달력이 생성되고 원하는 날짜를 입력하면 input태그에 날짜가 들어갑니다.

태그안에 속성값을 추가하여 input text박스를 달력으로 사용하거나 스크립트에 요소를 지정해서 사용할 수 있습니다.

 

* 참고로 한국어 언어가 없을 수 있습니다. 저는 datepicker.ko.js 파일을 따로 만들어서 사용중이고, 해당 파일은 위에서 제가 제공하는 파일을 받으시면 됩니다.

 

 

 

단일 달력에 시간 선택하기

$("#datepicker").datepicker({
    language: 'ko',
    timepicker: true,
    timeFormat: "hh:ii AA"
});

아까와 같은 소스에서 이번엔 timepicker 옵션만 true처리 해주면 시간 선택이 가능해집니다.

여기에 추가적으로 시간이 표현될 표현식을 만들어주면 정상적으로 시간 선택까지 할 수 있습니다.

 

 

 

연결된 달력 만들기(날짜 제한처리)

단일 달력의 경우 그냥 일자를 선택하게 해주면 되는데, 시작일 ~ 종료일형태로 되어있는 경우

종료일이 시작일보다 과거로 가거나 시작일이 종료일보다 미래로 선택되는것은 막아야 할텐데, 옵션을 통해 막도록 설정해보겠습니다.

 

datepicker.js

datePickerSet($("#datepicker1"), $("#datepicker2"), true); //다중은 시작하는 달력 먼저, 끝달력 2번째

/*
    * 달력 생성기
    * @param sDate 파라미터만 넣으면 1개짜리 달력 생성
    * @example   datePickerSet($("#datepicker"));
    * 
    * 
    * @param sDate, 
    * @param eDate 2개 넣으면 연결달력 생성되어 서로의 날짜를 넘어가지 않음
    * @example   datePickerSet($("#datepicker1"), $("#datepicker2"));
    */
function datePickerSet(sDate, eDate, flag) {

    //시작 ~ 종료 2개 짜리 달력 datepicker	
    if (!isValidStr(sDate) && !isValidStr(eDate) && sDate.length > 0 && eDate.length > 0) {
        var sDay = sDate.val();
        var eDay = eDate.val();

        if (flag && !isValidStr(sDay) && !isValidStr(eDay)) { //처음 입력 날짜 설정, update...			
            var sdp = sDate.datepicker().data("datepicker");
            sdp.selectDate(new Date(sDay.replace(/-/g, "/")));  //익스에서는 그냥 new Date하면 -을 인식못함 replace필요

            var edp = eDate.datepicker().data("datepicker");
            edp.selectDate(new Date(eDay.replace(/-/g, "/")));  //익스에서는 그냥 new Date하면 -을 인식못함 replace필요
        }

        //시작일자 세팅하기 날짜가 없는경우엔 제한을 걸지 않음
        if (!isValidStr(eDay)) {
            sDate.datepicker({
                maxDate: new Date(eDay.replace(/-/g, "/"))
            });
        }
        sDate.datepicker({
            language: 'ko',
            autoClose: true,
            onSelect: function () {
                datePickerSet(sDate, eDate);
            }
        });

        //종료일자 세팅하기 날짜가 없는경우엔 제한을 걸지 않음
        if (!isValidStr(sDay)) {
            eDate.datepicker({
                minDate: new Date(sDay.replace(/-/g, "/"))
            });
        }
        eDate.datepicker({
            language: 'ko',
            autoClose: true,
            onSelect: function () {
                datePickerSet(sDate, eDate);
            }
        });

        //한개짜리 달력 datepicker
    } else if (!isValidStr(sDate)) {
        var sDay = sDate.val();
        if (flag && !isValidStr(sDay)) { //처음 입력 날짜 설정, update...			
            var sdp = sDate.datepicker().data("datepicker");
            sdp.selectDate(new Date(sDay.replace(/-/g, "/"))); //익스에서는 그냥 new Date하면 -을 인식못함 replace필요
        }

        sDate.datepicker({
            language: 'ko',
            autoClose: true
        });
    }


    function isValidStr(str) {
        if (str == null || str == undefined || str == "")
            return true;
        else
            return false;
    }
}

datePickerSet라는 함수를 따로 만들어서 사용해봤습니다.

 

첫번째 파라미터는 시작일의 달력요소를, 두번째 파라미터에는 종료일의 달력요소를 넣어주면 됩니다.

3번째 파라미터는 처음 로드하면서 처리하는 값인지 아닌지 구분용입니다. true처리하고 로드하시면 됩니다.

시작일과 종료일자에 따라 제한이 걸리는 모습을 볼 수 있습니다.

반응형
반응형

보통 파일 업로드를 할때 체크하는게 보통 파일의 마지막 '.'위치를 찾아서 확장자를 체크하고 파일의 사이즈정도를 항상 js를 통해 체크하는수준이였는데, 이번에 파일의 위·변조 체크요청이 있어서 찾다보니 파일의 MIME TYPE을 체크하여 위변조를 체크할 수 있는 방법을 확인하였습니다.

Apache Tika를 사용하여 파일의 MIME TYPE을 체크하며, 그전에 MIME TYPE에 대해 간단하게 알아보겠습니다.

 

MIME TYPE

Multipurpose Internet Mail Extensions의 약자로 인터넷에서 파일의 성격과 형식에 따라 파일을 식별하는 방법이다.

 

브라우저의 HTTP 응답에 정의된 Content-type의 헤더값을 확인하면 extension/plugin 이다.

과거 SMTP 프로토콜을 사용하여 전송되는 이메일을 위해 작성되었다.

 

하위 유형 앞에 x-로 시작하는 경우는 표준이 아니며 등록되지 않았다는것을 의미한다.

하위 유형 앞에 vnd가 붙으면 공급업체에 따라 변경된다는것을 의미한다.(msoffice 등)

 

출처: https://www.freeformatter.com/mime-types-list.html

 

zpage.mime.types.title

MIME Types List I have compiled a full list of MIME types using the "mime.types" file of the Apache HTTPD virtual private server. I took the liberty of adding a name/description for each MIME type so that it's clearer what they represent. I have also inclu

www.freeformatter.com

 

MIME TYPE은 이정도로 간단하게 알아보고 Tika를 사용하여 MIME TYPE을 알아보겠습니다.

 

 

Tika

pom.xml

<!-- Tika -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>1.14</version>
</dependency>

dependency에 추가를 해줍니다.

 

 

(테스트 대상이 될 이미지 3개) 해당 파일들을 경로별로 체크해보겠습니다.

 

 

App.java

import java.io.File;
import java.io.IOException;

import org.apache.tika.Tika;

public class App{

    public static void main( String[] args ){
        File file1 = new File("C:/test/ggome.jpg");
        File file2 = new File("C:/test/kor.gif");
        File file3 = new File("C:/test/box.png");
        try {
            System.out.println(checkImageMimeType(file1));
            System.out.println(checkImageMimeType(file2));
            System.out.println(checkImageMimeType(file3));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean checkImageMimeType(File file) throws IOException{
        Tika tika = new Tika();

        String mimeType = tika.detect(file);
        System.out.println(mimeType);
        if(mimeType.startsWith("image")) {
            return true;
        }else {
            return false;
        }
    }
}

각각 파일들을 detect메소드를 통해 처리해보니 각각 파일의 종류와 확장자를 sysout을 통해 찍어주는 것을 볼 수 있습니다.

 

 

위변조 체크해보기

자 그럼 위변조가 목적이었으니 애초에 텍스트였던 파일을 강제로 확장자를 변경해서 체크해보겠습니다.

이런식으로 텍스트 파일을 만들고 확장자를 변경합니다.

 

 

소스를 통해 확인해보겠습니다.

import java.io.File;
import java.io.IOException;

import org.apache.tika.Tika;

public class App{

    public static void main( String[] args ){
        File file1 = new File("C:/test/test.jpg");
        try {
            System.out.println(checkImageMimeType(file1));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean checkImageMimeType(File file) throws IOException{
        Tika tika = new Tika();

        String mimeType = tika.detect(file);
        System.out.println(mimeType);
        if(mimeType.startsWith("image")) {
            return true;
        }else {
            return false;
        }
    }
}

확장자는 jpg이지만 텍스트 타입이라고 나오고 이미지타입이 아니기때문에 false처리된 것을 볼 수 있습니다.

반응형