001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.messaging.amf.io;
023
024import java.io.DataInputStream;
025import java.io.EOFException;
026import java.io.Externalizable;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.ObjectInput;
030import java.io.UTFDataFormatException;
031import java.util.ArrayList;
032import java.util.Date;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036
037import org.granite.context.GraniteContext;
038import org.granite.logging.Logger;
039import org.granite.messaging.amf.AMF3Constants;
040import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor;
041import org.granite.messaging.amf.io.util.DefaultActionScriptClassDescriptor;
042import org.granite.messaging.amf.io.util.externalizer.Externalizer;
043import org.granite.messaging.amf.io.util.instantiator.AbstractInstantiator;
044import org.granite.util.TypeUtil;
045import org.granite.util.XMLUtil;
046import org.granite.util.XMLUtilFactory;
047import org.w3c.dom.Document;
048
049/**
050 * @author Franck WOLFF
051 */
052public class AMF3Deserializer extends DataInputStream implements ObjectInput, AMF3Constants {
053
054    ///////////////////////////////////////////////////////////////////////////
055    // Fields.
056
057    protected static final Logger log = Logger.getLogger(AMF3Deserializer.class);
058    protected static final Logger logMore = Logger.getLogger(AMF3Deserializer.class.getName() + "_MORE");
059
060    protected final List<String> storedStrings = new ArrayList<String>();
061    protected final List<Object> storedObjects = new ArrayList<Object>();
062    protected final List<ActionScriptClassDescriptor> storedClassDescriptors = new ArrayList<ActionScriptClassDescriptor>();
063
064    protected final GraniteContext context = GraniteContext.getCurrentInstance();
065
066    protected final AMF3DeserializerSecurizer securizer = context.getGraniteConfig().getAmf3DeserializerSecurizer();
067
068    protected final XMLUtil xmlUtil = XMLUtilFactory.getXMLUtil();
069
070    protected final boolean debug;
071    protected final boolean debugMore;
072
073    ///////////////////////////////////////////////////////////////////////////
074    // Constructor.
075
076    public AMF3Deserializer(InputStream in) {
077        super(in);
078
079        debug = log.isDebugEnabled();
080        debugMore = logMore.isDebugEnabled();
081
082        if (debugMore) logMore.debug("new AMF3Deserializer(in=%s)", in);
083    }
084
085    ///////////////////////////////////////////////////////////////////////////
086    // ObjectInput implementation.
087
088    public Object readObject() throws IOException {
089        if (debugMore) logMore.debug("readObject()...");
090
091        try {
092                int type = readAMF3Integer();
093                return readObject(type);
094        }
095        catch (IOException e) {
096                throw e;
097        }
098        catch (Exception e) {
099                throw new AMF3SerializationException(e);
100        }
101    }
102
103    ///////////////////////////////////////////////////////////////////////////
104    // AMF3 deserialization.
105
106    protected Object readObject(int type) throws IOException {
107
108        if (debugMore) logMore.debug("readObject(type=0x%02X)", type);
109
110        switch (type) {
111        case AMF3_UNDEFINED: // 0x00;
112        case AMF3_NULL: // 0x01;
113            return null;
114        case AMF3_BOOLEAN_FALSE: // 0x02;
115            return Boolean.FALSE;
116        case AMF3_BOOLEAN_TRUE: // 0x03;
117            return Boolean.TRUE;
118        case AMF3_INTEGER: // 0x04;
119            return Integer.valueOf(readAMF3Integer());
120        case AMF3_NUMBER: // 0x05;
121            return readAMF3Double();
122        case AMF3_STRING: // 0x06;
123            return readAMF3String();
124        case AMF3_XML: // 0x07;
125            return readAMF3Xml();
126        case AMF3_DATE: // 0x08;
127            return readAMF3Date();
128        case AMF3_ARRAY: // 0x09;
129            return readAMF3Array();
130        case AMF3_OBJECT: // 0x0A;
131            return readAMF3Object();
132        case AMF3_XMLSTRING: // 0x0B;
133            return readAMF3XmlString();
134        case AMF3_BYTEARRAY: // 0x0C;
135            return readAMF3ByteArray();
136        case AMF3_VECTOR_INT: // 0x0D;
137                return readAMF3VectorInt();
138        case AMF3_VECTOR_UINT: // 0x0E;
139                return readAMF3VectorUint();
140        case AMF3_VECTOR_NUMBER: // 0x0F;
141                return readAMF3VectorNumber();
142        case AMF3_VECTOR_OBJECT: // 0x10;
143                return readAMF3VectorObject();
144        case AMF3_DICTIONARY: // 0x11;
145                return readAMF3Dictionary();
146
147        default:
148            throw new IllegalArgumentException("Unknown type: " + type);
149        }
150    }
151
152    protected int readAMF3Integer() throws IOException {
153        int result = 0;
154
155        int n = 0;
156        int b = readUnsignedByte();
157        while ((b & 0x80) != 0 && n < 3) {
158            result <<= 7;
159            result |= (b & 0x7f);
160            b = readUnsignedByte();
161            n++;
162        }
163        if (n < 3) {
164            result <<= 7;
165            result |= b;
166        } else {
167            result <<= 8;
168            result |= b;
169            if ((result & 0x10000000) != 0)
170                result |= 0xe0000000;
171        }
172
173        if (debugMore) logMore.debug("readAMF3Integer() -> %d", result);
174
175        return result;
176    }
177
178    protected Double readAMF3Double() throws IOException {
179        double d = readDouble();
180        Double result = (Double.isNaN(d) ? null : Double.valueOf(d));
181
182        if (debugMore) logMore.debug("readAMF3Double() -> %f", result);
183
184        return result;
185    }
186
187    protected String readAMF3String() throws IOException {
188        String result = null;
189
190        if (debugMore) logMore.debug("readAMF3String()...");
191
192        int type = readAMF3Integer();
193        if ((type & 0x01) == 0) // stored string
194            result = getFromStoredStrings(type >> 1);
195        else {
196            int length = type >> 1;
197            if (debugMore) logMore.debug("readAMF3String() - length=%d", length);
198
199            if (length > 0) {
200
201                byte[] utfBytes = new byte[length];
202                char[] utfChars = new char[length];
203
204                readFully(utfBytes);
205
206                int c, c2, c3, iBytes = 0, iChars = 0;
207                while (iBytes < length) {
208                    c = utfBytes[iBytes++] & 0xFF;
209                    if (c <= 0x7F)
210                        utfChars[iChars++] = (char)c;
211                    else {
212                        switch (c >> 4) {
213                        case 12: case 13:
214                            c2 = utfBytes[iBytes++];
215                            if ((c2 & 0xC0) != 0x80)
216                                throw new UTFDataFormatException("Malformed input around byte " + (iBytes-2));
217                            utfChars[iChars++] = (char)(((c & 0x1F) << 6) | (c2 & 0x3F));
218                            break;
219                        case 14:
220                            c2 = utfBytes[iBytes++];
221                            c3 = utfBytes[iBytes++];
222                            if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80))
223                                throw new UTFDataFormatException("Malformed input around byte " + (iBytes-3));
224                            utfChars[iChars++] = (char)(((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
225                            break;
226                        default:
227                            throw new UTFDataFormatException("Malformed input around byte " + (iBytes-1));
228                        }
229                    }
230                }
231                result = new String(utfChars, 0, iChars);
232
233                if (debugMore) logMore.debug("readAMF3String() - result=%s", result);
234
235                addToStoredStrings(result);
236            } else
237                result = "";
238        }
239
240        if (debugMore) logMore.debug("readAMF3String() -> %s", result);
241
242        return result;
243    }
244
245
246    protected Date readAMF3Date() throws IOException {
247        Date result = null;
248
249        int type = readAMF3Integer();
250        if ((type & 0x01) == 0) // stored Date
251            result = (Date)getFromStoredObjects(type >> 1);
252        else {
253            result = new Date((long)readDouble());
254            addToStoredObjects(result);
255        }
256
257        if (debugMore) logMore.debug("readAMF3Date() -> %s", result);
258
259        return result;
260    }
261
262    protected Object readAMF3Array() throws IOException {
263        Object result = null;
264
265        int type = readAMF3Integer();
266        if ((type & 0x01) == 0) // stored array.
267            result = getFromStoredObjects(type >> 1);
268        else {
269            final int size = type >> 1;
270
271            String key = readAMF3String();
272            if (key.length() == 0) {
273                Object[] objects = new Object[size];
274                addToStoredObjects(objects);
275
276                for (int i = 0; i < size; i++)
277                    objects[i] = readObject();
278
279                result = objects;
280            }
281            else {
282                Map<Object, Object> map = new HashMap<Object, Object>();
283                addToStoredObjects(map);
284
285                while(key.length() > 0) {
286                    map.put(key, readObject());
287                    key = readAMF3String();
288                }
289                for (int i = 0; i < size; i++)
290                    map.put(Integer.valueOf(i), readObject());
291
292                result = map;
293            }
294        }
295
296        if (debugMore) logMore.debug("readAMF3Array() -> %s", result);
297
298        return result;
299    }
300
301        protected int[] readAMF3VectorInt() throws IOException {
302        int[] vector = null;
303
304        int type = readAMF3Integer();
305        if ((type & 0x01) == 0) // stored vector.
306                vector = (int[])getFromStoredObjects(type >> 1);
307        else {
308                final int length = type >> 1;
309            vector = new int[length];
310            
311            addToStoredObjects(vector);
312            
313            @SuppressWarnings("unused")
314                        boolean fixedLength = readAMF3Integer() == 1;
315            
316            for (int i = 0; i < length; i++)
317                vector[i] = readInt();
318        }
319        
320        if (debugMore) logMore.debug("readAMF3VectorInt() -> %s", vector);
321
322        return vector;
323    }
324
325        protected long[] readAMF3VectorUint() throws IOException {
326                long[] vector = null;
327
328        int type = readAMF3Integer();
329        if ((type & 0x01) == 0) // stored vector.
330                vector = (long[])getFromStoredObjects(type >> 1);
331        else {
332                final int length = type >> 1;
333            vector = new long[length];
334            
335            addToStoredObjects(vector);
336            
337            @SuppressWarnings("unused")
338                        boolean fixedLength = readAMF3Integer() == 1;
339            
340            for (int i = 0; i < length; i++)
341                vector[i] = (readInt() & 0xffffffffL);
342        }
343        
344        if (debugMore) logMore.debug("readAMF3VectorUInt() -> %s", vector);
345
346        return vector;
347    }
348
349        protected double[] readAMF3VectorNumber() throws IOException {
350                double[] vector = null;
351
352        int type = readAMF3Integer();
353        if ((type & 0x01) == 0) // stored vector.
354                vector = (double[])getFromStoredObjects(type >> 1);
355        else {
356                final int length = type >> 1;
357            vector = new double[length];
358            
359            addToStoredObjects(vector);
360
361            @SuppressWarnings("unused")
362                        boolean fixedLength = readAMF3Integer() == 1;
363            
364            for (int i = 0; i < length; i++)
365                vector[i] = readDouble();
366        }
367        
368        if (debugMore) logMore.debug("readAMF3VectorDouble() -> %s", vector);
369
370        return vector;
371    }
372
373        @SuppressWarnings("unchecked")
374        protected List<Object> readAMF3VectorObject() throws IOException {
375        List<Object> vector = null;
376
377        int type = readAMF3Integer();
378        if ((type & 0x01) == 0) // stored vector.
379                vector = (List<Object>)getFromStoredObjects(type >> 1);
380        else {
381                final int length = type >> 1;
382            vector = new ArrayList<Object>(length);
383            
384            addToStoredObjects(vector);
385            
386            @SuppressWarnings("unused")
387                        boolean fixedLength = readAMF3Integer() == 1;
388            @SuppressWarnings("unused")
389                        String componentClassName = readAMF3String();
390            
391            for (int i = 0; i < length; i++)
392                vector.add(readObject());
393        }
394        
395        if (debugMore) logMore.debug("readAMF3VectorObject() -> %s", vector);
396
397        return vector;
398    }
399    
400    @SuppressWarnings("unchecked")
401        protected Map<Object, Object> readAMF3Dictionary() throws IOException {
402        Map<Object, Object> dictionary = null;
403
404        int type = readAMF3Integer();
405        if ((type & 0x01) == 0) // stored dictionary.
406                dictionary = (Map<Object, Object>)getFromStoredObjects(type >> 1);
407        else {
408                final int length = type >> 1;
409                
410                // AS3 Dictionary doesn't have a strict Java equivalent: use an HashMap, which
411                // could (unlikely) lead to duplicated keys collision...
412                dictionary = new HashMap<Object, Object>(length);
413            
414            addToStoredObjects(dictionary);
415            
416            @SuppressWarnings("unused")
417                        boolean weakKeys = readAMF3Integer() == 1;
418            
419            for (int i = 0; i < length; i++) {
420                Object key = readObject();
421                Object value = readObject();
422                dictionary.put(key, value);
423            }
424        }
425        
426        if (debugMore) logMore.debug("readAMF3Dictionary() -> %s", dictionary);
427
428        return dictionary;
429    }
430
431    protected Object readAMF3Object() throws IOException {
432        if (debug) log.debug("readAMF3Object()...");
433
434        Object result = null;
435
436        int type = readAMF3Integer();
437        if (debug) log.debug("readAMF3Object() - type=0x%02X", type);
438
439        if ((type & 0x01) == 0) // stored object.
440            result = getFromStoredObjects(type >> 1);
441        else {
442            boolean inlineClassDef = (((type >> 1) & 0x01) != 0);
443            if (debug) log.debug("readAMF3Object() - inlineClassDef=%b", inlineClassDef);
444
445            // read class decriptor.
446            ActionScriptClassDescriptor desc = null;
447            if (inlineClassDef) {
448                int propertiesCount = type >> 4;
449                if (debug) log.debug("readAMF3Object() - propertiesCount=%d", propertiesCount);
450
451                byte encoding = (byte)((type >> 2) & 0x03);
452                if (debug) log.debug("readAMF3Object() - encoding=%d", encoding);
453
454                String alias = readAMF3String();
455                String className = context.getGraniteConfig().getAliasRegistry().getTypeForAlias(alias);
456                if (debug) log.debug("readAMF3Object() - alias=%, className=%s", alias, className);
457                
458                // Check if the class is allowed to be instantiated.
459                if (securizer != null && !securizer.allowInstantiation(className))
460                        throw new SecurityException("Illegal attempt to instantiate class: " + className + ", securizer: " + securizer.getClass());
461
462                // try to find out custom AS3 class descriptor
463                Class<? extends ActionScriptClassDescriptor> descriptorType = null;
464                if (!"".equals(className))
465                    descriptorType = context.getGraniteConfig().getActionScriptDescriptor(className);
466                if (debug) log.debug("readAMF3Object() - descriptorType=%s", descriptorType);
467
468                if (descriptorType != null) {
469                    // instantiate descriptor
470                    Class<?>[] argsDef = new Class[]{String.class, byte.class};
471                    Object[] argsVal = new Object[]{className, Byte.valueOf(encoding)};
472                    try {
473                        desc = TypeUtil.newInstance(descriptorType, argsDef, argsVal);
474                    } catch (Exception e) {
475                        throw new RuntimeException("Could not instantiate AS descriptor: " + descriptorType, e);
476                    }
477                }
478                if (desc == null)
479                    desc = new DefaultActionScriptClassDescriptor(className, encoding);
480                addToStoredClassDescriptors(desc);
481
482                if (debug) log.debug("readAMF3Object() - defining %d properties...", propertiesCount);
483                for (int i = 0; i < propertiesCount; i++) {
484                    String name = readAMF3String();
485                    if (debug) log.debug("readAMF3Object() - defining property name=%s", name);
486                    desc.defineProperty(name);
487                }
488            } else
489                desc = getFromStoredClassDescriptors(type >> 2);
490
491            if (debug) log.debug("readAMF3Object() - actionScriptClassDescriptor=%s", desc);
492
493            int objectEncoding = desc.getEncoding();
494
495            // Find externalizer and create Java instance.
496            Externalizer externalizer = desc.getExternalizer();
497            if (externalizer != null) {
498                try {
499                    result = externalizer.newInstance(desc.getType(), this);
500                } catch (Exception e) {
501                    throw new RuntimeException("Could not instantiate type: " + desc.getType(), e);
502                }
503            } else
504                result = desc.newJavaInstance();
505
506            int index = addToStoredObjects(result);
507            
508            // Entity externalizers (eg. OpenJPA) may return null values for non-null AS3 objects (ie. proxies).
509            if (result == null) {
510                if (debug) log.debug("readAMF3Object() - Added null object to stored objects for actionScriptClassDescriptor=%s", desc);
511                return null;
512            }
513
514            // read object content...
515            if ((objectEncoding & 0x01) != 0) {
516                // externalizer.
517                if (externalizer != null) {
518                    if (debug) log.debug("readAMF3Object() - using externalizer=%s", externalizer);
519                    try {
520                        externalizer.readExternal(result, this);
521                    } catch (IOException e) {
522                        throw e;
523                    } catch (Exception e) {
524                        throw new RuntimeException("Could not read externalized object: " + result, e);
525                    }
526                }
527                // legacy externalizable.
528                else {
529                        if (debug) log.debug("readAMF3Object() - legacy Externalizable=%s", result.getClass());
530                        if (!(result instanceof Externalizable)) {
531                                throw new RuntimeException(
532                                        "The ActionScript3 class bound to " + result.getClass().getName() +
533                                        " (ie: [RemoteClass(alias=\"" + result.getClass().getName() + "\")])" +
534                                        " implements flash.utils.IExternalizable but this Java class neither" +
535                                        " implements java.io.Externalizable nor is in the scope of a configured" +
536                                        " externalizer (please fix your granite-config.xml)"
537                                );
538                        }
539                    try {
540                        ((Externalizable)result).readExternal(this);
541                    } catch (IOException e) {
542                        throw e;
543                    } catch (Exception e) {
544                        throw new RuntimeException("Could not read externalizable object: " + result, e);
545                    }
546                }
547            }
548            else {
549                // defined values...
550                if (desc.getPropertiesCount() > 0) {
551                    if (debug) log.debug("readAMF3Object() - reading defined properties...");
552                    for (int i = 0; i < desc.getPropertiesCount(); i++) {
553                        byte vType = readByte();
554                        Object value = readObject(vType);
555                        if (debug) log.debug("readAMF3Object() - setting defined property: %s=%s", desc.getPropertyName(i), value);
556                        desc.setPropertyValue(i, result, value);
557                    }
558                }
559
560                // dynamic values...
561                if (objectEncoding == 0x02) {
562                    if (debug) log.debug("readAMF3Object() - reading dynamic properties...");
563                    while (true) {
564                        String name = readAMF3String();
565                        if (name.length() == 0)
566                            break;
567                        byte vType = readByte();
568                        Object value = readObject(vType);
569                        if (debug) log.debug("readAMF3Object() - setting dynamic property: %s=%s", name, value);
570                        desc.setPropertyValue(name, result, value);
571                    }
572                }
573            }
574
575            if (result instanceof AbstractInstantiator<?>) {
576                if (debug) log.debug("readAMF3Object() - resolving instantiator...");
577                try {
578                    result = ((AbstractInstantiator<?>)result).resolve();
579                } catch (Exception e) {
580                    throw new RuntimeException("Could not instantiate object: " + result, e);
581                }
582                setStoredObject(index, result);
583            }
584        }
585
586        if (debug) log.debug("readAMF3Object() -> %s", result);
587
588        return result;
589    }
590
591    protected Document readAMF3Xml() throws IOException {
592        String xml = readAMF3XmlString();
593        Document result = xmlUtil.buildDocument(xml);
594
595        if (debugMore) logMore.debug("readAMF3Xml() -> %s", result);
596
597        return result;
598    }
599
600    protected String readAMF3XmlString() throws IOException {
601        String result = null;
602
603        int type = readAMF3Integer();
604        if ((type & 0x01) == 0) // stored object
605            result = (String)getFromStoredObjects(type >> 1);
606        else {
607            byte[] bytes = readBytes(type >> 1);
608            result = new String(bytes, "UTF-8");
609            addToStoredObjects(result);
610        }
611
612        if (debugMore) logMore.debug("readAMF3XmlString() -> %s", result);
613
614        return result;
615    }
616
617    protected byte[] readAMF3ByteArray() throws IOException {
618        byte[] result = null;
619
620        int type = readAMF3Integer();
621        if ((type & 0x01) == 0) // stored object.
622            result = (byte[])getFromStoredObjects(type >> 1);
623        else {
624            result = readBytes(type >> 1);
625            addToStoredObjects(result);
626        }
627
628        if (debugMore) logMore.debug("readAMF3ByteArray() -> %s", result);
629
630        return result;
631    }
632
633    ///////////////////////////////////////////////////////////////////////////
634    // Cached objects methods.
635
636    protected void addToStoredStrings(String s) {
637        if (debug) log.debug("addToStoredStrings(s=%s) at index=%d", s, storedStrings.size());
638        storedStrings.add(s);
639    }
640
641    protected String getFromStoredStrings(int index) {
642        if (debug) log.debug("getFromStoredStrings(index=%d)", index);
643        String s = storedStrings.get(index);
644        if (debug) log.debug("getFromStoredStrings() -> %s", s);
645        return s;
646    }
647
648    protected int addToStoredObjects(Object o) {
649        int index = storedObjects.size();
650        if (debug) log.debug("addToStoredObjects(o=%s) at index=%d", o, index);
651        storedObjects.add(o);
652        return index;
653    }
654
655    protected void setStoredObject(int index, Object o) {
656        if (debug) log.debug("setStoredObject(index=%d, o=%s)", index, o);
657        storedObjects.set(index, o);
658    }
659
660    protected Object getFromStoredObjects(int index) {
661        if (debug) log.debug("getFromStoredObjects(index=%d)", index);
662        Object o = storedObjects.get(index);
663        if (debug) log.debug("getFromStoredObjects() -> %s", o);
664        return o;
665    }
666
667    protected void addToStoredClassDescriptors(ActionScriptClassDescriptor desc) {
668        if (debug) log.debug("addToStoredClassDescriptors(desc=%s) at index=%d", desc, storedClassDescriptors.size());
669        storedClassDescriptors.add(desc);
670    }
671
672    protected ActionScriptClassDescriptor getFromStoredClassDescriptors(int index) {
673        if (debug) log.debug("getFromStoredClassDescriptors(index=%d)", index);
674        ActionScriptClassDescriptor desc = storedClassDescriptors.get(index);
675        if (debug) log.debug("getFromStoredClassDescriptors() -> %s", desc);
676        return desc;
677    }
678
679    ///////////////////////////////////////////////////////////////////////////
680    // Utilities.
681
682    protected byte[] readBytes(int count) throws IOException {
683        byte[] bytes = new byte[count];
684        //readFully(bytes);
685        
686        int b = -1;
687        for (int i = 0; i < count; i++) {
688                b = in.read();
689                if (b == -1)
690                        throw new EOFException();
691                bytes[i] = (byte)b;
692        }
693        return bytes;
694    }
695}