Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryKeyFunction;
import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.util.PathUtils;

/**
* Support class for {@link LocalPathPrefixComposerFactory} implementations: it predefines and makes re-usable
Expand Down Expand Up @@ -286,13 +287,13 @@ public String getPathPrefixForRemoteArtifact(Artifact artifact, RemoteRepository
}
String result = remotePrefix;
if (!splitRemoteRepositoryLast && splitRemoteRepository) {
result += "/" + repositoryKeyFunction.apply(repository, null);
result += "/" + repositoryKeySegment(repository);
}
if (splitRemote) {
result += "/" + (artifact.isSnapshot() ? snapshotsPrefix : releasesPrefix);
}
if (splitRemoteRepositoryLast && splitRemoteRepository) {
result += "/" + repositoryKeyFunction.apply(repository, null);
result += "/" + repositoryKeySegment(repository);
}
return result;
}
Expand All @@ -316,19 +317,29 @@ public String getPathPrefixForRemoteMetadata(Metadata metadata, RemoteRepository
}
String result = remotePrefix;
if (!splitRemoteRepositoryLast && splitRemoteRepository) {
result += "/" + repositoryKeyFunction.apply(repository, null);
result += "/" + repositoryKeySegment(repository);
}
if (splitRemote) {
result += "/" + (isSnapshot(metadata) ? snapshotsPrefix : releasesPrefix);
}
if (splitRemoteRepositoryLast && splitRemoteRepository) {
result += "/" + repositoryKeyFunction.apply(repository, null);
result += "/" + repositoryKeySegment(repository);
}
return result;
}

protected boolean isSnapshot(Metadata metadata) {
return !metadata.getVersion().isEmpty() && metadata.getVersion().endsWith("-SNAPSHOT");
}

/**
* Defense in depth: the repository key is spliced into the local repository path prefix, so it
* must be a safe path segment.
*/
private String repositoryKeySegment(RemoteRepository repository) {
String key = repositoryKeyFunction.apply(repository, null);
PathUtils.validatePathComponent(key, "repository key");
return key;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.eclipse.aether.spi.io.ChecksumProcessor;
import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.util.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -178,6 +179,9 @@ private String calculateArtifactPath(
String path = localPathComposer.getPathForArtifact(artifact, false) + "."
+ checksumAlgorithmFactory.getFileExtension();
if (originAware) {
// defense in depth: the repository key is spliced into a path under the checksums basedir,
// so it must be a safe path segment
PathUtils.validatePathComponent(safeRepositoryId, "repository key");
path = safeRepositoryId + "/" + path;
}
return path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,30 @@ void fullConfig() {
assertNotNull(prefix);
assertEquals("cached/my-repo/releases", prefix);
}

@Test
void dotDotRepositoryIdIsNeutralizedInSplitPrefix() {
DefaultRepositorySystemSession session = TestUtils.newSession();
session.setConfigProperty(DefaultLocalPathPrefixComposerFactory.CONFIG_PROP_SPLIT, Boolean.TRUE.toString());
session.setConfigProperty(
DefaultLocalPathPrefixComposerFactory.CONFIG_PROP_SPLIT_REMOTE_REPOSITORY, Boolean.TRUE.toString());

LocalPathPrefixComposerFactory factory =
new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory());
LocalPathPrefixComposer composer = factory.createComposer(session);
assertNotNull(composer);

// a repository id of ".." must be neutralized into a harmless path segment rather than spliced
// into the prefix as-is
RemoteRepository dotDotRepository =
new RemoteRepository.Builder("..", "default", "https://repo.example.org/").build();

String prefix = composer.getPathPrefixForRemoteArtifact(releaseArtifact, dotDotRepository);
assertNotNull(prefix);
assertEquals("cached/-DOTDOT-", prefix);

prefix = composer.getPathPrefixForRemoteMetadata(releaseMetadata, dotDotRepository);
assertNotNull(prefix);
assertEquals("cached/-DOTDOT-", prefix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,16 @@ public static String stringToPathSegment(String string) {
pos = result.indexOf(illegal);
}
}
return result.toString();
// Strings consisting solely of dots contain no illegal character, yet "." and ".." carry path
// meaning when used as a path segment. Map them to explicit tokens, mirroring the character
// replacements above.
String segment = result.toString();
if (segment.equals(".")) {
return "-DOT-";
} else if (segment.equals("..")) {
return "-DOTDOT-";
}
return segment;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ void stringToPathSegment_fixes() {
assertEquals("bad-COLON-id", badFixedId);
}

@Test
void stringToPathSegment_dotSegments() {
// "." and ".." contain no illegal character, but carry path meaning when used as a path segment
assertEquals("-DOTDOT-", PathUtils.stringToPathSegment(".."));
assertEquals("-DOT-", PathUtils.stringToPathSegment("."));
// dotted names and longer dot runs are inert as single path segments and stay untouched
assertEquals("...", PathUtils.stringToPathSegment("..."));
assertEquals("my.repo", PathUtils.stringToPathSegment("my.repo"));
assertEquals("repo..id", PathUtils.stringToPathSegment("repo..id"));
assertEquals("..id", PathUtils.stringToPathSegment("..id"));
}

@Test
void stringToPathSegment_allCharsBad() {
String veryBad = "\\/:\"<>|?*";
Expand Down
Loading