/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services 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 Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.messaging.amf.io; import java.io.DataInputStream; import java.io.EOFException; import java.io.Externalizable; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import org.granite.config.AMF3Config; import org.granite.config.ExternalizersConfig; import org.granite.config.api.AliasRegistryConfig; import org.granite.context.GraniteContext; import org.granite.messaging.AliasRegistry; import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor; import org.granite.messaging.amf.io.util.DefaultActionScriptClassDescriptor; import org.granite.messaging.amf.io.util.Property; import org.granite.messaging.amf.io.util.externalizer.Externalizer; import org.granite.messaging.amf.io.util.instantiator.AbstractInstantiator; import org.granite.util.TypeUtil; import org.granite.util.XMLUtil; import org.granite.util.XMLUtilFactory; import org.w3c.dom.Document; /** * @author Franck WOLFF */ public class AMF3Deserializer implements ObjectInput, AMF3Constants { /////////////////////////////////////////////////////////////////////////// // Fields. protected final List<String> storedStrings; protected final List<Object> storedObjects; protected final List<ActionScriptClassDescriptor> storedClassDescriptors; protected Map<String, Document> documentCache; protected final AliasRegistry aliasRegistry; protected final ExternalizersConfig externalizersConfig; protected final AMF3DeserializerSecurizer securizer; protected final XMLUtil xmlUtil; private final InputStream in; private final byte[] buffer; private int position; private int size; /////////////////////////////////////////////////////////////////////////// // Constructor. public AMF3Deserializer(InputStream in) { this(in, 1024); } public AMF3Deserializer(InputStream in, int capacity) { this.in = in; this.buffer = new byte[capacity]; this.position = 0; this.size = 0; this.storedStrings = new ArrayList<String>(64); this.storedObjects = new ArrayList<Object>(64); this.storedClassDescriptors = new ArrayList<ActionScriptClassDescriptor>(); this.documentCache = null; // created on demand. GraniteContext context = GraniteContext.getCurrentInstance(); this.aliasRegistry = ((AliasRegistryConfig)context.getGraniteConfig()).getAliasRegistry(); this.externalizersConfig = (ExternalizersConfig)context.getGraniteConfig(); this.securizer = ((AMF3Config)context.getGraniteConfig()).getAmf3DeserializerSecurizer(); this.xmlUtil = XMLUtilFactory.getXMLUtil(); } public void reset() { this.storedStrings.clear(); this.storedObjects.clear(); this.storedClassDescriptors.clear(); this.documentCache = null; } /////////////////////////////////////////////////////////////////////////// // ObjectInput implementation. public Object readObject() throws IOException { ensureAvailable(1); int type = buffer[position++]; return readObject(type); } /////////////////////////////////////////////////////////////////////////// // AMF3 deserialization methods. protected Object readObject(int type) throws IOException { switch (type) { case AMF3_UNDEFINED: // 0x00; case AMF3_NULL: // 0x01; return null; case AMF3_BOOLEAN_FALSE: // 0x02; return Boolean.FALSE; case AMF3_BOOLEAN_TRUE: // 0x03; return Boolean.TRUE; case AMF3_INTEGER: // 0x04; return Integer.valueOf(readAMF3Integer()); case AMF3_NUMBER: // 0x05; return readAMF3Double(); case AMF3_STRING: // 0x06; return readAMF3String(); case AMF3_XML: // 0x07; return readAMF3Xml(); case AMF3_DATE: // 0x08; return readAMF3Date(); case AMF3_ARRAY: // 0x09; return readAMF3Array(); case AMF3_OBJECT: // 0x0A; return readAMF3Object(); case AMF3_XMLSTRING: // 0x0B; return readAMF3XmlString(); case AMF3_BYTEARRAY: // 0x0C; return readAMF3ByteArray(); case AMF3_VECTOR_INT: // 0x0D; return readAMF3VectorInt(); case AMF3_VECTOR_UINT: // 0x0E; return readAMF3VectorUint(); case AMF3_VECTOR_NUMBER: // 0x0F; return readAMF3VectorNumber(); case AMF3_VECTOR_OBJECT: // 0x10; return readAMF3VectorObject(); case AMF3_DICTIONARY: // 0x11; return readAMF3Dictionary(); default: throw new IllegalArgumentException("Unknown type: " + type); } } protected int readAMF3Integer() throws IOException { return ((readAMF3UnsignedInteger() << 3) >> 3); } protected int readAMF3UnsignedInteger() throws IOException { ensureAvailable(1); byte b = buffer[position++]; if (b >= 0) return b; ensureAvailable(1); int result = (b & 0x7F) << 7; b = buffer[position++]; if (b >= 0) return (result | b); ensureAvailable(1); result = (result | (b & 0x7F)) << 7; b = buffer[position++]; if (b >= 0) return (result | b); ensureAvailable(1); return (((result | (b & 0x7F)) << 8) | (buffer[position++] & 0xFF)); } protected Double readAMF3Double() throws IOException { ensureAvailable(8); double d = Double.longBitsToDouble(readLongData(buffer, position)); position += 8; return Double.isNaN(d) ? null : d; } protected String readAMF3String() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored string return storedStrings.get(lengthOrIndex); if (lengthOrIndex == 0) return ""; String result; if (lengthOrIndex <= buffer.length) { ensureAvailable(lengthOrIndex); result = new String(buffer, position, lengthOrIndex, UTF8); position += lengthOrIndex; } else { byte[] bytes = new byte[lengthOrIndex]; readFully(bytes, 0, lengthOrIndex); result = new String(bytes, UTF8); } storedStrings.add(result); return result; } protected Date readAMF3Date() throws IOException { final int type = readAMF3UnsignedInteger(); if ((type & 0x01) == 0) // stored Date return (Date)storedObjects.get(type >>> 1); ensureAvailable(8); Date result = new Date((long)Double.longBitsToDouble(readLongData(buffer, position))); position += 8; storedObjects.add(result); return result; } protected Object readAMF3Array() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored array. return storedObjects.get(lengthOrIndex); String key = readAMF3String(); if (key.length() == 0) { Object[] objects = new Object[lengthOrIndex]; storedObjects.add(objects); for (int i = 0; i < lengthOrIndex; i++) objects[i] = readObject(); return objects; } Map<Object, Object> map = new HashMap<Object, Object>(lengthOrIndex); storedObjects.add(map); while (key.length() > 0) { map.put(key, readObject()); key = readAMF3String(); } for (int i = 0; i < lengthOrIndex; i++) map.put(Integer.valueOf(i), readObject()); return map; } protected int[] readAMF3VectorInt() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored vector. return (int[])storedObjects.get(lengthOrIndex); readByte(); // fixed flag: unused... int[] vector = new int[lengthOrIndex]; storedObjects.add(vector); for (int i = 0; i < lengthOrIndex; i++) vector[i] = readInt(); return vector; } protected long[] readAMF3VectorUint() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored vector. return (long[])storedObjects.get(lengthOrIndex); readByte(); // fixed flag: unused... long[] vector = new long[lengthOrIndex]; storedObjects.add(vector); for (int i = 0; i < lengthOrIndex; i++) vector[i] = (readInt() & 0xffffffffL); return vector; } protected double[] readAMF3VectorNumber() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored vector. return (double[])storedObjects.get(lengthOrIndex); readByte(); // fixed flag: unused... double[] vector = new double[lengthOrIndex]; storedObjects.add(vector); for (int i = 0; i < lengthOrIndex; i++) vector[i] = readDouble(); return vector; } @SuppressWarnings("unchecked") protected List<Object> readAMF3VectorObject() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored vector. return (List<Object>)storedObjects.get(lengthOrIndex); readByte(); // fixed flag: unused... readAMF3String(); // component class name: unused... List<Object> vector = new ArrayList<Object>(lengthOrIndex); storedObjects.add(vector); for (int i = 0; i < lengthOrIndex; i++) vector.add(readObject()); return vector; } @SuppressWarnings("unchecked") protected Map<Object, Object> readAMF3Dictionary() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored dictionary. return (Map<Object, Object>)storedObjects.get(lengthOrIndex); readByte(); // weak keys flag: unused... // AS3 Dictionary doesn't have a strict Java equivalent: use an HashMap, which // could (unlikely) lead to duplicated keys collision... Map<Object, Object> dictionary = new HashMap<Object, Object>(lengthOrIndex); storedObjects.add(dictionary); for (int i = 0; i < lengthOrIndex; i++) { Object key = readObject(); Object value = readObject(); dictionary.put(key, value); } return dictionary; } protected Document readAMF3Xml() throws IOException { String xml = readAMF3XmlString(); if (documentCache == null) documentCache = new IdentityHashMap<String, Document>(32); Document doc = documentCache.get(xml); if (doc == null) { doc = xmlUtil.buildDocument(xml); documentCache.put(xml, doc); } return doc; } protected String readAMF3XmlString() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored object return (String)storedObjects.get(lengthOrIndex); byte[] bytes = new byte[lengthOrIndex]; readFully(bytes, 0, lengthOrIndex); String result = new String(bytes, UTF8); storedObjects.add(result); return result; } protected byte[] readAMF3ByteArray() throws IOException { final int type = readAMF3UnsignedInteger(); final int lengthOrIndex = type >>> 1; if ((type & 0x01) == 0) // stored object. return (byte[])storedObjects.get(lengthOrIndex); byte[] result = new byte[lengthOrIndex]; readFully(result, 0, lengthOrIndex); storedObjects.add(result); return result; } protected Object readAMF3Object() throws IOException { final int type = readAMF3UnsignedInteger(); if ((type & 0x01) == 0) // stored object. return storedObjects.get(type >>> 1); ActionScriptClassDescriptor desc = readActionScriptClassDescriptor(type); Object result = newInstance(desc); final int index = storedObjects.size(); storedObjects.add(result); // Entity externalizers (eg. OpenJPA) may return null values for non-null AS3 objects (ie. proxies). if (result == null) return null; if (desc.isExternalizable()) readExternalizable(desc, result); else readStandard(desc, result); if (result instanceof AbstractInstantiator<?>) { try { result = ((AbstractInstantiator<?>)result).resolve(); } catch (Exception e) { throw new RuntimeException("Could not instantiate object: " + result, e); } storedObjects.set(index, result); } return result; } protected void readExternalizable(ActionScriptClassDescriptor desc, Object result) throws IOException { Externalizer externalizer = desc.getExternalizer(); if (externalizer != null) { try { externalizer.readExternal(result, this); } catch (IOException e) { throw e; } catch (Exception e) { throw new RuntimeException("Could not read externalized object: " + result, e); } } else { if (!(result instanceof Externalizable)) { throw new RuntimeException( "The ActionScript3 class bound to " + result.getClass().getName() + " (ie: [RemoteClass(alias=\"" + result.getClass().getName() + "\")])" + " implements flash.utils.IExternalizable but this Java class neither" + " implements java.io.Externalizable nor is in the scope of a configured" + " externalizer (please fix your granite-config.xml)" ); } try { ((Externalizable)result).readExternal(this); } catch (IOException e) { throw e; } catch (Exception e) { throw new RuntimeException("Could not read externalizable object: " + result, e); } } } protected void readStandard(ActionScriptClassDescriptor desc, Object result) throws IOException { // defined values... final int count = desc.getPropertiesCount(); for (int i = 0; i < count; i++) { Property property = desc.getProperty(i); Object value = readObject(readUnsignedByte()); if (value != null && value.getClass() == property.getType()) property.setValue(result, value, false); else property.setValue(result, value, true); } // dynamic values... if (desc.isDynamic()) { while (true) { String name = readAMF3String(); if (name.length() == 0) break; Object value = readObject(readUnsignedByte()); desc.setPropertyValue(name, result, value); } } } protected Object newInstance(ActionScriptClassDescriptor desc) { Externalizer externalizer = desc.getExternalizer(); if (externalizer == null) return desc.newJavaInstance(); try { return externalizer.newInstance(desc.getType(), this); } catch (Exception e) { throw new RuntimeException("Could not instantiate type: " + desc.getType(), e); } } protected ActionScriptClassDescriptor readActionScriptClassDescriptor(int flags) throws IOException { if ((flags & 0x02) == 0) return storedClassDescriptors.get(flags >>> 2); return readInlineActionScriptClassDescriptor(flags); } protected ActionScriptClassDescriptor readInlineActionScriptClassDescriptor(int flags) throws IOException { final int propertiesCount = flags >>> 4; final byte encoding = (byte)((flags >>> 2) & 0x03); String alias = readAMF3String(); String className = aliasRegistry.getTypeForAlias(alias); // Check if the class is allowed to be instantiated. if (securizer != null && !securizer.allowInstantiation(className)) throw new SecurityException("Illegal attempt to instantiate class: " + className + ", securizer: " + securizer.getClass()); // Find custom AS3 class descriptor if any. Class<? extends ActionScriptClassDescriptor> descriptorType = null; if (!"".equals(className)) descriptorType = externalizersConfig.getActionScriptDescriptor(className); ActionScriptClassDescriptor desc = null; if (descriptorType != null) { // instantiate descriptor try { desc = TypeUtil.newInstance( descriptorType, new Class[]{String.class, byte.class}, new Object[]{className, Byte.valueOf(encoding)} ); } catch (Exception e) { throw new RuntimeException("Could not instantiate AS descriptor: " + descriptorType, e); } } if (desc == null) desc = new DefaultActionScriptClassDescriptor(className, encoding); for (int i = 0; i < propertiesCount; i++) { String name = readAMF3String(); desc.defineProperty(name); } storedClassDescriptors.add(desc); return desc; } /////////////////////////////////////////////////////////////////////////// // Cached objects methods. protected void addToStoredClassDescriptors(ActionScriptClassDescriptor desc) { storedClassDescriptors.add(desc); } protected ActionScriptClassDescriptor getFromStoredClassDescriptors(int index) { return storedClassDescriptors.get(index); } /////////////////////////////////////////////////////////////////////////// // Utilities. private static long readLongData(byte[] buffer, int position) { return (buffer[position++] & 0xFFL) << 56 | (buffer[position++] & 0xFFL) << 48 | (buffer[position++] & 0xFFL) << 40 | (buffer[position++] & 0xFFL) << 32 | (buffer[position++] & 0xFFL) << 24 | (buffer[position++] & 0xFFL) << 16 | (buffer[position++] & 0xFFL) << 8 | (buffer[position] & 0xFFL); } private void ensureAvailable(int count) throws IOException { if (size - position < count) ensureAvailable0(count); } private void ensureAvailable0(final int count) throws IOException { if (count > buffer.length) throw new IllegalArgumentException("Count=" + count + " cannot be larger than buffer.length=" + buffer.length); if (position > 0) { size -= position; System.arraycopy(buffer, position, buffer, 0, size); position = 0; } do { int read = in.read(buffer, size, buffer.length - size); if (read <= 0) { if (read == -1) throw new EOFException("Count not read " + count + " bytes from input stream"); throw new RuntimeException("Internal error: buffer.length=" + buffer.length + ", size=" + size + ", count=" + count); } size += read; } while (size < count); } /////////////////////////////////////////////////////////////////////////// // @Override public void readFully(byte[] b) throws IOException { readFully(b, 0, b.length); } @Override public void readFully(byte[] b, int off, int len) throws IOException { if (b == null) throw new NullPointerException(); if (off < 0 || len < 0 || len > b.length - off) throw new IndexOutOfBoundsException(); if (len == 0) return; final int left = size - position; if (len <= left) { System.arraycopy(buffer, position, b, off, len); position += len; } else { if (left > 0) { System.arraycopy(buffer, position, b, off, left); off += left; len -= left; position = size; } while (len > 0) { int count = in.read(b, off, len); if (count <= 0) throw new EOFException(); off += count; len -= count; } } } @Override public int skipBytes(int n) throws IOException { return (int)skip(n); } @Override public boolean readBoolean() throws IOException { ensureAvailable(1); return (buffer[position++] != 0); } @Override public byte readByte() throws IOException { ensureAvailable(1); return buffer[position++]; } @Override public int readUnsignedByte() throws IOException { ensureAvailable(1); return (buffer[position++] & 0xFF); } @Override public short readShort() throws IOException { ensureAvailable(2); return (short)(((buffer[position++] & 0xFF) << 8) | (buffer[position++] & 0xFF)); } @Override public int readUnsignedShort() throws IOException { ensureAvailable(2); return (((buffer[position++] & 0xFF) << 8) | (buffer[position++] & 0xFF)); } @Override public char readChar() throws IOException { ensureAvailable(2); return (char)(((buffer[position++] & 0xFF) << 8) | (buffer[position++] & 0xFF)); } @Override public int readInt() throws IOException { ensureAvailable(4); final byte[] buffer = this.buffer; int position = this.position; int i = ( ((buffer[position++] & 0xFF) << 24) | ((buffer[position++] & 0xFF) << 16) | ((buffer[position++] & 0xFF) << 8) | ((buffer[position++] & 0xFF) ) ); this.position = position; return i; } @Override public long readLong() throws IOException { ensureAvailable(8); final byte[] buffer = this.buffer; int position = this.position; long l = ( ((buffer[position++] & 0xFFL) << 56) | ((buffer[position++] & 0xFFL) << 48) | ((buffer[position++] & 0xFFL) << 40) | ((buffer[position++] & 0xFFL) << 32) | ((buffer[position++] & 0xFFL) << 24) | ((buffer[position++] & 0xFFL) << 16) | ((buffer[position++] & 0xFFL) << 8) | ((buffer[position++] & 0xFFL) ) ); this.position = position; return l; } @Override public float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } @Override public double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } @Override @Deprecated @SuppressWarnings("resource") public String readLine() throws IOException { // Highly inefficient, but readLine() should never be used // when deserializing AMF3 data... return new DataInputStream(new InputStream() { @Override public int read() throws IOException { ensureAvailable(1); return buffer[position++]; } }).readLine(); } @Override public String readUTF() throws IOException { return DataInputStream.readUTF(this); } @Override public int read() throws IOException { ensureAvailable(1); return buffer[position++]; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { if (b == null) throw new NullPointerException(); if (off < 0 || len < 0 || len > b.length - off) throw new IndexOutOfBoundsException(); if (len == 0) return 0; ensureAvailable(1); int count = Math.min(size - position, len); System.arraycopy(buffer, position, b, off, count); position += count; return count; } @Override public long skip(long n) throws IOException { if (n <= 0) return 0; final int left = size - position; if (n <= left) { position += n; return n; } position = size; long total = left; while (total < n) { long count = in.skip(n - total); if (count <= 0) return total; total += count; } return total; } @Override public int available() throws IOException { return (size - position) + in.available(); } @Override public void close() throws IOException { position = size; in.close(); } }