Skip to content

Commit c5bbb38

Browse files
committed
Fix namespace handling in Maven and Metadata readers
Accept POM 4.1.0 and METADATA 1.1.0 plus no-namespace, reject others Add JUnit tests for valid and invalid namespaces
1 parent 1fc1972 commit c5bbb38

File tree

3 files changed

+257
-34
lines changed

3 files changed

+257
-34
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.model.v4;
20+
21+
import javax.xml.stream.XMLStreamException;
22+
23+
import java.io.StringReader;
24+
25+
import org.apache.maven.api.model.InputSource;
26+
import org.apache.maven.api.model.Model;
27+
import org.junit.jupiter.api.Test;
28+
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertNotNull;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
32+
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
34+
class MavenStaxReaderNamespaceTest {
35+
36+
private static final InputSource NO_INPUT_SOURCE = null;
37+
38+
private static final String POM_41 = ""
39+
+ "<project xmlns=\"http://maven.apache.org/POM/4.1.0\" "
40+
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
41+
+ " <modelVersion>4.1.0</modelVersion>"
42+
+ " <groupId>com.acme</groupId>"
43+
+ " <artifactId>demo</artifactId>"
44+
+ " <version>1.0</version>"
45+
+ "</project>";
46+
47+
private static final String POM_40 = ""
48+
+ "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" "
49+
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
50+
+ " <modelVersion>4.0.0</modelVersion>"
51+
+ " <groupId>com.acme</groupId>"
52+
+ " <artifactId>demo</artifactId>"
53+
+ " <version>1.0</version>"
54+
+ "</project>";
55+
56+
private static final String POM_NO_NS = ""
57+
+ "<project>"
58+
+ " <modelVersion>4.0.0</modelVersion>"
59+
+ " <groupId>com.acme</groupId>"
60+
+ " <artifactId>demo</artifactId>"
61+
+ " <version>1.0</version>"
62+
+ "</project>";
63+
64+
private static final String POM_BAD_NS = ""
65+
+ "<project xmlns=\"http://example.com/not/pom\">"
66+
+ " <modelVersion>4.1.0</modelVersion>"
67+
+ " <groupId>com.acme</groupId>"
68+
+ " <artifactId>demo</artifactId>"
69+
+ " <version>1.0</version>"
70+
+ "</project>";
71+
72+
@Test
73+
void acceptsPom41() throws Exception {
74+
MavenStaxReader reader = new MavenStaxReader();
75+
Model model = reader.read(new StringReader(POM_41), /*strict*/ true, NO_INPUT_SOURCE);
76+
assertNotNull(model);
77+
assertEquals("com.acme", model.getGroupId());
78+
assertEquals("demo", model.getArtifactId());
79+
assertEquals("1.0", model.getVersion());
80+
}
81+
82+
@Test
83+
void acceptsPom40() throws Exception {
84+
MavenStaxReader reader = new MavenStaxReader();
85+
Model model = reader.read(new StringReader(POM_40), true, NO_INPUT_SOURCE);
86+
assertNotNull(model);
87+
assertEquals("com.acme", model.getGroupId());
88+
assertEquals("demo", model.getArtifactId());
89+
assertEquals("1.0", model.getVersion());
90+
}
91+
92+
@Test
93+
void acceptsPomWithoutNamespace() throws Exception {
94+
MavenStaxReader reader = new MavenStaxReader();
95+
Model model = reader.read(new StringReader(POM_NO_NS), true, NO_INPUT_SOURCE);
96+
assertNotNull(model);
97+
assertEquals("com.acme", model.getGroupId());
98+
assertEquals("demo", model.getArtifactId());
99+
assertEquals("1.0", model.getVersion());
100+
}
101+
102+
@Test
103+
void rejectsUnexpectedPomNamespace() {
104+
MavenStaxReader reader = new MavenStaxReader();
105+
XMLStreamException ex = assertThrows(
106+
XMLStreamException.class, () -> reader.read(new StringReader(POM_BAD_NS), true, NO_INPUT_SOURCE));
107+
// sanity check: message mentions unrecognized namespace
108+
assertTrue(ex.getMessage().toLowerCase().contains("unrecognized pom namespace"));
109+
}
110+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.model.v4;
20+
21+
import javax.xml.stream.XMLStreamException;
22+
23+
import java.io.StringReader;
24+
25+
import org.apache.maven.api.metadata.Metadata;
26+
import org.apache.maven.metadata.v4.MetadataStaxReader;
27+
import org.junit.jupiter.api.Test;
28+
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertNotNull;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
32+
import static org.junit.jupiter.api.Assertions.assertTrue;
33+
34+
class MetadataStaxReaderNamespaceTest {
35+
36+
private static final String META_110 = ""
37+
+ "<metadata xmlns=\"http://maven.apache.org/METADATA/1.1.0\">"
38+
+ " <groupId>com.acme</groupId>"
39+
+ " <artifactId>demo</artifactId>"
40+
+ " <versioning>"
41+
+ " <latest>1.0</latest>"
42+
+ " <release>1.0</release>"
43+
+ " </versioning>"
44+
+ "</metadata>";
45+
46+
private static final String META_NO_NS = ""
47+
+ "<metadata>"
48+
+ " <groupId>com.acme</groupId>"
49+
+ " <artifactId>demo</artifactId>"
50+
+ " <versioning>"
51+
+ " <latest>1.0</latest>"
52+
+ " </versioning>"
53+
+ "</metadata>";
54+
55+
private static final String META_BAD_NS = ""
56+
+ "<metadata xmlns=\"http://example.com/not/metadata\">"
57+
+ " <groupId>com.acme</groupId>"
58+
+ " <artifactId>demo</artifactId>"
59+
+ "</metadata>";
60+
61+
@Test
62+
void acceptsMetadata110() throws Exception {
63+
MetadataStaxReader reader = new MetadataStaxReader();
64+
Metadata md = reader.read(new StringReader(META_110), /*strict*/ true);
65+
assertNotNull(md);
66+
assertEquals("com.acme", md.getGroupId());
67+
assertEquals("demo", md.getArtifactId());
68+
assertNotNull(md.getVersioning());
69+
assertEquals("1.0", md.getVersioning().getLatest());
70+
}
71+
72+
@Test
73+
void acceptsMetadataWithoutNamespace() throws Exception {
74+
MetadataStaxReader reader = new MetadataStaxReader();
75+
Metadata md = reader.read(new StringReader(META_NO_NS), true);
76+
assertNotNull(md);
77+
assertEquals("com.acme", md.getGroupId());
78+
assertEquals("demo", md.getArtifactId());
79+
}
80+
81+
@Test
82+
void rejectsUnexpectedMetadataNamespace() {
83+
MetadataStaxReader reader = new MetadataStaxReader();
84+
XMLStreamException ex =
85+
assertThrows(XMLStreamException.class, () -> reader.read(new StringReader(META_BAD_NS), true));
86+
assertTrue(ex.getMessage().toLowerCase().contains("unrecognized metadata namespace"));
87+
}
88+
}

src/mdo/reader-stax.vm

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -246,51 +246,76 @@ public class ${className} {
246246
#end
247247
} //-- ${root.name} read(InputStream, boolean)
248248

249-
/**
250-
* Method read.
251-
*
252-
* @param parser a parser object.
253-
* @param strict a strict object.
254-
* @throws XMLStreamException XMLStreamException if
255-
* any.
256-
* @return ${root.name}
257-
*/
249+
/**
250+
* Method read.
251+
*
252+
* @param parser a parser object.
253+
* @param strict a strict object.
254+
* @throws XMLStreamException XMLStreamException if any.
255+
* @return ${root.name}
256+
*/
258257
#if ( $locationTracking )
259-
public ${root.name} read(XMLStreamReader parser, boolean strict, InputSource inputSrc) throws XMLStreamException {
258+
public ${root.name} read(XMLStreamReader parser, boolean strict, InputSource inputSrc) throws XMLStreamException {
260259
#else
261-
public ${root.name} read(XMLStreamReader parser, boolean strict) throws XMLStreamException {
260+
public ${root.name} read(XMLStreamReader parser, boolean strict) throws XMLStreamException {
262261
#end
263262
#if ( $needXmlContext )
264-
Deque<Object> context = new ArrayDeque<>();
263+
Deque<Object> context = new ArrayDeque<>();
265264
#end
266-
$rootUcapName $rootLcapName = null;
267-
int eventType = parser.getEventType();
268-
boolean parsed = false;
269-
while (eventType != XMLStreamReader.END_DOCUMENT) {
270-
if (eventType == XMLStreamReader.START_ELEMENT) {
271-
if (strict && ! "${rootTag}".equals(parser.getLocalName())) {
272-
throw new XMLStreamException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser.getLocation(), null);
273-
} else if (parsed) {
274-
// fallback, already expected a XMLStreamException due to invalid XML
275-
throw new XMLStreamException("Duplicated tag: '${rootTag}'", parser.getLocation(), null);
276-
}
265+
$rootUcapName $rootLcapName = null;
266+
int eventType = parser.getEventType();
267+
boolean parsed = false;
268+
while (eventType != XMLStreamReader.END_DOCUMENT) {
269+
if (eventType == XMLStreamReader.START_ELEMENT) {
270+
if (strict && ! "${rootTag}".equals(parser.getLocalName())) {
271+
throw new XMLStreamException("Expected root element '${rootTag}' but found '" + parser.getName() + "'", parser.getLocation(), null);
272+
} else if (parsed) {
273+
throw new XMLStreamException("Duplicated tag: '${rootTag}'", parser.getLocation(), null);
274+
}
275+
276+
// Enforce root namespace per model (strict mode).
277+
String rootNs = parser.getNamespaceURI();
278+
boolean hasNs = rootNs != null && !rootNs.isEmpty();
279+
if (strict && hasNs) {
280+
if ("project".equals("${rootTag}")) {
281+
// Accept any official POM namespace version (e.g., 4.0.0, 4.1.0)
282+
if (!rootNs.startsWith("http://maven.apache.org/POM/")) {
283+
throw new XMLStreamException(
284+
"Unrecognized POM namespace '" + rootNs
285+
+ "'. Expected something like 'http://maven.apache.org/POM/4.x.y' or no namespace.",
286+
parser.getLocation(), null);
287+
}
288+
} else if ("metadata".equals("${rootTag}")) {
289+
// Accept official METADATA namespaces (e.g., 1.1.0)
290+
if (!rootNs.startsWith("http://maven.apache.org/METADATA/")) {
291+
throw new XMLStreamException(
292+
"Unrecognized METADATA namespace '" + rootNs
293+
+ "'. Expected something like 'http://maven.apache.org/METADATA/1.x.y' or no namespace.",
294+
parser.getLocation(), null);
295+
}
296+
} else {
297+
// Other models: do not enforce at root level.
298+
}
299+
}
300+
277301
#if ( $locationTracking )
278-
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), inputSrc);
302+
$rootLcapName = parse${rootUcapName}(parser, strict, rootNs, inputSrc);
279303
#elseif ( $needXmlContext )
280-
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI(), context);
304+
$rootLcapName = parse${rootUcapName}(parser, strict, rootNs, context);
281305
#else
282-
$rootLcapName = parse${rootUcapName}(parser, strict, parser.getNamespaceURI());
306+
$rootLcapName = parse${rootUcapName}(parser, strict, rootNs);
283307
#end
284-
parsed = true;
285-
}
286-
eventType = parser.next();
287-
}
288-
if (parsed) {
289-
return $rootLcapName;
290-
}
291-
throw new XMLStreamException("Expected root element '${rootTag}' but found no element at all: invalid XML document", parser.getLocation(), null);
308+
parsed = true;
309+
}
310+
eventType = parser.next();
311+
}
312+
if (parsed) {
313+
return $rootLcapName;
314+
}
315+
throw new XMLStreamException("Expected root element '${rootTag}' but found no element at all: invalid XML document", parser.getLocation(), null);
292316
} //-- ${root.name} read(XMLStreamReader, boolean)
293317

318+
294319
#foreach ( $class in $model.allClasses )
295320
#if ( $class.name != "InputSource" && $class.name != "InputLocation" )
296321
#set ( $classUcapName = $Helper.capitalise( $class.name ) )

0 commit comments

Comments
 (0)