[MNG-6946] Build/consumer incorrectly transforms name of artifactId

This commit is contained in:
rfscholte 2020-06-26 12:58:50 +02:00
parent a945ca4ed3
commit 76427d2320
8 changed files with 224 additions and 76 deletions

View File

@ -21,14 +21,13 @@ package org.apache.maven.xml;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerFactory;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
/**
* Creates XML related factories with OWASP advices applied
@ -56,46 +55,9 @@ public final class Factories
return tf;
}
public static SAXParserFactory newSAXParserFactory()
{
SAXParserFactory spf = SAXParserFactory.newInstance();
try
{
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// Using the SAXParserFactory's setFeature
spf.setFeature( "http://xml.org/sax/features/external-general-entities", false );
// Xerces 2 only - http://xerces.apache.org/xerces-j/features.html#external-general-entities
spf.setFeature( "http://apache.org/xml/features/disallow-doctype-decl", true );
}
catch ( ParserConfigurationException e )
{
// Tried an unsupported feature.
}
catch ( SAXNotRecognizedException e )
{
// Tried an unknown feature.
}
catch ( SAXNotSupportedException e )
{
// Tried a feature known to the parser but unsupported.
}
return spf;
}
public static SAXParser newSAXParser() throws ParserConfigurationException, SAXException
{
SAXParser saxParser = newSAXParserFactory().newSAXParser();
return saxParser;
}
public static XMLReader newXMLReader() throws SAXException, ParserConfigurationException
{
XMLReader reader = newSAXParser().getXMLReader();
XMLReader reader = XMLReaderFactory.createXMLReader();
try
{

View File

@ -20,6 +20,8 @@ package org.apache.maven.xml.sax.filter;
*/
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import org.apache.maven.xml.sax.SAXEvent;
@ -44,7 +46,7 @@ abstract class AbstractEventXMLFilter extends AbstractSAXFilter
// characters BEFORE startElement must get state of startingElement
// this way removing based on state keeps correct formatting
private SAXEvent characters;
private List<SAXEvent> charactersSegments = new ArrayList<>();
private boolean lockCharacters = false;
@ -82,18 +84,20 @@ abstract class AbstractEventXMLFilter extends AbstractSAXFilter
if ( isParsing() )
{
final String eventState = getState();
final SAXEvent charactersEvent = characters;
if ( !lockCharacters && charactersEvent != null )
if ( !lockCharacters )
{
saxEvents.add( () ->
charactersSegments.stream().forEach( e ->
{
if ( acceptEvent( eventState ) )
saxEvents.add( () ->
{
charactersEvent.execute();
}
if ( acceptEvent( eventState ) )
{
e.execute();
}
} );
} );
characters = null;
charactersSegments.clear();
}
saxEvents.add( () ->
@ -126,18 +130,17 @@ abstract class AbstractEventXMLFilter extends AbstractSAXFilter
protected final void executeEvents() throws SAXException
{
final String eventState = getState();
final SAXEvent charactersEvent = characters;
if ( charactersEvent != null )
charactersSegments.stream().forEach( e ->
{
saxEvents.add( () ->
{
if ( acceptEvent( eventState ) )
{
charactersEvent.execute();
e.execute();
}
} );
characters = null;
}
} );
charactersSegments.clear();
// not with streams due to checked SAXException
while ( !saxEvents.isEmpty() )
@ -204,7 +207,7 @@ abstract class AbstractEventXMLFilter extends AbstractSAXFilter
}
else if ( isParsing() )
{
this.characters = getEventFactory().characters( ch, start, length );
this.charactersSegments.add( getEventFactory().characters( ch, start, length ) );
}
else
{

View File

@ -126,5 +126,19 @@ public class AbstractSAXFilter extends XMLFilterImpl implements LexicalHandler
lexicalHandler.comment( ch, start, length );
}
}
protected static String nullSafeAppend( String originalValue, String charSegment )
{
if ( originalValue == null )
{
return charSegment;
}
else
{
return originalValue + charSegment;
}
}
}

View File

@ -21,10 +21,11 @@ package org.apache.maven.xml.sax.filter;
import java.util.function.Function;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* Resolves all ci-friendly properties occurrences
* Resolves all ci-friendly properties occurrences between version-tags
*
* @author Robert Scholte
* @since 3.7.0
@ -34,6 +35,10 @@ class CiFriendlyXMLFilter
{
private Function<String, String> replaceChain = Function.identity();
private String characters;
private boolean parseVersion;
public CiFriendlyXMLFilter setChangelist( String changelist )
{
replaceChain = replaceChain.andThen( t -> t.replace( "${changelist}", changelist ) );
@ -64,20 +69,49 @@ class CiFriendlyXMLFilter
public void characters( char[] ch, int start, int length )
throws SAXException
{
String text = new String( ch, start, length );
// assuming this has the best performance
if ( text.contains( "${" ) )
if ( parseVersion )
{
String newText = replaceChain.apply( text );
super.characters( newText.toCharArray(), 0, newText.length() );
this.characters = nullSafeAppend( characters, new String( ch, start, length ) );
}
else
{
super.characters( ch, start, length );
}
}
@Override
public void startElement( String uri, String localName, String qName, Attributes atts )
throws SAXException
{
if ( !parseVersion && "version".equals( localName ) )
{
parseVersion = true;
}
super.startElement( uri, localName, qName, atts );
}
@Override
public void endElement( String uri, String localName, String qName )
throws SAXException
{
if ( parseVersion )
{
// assuming this has the best performance
if ( characters != null && characters.contains( "${" ) )
{
char[] ch = replaceChain.apply( characters ).toCharArray();
super.characters( ch, 0, ch.length );
}
else
{
char[] ch = characters.toCharArray();
super.characters( ch, 0, ch.length );
}
characters = null;
parseVersion = false;
}
super.endElement( uri, localName, qName );
}
}

View File

@ -120,19 +120,21 @@ class ParentXMLFilter
{
final String eventState = state;
final String charSegment = new String( ch, start, length );
switch ( eventState )
{
case "parent":
parentWhitespace = new String( ch, start, length );
parentWhitespace = nullSafeAppend( parentWhitespace, charSegment );
break;
case "relativePath":
relativePath = new String( ch, start, length );
relativePath = nullSafeAppend( relativePath, charSegment );
break;
case "groupId":
groupId = new String( ch, start, length );
groupId = nullSafeAppend( groupId, charSegment );
break;
case "artifactId":
artifactId = new String( ch, start, length );
artifactId = nullSafeAppend( artifactId, charSegment );
break;
default:
break;

View File

@ -39,7 +39,7 @@ public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
private String state;
// whiteSpace after <dependency>, to be used to position <version>
private String dependencyWhitespace = "";
private String dependencyWhitespace;
private boolean hasVersion;
@ -79,16 +79,17 @@ public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
if ( parsingDependency )
{
final String eventState = state;
String value = new String( ch, start, length );
switch ( eventState )
{
case "dependency":
dependencyWhitespace = new String( ch, start, length );
dependencyWhitespace = nullSafeAppend( dependencyWhitespace, value );
break;
case "groupId":
groupId = new String( ch, start, length );
groupId = nullSafeAppend( groupId, value );
break;
case "artifactId":
artifactId = new String( ch, start, length );
artifactId = nullSafeAppend( artifactId, value );
break;
default:
break;
@ -115,8 +116,12 @@ public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
{
try ( Includer i = super.include() )
{
super.characters( dependencyWhitespace.toCharArray(), 0,
dependencyWhitespace.length() );
if ( dependencyWhitespace != null )
{
super.characters( dependencyWhitespace.toCharArray(), 0,
dependencyWhitespace.length() );
}
String versionQName = SAXEventUtils.renameQName( qName, "version" );
super.startElement( uri, "version", versionQName, null );
@ -131,6 +136,7 @@ public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
// reset
hasVersion = false;
dependencyWhitespace = null;
groupId = null;
artifactId = null;

View File

@ -23,6 +23,7 @@ import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.ContentHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
@ -35,10 +36,10 @@ import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.apache.maven.xml.Factories;
import org.apache.maven.xml.sax.filter.AbstractSAXFilter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
public abstract class AbstractXMLFilterTests
{
@ -55,7 +56,10 @@ public abstract class AbstractXMLFilterTests
{
XMLReader r = Factories.newXMLReader();
filter.setParent( r );
AbstractSAXFilter perChar = new PerCharXMLFilter();
perChar.setParent( r );
filter.setParent( perChar );
filter.setFeature( "http://xml.org/sax/features/namespaces", true );
}
}
@ -86,6 +90,9 @@ public abstract class AbstractXMLFilterTests
throws TransformerException, SAXException, ParserConfigurationException
{
setParent( filter );
filter = new PerCharXMLFilter( filter );
return transform( new StringReader( input ), filter );
}
@ -107,7 +114,7 @@ public abstract class AbstractXMLFilterTests
}
transformerHandler.setResult( result );
Transformer transformer = transformerFactory.newTransformer();
SAXSource transformSource = new SAXSource( filter, new InputSource( input ) );
SAXResult transformResult = new SAXResult( transformerHandler );
@ -116,4 +123,57 @@ public abstract class AbstractXMLFilterTests
return writer.toString();
}
/**
* From {@link ContentHandler}
* <q>Your code should not assume that algorithms using char-at-a-time idioms will be working in characterunits;
* in some cases they will split characters. This is relevant wherever XML permits arbitrary characters, such as
* attribute values,processing instruction data, and comments as well as in data reported from this method. It's
* also generally relevant whenever Java code manipulates internationalized text; the issue isn't unique to XML.</q>
*
* @author Robert Scholte
*/
class PerCharXMLFilter
extends AbstractSAXFilter
{
public PerCharXMLFilter()
{
super();
}
public <T extends XMLReader & LexicalHandler> PerCharXMLFilter( T parent )
{
super( parent );
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
for ( int i = 0; i < length; i++ )
{
super.characters( ch, start + i, 1 );
}
}
@Override
public void comment( char[] ch, int start, int length )
throws SAXException
{
for ( int i = 0; i < length; i++ )
{
super.comment( ch, start + i, 1 );
}
}
@Override
public void ignorableWhitespace( char[] ch, int start, int length )
throws SAXException
{
for ( int i = 0; i < length; i++ )
{
super.ignorableWhitespace( ch, start + i, 1 );
}
}
}
}

View File

@ -0,0 +1,67 @@
package org.apache.maven.xml.sax.filter;
/*
* 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.
*/
import static org.junit.Assert.assertEquals;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.SAXException;
public class CiFriendlyXMLFilterTest extends AbstractXMLFilterTests
{
private CiFriendlyXMLFilter filter;
@Before
public void setUp()
{
filter = new CiFriendlyXMLFilter();
filter.setChangelist( "CHANGELIST" );
}
@Override
protected AbstractSAXFilter getFilter()
throws TransformerException, SAXException, ParserConfigurationException
{
return filter;
}
@Test
public void changelist() throws Exception
{
String input = "<project>"
+ " <groupId>GROUPID</groupId>"
+ " <artifactId>ARTIFACTID</artifactId>"
+ "<version>${changelist}</version>"
+ "</project>";
String expected = "<project>"
+ " <groupId>GROUPID</groupId>"
+ " <artifactId>ARTIFACTID</artifactId>"
+ "<version>CHANGELIST</version>"
+ "</project>";
String actual = transform( input );
assertEquals( expected, actual );
}
}