--- old/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/BridgeImpl.java 2017-05-30 14:50:19.529723374 +0100 +++ new/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/BridgeImpl.java 2017-05-30 14:50:19.469723376 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,7 +76,7 @@ public void marshal(Marshaller _m, T t, XMLStreamWriter output) throws JAXBException { MarshallerImpl m = (MarshallerImpl)_m; - m.write(tagName,bi,t,XMLStreamWriterOutput.create(output,context),new StAXPostInitAction(output,m.serializer)); + m.write(tagName,bi,t,XMLStreamWriterOutput.create(output,context, m.getEscapeHandler()),new StAXPostInitAction(output,m.serializer)); } public void marshal(Marshaller _m, T t, OutputStream output, NamespaceContext nsContext) throws JAXBException { --- old/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/MarshallerImpl.java 2017-05-30 14:50:19.749723365 +0100 +++ new/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/MarshallerImpl.java 2017-05-30 14:50:19.689723367 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -160,7 +160,7 @@ @Override public void marshal(Object obj, XMLStreamWriter writer) throws JAXBException { - write(obj, XMLStreamWriterOutput.create(writer,context), new StAXPostInitAction(writer,serializer)); + write(obj, XMLStreamWriterOutput.create(writer,context, escapeHandler), new StAXPostInitAction(writer,serializer)); } @Override @@ -364,6 +364,15 @@ } + /** + * Returns escape handler provided with JAXB context parameters. + * + * @return escape handler + */ + CharacterEscapeHandler getEscapeHandler() { + return escapeHandler; + } + // // // create XMLWriter by specifing various type of output. --- old/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/FastInfosetStreamWriterOutput.java 2017-05-30 14:50:19.973723356 +0100 +++ new/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/FastInfosetStreamWriterOutput.java 2017-05-30 14:50:19.917723359 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.xml.internal.bind.v2.runtime.output; +import com.sun.xml.internal.bind.marshaller.NoEscapeHandler; import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; import com.sun.xml.internal.bind.v2.runtime.Name; import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; @@ -220,7 +221,7 @@ public FastInfosetStreamWriterOutput(StAXDocumentSerializer out, JAXBContextImpl context) { - super(out); + super(out, NoEscapeHandler.theInstance); this.fiout = out; this.localNames = context.getUTF8NameTable(); --- old/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/StAXExStreamWriterOutput.java 2017-05-30 14:50:20.201723347 +0100 +++ new/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/StAXExStreamWriterOutput.java 2017-05-30 14:50:20.137723350 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ import javax.xml.stream.XMLStreamException; +import com.sun.xml.internal.bind.marshaller.NoEscapeHandler; import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data; import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx; @@ -40,7 +41,7 @@ private final XMLStreamWriterEx out; public StAXExStreamWriterOutput(XMLStreamWriterEx out) { - super(out); + super(out, NoEscapeHandler.theInstance); this.out = out; } --- old/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/XMLStreamWriterOutput.java 2017-05-30 14:50:20.421723339 +0100 +++ new/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/XMLStreamWriterOutput.java 2017-05-30 14:50:20.361723341 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,15 +26,16 @@ package com.sun.xml.internal.bind.v2.runtime.output; import java.io.IOException; +import java.io.Writer; import java.lang.reflect.Constructor; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler; import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; -import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl; import org.xml.sax.SAXException; /** @@ -53,7 +54,7 @@ * Creates a new {@link XmlOutput} from a {@link XMLStreamWriter}. * This method recognizes an FI StAX writer. */ - public static XmlOutput create(XMLStreamWriter out, JAXBContextImpl context) { + public static XmlOutput create(XMLStreamWriter out, JAXBContextImpl context, CharacterEscapeHandler escapeHandler) { // try optimized path final Class writerClass = out.getClass(); if (writerClass==FI_STAX_WRITER_CLASS) { @@ -69,17 +70,26 @@ } } + CharacterEscapeHandler xmlStreamEscapeHandler = escapeHandler != null ? + escapeHandler : NewLineEscapeHandler.theInstance; + // otherwise the normal writer. - return new XMLStreamWriterOutput(out); + return new XMLStreamWriterOutput(out, xmlStreamEscapeHandler); } private final XMLStreamWriter out; + private final CharacterEscapeHandler escapeHandler; + + private final XmlStreamOutWriterAdapter writerWrapper; + protected final char[] buf = new char[256]; - protected XMLStreamWriterOutput(XMLStreamWriter out) { + protected XMLStreamWriterOutput(XMLStreamWriter out, CharacterEscapeHandler escapeHandler) { this.out = out; + this.escapeHandler = escapeHandler; + this.writerWrapper = new XmlStreamOutWriterAdapter(out); } // not called if we are generating fragments @@ -137,7 +147,7 @@ public void text(String value, boolean needsSeparatingWhitespace) throws IOException, SAXException, XMLStreamException { if(needsSeparatingWhitespace) out.writeCharacters(" "); - out.writeCharacters(value); + escapeHandler.escape(value.toCharArray(), 0, value.length(), false, writerWrapper); } public void text(Pcdata value, boolean needsSeparatingWhitespace) throws IOException, SAXException, XMLStreamException { @@ -207,4 +217,82 @@ } } + + /** + * Performs character escaping only for new lines. + */ + private static class NewLineEscapeHandler implements CharacterEscapeHandler { + + public static final NewLineEscapeHandler theInstance = new NewLineEscapeHandler(); + + @Override + public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException { + int limit = start+length; + int lastEscaped = start; + + for (int i = start; i < limit; i++) { + char c = ch[i]; + if (c == '\r' || c == '\n') { + if (i != lastEscaped) { + out.write(ch, lastEscaped, i - lastEscaped); + } + lastEscaped = i + 1; + if (out instanceof XmlStreamOutWriterAdapter) { + try { + ((XmlStreamOutWriterAdapter)out).writeEntityRef("#x" + Integer.toHexString(c)); + } catch (XMLStreamException e) { + throw new IOException("Error writing xml stream", e); + } + } else { + out.write("&#x"); + out.write(Integer.toHexString(c)); + out.write(';'); + } + } + } + if (lastEscaped != limit) { + out.write(ch, lastEscaped, length - lastEscaped); + } + } + } + + private static final class XmlStreamOutWriterAdapter extends Writer { + + private final XMLStreamWriter writer; + + private XmlStreamOutWriterAdapter(XMLStreamWriter writer) { + this.writer = writer; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + try { + writer.writeCharacters(cbuf, off, len); + } catch (XMLStreamException e) { + throw new IOException("Error writing XML stream", e); + } + } + + public void writeEntityRef(String entityReference) throws XMLStreamException { + writer.writeEntityRef(entityReference); + } + + @Override + public void flush() throws IOException { + try { + writer.flush(); + } catch (XMLStreamException e) { + throw new IOException("Error flushing XML stream", e); + } + } + + @Override + public void close() throws IOException { + try { + writer.close(); + } catch (XMLStreamException e) { + throw new IOException("Error closing XML stream", e); + } + } + } } --- /dev/null 2017-05-30 11:11:54.986234977 +0100 +++ new/jaxws/src/share/jaxws_classes/com/sun/xml/internal/bind/marshaller/NoEscapeHandler.java 2017-05-30 14:50:20.589723332 +0100 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.xml.internal.bind.marshaller; + +import java.io.IOException; +import java.io.Writer; + +/** + * Performs no character escaping. + * + * @author + * Roman Grigoriadi (roman.grigoriadi@oracle.com) + */ +public class NoEscapeHandler implements CharacterEscapeHandler { + + public static final NoEscapeHandler theInstance = new NoEscapeHandler(); + + @Override + public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException { + out.write(ch, start, length); + } +} --- /dev/null 2017-05-30 11:11:54.986234977 +0100 +++ new/jdk/test/javax/xml/ws/8172297/Main.java 2017-05-30 14:50:20.797723324 +0100 @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8172297 + * @summary Test that carriage-return and new-line characters + * are preserved in webservice parameters + * @compile ws/HelloWorld.java ws/HelloWorldImpl.java Main.java + * @run testng/othervm Main + */ + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.URL; +import java.util.concurrent.CountDownLatch; + +import javax.xml.namespace.QName; +import javax.xml.ws.Endpoint; +import javax.xml.ws.Service; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import ws.HelloWorld; +import ws.HelloWorldImpl; + +public class Main { + + @Test + public void runTest() throws Exception { + // + CountDownLatch serverInitSignal = new CountDownLatch(1); + CountDownLatch testDoneSignal = new CountDownLatch(1); + + WebserviceRunner serverThread = new WebserviceRunner(serverInitSignal, testDoneSignal); + (new Thread(serverThread)).start(); + + serverInitSignal.await(); + + boolean paramModified = runClientCode(serverThread.getPort()); + + testDoneSignal.countDown(); + + Assert.assertFalse(paramModified, "WS parameter was modified during round trip."); + } + + /* + * Connects to launched web service endpoint, sends message with CR/NL symbols and + * checks if it was modified during the round trip client/server communication. + */ + private boolean runClientCode(int port) throws Exception { + System.out.println("Launching WS client connection on " + port + " port"); + URL url = new URL("http://localhost:" + port + "/ws/hello?wsdl"); + QName qname = new QName("http://ws/", "HelloWorldImplService"); + Service service = Service.create(url, qname); + + HelloWorld hello = (HelloWorld) service.getPort(HelloWorld.class); + + logStringContent("Client input parameter", WS_PARAM_VALUE); + + String response = hello.getHelloWorldAsString(WS_PARAM_VALUE); + logStringContent("Client response parameter", response); + + return !WS_PARAM_VALUE.equals(response); + } + + /* + * Outputs the parameter value with newline and carriage-return symbols + * replaced with #CR and #NL text abbreviations. + */ + private static void logStringContent(String description, String parameter) { + String readableContent = parameter.replaceAll("\r", "#CR") + .replaceAll("\n", "#NL"); + System.out.println(description + ": '" + readableContent + "'"); + } + + /* Web service parameter value with newline and carriage-return symbols */ + private final static String WS_PARAM_VALUE = "\r\r\n\r\r CarriageReturn and " + +"NewLine \r\n\n Test \r\r\r\r"; + + /* + * Web service server thread that publishes WS on vacant port and waits + * for client to finalize testing + */ + class WebserviceRunner implements Runnable { + // Latch used to signalize when WS endpoint is initialized + private final CountDownLatch initSignal; + // Latch used to signalize when client completed testing + private final CountDownLatch doneSignal; + // Port where WS endpoint is published + private volatile int port = 0; + + // Constructor + WebserviceRunner(CountDownLatch initSignal, CountDownLatch doneSignal) { + this.initSignal = initSignal; + this.doneSignal = doneSignal; + } + + // Returns port of the published endpoint + public int getPort() { + return port; + } + + /* + * Publish web service on vacant port and waits for the client to + * complete testing. + */ + public void run() { + try { + // Find vacant port number + ServerSocket ss = new ServerSocket(0); + port = ss.getLocalPort(); + ss.close(); + + // Publish WebService + System.out.println("Publishing WebService on " + port + " port"); + Endpoint ep = Endpoint.publish("http://localhost:" + port + "/ws/hello", new HelloWorldImpl()); + + // Notify main thread that WS endpoint is published + initSignal.countDown(); + + // Wait for main thread to complete testing + System.out.println("Waiting for done signal from test client."); + doneSignal.await(); + + // Terminate WS endpoint + System.out.println("Got done signal from the client. Stopping WS endpoint."); + ep.stop(); + } catch (IOException ioe) { + System.out.println("Failed to get vacant port number:" + ioe); + } catch (InterruptedException ie) { + System.out.println("Failed to wait for test completion:" + ie); + } + } + } +} --- /dev/null 2017-05-30 11:11:54.986234977 +0100 +++ new/jdk/test/javax/xml/ws/8172297/ws/HelloWorld.java 2017-05-30 14:50:21.113723312 +0100 @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package ws; + +import javax.jws.WebMethod; +import javax.jws.WebService; +import javax.jws.soap.SOAPBinding; +import javax.jws.soap.SOAPBinding.Style; +import javax.jws.soap.SOAPBinding.Use; + +//Web service endpoint interface +@WebService +@SOAPBinding(style = Style.DOCUMENT, use = Use.LITERAL) +public interface HelloWorld { + + @WebMethod + String getHelloWorldAsString(String name); +} --- /dev/null 2017-05-30 11:11:54.986234977 +0100 +++ new/jdk/test/javax/xml/ws/8172297/ws/HelloWorldImpl.java 2017-05-30 14:50:21.421723300 +0100 @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package ws; + +import javax.jws.WebService; + +// Simple web service implementation that echoes its parameter +@WebService(endpointInterface = "ws.HelloWorld") +public class HelloWorldImpl implements HelloWorld { + + @Override + public String getHelloWorldAsString(String name) { + System.out.println("Server-side parameter value: '" + + name.replaceAll("\r", "#CR") + .replaceAll("\n", "#NL") + "'"); + return name; + } +}