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 @@ -273,6 +273,8 @@ protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadE
ValidatingMetadataXpp3Reader mappingReader = new ValidatingMetadataXpp3Reader();

result = mappingReader.read(reader, false);

validateVersioning(result);
} catch (FileNotFoundException e) {
throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e);
} catch (IOException | XmlPullParserException e) {
Expand All @@ -282,6 +284,51 @@ protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadE
return result;
}

/**
* Version tokens adopted from repository metadata must be valid coordinate components; metadata carrying
* anything else is treated as invalid.
*/
private static void validateVersioning(Metadata metadata) throws RepositoryMetadataReadException {
if (metadata == null) {
return;
}
Versioning versioning = metadata.getVersioning();
if (versioning == null) {
return;
}
validateVersionToken(versioning.getLatest());
validateVersionToken(versioning.getRelease());
for (String version : versioning.getVersions()) {
validateVersionToken(version);
}
for (SnapshotVersion snapshotVersion : versioning.getSnapshotVersions()) {
validateVersionToken(snapshotVersion.getVersion());
}
Snapshot snapshot = versioning.getSnapshot();
if (snapshot != null) {
validateVersionToken(snapshot.getTimestamp());
}
}

private static void validateVersionToken(String value) throws RepositoryMetadataReadException {
if (value == null || value.isEmpty()) {
return;
}
boolean valid = !"..".equals(value);
if (valid) {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '/' || c == '\\' || c == ':' || Character.isISOControl(c)) {
valid = false;
break;
}
}
}
if (!valid) {
throw new RepositoryMetadataReadException("Metadata contains an invalid version token: '" + value + "'");
}
}

/**
* Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local
* metadata if necessary to allow proper merging/updating of metadata during deployment.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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
*
* http://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.maven.artifact.repository.metadata;

import java.io.File;
import java.net.URL;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Tests that {@link DefaultRepositoryMetadataManager} rejects repository metadata carrying version tokens that
* are not valid coordinate components, on the legacy read path used when metadata is loaded for merging.
*/
public class DefaultRepositoryMetadataManagerTest {

private final DefaultRepositoryMetadataManager manager = new DefaultRepositoryMetadataManager();

@Test
void testMetadataWithInvalidVersionTokenIsRejected() {
File metadataFile = testFile("metadata-invalid-token/maven-metadata.xml");

RepositoryMetadataReadException exception =
assertThrows(RepositoryMetadataReadException.class, () -> manager.readMetadata(metadataFile));

assertTrue(exception.getMessage().contains("invalid version token"), exception.getMessage());
}

@Test
void testMetadataWithInvalidSnapshotTimestampIsRejected() {
File metadataFile = testFile("metadata-invalid-timestamp/maven-metadata.xml");

RepositoryMetadataReadException exception =
assertThrows(RepositoryMetadataReadException.class, () -> manager.readMetadata(metadataFile));

assertTrue(exception.getMessage().contains("invalid version token"), exception.getMessage());
}

private static File testFile(String resource) {
URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
assertNotNull(url, "test resource not found: " + resource);
return new File(url.getFile());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
~ 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
~
~ http://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.
-->

<metadata xmlns="http://maven.apache.org/METADATA/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/METADATA/1.1.0 http://maven.apache.org/xsd/metadata-1.1.0.xsd"
modelVersion="1.1.0">
<groupId>org.apache.maven.its</groupId>
<artifactId>dep-invalid-timestamp</artifactId>
<version>1.0-SNAPSHOT</version><!-- metadata with an invalid snapshot timestamp token -->
<versioning>
<snapshot>
<timestamp>20120809.112920:1</timestamp>
<buildNumber>1</buildNumber>
</snapshot>
<lastUpdated>20120809112920</lastUpdated>
</versioning>
</metadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
~ 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
~
~ http://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.
-->

<metadata xmlns="http://maven.apache.org/METADATA/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/METADATA/1.1.0 http://maven.apache.org/xsd/metadata-1.1.0.xsd"
modelVersion="1.1.0">
<groupId>org.apache.maven.its</groupId>
<artifactId>dep-invalid-token</artifactId>
<version>1.0-SNAPSHOT</version><!-- metadata with an invalid release token -->
<versioning>
<release>1.0:2.0</release>
<lastUpdated>20120809112920</lastUpdated>
</versioning>
</metadata>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
import java.util.Objects;

import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.Plugin;
import org.apache.maven.artifact.repository.metadata.Snapshot;
import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.repository.internal.metadata.ValidatingMetadataXpp3Reader;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
Expand All @@ -54,7 +58,9 @@ public Metadata read(Reader input, Map<String, ?> options) throws IOException {
Objects.requireNonNull(input, "input cannot be null");

try (Reader in = input) {
return new ValidatingMetadataXpp3Reader().read(in, isStrict(options));
Metadata metadata = new ValidatingMetadataXpp3Reader().read(in, isStrict(options));
validateMetadata(metadata);
return metadata;
} catch (XmlPullParserException e) {
throw new MetadataParseException(e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
}
Expand All @@ -64,7 +70,9 @@ public Metadata read(InputStream input, Map<String, ?> options) throws IOExcepti
Objects.requireNonNull(input, "input cannot be null");

try (InputStream in = input) {
return new ValidatingMetadataXpp3Reader().read(in, isStrict(options));
Metadata metadata = new ValidatingMetadataXpp3Reader().read(in, isStrict(options));
validateMetadata(metadata);
return metadata;
} catch (XmlPullParserException e) {
throw new MetadataParseException(e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
}
Expand All @@ -74,4 +82,57 @@ private boolean isStrict(Map<String, ?> options) {
Object value = (options != null) ? options.get(IS_STRICT) : null;
return value == null || Boolean.parseBoolean(value.toString());
}

/**
* Coordinate-shaped tokens read from this metadata (versions, plugin artifactIds and prefixes) get carried
* forward by callers as if they were already-validated path and coordinate components. Reject anything that
* would not itself be a valid coordinate component here, before it leaves this reader.
*/
private static void validateMetadata(Metadata metadata) throws IOException {
if (metadata == null) {
return;
}

Versioning versioning = metadata.getVersioning();
if (versioning != null) {
validateToken("version", versioning.getRelease());
validateToken("version", versioning.getLatest());
for (String version : versioning.getVersions()) {
validateToken("version", version);
}
for (SnapshotVersion snapshotVersion : versioning.getSnapshotVersions()) {
validateToken("version", snapshotVersion.getVersion());
}
Snapshot snapshot = versioning.getSnapshot();
if (snapshot != null) {
validateToken("snapshot timestamp", snapshot.getTimestamp());
}
}

if (metadata.getPlugins() != null) {
for (Plugin plugin : metadata.getPlugins()) {
validateToken("plugin artifactId", plugin.getArtifactId());
validateToken("plugin prefix", plugin.getPrefix());
}
}
}

private static void validateToken(String field, String value) throws IOException {
if (value == null || value.isEmpty()) {
return;
}
boolean valid = !"..".equals(value);
if (valid) {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '/' || c == '\\' || c == ':' || Character.isISOControl(c)) {
valid = false;
break;
}
}
}
if (!valid) {
throw new IOException("Metadata contains an invalid " + field + ": '" + value + "'");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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
*
* http://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.maven.artifact.repository.metadata.io;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;

import org.apache.maven.artifact.repository.metadata.Metadata;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class DefaultMetadataReaderTest {

private final DefaultMetadataReader reader = new DefaultMetadataReader();

private File resource(String name) throws URISyntaxException {
return new File(getClass().getResource(name).toURI());
}

@Test
public void testWellFormedMetadataParsesUnchanged() throws Exception {
Metadata metadata = reader.read(resource("well-formed-metadata.xml"), Collections.emptyMap());

assertEquals("org.apache.maven.its", metadata.getGroupId());
assertEquals("sample", metadata.getArtifactId());
assertEquals("1.1", metadata.getVersioning().getRelease());
assertEquals("1.1", metadata.getVersioning().getLatest());
assertEquals("maven-sample-plugin", metadata.getPlugins().get(0).getArtifactId());
}

@Test
public void testVersionContainingColonIsRejected() throws Exception {
File input = resource("invalid-version-token.xml");

IOException e = assertThrows(IOException.class, () -> reader.read(input, Collections.emptyMap()));
assertTrue(e.getMessage().contains("1.0:evil"));
}

@Test
public void testPluginArtifactIdContainingSlashIsRejected() throws Exception {
File input = resource("invalid-plugin-artifactid.xml");

IOException e = assertThrows(IOException.class, () -> reader.read(input, Collections.emptyMap()));
assertTrue(e.getMessage().contains("maven/sample-plugin"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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
~
~ http://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.
-->
<metadata>
<groupId>org.apache.maven.its</groupId>
<artifactId>sample</artifactId>
<plugins>
<plugin>
<name>Sample Plugin</name>
<prefix>sample</prefix>
<!-- not a valid coordinate component -->
<artifactId>maven/sample-plugin</artifactId>
</plugin>
</plugins>
</metadata>
Loading
Loading