package aQute.http.testservers; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.math.BigInteger; import java.net.URI; import java.security.KeyManagementException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import aQute.http.testservers.HttpTestServer.Config; import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoHTTPD.Response.IStatus; class Server extends NanoHTTPD { private static final char[] PASSWORD = "123456789".toCharArray(); private X509Certificate[] certificateChain; private Config config; final static SecureRandom random = new SecureRandom(); final Map<String,HttpContext> contexts = new HashMap<>(); public Server(HttpTestServer.Config config) throws Exception { super(config.host, config.port); this.config = config; if (config.https) { KeyPair pair = createKey(); certificateChain = createSelfSignedCertifcate(pair); KeyStore keystore = createKeystore(pair); SSLContext ctx = createTLSContext(keystore); makeSecure(ctx.getServerSocketFactory(), null); } } @Override public Response serve(IHTTPSession session) { try { final aQute.http.testservers.HttpTestServer.Response response = new HttpTestServer.Response(); final aQute.http.testservers.HttpTestServer.Request request = new HttpTestServer.Request(); Map<String,String> headers = session.getHeaders(); if (headers.containsKey("content-length")) { int length = Integer.parseInt(headers.get("content-length")); request.content = new byte[length]; DataInputStream din = new DataInputStream(session.getInputStream()); din.readFully(request.content); } HttpContext context = findHandler(session.getUri()); if (context == null) { return newFixedLengthResponse(NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", "Method not found for " + session.getUri()); } request.uri = new URI(session.getUri()); request.headers = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); request.headers.putAll(session.getHeaders()); request.args = session.getParms(); request.ip = session.getHeaders().get("remote-addr"); request.method = session.getMethod().name(); String path = request.uri.getPath().substring(context.path.length()); if (path.startsWith("/")) path = path.substring(1); context.handler.handle(path, request, response); if (response.content == null) response.content = new byte[0]; if (response.length < 0) response.length = response.content.length; IStatus status = NanoHTTPD.Response.Status.OK; for (IStatus v : fi.iki.elonen.NanoHTTPD.Response.Status.values()) { if (v.getRequestStatus() == response.code) { status = v; break; } } Response r; if (response.stream != null) r = newFixedLengthResponse(status, response.mimeType, response.stream, response.length); else r = newFixedLengthResponse(status, response.mimeType, new ByteArrayInputStream(response.content), response.length); for (Map.Entry<String,String> entry : response.headers.entrySet()) { r.addHeader(entry.getKey(), entry.getValue()); } if (response.mimeType != null) r.setMimeType(response.mimeType); return r; } catch (Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); pw.close(); Response r = newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, "text/plain", sw.toString()); return r; } } private HttpContext findHandler(String uri) { while (uri.length() > 1 && uri.endsWith("/")) uri = uri.substring(0, uri.length() - 1); while (true) { HttpContext context = contexts.get(uri); if (context != null) { return context; } if (uri.equals("/")) return null; int n = uri.lastIndexOf('/'); if (n == -1) return null; uri = uri.substring(0, n); } } private KeyStore createKeystore(KeyPair pair) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(null, null); keystore.setKeyEntry(config.host, pair.getPrivate(), PASSWORD, certificateChain); return keystore; } private SSLContext createTLSContext(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { SSLContext ctx = SSLContext.getInstance("TLS"); KeyManagerFactory keyManagers = KeyManagerFactory.getInstance("SunX509"); keyManagers.init(keystore, PASSWORD); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); trustManagerFactory.init(keystore); ctx.init(keyManagers.getKeyManagers(), trustManagerFactory.getTrustManagers(), random); return ctx; } private X509Certificate[] createSelfSignedCertifcate(KeyPair keyPair) throws Exception { X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); nameBuilder.addRDN(BCStyle.CN, "localhost"); Date notBefore = new Date(); Date notAfter = new Date(System.currentTimeMillis() + 24 * 3 * 60 * 60 * 1000); BigInteger serialNumber = new BigInteger(128, random); X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(nameBuilder.build(), serialNumber, notBefore, notAfter, nameBuilder.build(), keyPair.getPublic()); ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption") .build(keyPair.getPrivate()); X509Certificate certificate = new JcaX509CertificateConverter() .getCertificate(certificateBuilder.build(contentSigner)); return new X509Certificate[] { certificate }; } private KeyPair createKey() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(config.keysize, random); KeyPair pair = keyGen.generateKeyPair(); return pair; } public void createContext(HttpHandler handler, String path) { while (path.endsWith("/")) path = path.substring(0, path.length() - 1); if (path.isEmpty()) path = "/"; HttpContext context = new HttpContext(); context.handler = handler; context.path = path; contexts.put(path, context); } public X509Certificate[] getCertificateChain() { return certificateChain; } protected boolean useGzipWhenAccepted(Response r) { return false; } }