001package com.box.sdkgen.internal.utils; 002 003import com.box.sdkgen.box.errors.BoxSDKError; 004import com.box.sdkgen.internal.SerializableObject; 005import com.box.sdkgen.serialization.json.EnumWrapper; 006import com.box.sdkgen.serialization.json.JsonManager; 007import com.box.sdkgen.serialization.json.Valuable; 008import com.fasterxml.jackson.databind.JsonNode; 009import com.fasterxml.jackson.databind.ObjectMapper; 010import com.fasterxml.jackson.databind.node.ArrayNode; 011import java.io.ByteArrayInputStream; 012import java.io.ByteArrayOutputStream; 013import java.io.FileNotFoundException; 014import java.io.FileOutputStream; 015import java.io.IOException; 016import java.io.InputStream; 017import java.io.OutputStream; 018import java.math.BigInteger; 019import java.nio.charset.StandardCharsets; 020import java.nio.file.Files; 021import java.nio.file.Paths; 022import java.security.MessageDigest; 023import java.time.OffsetDateTime; 024import java.time.ZoneOffset; 025import java.time.format.DateTimeFormatter; 026import java.time.format.DateTimeFormatterBuilder; 027import java.time.format.DateTimeParseException; 028import java.time.temporal.ChronoField; 029import java.time.temporal.ChronoUnit; 030import java.util.Arrays; 031import java.util.Base64; 032import java.util.HashMap; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Locale; 036import java.util.Map; 037import java.util.Objects; 038import java.util.Set; 039import java.util.UUID; 040import java.util.function.BiFunction; 041import java.util.stream.Collectors; 042import javax.crypto.Mac; 043import javax.crypto.spec.SecretKeySpec; 044import org.jose4j.jws.JsonWebSignature; 045import org.jose4j.jwt.JwtClaims; 046import org.jose4j.jwt.NumericDate; 047import org.jose4j.lang.JoseException; 048 049public class UtilsManager { 050 private static final int BUFFER_SIZE = 8192; 051 052 private static final DateTimeFormatter OFFSET_DATE_TIME_FORMAT = 053 new DateTimeFormatterBuilder() 054 .appendPattern("yyyy-MM-dd'T'HH:mm:ss") 055 .optionalStart() 056 .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) 057 .optionalEnd() 058 .appendOffsetId() 059 .toFormatter(); 060 private static final DateTimeFormatter OFFSET_DATE_FORMAT = 061 DateTimeFormatter.ofPattern("yyyy-MM-dd"); 062 063 public static <K, V> Map<K, V> mapOf(Entry<K, V>... entries) { 064 return Arrays.stream(entries) 065 .collect( 066 HashMap::new, 067 (map, entry) -> map.put(entry.getKey(), entry.getValue()), 068 HashMap::putAll); 069 } 070 071 public static <V> Set<V> setOf(V... values) { 072 return Arrays.stream(values).collect(Collectors.toSet()); 073 } 074 075 public static <K, V> Entry<K, V> entryOf(K key, V value) { 076 return Entry.of(key, value); 077 } 078 079 public static <K, V> Map<K, V> mergeMaps(Map<K, V> map1, Map<K, V> map2) { 080 Map<K, V> mergedMap = new HashMap<>(); 081 if (map1 != null) { 082 mergedMap.putAll(map1); 083 } 084 if (map2 != null) { 085 mergedMap.putAll(map2); 086 } 087 return mergedMap; 088 } 089 090 public static Map<String, String> prepareParams(Map<String, String> map) { 091 map.values().removeIf(Objects::isNull); 092 return map; 093 } 094 095 public static String convertToString(Object value) { 096 if (value == null) { 097 return null; 098 } 099 if (value instanceof EnumWrapper) { 100 return ((EnumWrapper<?>) value).getStringValue(); 101 } 102 if (value instanceof Valuable) { 103 return ((Valuable) value).getValue(); 104 } 105 if (value instanceof List) { 106 List<?> list = (List<?>) value; 107 if (!list.isEmpty() && list.get(0) instanceof SerializableObject) { 108 return JsonManager.serialize(value).toString(); 109 } else { 110 return ((List<?>) value) 111 .stream().map(UtilsManager::convertToString).collect(Collectors.joining(",")); 112 } 113 } 114 if (value instanceof ArrayNode) { 115 return convertToString(new ObjectMapper().convertValue(value, List.class)); 116 } 117 if (value instanceof JsonNode) { 118 return ((JsonNode) value).asText(); 119 } 120 if (value instanceof SerializableObject) { 121 return JsonManager.serialize(value).toString(); 122 } 123 return value.toString(); 124 } 125 126 public static void writeInputStreamToOutputStream(InputStream input, OutputStream output) { 127 try { 128 byte[] buffer = new byte[BUFFER_SIZE]; 129 int n = input.read(buffer); 130 while (n != -1) { 131 output.write(buffer, 0, n); 132 n = input.read(buffer); 133 } 134 } catch (IOException e) { 135 throw new RuntimeException(e); 136 } finally { 137 try { 138 input.close(); 139 output.close(); 140 } catch (IOException e) { 141 throw new RuntimeException(e); 142 } 143 } 144 } 145 146 public static String getUuid() { 147 return UUID.randomUUID().toString(); 148 } 149 150 public static byte[] generateByteBuffer(int size) { 151 byte[] bytes = new byte[size]; 152 Arrays.fill(bytes, (byte) 0); 153 return bytes; 154 } 155 156 public static InputStream generateByteStream(int size) { 157 byte[] bytes = generateByteBuffer(size); 158 return new ByteArrayInputStream(bytes); 159 } 160 161 public static InputStream generateByteStreamFromBuffer(byte[] buffer) { 162 return new ByteArrayInputStream(buffer); 163 } 164 165 public static byte[] readByteStream(InputStream inputStream) { 166 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 167 byte[] data = new byte[BUFFER_SIZE]; 168 int bytesRead; 169 try { 170 while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { 171 buffer.write(data, 0, bytesRead); 172 } 173 } catch (IOException e) { 174 throw new RuntimeException(e); 175 } finally { 176 try { 177 inputStream.close(); 178 } catch (IOException e) { 179 throw new RuntimeException(e); 180 } 181 } 182 183 return buffer.toByteArray(); 184 } 185 186 public static boolean bufferEquals(byte[] buffer1, byte[] buffer2) { 187 return Arrays.equals(buffer1, buffer2); 188 } 189 190 public static int bufferLength(byte[] buffer) { 191 return buffer.length; 192 } 193 194 public static InputStream decodeBase64ByteStream(String value) { 195 return new ByteArrayInputStream(Base64.getDecoder().decode(value)); 196 } 197 198 public static String decodeBase64(String value) { 199 return new String(Base64.getDecoder().decode(value)); 200 } 201 202 public static InputStream stringToByteStream(String value) { 203 return new ByteArrayInputStream(value.getBytes()); 204 } 205 206 public static OutputStream getFileOutputStream(String filePath) { 207 try { 208 return new FileOutputStream(filePath); 209 } catch (FileNotFoundException e) { 210 throw new RuntimeException(e); 211 } 212 } 213 214 public static void closeFileOutputStream(OutputStream outputStream) { 215 try { 216 outputStream.close(); 217 } catch (IOException e) { 218 throw new RuntimeException(e); 219 } 220 } 221 222 public static byte[] readBufferFromFile(String filePath) { 223 try { 224 InputStream inputStream = Files.newInputStream(Paths.get(filePath)); 225 return readByteStream(inputStream); 226 } catch (IOException e) { 227 throw new RuntimeException(e); 228 } 229 } 230 231 public static String getEnvVar(String envVar) { 232 return System.getenv(envVar); 233 } 234 235 public static void delayInSeconds(int seconds) { 236 try { 237 Thread.sleep(seconds * 1000L); 238 } catch (InterruptedException e) { 239 throw new RuntimeException(e); 240 } 241 } 242 243 public static String readTextFromFile(String filePath) { 244 try { 245 return new String(Files.readAllBytes(Paths.get(filePath))); 246 } catch (IOException e) { 247 throw new RuntimeException(e); 248 } 249 } 250 251 public static boolean isBrowser() { 252 return false; 253 } 254 255 public static long getEpochTimeInSeconds() { 256 return System.currentTimeMillis() / 1000; 257 } 258 259 public static String createJwtAssertion( 260 Map<String, Object> claims, JwtKey jwtKey, JwtSignOptions jwtOptions) { 261 JwtClaims jwtClaims = new JwtClaims(); 262 jwtClaims.setIssuer(jwtOptions.getIssuer()); 263 jwtClaims.setAudience(jwtOptions.getAudience()); 264 jwtClaims.setExpirationTime(NumericDate.fromSeconds((Long) claims.get("exp"))); 265 266 jwtClaims.setSubject(jwtOptions.getSubject()); 267 jwtClaims.setClaim("box_sub_type", claims.get("box_sub_type")); 268 jwtClaims.setGeneratedJwtId(64); 269 270 JsonWebSignature jws = new JsonWebSignature(); 271 jws.setPayload(jwtClaims.toJson()); 272 jws.setKey( 273 jwtOptions.privateKeyDecryptor.decryptPrivateKey(jwtKey.getKey(), jwtKey.getPassphrase())); 274 jws.setAlgorithmHeaderValue(jwtOptions.getAlgorithm().getValue()); 275 jws.setHeader("typ", "JWT"); 276 if ((jwtOptions.getKeyid() != null) && !jwtOptions.getKeyid().isEmpty()) { 277 jws.setHeader("kid", jwtOptions.getKeyid()); 278 } 279 280 String assertion; 281 282 try { 283 assertion = jws.getCompactSerialization(); 284 } catch (JoseException e) { 285 throw new BoxSDKError("Error serializing JSON Web Token assertion.", e); 286 } 287 288 return assertion; 289 } 290 291 public static JsonNode getValueFromObjectRawData(SerializableObject obj, String key) { 292 JsonNode value = obj.getRawData(); 293 for (String k : key.split("\\.")) { 294 if (value == null || !value.has(k)) { 295 return null; 296 } 297 value = value.get(k); 298 } 299 300 return value; 301 } 302 303 public static double random(double min, double max) { 304 return Math.random() * (max - min) + min; 305 } 306 307 public static String hexToBase64(String hex) { 308 return Base64.getEncoder().encodeToString(new BigInteger(hex, 16).toByteArray()); 309 } 310 311 public static Iterator<InputStream> iterateChunks( 312 InputStream stream, long chunkSize, long fileSize) { 313 return new Iterator<InputStream>() { 314 private boolean streamIsFinished = false; 315 316 @Override 317 public boolean hasNext() { 318 return !streamIsFinished; 319 } 320 321 @Override 322 public InputStream next() { 323 try { 324 byte[] buffer = new byte[(int) chunkSize]; 325 int bytesRead = 0; 326 327 while (bytesRead < chunkSize) { 328 int read = stream.read(buffer, bytesRead, (int) (chunkSize - bytesRead)); 329 if (read == -1) { 330 // End of stream 331 streamIsFinished = true; 332 break; 333 } 334 bytesRead += read; 335 } 336 337 if (bytesRead == 0) { 338 // No more data to yield 339 streamIsFinished = true; 340 return null; 341 } 342 343 // Return the chunk as a ByteArrayInputStream 344 return new ByteArrayInputStream(buffer, 0, bytesRead); 345 } catch (Exception e) { 346 throw new RuntimeException("Error reading from stream", e); 347 } 348 } 349 }; 350 } 351 352 /** 353 * Reduces an iterator using a reducer function and an initial value. 354 * 355 * @param <Accumulator> The type of the accumulator (result) 356 * @param <T> The type of the items in the iterator 357 * @param iterator The iterator to process 358 * @param reducer The reducer function 359 * @param initialValue The initial value for the accumulator 360 * @return The accumulated result 361 */ 362 public static <Accumulator, T> Accumulator reduceIterator( 363 Iterator<T> iterator, 364 BiFunction<Accumulator, T, Accumulator> reducer, 365 Accumulator initialValue) { 366 Accumulator result = initialValue; 367 368 while (iterator.hasNext()) { 369 T item = iterator.next(); 370 result = reducer.apply(result, item); 371 } 372 373 return result; 374 } 375 376 public static Map<String, String> sanitizeMap( 377 Map<String, String> dictionary, Map<String, String> keysToSanitize) { 378 return dictionary.entrySet().stream() 379 .collect( 380 Collectors.toMap( 381 Map.Entry::getKey, 382 entry -> 383 keysToSanitize.containsKey(entry.getKey().toLowerCase(Locale.ROOT)) 384 ? JsonManager.sanitizedValue() 385 : entry.getValue())); 386 } 387 388 public static OffsetDateTime dateTimeFromString(String dateString) { 389 try { 390 return OffsetDateTime.parse(dateString, OFFSET_DATE_TIME_FORMAT); 391 } catch (DateTimeParseException e) { 392 return null; 393 } 394 } 395 396 public static String dateTimeToString(OffsetDateTime dateTime) { 397 return dateTime.truncatedTo(ChronoUnit.SECONDS).format(OFFSET_DATE_TIME_FORMAT); 398 } 399 400 public static OffsetDateTime dateFromString(String dateString) { 401 try { 402 // For date-only strings, parse as date and convert to OffsetDateTime at start of day UTC 403 if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) { 404 return OffsetDateTime.parse(dateString + "T00:00:00Z"); 405 } 406 // Otherwise try to parse as full OffsetDateTime 407 return dateTimeFromString(dateString); 408 } catch (DateTimeParseException e) { 409 return null; 410 } 411 } 412 413 public static String dateToString(OffsetDateTime date) { 414 return date.format(OFFSET_DATE_FORMAT); 415 } 416 417 public static String escapeUnicode(String value) { 418 if (value == null) { 419 return null; 420 } 421 422 StringBuilder result = new StringBuilder(); 423 for (int i = 0; i < value.length(); i++) { 424 char ch = value.charAt(i); 425 if (ch >= 0x007F) { 426 result.append(String.format("\\u%04x", (int) ch)); 427 } else if (ch == '\\') { 428 result.append("\\\\"); 429 } else if (ch == '\n') { 430 result.append("\\n"); 431 } else if (ch == '\r') { 432 result.append("\\r"); 433 } else if (ch == '\t') { 434 result.append("\\t"); 435 } else if (ch == '/') { 436 if (i == 0 || value.charAt(i - 1) != '\\') { 437 result.append("\\/"); 438 } else { 439 result.append(ch); 440 } 441 } else { 442 result.append(ch); 443 } 444 } 445 return result.toString(); 446 } 447 448 public static OffsetDateTime epochSecondsToDateTime(long seconds) { 449 return OffsetDateTime.ofInstant(java.time.Instant.ofEpochSecond(seconds), ZoneOffset.UTC); 450 } 451 452 public static long dateTimeToEpochSeconds(OffsetDateTime dateTime) { 453 return dateTime.toEpochSecond(); 454 } 455 456 public static boolean compareSignatures(String expectedSignature, String receivedSignature) { 457 if (expectedSignature == null || receivedSignature == null) { 458 return false; 459 } 460 byte[] expectedBytes = expectedSignature.getBytes(StandardCharsets.UTF_8); 461 byte[] receivedBytes = receivedSignature.getBytes(StandardCharsets.UTF_8); 462 return MessageDigest.isEqual(expectedBytes, receivedBytes); 463 } 464 465 public static String computeWebhookSignature( 466 String body, Map<String, String> headers, String signatureKey, boolean escapeBody) { 467 if (signatureKey == null) { 468 return null; 469 } 470 if (!"1".equals(headers.get("box-signature-version"))) { 471 return null; 472 } 473 if (!"HmacSHA256".equals(headers.get("box-signature-algorithm"))) { 474 return null; 475 } 476 if (!headers.containsKey("box-delivery-timestamp")) { 477 return null; 478 } 479 480 try { 481 String escapedBody = escapeBody ? escapeUnicode(body) : body; 482 byte[] encodedSignatureKey = signatureKey.getBytes("UTF-8"); 483 byte[] encodedBody = escapedBody.getBytes("UTF-8"); 484 byte[] encodedTimestamp = headers.get("box-delivery-timestamp").getBytes("UTF-8"); 485 Mac mac = Mac.getInstance("HmacSHA256"); 486 SecretKeySpec secretKeySpec = new SecretKeySpec(encodedSignatureKey, "HmacSHA256"); 487 mac.init(secretKeySpec); 488 mac.update(encodedBody); 489 mac.update(encodedTimestamp); 490 byte[] hmacDigest = mac.doFinal(); 491 return Base64.getEncoder().encodeToString(hmacDigest); 492 } catch (Exception e) { 493 return null; 494 } 495 } 496}