/* * Copyright 2008-2017 by Emeric Vernat * * This file is part of Java Melody. * * Licensed 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 net.bull.javamelody; import java.awt.datatransfer.DataFlavor; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Serializable; import java.util.Locale; import java.util.Map; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.collections.MapConverter; import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver; import com.thoughtworks.xstream.io.xml.CompactWriter; import com.thoughtworks.xstream.security.NoTypePermission; import com.thoughtworks.xstream.security.NullPermission; import com.thoughtworks.xstream.security.PrimitiveTypePermission; /** * Liste des formats de transport entre un serveur de collecte et une application monitorée * (hors protocole à priori http). * @author Emeric Vernat */ enum TransportFormat { /** * Sérialisation java. */ SERIALIZED(DataFlavor.javaSerializedObjectMimeType), /** * XML (avec XStream/XPP). */ XML("text/xml; charset=utf-8"), /** * JSON (écriture en JSON avec XStream). * Note : il serait possible aussi de le faire avec Jackson (http://jackson.codehaus.org/) */ JSON("application/json"); private static final String NULL_VALUE = "null"; // classe interne pour qu'elle ne soit pas chargée avec la classe TransportFormat // et qu'ainsi on ne dépende pas de XStream si on ne se sert pas du format xml private static final class XmlIO { private static final String PACKAGE_NAME = TransportFormat.class.getName().substring(0, TransportFormat.class.getName().length() - TransportFormat.class.getSimpleName().length() - 1); private static final String XML_CHARSET_NAME = "utf-8"; private XmlIO() { super(); } static void writeToXml(Serializable serializable, BufferedOutputStream bufferedOutput) throws IOException { final XStream xstream = createXStream(false); // on wrappe avec un CompactWriter pour gagner 25% en taille de flux (retours chariots) // et donc un peu en performances final CompactWriter writer = new CompactWriter( new OutputStreamWriter(bufferedOutput, XML_CHARSET_NAME)); try { xstream.marshal(serializable, writer); } finally { writer.close(); } } static Object readFromXml(InputStream bufferedInput) throws IOException { final XStream xstream = createXStream(false); // see http://x-stream.github.io/security.html // clear out existing permissions and set own ones xstream.addPermission(NoTypePermission.NONE); // allow some basics xstream.addPermission(NullPermission.NULL); xstream.addPermission(PrimitiveTypePermission.PRIMITIVES); xstream.allowTypesByWildcard( new String[] { "java.lang.*", "java.util.*", "java.util.concurrent.*" }); // allow any type from the same package xstream.allowTypesByWildcard(new String[] { PACKAGE_NAME + ".*" }); final InputStreamReader reader = new InputStreamReader(bufferedInput, XML_CHARSET_NAME); try { return xstream.fromXML(reader); } finally { reader.close(); } } static void writeToJson(Serializable serializable, BufferedOutputStream bufferedOutput) throws IOException { final XStream xstream = createXStream(true); try { xstream.toXML(serializable, bufferedOutput); } finally { bufferedOutput.close(); } } private static XStream createXStream(boolean json) { final XStream xstream; if (json) { // format json xstream = new XStream(new JsonHierarchicalStreamDriver()); xstream.setMode(XStream.NO_REFERENCES); } else { // sinon format xml, utilise la dépendance XPP3 par défaut xstream = new XStream(); } for (final Map.Entry<String, Class<?>> entry : XStreamAlias.getMap().entrySet()) { xstream.alias(entry.getKey(), entry.getValue()); } final MapConverter mapConverter = new MapConverter(xstream.getMapper()) { /** {@inheritDoc} */ @SuppressWarnings("rawtypes") @Override public boolean canConvert(Class type) { return true; // Counter.requests est bien une map } }; xstream.registerLocalConverter(Counter.class, "requests", mapConverter); xstream.registerLocalConverter(Counter.class, "rootCurrentContextsByThreadId", mapConverter); return xstream; } } private static class MyObjectInputStream extends ObjectInputStream { private static final String PACKAGE_NAME = TransportFormat.class.getName().substring(0, TransportFormat.class.getName().length() - TransportFormat.class.getSimpleName().length() - 1); MyObjectInputStream(InputStream input) throws IOException { super(input); } // during deserialization, protect ourselves from malicious payload // http://www.ibm.com/developerworks/library/se-lookahead/index.html @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { final String name = desc.getName(); int i = 0; if (name.indexOf("[[") == 0) { // 2 dimensions array i++; } if (name.indexOf("[L", i) == i) { // 1 dimension array i += 2; } if (name.indexOf("java.lang.", i) == i || name.indexOf("java.util.", i) == i || name.indexOf(PACKAGE_NAME, i) == i || name.length() <= 2) { // if name.length() == 2, primitive type or array (such as [B in javamelody-swing) return super.resolveClass(desc); } throw new ClassNotFoundException(name); } } private final String code; // NOPMD private final String mimeType; // NOPMD TransportFormat(String mimeType) { this.mimeType = mimeType; this.code = this.toString().toLowerCase(Locale.ENGLISH); } static TransportFormat valueOfIgnoreCase(String transportFormat) { return valueOf(transportFormat.toUpperCase(Locale.ENGLISH).trim()); } static boolean isATransportFormat(String format) { if (format == null) { return false; } final String upperCase = format.toUpperCase(Locale.ENGLISH).trim(); for (final TransportFormat transportFormat : TransportFormat.values()) { if (transportFormat.toString().equals(upperCase)) { return true; } } return false; } static void pump(InputStream input, OutputStream output) throws IOException { final byte[] bytes = new byte[4 * 1024]; int length = input.read(bytes); while (length != -1) { output.write(bytes, 0, length); length = input.read(bytes); } } String getCode() { return code; } String getMimeType() { return mimeType; } void checkDependencies() throws IOException { if (this == XML || this == JSON) { try { Class.forName("com.thoughtworks.xstream.XStream"); } catch (final ClassNotFoundException e) { throw new IOException( "Classes of the XStream library not found. Add the XStream dependency in your webapp for the XML or JSON formats.", e); } } if (this == XML) { try { Class.forName("org.xmlpull.v1.XmlPullParser"); } catch (final ClassNotFoundException e) { throw new IOException( "Classes of the XPP3 library not found. Add the XPP3 dependency in your webapp for the XML format.", e); } } } void writeSerializableTo(Serializable serializable, OutputStream output) throws IOException { final Serializable nonNullSerializable; if (serializable == null) { nonNullSerializable = NULL_VALUE; } else { nonNullSerializable = serializable; } final BufferedOutputStream bufferedOutput = new BufferedOutputStream(output); switch (this) { case SERIALIZED: final ObjectOutputStream out = new ObjectOutputStream(bufferedOutput); try { out.writeObject(nonNullSerializable); } finally { out.close(); } break; case XML: // Rq : sans xstream et si jdk 1.6, on pourrait sinon utiliser // XMLStreamWriter et XMLStreamReader ou JAXB avec des annotations XmlIO.writeToXml(nonNullSerializable, bufferedOutput); break; case JSON: XmlIO.writeToJson(nonNullSerializable, bufferedOutput); break; default: throw new IllegalStateException(toString()); } } Serializable readSerializableFrom(InputStream input) throws IOException, ClassNotFoundException { final InputStream bufferedInput = new BufferedInputStream(input); final Object result; switch (this) { case SERIALIZED: final ObjectInputStream in = createObjectInputStream(bufferedInput); try { result = in.readObject(); } finally { in.close(); } break; case XML: result = XmlIO.readFromXml(bufferedInput); break; case JSON: // pas possible avec JsonHierarchicalStreamDriver // (http://x-stream.github.io/json-tutorial.html) throw new UnsupportedOperationException(); default: throw new IllegalStateException(toString()); } if (NULL_VALUE.equals(result)) { return null; } // c'est un Serializable que l'on a écrit return (Serializable) result; } static ObjectInputStream createObjectInputStream(InputStream input) throws IOException { return new MyObjectInputStream(input); } }