Confluenceのweb-panelプラグインにて、VelocityテンプレートにHTMLエスケープされないでテンプレート変数をアサインしてみる
Confluenceでプラグイン開発してると、web-panelでテンプレートを使うのだけど、これにアサインする変数はすべてhtmlエスケープされる。「"」だと、「"」となる。たいがいの場合はこれでいいのだけど、JSONをアサインする際に困った。
アサインするのは、ContextProviderです。
atlassian-plugin.xml
<web-panel key="foo-panel" location="atl.header"> <resource name="view" type="velocity" location="templates/foo.vm"/> <context-provider class="jp.junoe.confluence.plugins.FooContextProvider"/> </web-panel>
でFooContextProvider::getContextMap()をごにょぼにょする。
そこでいくつか方法を試してみた。
結論から言うと、まだ成功してない。だけ見たい方は最後に。
jsonatorを使ってみた
テンプレート変数には、$jsonatorというのがある。このあたりを見てなんとなく
https://developer.atlassian.com/display/CONFDEV/Generating+JSON+output+in+Confluence+with+Jsonator
https://docs.atlassian.com/atlassian-confluence/latest/com/atlassian/confluence/json/jsonator/Jsonator.html
$jsonator.convert($obj).serialize()
ってテンプレートに書いてみた。$objは
Map<String, String> bean = new HashMap<String, String>(); bean.put("history", "my history"); context.put("obj", bean);
という感じ。
結果
JSONに展開してくれたのだけど、展開後のJSONをエスケープしてくれたのでダメだった。どうやら、出力の最後の最後でエスケープするらしい。テンプレート関数を使ってもダメ。
ちなみに、テンプレートで使えそうなGeneralUtilっていうのを使ってみる方法をこのとき見つけた。
https://docs.atlassian.com/atlassian-confluence/latest/index.html?com/atlassian/confluence/util/GeneralUtil.html
web-panel-rendererを使ってみる。
これはまだ途中。
デフォルトで与えられているテンプレートレンダラーを使うからよくない。
そこで、新しくレンダラーを作る。
レンダラーモジュールについてはこちら。
Web Panel Renderer Plugin Module - Confluence Development - Atlassian Developer Documentation
コマンドプロンプトからモジュールを追加する | ナレッジオンデマンド株式会社
を参考にしてweb-panel-rendererモジュール追加。
atlassian-plugin.xml
<web-panel-renderer name="Foo Property Web Panel Renderer" i18n-name-key="foo-property-web-panel-renderer.name" key="foo-property-web-panel-renderer" class="jp.junoe.confluence.plugins.web.FooPropertyWebPanelRenderer"> <description key="foo-property-web-panel-renderer.description"> The Foo Property Web Panel Renderer Plugin</description> </web-panel-renderer>
って感じ。
で、肝心のクラスには ConfluenceAwareWebPanelRenderer をExtendsして作る。ConfluenceAwareWebPanelRenderer はConfluenceのVelocityのデフォルトレンダラー。
package jp.junoe.confluence.plugins.web; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.atlassian.confluence.renderer.radeox.macros.MacroUtils; import com.atlassian.confluence.util.collections.CompositeMap; import com.atlassian.confluence.util.velocity.VelocityUtils; import com.atlassian.plugin.Plugin; import com.atlassian.plugin.web.renderer.RendererException; import com.atlassian.plugin.web.renderer.WebPanelRenderer; import com.atlassian.confluence.plugin.web.renderer.ConfluenceAwareWebPanelRenderer; import java.io.IOException; import java.io.Writer; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import org.apache.velocity.app.VelocityEngine /** * Renders a web panel in the context of Confluence. * This makes core velocity macros and templates available for use in the velocity template */ public class FooPropertyWebPanelRenderer extends ConfluenceAwareWebPanelRenderer implements WebPanelRenderer { private static final Logger log = LoggerFactory.getLogger(FooPropertyWebPanelRenderer.class); @Override public String getResourceType() { return "foo-velocity"; } @Override public void render(String templateName, Plugin plugin, Map<String, Object> context, Writer writer) throws RendererException, IOException { Map<String, Object> defaultContext = MacroUtils.defaultVelocityContext(); Map<String, Object> map = CompositeMap.of(defaultContext, context); VelocityUtils.writeRenderedTemplate(writer, templateName, map); } @Override public String renderFragment(String fragment, Plugin plugin, Map<String, Object> context) throws RendererException { Map<String, Object> defaultContext = MacroUtils.defaultVelocityContext(); Map<String, Object> map = CompositeMap.of(defaultContext, context); return VelocityUtils.getRenderedContent(fragment, map); } @Override public void renderFragment(Writer writer, String fragment, Plugin plugin, Map<String, Object> context) throws RendererException, IOException { Map<String, Object> defaultContext = MacroUtils.defaultVelocityContext(); Map<String, Object> map = CompositeMap.of(defaultContext, context); VelocityUtils.writeRenderedContent(writer, fragment, map); } public String unescapeHTMLEntities(String str) { return str; } }
getResourceType()で返す文字列が重要。
これをweb-panelモジュールのresourceタイプにする。foo-velocityをreturnしてるので
<web-panel key="foo-panel" location="atl.header"> <resource name="view" type="foo-velocity" location="templates/panel.vm"/> <context-provider class="jp.junoe.confluence.plugins.FooContextProvider"/> </web-panel>
となる。
これでRenderer自体をとることができた。
で、どうするか?
Velocityを
VelocityEngine v = VelocityUtils.getVelocityEngine();
で取得することができるので、ここにRawで変数を与えていけばいいんじゃないかと思っている。
が、実際についてはまだ。
同じ質問を見つけた
AtlassianのQ&Aサイトで
How to prevent velocity escape html - Atlassian Answers
というのを見つけた。
@HtmlSafeというのを使って宣言するといいらしい。
ただ、まだやっていない。
disableAntiXSS()
その質問に言及のあった
https://developer.atlassian.com/display/CONFDEV/Enabling+XSS+Protection+in+Plugins
を見ると、テンプレートで#disableAntiXSS()を実行するといいらしい。ただ、これをやってみてもダメだった。
変数にHtmlというpostfixを付けるのが正解
Enabling XSS Protection in Plugins - Confluence Development - Atlassian Developer Documentationの「How do I opt out of Anti-XSS protection?」のセクション
あるいは、先ほどの質問の Damian Nowak さんの回答。
単にVelocityテンプレートで
#set($successMessageHtml = $successMessage)
というようにHtmlを最後につけた変数にすればいいだけという。。。
他にもいくつか方法があるのだけど、これが一番簡単だった。