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