Skip to content

Commit

Permalink
[JENKINS-48463] refactored XStream2 to default to KXml2Driver by defa…
Browse files Browse the repository at this point in the history
…ult.

Changed all explicit references of the hierarchical driver to use a static
convinience method XStream2.getDefaultDriver() to ensure all XML operations
are using the same driver.
  • Loading branch information
mikecirioli committed Jan 24, 2018
1 parent 37365ee commit 570b52d
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 38 deletions.
7 changes: 4 additions & 3 deletions core/src/main/java/hudson/XmlFile.java
Expand Up @@ -26,7 +26,7 @@
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.xml.KXml2Driver;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Descriptor;
import hudson.util.AtomicFileWriter;
Expand Down Expand Up @@ -336,13 +336,14 @@ private void attempt() throws Eureka {
/**
* {@link XStream} instance is supposed to be thread-safe.
*/
private static final XStream DEFAULT_XSTREAM = new XStream2();

private static final Logger LOGGER = Logger.getLogger(XmlFile.class.getName());

private static final SAXParserFactory JAXP = SAXParserFactory.newInstance();

private static final KXml2Driver DEFAULT_DRIVER = new KXml2Driver();
private static final HierarchicalStreamDriver DEFAULT_DRIVER = XStream2.getDefaultDriver();

private static final XStream DEFAULT_XSTREAM = new XStream2(DEFAULT_DRIVER);

static {
JAXP.setNamespaceAware(true);
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/java/hudson/model/View.java
Expand Up @@ -26,7 +26,6 @@

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.io.StreamException;
import com.thoughtworks.xstream.io.xml.KXml2Driver;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.ExtensionPoint;
Expand Down Expand Up @@ -1213,7 +1212,7 @@ public void updateByXml(Source source) throws IOException {
// Do not allow overwriting view name as it might collide with another
// view in same ViewGroup and might not satisfy Jenkins.checkGoodName.
String oldname = name;
Object o = Jenkins.XSTREAM.unmarshal(new KXml2Driver().createReader(in), this);
Object o = Jenkins.XSTREAM.unmarshal(XStream2.getDefaultDriver().createReader(in), this);
if (!o.getClass().equals(getClass())) {
// ensure that we've got the same view type. extending this code to support updating
// to different view type requires destroying & creating a new view type
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/java/hudson/util/XStream2.java
Expand Up @@ -26,6 +26,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.KXml2Driver;
import com.thoughtworks.xstream.mapper.AnnotationMapper;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;
Expand Down Expand Up @@ -86,7 +87,18 @@ public class XStream2 extends XStream {
*/
private MapperInjectionPoint mapperInjectionPoint;

/**
* Convinience method so we only have to change the driver in one place
* if we switch to something new in the future
*
* @return a new instance of the HierarchicalStreamDriver we want to use
*/
public static HierarchicalStreamDriver getDefaultDriver() {
return new KXml2Driver();
}

public XStream2() {
super(getDefaultDriver());
init();
classOwnership = null;
}
Expand All @@ -98,6 +110,7 @@ public XStream2(HierarchicalStreamDriver hierarchicalStreamDriver) {
}

XStream2(ClassOwnership classOwnership) {
super(getDefaultDriver());
init();
this.classOwnership = classOwnership;
}
Expand Down
10 changes: 5 additions & 5 deletions core/src/main/java/jenkins/util/xstream/XStreamDOM.java
Expand Up @@ -34,11 +34,11 @@
import com.thoughtworks.xstream.io.xml.AbstractXmlReader;
import com.thoughtworks.xstream.io.xml.AbstractXmlWriter;
import com.thoughtworks.xstream.io.xml.DocumentReader;
import com.thoughtworks.xstream.io.xml.KXml2Driver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
import hudson.Util;
import hudson.util.VariableResolver;

import hudson.util.XStream2;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
Expand Down Expand Up @@ -241,11 +241,11 @@ public static WriterImpl newWriter() {
* Writes this {@link XStreamDOM} into {@link OutputStream}.
*/
public void writeTo(OutputStream os) {
writeTo(new KXml2Driver().createWriter(os));
writeTo(XStream2.getDefaultDriver().createWriter(os));
}

public void writeTo(Writer w) {
writeTo(new KXml2Driver().createWriter(w));
writeTo(XStream2.getDefaultDriver().createWriter(w));
}

public void writeTo(HierarchicalStreamWriter w) {
Expand All @@ -262,11 +262,11 @@ public static XStreamDOM from(XStream xs, Object obj) {
}

public static XStreamDOM from(InputStream in) {
return from(new KXml2Driver().createReader(in));
return from(XStream2.getDefaultDriver().createReader(in));
}

public static XStreamDOM from(Reader in) {
return from(new KXml2Driver().createReader(in));
return from(XStream2.getDefaultDriver().createReader(in));
}

public static XStreamDOM from(HierarchicalStreamReader in) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/test/java/hudson/PluginManagerTest.java
Expand Up @@ -64,7 +64,7 @@ public class PluginManagerTest {
@Issue("SECURITY-167")
@Test
public void parseInvalidRequestedPlugins() throws Exception {
String evilXML = "<?xml version='1.1' encoding='UTF-8'?>\n" +
String evilXML = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<!DOCTYPE project[<!ENTITY foo SYSTEM \"file:///\">]>\n" +
"<root>\n" +
" <stuff plugin='stuff@1.0'>\n" +
Expand Down
2 changes: 1 addition & 1 deletion core/src/test/java/hudson/util/XStream2EncodingTest.java
Expand Up @@ -70,7 +70,7 @@ private void clearDefaultEncoding() {
Thing t = (Thing) xs.fromXML(new ByteArrayInputStream(ambiguousXml));
assertThat(t.field, not(msg));
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
baos2.write("<?xml version='1.1' encoding='UTF-8'?>\n".getBytes("UTF-8"));
baos2.write("<?xml version='1.0' encoding='UTF-8'?>\n".getBytes("UTF-8"));
baos2.write(ambiguousXml);
t = (Thing) xs.fromXML(new ByteArrayInputStream(ambiguousXml));
assertThat(t.field, not(msg));
Expand Down
36 changes: 18 additions & 18 deletions core/src/test/java/jenkins/model/RunIdMigratorTest.java
Expand Up @@ -83,13 +83,13 @@ public class RunIdMigratorTest {

@Test public void legacy() throws Exception {
assumeFalse("Symlinks don't work well on Windows", Functions.isWindows());
write("2014-01-02_03-04-05/build.xml", "<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>99</number>\n <otherstuff>ok</otherstuff>\n</run>");
write("2014-01-02_03-04-05/build.xml", "<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>99</number>\n <otherstuff>ok</otherstuff>\n</run>");
link("99", "2014-01-02_03-04-05");
link("lastFailedBuild", "-1");
link("lastSuccessfulBuild", "99");
assertEquals("{2014-01-02_03-04-05={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>99</number>\n <otherstuff>ok</otherstuff>\n</run>'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize());
assertEquals("{2014-01-02_03-04-05={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>99</number>\n <otherstuff>ok</otherstuff>\n</run>'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize());
assertTrue(migrator.migrate(dir, null));
assertEquals("{99={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <id>2014-01-02_03-04-05</id>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize());
assertEquals("{99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <id>2014-01-02_03-04-05</id>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize());
assertEquals(99, migrator.findNumber("2014-01-02_03-04-05"));
migrator = new RunIdMigrator();
assertFalse(migrator.migrate(dir, null));
Expand All @@ -104,58 +104,58 @@ public class RunIdMigratorTest {
assumeFalse("Symlinks don't work well on Windows", Functions.isWindows());
write("2014-01-02_03-04-04/build.xml", "<run>\n <number>98</number>\n</run>");
link("98", "2014-01-02_03-04-04");
write("99/build.xml", "<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("99/build.xml", "<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
link("lastFailedBuild", "-1");
link("lastSuccessfulBuild", "99");
assertEquals("{2014-01-02_03-04-04={build.xml='<run>\n <number>98</number>\n</run>'}, 98=→2014-01-02_03-04-04, 99={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize());
assertEquals("{2014-01-02_03-04-04={build.xml='<run>\n <number>98</number>\n</run>'}, 98=→2014-01-02_03-04-04, 99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize());
assertTrue(migrator.migrate(dir, null));
assertEquals("{98={build.xml='<run>\n <id>2014-01-02_03-04-04</id>\n <timestamp>1388649844000</timestamp>\n</run>'}, 99={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n'}", summarize());
assertEquals("{98={build.xml='<run>\n <id>2014-01-02_03-04-04</id>\n <timestamp>1388649844000</timestamp>\n</run>'}, 99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n'}", summarize());
}

@Test public void reverseImmediately() throws Exception {
assumeFalse("Symlinks don't work well on Windows", Functions.isWindows());
File root = dir;
dir = new File(dir, "jobs/somefolder/jobs/someproject/promotions/OK/builds");
write("99/build.xml", "<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <id>2014-01-02_03-04-05</id>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("99/build.xml", "<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <id>2014-01-02_03-04-05</id>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
link("lastFailedBuild", "-1");
link("lastSuccessfulBuild", "99");
write("legacyIds", "2014-01-02_03-04-05 99\n");
assertEquals("{99={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <id>2014-01-02_03-04-05</id>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize());
assertEquals("{99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <id>2014-01-02_03-04-05</id>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize());
RunIdMigrator.main(root.getAbsolutePath());
assertEquals("{2014-01-02_03-04-05={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>99</number>\n <otherstuff>ok</otherstuff>\n</run>'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize());
assertEquals("{2014-01-02_03-04-05={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>99</number>\n <otherstuff>ok</otherstuff>\n</run>'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize());
}

@Test public void reverseAfterNewBuilds() throws Exception {
assumeFalse("Symlinks don't work well on Windows", Functions.isWindows());
File root = dir;
dir = new File(dir, "jobs/someproject/modules/test$test/builds");
write("1/build.xml", "<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("1/build.xml", "<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("legacyIds", "");
assertEquals("{1={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, legacyIds=''}", summarize());
assertEquals("{1={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, legacyIds=''}", summarize());
RunIdMigrator.main(root.getAbsolutePath());
assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>1</number>\n <otherstuff>ok</otherstuff>\n</run>'}}", summarize());
assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>1</number>\n <otherstuff>ok</otherstuff>\n</run>'}}", summarize());
}

@Test public void reverseMatrixAfterNewBuilds() throws Exception {
assumeFalse("Symlinks don't work well on Windows", Functions.isWindows());
File root = dir;
dir = new File(dir, "jobs/someproject/Environment=prod/builds");
write("1/build.xml", "<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("1/build.xml", "<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("legacyIds", "");
assertEquals("{1={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, legacyIds=''}", summarize());
assertEquals("{1={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, legacyIds=''}", summarize());
RunIdMigrator.main(root.getAbsolutePath());
assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>1</number>\n <otherstuff>ok</otherstuff>\n</run>'}}", summarize());
assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>1</number>\n <otherstuff>ok</otherstuff>\n</run>'}}", summarize());
}

@Test public void reverseMavenAfterNewBuilds() throws Exception {
assumeFalse("Symlinks don't work well on Windows", Functions.isWindows());
File root = dir;
dir = new File(dir, "jobs/someproject/test$test/builds");
write("1/build.xml", "<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("1/build.xml", "<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>");
write("legacyIds", "");
assertEquals("{1={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, legacyIds=''}", summarize());
assertEquals("{1={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <timestamp>1388649845000</timestamp>\n <otherstuff>ok</otherstuff>\n</run>'}, legacyIds=''}", summarize());
RunIdMigrator.main(root.getAbsolutePath());
assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='<?xml version='1.1' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>1</number>\n <otherstuff>ok</otherstuff>\n</run>'}}", summarize());
assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n<run>\n <stuff>ok</stuff>\n <number>1</number>\n <otherstuff>ok</otherstuff>\n</run>'}}", summarize());
}

// TODO test sane recovery from various error conditions
Expand Down
3 changes: 1 addition & 2 deletions core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java
Expand Up @@ -23,7 +23,6 @@
*/
package jenkins.util.xstream;

import com.thoughtworks.xstream.io.xml.KXml2Driver;
import hudson.util.XStream2;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
Expand Down Expand Up @@ -54,7 +53,7 @@ public static class Foo {

@Before
public void setUp() throws Exception {
xs = new XStream2(new KXml2Driver());
xs = new XStream2();
xs.alias("foo", Foo.class);
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/test/java/jenkins/xml/XMLUtilsTest.java
Expand Up @@ -49,7 +49,7 @@ public class XMLUtilsTest {
@Issue("SECURITY-167")
@Test()
public void testSafeTransformDoesNotProcessForeignResources() throws Exception {
final String xml = "<?xml version='1.1' encoding='UTF-8'?>\n" +
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<!DOCTYPE project[\n" +
" <!ENTITY foo SYSTEM \"file:///\">\n" +
"]>\n" +
Expand Down Expand Up @@ -82,7 +82,7 @@ public void testSafeTransformDoesNotProcessForeignResources() throws Exception {
@Issue("SECURITY-167")
@Test()
public void testUpdateByXmlIDoesNotFail() throws Exception {
final String xml = "<?xml version='1.1' encoding='UTF-8'?>\n" +
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<project>\n" +
" <actions/>\n" +
" <description>&amp;</description>\n" +
Expand Down Expand Up @@ -119,7 +119,7 @@ public void testGetValue() throws XPathExpressionException, SAXException, IOExce
@Test
public void testParse_with_XXE() throws IOException, XPathExpressionException {
try {
final String xml = "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n" +
final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE foo [\n" +
" <!ELEMENT foo ANY >\n" +
" <!ENTITY xxe SYSTEM \"http://abc.com/temp/test.jsp\" >]> " +
Expand Down
4 changes: 2 additions & 2 deletions test/src/test/java/hudson/model/AbstractItemSecurityTest.java
Expand Up @@ -47,7 +47,7 @@ public class AbstractItemSecurityTest {
@Issue("SECURITY-167")
@Test()
public void testUpdateByXmlDoesNotProcessForeignResources() throws Exception {
final String xml = "<?xml version='1.1' encoding='UTF-8'?>\n" +
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<!DOCTYPE project[\n" +
" <!ENTITY foo SYSTEM \"file:///\">\n" +
"]>\n" +
Expand All @@ -73,7 +73,7 @@ public void testUpdateByXmlDoesNotProcessForeignResources() throws Exception {
@Issue("SECURITY-167")
@Test()
public void testUpdateByXmlDoesNotFail() throws Exception {
final String xml = "<?xml version='1.1' encoding='UTF-8'?>\n" +
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
"<project>\n" +
" <description>&amp;</description>\n" +
" <scm class=\"hudson.scm.NullSCM\"/>\n" +
Expand Down

0 comments on commit 570b52d

Please sign in to comment.