Skip to content

Commit a6c0137

Browse files
New Release merge - 2025/Apr/22 (#945)
* Enhanced file preview feature - Run file preview as authenticated user (#936), Property to allow composing file previews (#935), Do not create temp file during generating preview (#921) * Use the handle url instead of the items url with the UUID (#938) * Items file metadata not correctly updated (#940) * Remove duplicate dependency element with identical content (warnings in build) * Anonymous users should be able to use shortener * Fixed internal server error, when context.commit() was called multiple times in search request * Catch DSpaceBadRequestException rather than BadRequestException * Fixed security issue for downloading file with non anonymous license * Fixed Clarinuserregistration nullpoint exception (#941)
1 parent 4c60bad commit a6c0137

44 files changed

Lines changed: 999 additions & 351 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dspace-api/pom.xml

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -859,23 +859,6 @@
859859
</exclusions>
860860
</dependency>
861861

862-
<dependency>
863-
<groupId>io.findify</groupId>
864-
<artifactId>s3mock_2.13</artifactId>
865-
<version>0.2.6</version>
866-
<scope>test</scope>
867-
<exclusions>
868-
<exclusion>
869-
<groupId>com.amazonawsl</groupId>
870-
<artifactId>aws-java-sdk-s3</artifactId>
871-
</exclusion>
872-
<exclusion>
873-
<groupId>com.amazonaws</groupId>
874-
<artifactId>aws-java-sdk-s3</artifactId>
875-
</exclusion>
876-
</exclusions>
877-
</dependency>
878-
879862
</dependencies>
880863

881864
<dependencyManagement>

dspace-api/src/main/java/org/dspace/api/DSpaceApi.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public static void handle_HandleManager_registerFinalHandleURL(Logger log,
9797
}
9898

9999
String url = configurationService.getProperty("dspace.ui.url");
100-
url = generateItemURLWithUUID(url, dso);
100+
url = generateItemURLWithHandle(url, dso);
101101

102102
/*
103103
* request modification of the PID to point to the correct URL, which
@@ -115,17 +115,18 @@ public static void handle_HandleManager_registerFinalHandleURL(Logger log,
115115
}
116116

117117
/**
118-
* Generate a URL for the given DSpaceObject. The URL consist of the base URL and the ID of the DSpace object.
119-
* E.g. `http://localhost:4000/items/<UUID>`
118+
* Generate a URL with the handle for the given DSpaceObject.
119+
* The URL consist of the base URL and the handle of the DSpace object.
120+
* E.g. `http://localhost:4000/handle/<ITEMS_HANDLE>`
120121
* @param url base URL of the DSpace instance
121122
* @param dSpaceObject the DSpace object for which the URL is generated
122123
* @return the generated URL
123124
*/
124-
public static String generateItemURLWithUUID(String url, DSpaceObject dSpaceObject) {
125+
public static String generateItemURLWithHandle(String url, DSpaceObject dSpaceObject) {
125126
if (dSpaceObject == null) {
126127
log.error("DSpaceObject is null, cannot generate URL");
127128
return url;
128129
}
129-
return url + (url.endsWith("/") ? "" : "/") + "items/" + dSpaceObject.getID();
130+
return url + (url.endsWith("/") ? "" : "/") + "handle/" + dSpaceObject.getHandle();
130131
}
131132
}

dspace-api/src/main/java/org/dspace/authorize/AuthorizationBitstreamUtils.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88
package org.dspace.authorize;
99

10+
import static org.dspace.content.clarin.ClarinLicense.Confirmation;
11+
1012
import java.sql.SQLException;
1113
import java.util.List;
1214
import java.util.Objects;
@@ -117,10 +119,10 @@ public boolean authorizeLicenseWithUser(Context context, UUID bitstreamID) throw
117119

118120
// Bitstream should have only one type of the Clarin license, so we could get first record
119121
ClarinLicense clarinLicense = Objects.requireNonNull(clarinLicenseResourceMappings.get(0)).getLicense();
120-
// 3 - Allow download for anonymous users, but with license confirmation
121-
// 0 - License confirmation is not required
122-
if (Objects.equals(clarinLicense.getConfirmation(), 3) ||
123-
Objects.equals(clarinLicense.getConfirmation(), 0)) {
122+
// ALLOW_ANONYMOUS - Allow download for anonymous users, but with license confirmation
123+
// NOT_REQUIRED - License confirmation is not required
124+
if ((clarinLicense.getConfirmation() == Confirmation.ALLOW_ANONYMOUS) ||
125+
(clarinLicense.getConfirmation() == Confirmation.NOT_REQUIRED)) {
124126
return true;
125127
}
126128
return false;

dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ public void update(Context context, Bitstream bitstream) throws SQLException, Au
266266
@Override
267267
public void delete(Context context, Bitstream bitstream) throws SQLException, AuthorizeException {
268268

269-
// changed to a check on delete
269+
// Changed to a check on delete
270270
// Check authorisation
271271
authorizeService.authorizeAction(context, bitstream, Constants.DELETE);
272272
log.info(LogHelper.getHeader(context, "delete_bitstream",
@@ -278,26 +278,26 @@ public void delete(Context context, Bitstream bitstream) throws SQLException, Au
278278
// Remove bitstream itself
279279
bitstream.setDeleted(true);
280280
update(context, bitstream);
281-
// Update Item's metadata about bitstreams
282-
clarinItemService.updateItemFilesMetadata(context, bitstream);
283281

284-
//Remove our bitstream from all our bundles
282+
// Remove our bitstream from all our bundles
285283
final List<Bundle> bundles = bitstream.getBundles();
286284
for (Bundle bundle : bundles) {
287285
authorizeService.authorizeAction(context, bundle, Constants.REMOVE);
288-
//We also need to remove the bitstream id when it's set as bundle's primary bitstream
286+
// We also need to remove the bitstream id when it's set as bundle's primary bitstream
289287
if (bitstream.equals(bundle.getPrimaryBitstream())) {
290288
bundle.unsetPrimaryBitstreamID();
291289
}
292290
bundle.removeBitstream(bitstream);
293291
}
294-
//Remove all bundles from the bitstream object, clearing the connection in 2 ways
292+
// Update Item's metadata about bitstreams
293+
clarinItemService.updateItemFilesMetadata(context, bitstream);
294+
// Remove all bundles from the bitstream object, clearing the connection in 2 ways
295295
bundles.clear();
296296

297297
// Remove policies only after the bitstream has been updated (otherwise the current user has not WRITE rights)
298298
authorizeService.removeAllPolicies(context, bitstream);
299299

300-
// detach the license from the bitstream
300+
// Detach the license from the bitstream
301301
clarinLicenseResourceMappingService.detachLicenses(context, bitstream);
302302
}
303303

dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ public void removeBitstream(Context context, Bundle bundle, Bitstream bitstream)
242242
if (owningItem != null) {
243243
itemService.updateLastModified(context, owningItem);
244244
itemService.update(context, owningItem);
245-
clarinItemService.updateItemFilesMetadata(context, owningItem, bundle);
246245
}
247246

248247
// In the event that the bitstream to remove is actually
@@ -265,6 +264,7 @@ public void removeBitstream(Context context, Bundle bundle, Bitstream bitstream)
265264
bundle.removeBitstream(bitstream);
266265
bitstream.getBundles().remove(bundle);
267266
}
267+
clarinItemService.updateItemFilesMetadata(context, owningItem, bundle);
268268
}
269269

270270
@Override

dspace-api/src/main/java/org/dspace/content/PreviewContentServiceImpl.java

Lines changed: 35 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313
import java.io.InputStreamReader;
1414
import java.io.UnsupportedEncodingException;
1515
import java.nio.charset.StandardCharsets;
16-
import java.nio.file.FileSystem;
17-
import java.nio.file.FileSystems;
1816
import java.nio.file.Files;
17+
import java.nio.file.InvalidPathException;
1918
import java.nio.file.Path;
2019
import java.nio.file.Paths;
21-
import java.nio.file.StandardCopyOption;
2220
import java.sql.SQLException;
2321
import java.util.ArrayList;
2422
import java.util.Arrays;
@@ -31,6 +29,8 @@
3129
import java.util.function.Function;
3230
import java.util.regex.Matcher;
3331
import java.util.regex.Pattern;
32+
import java.util.zip.ZipEntry;
33+
import java.util.zip.ZipInputStream;
3434

3535
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
3636
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@@ -287,18 +287,6 @@ private <T, U> Hashtable<String, T> createSubMap(Map<String, U> sourceMap, Funct
287287
return sub;
288288
}
289289

290-
/**
291-
* Creates a temporary file with the appropriate extension based on the specified file type.
292-
* @param fileType the type of file for which to create a temporary file
293-
* @return a Path object representing the temporary file
294-
* @throws IOException if an I/O error occurs while creating the file
295-
*/
296-
private Path createTempFile(String fileType) throws IOException {
297-
String extension = ARCHIVE_TYPE_TAR.equals(fileType) ?
298-
String.format(".%s", ARCHIVE_TYPE_TAR) : String.format(".%s", ARCHIVE_TYPE_ZIP);
299-
return Files.createTempFile("temp", extension);
300-
}
301-
302290
/**
303291
* Adds a file path and its size to the list of file paths.
304292
* If the path represents a directory, appends a "/" to the path.
@@ -307,68 +295,50 @@ private Path createTempFile(String fileType) throws IOException {
307295
* @param size the size of the file or directory
308296
*/
309297
private void addFilePath(List<String> filePaths, String path, long size) {
310-
String fileInfo = (Files.isDirectory(Paths.get(path))) ? path + "/|" + size : path + "|" + size;
298+
String fileInfo = "";
299+
try {
300+
Path filePath = Paths.get(path);
301+
boolean isDir = Files.isDirectory(filePath);
302+
fileInfo = (isDir ? path + "/|" : path + "|") + size;
303+
} catch (NullPointerException | InvalidPathException | SecurityException e) {
304+
log.error(String.format("Failed to add file path. Path: '%s', Size: %d", path, size), e);
305+
}
311306
filePaths.add(fileInfo);
312307
}
313308

314309
/**
315310
* Processes a TAR file, extracting its entries and adding their paths to the provided list.
316311
* @param filePaths the list to populate with the extracted file paths
317-
* @param tempFile the temporary TAR file to process
312+
* @param inputStream the TAR file data
318313
* @throws IOException if an I/O error occurs while reading the TAR file
319314
*/
320-
private void processTarFile(List<String> filePaths, Path tempFile) throws IOException {
321-
try (InputStream fi = Files.newInputStream(tempFile);
322-
TarArchiveInputStream tis = new TarArchiveInputStream(fi)) {
315+
private void processTarFile(List<String> filePaths, InputStream inputStream) throws IOException {
316+
try (TarArchiveInputStream tis = new TarArchiveInputStream(inputStream)) {
323317
TarArchiveEntry entry;
324318
while ((entry = tis.getNextTarEntry()) != null) {
325-
addFilePath(filePaths, entry.getName(), entry.getSize());
319+
if (!entry.isDirectory()) {
320+
// Add the file path and its size (from the TAR entry)
321+
addFilePath(filePaths, entry.getName(), entry.getSize());
322+
}
326323
}
327324
}
328325
}
329326

330327
/**
331328
* Processes a ZIP file, extracting its entries and adding their paths to the provided list.
332329
* @param filePaths the list to populate with the extracted file paths
333-
* @param zipFileSystem the FileSystem object representing the ZIP file
334-
* @throws IOException if an I/O error occurs while reading the ZIP file
335-
*/
336-
private void processZipFile(List<String> filePaths, FileSystem zipFileSystem) throws IOException {
337-
Path root = zipFileSystem.getPath("/");
338-
Files.walk(root).forEach(path -> {
339-
try {
340-
long fileSize = Files.size(path);
341-
addFilePath(filePaths, path.toString().substring(1), fileSize);
342-
} catch (IOException e) {
343-
log.error("An error occurred while getting the size of the zip file.", e);
344-
}
345-
});
346-
}
347-
348-
/**
349-
* Closes the specified FileSystem resource if it is not null.
350-
* @param zipFileSystem the FileSystem to close
330+
* @param inputStream the ZIP file data
331+
* @throws IOException if an I/O error occurs while reading the ZIP file
351332
*/
352-
private void closeFileSystem(FileSystem zipFileSystem) {
353-
if (Objects.nonNull(zipFileSystem)) {
354-
try {
355-
zipFileSystem.close();
356-
} catch (IOException e) {
357-
log.error("An error occurred while closing the zip file.", e);
358-
}
359-
}
360-
}
361-
362-
/**
363-
* Deletes the specified temporary file if it is not null.
364-
* @param tempFile the Path object representing the temporary file to delete
365-
*/
366-
private void deleteTempFile(Path tempFile) {
367-
if (Objects.nonNull(tempFile)) {
368-
try {
369-
Files.delete(tempFile);
370-
} catch (IOException e) {
371-
log.error("An error occurred while deleting temp file.", e);
333+
private void processZipFile(List<String> filePaths, InputStream inputStream) throws IOException {
334+
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
335+
ZipEntry entry;
336+
while ((entry = zipInputStream.getNextEntry()) != null) {
337+
if (!entry.isDirectory()) {
338+
// Add the file path and its size (from the ZIP entry)
339+
long fileSize = entry.getSize();
340+
addFilePath(filePaths, entry.getName(), fileSize);
341+
}
372342
}
373343
}
374344
}
@@ -406,36 +376,20 @@ private String buildXmlResponse(List<String> filePaths) {
406376
}
407377

408378
/**
409-
* Extracts files from an InputStream, processes them based on the specified file type (tar or zip),
379+
* Processes file data based on the specified file type (tar or zip),
410380
* and returns an XML representation of the file paths.
411381
* @param inputStream the InputStream containing the file data
412382
* @param fileType the type of file to extract ("tar" or "zip")
413383
* @return an XML string representing the extracted file paths
414384
*/
415385
private String extractFile(InputStream inputStream, String fileType) throws Exception {
416386
List<String> filePaths = new ArrayList<>();
417-
Path tempFile = null;
418-
FileSystem zipFileSystem = null;
419-
420-
try {
421-
// Create a temporary file based on the file type
422-
tempFile = createTempFile(fileType);
423-
424-
// Copy the input stream to the temporary file
425-
Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
426-
427-
// Process the file based on its type
428-
if (ARCHIVE_TYPE_TAR.equals(fileType)) {
429-
processTarFile(filePaths, tempFile);
430-
} else {
431-
zipFileSystem = FileSystems.newFileSystem(tempFile, (ClassLoader) null);
432-
processZipFile(filePaths, zipFileSystem);
433-
}
434-
} finally {
435-
closeFileSystem(zipFileSystem);
436-
deleteTempFile(tempFile);
387+
// Process the file based on its type
388+
if (ARCHIVE_TYPE_TAR.equals(fileType)) {
389+
processTarFile(filePaths, inputStream);
390+
} else {
391+
processZipFile(filePaths, inputStream);
437392
}
438-
439393
return buildXmlResponse(filePaths);
440394
}
441395

dspace-api/src/main/java/org/dspace/content/clarin/ClarinItemServiceImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ public void updateItemFilesMetadata(Context context, Item item, Bundle bundle) t
155155
return;
156156
}
157157

158+
if (Objects.isNull(item)) {
159+
log.error("Cannot update the item files metadata because the item is null.");
160+
return;
161+
}
162+
158163
int totalNumberOfFiles = 0;
159164
long totalSizeofFiles = 0;
160165

dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicense.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import javax.persistence.CascadeType;
1717
import javax.persistence.Column;
1818
import javax.persistence.Entity;
19+
import javax.persistence.EnumType;
20+
import javax.persistence.Enumerated;
1921
import javax.persistence.FetchType;
2022
import javax.persistence.GeneratedValue;
2123
import javax.persistence.GenerationType;
@@ -83,7 +85,8 @@ public class ClarinLicense implements ReloadableEntity<Integer> {
8385
private String definition = null;
8486

8587
@Column(name = "confirmation")
86-
private Integer confirmation = 0;
88+
@Enumerated(EnumType.ORDINAL)
89+
private Confirmation confirmation = Confirmation.NOT_REQUIRED;
8790

8891
@Column(name = "required_info")
8992
private String requiredInfo = null;
@@ -111,11 +114,11 @@ public void setDefinition(String definition) {
111114
this.definition = definition;
112115
}
113116

114-
public Integer getConfirmation() {
115-
return confirmation;
117+
public Confirmation getConfirmation() {
118+
return confirmation == null ? Confirmation.NOT_REQUIRED : confirmation;
116119
}
117120

118-
public void setConfirmation(Integer confirmation) {
121+
public void setConfirmation(Confirmation confirmation) {
119122
this.confirmation = confirmation;
120123
}
121124

@@ -191,4 +194,29 @@ public ClarinUserRegistration getEperson() {
191194
public void setEperson(ClarinUserRegistration eperson) {
192195
this.eperson = eperson;
193196
}
197+
198+
public enum Confirmation {
199+
200+
// if new Confirmation value is needed, add it to the end of this list, to not break the backward compatibility
201+
NOT_REQUIRED(0), ASK_ONLY_ONCE(1), ASK_ALWAYS(2), ALLOW_ANONYMOUS(3);
202+
203+
private final int value;
204+
205+
Confirmation(int value) {
206+
this.value = value;
207+
}
208+
209+
public int getValue() {
210+
return value;
211+
}
212+
213+
public static Confirmation getConfirmation(int value) {
214+
return Arrays.stream(values())
215+
.filter(v -> (v.getValue() == value))
216+
.findFirst()
217+
.orElseThrow(() ->
218+
new IllegalArgumentException("No license confirmation found for value: " + value));
219+
}
220+
221+
}
194222
}

0 commit comments

Comments
 (0)