11/*
2- * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
33 *
44 * This program and the accompanying materials are made available under the
55 * terms of the Eclipse Public License v. 2.0, which is available at
1818
1919import jakarta .ws .rs .SeBootstrap ;
2020import jakarta .ws .rs .core .UriBuilder ;
21+ import org .glassfish .jersey .internal .config .ExternalPropertiesConfigurationFactory ;
22+ import org .glassfish .jersey .internal .config .SystemPropertiesConfigurationModel ;
23+ import org .glassfish .jersey .internal .util .PropertiesClass ;
2124import org .glassfish .jersey .server .internal .LocalizationMessages ;
2225import org .glassfish .jersey .server .spi .Container ;
2326import org .glassfish .jersey .server .spi .WebServer ;
2427
2528import javax .net .ssl .SSLContext ;
29+ import java .io .IOException ;
30+ import java .net .ServerSocket ;
2631import java .net .URI ;
2732import java .security .NoSuchAlgorithmException ;
33+ import java .util .Collections ;
2834import java .util .HashMap ;
2935import java .util .Map ;
3036import java .util .Optional ;
37+ import java .util .Random ;
3138import java .util .function .BiFunction ;
3239import java .util .logging .Logger ;
3340
41+ import static java .lang .Boolean .FALSE ;
3442import static java .lang .Boolean .TRUE ;
3543
3644/**
4048 */
4149public final class JerseySeBootstrapConfiguration implements SeBootstrap .Configuration {
4250 private static final Logger LOGGER = Logger .getLogger (JerseySeBootstrapConfiguration .class .getName ());
51+ protected static final Random RANDOM = new Random ();
4352 private final SeBootstrap .Configuration configuration ;
4453
4554 private JerseySeBootstrapConfiguration (SeBootstrap .Configuration configuration ) {
@@ -61,16 +70,60 @@ public Object property(String name) {
6170 public URI uri (boolean resolveDefaultPort ) {
6271 final String protocol = configuration .protocol ();
6372 final String host = configuration .host ();
64- final int configPort = configuration .port ();
65- final int port = (configPort < 0 && resolveDefaultPort )
66- ? isHttps () ? Container .DEFAULT_HTTPS_PORT : Container .DEFAULT_HTTP_PORT
67- : configPort ;
73+ final int port = resolveDefaultPort ? resolvePort () : configuration .port ();
6874 final String rootPath = configuration .rootPath ();
6975 final URI uri = UriBuilder .newInstance ().scheme (protocol .toLowerCase ()).host (host ).port (port ).path (rootPath )
7076 .build ();
7177 return uri ;
7278 }
7379
80+ private int resolvePort () {
81+ final int configPort = configuration .port ();
82+ final int basePort = allowPrivilegedPorts () ? 0 : 8000 ;
83+ final int port ;
84+ switch (configPort ) {
85+ case SeBootstrap .Configuration .DEFAULT_PORT :
86+ port = basePort + (isHttps () ? Container .DEFAULT_HTTPS_PORT : Container .DEFAULT_HTTP_PORT );
87+ break ;
88+ case SeBootstrap .Configuration .FREE_PORT :
89+ port = _resolvePort (basePort == 0 );
90+ break ;
91+ default :
92+ port = configPort ;
93+ break ;
94+ }
95+ return port ;
96+ }
97+
98+ private int _resolvePort (boolean allowPrivilegedPort ) {
99+ final int basePort = allowPrivilegedPort ? 0 : 1023 ;
100+ // Get the initial range parameters
101+ final int lower = basePort ;
102+ final int range = 0xFFFF ;
103+
104+ // Select a start point in the range
105+ final int initialOffset = RANDOM .nextInt (range - lower );
106+
107+ // Loop the offset through all ports in the range and attempt
108+ // to bind to each
109+ int offset = initialOffset ;
110+ ServerSocket socket ;
111+ do {
112+ final int port = lower + offset ;
113+ try {
114+ socket = new ServerSocket (port );
115+ socket .close ();
116+ return port ;
117+ } catch (IOException caught ) {
118+ // Swallow exceptions until the end
119+ }
120+ offset = (offset + 1 ) % range ;
121+ } while (offset != initialOffset );
122+
123+ // If a port can't be bound, throw the exception
124+ throw new IllegalArgumentException (LocalizationMessages .COULD_NOT_BIND_TO_ANY_PORT ());
125+ }
126+
74127 /**
75128 * Return {@link SSLContext} in the configuration if the protocol scheme is {@code HTTPS}.
76129 * @return the SSLContext in the configuration.
@@ -100,6 +153,16 @@ public boolean autoStart() {
100153 return autoStart ;
101154 }
102155
156+ /**
157+ * Defines if the {@link WebServer} should start on a privileged port when port is not set.
158+ * @return true if {@link ServerProperties#WEBSERVER_AUTO_START} is {@code true}, {@code false} otherwise.
159+ */
160+ public boolean allowPrivilegedPorts () {
161+ return Optional .ofNullable (
162+ (Boolean ) configuration .property (ServerProperties .WEBSERVER_ALLOW_PRIVILEGED_PORTS ))
163+ .orElse (FALSE );
164+ }
165+
103166 /**
104167 * Factory method creating {@code JerseySeBootstrapConfiguration} wrapper around {@link SeBootstrap.Configuration}.
105168 * @param configuration wrapped configuration
@@ -129,16 +192,17 @@ public static final class Builder implements SeBootstrap.Configuration.Builder {
129192 PROPERTY_TYPES .put (SeBootstrap .Configuration .ROOT_PATH , String .class );
130193 PROPERTY_TYPES .put (SeBootstrap .Configuration .SSL_CONTEXT , SSLContext .class );
131194 PROPERTY_TYPES .put (SeBootstrap .Configuration .SSL_CLIENT_AUTHENTICATION , SSLClientAuthentication .class );
132- PROPERTY_TYPES .put (ServerProperties .WEBSERVER_CLASS , Class .class );
195+ PROPERTY_TYPES .put (ServerProperties .WEBSERVER_ALLOW_PRIVILEGED_PORTS , Boolean .class );
133196 PROPERTY_TYPES .put (ServerProperties .WEBSERVER_AUTO_START , Boolean .class );
197+ PROPERTY_TYPES .put (ServerProperties .WEBSERVER_CLASS , Class .class );
134198 }
135199
136200 private final Map <String , Object > properties = new HashMap <>();
137201
138202 private Builder () {
139203 this .properties .put (SeBootstrap .Configuration .PROTOCOL , "HTTP" ); // upper case mandated by javadoc
140204 this .properties .put (SeBootstrap .Configuration .HOST , "localhost" );
141- this .properties .put (SeBootstrap .Configuration .PORT , -1 ); // Auto-select port 80 for HTTP or 443 for HTTPS
205+ this .properties .put (SeBootstrap .Configuration .PORT , -1 ); // Auto-select port 8080 for HTTP or 8443 for HTTPS
142206 this .properties .put (SeBootstrap .Configuration .ROOT_PATH , "/" );
143207 this .properties .put (ServerProperties .WEBSERVER_CLASS , WebServer .class ); // Auto-select first provider
144208 try {
@@ -149,6 +213,15 @@ private Builder() {
149213 this .properties .put (SeBootstrap .Configuration .SSL_CLIENT_AUTHENTICATION ,
150214 SeBootstrap .Configuration .SSLClientAuthentication .NONE );
151215 this .properties .put (ServerProperties .WEBSERVER_AUTO_START , TRUE );
216+ this .properties .put (ServerProperties .WEBSERVER_ALLOW_PRIVILEGED_PORTS , FALSE );
217+
218+ SystemPropertiesConfigurationModel propertiesConfigurationModel = new SystemPropertiesConfigurationModel (
219+ Collections .singletonList (Properties .class .getName ())
220+ );
221+ from ((name , aClass ) -> String .class .equals (aClass ) || Integer .class .equals (aClass ) || Boolean .class .equals (aClass )
222+ ? propertiesConfigurationModel .getOptionalProperty (name , aClass )
223+ : Optional .empty ()
224+ );
152225 }
153226
154227 @ Override
@@ -208,4 +281,41 @@ public JerseySeBootstrapConfiguration.Builder from(Object externalConfig) {
208281 return this ;
209282 }
210283 }
284+
285+ /**
286+ * Name the properties to be internally read from System properties by {@link ExternalPropertiesConfigurationFactory}.
287+ * This is required just when SecurityManager is on, otherwise all system properties are read.
288+ */
289+ @ PropertiesClass
290+ private static class Properties {
291+ /**
292+ * See {@link SeBootstrap.Configuration#PROTOCOL} property.
293+ */
294+ public static final String SE_BOOTSTRAP_CONFIGURATION_PROTOCOL = SeBootstrap .Configuration .PROTOCOL ;
295+
296+ /**
297+ * See {@link SeBootstrap.Configuration#HOST} property.
298+ */
299+ public static final String SE_BOOTSTRAP_CONFIGURATION_HOST = SeBootstrap .Configuration .HOST ;
300+
301+ /**
302+ * See {@link SeBootstrap.Configuration#PORT} property.
303+ */
304+ public static final String SE_BOOTSTRAP_CONFIGURATION_PORT = SeBootstrap .Configuration .PORT ;
305+
306+ /**
307+ * See {@link SeBootstrap.Configuration#ROOT_PATH} property.
308+ */
309+ public static final String SE_BOOTSTRAP_CONFIGURATION_ROOT_PATH = SeBootstrap .Configuration .ROOT_PATH ;
310+
311+ /**
312+ * See {@link ServerProperties#WEBSERVER_ALLOW_PRIVILEGED_PORTS} property.
313+ */
314+ public static final String WEBSERVER_ALLOW_PRIVILEGED_PORTS = ServerProperties .WEBSERVER_ALLOW_PRIVILEGED_PORTS ;
315+
316+ /**
317+ * See {@link ServerProperties#WEBSERVER_AUTO_START} property.
318+ */
319+ public static final String WEBSERVER_AUTO_START = ServerProperties .WEBSERVER_AUTO_START ;
320+ }
211321}
0 commit comments