package cc.moecraft.icq.receiver; import cc.moecraft.icq.PicqBotX; import cc.moecraft.icq.event.events.local.EventLocalHttpFail; import cc.moecraft.icq.event.events.local.EventLocalHttpFail.Reason; import cc.moecraft.icq.exceptions.HttpServerException; import cc.moecraft.icq.utils.SHA1Utils; import cc.moecraft.logger.HyLogger; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import lombok.Getter; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import static cc.moecraft.icq.PicqConstants.HTTP_API_VERSION_DETECTION; import static cc.moecraft.icq.event.events.local.EventLocalHttpFail.Reason.*; import static cc.moecraft.icq.utils.NetUtils.read; /** * The class {@code PicqHttpServer} is a http server to receive and * parse the package sent from the http plugin of CoolQ. * <p> * Class created by the HyDEV Team on 2019-03-23! * * @author HyDEV Team (https://github.com/HyDevelop) * @author Hykilpikonna (https://github.com/hykilpikonna) * @author Vanilla (https://github.com/VergeDX) * @since 2019-03-23 12:55 */ @Getter public class PicqHttpServer { /** * 端口号 (0~65535) */ private final int port; /** * 机器人对象 */ private final PicqBotX bot; /** * 日志对象 */ protected final HyLogger logger; /** * HttpServer 对象 */ protected HttpServer server; /** * 构造一个Http服务器 * * @param port 端口 * @param bot 机器人 */ public PicqHttpServer(int port, PicqBotX bot) { this.port = port; this.bot = bot; logger = bot.getLogger(); } /** * 启动 Http 服务器 */ public void start() { try { // 使用 Java SE 6 内置的 HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); // 添加监听器 server.createContext("/", new PicqHttpHandler()); // 启动 server.start(); } catch (IOException e) { throw new HttpServerException(logger, e); } } /** * Http 监听器 */ private class PicqHttpHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { // 是否暂停 if (bot.getConfig().isHttpPaused()) { respondAndClose(exchange, 503, ""); return; } // 验证 HTTP头 if (!validateHeader(exchange)) { respondAndClose(exchange, 200, "Oh hi there! How are you?"); return; } // 获取请求数据 String data = read(exchange.getRequestBody()); // 验证 SHA1 if (!validateSHA1(exchange, data)) { respondAndClose(exchange, 403, ""); return; } // 输出Debug printDebug(exchange, data); // 调用事件 bot.getEventManager().getEventParser().call(data); // 回复成功 respondAndClose(exchange, 204, ""); } } /** * 验证一个请求 * * @param exchange 请求 * @return 是否为 CoolQ Http 请求 */ private boolean validateHeader(HttpExchange exchange) { // 必须是 POST if (!exchange.getRequestMethod().toLowerCase().equals("post")) { return failed(INCORRECT_REQUEST_METHOD, "Not POST"); } // 获取头 Headers headers = exchange.getRequestHeaders(); String contentType = headers.getFirst("content-type"); String userAgent = headers.getFirst("user-agent"); // 必须是 UTF-8 if (!contentType.toLowerCase().contains("charset=utf-8")) { return failed(INCORRECT_CHARSET, "Not UTF-8"); } // 必须是 JSON if (!contentType.contains("application/json")) { return failed(INCORRECT_APPLICATION_TYPE, "Not JSON"); } // 判断版本 if (!bot.getConfig().isNoVerify() && !userAgent.matches(HTTP_API_VERSION_DETECTION)) { reportIncorrectVersion(userAgent); return failed(INCORRECT_VERSION, "Supported Version: " + HTTP_API_VERSION_DETECTION); } return true; } /** * 验证 HAMC SHA1 (如果有的话) * * @param exchange 请求 * @param data 数据 * @return 如果有, 是否验证成功 */ private boolean validateSHA1(HttpExchange exchange, String data) { // 是否启用验证 if (bot.getConfig().getSecret().isEmpty()) { return true; } // 获取 SHA1 String signature = exchange.getRequestHeaders().getFirst("x-signature"); // CoolQ HTTP 是否有发送 SHA1 if (signature == null || signature.isEmpty()) { return failed(INCORRECT_SHA1, "Signature Empty"); } // 获取 SHA1 里面的 SHA1 嗯x signature = signature.replace("sha1=", ""); // 生成 SHA1 String generatedSignature = SHA1Utils.generateHAMCSHA1(data, bot.getConfig().getSecret()); // 判断生成的和获取的是不是一样的 if (!signature.equals(generatedSignature)) { return failed(INCORRECT_SHA1, "Signature Mismatch: \n" + "- Sent: " + signature + "\n- Generated: " + generatedSignature); } return true; } /** * 报告失败 * * @param reason 失败原因 */ private boolean failed(Reason reason, String text) { getBot().getEventManager().call(new EventLocalHttpFail(reason)); logger.debug("Http Failed: {}: {}", reason, text); return false; } /** * 报告版本错误 * * @param currentVersion 当前版本 */ private void reportIncorrectVersion(String currentVersion) { logger.error("HTTP API请求版本不正确, 设置的兼容版本为: " + HTTP_API_VERSION_DETECTION); logger.error("当前版本为: " + currentVersion); logger.error("推荐更新这个类库或者HTTP API的版本"); logger.error("如果要无视版本检查, 请修改 HTTP_API_VERSION_DETECTION"); } /** * 回复输出 * * @param exchange 请求 * @param code HTTP返回码 (204 = CoolQ 处理成功) * @param response 回复 */ private void respondAndClose(HttpExchange exchange, int code, String response) throws IOException { byte[] bytes = response.getBytes(); exchange.sendResponseHeaders(code, bytes.length == 0 ? -1 : bytes.length); OutputStream out = exchange.getResponseBody(); out.write(bytes); out.close(); } /** * 输出Debug消息 * * @param exchange 请求 * @param data 数据 */ private void printDebug(HttpExchange exchange, String data) { if (!bot.getConfig().isDebug()) return; logger.debug("收到新请求: {}", exchange.getRequestHeaders().getFirst("user-agent")); logger.debug("- 数据: {}", data); } }