I’m trying to create a custom API endpoint that responds with plain text. No matter what I do, the response always gets returned as HTML. As an experiment, I also tried a JSON response, and it had the same issue.
Here’s a simplified example to demonstrate the problem. I create a terminal page at /xwiki/bin/view/api/test with the following content:
{{velocity}}
#if ($stringtool.contains("$!request.getRequestURL()", "/get/"))
#set ($discard = $response.setContentType('text/plain'))
#set ($name = "$!request.name")
Hello, $name
#end
{{/velocity}}
If I call this endpoint with:
https://www.example.com/xwiki/bin/get/api/test?name=Rob
the response looks like this:
<p>Hello, Rob</p>
For more complex responses with multiple lines, I get a <br>
inserted between each line.
I get the same output whether I go through the browser (Chrome/Brave) or via the command line using curl.
What do I need to do to force a plain text response of:
Hello, Rob
If it matters, I’m running XWiki behind an Apache reverse proxy with the following settings:
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPreserveHost On
ProxyPass /xwiki http://localhost:8080/xwiki nocanon
ProxyPassReverse /xwiki http://localhost:8080/xwiki
1 Like
The problem is that your output goes through XWiki rendering which is both bad for performance and for security (the code you showed contains a remote code execution vulnerability, see the security developer guide how to escape user input). If you want to output content directly, you should use the #rawResponse
Velocity macro that allows you to send the output directly to the client without going through XWiki rendering - then you also don’t need to escape anything.
2 Likes
Thanks for your reply Michael. The parameter was just something I was experimenting with on an internal server and not used in any production code, so I hadn’t done any cleansing of the the input yet, but I appreciate the warning nonetheless.
I wasn’t aware of the #rawResponse
macro. Thank you very much for that link. Not only did this fix my current problem, but I think it will fix some performance issues I’m having with other endpoints.
I wish this were mentioned somewhere in the custom API endpoint documentation, it would have saved me a ton of time. I didn’t even think to search the source code for an undocumented macro that might be useful in this case. Dozens of web searches and even Perplexity queries didn’t turn up anything related to this macro.
Thank you very much for your help!
For anyone who wants to see the solution, here is what I did (without the unsafe parameter):
{{velocity}}
#if ($stringtool.contains("$!request.getRequestURL()", "/get/"))
#set ($discard= $response.setContentType('text/plain'))
#set ($data = "Hello, world!")
#rawResponse($data, 'text/plain')
#end
{{/velocity}}
This will generate the response:
Hello, world!
One more question if you don’t mind: I was re-reading the Custom Resources section of the REST API documentation and was reminded of another solution I had used long ago that I couldn’t recall: adding “&outputSyntax=plain
” to the end of the request URL. This also works to get rid of the HTML in the response. Does this use the #rawResponse()
macro or similar behind the scenes? Is there an advantage to one method versus the other?
This still uses the full XWiki rendering, which interprets, e.g., smileys and other stuff and is slow. This just means it renders to plain text instead of HTML. The #rawResponse
variant is clearly the recommended approach that is faster, more reliable and more secure.
1 Like
Thanks, Michael. That’s very helpful information. I really appreciate your insights.