Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
77a448c
chore(deps): bump jacksonVersion from 2.22.0 to 2.22.1
dependabot[bot] Jul 17, 2026
eb44111
chore(deps): update actions/setup-java action to v5.6.0
renovate[bot] Jul 19, 2026
94cb502
fix(deps): update dependency org.junit:junit-bom to v6.1.2
renovate[bot] Jul 20, 2026
20052d3
test: write tests to detect missing paths when reading a crate
Pfeil Jul 22, 2026
2a1f0e1
docs: add javadoc to DataEntity.getPath
Pfeil Jul 22, 2026
ffe5b91
fix: writing files uses decoded ID of an entity
Pfeil Jul 22, 2026
ee2a7f6
fix: detect files matching the decoded or encoded id of an entity
Pfeil Jul 22, 2026
a0564bb
fix(test): avoid invalid paths on windows in a test
Pfeil Jul 22, 2026
14d9f10
test: loading and saving "heals" filename in crates
Pfeil Jul 22, 2026
3f51510
test: all writers write decoded ids as filenames
Pfeil Jul 22, 2026
43fe41f
fix: zip stream writer writes decoded filenames
Pfeil Jul 22, 2026
3898149
chore(deps): update actions/checkout action to v7.0.1
renovate[bot] Jul 23, 2026
668309e
chore(deps): update github/codeql-action action to v4.37.3
renovate[bot] Jul 25, 2026
e75c9b2
test: avoid emoji in filenames because it causes issues on windows
Pfeil Jul 27, 2026
d596dcc
test: avoid pipe in filenames because it might cause issues on windows
Pfeil Jul 27, 2026
e370c45
Revert "test: avoid emoji in filenames because it causes issues on wi…
Pfeil Jul 27, 2026
90f814f
Merge pull request #353 from kit-data-manager/320-encoded-paths-are-n…
Pfeil Jul 27, 2026
f39d27d
Merge pull request #354 from kit-data-manager/renovate/actions-checko…
Pfeil Jul 27, 2026
996b324
Merge pull request #351 from kit-data-manager/dependabot/gradle/jacks…
Pfeil Jul 27, 2026
60b3446
Merge pull request #347 from kit-data-manager/renovate/github-codeql-…
Pfeil Jul 27, 2026
cc14008
Merge pull request #346 from kit-data-manager/renovate/junit-framewor…
Pfeil Jul 27, 2026
3cd9985
Merge pull request #343 from kit-data-manager/renovate/actions-setup-…
Pfeil Jul 27, 2026
3d7f6cd
cleanup: remove debug println calls
Pfeil Aug 12, 2026
359edde
fix: add checks of the filenames when writing files
Pfeil Aug 13, 2026
ca3e605
Merge pull request #362 from kit-data-manager/add-path-checks-when-wr…
Pfeil Aug 13, 2026
23cf2c7
fix(deps): update dependency org.junit:junit-bom to v6.1.3
renovate[bot] Aug 13, 2026
555c88f
fix(deps): update dependency commons-validator:commons-validator to v…
renovate[bot] Aug 13, 2026
d5e5569
fix: Normalize base folder in writer containment check
Pfeil Aug 13, 2026
2ce6ea6
fix: ensure zip path sanitization normalizes properly
Pfeil Aug 14, 2026
c292365
Merge pull request #357 from kit-data-manager/renovate/commons-valida…
Pfeil Aug 14, 2026
0c2cb9c
chore(deps): update github/codeql-action action to v4.37.6
renovate[bot] Aug 14, 2026
d9a51f0
chore(deps): update actions/setup-java action to v5.7.0
renovate[bot] Aug 14, 2026
b957660
chore(deps): update gradle/actions action to v6.3.0
renovate[bot] Aug 14, 2026
1d12d81
Merge pull request #361 from kit-data-manager/renovate/junit-framewor…
Pfeil Aug 14, 2026
3270694
Merge pull request #363 from kit-data-manager/add-path-checks-when-wr…
Pfeil Aug 14, 2026
528cbcd
Merge pull request #360 from kit-data-manager/renovate/gradle-actions…
Pfeil Aug 14, 2026
24b3026
Merge pull request #359 from kit-data-manager/renovate/actions-setup-…
Pfeil Aug 14, 2026
c9fce7d
Merge pull request #358 from kit-data-manager/renovate/github-codeql-…
Pfeil Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6

# Command-line programs to run using the OS shell.
# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -69,4 +69,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
10 changes: 5 additions & 5 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Comment thread
Pfeil marked this conversation as resolved.
- name: Set up openJDK version
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: ${{ matrix.jdk }}
distribution: "zulu"
- name: Install Dependencies
run: npm install -g ro-crate-html-js
- name: Setup Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Build and Test with Gradle
run: ./gradlew -Dprofile=release build
- name: Upload (test) reports as artifact on GitHub on manual runs
Expand All @@ -47,9 +47,9 @@ jobs:
jdkversion: 25
steps:
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up OpenJDK version ...
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "zulu"
java-version: ${{ env.jdkversion }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publishRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Java
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: 21
distribution: 'zulu' # openjdk
Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ repositories {
}

ext {
jacksonVersion = '2.22.0'
jacksonVersion = '2.22.1'
}

dependencies {
// JUnit setup for testing
testImplementation(platform("org.junit:junit-bom:6.1.0"))
testImplementation(platform("org.junit:junit-bom:6.1.3"))
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
// JSON object mapping / (de-)serialization
Expand All @@ -58,7 +58,7 @@ dependencies {
// compare json documents in tests
implementation 'com.github.fslev:json-compare:8.1'
// url validator
implementation group: 'commons-validator', name: 'commons-validator', version: '1.10.1'
implementation group: 'commons-validator', name: 'commons-validator', version: '1.11.0'
// logging
implementation group: 'org.slf4j', name: 'slf4j-jdk14', version: '2.0.18'
// JSON-LD, Zenodo mapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ public void addAuthorId(String id) {
this.addIdProperty("author", id);
}

/**
* Gets the path of this entity.
* <p>
* When reading a crate, this will point to the entity's file within the crate.
* When creating a new entity or crate, it might point to a file outside the crate,
* as set by {@link DataEntityBuilder#setLocation(Path)}.
* Such a file will be copied into the crate when writing.
*
* @return the path to the file this entity represents.
*/
@JsonIgnore
public Path getPath() {
return path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
Expand Down Expand Up @@ -240,12 +241,13 @@ protected Optional<File> checkFolderHasFile(String filepathOrId, File folder) {
if (IdentifierUtils.isUrl(filepathOrId)) {
return Optional.empty();
}
return IdentifierUtils.decode(filepathOrId)
.map(decoded -> folder.toPath().resolve(decoded).normalize())
return Stream.of(IdentifierUtils.decode(filepathOrId).orElse(filepathOrId), filepathOrId)
.map(filename -> folder.toPath().resolve(filename).normalize().toAbsolutePath())
// defence-in-depth: ensure we are still inside the crate folder
.filter(resolved -> resolved.startsWith(folder.toPath()))
.map(Path::toFile)
.filter(File::exists);
.filter(File::exists)
.findFirst();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import edu.kit.datamanager.ro_crate.Crate;
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
import edu.kit.datamanager.ro_crate.special.IdentifierUtils;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -14,6 +15,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

/**
* A class for writing a crate to a folder.
Expand Down Expand Up @@ -64,16 +66,25 @@ public void save(Crate crate, String destination) throws IOException {
}
}
for (DataEntity dataEntity : crate.getAllDataEntities()) {
savetoFile(dataEntity, file);
saveToFile(dataEntity, file);
}
}

private void savetoFile(DataEntity entity, File file) throws IOException {
private void saveToFile(DataEntity entity, File file) throws IOException {
if (entity.getPath() != null) {
String id = entity.getId();
String filename = IdentifierUtils.decode(id).orElse(id);
Path baseFolder = file.toPath().toAbsolutePath().normalize();
Path destination = baseFolder.resolve(filename).normalize();
// defence-in-depth: ensure the resolved path remains inside the crate folder
if (!destination.startsWith(baseFolder)) {
logger.warn("Skipping entity '{}': resolved path '{}' escapes destination folder '{}'", id, destination, file);
return;
}
if (entity.getPath().toFile().isDirectory()) {
FileUtils.copyDirectory(entity.getPath().toFile(), file.toPath().resolve(entity.getId()).toFile());
FileUtils.copyDirectory(entity.getPath().toFile(), destination.toFile());
} else {
FileUtils.copyFile(entity.getPath().toFile(), file.toPath().resolve(entity.getId()).toFile());
FileUtils.copyFile(entity.getPath().toFile(), destination.toFile());
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import edu.kit.datamanager.ro_crate.preview.CratePreview;
import edu.kit.datamanager.ro_crate.special.IdentifierUtils;
import edu.kit.datamanager.ro_crate.util.FileSystemUtil;
import edu.kit.datamanager.ro_crate.util.ZipStreamUtil;
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
Expand Down Expand Up @@ -154,16 +157,67 @@ private void saveToStream(DataEntity entity, ZipOutputStream zipStream, String p
}

boolean isDirectory = entity.getPath().toFile().isDirectory();
String id = entity.getId();
String filename = IdentifierUtils.decode(id).orElse(id);
String safeName = sanitizeZipEntryName(filename);
if (safeName.isEmpty()) {
logger.warn("Skipping entity '{}': decoded name resolves outside the crate root", id);
return;
}
String entryName = prefix + safeName;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (isDirectory) {
ZipStreamUtil.addFolderToZipStream(
zipStream,
entity.getPath().toFile(),
prefix + entity.getId());
entryName);
} else {
ZipStreamUtil.addFileToZipStream(
zipStream,
entity.getPath().toFile(),
prefix + entity.getId());
entryName);
}
}

/**
* Normalizes a decoded entry name into a safe relative path, consistent
* with the containment check in {@link WriteFolderStrategy#saveToFile}.
* <p>
* Absolute paths (leading {@code /}) and Windows drive-qualified paths
* (e.g. {@code C:/}) are rejected. Internal {@code .} and {@code ..}
* segments are resolved; if the result escapes the virtual crate root
* the name is rejected.
*
* @param name the raw decoded entry name
* @return a canonical relative entry name, or an empty string if the name
* is absolute, drive-qualified, or escapes the crate root
*/
private static String sanitizeZipEntryName(String name) {
// zip entries always use forward slashes as separators
String normalized = name.replace('\\', '/');

// Reject absolute paths and Windows drive-qualified paths (e.g. C:/)
if (normalized.startsWith("/")
|| (normalized.length() >= 2 && normalized.charAt(1) == ':'
&& Character.isLetter(normalized.charAt(0)))) {
return "";
}

// Normalize "." and ".." segments, rejecting paths that escape the root
String[] segments = normalized.split("/");
List<String> stack = new ArrayList<>();
for (String segment : segments) {
if (segment.isEmpty() || segment.equals(".")) {
continue;
}
if (segment.equals("..")) {
if (stack.isEmpty()) {
return "";
}
stack.remove(stack.size() - 1);
} else {
stack.add(segment);
}
}
return String.join("/", stack);
}
}
Loading
Loading