Go에서는 HTML을 출력하기 위해서 html/template 패키지를 사용합니다. html/template 패키지의 소개에 보면 맨 첫줄에 "Package template (html/template) implements data-driven templates for generating HTML output safe against code injection."라고 적혀 있습니다. 즉, html/template 패키지를 이용하여 데이터를 포함한 HTML코드를 만들 때, 데이터의 특수 문자를 escaping하여, 출력된 화면에서 원치 않는 코드가 실행되는 것을 막을 수 있다는 의미입니다.
예시를 보면 더 잘 이해할 수 있습니다.
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
위 코드로 실행된 코드는
Hello, <script>alert('you have been pwned')</script>!
가 아니라 다음과 같이 특수 문자가 안전하게 문자로 변경( escaping )되어 출력됩니다.
Hello, <script>alert('you have been pwned')</script>!
첫번 째의 결과를 HTML 코드로 만들게 되면 의도하지 않은 동작( alert가 발생합니다)을 하게 되는데, html/template 패키지에서 생성된 코드는 그런일이 발생하지 않도록 방지해 줍니다.
하지만 html/template 패키지를 사용한다고 해서 항상 원하는 결과를 얻지는 못한다는 것을 최근에 알았습니다. 의도하지 않은 코드를 생성해서 당황하게 만드는 일이었는데, 상황은 이랬습니다. 다음과 같은 HTML 파일을 사용합니다.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<img src={{.}}>
</body>
</html>
화면에 이미지를 보여주는 화면으로, img 태그의 값이 템플릿에서 데이터로 치환을 위한 {{.}} 코드가 있고 다음과 같이 HTML을 생성하기 위한 go 코드가 있습니다.
tmpl, _ := template.ParseFiles("index.html")
tmpl.Execute(out, data)
index.html 파일을 읽어 템플릿을 만들고, 템플릿에, data를 삽입하여, HTML로 출력하는 코드입니다. 이때, data의 값으로 "http://www.warehouse.com/image.jpg"을 전달하여 다음과 같은 출력이 되기를 바랐습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<img src="http://www.warehouse.com/image.jpg">
</body>
</html>
그런데, 출력결과는 당황스럽게도, 다음과 같이 치환이 되어 있었습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<img src="#ZgotmplZ">
</body>
</html>
"#ZgotmplZ"라는 게 생겼습니다. 이걸 처음보고, html/template는 단축 URL도 만들어주는 건가? 라고 생각하기도 했지만, 이미지가 제대로 표시되지 않는 것을 보고, 그건 아니다 싶었습니다.
한동안 인터넷을 뒤지다가 결국엔 답을 처음 읽었던 문서에서 찾았는데요. 중간보다 조금 아래에 이런 말이 적혀있었습니다.
"ZgotmplZ" is a special value that indicates that unsafe content reached a
CSS or URL context at runtime. The output of the example will be
<img src="#ZgotmplZ">
CSS나 URL이 안전하지 않은 내용을 담고 있다고 판단되면 실행 중에 출력되는 특별한 값이 "ZgotmlZ"이다. 즉, 제가 data로 전달한 "http://www.warehouse.com/image.jpg"이 안전하지 않다고 판단되었다는 거였습니다. data로 전달할 때 객체의 값을 stringify를 해서 보냈는데, 생성된 문자열에 따옴표가 붙어 있어서 문제가 되었습니다. http://www.warehouse.com/image.jpg라면 문제되지 않지만, "http://www.warehouse.com/image.jpg"는 안전하지 않다고 판단하는 모양입니다.
Go는 이제까지 겪어보지 못한 재미있는 언어 같습니다. 에러를 내는게 당연하다고 생각했는데, 이렇게 에러를 발생시키지 않고 알려주는 방식도 괜찮은 것 같습니다. 다시 한번 Go에서 에러처리는 직접해야 한다는 것, 그리고 매뉴얼은 끝까지 꼼꼼하게 잘 읽어야 한다는 것을 느끼게 하는 경험이었습니다.
참고 : https://golang.org/pkg/html/template/