diff --git a/google-oauth-client-jetty/pom.xml b/google-oauth-client-jetty/pom.xml index d37ca4e60..1bcfbc6b7 100644 --- a/google-oauth-client-jetty/pom.xml +++ b/google-oauth-client-jetty/pom.xml @@ -86,10 +86,6 @@ com.google.oauth-client google-oauth-client-java6 - - org.eclipse.jetty - jetty-server - junit junit diff --git a/google-oauth-client-jetty/src/main/java/com/google/api/client/extensions/jetty/auth/oauth2/LocalServerReceiver.java b/google-oauth-client-jetty/src/main/java/com/google/api/client/extensions/jetty/auth/oauth2/LocalServerReceiver.java index 06f26f873..341661a10 100644 --- a/google-oauth-client-jetty/src/main/java/com/google/api/client/extensions/jetty/auth/oauth2/LocalServerReceiver.java +++ b/google-oauth-client-jetty/src/main/java/com/google/api/client/extensions/jetty/auth/oauth2/LocalServerReceiver.java @@ -14,17 +14,24 @@ package com.google.api.client.extensions.jetty.auth.oauth2; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static java.net.HttpURLConnection.HTTP_OK; + import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver; import com.google.api.client.util.Throwables; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintWriter; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Semaphore; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; /** * OAuth 2.0 verification code receiver that runs a Jetty server on a free port, waiting for a @@ -34,8 +41,8 @@ * Implementation is thread-safe. *

* - * @since 1.11 * @author Yaniv Inbar + * @since 1.11 */ public final class LocalServerReceiver implements VerificationCodeReceiver { @@ -43,25 +50,39 @@ public final class LocalServerReceiver implements VerificationCodeReceiver { private static final String CALLBACK_PATH = "/Callback"; - /** Server or {@code null} before {@link #getRedirectUri()}. */ - private Server server; + /** + * Server or {@code null} before {@link #getRedirectUri()}. + */ + private HttpServer server; - /** Verification code or {@code null} for none. */ + /** + * Verification code or {@code null} for none. + */ String code; - /** Error code or {@code null} for none. */ + /** + * Error code or {@code null} for none. + */ String error; - /** To block until receiving an authorization response or stop() is called. */ + /** + * To block until receiving an authorization response or stop() is called. + */ final Semaphore waitUnlessSignaled = new Semaphore(0 /* initially zero permit */); - /** Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. */ + /** + * Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. + */ private int port; - /** Host name to use. */ + /** + * Host name to use. + */ private final String host; - /** Callback path of redirect_uri */ + /** + * Callback path of redirect_uri. + */ private final String callbackPath; /** @@ -71,8 +92,8 @@ public final class LocalServerReceiver implements VerificationCodeReceiver { private String successLandingPageUrl; /** - * URL to an HTML page to be shown (via redirect) after failed login. If null, a canned - * default landing page will be shown (via direct response). + * URL to an HTML page to be shown (via redirect) after failed login. If null, a canned default + * landing page will be shown (via direct response). */ private String failureLandingPageUrl; @@ -94,7 +115,7 @@ public LocalServerReceiver() { * @param port Port to use or {@code -1} to select an unused port */ LocalServerReceiver(String host, int port, - String successLandingPageUrl, String failureLandingPageUrl) { + String successLandingPageUrl, String failureLandingPageUrl) { this(host, port, CALLBACK_PATH, successLandingPageUrl, failureLandingPageUrl); } @@ -105,7 +126,7 @@ public LocalServerReceiver() { * @param port Port to use or {@code -1} to select an unused port */ LocalServerReceiver(String host, int port, String callbackPath, - String successLandingPageUrl, String failureLandingPageUrl) { + String successLandingPageUrl, String failureLandingPageUrl) { this.host = host; this.port = port; this.callbackPath = callbackPath; @@ -115,28 +136,42 @@ public LocalServerReceiver() { @Override public String getRedirectUri() throws IOException { - server = new Server(port != -1 ? port : 0); - Connector connector = server.getConnectors()[0]; - connector.setHost(host); - server.setHandler(new CallbackHandler()); + + server = HttpServer.create(new InetSocketAddress(port != -1 ? port : findOpenPort()), 0); + HttpContext context = server.createContext(callbackPath, new CallbackHandler()); + server.setExecutor(null); + try { server.start(); - port = connector.getLocalPort(); + port = server.getAddress().getPort(); } catch (Exception e) { Throwables.propagateIfPossible(e); throw new IOException(e); } - return "http://" + connector.getHost() + ":" + port + callbackPath; + return "http://" + this.getHost() + ":" + port + callbackPath; + } + + /* + *Copied from Jetty findFreePort() as referenced by: https://gist.github.com/vorburger/3429822 + */ + + private int findOpenPort() { + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + return socket.getLocalPort(); + } catch (IOException e) { + throw new IllegalStateException("No free TCP/IP port to start embedded HTTP Server on"); + } } /** - * Blocks until the server receives a login result, or the server is stopped - * by {@link #stop()}, to return an authorization code. + * Blocks until the server receives a login result, or the server is stopped by {@link #stop()}, + * to return an authorization code. * - * @return authorization code if login succeeds; may return {@code null} if the server - * is stopped by {@link #stop()} - * @throws IOException if the server receives an error code (through an HTTP request - * parameter {@code error}) + * @return authorization code if login succeeds; may return {@code null} if the server is stopped + * by {@link #stop()} + * @throws IOException if the server receives an error code (through an HTTP request parameter + * {@code error}) */ @Override public String waitForCode() throws IOException { @@ -152,7 +187,7 @@ public void stop() throws IOException { waitUnlessSignaled.release(); if (server != null) { try { - server.stop(); + server.stop(0); } catch (Exception e) { Throwables.propagateIfPossible(e); throw new IOException(e); @@ -161,7 +196,9 @@ public void stop() throws IOException { } } - /** Returns the host name to use. */ + /** + * Returns the host name to use. + */ public String getHost() { return host; } @@ -189,10 +226,14 @@ public String getCallbackPath() { */ public static final class Builder { - /** Host name to use. */ + /** + * Host name to use. + */ private String host = LOCALHOST; - /** Port to use or {@code -1} to select an unused port. */ + /** + * Port to use or {@code -1} to select an unused port. + */ private int port = -1; private String successLandingPageUrl; @@ -200,40 +241,54 @@ public static final class Builder { private String callbackPath = CALLBACK_PATH; - /** Builds the {@link LocalServerReceiver}. */ + /** + * Builds the {@link LocalServerReceiver}. + */ public LocalServerReceiver build() { return new LocalServerReceiver(host, port, callbackPath, - successLandingPageUrl, failureLandingPageUrl); + successLandingPageUrl, failureLandingPageUrl); } - /** Returns the host name to use. */ + /** + * Returns the host name to use. + */ public String getHost() { return host; } - /** Sets the host name to use. */ + /** + * Sets the host name to use. + */ public Builder setHost(String host) { this.host = host; return this; } - /** Returns the port to use or {@code -1} to select an unused port. */ + /** + * Returns the port to use or {@code -1} to select an unused port. + */ public int getPort() { return port; } - /** Sets the port to use or {@code -1} to select an unused port. */ + /** + * Sets the port to use or {@code -1} to select an unused port. + */ public Builder setPort(int port) { this.port = port; return this; } - /** Returns the callback path of redirect_uri */ + /** + * Returns the callback path of redirect_uri. + */ public String getCallbackPath() { return callbackPath; } - /** Set the callback path of redirect_uri */ + /** + * Set the callback path of redirect_uri. + */ public Builder setCallbackPath(String callbackPath) { this.callbackPath = callbackPath; return this; @@ -247,44 +302,63 @@ public Builder setLandingPages(String successLandingPageUrl, String failureLandi } /** - * Jetty handler that takes the verifier token passed over from the OAuth provider and stashes it - * where {@link #waitForCode} will find it. + * HttpServer handler that takes the verifier token passed over from the OAuth provider and + * stashes it where {@link #waitForCode} will find it. */ - class CallbackHandler extends AbstractHandler { + class CallbackHandler implements HttpHandler { @Override - public void handle( - String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response - ) - throws IOException { - if (!callbackPath.equals(target)) { + public void handle(HttpExchange httpExchange) throws IOException { + + if (!callbackPath.equals(httpExchange.getRequestURI().getPath())) { return; } + StringBuilder body = new StringBuilder(); + try { - ((Request) request).setHandled(true); - error = request.getParameter("error"); - code = request.getParameter("code"); + Map parms = + this.queryToMap(httpExchange.getRequestURI().getQuery()); + error = parms.get("error"); + code = parms.get("code"); + Headers respHeaders = httpExchange.getResponseHeaders(); if (error == null && successLandingPageUrl != null) { - response.sendRedirect(successLandingPageUrl); + respHeaders.add("Location", successLandingPageUrl); + httpExchange.sendResponseHeaders(HTTP_MOVED_TEMP, -1); } else if (error != null && failureLandingPageUrl != null) { - response.sendRedirect(failureLandingPageUrl); + respHeaders.add("Location", failureLandingPageUrl); + httpExchange.sendResponseHeaders(HTTP_MOVED_TEMP, -1); } else { - writeLandingHtml(response); + writeLandingHtml(httpExchange, respHeaders); } - response.flushBuffer(); - } - finally { + httpExchange.close(); + } finally { waitUnlessSignaled.release(); } } - private void writeLandingHtml(HttpServletResponse response) throws IOException { - response.setStatus(HttpServletResponse.SC_OK); - response.setContentType("text/html"); + private Map queryToMap(String query) { + Map result = new HashMap(); + if (query != null) { + for (String param : query.split("&")) { + String pair[] = param.split("="); + if (pair.length > 1) { + result.put(pair[0], pair[1]); + } else { + result.put(pair[0], ""); + } + } + } + return result; + } + + private void writeLandingHtml(HttpExchange exchange, Headers headers) throws IOException { + OutputStream os = exchange.getResponseBody(); + exchange.sendResponseHeaders(HTTP_OK, 0); + headers.add("ContentType", "text/html"); - PrintWriter doc = response.getWriter(); + PrintWriter doc = new PrintWriter(os); doc.println(""); doc.println("OAuth 2.0 Authentication Token Received"); doc.println(""); @@ -292,6 +366,9 @@ private void writeLandingHtml(HttpServletResponse response) throws IOException { doc.println(""); doc.println(""); doc.flush(); + os.close(); } + } + } diff --git a/pom.xml b/pom.xml index 71a42f8b8..b3dd6d9dd 100644 --- a/pom.xml +++ b/pom.xml @@ -150,11 +150,6 @@ google-oauth-client-jetty ${project.version}
- - org.eclipse.jetty - jetty-server - ${project.jetty.version} - org.datanucleus datanucleus-core @@ -399,6 +394,9 @@ java17 1.0 + + com.sun.net.httpserver.* +