1414
1515package com .google .api .client .extensions .jetty .auth .oauth2 ;
1616
17+ import static java .net .HttpURLConnection .HTTP_MOVED_TEMP ;
18+ import static java .net .HttpURLConnection .HTTP_OK ;
19+
1720import com .google .api .client .extensions .java6 .auth .oauth2 .VerificationCodeReceiver ;
1821import com .google .api .client .util .Throwables ;
22+ import com .sun .net .httpserver .Headers ;
23+ import com .sun .net .httpserver .HttpContext ;
24+ import com .sun .net .httpserver .HttpExchange ;
25+ import com .sun .net .httpserver .HttpHandler ;
26+ import com .sun .net .httpserver .HttpServer ;
1927import java .io .IOException ;
28+ import java .io .OutputStream ;
2029import java .io .PrintWriter ;
30+ import java .net .InetSocketAddress ;
31+ import java .net .ServerSocket ;
32+ import java .util .HashMap ;
33+ import java .util .Map ;
2134import java .util .concurrent .Semaphore ;
22- import javax .servlet .http .HttpServletRequest ;
23- import javax .servlet .http .HttpServletResponse ;
24- import org .eclipse .jetty .server .Connector ;
25- import org .eclipse .jetty .server .Request ;
26- import org .eclipse .jetty .server .Server ;
27- import org .eclipse .jetty .server .handler .AbstractHandler ;
2835
2936/**
3037 * OAuth 2.0 verification code receiver that runs a Jetty server on a free port, waiting for a
3441 * Implementation is thread-safe.
3542 * </p>
3643 *
37- * @since 1.11
3844 * @author Yaniv Inbar
45+ * @since 1.11
3946 */
4047public final class LocalServerReceiver implements VerificationCodeReceiver {
4148
4249 private static final String LOCALHOST = "localhost" ;
4350
4451 private static final String CALLBACK_PATH = "/Callback" ;
4552
46- /** Server or {@code null} before {@link #getRedirectUri()}. */
47- private Server server ;
53+ /**
54+ * Server or {@code null} before {@link #getRedirectUri()}.
55+ */
56+ private HttpServer server ;
4857
49- /** Verification code or {@code null} for none. */
58+ /**
59+ * Verification code or {@code null} for none.
60+ */
5061 String code ;
5162
52- /** Error code or {@code null} for none. */
63+ /**
64+ * Error code or {@code null} for none.
65+ */
5366 String error ;
5467
55- /** To block until receiving an authorization response or stop() is called. */
68+ /**
69+ * To block until receiving an authorization response or stop() is called.
70+ */
5671 final Semaphore waitUnlessSignaled = new Semaphore (0 /* initially zero permit */ );
5772
58- /** Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. */
73+ /**
74+ * Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}.
75+ */
5976 private int port ;
6077
61- /** Host name to use. */
78+ /**
79+ * Host name to use.
80+ */
6281 private final String host ;
6382
64- /** Callback path of redirect_uri */
83+ /**
84+ * Callback path of redirect_uri.
85+ */
6586 private final String callbackPath ;
6687
6788 /**
@@ -71,8 +92,8 @@ public final class LocalServerReceiver implements VerificationCodeReceiver {
7192 private String successLandingPageUrl ;
7293
7394 /**
74- * URL to an HTML page to be shown (via redirect) after failed login. If null, a canned
75- * default landing page will be shown (via direct response).
95+ * URL to an HTML page to be shown (via redirect) after failed login. If null, a canned default
96+ * landing page will be shown (via direct response).
7697 */
7798 private String failureLandingPageUrl ;
7899
@@ -94,7 +115,7 @@ public LocalServerReceiver() {
94115 * @param port Port to use or {@code -1} to select an unused port
95116 */
96117 LocalServerReceiver (String host , int port ,
97- String successLandingPageUrl , String failureLandingPageUrl ) {
118+ String successLandingPageUrl , String failureLandingPageUrl ) {
98119 this (host , port , CALLBACK_PATH , successLandingPageUrl , failureLandingPageUrl );
99120 }
100121
@@ -105,7 +126,7 @@ public LocalServerReceiver() {
105126 * @param port Port to use or {@code -1} to select an unused port
106127 */
107128 LocalServerReceiver (String host , int port , String callbackPath ,
108- String successLandingPageUrl , String failureLandingPageUrl ) {
129+ String successLandingPageUrl , String failureLandingPageUrl ) {
109130 this .host = host ;
110131 this .port = port ;
111132 this .callbackPath = callbackPath ;
@@ -115,28 +136,42 @@ public LocalServerReceiver() {
115136
116137 @ Override
117138 public String getRedirectUri () throws IOException {
118- server = new Server (port != -1 ? port : 0 );
119- Connector connector = server .getConnectors ()[0 ];
120- connector .setHost (host );
121- server .setHandler (new CallbackHandler ());
139+
140+ server = HttpServer .create (new InetSocketAddress (port != -1 ? port : findOpenPort ()), 0 );
141+ HttpContext context = server .createContext (callbackPath , new CallbackHandler ());
142+ server .setExecutor (null );
143+
122144 try {
123145 server .start ();
124- port = connector . getLocalPort ();
146+ port = server . getAddress (). getPort ();
125147 } catch (Exception e ) {
126148 Throwables .propagateIfPossible (e );
127149 throw new IOException (e );
128150 }
129- return "http://" + connector .getHost () + ":" + port + callbackPath ;
151+ return "http://" + this .getHost () + ":" + port + callbackPath ;
152+ }
153+
154+ /*
155+ *Copied from Jetty findFreePort() as referenced by: https://gist.github.com/vorburger/3429822
156+ */
157+
158+ private int findOpenPort () {
159+ try (ServerSocket socket = new ServerSocket (0 )) {
160+ socket .setReuseAddress (true );
161+ return socket .getLocalPort ();
162+ } catch (IOException e ) {
163+ throw new IllegalStateException ("No free TCP/IP port to start embedded HTTP Server on" );
164+ }
130165 }
131166
132167 /**
133- * Blocks until the server receives a login result, or the server is stopped
134- * by {@link #stop()}, to return an authorization code.
168+ * Blocks until the server receives a login result, or the server is stopped by {@link #stop()},
169+ * to return an authorization code.
135170 *
136- * @return authorization code if login succeeds; may return {@code null} if the server
137- * is stopped by {@link #stop()}
138- * @throws IOException if the server receives an error code (through an HTTP request
139- * parameter {@code error})
171+ * @return authorization code if login succeeds; may return {@code null} if the server is stopped
172+ * by {@link #stop()}
173+ * @throws IOException if the server receives an error code (through an HTTP request parameter
174+ * {@code error})
140175 */
141176 @ Override
142177 public String waitForCode () throws IOException {
@@ -152,7 +187,7 @@ public void stop() throws IOException {
152187 waitUnlessSignaled .release ();
153188 if (server != null ) {
154189 try {
155- server .stop ();
190+ server .stop (0 );
156191 } catch (Exception e ) {
157192 Throwables .propagateIfPossible (e );
158193 throw new IOException (e );
@@ -161,7 +196,9 @@ public void stop() throws IOException {
161196 }
162197 }
163198
164- /** Returns the host name to use. */
199+ /**
200+ * Returns the host name to use.
201+ */
165202 public String getHost () {
166203 return host ;
167204 }
@@ -189,51 +226,69 @@ public String getCallbackPath() {
189226 */
190227 public static final class Builder {
191228
192- /** Host name to use. */
229+ /**
230+ * Host name to use.
231+ */
193232 private String host = LOCALHOST ;
194233
195- /** Port to use or {@code -1} to select an unused port. */
234+ /**
235+ * Port to use or {@code -1} to select an unused port.
236+ */
196237 private int port = -1 ;
197238
198239 private String successLandingPageUrl ;
199240 private String failureLandingPageUrl ;
200241
201242 private String callbackPath = CALLBACK_PATH ;
202243
203- /** Builds the {@link LocalServerReceiver}. */
244+ /**
245+ * Builds the {@link LocalServerReceiver}.
246+ */
204247 public LocalServerReceiver build () {
205248 return new LocalServerReceiver (host , port , callbackPath ,
206- successLandingPageUrl , failureLandingPageUrl );
249+ successLandingPageUrl , failureLandingPageUrl );
207250 }
208251
209- /** Returns the host name to use. */
252+ /**
253+ * Returns the host name to use.
254+ */
210255 public String getHost () {
211256 return host ;
212257 }
213258
214- /** Sets the host name to use. */
259+ /**
260+ * Sets the host name to use.
261+ */
215262 public Builder setHost (String host ) {
216263 this .host = host ;
217264 return this ;
218265 }
219266
220- /** Returns the port to use or {@code -1} to select an unused port. */
267+ /**
268+ * Returns the port to use or {@code -1} to select an unused port.
269+ */
221270 public int getPort () {
222271 return port ;
223272 }
224273
225- /** Sets the port to use or {@code -1} to select an unused port. */
274+ /**
275+ * Sets the port to use or {@code -1} to select an unused port.
276+ */
226277 public Builder setPort (int port ) {
227278 this .port = port ;
228279 return this ;
229280 }
230281
231- /** Returns the callback path of redirect_uri */
282+ /**
283+ * Returns the callback path of redirect_uri.
284+ */
232285 public String getCallbackPath () {
233286 return callbackPath ;
234287 }
235288
236- /** Set the callback path of redirect_uri */
289+ /**
290+ * Set the callback path of redirect_uri.
291+ */
237292 public Builder setCallbackPath (String callbackPath ) {
238293 this .callbackPath = callbackPath ;
239294 return this ;
@@ -247,51 +302,73 @@ public Builder setLandingPages(String successLandingPageUrl, String failureLandi
247302 }
248303
249304 /**
250- * Jetty handler that takes the verifier token passed over from the OAuth provider and stashes it
251- * where {@link #waitForCode} will find it.
305+ * HttpServer handler that takes the verifier token passed over from the OAuth provider and
306+ * stashes it where {@link #waitForCode} will find it.
252307 */
253- class CallbackHandler extends AbstractHandler {
308+ class CallbackHandler implements HttpHandler {
254309
255310 @ Override
256- public void handle (
257- String target , Request baseRequest , HttpServletRequest request , HttpServletResponse response
258- )
259- throws IOException {
260- if (!callbackPath .equals (target )) {
311+ public void handle (HttpExchange httpExchange ) throws IOException {
312+
313+ if (!callbackPath .equals (httpExchange .getRequestURI ().getPath ())) {
261314 return ;
262315 }
263316
317+ StringBuilder body = new StringBuilder ();
318+
264319 try {
265- ((Request ) request ).setHandled (true );
266- error = request .getParameter ("error" );
267- code = request .getParameter ("code" );
320+ Map <String , String > parms =
321+ this .queryToMap (httpExchange .getRequestURI ().getQuery ());
322+ error = parms .get ("error" );
323+ code = parms .get ("code" );
268324
325+ Headers respHeaders = httpExchange .getResponseHeaders ();
269326 if (error == null && successLandingPageUrl != null ) {
270- response .sendRedirect (successLandingPageUrl );
327+ respHeaders .add ("Location" , successLandingPageUrl );
328+ httpExchange .sendResponseHeaders (HTTP_MOVED_TEMP , -1 );
271329 } else if (error != null && failureLandingPageUrl != null ) {
272- response .sendRedirect (failureLandingPageUrl );
330+ respHeaders .add ("Location" , failureLandingPageUrl );
331+ httpExchange .sendResponseHeaders (HTTP_MOVED_TEMP , -1 );
273332 } else {
274- writeLandingHtml (response );
333+ writeLandingHtml (httpExchange , respHeaders );
275334 }
276- response .flushBuffer ();
277- }
278- finally {
335+ httpExchange .close ();
336+ } finally {
279337 waitUnlessSignaled .release ();
280338 }
281339 }
282340
283- private void writeLandingHtml (HttpServletResponse response ) throws IOException {
284- response .setStatus (HttpServletResponse .SC_OK );
285- response .setContentType ("text/html" );
341+ private Map <String , String > queryToMap (String query ) {
342+ Map <String , String > result = new HashMap <String , String >();
343+ if (query != null ) {
344+ for (String param : query .split ("&" )) {
345+ String pair [] = param .split ("=" );
346+ if (pair .length > 1 ) {
347+ result .put (pair [0 ], pair [1 ]);
348+ } else {
349+ result .put (pair [0 ], "" );
350+ }
351+ }
352+ }
353+ return result ;
354+ }
355+
356+ private void writeLandingHtml (HttpExchange exchange , Headers headers ) throws IOException {
357+ OutputStream os = exchange .getResponseBody ();
358+ exchange .sendResponseHeaders (HTTP_OK , 0 );
359+ headers .add ("ContentType" , "text/html" );
286360
287- PrintWriter doc = response . getWriter ( );
361+ PrintWriter doc = new PrintWriter ( os );
288362 doc .println ("<html>" );
289363 doc .println ("<head><title>OAuth 2.0 Authentication Token Received</title></head>" );
290364 doc .println ("<body>" );
291365 doc .println ("Received verification code. You may now close this window." );
292366 doc .println ("</body>" );
293367 doc .println ("</html>" );
294368 doc .flush ();
369+ os .close ();
295370 }
371+
296372 }
373+
297374}
0 commit comments