WebEngine.java

1
package com.renomad.minum.web;
2
3
import com.renomad.minum.state.Constants;
4
import com.renomad.minum.logging.ILogger;
5
import com.renomad.minum.security.ITheBrig;
6
import com.renomad.minum.state.Context;
7
8
import javax.net.ssl.KeyManagerFactory;
9
import javax.net.ssl.SSLContext;
10
import java.io.IOException;
11
import java.io.InputStream;
12
import java.net.ServerSocket;
13
import java.net.Socket;
14
import java.net.URL;
15
import java.nio.file.Path;
16
import java.security.*;
17
import java.util.concurrent.ExecutorService;
18
19
import static com.renomad.minum.web.HttpServerType.ENCRYPTED_HTTP;
20
import static com.renomad.minum.web.HttpServerType.PLAIN_TEXT_HTTP;
21
22
/**
23
 * This class contains the basic internet capabilities.
24
 * <br><br>
25
 * Think of this class as managing some of the lowest-level internet
26
 * communications we need to handle to support a web application. Sockets,
27
 * Servers, Threads, that kind of stuff.
28
 */
29
final class WebEngine {
30
31
  private final ITheBrig theBrig;
32
  private final Constants constants;
33
  private final Context context;
34
  private final ExecutorService executorService;
35
  private final WebFramework webFramework;
36
37
  WebEngine(Context context, WebFramework webFramework) {
38
    this.logger = context.getLogger();
39
    this.logger.logDebug(() -> "Using a supplied logger in WebEngine");
40 1 1. <init> : negated conditional → KILLED
    this.theBrig = context.getFullSystem() != null ? context.getFullSystem().getTheBrig() : null;
41
    this.constants = context.getConstants();
42
    this.context = context;
43
    this.executorService = context.getExecutorService();
44
    this.webFramework = webFramework;
45
  }
46
47
  private final ILogger logger;
48
  static final String HTTP_CRLF = "\r\n";
49
50
  IServer startServer() {
51
    int port = constants.serverPort;
52
      ServerSocket ss;
53
      try {
54
          ss = new ServerSocket(port);
55
      } catch (Exception e) {
56
          throw new WebServerException(e);
57
      }
58
      logger.logDebug(() -> String.format("Just created a new ServerSocket: %s", ss));
59
    IServer server = new Server(ss, context, "http server", theBrig, webFramework, executorService, PLAIN_TEXT_HTTP);
60
    logger.logDebug(() -> String.format("Just created a new Server: %s", server));
61 1 1. startServer : removed call to com/renomad/minum/web/IServer::start → KILLED
    server.start();
62
    String hostname = constants.hostName;
63
    logger.logDebug(() -> String.format("%s started at http://%s:%s", server, hostname, port));
64 1 1. startServer : replaced return value with null for com/renomad/minum/web/WebEngine::startServer → TIMED_OUT
    return server;
65
  }
66
67
  IServer startSslServer() {
68
69
    /*
70
     * If we find the keystore and pass in the system properties
71
     */
72
    final var useExternalKeystore = isProvidedKeystoreProperties(constants.keystorePath, constants.keystorePassword, logger);
73
    KeyStoreResult keystoreResult = getKeyStoreResult(useExternalKeystore, constants.keystorePath, constants.keystorePassword, logger);
74
75
    int port = constants.secureServerPort;
76
    ServerSocket ss = createSslSocketWithSpecificKeystore(port, keystoreResult.keystoreUrl(), keystoreResult.keystorePassword());
77
    logger.logDebug(() -> String.format("Just created a new ServerSocket: %s", ss));
78
    IServer server = new Server(ss, context, "https server", theBrig, webFramework, executorService, ENCRYPTED_HTTP);
79
    logger.logDebug(() -> String.format("Just created a new SSL Server: %s", server));
80 1 1. startSslServer : removed call to com/renomad/minum/web/IServer::start → KILLED
    server.start();
81
    String hostname = constants.hostName;
82
    logger.logDebug(() -> String.format("%s started at https://%s:%s", server, hostname, port));
83 1 1. startSslServer : replaced return value with null for com/renomad/minum/web/WebEngine::startSslServer → KILLED
    return server;
84
  }
85
86
  static KeyStoreResult getKeyStoreResult(
87
          boolean useExternalKeystore,
88
          String keystorePath,
89
          String keystorePassword,
90
          ILogger logger) {
91 1 1. getKeyStoreResult : negated conditional → SURVIVED
    if (useExternalKeystore) {
92
      logger.logDebug(() -> "Using keystore and password referenced in minum.config");
93
    } else {
94
      logger.logDebug(() -> "Using the default (self-signed / testing-only) certificate");
95
    }
96
97
    final URL keystoreUrl;
98
    try {
99 1 1. getKeyStoreResult : negated conditional → KILLED
        keystoreUrl = useExternalKeystore ?
100
                Path.of(keystorePath).toUri().toURL() :
101
                WebEngine.class.getResource("/certs/keystore");
102
    } catch (Exception e) {
103
        throw new WebServerException("Error while building keystoreUrl: " + e);
104
    }
105 1 1. getKeyStoreResult : negated conditional → KILLED
    final String keystorePasswordFinal = useExternalKeystore ?
106
          keystorePassword :
107
          "passphrase";
108 1 1. getKeyStoreResult : replaced return value with null for com/renomad/minum/web/WebEngine::getKeyStoreResult → KILLED
    return new KeyStoreResult(keystoreUrl, keystorePasswordFinal);
109
  }
110
111
  record KeyStoreResult(URL keystoreUrl, String keystorePassword) { }
112
113
114
  /**
115
   * Look into the system properties to see whether values have been
116
   * set for the keystore and keystorePassword keys.
117
   * <p>
118
   * the key for keystore is:  javax.net.ssl.keyStore
119
   * the key for keystorePassword is: javax.net.ssl.keyStorePassword
120
   * <p>
121
   * It's smart, if you are creating a server that will run
122
   * with a genuine signed certificate, to have those files
123
   * stored somewhere and then set these system properties.  That
124
   * way, it's a characteristic of a particular server - it's not
125
   * needed to bundle the certificate with the actual server in
126
   * any way.
127
   * <p>
128
   * We *do* bundle a cert, but it's for testing and is self-signed.
129
   */
130
  static Boolean isProvidedKeystoreProperties(String keystorePath, String keystorePassword, ILogger logger) {
131
132
    // get the directory to the keystore from a system property
133 2 1. isProvidedKeystoreProperties : negated conditional → KILLED
2. isProvidedKeystoreProperties : negated conditional → KILLED
    boolean hasKeystore = ! (keystorePath == null || keystorePath.isBlank());
134 1 1. isProvidedKeystoreProperties : negated conditional → KILLED
    if (! hasKeystore) {
135
      logger.logDebug(() -> "Keystore system property was not set");
136
    }
137
138
    // get the password to that keystore from a system property
139 2 1. isProvidedKeystoreProperties : negated conditional → KILLED
2. isProvidedKeystoreProperties : negated conditional → KILLED
    boolean hasKeystorePassword = ! (keystorePassword == null || keystorePassword.isBlank());
140 1 1. isProvidedKeystoreProperties : negated conditional → KILLED
    if (! hasKeystorePassword) {
141
      logger.logDebug(() -> "keystorePassword system property was not set");
142
    }
143
144 3 1. isProvidedKeystoreProperties : replaced Boolean return with True for com/renomad/minum/web/WebEngine::isProvidedKeystoreProperties → KILLED
2. isProvidedKeystoreProperties : negated conditional → KILLED
3. isProvidedKeystoreProperties : negated conditional → KILLED
    return hasKeystore && hasKeystorePassword;
145
  }
146
147
148
  /**
149
   * Create an SSL Socket using a specified keystore
150
   */
151
  ServerSocket createSslSocketWithSpecificKeystore(int sslPort, URL keystoreUrl, String keystorePassword) {
152
    try (InputStream keystoreInputStream = keystoreUrl.openStream()) {
153
      final var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
154
      char[] passwordCharArray = keystorePassword.toCharArray();
155 1 1. createSslSocketWithSpecificKeystore : removed call to java/security/KeyStore::load → KILLED
      keyStore.load(keystoreInputStream, passwordCharArray);
156
157
      final var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
158 1 1. createSslSocketWithSpecificKeystore : removed call to javax/net/ssl/KeyManagerFactory::init → KILLED
      keyManagerFactory.init(keyStore, passwordCharArray);
159
160
      final var keyManagers = keyManagerFactory.getKeyManagers();
161
162
      final var sslContext = SSLContext.getInstance("TLSv1.3");
163 1 1. createSslSocketWithSpecificKeystore : removed call to javax/net/ssl/SSLContext::init → KILLED
      sslContext.init(keyManagers, null, new SecureRandom());
164
165
      final var socketFactory = sslContext.getServerSocketFactory();
166
      return socketFactory.createServerSocket(sslPort);
167
    } catch (Exception ex) {
168
      logger.logDebug(ex::getMessage);
169
      throw new WebServerException(ex);
170
    }
171
  }
172
173
  /**
174
   * Create a client {@link ISocketWrapper} connected to the running host server
175
   */
176
  ISocketWrapper startClient(Socket socket) throws IOException {
177
    logger.logDebug(() -> String.format("Just created new client socket: %s", socket));
178 1 1. startClient : replaced return value with null for com/renomad/minum/web/WebEngine::startClient → KILLED
    return new SocketWrapper(socket, null, logger, constants.socketTimeoutMillis, constants.hostName);
179
  }
180
181
  /**
182
   * Intentionally return just the default object toString, this is only used
183
   * to differentiate between multiple instances in memory.
184
   */
185
  @Override
186
  public String toString() {
187 1 1. toString : replaced return value with "" for com/renomad/minum/web/WebEngine::toString → KILLED
    return super.toString();
188
  }
189
190
}

Mutations

40

1.1
Location : <init>
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

61

1.1
Location : startServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_WithRedirect(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/IServer::start → KILLED

64

1.1
Location : startServer
Killed by : none
replaced return value with null for com/renomad/minum/web/WebEngine::startServer → TIMED_OUT

80

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/IServer::start → KILLED

83

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::startSslServer → KILLED

91

1.1
Location : getKeyStoreResult
Killed by : none
negated conditional → SURVIVED
Covering tests

99

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

105

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

108

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::getKeyStoreResult → KILLED

133

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

134

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

139

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

140

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

144

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced Boolean return with True for com/renomad/minum/web/WebEngine::isProvidedKeystoreProperties → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.WebEngineTests
negated conditional → KILLED

3.3
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

155

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to java/security/KeyStore::load → KILLED

158

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to javax/net/ssl/KeyManagerFactory::init → KILLED

163

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to javax/net/ssl/SSLContext::init → KILLED

178

1.1
Location : startClient
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/WebEngine::startClient → KILLED

187

1.1
Location : toString
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with "" for com/renomad/minum/web/WebEngine::toString → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0