private void setUpBinaryService() throws Exception { when(mockBinary.getContent(eq(3), eq(10))).thenReturn(new ByteArrayInputStream("e input".getBytes(UTF_8))); when(mockBinary.getContent()).thenReturn(new ByteArrayInputStream("Some input stream".getBytes(UTF_8))); when(mockBinaryService.generateIdentifier()).thenReturn("file:///" + randomUUID()); when(mockBinaryService.supportedAlgorithms()).thenReturn(new HashSet<>(asList("MD5", "SHA-1"))); when(mockDigest.digest()).thenReturn("computed-digest".getBytes(UTF_8)); when(mockBinaryService.calculateDigest(any(IRI.class), any(MessageDigest.class))) .thenReturn(completedFuture(mockDigest)); when(mockBinaryService.get(any(IRI.class))).thenAnswer(inv -> completedFuture(mockBinary)); when(mockBinaryService.purgeContent(any(IRI.class))).thenReturn(completedFuture(null)); when(mockBinaryService.setContent(any(BinaryMetadata.class), any(InputStream.class), any())) .thenAnswer(inv -> { readLines((InputStream) inv.getArguments()[1], UTF_8); return completedFuture(null); }); when(mockBinaryService.setContent(any(BinaryMetadata.class), any(InputStream.class))) .thenAnswer(inv -> { readLines((InputStream) inv.getArguments()[1], UTF_8); return completedFuture(null); }); doCallRealMethod().when(mockBinaryService) .setContent(any(BinaryMetadata.class), any(InputStream.class), any()); }
private CompletionStage<Optional<String>> computeInstanceDigest(final IRI dsid) { // Add instance digests, if Requested and supported if (nonNull(getRequest().getWantDigest())) { final Optional<String> algorithm = getRequest().getWantDigest().getAlgorithms().stream() .filter(getServices().getBinaryService().supportedAlgorithms()::contains).findFirst(); if (algorithm.isPresent()) { final String alg = algorithm.filter(isEqual("SHA").negate()).orElse("SHA-1"); return getServices().getBinaryService().calculateDigest(dsid, getDigest(alg)) .thenApply(MessageDigest::digest) .thenApply(getEncoder()::encodeToString) .thenApply(digest -> Optional.of(algorithm.get().toLowerCase() + "=" + digest)); } } return completedFuture(empty()); }
protected CompletionStage<Void> persistContent(final BinaryMetadata metadata, final Digest digest) { if (isNull(digest)) { return persistContent(metadata); } try { final String alg = of(digest).map(Digest::getAlgorithm).map(String::toUpperCase) .filter(isEqual("SHA").negate()).orElse("SHA-1"); return getServices().getBinaryService().setContent(metadata, entity, MessageDigest.getInstance(alg)) .thenApply(MessageDigest::digest) .thenApply(getEncoder()::encodeToString).thenCompose(serverComputed -> { if (digest.getDigest().equals(serverComputed)) { LOGGER.debug("Successfully persisted digest-verified bitstream: {}", metadata.getIdentifier()); return completedFuture(null); } return getServices().getBinaryService().purgeContent(metadata.getIdentifier()) .thenAccept(future -> { throw new BadRequestException( "Supplied digest value does not match the server-computed digest: " + serverComputed); }); }); } catch (final NoSuchAlgorithmException ex) { throw new BadRequestException("Invalid digest algorithm: " + ex.getMessage()); } }
/** * Set the content for a binary object using a digest algorithm. * * @implSpec The default implementation will compute a digest while processing the {@code InputStream}. * @param metadata the binary metadata * @param stream the context * @param algorithm the digest algorithm * @return the new completion stage containing the server-computed digest. */ default CompletionStage<MessageDigest> setContent(final BinaryMetadata metadata, final InputStream stream, final MessageDigest algorithm) { final DigestInputStream input = new DigestInputStream(stream, algorithm); return setContent(metadata, input).thenApply(future -> input.getMessageDigest()); }
@Test public void testFilePurge() { final BinaryService service = new FileBinaryService(); final IRI fileIRI = rdf.createIRI("file:///" + randomFilename()); final InputStream inputStream = new ByteArrayInputStream("Some data".getBytes(UTF_8)); assertNull(service.setContent(BinaryMetadata.builder(fileIRI).build(), inputStream) .toCompletableFuture().join(), "setContent didn't complete cleanly!"); assertEquals("Some data", uncheckedToString(service.get(fileIRI).thenApply(Binary::getContent) .toCompletableFuture().join()), "incorrect value for getContent!"); assertNull(service.purgeContent(fileIRI).toCompletableFuture().join(), "purgeContent didn't complete cleanly!"); assertNull(service.purgeContent(fileIRI).toCompletableFuture().join(), "purgeContent (2) didn't complete cleanly!"); }
@Test public void testSetFileContent() { final String contents = "A new file"; final BinaryService service = new FileBinaryService(); final IRI fileIRI = rdf.createIRI("file:///" + randomFilename()); final InputStream inputStream = new ByteArrayInputStream(contents.getBytes(UTF_8)); assertNull(service.setContent(BinaryMetadata.builder(fileIRI).build(), inputStream) .toCompletableFuture().join(), "Setting content didn't complete cleanly!"); assertEquals(contents, service.get(fileIRI).thenApply(Binary::getContent).thenApply(this::uncheckedToString) .toCompletableFuture().join(), "Fetching new content returned incorrect value!"); }
@Test public void testGetContent() throws IOException { final ByteArrayInputStream inputStream = new ByteArrayInputStream("FooBar".getBytes(UTF_8)); when(mockBinaryService.get(eq(identifier))).thenAnswer(inv -> completedFuture(mockBinary)); when(mockBinary.getContent(anyInt(), anyInt())).thenReturn(inputStream); try (final InputStream content = mockBinaryService.get(identifier) .thenApply(b -> b.getContent(0, 6)).toCompletableFuture().join()) { assertEquals("FooBar", IOUtils.toString(content, UTF_8), "Binary content did not match"); } }
@Test public void testGetBinaryDigestError() throws Exception { when(mockBinaryService.calculateDigest(eq(binaryInternalIdentifier), any(MessageDigest.class))) .thenAnswer(inv -> supplyAsync(() -> { throw new RuntimeTrellisException("Expected exception"); })); final Response res = target(BINARY_PATH).request().header(WANT_DIGEST, "MD5").get(); assertEquals(SC_INTERNAL_SERVER_ERROR, res.getStatus(), "Unexpected response code!"); }
@Test public void testIdSupplier() { final BinaryService service = new FileBinaryService(); assertTrue(service.generateIdentifier().startsWith("file:///"), "Identifier has incorrect prefix!"); assertNotEquals(service.generateIdentifier(), service.generateIdentifier(), "Identifiers are not unique!"); }
@Test public void testSupportedAlgorithms() { final BinaryService service = new FileBinaryService(); assertTrue(service.supportedAlgorithms().contains("SHA")); assertTrue(service.supportedAlgorithms().contains("SHA-1")); assertTrue(service.supportedAlgorithms().contains("SHA-256")); assertTrue(service.supportedAlgorithms().contains("MD5")); }
/** * Set the content for a binary object using a digest algorithm. * * @implSpec The default implementation will compute a digest while processing the {@code InputStream}. * @param metadata the binary metadata * @param stream the context * @param algorithm the digest algorithm * @return the new completion stage containing the server-computed digest. */ default CompletionStage<MessageDigest> setContent(final BinaryMetadata metadata, final InputStream stream, final MessageDigest algorithm) { final DigestInputStream input = new DigestInputStream(stream, algorithm); return setContent(metadata, input).thenApply(future -> input.getMessageDigest()); }
@Test public void testGetBinaryErrorSkip() throws IOException { when(mockBinaryService.get(eq(binaryInternalIdentifier))).thenAnswer(inv -> completedFuture(mockBinary)); when(mockBinary.getContent()).thenReturn(mockInputStream); when(mockInputStream.skip(anyLong())).thenThrow(new IOException()); final Response res = target(BINARY_PATH).request().header(RANGE, "bytes=300-400").get(); assertEquals(SC_INTERNAL_SERVER_ERROR, res.getStatus(), "Unexpected response code!"); }
@Test public void testBase64Digest() throws IOException { final BinaryService service = new FileBinaryService(); assertEquals("oZ1Y1O/8vs39RH31fh9lrA==", service.calculateDigest(file, getDigest("MD5")) .thenApply(MessageDigest::digest).thenApply(getEncoder()::encodeToString) .toCompletableFuture().join(), "Bad MD5 digest!"); assertEquals("QJuYLse9SK/As177lt+rSfixyH0=", service.calculateDigest(file, getDigest("SHA-1")) .thenApply(MessageDigest::digest).thenApply(getEncoder()::encodeToString) .toCompletableFuture().join(), "Bad SHA digest!"); assertThrows(CompletionException.class, () -> service.calculateDigest(rdf.createIRI("file:///" + randomFilename()), getDigest("MD5")) .toCompletableFuture().join(), "Computing digest on invalid file should throw an exception!"); }
LOGGER.trace("Successfully checked for bad digest value"); final String mimeType = ofNullable(getRequest().getContentType()).orElse(APPLICATION_OCTET_STREAM); final IRI binaryLocation = rdf.createIRI(getServices().getBinaryService().generateIdentifier());
private void setUpBinaryService() throws Exception { when(mockBinaryService.supportedAlgorithms()).thenReturn(new HashSet<>(asList("MD5", "SHA-1", "SHA"))); when(mockDigest.digest()).thenReturn(getDecoder().decode("Q29tcHV0ZWREaWdlc3Q=")); when(mockBinaryService.calculateDigest(eq(binaryInternalIdentifier), any(MessageDigest.class))) .thenReturn(completedFuture(mockDigest)); when(mockBinaryService.get(eq(binaryInternalIdentifier))).thenAnswer(inv -> completedFuture(mockBinary)); when(mockBinary.getContent(eq(3), eq(10))).thenReturn(new ByteArrayInputStream("e input".getBytes(UTF_8))); when(mockBinary.getContent()).thenReturn(new ByteArrayInputStream("Some input stream".getBytes(UTF_8))); when(mockBinaryService.setContent(any(BinaryMetadata.class), any(InputStream.class), any())) .thenAnswer(inv -> { readLines((InputStream) inv.getArguments()[1], UTF_8); return completedFuture(null); }); when(mockBinaryService.setContent(any(BinaryMetadata.class), any(InputStream.class))) .thenAnswer(inv -> { readLines((InputStream) inv.getArguments()[1], UTF_8); return completedFuture(null); }); when(mockBinaryService.purgeContent(any(IRI.class))).thenReturn(completedFuture(null)); when(mockBinaryService.generateIdentifier()).thenReturn(RANDOM_VALUE); doCallRealMethod().when(mockBinaryService) .setContent(any(BinaryMetadata.class), any(InputStream.class), any()); }
@BeforeEach public void setUp() { initMocks(this); doCallRealMethod().when(mockBinaryService).setContent(any(BinaryMetadata.class), any(InputStream.class), any(MessageDigest.class)); }
private CompletionStage<InputStream> getBinaryStream(final IRI dsid, final TrellisRequest req) { if (isNull(req.getRange())) { return getServices().getBinaryService().get(dsid).thenApply(Binary::getContent); } return getServices().getBinaryService().get(dsid) .thenApply(b -> b.getContent(req.getRange().getFrom(), req.getRange().getTo())); }
@Test @DisabledOnJre(JAVA_8) public void testJdk9Digests() throws IOException { final BinaryService service = new FileBinaryService(); assertEquals("FQgyH2yU2NhyMTZ7YDDKwV5vcWUBM1zq0uoIYUiHH+4=", service.calculateDigest(file, getDigest("SHA3-256")).thenApply(MessageDigest::digest) .thenApply(getEncoder()::encodeToString).toCompletableFuture().join(), "Bad SHA3-256 digest!"); assertEquals("746UDLrFXM61gzI0FnoVT2S0Z7EmQUfhHnoSYwkR2MHzbBe6j9rMigQBfR8ApZUA", service.calculateDigest(file, getDigest("SHA3-384")).thenApply(MessageDigest::digest) .thenApply(getEncoder()::encodeToString).toCompletableFuture().join(), "Bad SHA3-384 digest!"); assertEquals("Ecu/R0kV4eL0J/VOpyVA2Lz0T6qsJj9ioQ+QorJDztJeMj6uhf6zqyhZnu9zMYiwrkX8U4oWiZMDT/0fWjOyYg==", service.calculateDigest(file, getDigest("SHA3-512")).thenApply(MessageDigest::digest) .thenApply(getEncoder()::encodeToString).toCompletableFuture().join(), "Bad SHA3-512 digest!"); }
final IRI binaryLocation = rdf.createIRI(getServices().getBinaryService().generateIdentifier());
protected CompletionStage<Void> persistContent(final BinaryMetadata metadata) { return getServices().getBinaryService().setContent(metadata, entity) .whenComplete(HttpUtils.closeInputStreamAsync(entity)); }