mirror of https://github.com/apache/ant-ivy
CVE-2022-37866 prevent path-traversal with bogus module coordinates
This commit is contained in:
parent
03b6b8c3ae
commit
3f374602d4
|
|
@ -22,6 +22,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
|
@ -135,7 +136,7 @@ public final class IvyPatternHelper {
|
|||
if (token.indexOf(':') > 0) {
|
||||
token = token.substring(token.indexOf(':') + 1);
|
||||
}
|
||||
tokens.put(token, entry.getValue());
|
||||
tokens.put(token, new Validated(token, entry.getValue()));
|
||||
}
|
||||
}
|
||||
if (extraArtifactAttributes != null) {
|
||||
|
|
@ -144,19 +145,19 @@ public final class IvyPatternHelper {
|
|||
if (token.indexOf(':') > 0) {
|
||||
token = token.substring(token.indexOf(':') + 1);
|
||||
}
|
||||
tokens.put(token, entry.getValue());
|
||||
tokens.put(token, new Validated(token, entry.getValue()));
|
||||
}
|
||||
}
|
||||
tokens.put(ORGANISATION_KEY, org == null ? "" : org);
|
||||
tokens.put(ORGANISATION_KEY2, org == null ? "" : org);
|
||||
tokens.put(ORGANISATION_KEY, org == null ? "" : new Validated(ORGANISATION_KEY, org));
|
||||
tokens.put(ORGANISATION_KEY2, org == null ? "" : new Validated(ORGANISATION_KEY2, org));
|
||||
tokens.put(ORGANISATION_PATH_KEY, org == null ? "" : org.replace('.', '/'));
|
||||
tokens.put(MODULE_KEY, module == null ? "" : module);
|
||||
tokens.put(BRANCH_KEY, branch == null ? "" : branch);
|
||||
tokens.put(REVISION_KEY, revision == null ? "" : revision);
|
||||
tokens.put(ARTIFACT_KEY, artifact == null ? module : artifact);
|
||||
tokens.put(TYPE_KEY, type == null ? "jar" : type);
|
||||
tokens.put(EXT_KEY, ext == null ? "jar" : ext);
|
||||
tokens.put(CONF_KEY, conf == null ? "default" : conf);
|
||||
tokens.put(MODULE_KEY, module == null ? "" : new Validated(MODULE_KEY, module));
|
||||
tokens.put(BRANCH_KEY, branch == null ? "" : new Validated(BRANCH_KEY, branch));
|
||||
tokens.put(REVISION_KEY, revision == null ? "" : new Validated(REVISION_KEY, revision));
|
||||
tokens.put(ARTIFACT_KEY, new Validated(ARTIFACT_KEY, artifact == null ? module : artifact));
|
||||
tokens.put(TYPE_KEY, type == null ? "jar" : new Validated(TYPE_KEY, type));
|
||||
tokens.put(EXT_KEY, ext == null ? "jar" : new Validated(EXT_KEY, ext));
|
||||
tokens.put(CONF_KEY, conf == null ? "default" : new Validated(CONF_KEY, conf));
|
||||
if (origin == null) {
|
||||
tokens.put(ORIGINAL_ARTIFACTNAME_KEY, new OriginalArtifactNameValue(org, module,
|
||||
branch, revision, artifact, type, ext, extraModuleAttributes,
|
||||
|
|
@ -328,7 +329,9 @@ public final class IvyPatternHelper {
|
|||
+ pattern);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
String afterTokenSubstitution = buffer.toString();
|
||||
checkAgainstPathTraversal(pattern, afterTokenSubstitution);
|
||||
return afterTokenSubstitution;
|
||||
}
|
||||
|
||||
public static String substituteVariable(String pattern, String variable, String value) {
|
||||
|
|
@ -518,4 +521,49 @@ public final class IvyPatternHelper {
|
|||
}
|
||||
return pattern.substring(startIndex + 1, endIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class returns a captured value after validating it doesn't
|
||||
* contain any path traversal sequence.
|
||||
*
|
||||
* <p>{@code toString}</p> will be invoked when the value is
|
||||
* actually used as a token inside of a pattern passed to {@link
|
||||
* #substituteTokens}.</p>
|
||||
*/
|
||||
private static class Validated {
|
||||
private final String tokenName, tokenValue;
|
||||
|
||||
private Validated(String tokenName, String tokenValue) {
|
||||
this.tokenName = tokenName;
|
||||
this.tokenValue = tokenValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (tokenValue != null && !tokenValue.isEmpty()) {
|
||||
StringTokenizer tok = new StringTokenizer(tokenValue.replace("\\", "/"), "/");
|
||||
while (tok.hasMoreTokens()) {
|
||||
if ("..".equals(tok.nextToken())) {
|
||||
throw new IllegalArgumentException("\'" + tokenName + "\' value " + tokenValue + " contains an illegal path sequence");
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokenValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkAgainstPathTraversal(String pattern, String afterTokenSubstitution) {
|
||||
String root = getTokenRoot(pattern);
|
||||
int rootLen = root.length(); // it is OK to have a token root containing .. sequences
|
||||
if (root.endsWith("/") || root.endsWith("\\")) {
|
||||
--rootLen;
|
||||
}
|
||||
String patternedPartWithNormalizedSlashes =
|
||||
afterTokenSubstitution.substring(rootLen).replace("\\", "/");
|
||||
if (patternedPartWithNormalizedSlashes.endsWith("/..")
|
||||
|| patternedPartWithNormalizedSlashes.indexOf("/../") >= 0) {
|
||||
throw new IllegalArgumentException("path after token expansion contains an illegal sequence");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -683,8 +683,10 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv
|
|||
}
|
||||
|
||||
private PropertiesFile getCachedDataFile(ModuleRevisionId mRevId) {
|
||||
return new PropertiesFile(new File(getRepositoryCacheRoot(), IvyPatternHelper.substitute(
|
||||
getDataFilePattern(), mRevId)), "ivy cached data file for " + mRevId);
|
||||
File file = new File(getRepositoryCacheRoot(), IvyPatternHelper.substitute(
|
||||
getDataFilePattern(), mRevId));
|
||||
assertInsideCache(file);
|
||||
return new PropertiesFile(file, "ivy cached data file for " + mRevId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -693,9 +695,10 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv
|
|||
*/
|
||||
private PropertiesFile getCachedDataFile(String resolverName, ModuleRevisionId mRevId) {
|
||||
// we append ".${resolverName} onto the end of the regular ivydata location
|
||||
return new PropertiesFile(new File(getRepositoryCacheRoot(),
|
||||
IvyPatternHelper.substitute(getDataFilePattern(), mRevId) + "." + resolverName),
|
||||
"ivy cached data file for " + mRevId);
|
||||
File file = new File(getRepositoryCacheRoot(),
|
||||
IvyPatternHelper.substitute(getDataFilePattern(), mRevId) + "." + resolverName);
|
||||
assertInsideCache(file);
|
||||
return new PropertiesFile(file, "ivy cached data file for " + mRevId);
|
||||
}
|
||||
|
||||
public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd,
|
||||
|
|
@ -1029,6 +1032,7 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv
|
|||
+ resourceResolver
|
||||
+ "': pointing repository to ivy cache is forbidden !");
|
||||
}
|
||||
assertInsideCache(archiveFile);
|
||||
if (listener != null) {
|
||||
listener.startArtifactDownload(this, artifactRef, artifact, origin);
|
||||
}
|
||||
|
|
@ -1147,6 +1151,7 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv
|
|||
}
|
||||
|
||||
// actual download
|
||||
assertInsideCache(archiveFile);
|
||||
if (archiveFile.exists()) {
|
||||
archiveFile.delete();
|
||||
}
|
||||
|
|
@ -1534,6 +1539,16 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv
|
|||
Message.debug("\t\tchangingMatcher: " + getChangingMatcherName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the given path points outside of the cache.
|
||||
*/
|
||||
public final void assertInsideCache(File fileInCache) {
|
||||
File root = getRepositoryCacheRoot();
|
||||
if (root != null && !FileUtil.isLeadingPath(root, fileInCache)) {
|
||||
throw new IllegalArgumentException(fileInCache + " is outside of the cache");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the {@link ArtifactOrigin#getLocation() location of the artifact origin} is a
|
||||
* {@code file:} scheme URI, then this method parses that URI and returns back the
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ public class DefaultResolutionCacheManager implements ResolutionCacheManager, Iv
|
|||
IOException {
|
||||
ModuleRevisionId mrevId = md.getResolvedModuleRevisionId();
|
||||
File ivyFileInCache = getResolvedIvyFileInCache(mrevId);
|
||||
assertInsideCache(ivyFileInCache);
|
||||
md.toIvyFile(ivyFileInCache);
|
||||
|
||||
Properties paths = new Properties();
|
||||
|
|
@ -188,12 +189,22 @@ public class DefaultResolutionCacheManager implements ResolutionCacheManager, Iv
|
|||
if (!paths.isEmpty()) {
|
||||
File parentsFile = getResolvedIvyPropertiesInCache(ModuleRevisionId.newInstance(mrevId,
|
||||
mrevId.getRevision() + "-parents"));
|
||||
assertInsideCache(parentsFile);
|
||||
FileOutputStream out = new FileOutputStream(parentsFile);
|
||||
paths.store(out, null);
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the given path points outside of the cache.
|
||||
*/
|
||||
public final void assertInsideCache(File fileInCache) {
|
||||
if (!FileUtil.isLeadingPath(getResolutionCacheRoot(), fileInCache)) {
|
||||
throw new IllegalArgumentException(fileInCache + " is outside of the cache");
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLocalParents(ModuleRevisionId baseMrevId, ModuleDescriptor md, File mdFile,
|
||||
Properties paths) throws ParseException, IOException {
|
||||
for (ExtendsDescriptor parent : md.getInheritedDescriptors()) {
|
||||
|
|
@ -206,6 +217,7 @@ public class DefaultResolutionCacheManager implements ResolutionCacheManager, Iv
|
|||
ModuleRevisionId pRevId = ModuleRevisionId.newInstance(baseMrevId,
|
||||
baseMrevId.getRevision() + "-parent." + paths.size());
|
||||
File parentFile = getResolvedIvyFileInCache(pRevId);
|
||||
assertInsideCache(parentFile);
|
||||
parentMd.toIvyFile(parentFile);
|
||||
|
||||
paths.setProperty(mdFile.getName() + "|" + parent.getLocation(),
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.apache.ivy.Ivy;
|
|||
import org.apache.ivy.core.IvyContext;
|
||||
import org.apache.ivy.core.LogOptions;
|
||||
import org.apache.ivy.core.cache.ArtifactOrigin;
|
||||
import org.apache.ivy.core.cache.DefaultResolutionCacheManager;
|
||||
import org.apache.ivy.core.cache.ResolutionCacheManager;
|
||||
import org.apache.ivy.core.event.EventManager;
|
||||
import org.apache.ivy.core.event.download.PrepareDownloadEvent;
|
||||
|
|
@ -262,6 +263,9 @@ public class ResolveEngine {
|
|||
// this is used by the deliver task to resolve dynamic revisions to static ones
|
||||
File ivyPropertiesInCache = cacheManager.getResolvedIvyPropertiesInCache(md
|
||||
.getResolvedModuleRevisionId());
|
||||
if (cacheManager instanceof DefaultResolutionCacheManager) {
|
||||
((DefaultResolutionCacheManager) cacheManager).assertInsideCache(ivyPropertiesInCache);
|
||||
}
|
||||
Properties props = new Properties();
|
||||
if (dependencies.length > 0) {
|
||||
Map<ModuleId, ModuleRevisionId> forcedRevisions = new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -290,6 +290,11 @@ public class RetrieveEngine {
|
|||
String destIvyPattern = IvyPatternHelper.substituteVariables(options.getDestIvyPattern(),
|
||||
settings.getVariables());
|
||||
|
||||
File fileRetrieveRoot = settings.resolveFile(IvyPatternHelper
|
||||
.getTokenRoot(destFilePattern));
|
||||
File ivyRetrieveRoot = destIvyPattern == null ? null : settings
|
||||
.resolveFile(IvyPatternHelper.getTokenRoot(destIvyPattern));
|
||||
|
||||
// find what we must retrieve where
|
||||
|
||||
// ArtifactDownloadReport source -> Set (String copyDestAbsolutePath)
|
||||
|
|
@ -340,6 +345,7 @@ public class RetrieveEngine {
|
|||
}
|
||||
|
||||
String destPattern = "ivy".equals(adr.getType()) ? destIvyPattern : destFilePattern;
|
||||
File root = "ivy".equals(adr.getType()) ? ivyRetrieveRoot : fileRetrieveRoot;
|
||||
|
||||
if (!"ivy".equals(adr.getType())
|
||||
&& !options.getArtifactFilter().accept(adr.getArtifact())) {
|
||||
|
|
@ -357,7 +363,14 @@ public class RetrieveEngine {
|
|||
dest = new HashSet<>();
|
||||
artifactsToCopy.put(adr, dest);
|
||||
}
|
||||
String copyDest = settings.resolveFile(destFileName).getAbsolutePath();
|
||||
File copyDestFile = settings.resolveFile(destFileName).getAbsoluteFile();
|
||||
if (root != null &&
|
||||
!FileUtil.isLeadingPath(root, copyDestFile)) {
|
||||
Message.warn("not retrieving artifact " + artifact + " as its destination "
|
||||
+ copyDestFile + " is not inside " + root);
|
||||
continue;
|
||||
}
|
||||
String copyDest = copyDestFile.getPath();
|
||||
|
||||
String[] destinations = new String[] {copyDest};
|
||||
if (options.getMapper() != null) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.io.FileOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.ivy.core.cache.DefaultResolutionCacheManager;
|
||||
import org.apache.ivy.core.cache.ResolutionCacheManager;
|
||||
import org.apache.ivy.core.report.ConfigurationResolveReport;
|
||||
import org.apache.ivy.core.report.ResolveReport;
|
||||
|
|
@ -52,6 +53,9 @@ public class XmlReportOutputter implements ReportOutputter {
|
|||
ResolutionCacheManager cacheMgr) throws IOException {
|
||||
File reportFile = cacheMgr.getConfigurationResolveReportInCache(resolveId,
|
||||
report.getConfiguration());
|
||||
if (cacheMgr instanceof DefaultResolutionCacheManager) {
|
||||
((DefaultResolutionCacheManager) cacheMgr).assertInsideCache(reportFile);
|
||||
}
|
||||
File reportParentDir = reportFile.getParentFile();
|
||||
reportParentDir.mkdirs();
|
||||
OutputStream stream = new FileOutputStream(reportFile);
|
||||
|
|
|
|||
|
|
@ -49,13 +49,15 @@ public class FileRepository extends AbstractRepository {
|
|||
}
|
||||
|
||||
public void get(String source, File destination) throws IOException {
|
||||
File s = getFile(source);
|
||||
fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
|
||||
copy(getFile(source), destination, true);
|
||||
copy(s, destination, true);
|
||||
}
|
||||
|
||||
public void put(File source, String destination, boolean overwrite) throws IOException {
|
||||
File d = getFile(destination);
|
||||
fireTransferInitiated(getResource(destination), TransferEvent.REQUEST_PUT);
|
||||
copy(source, getFile(destination), overwrite);
|
||||
copy(source, d, overwrite);
|
||||
}
|
||||
|
||||
public void move(File src, File dest) throws IOException {
|
||||
|
|
@ -112,7 +114,11 @@ public class FileRepository extends AbstractRepository {
|
|||
if (baseDir == null) {
|
||||
return Checks.checkAbsolute(source, "source");
|
||||
}
|
||||
return FileUtil.resolveFile(baseDir, source);
|
||||
File file = FileUtil.resolveFile(baseDir, source);
|
||||
if (!FileUtil.isLeadingPath(baseDir, file)) {
|
||||
throw new IllegalArgumentException(source + " outside of repository root");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ package org.apache.ivy.core.cache;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.ivy.Ivy;
|
||||
|
|
@ -35,14 +37,19 @@ import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
|
|||
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
|
||||
import org.apache.ivy.core.module.id.ModuleId;
|
||||
import org.apache.ivy.core.module.id.ModuleRevisionId;
|
||||
import org.apache.ivy.core.report.ArtifactDownloadReport;
|
||||
import org.apache.ivy.core.report.DownloadStatus;
|
||||
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
|
||||
import org.apache.ivy.core.settings.IvySettings;
|
||||
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
|
||||
import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
|
||||
import org.apache.ivy.plugins.repository.BasicResource;
|
||||
import org.apache.ivy.plugins.repository.Resource;
|
||||
import org.apache.ivy.plugins.repository.ResourceDownloader;
|
||||
import org.apache.ivy.plugins.repository.url.URLResource;
|
||||
import org.apache.ivy.plugins.resolver.MockResolver;
|
||||
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
|
||||
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
|
||||
import org.apache.ivy.util.DefaultMessageLogger;
|
||||
import org.apache.ivy.util.Message;
|
||||
import org.apache.tools.ant.Project;
|
||||
|
|
@ -138,6 +145,57 @@ public class DefaultRepositoryCacheManagerTest {
|
|||
assertTrue(ArtifactOrigin.isUnknown(found));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wontWritePropertiesOutsideOfCache() {
|
||||
cacheManager.setDataFilePattern("a/../../../../../../");
|
||||
try {
|
||||
cacheManager.saveArtifactOrigin(artifact, origin);
|
||||
fail("expected an exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
ModuleId mi = new ModuleId("org", "module");
|
||||
ModuleRevisionId mridLatest = new ModuleRevisionId(mi, "trunk", "latest.integration");
|
||||
try {
|
||||
cacheManager.saveResolvedRevision("resolver1", mridLatest, "1.1");
|
||||
fail("expected an exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wontDownloadOutsideOfCache() throws Exception {
|
||||
DefaultRepositoryCacheManager mgr = new DefaultRepositoryCacheManager() {
|
||||
{
|
||||
setUseOrigin(false);
|
||||
setSettings(ivy.getSettings());
|
||||
setBasedir(cacheManager.getBasedir());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArchivePathInCache(Artifact artifact, ArtifactOrigin origin) {
|
||||
return "../foo.txt";
|
||||
}
|
||||
};
|
||||
|
||||
ArtifactResourceResolver resolver = new ArtifactResourceResolver() {
|
||||
@Override
|
||||
public ResolvedResource resolve(Artifact artifact) {
|
||||
try {
|
||||
return new ResolvedResource(new URLResource(new URL("https://ant.apache.org/")), "latest");
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ArtifactDownloadReport report = mgr.download(artifact, resolver, null, new CacheDownloadOptions());
|
||||
assertEquals(DownloadStatus.FAILED, report.getDownloadStatus());
|
||||
assertTrue(report.getDownloadDetails().contains("is outside"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testLatestIntegrationIsCachedPerResolver() throws Exception {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.apache.ivy.core.cache;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
|
||||
import org.apache.ivy.core.module.id.ModuleRevisionId;
|
||||
import org.apache.ivy.util.FileUtil;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DefaultResolutionCacheManagerTest {
|
||||
|
||||
private File cacheDir;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
cacheDir = new File("build/cache");
|
||||
cacheDir.mkdirs();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (cacheDir != null && cacheDir.exists()) {
|
||||
FileUtil.forceDelete(cacheDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wontWriteIvyFileOutsideOfCache() throws Exception {
|
||||
DefaultResolutionCacheManager cm = new DefaultResolutionCacheManager(cacheDir) {
|
||||
@Override
|
||||
public File getResolvedIvyFileInCache(ModuleRevisionId mrid) {
|
||||
return new File(getResolutionCacheRoot(), "../test.ivy.xml");
|
||||
}
|
||||
};
|
||||
ModuleRevisionId mrid = ModuleRevisionId.newInstance("org", "name", "rev");
|
||||
try {
|
||||
cm.saveResolvedModuleDescriptor(DefaultModuleDescriptor.newDefaultInstance(mrid));
|
||||
fail("expected exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import java.util.Date;
|
|||
|
||||
import org.apache.ivy.Ivy;
|
||||
import org.apache.ivy.core.cache.ArtifactOrigin;
|
||||
import org.apache.ivy.core.cache.DefaultResolutionCacheManager;
|
||||
import org.apache.ivy.core.module.descriptor.Artifact;
|
||||
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
|
||||
import org.apache.ivy.core.module.id.ModuleRevisionId;
|
||||
|
|
@ -39,6 +40,7 @@ import org.junit.Test;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ResolveEngineTest {
|
||||
|
||||
|
|
@ -89,6 +91,39 @@ public class ResolveEngineTest {
|
|||
"jar", "jar"), new File("test/repositories/1/org1/mod1.1/jars/mod1.1-1.0.jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wontWriteResolvedDependenciesOutsideOfCache() throws Exception {
|
||||
DefaultResolutionCacheManager orig = (DefaultResolutionCacheManager) ivy.getSettings()
|
||||
.getResolutionCacheManager();
|
||||
|
||||
DefaultResolutionCacheManager fake = new DefaultResolutionCacheManager() {
|
||||
{
|
||||
setBasedir(orig.getBasedir());
|
||||
setSettings(ivy.getSettings());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getResolvedIvyPropertiesInCache(ModuleRevisionId mrid) {
|
||||
return new File(getBasedir(), "../foo.properties");
|
||||
}
|
||||
};
|
||||
|
||||
ivy.getSettings().setResolutionCacheManager(fake);
|
||||
ResolveEngine engine = new ResolveEngine(ivy.getSettings(), ivy.getEventManager(),
|
||||
ivy.getSortEngine());
|
||||
|
||||
ResolveOptions options = new ResolveOptions();
|
||||
options.setConfs(new String[] {"*"});
|
||||
|
||||
ModuleRevisionId mRevId = ModuleRevisionId.parse("org1#mod1.1;1.0");
|
||||
try {
|
||||
engine.resolve(mRevId, options, true);
|
||||
fail("expected an exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that setting the dictator resolver on the resolve engine doesn't change the
|
||||
* dependency resolver set in the Ivy settings. See IVY-1618 for details.
|
||||
|
|
|
|||
|
|
@ -20,12 +20,15 @@ package org.apache.ivy.core.retrieve;
|
|||
import org.apache.ivy.Ivy;
|
||||
import org.apache.ivy.TestHelper;
|
||||
import org.apache.ivy.core.IvyPatternHelper;
|
||||
import org.apache.ivy.core.cache.ResolutionCacheManager;
|
||||
import org.apache.ivy.core.event.IvyEvent;
|
||||
import org.apache.ivy.core.event.IvyListener;
|
||||
import org.apache.ivy.core.event.retrieve.EndRetrieveArtifactEvent;
|
||||
import org.apache.ivy.core.event.retrieve.EndRetrieveEvent;
|
||||
import org.apache.ivy.core.event.retrieve.StartRetrieveArtifactEvent;
|
||||
import org.apache.ivy.core.event.retrieve.StartRetrieveEvent;
|
||||
import org.apache.ivy.core.module.descriptor.Configuration;
|
||||
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
|
||||
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
|
||||
import org.apache.ivy.core.module.id.ModuleId;
|
||||
import org.apache.ivy.core.module.id.ModuleRevisionId;
|
||||
|
|
@ -36,8 +39,11 @@ import org.apache.ivy.util.DefaultMessageLogger;
|
|||
import org.apache.ivy.util.Message;
|
||||
import org.apache.ivy.util.MockMessageLogger;
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.taskdefs.Copy;
|
||||
import org.apache.tools.ant.taskdefs.Delete;
|
||||
import org.apache.tools.ant.taskdefs.condition.JavaVersion;
|
||||
import org.apache.tools.ant.types.FilterChain;
|
||||
import org.apache.tools.ant.filters.TokenFilter.ReplaceString;
|
||||
import org.junit.After;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
|
|
@ -51,6 +57,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
|
@ -61,6 +68,7 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class RetrieveTest {
|
||||
|
||||
|
|
@ -135,6 +143,47 @@ public class RetrieveTest {
|
|||
"jar", "jar", "default")).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wontRetrieveOutsideOfDestRoot() throws Exception {
|
||||
ResolveReport report = ivy.resolve(new File(
|
||||
"test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
|
||||
getResolveOptions(new String[] {"*"}));
|
||||
assertNotNull(report);
|
||||
ModuleDescriptor md = report.getModuleDescriptor();
|
||||
assertNotNull(md);
|
||||
|
||||
Ivy testIvy = Ivy.newInstance();
|
||||
testIvy.configure(new File("test/repositories/ivysettings.xml"));
|
||||
testIvy.getSettings()
|
||||
.setResolutionCacheManager(new FindAllResolutionCacheManager(ivy.getResolutionCacheManager(), md));
|
||||
|
||||
Copy copy = new Copy();
|
||||
copy.setProject(new Project());
|
||||
copy.setFile(new File("build/cache/org1-mod1.1-default.xml"));
|
||||
copy.setTofile(new File("build/cache/fake-default.xml"));
|
||||
FilterChain fc = copy.createFilterChain();
|
||||
ReplaceString rsOrg = new ReplaceString();
|
||||
rsOrg.setFrom("organisation=\"org1\"");
|
||||
rsOrg.setTo("organisation=\"fake\"");
|
||||
fc.addReplaceString(rsOrg);
|
||||
copy.setOverwrite(true);
|
||||
copy.execute();
|
||||
|
||||
MockMessageLogger mockLogger = new MockMessageLogger();
|
||||
Message.setDefaultLogger(mockLogger);
|
||||
|
||||
ModuleRevisionId id = ModuleRevisionId.newInstance("fake", "a", "1.1");
|
||||
ModuleDescriptor fake = DefaultModuleDescriptor.newDefaultInstance(id);
|
||||
String pattern = "build/[organisation]/../../../[artifact]-[revision].[ext]";
|
||||
try {
|
||||
testIvy.retrieve(fake.getModuleRevisionId(),
|
||||
getRetrieveOptions().setDestArtifactPattern(pattern));
|
||||
fail("expected an exception");
|
||||
} catch (RuntimeException ex) {
|
||||
assertTrue(ex.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieveSameFileConflict() throws Exception {
|
||||
// mod1.1 depends on mod1.2
|
||||
|
|
@ -610,4 +659,58 @@ public class RetrieveTest {
|
|||
return new ResolveOptions().setConfs(confs);
|
||||
}
|
||||
|
||||
|
||||
private static class FindAllResolutionCacheManager implements ResolutionCacheManager {
|
||||
|
||||
private final ResolutionCacheManager real;
|
||||
private final ModuleRevisionId staticMrid;
|
||||
private final ModuleDescriptor staticModuleDescriptor;
|
||||
private static final String RESOLVE_ID = "org1-mod1.1";
|
||||
|
||||
private FindAllResolutionCacheManager(ResolutionCacheManager real, ModuleDescriptor md) {
|
||||
this.real = real;
|
||||
staticModuleDescriptor = md;
|
||||
staticMrid = md.getModuleRevisionId();
|
||||
}
|
||||
|
||||
public File getResolutionCacheRoot() {
|
||||
return real.getResolutionCacheRoot();
|
||||
}
|
||||
|
||||
public File getResolvedIvyFileInCache(ModuleRevisionId mrid) {
|
||||
return real.getResolvedIvyFileInCache(staticMrid);
|
||||
}
|
||||
|
||||
public File getResolvedIvyPropertiesInCache(ModuleRevisionId mrid) {
|
||||
return real.getResolvedIvyPropertiesInCache(staticMrid);
|
||||
}
|
||||
|
||||
public File getConfigurationResolveReportInCache(String resolveId, String conf) {
|
||||
return new File("build/cache/fake-default.xml");
|
||||
}
|
||||
|
||||
public File[] getConfigurationResolveReportsInCache(final String resolveId) {
|
||||
return real.getConfigurationResolveReportsInCache(RESOLVE_ID);
|
||||
}
|
||||
|
||||
public ModuleDescriptor getResolvedModuleDescriptor(ModuleRevisionId mrid)
|
||||
throws ParseException, IOException {
|
||||
if (mrid.getOrganisation().equals("fake")) {
|
||||
DefaultModuleDescriptor md = new DefaultModuleDescriptor(staticModuleDescriptor.getParser(), staticModuleDescriptor.getResource());
|
||||
md.setModuleRevisionId(mrid);
|
||||
md.setPublicationDate(staticModuleDescriptor.getPublicationDate());
|
||||
for (Configuration conf : staticModuleDescriptor.getConfigurations()) {
|
||||
md.addConfiguration(conf);
|
||||
}
|
||||
return md;
|
||||
}
|
||||
return real.getResolvedModuleDescriptor(mrid);
|
||||
}
|
||||
|
||||
public void saveResolvedModuleDescriptor(ModuleDescriptor md) {
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.apache.ivy.plugins.repository.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.ivy.util.FileUtil;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class FileRepositoryTest {
|
||||
|
||||
private File repoDir;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
repoDir = new File("build/filerepo").getAbsoluteFile();
|
||||
repoDir.mkdirs();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (repoDir != null && repoDir.exists()) {
|
||||
FileUtil.forceDelete(repoDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putWrites() throws Exception {
|
||||
FileRepository fp = new FileRepository(repoDir);
|
||||
fp.put(new File("build.xml"), "foo/bar/baz.xml", true);
|
||||
assertTrue(new File(repoDir + "/foo/bar/baz.xml").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putWontWriteOutsideBasedir() throws Exception {
|
||||
FileRepository fp = new FileRepository(repoDir);
|
||||
try {
|
||||
fp.put(new File("build.xml"), "../baz.xml", true);
|
||||
fail("should have thrown an exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReads() throws Exception {
|
||||
FileRepository fp = new FileRepository(repoDir);
|
||||
fp.put(new File("build.xml"), "foo/bar/baz.xml", true);
|
||||
fp.get("foo/bar/baz.xml", new File("build/filerepo/a.xml"));
|
||||
assertTrue(new File(repoDir + "/a.xml").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWontReadOutsideBasedir() throws Exception {
|
||||
FileRepository fp = new FileRepository(repoDir);
|
||||
try {
|
||||
fp.get("../../build.xml", new File("build/filerepo/a.xml"));
|
||||
fail("should have thrown an exception");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package org.apache.ivy.util;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -80,4 +81,94 @@ public class IvyPatternHelperTest {
|
|||
String pattern = "lib/([type]/)[artifact].[ext]";
|
||||
assertEquals("lib/", IvyPatternHelper.getTokenRoot(pattern));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInOrganisation() {
|
||||
String pattern = "[organisation]/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "../org", "module", "revision", "artifact", "type", "ext", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInOrganization() {
|
||||
String pattern = "[organization]/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "../org", "module", "revision", "artifact", "type", "ext", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInModule() {
|
||||
String pattern = "[module]/build/archives (x86)/[type]s/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "org", "..\\module", "revision", "artifact", "type", "ext", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInRevision() {
|
||||
String pattern = "[type]s/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision/..", "artifact", "type", "ext", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInArtifact() {
|
||||
String pattern = "[type]s/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision", "artifact\\..", "type", "ext", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInType() {
|
||||
String pattern = "[type]s/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision", "artifact", "ty/../pe", "ext", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInExt() {
|
||||
String pattern = "[type]s/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision", "artifact", "type", "ex//..//t", "conf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInConf() {
|
||||
String pattern = "[conf]/[artifact]-[revision].[ext]";
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision", "artifact", "type", "ext", "co\\..\\nf");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInModuleAttributes() {
|
||||
String pattern = "[foo]/[artifact]-[revision].[ext]";
|
||||
Map<String, String> a = new HashMap<String, String>() {{
|
||||
put("foo", "..");
|
||||
}};
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision", "artifact", "type", "ext", "conf",
|
||||
a, Collections.emptyMap());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalInArtifactAttributes() {
|
||||
String pattern = "[foo]/[artifact]-[revision].[ext]";
|
||||
Map<String, String> a = new HashMap<String, String>() {{
|
||||
put("foo", "a/../b");
|
||||
}};
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision", "artifact", "type", "ext", "conf",
|
||||
Collections.emptyMap(), a);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void ignoresPathTraversalInCoordinatesNotUsedInPatern() {
|
||||
String pattern = "abc";
|
||||
Map<String, String> a = new HashMap<String, String>() {{
|
||||
put("foo", "a/../b");
|
||||
}};
|
||||
assertEquals("abc",
|
||||
IvyPatternHelper.substitute(pattern, "../org", "../module", "../revision", "../artifact", "../type", "../ext", "../conf",
|
||||
a, a)
|
||||
);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPathTraversalWithoutExplicitDoubleDot() {
|
||||
String pattern = "root/[conf]/[artifact]-[revision].[ext]";
|
||||
// forms revision/../ext after substitution
|
||||
IvyPatternHelper.substitute(pattern, "org", "module", "revision/", "artifact", "type", "./ext", "conf");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue