001package gudusoft.gsqlparser.demos.sqlguard; 002 003import com.sun.net.httpserver.HttpExchange; 004import com.sun.net.httpserver.HttpHandler; 005import com.sun.net.httpserver.HttpServer; 006 007import java.io.ByteArrayOutputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.io.OutputStream; 011import java.net.HttpURLConnection; 012import java.net.InetAddress; 013import java.net.InetSocketAddress; 014import java.net.URL; 015import java.nio.charset.StandardCharsets; 016import java.util.concurrent.ArrayBlockingQueue; 017import java.util.concurrent.ThreadPoolExecutor; 018import java.util.concurrent.TimeUnit; 019 020public class SqlGuardHttpServer { 021 public static final int MAX_BODY_BYTES = 1572864; 022 private final String host; 023 private final int port; 024 private final SqlGuardService service; 025 private HttpServer server; 026 027 public SqlGuardHttpServer(String host, int port, SqlGuardService service) { 028 this.host = host == null ? "127.0.0.1" : host; 029 this.port = port; 030 this.service = service; 031 } 032 033 public void start() throws IOException { 034 InetAddress bindAddress = InetAddress.getByName(host); 035 if (!bindAddress.isLoopbackAddress()) { 036 throw new IOException("SQL Guard worker refuses non-loopback bind address: " + host); 037 } 038 server = HttpServer.create(new InetSocketAddress(bindAddress, port), 16); 039 server.createContext("/healthz", new Health()); 040 server.createContext("/check", new Check()); 041 server.setExecutor(new ThreadPoolExecutor( 042 4, 4, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(32))); 043 server.start(); 044 } 045 046 public void stop(int delay) { 047 if (server != null) server.stop(delay); 048 } 049 050 public InetSocketAddress address() { 051 return server.getAddress(); 052 } 053 054 class Health implements HttpHandler { 055 public void handle(HttpExchange x) throws IOException { 056 if (!"GET".equals(x.getRequestMethod())) { 057 send(x, 405, "{\"ok\":false}"); 058 return; 059 } 060 send(x, 200, "{\"ok\":true,\"service\":\"sql-guard-worker\"}"); 061 } 062 } 063 064 class Check implements HttpHandler { 065 public void handle(HttpExchange x) throws IOException { 066 if (!"POST".equals(x.getRequestMethod())) { 067 send(x, 405, "{\"ok\":false}"); 068 return; 069 } 070 String body; 071 try { 072 body = readLimited(x.getRequestBody(), MAX_BODY_BYTES); 073 } catch (IOException e) { 074 send(x, 413, SqlGuardResponse.error(null, "REQUEST_TOO_LARGE", "Request body too large.").toJson()); 075 return; 076 } 077 SqlGuardResponse r; 078 try { 079 r = service.check(SqlGuardRequest.fromJson(body)); 080 } catch (Exception e) { 081 r = SqlGuardResponse.error(null, "INVALID_REQUEST", "Invalid JSON request."); 082 } 083 send(x, r.ok ? 200 : 400, r.toJson()); 084 } 085 } 086 087 /** 088 * Reads at most {@code max} bytes from {@code in}, refusing input that exceeds the limit before it is buffered. 089 * 090 * <p>PUBLIC ON PURPOSE: every shipped jar is ProGuard-obfuscated under a 091 * compatibility-first policy that preserves public and protected members 092 * and renames everything else. A package-private method is therefore 093 * unreachable from the released jar, which is what the test suite runs 094 * against in the nightly obfuscated-jar gate. Keeping this public is what 095 * lets that gate execute the real shipped bytes.</p> 096 * 097 * @since 4.2.6 098 */ 099 public static String readLimited(InputStream in, int max) throws IOException { 100 ByteArrayOutputStream out = new ByteArrayOutputStream(); 101 byte[] buf = new byte[8192]; 102 int total = 0; 103 int n; 104 while ((n = in.read(buf)) != -1) { 105 if (total + n > max) { 106 throw new IOException("too large"); 107 } 108 total += n; 109 out.write(buf, 0, n); 110 } 111 return new String(out.toByteArray(), StandardCharsets.UTF_8); 112 } 113 114 static void send(HttpExchange x, int status, String body) throws IOException { 115 byte[] b = body.getBytes(StandardCharsets.UTF_8); 116 x.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); 117 x.sendResponseHeaders(status, b.length); 118 OutputStream os = x.getResponseBody(); 119 try { 120 os.write(b); 121 } finally { 122 os.close(); 123 } 124 } 125 126 public static String httpGet(String url) throws IOException { 127 HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection(); 128 c.setRequestMethod("GET"); 129 return readAll(c); 130 } 131 132 public static String httpPost(String url, String body) throws IOException { 133 HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection(); 134 c.setRequestMethod("POST"); 135 c.setDoOutput(true); 136 c.setRequestProperty("Content-Type", "application/json"); 137 OutputStream os = c.getOutputStream(); 138 try { 139 os.write(body.getBytes(StandardCharsets.UTF_8)); 140 } finally { 141 os.close(); 142 } 143 return readAll(c); 144 } 145 146 private static String readAll(HttpURLConnection c) throws IOException { 147 InputStream in = c.getResponseCode() >= 400 ? c.getErrorStream() : c.getInputStream(); 148 if (in == null) { 149 return ""; 150 } 151 try { 152 ByteArrayOutputStream out = new ByteArrayOutputStream(); 153 byte[] buf = new byte[4096]; 154 int n; 155 while ((n = in.read(buf)) != -1) out.write(buf, 0, n); 156 return new String(out.toByteArray(), StandardCharsets.UTF_8); 157 } finally { 158 in.close(); 159 } 160 } 161}