1 package org.jsoup.integration; 2 3 import org.eclipse.jetty.http.HttpVersion; 4 import org.eclipse.jetty.server.Connector; 5 import org.eclipse.jetty.server.HttpConfiguration; 6 import org.eclipse.jetty.server.HttpConnectionFactory; 7 import org.eclipse.jetty.server.SecureRequestCustomizer; 8 import org.eclipse.jetty.server.Server; 9 import org.eclipse.jetty.server.ServerConnector; 10 import org.eclipse.jetty.server.SslConnectionFactory; 11 import org.eclipse.jetty.server.handler.HandlerWrapper; 12 import org.eclipse.jetty.servlet.FilterHolder; 13 import org.eclipse.jetty.servlet.FilterMapping; 14 import org.eclipse.jetty.servlet.ServletHandler; 15 import org.eclipse.jetty.util.ssl.SslContextFactory; 16 import org.jsoup.integration.servlets.AuthFilter; 17 import org.jsoup.integration.servlets.BaseServlet; 18 import org.jsoup.integration.servlets.ProxyServlet; 19 20 import javax.net.ssl.HttpsURLConnection; 21 import javax.net.ssl.SSLContext; 22 import javax.net.ssl.SSLSocketFactory; 23 import javax.net.ssl.TrustManager; 24 import javax.net.ssl.TrustManagerFactory; 25 import java.io.File; 26 import java.io.FileNotFoundException; 27 import java.io.IOException; 28 import java.net.InetSocketAddress; 29 import java.nio.file.Files; 30 import java.security.KeyManagementException; 31 import java.security.KeyStore; 32 import java.security.KeyStoreException; 33 import java.security.NoSuchAlgorithmException; 34 import java.security.cert.CertificateException; 35 import java.util.concurrent.atomic.AtomicInteger; 36 37 public class TestServer { 38 static int Port; 39 static int TlsPort; 40 41 private static final String Localhost = "localhost"; 42 private static final String KeystorePassword = "hunter2"; 43 44 private static final Server Jetty = newServer(); 45 private static final ServletHandler JettyHandler = new ServletHandler(); 46 private static final Server Proxy = newServer(); 47 private static final Server AuthedProxy = newServer(); 48 private static final HandlerWrapper ProxyHandler = new HandlerWrapper(); 49 private static final HandlerWrapper AuthedProxyHandler = new HandlerWrapper(); 50 private static final ProxySettings ProxySettings = new ProxySettings(); 51 newServer()52 private static Server newServer() { 53 return new Server(new InetSocketAddress(Localhost, 0)); 54 } 55 56 static { 57 Jetty.setHandler(JettyHandler); 58 Proxy.setHandler(ProxyHandler); 59 AuthedProxy.setHandler(AuthedProxyHandler); 60 61 // TLS setup: 62 try { 63 File keystoreFile = ParseTest.getFile("/local-cert/server.pfx"); keystoreFile.toString()64 if (!keystoreFile.exists()) throw new FileNotFoundException(keystoreFile.toString()); addHttpsConnector(keystoreFile, Jetty)65 addHttpsConnector(keystoreFile, Jetty); 66 setupDefaultTrust(keystoreFile); 67 } catch (Exception e) { 68 throw new IllegalStateException(e); 69 } 70 } 71 TestServer()72 private TestServer() { 73 } 74 start()75 public static void start() { 76 synchronized (Jetty) { 77 if (Jetty.isStarted()) return; 78 79 try { 80 Jetty.start(); 81 JettyHandler.addFilterWithMapping(new FilterHolder(new AuthFilter(false, false)), "/*", FilterMapping.ALL); 82 Connector[] jcons = Jetty.getConnectors(); 83 Port = ((ServerConnector) jcons[0]).getLocalPort(); 84 TlsPort = ((ServerConnector) jcons[1]).getLocalPort(); 85 86 ProxyHandler.setHandler(ProxyServlet.createHandler(false)); // includes proxy, CONNECT proxy, and Auth filters 87 Proxy.start(); 88 ProxySettings.port = ((ServerConnector) Proxy.getConnectors()[0]).getLocalPort(); 89 90 AuthedProxyHandler.setHandler(ProxyServlet.createHandler(true)); 91 AuthedProxy.start(); 92 ProxySettings.authedPort = ((ServerConnector) AuthedProxy.getConnectors()[0]).getLocalPort(); 93 } catch (Exception e) { 94 throw new IllegalStateException(e); 95 } 96 } 97 } 98 99 /** 100 Close any current connections to the authed proxy. Tunneled connections only authenticate in their first 101 CONNECT, and may be kept alive and reused. So when we want to test unauthed - authed flows, we need to disconnect 102 them first. 103 */ closeAuthedProxyConnections()104 static int closeAuthedProxyConnections() { 105 ServerConnector connector = (ServerConnector) AuthedProxy.getConnectors()[0]; 106 AtomicInteger count = new AtomicInteger(); 107 connector.getConnectedEndPoints().forEach(endPoint -> { 108 endPoint.close(); 109 count.getAndIncrement(); 110 }); 111 return count.get(); 112 } 113 map(Class<? extends BaseServlet> servletClass)114 public static ServletUrls map(Class<? extends BaseServlet> servletClass) { 115 synchronized (Jetty) { 116 if (!Jetty.isStarted()) 117 start(); // if running out of the test cases 118 119 String path = "/" + servletClass.getSimpleName(); 120 JettyHandler.addServletWithMapping(servletClass, path + "/*"); 121 String url = "http://" + Localhost + ":" + Port + path; 122 String tlsUrl = "https://" + Localhost + ":" + TlsPort + path; 123 124 return new ServletUrls(url, tlsUrl); 125 } 126 } 127 128 public static class ServletUrls { 129 public final String url; 130 public final String tlsUrl; 131 ServletUrls(String url, String tlsUrl)132 public ServletUrls(String url, String tlsUrl) { 133 this.url = url; 134 this.tlsUrl = tlsUrl; 135 } 136 } 137 proxySettings()138 public static ProxySettings proxySettings() { 139 synchronized (Jetty) { 140 if (!Jetty.isStarted()) 141 start(); 142 143 return ProxySettings; 144 } 145 } 146 147 //public static String proxy 148 public static class ProxySettings { 149 final String hostname = Localhost; 150 int port; 151 int authedPort; 152 } 153 addHttpsConnector(File keystoreFile, Server server)154 private static void addHttpsConnector(File keystoreFile, Server server) { 155 // Cribbed from https://github.com/jetty/jetty.project/blob/jetty-9.4.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java 156 SslContextFactory sslContextFactory = new SslContextFactory.Server(); 157 String path = keystoreFile.getAbsolutePath(); 158 sslContextFactory.setKeyStorePath(path); 159 sslContextFactory.setKeyStorePassword(KeystorePassword); 160 sslContextFactory.setKeyManagerPassword(KeystorePassword); 161 sslContextFactory.setTrustStorePath(path); 162 sslContextFactory.setTrustStorePassword(KeystorePassword); 163 164 HttpConfiguration httpConfig = new HttpConfiguration(); 165 httpConfig.setSecureScheme("https"); 166 HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); 167 httpsConfig.addCustomizer(new SecureRequestCustomizer()); 168 169 ServerConnector sslConnector = new ServerConnector( 170 server, 171 new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), 172 new HttpConnectionFactory(httpsConfig)); 173 server.addConnector(sslConnector); 174 } 175 setupDefaultTrust(File keystoreFile)176 private static void setupDefaultTrust(File keystoreFile) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException { 177 // Configure HttpsUrlConnection (jsoup) to trust (only) this cert 178 KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 179 trustStore.load(Files.newInputStream(keystoreFile.toPath()), KeystorePassword.toCharArray()); 180 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 181 trustManagerFactory.init(trustStore); 182 TrustManager[] managers = trustManagerFactory.getTrustManagers(); 183 SSLContext tls = SSLContext.getInstance("TLS"); 184 tls.init(null, managers, null); 185 SSLSocketFactory socketFactory = tls.getSocketFactory(); 186 HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); 187 } 188 } 189