goでxmlをescapeする話
はてなブログのクライアントを書いている時にxmlをPOSTする必要があった。考えるのが面倒だったので text/template
を使っていたのだけれど。
これでは問題があった。本文がescapeされないのでinvalidなrequestになってしまう。
具体的にはこういう話。
package main import ( "log" "os" "strings" "text/template" ) func main() { tmpl := template.Must(template.New("template").Parse(strings.TrimSpace(` <?xml version="1.0" encoding="utf-8"?> <content>{{.}}</content> `))) content := `&foo とか&が書かれた文章` if err := tmpl.Execute(os.Stdout, content); err != nil { log.Fatal(err) } }
これの実行結果が以下の様になってしまう。 &
はescapeしなければダメ。
<?xml version="1.0" encoding="utf-8"?> <content>&foo とか&が書かれた文章</content>
html.templateなら問題なし?
それじゃ自動でescapeしてくれるhtml.template
を使えば良いかというとそうでもない。先頭の<?xml
部分もescapeしようとしてしまう。
package main import ( "html/template" "log" "os" "strings" ) func main() { tmpl := template.Must(template.New("template").Parse(strings.TrimSpace(` <?xml version="1.0" encoding="utf-8"?> <content>{{.}}</content> `))) content := `&foo とか&が書かれた文章` if err := tmpl.Execute(os.Stdout, content); err != nil { log.Fatal(err) } }
これはinvalidな出力。
<?xml version="1.0" encoding="utf-8"?> <content>&foo とか&が書かれた文章</content>
実際
実際同じようなことをやってしまった人がいてissueは却下されている。
https://github.com/golang/go/issues/3133
xmlはhtmlじゃないでしょ。text/templateを使ってescapeしましょうみたいな話。
まじめにescapeしましょう
package main import ( "html" "log" "os" "strings" "text/template" ) func main() { tmpl := template.Must(template.New("template").Parse(strings.TrimSpace(` <?xml version="1.0" encoding="utf-8"?> <content>{{.}}</content> `))) content := `&foo とか&が書かれた文章` if err := tmpl.Execute(os.Stdout, html.EscapeString(content)); err != nil { log.Fatal(err) } }
今度は大丈夫。
<?xml version="1.0" encoding="utf-8"?> <content>&foo とか&が書かれた文章</content>
ところで
ところでstruct中のstringを再帰的に辿ってescapeしていく処理が欲しくなったりするんですが。やっぱりreflectパッケージが必要になるしオススメしないみたいな感じになるんですかね。
text/template
にはpipelineという仕組みがあるっぽいのでこれを上手く使って全部手動でescapeするのが無難?
もしくは真面目にこちらを使いましょうとか。