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    
021    package org.granite.messaging.amf.io;
022    
023    import java.io.DataInputStream;
024    import java.io.EOFException;
025    import java.io.Externalizable;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.ObjectInput;
029    import java.io.UTFDataFormatException;
030    import java.util.ArrayList;
031    import java.util.Date;
032    import java.util.HashMap;
033    import java.util.List;
034    import java.util.Map;
035    
036    import org.granite.context.GraniteContext;
037    import org.granite.logging.Logger;
038    import org.granite.messaging.amf.AMF3Constants;
039    import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor;
040    import org.granite.messaging.amf.io.util.DefaultActionScriptClassDescriptor;
041    import org.granite.messaging.amf.io.util.externalizer.Externalizer;
042    import org.granite.messaging.amf.io.util.instantiator.AbstractInstantiator;
043    import org.granite.util.TypeUtil;
044    import org.granite.util.XMLUtil;
045    import org.granite.util.XMLUtilFactory;
046    import org.w3c.dom.Document;
047    
048    /**
049     * @author Franck WOLFF
050     */
051    public 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().getAliasRegistry().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 object
574                result = (String)getFromStoredObjects(type >> 1);
575            else {
576                byte[] bytes = readBytes(type >> 1);
577                result = new String(bytes, "UTF-8");
578                addToStoredObjects(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    }