/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.http;
import nxt.util.Convert;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
public class APITestServlet extends HttpServlet {
private static final String header1 =
"<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
" <meta charset='UTF-8'/>\n" +
" <meta http-equiv='X-UA-Compatible' content='IE=edge'>\n" +
" <meta name='viewport' content='width=device-width, initial-scale=1'>\n" +
" <title>Nxt http API</title>\n" +
" <link href='css/bootstrap.min.css' rel='stylesheet' type='text/css' />\n" +
" <link href='css/font-awesome.min.css' rel='stylesheet' type='text/css' />\n" +
" <link href='css/highlight.style.css' rel='stylesheet' type='text/css' />\n" +
" <style type='text/css'>\n" +
" table {border-collapse: collapse;}\n" +
" td {padding: 10px;}\n" +
" .result {white-space: pre; font-family: monospace; overflow: auto;}\n" +
" </style>\n" +
"</head>\n" +
"<body>\n" +
"<div class='navbar navbar-default' role='navigation'>\n" +
" <div class='container' style='min-width: 90%;'>\n" +
" <div class='navbar-header'>\n" +
" <a class='navbar-brand' href='/test'>Nxt http API</a>\n" +
" </div>\n" +
" <div class='navbar-collapse collapse'>\n" +
" <ul class='nav navbar-nav navbar-right'>\n" +
" <li><input type='text' class='form-control' id='search' " +
" placeholder='Search' style='margin-top:8px;'></li>\n" +
" <li><a href='https://nxtwiki.org/wiki/The_Nxt_API' target='_blank' style='margin-left:20px;'>Wiki Docs</a></li>\n" +
" </ul>\n" +
" </div>\n" +
" </div>\n" +
"</div>\n" +
"<div class='container' style='min-width: 90%;'>\n" +
"<div class='row'>\n" +
" <div class='col-xs-12' style='margin-bottom:10px;'>\n" +
" <div class='pull-right'>\n" +
" <div class='btn-group'>\n" +
" <button type='button' class='btn btn-default btn-sm dropdown-toggle' data-toggle='dropdown'>\n" +
" <i class='fa fa-check-circle-o'></i> <i class='fa fa-circle-o'></i>\n" +
" </button>\n" +
" <ul class='dropdown-menu' role='menu' style='font-size:12px;'>\n" +
" <li><a href='#' id='navi-select-all-d-add-btn'>Select All Displayed (Add)</a></li>\n" +
" <li><a href='#' id='navi-select-all-d-replace-btn'>Select All Displayed (Replace)</a></li>\n" +
" <li><a href='#' id='navi-deselect-all-d-btn'>Deselect All Displayed</a></li>\n" +
" <li><a href='#' id='navi-deselect-all-btn'>Deselect All</a></li>\n" +
" </ul>\n" +
" </div>\n" +
" <button type='button' id='navi-show-fields' data-navi-val='ALL' class='btn btn-default btn-sm' style='width:165px;'>Show Non-Empty Fields</button>\n" +
" <button type='button' id='navi-show-tabs' data-navi-val='ALL' class='btn btn-default btn-sm' style='width:130px;'>Show Open Tabs</button>\n" +
" </div>\n" +
" </div>\n" +
"</div>\n" +
"<div class='row' style='margin-bottom:15px;'>\n" +
"<div class='col-xs-4 col-sm-3 col-md-2'>\n" +
"<ul class='nav nav-pills nav-stacked'>\n";
private static final String header2 =
"</ul>\n" +
"</div> <!-- col -->" +
"<div class='col-xs-8 col-sm-9 col-md-10'>\n" +
"<div class='panel-group' id='accordion'>\n";
private static final String footer1 =
"</div> <!-- panel-group -->\n" +
"</div> <!-- col -->\n" +
"</div> <!-- row -->\n" +
"</div> <!-- container -->\n" +
"<script src='js/3rdparty/jquery.js'></script>\n" +
"<script src='js/3rdparty/bootstrap.js' type='text/javascript'></script>\n" +
"<script src='js/3rdparty/highlight.pack.js' type='text/javascript'></script>\n" +
"<script src='js/ats.js' type='text/javascript'></script>\n" +
"<script src='js/ats.util.js' type='text/javascript'></script>\n" +
"<script>\n" +
"$(document).ready(function() {";
private static final String footer2 =
"});\n" +
"</script>\n" +
"</body>\n" +
"</html>\n";
private static final List<String> allRequestTypes = new ArrayList<>(APIServlet.apiRequestHandlers.keySet());
static {
Collections.sort(allRequestTypes);
}
private static final SortedMap<String, SortedSet<String>> requestTags = new TreeMap<>();
static {
for (Map.Entry<String, APIServlet.APIRequestHandler> entry : APIServlet.apiRequestHandlers.entrySet()) {
String requestType = entry.getKey();
Set<APITag> apiTags = entry.getValue().getAPITags();
for (APITag apiTag : apiTags) {
SortedSet<String> set = requestTags.get(apiTag.name());
if (set == null) {
set = new TreeSet<>();
requestTags.put(apiTag.name(), set);
}
set.add(requestType);
}
}
}
private static String buildLinks(HttpServletRequest req) {
StringBuilder buf = new StringBuilder();
String requestTag = Convert.nullToEmpty(req.getParameter("requestTag"));
buf.append("<li");
if (requestTag.equals("")
& !req.getParameterMap().containsKey("requestType")
& !req.getParameterMap().containsKey("requestTypes")) {
buf.append(" class='active'");
}
buf.append("><a href='/test'>ALL</a></li>\n");
buf.append("<li");
if (req.getParameterMap().containsKey("requestTypes")) {
buf.append(" class='active'");
}
buf.append("><a href='/test?requestTypes=' id='navi-selected'>SELECTED</a></li>\n");
for (APITag apiTag : APITag.values()) {
if (requestTags.get(apiTag.name()) != null) {
buf.append("<li");
if (requestTag.equals(apiTag.name())) {
buf.append(" class='active'");
}
buf.append("><a href='/test?requestTag=").append(apiTag.name()).append("'>");
buf.append(apiTag.getDisplayName()).append("</a></li>\n");
}
}
return buf.toString();
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate, private");
resp.setHeader("Pragma", "no-cache");
resp.setDateHeader("Expires", 0);
resp.setContentType("text/html; charset=UTF-8");
if (! API.isAllowed(req.getRemoteHost())) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
try (PrintWriter writer = resp.getWriter()) {
writer.print(header1);
writer.print(buildLinks(req));
writer.print(header2);
String requestType = Convert.nullToEmpty(req.getParameter("requestType"));
APIServlet.APIRequestHandler requestHandler = APIServlet.apiRequestHandlers.get(requestType);
StringBuilder bufJSCalls = new StringBuilder();
if (requestHandler != null) {
writer.print(form(req, requestType, true, requestHandler));
bufJSCalls.append(" ATS.apiCalls.push('").append(requestType).append("');\n");
} else if (!req.getParameterMap().containsKey("requestTypes")) {
String requestTag = Convert.nullToEmpty(req.getParameter("requestTag"));
Set<String> taggedTypes = requestTags.get(requestTag);
for (String type : (taggedTypes != null ? taggedTypes : allRequestTypes)) {
requestHandler = APIServlet.apiRequestHandlers.get(type);
writer.print(form(req, type, false, requestHandler));
bufJSCalls.append(" ATS.apiCalls.push('").append(type).append("');\n");
}
} else {
String requestTypes = Convert.nullToEmpty(req.getParameter("requestTypes"));
if (!requestTypes.equals("")) {
Set<String> selectedRequestTypes = new TreeSet<>(Arrays.asList(requestTypes.split("_")));
for (String type: selectedRequestTypes) {
requestHandler = APIServlet.apiRequestHandlers.get(type);
writer.print(form(req, type, false, requestHandler));
bufJSCalls.append(" ATS.apiCalls.push('").append(type).append("');\n");
}
} else {
writer.print(fullTextMessage("No API calls selected.", "info"));
}
}
writer.print(footer1);
writer.print(bufJSCalls.toString());
writer.print(footer2);
}
}
private static String fullTextMessage(String msg, String msgType) {
return "<div class='alert alert-" + msgType + "' role='alert'>" + msg + "</div>\n";
}
private static String form(HttpServletRequest req, String requestType, boolean singleView, APIServlet.APIRequestHandler requestHandler) {
String className = requestHandler.getClass().getName();
List<String> parameters = requestHandler.getParameters();
boolean requirePost = requestHandler.requirePost();
String fileParameter = requestHandler.getFileParameter();
StringBuilder buf = new StringBuilder();
buf.append("<div class='panel panel-default api-call-All' ");
buf.append("id='api-call-").append(requestType).append("'>\n");
buf.append("<div class='panel-heading'>\n");
buf.append("<h4 class='panel-title'>\n");
buf.append("<a data-toggle='collapse' class='collapse-link' data-target='#collapse").append(requestType).append("' href='#'>");
buf.append(requestType);
buf.append("</a>\n");
buf.append("<span style='float:right;font-weight:normal;font-size:14px;'>\n");
if (!singleView) {
buf.append("<a href='/test?requestType=").append(requestType);
buf.append("' target='_blank' style='font-weight:normal;font-size:14px;color:#777;'>\n<span class='glyphicon glyphicon-new-window'></span>\n</a>");
buf.append(" \n");
}
buf.append("<a style='font-weight:normal;font-size:14px;color:#777;' href='/doc/");
buf.append(className.replace('.', '/')).append(".html' target='_blank'>javadoc</a> \n");
buf.append("<a style='font-weight:normal;font-size:14px;color:#777;' href='https://nxtwiki.org/wiki/The_Nxt_API#");
appendWikiLink(className.substring(className.lastIndexOf('.') + 1), buf);
buf.append("' target='_blank'>wiki</a> \n");
buf.append(" \n<input type='checkbox' class='api-call-sel-ALL' ");
buf.append("id='api-call-sel-").append(requestType).append("'>\n");
buf.append("</span>\n");
buf.append("</h4>\n");
buf.append("</div> <!-- panel-heading -->\n");
buf.append("<div id='collapse").append(requestType).append("' class='panel-collapse collapse");
if (singleView) {
buf.append(" in");
}
buf.append("'>\n");
buf.append("<div class='panel-body'>\n");
buf.append("<form action='/nxt' method='POST' ");
if (fileParameter != null) {
buf.append("enctype='multipart/form-data' ");
}
buf.append("onsubmit='return ATS.submitForm(this");
if (fileParameter != null) {
buf.append(", \"").append(fileParameter).append("\"");
}
buf.append(")'>\n");
buf.append("<input type='hidden' name='requestType' value='").append(requestType).append("'/>\n");
buf.append("<div class='col-xs-12 col-lg-6' style='min-width: 40%;'>\n");
buf.append("<table class='table'>\n");
if (fileParameter != null) {
buf.append("<tr class='api-call-input-tr'>\n");
buf.append("<td>").append(fileParameter).append(":</td>\n");
buf.append("<td><input type='file' name='").append(fileParameter).append("' id='").append(fileParameter).append(requestType).append("' ");
buf.append("style='width:100%;min-width:200px;'/></td>\n");
buf.append("</tr>\n");
}
for (String parameter : parameters) {
buf.append("<tr class='api-call-input-tr'>\n");
buf.append("<td>").append(parameter).append(":</td>\n");
buf.append("<td><input type='").append(isPassword(parameter) ? "password" : "text").append("' ");
buf.append("name='").append(parameter).append("' ");
String value = Convert.emptyToNull(req.getParameter(parameter));
if (value != null) {
buf.append("value='").append(value.replace("'", """)).append("' ");
}
buf.append("style='width:100%;min-width:200px;'/></td>\n");
buf.append("</tr>\n");
}
buf.append("<tr>\n");
buf.append("<td colspan='2'><input type='submit' class='btn btn-default' value='submit'/></td>\n");
buf.append("</tr>\n");
buf.append("</table>\n");
buf.append("</div>\n");
buf.append("<div class='col-xs-12 col-lg-6' style='min-width: 50%;'>\n");
buf.append("<h5 style='margin-top:0px;'>\n");
if (!requirePost) {
buf.append("<span style='float:right;' class='uri-link'>");
buf.append("</span>\n");
} else {
buf.append("<span style='float:right;font-size:12px;font-weight:normal;'>POST only</span>\n");
}
buf.append("Response</h5>\n");
buf.append("<pre class='hljs json'><code class='result'>JSON response</code></pre>\n");
buf.append("</div>\n");
buf.append("</form>\n");
buf.append("</div> <!-- panel-body -->\n");
buf.append("</div> <!-- panel-collapse -->\n");
buf.append("</div> <!-- panel -->\n");
return buf.toString();
}
private static boolean isPassword(String parameter) {
return "secretPhrase".equals(parameter) || "adminPassword".equals(parameter) || "recipientSecretPhrase".equals(parameter);
}
private static void appendWikiLink(String className, StringBuilder buf) {
for (int i = 0; i < className.length(); i++) {
char c = className.charAt(i);
if (i == 0) {
c = Character.toUpperCase(c);
}
buf.append(c);
if (i < className.length() - 2 && Character.isUpperCase(className.charAt(i + 1))
&& (Character.isLowerCase(c) || Character.isLowerCase(className.charAt(i + 2)))) {
buf.append('_');
}
}
}
}