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