| 1 | package com.renomad.minum.state; | |
| 2 | ||
| 3 | import com.renomad.minum.logging.LoggingLevel; | |
| 4 | import com.renomad.minum.utils.TimeUtils; | |
| 5 | import com.renomad.minum.web.WebServerException; | |
| 6 | ||
| 7 | import java.io.FileInputStream; | |
| 8 | import java.io.IOException; | |
| 9 | import java.util.*; | |
| 10 | ||
| 11 | /** | |
| 12 | * Very important system design decisions are made here. All | |
| 13 | * developers on this project should look through each of these. | |
| 14 | */ | |
| 15 | public final class Constants { | |
| 16 | ||
| 17 | private final Properties properties; | |
| 18 | ||
| 19 | public Constants() { | |
| 20 | this(null); | |
| 21 | } | |
| 22 | ||
| 23 | public Constants(Properties props) { | |
| 24 | startTime = System.currentTimeMillis(); | |
| 25 | properties = Objects.requireNonNullElseGet(props, Constants::getConfiguredProperties); | |
| 26 | serverPort = Math.toIntExact(getProp("SERVER_PORT", 8080)); | |
| 27 |
4
1. <init> : changed conditional boundary → KILLED 2. <init> : changed conditional boundary → KILLED 3. <init> : negated conditional → KILLED 4. <init> : negated conditional → KILLED |
if (serverPort < -1 || serverPort > 65_535) throw new WebServerException("SERVER_PORT must be between -1 and 65,535. Value was: " + serverPort); |
| 28 | ||
| 29 | secureServerPort = Math.toIntExact(getProp("SSL_SERVER_PORT", 8443)); | |
| 30 |
4
1. <init> : changed conditional boundary → TIMED_OUT 2. <init> : changed conditional boundary → KILLED 3. <init> : negated conditional → KILLED 4. <init> : negated conditional → KILLED |
if (secureServerPort < -1 || secureServerPort > 65_535) throw new WebServerException("SSL_SERVER_PORT must be between -1 and 65,535. Value was: " + secureServerPort); |
| 31 | ||
| 32 | hostName = getNonEmptyProperty("HOST_NAME", "localhost"); | |
| 33 | dbDirectory = getNonEmptyProperty("DB_DIRECTORY", "db"); | |
| 34 | staticFilesDirectory = getNonEmptyProperty("STATIC_FILES_DIRECTORY", "static"); | |
| 35 | logLevels = convertLoggingStringsToEnums(getProp("LOG_LEVELS", "DEBUG,AUDIT")); | |
| 36 | keystorePath = properties.getProperty("KEYSTORE_PATH", ""); | |
| 37 | keystorePassword = properties.getProperty("KEYSTORE_PASSWORD", ""); | |
| 38 | maxReadSizeBytes = Math.toIntExact(getPositiveNonZeroProp("MAX_READ_SIZE_BYTES", 10 * 1024 * 1024)); | |
| 39 | maxReadLineSizeBytes = Math.toIntExact(getPositiveNonZeroProp("MAX_READ_LINE_SIZE_BYTES", 1024)); | |
| 40 | socketTimeoutMillis = Math.toIntExact(getProp("SOCKET_TIMEOUT_MILLIS", 7 * 1000)); | |
| 41 |
2
1. <init> : changed conditional boundary → TIMED_OUT 2. <init> : negated conditional → KILLED |
if (socketTimeoutMillis < 0) throw new WebServerException("SOCKET_TIMEOUT_MILLIS must be a positive value. Value was: " + socketTimeoutMillis); |
| 42 | ||
| 43 | keepAliveTimeoutSeconds = getPositiveNonZeroProp("KEEP_ALIVE_TIMEOUT_SECONDS", 3); | |
| 44 | vulnSeekingJailDuration = getPositiveNonZeroProp("VULN_SEEKING_JAIL_DURATION", 10 * 1000); | |
| 45 | isTheBrigEnabled = getProp("IS_THE_BRIG_ENABLED", true); | |
| 46 | suspiciousErrors = new HashSet<>(getProp("SUSPICIOUS_ERRORS", "")); | |
| 47 | suspiciousPaths = new HashSet<>(getProp("SUSPICIOUS_PATHS", "")); | |
| 48 | extraMimeMappings = getProp("EXTRA_MIME_MAPPINGS", ""); | |
| 49 | staticFileCacheTime = getPositiveNonZeroProp("STATIC_FILE_CACHE_TIME", 60 * 5); | |
| 50 | useCacheForStaticFiles = getProp("USE_CACHE_FOR_STATIC_FILES", true); | |
| 51 | maxElementsLruCacheStaticFiles = Math.toIntExact(getPositiveNonZeroProp("MAX_ELEMENTS_LRU_CACHE_STATIC_FILES", 1000)); | |
| 52 | maxAppendCount = Math.toIntExact(getPositiveNonZeroProp("MAX_DATABASE_APPEND_COUNT", 100_000)); | |
| 53 | maxLinesPerConsolidatedDatabaseFile = Math.toIntExact(getPositiveNonZeroProp("MAX_DATABASE_CONSOLIDATED_FILE_LINES", 100_000)); | |
| 54 | enableSystemRunningMarker = getProp("ENABLE_SYSTEM_RUNNING_MARKER", true); | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * The port for our regular, non-encrypted server | |
| 59 | */ | |
| 60 | public final int serverPort; | |
| 61 | ||
| 62 | /** | |
| 63 | * The port for our encrypted server | |
| 64 | */ | |
| 65 | public final int secureServerPort; | |
| 66 | ||
| 67 | /** | |
| 68 | * This is returned as the "host:" attribute in an HTTP request | |
| 69 | */ | |
| 70 | public final String hostName; | |
| 71 | ||
| 72 | /** | |
| 73 | * This is the root directory of our database | |
| 74 | */ | |
| 75 | public final String dbDirectory; | |
| 76 | ||
| 77 | /** | |
| 78 | * Root directory of static files | |
| 79 | */ | |
| 80 | public final String staticFilesDirectory; | |
| 81 | ||
| 82 | /** | |
| 83 | * The default logging levels | |
| 84 | */ | |
| 85 | public final List<LoggingLevel> logLevels; | |
| 86 | ||
| 87 | /** | |
| 88 | * The path to the keystore, required for encrypted TLS communication | |
| 89 | */ | |
| 90 | public final String keystorePath; | |
| 91 | ||
| 92 | /** | |
| 93 | * The password of the keystore, used for TLS | |
| 94 | */ | |
| 95 | public final String keystorePassword; | |
| 96 | ||
| 97 | /** | |
| 98 | * this is the most bytes we'll read while parsing the Request body | |
| 99 | */ | |
| 100 | public final int maxReadSizeBytes; | |
| 101 | ||
| 102 | /** | |
| 103 | * this is the most bytes we'll read on a single line, when reading by | |
| 104 | * line. This is especially relevant when reading headers and request lines, which | |
| 105 | * can bulk up with jwt's or query strings, respectively. | |
| 106 | */ | |
| 107 | public final int maxReadLineSizeBytes; | |
| 108 | ||
| 109 | /** | |
| 110 | * How long will we let a socket live before we crash it closed? | |
| 111 | * See {@link java.net.Socket#setSoTimeout(int)} | |
| 112 | */ | |
| 113 | public final int socketTimeoutMillis; | |
| 114 | ||
| 115 | /** | |
| 116 | * We include this value in the keep-alive header. It lets the | |
| 117 | * browser know how long to hold the socket open, in seconds, | |
| 118 | * before it decides we aren't sending anything else and closes it. | |
| 119 | */ | |
| 120 | public final long keepAliveTimeoutSeconds; | |
| 121 | ||
| 122 | /** | |
| 123 | * If a client does something that we consider an indicator for attacking, put them in | |
| 124 | * jail for a longer duration. | |
| 125 | */ | |
| 126 | public final long vulnSeekingJailDuration; | |
| 127 | ||
| 128 | /** | |
| 129 | * TheBrig is what puts client ip's in jail, if we feel they are attacking us. | |
| 130 | * If this is disabled, that functionality is removed. | |
| 131 | */ | |
| 132 | public final boolean isTheBrigEnabled; | |
| 133 | ||
| 134 | /** | |
| 135 | * These are a list of error messages that often indicate unusual behavior, maybe an attacker | |
| 136 | */ | |
| 137 | public final Set<String> suspiciousErrors; | |
| 138 | ||
| 139 | /** | |
| 140 | * These are a list of paths that often indicate unusual behavior, maybe an attacker | |
| 141 | */ | |
| 142 | public final Set<String> suspiciousPaths; | |
| 143 | ||
| 144 | /** | |
| 145 | * This value is the result of running System.currentTimeMillis() when this | |
| 146 | * class gets instantiated, and that is done at the very beginning. | |
| 147 | */ | |
| 148 | public final long startTime; | |
| 149 | ||
| 150 | /** | |
| 151 | * These are key-value pairs for mappings between a file | |
| 152 | * suffix and a mime type. | |
| 153 | * <p> | |
| 154 | * These are read by our system in the StaticFilesCache | |
| 155 | * as key-1,value-1,key-2,value-2,... and so on. | |
| 156 | * </p> | |
| 157 | * @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">Basics of HTTP</a> | |
| 158 | * | |
| 159 | */ | |
| 160 | public final List<String> extraMimeMappings; | |
| 161 | ||
| 162 | /** | |
| 163 | * Length of time, in seconds, for static files to be cached, | |
| 164 | * per the provisions of the Cache-Control header, e.g. | |
| 165 | * <pre> | |
| 166 | * {@code Cache-Control: max-age=300} | |
| 167 | * </pre> | |
| 168 | */ | |
| 169 | public final long staticFileCacheTime; | |
| 170 | ||
| 171 | /** | |
| 172 | * Whether we will use caching for the static files. | |
| 173 | * <p> | |
| 174 | * When a user requests a path we don't recognize, we | |
| 175 | * go looking for it. | |
| 176 | * If we have already found it for someone else, it will | |
| 177 | * be in a cache. | |
| 178 | * </p> | |
| 179 | * <p> | |
| 180 | * However, if we are doing development, it helps to | |
| 181 | * not have caching enabled - it can confuse. | |
| 182 | * </p> | |
| 183 | */ | |
| 184 | public final boolean useCacheForStaticFiles; | |
| 185 | ||
| 186 | /** | |
| 187 | * This is the maximum count of database entries that will | |
| 188 | * be appended to a file before switching to a new file. | |
| 189 | * Only applies when using DbEngine2. | |
| 190 | */ | |
| 191 | public final int maxAppendCount; | |
| 192 | ||
| 193 | /** | |
| 194 | * This number is the maximum count of lines we will allow in a consolidated | |
| 195 | * database file, to support the needs of the database. | |
| 196 | */ | |
| 197 | public final int maxLinesPerConsolidatedDatabaseFile; | |
| 198 | ||
| 199 | /** | |
| 200 | * This constant controls the maximum number of elements for the {@link com.renomad.minum.utils.LRUCache} | |
| 201 | * we create for use by {@link com.renomad.minum.utils.FileUtils}. As files are read | |
| 202 | * by FileUtil's methods, they will be stored in this cache, to avoid reading from | |
| 203 | * disk. However, caching can certainly complicate things, so if you would prefer | |
| 204 | * not to store these values in a cache, set {@link #useCacheForStaticFiles} to false. | |
| 205 | * <p> | |
| 206 | * The unit here is the number of elements to store in the cache. Be aware: elements | |
| 207 | * can be of any size, so two caches each having a max size of 1000 elements could be | |
| 208 | * drastically different sizes. | |
| 209 | * </p> | |
| 210 | */ | |
| 211 | public final int maxElementsLruCacheStaticFiles; | |
| 212 | ||
| 213 | /** | |
| 214 | * This flag controls whether the system will write a file to disk | |
| 215 | * indicating that the program is running, and delete that file | |
| 216 | * when the program ends. Default is true. | |
| 217 | */ | |
| 218 | public final boolean enableSystemRunningMarker; | |
| 219 | ||
| 220 | ||
| 221 | /* ************************ ** | |
| 222 | HELPER METHODS | |
| 223 | ** ************************ */ | |
| 224 | ||
| 225 | ||
| 226 | /** | |
| 227 | * A helper method to remove some redundant boilerplate code for grabbing | |
| 228 | * configuration values from minum.config | |
| 229 | */ | |
| 230 | private long getProp(String propName, long propDefault) { | |
| 231 |
1
1. getProp : replaced long return with 0 for com/renomad/minum/state/Constants::getProp → KILLED |
return Long.parseLong(properties.getProperty(propName, String.valueOf(propDefault))); |
| 232 | } | |
| 233 | ||
| 234 | private long getPositiveNonZeroProp(String propName, long propDefault) { | |
| 235 | long value = getProp(propName, propDefault); | |
| 236 |
2
1. getPositiveNonZeroProp : changed conditional boundary → TIMED_OUT 2. getPositiveNonZeroProp : negated conditional → KILLED |
if (value <= 0L) throw new WebServerException(propName + " must be a positive non-zero value. Value was: " + value); |
| 237 |
1
1. getPositiveNonZeroProp : replaced long return with 0 for com/renomad/minum/state/Constants::getPositiveNonZeroProp → KILLED |
return value; |
| 238 | } | |
| 239 | ||
| 240 | /** | |
| 241 | * A helper method to remove some redundant boilerplate code for grabbing | |
| 242 | * configuration values from minum.config | |
| 243 | */ | |
| 244 | private boolean getProp(String propName, boolean propDefault) { | |
| 245 |
2
1. getProp : replaced boolean return with false for com/renomad/minum/state/Constants::getProp → TIMED_OUT 2. getProp : replaced boolean return with true for com/renomad/minum/state/Constants::getProp → KILLED |
return Boolean.parseBoolean(properties.getProperty(propName, String.valueOf(propDefault))); |
| 246 | } | |
| 247 | ||
| 248 | /** | |
| 249 | * A helper method to remove some redundant boilerplate code for grabbing | |
| 250 | * configuration values from minum.config | |
| 251 | */ | |
| 252 | private List<String> getProp(String propName, String propDefault) { | |
| 253 | String propValue = properties.getProperty(propName); | |
| 254 |
1
1. getProp : replaced return value with Collections.emptyList for com/renomad/minum/state/Constants::getProp → TIMED_OUT |
return extractList(propValue, propDefault); |
| 255 | } | |
| 256 | ||
| 257 | private String getNonEmptyProperty(String propName, String propDefault) { | |
| 258 | String propValue = properties.getProperty(propName, propDefault); | |
| 259 |
1
1. getNonEmptyProperty : negated conditional → KILLED |
if (propValue.isBlank()) throw new WebServerException(propName + " must be a non-empty string"); |
| 260 |
1
1. getNonEmptyProperty : replaced return value with "" for com/renomad/minum/state/Constants::getNonEmptyProperty → TIMED_OUT |
return propValue; |
| 261 | } | |
| 262 | ||
| 263 | /** | |
| 264 | * Extract a list out of a comma-delimited string. | |
| 265 | * <br> | |
| 266 | * Example: a,b, c, d -> List.of("a","b","c","d") | |
| 267 | * @param propValue the value of a property | |
| 268 | * @param propDefault the default value to use, if the propValue is null | |
| 269 | */ | |
| 270 | static List<String> extractList(String propValue, String propDefault) { | |
| 271 |
1
1. extractList : negated conditional → KILLED |
if (propValue == null) { |
| 272 |
1
1. extractList : negated conditional → KILLED |
if (propDefault.isBlank()) { |
| 273 | return List.of(); | |
| 274 | } else { | |
| 275 |
1
1. extractList : replaced return value with Collections.emptyList for com/renomad/minum/state/Constants::extractList → TIMED_OUT |
return Arrays.asList(propDefault.trim().split("\\s*,\\s*")); |
| 276 | } | |
| 277 | } else { | |
| 278 |
1
1. extractList : replaced return value with Collections.emptyList for com/renomad/minum/state/Constants::extractList → KILLED |
return Arrays.asList(propValue.trim().split("\\s*,\\s*")); |
| 279 | } | |
| 280 | } | |
| 281 | ||
| 282 | public static Properties getConfiguredProperties() { | |
| 283 |
1
1. getConfiguredProperties : replaced return value with null for com/renomad/minum/state/Constants::getConfiguredProperties → KILLED |
return getConfiguredProperties("minum.config"); |
| 284 | } | |
| 285 | ||
| 286 | /** | |
| 287 | * Reads properties from minum.config | |
| 288 | */ | |
| 289 | static Properties getConfiguredProperties(String fileName) { | |
| 290 | var props = new Properties(); | |
| 291 | try (FileInputStream fis = new FileInputStream(fileName)) { | |
| 292 | System.out.println(TimeUtils.getTimestampIsoInstant() + | |
| 293 | " found properties file at ./minum.config. Loading properties"); | |
| 294 |
1
1. getConfiguredProperties : removed call to java/util/Properties::load → KILLED |
props.load(fis); |
| 295 | } catch (IOException ex) { | |
| 296 | System.out.println(CONFIG_ERROR_MESSAGE); | |
| 297 | } | |
| 298 |
1
1. getConfiguredProperties : replaced return value with null for com/renomad/minum/state/Constants::getConfiguredProperties → KILLED |
return props; |
| 299 | } | |
| 300 | ||
| 301 | /** | |
| 302 | * Given a list of strings representing logging levels, | |
| 303 | * convert it to a list of enums. Log levels are enumerated | |
| 304 | * in {@link LoggingLevel}. | |
| 305 | */ | |
| 306 | static List<LoggingLevel> convertLoggingStringsToEnums(List<String> logLevels) { | |
| 307 | List<String> logLevelStrings = logLevels.stream().map(String::toUpperCase).toList(); | |
| 308 | List<LoggingLevel> enabledLoggingLevels = new ArrayList<>(); | |
| 309 | for (LoggingLevel t : LoggingLevel.values()) { | |
| 310 | if (logLevelStrings.contains(t.name())) { | |
| 311 | enabledLoggingLevels.add(t); | |
| 312 | } | |
| 313 | } | |
| 314 |
1
1. convertLoggingStringsToEnums : replaced return value with Collections.emptyList for com/renomad/minum/state/Constants::convertLoggingStringsToEnums → TIMED_OUT |
return enabledLoggingLevels; |
| 315 | } | |
| 316 | ||
| 317 | private static final String CONFIG_ERROR_MESSAGE = """ | |
| 318 | | |
| 319 | | |
| 320 | | |
| 321 | ---------------------------------------------------------------- | |
| 322 | ----------------- System Configuration Missing ----------------- | |
| 323 | ---------------------------------------------------------------- | |
| 324 | | |
| 325 | No properties file found at ./minum.config | |
| 326 | | |
| 327 | Continuing, using defaults. See source code for Minum for an | |
| 328 | example minum.config, which will allow you to customize behavior. | |
| 329 | | |
| 330 | ---------------------------------------------------------------- | |
| 331 | ---------------------------------------------------------------- | |
| 332 | """; | |
| 333 | ||
| 334 | @Override | |
| 335 | public boolean equals(Object o) { | |
| 336 |
3
1. equals : replaced boolean return with true for com/renomad/minum/state/Constants::equals → KILLED 2. equals : negated conditional → KILLED 3. equals : negated conditional → KILLED |
if (o == null || getClass() != o.getClass()) return false; |
| 337 | Constants constants = (Constants) o; | |
| 338 |
26
1. equals : negated conditional → KILLED 2. equals : negated conditional → KILLED 3. equals : negated conditional → KILLED 4. equals : negated conditional → KILLED 5. equals : negated conditional → KILLED 6. equals : negated conditional → KILLED 7. equals : negated conditional → KILLED 8. equals : negated conditional → KILLED 9. equals : negated conditional → KILLED 10. equals : negated conditional → KILLED 11. equals : negated conditional → KILLED 12. equals : negated conditional → KILLED 13. equals : negated conditional → KILLED 14. equals : negated conditional → KILLED 15. equals : negated conditional → KILLED 16. equals : negated conditional → KILLED 17. equals : negated conditional → KILLED 18. equals : negated conditional → KILLED 19. equals : negated conditional → KILLED 20. equals : negated conditional → KILLED 21. equals : negated conditional → KILLED 22. equals : replaced boolean return with true for com/renomad/minum/state/Constants::equals → KILLED 23. equals : negated conditional → KILLED 24. equals : negated conditional → KILLED 25. equals : negated conditional → KILLED 26. equals : negated conditional → KILLED |
return serverPort == constants.serverPort && secureServerPort == constants.secureServerPort && maxReadSizeBytes == constants.maxReadSizeBytes && maxReadLineSizeBytes == constants.maxReadLineSizeBytes && socketTimeoutMillis == constants.socketTimeoutMillis && keepAliveTimeoutSeconds == constants.keepAliveTimeoutSeconds && vulnSeekingJailDuration == constants.vulnSeekingJailDuration && isTheBrigEnabled == constants.isTheBrigEnabled && startTime == constants.startTime && staticFileCacheTime == constants.staticFileCacheTime && useCacheForStaticFiles == constants.useCacheForStaticFiles && maxAppendCount == constants.maxAppendCount && maxLinesPerConsolidatedDatabaseFile == constants.maxLinesPerConsolidatedDatabaseFile && maxElementsLruCacheStaticFiles == constants.maxElementsLruCacheStaticFiles && enableSystemRunningMarker == constants.enableSystemRunningMarker && Objects.equals(properties, constants.properties) && Objects.equals(hostName, constants.hostName) && Objects.equals(dbDirectory, constants.dbDirectory) && Objects.equals(staticFilesDirectory, constants.staticFilesDirectory) && Objects.equals(logLevels, constants.logLevels) && Objects.equals(keystorePath, constants.keystorePath) && Objects.equals(keystorePassword, constants.keystorePassword) && Objects.equals(suspiciousErrors, constants.suspiciousErrors) && Objects.equals(suspiciousPaths, constants.suspiciousPaths) && Objects.equals(extraMimeMappings, constants.extraMimeMappings); |
| 339 | } | |
| 340 | ||
| 341 | @Override | |
| 342 | public int hashCode() { | |
| 343 |
1
1. hashCode : replaced int return with 0 for com/renomad/minum/state/Constants::hashCode → KILLED |
return Objects.hash(properties, serverPort, secureServerPort, hostName, dbDirectory, staticFilesDirectory, logLevels, keystorePath, keystorePassword, maxReadSizeBytes, maxReadLineSizeBytes, socketTimeoutMillis, keepAliveTimeoutSeconds, vulnSeekingJailDuration, isTheBrigEnabled, suspiciousErrors, suspiciousPaths, startTime, extraMimeMappings, staticFileCacheTime, useCacheForStaticFiles, maxAppendCount, maxLinesPerConsolidatedDatabaseFile, maxElementsLruCacheStaticFiles, enableSystemRunningMarker); |
| 344 | } | |
| 345 | } | |
| 346 | ||
| 347 | ||
| 348 | ||
Mutations | ||
| 27 |
1.1 2.2 3.3 4.4 |
|
| 30 |
1.1 2.2 3.3 4.4 |
|
| 41 |
1.1 2.2 |
|
| 231 |
1.1 |
|
| 236 |
1.1 2.2 |
|
| 237 |
1.1 |
|
| 245 |
1.1 2.2 |
|
| 254 |
1.1 |
|
| 259 |
1.1 |
|
| 260 |
1.1 |
|
| 271 |
1.1 |
|
| 272 |
1.1 |
|
| 275 |
1.1 |
|
| 278 |
1.1 |
|
| 283 |
1.1 |
|
| 294 |
1.1 |
|
| 298 |
1.1 |
|
| 314 |
1.1 |
|
| 336 |
1.1 2.2 3.3 |
|
| 338 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 11.11 12.12 13.13 14.14 15.15 16.16 17.17 18.18 19.19 20.20 21.21 22.22 23.23 24.24 25.25 26.26 |
|
| 343 |
1.1 |