flex.messaging.io.amfx.AmfxInput Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.io.amfx;
import java.io.ByteArrayInputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.xml.sax.Attributes;
import flex.messaging.MessageException;
import flex.messaging.io.AbstractProxy;
import flex.messaging.io.ArrayCollection;
import flex.messaging.io.BeanProxy;
import flex.messaging.io.ClassAliasRegistry;
import flex.messaging.io.PropertyProxy;
import flex.messaging.io.PropertyProxyRegistry;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.SerializationException;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.AmfTrace;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.MessageHeader;
import flex.messaging.util.ClassUtil;
import flex.messaging.util.Hex;
import flex.messaging.util.XMLUtil;
/**
* Context for AMFX specific SAX handler.Contains start and end tag handlers for each of
* the XML elements that occur in an AMFX request. The AmfxMessageDeserializer enforces
* a naming convention for these handlers of xyz_start for the start handler and xyz_end
* for the end handler of element xyz.
*
* Note that this context MUST be reset if reused between AMFX packet parsings.
*
* @see AmfxMessageDeserializer
* @see AmfxOutput
*/
public class AmfxInput
{
/**
* This is the initial capacity that will be used for AMF arrays that have
* length greater than 1024.
*/
public static final int INITIAL_ARRAY_CAPACITY = 1024;
private SerializationContext context;
private BeanProxy beanproxy = new BeanProxy();
private final ArrayList objectTable;
private final ArrayList stringTable;
private final ArrayList traitsTable;
private StringBuffer text;
private ActionMessage message;
private MessageHeader currentHeader;
private MessageBody currentBody;
private Stack objectStack;
private Stack proxyStack;
private Stack arrayPropertyStack;
private Stack ecmaArrayIndexStack;
private Stack strictArrayIndexStack;
private Stack dictionaryStack;
private Stack traitsStack;
private boolean isStringReference;
private boolean isTraitProperty;
/*
* DEBUG LOGGING
*/
protected boolean isDebug;
protected AmfTrace trace;
/**
* Constructor.
* Construct an AmfxInput by passing in a SerialziationContext
object
*
* @param context the SerialziationContext
object
*/
public AmfxInput(SerializationContext context)
{
this.context = context;
stringTable = new ArrayList(64);
objectTable = new ArrayList(64);
traitsTable = new ArrayList(10);
objectStack = new Stack();
proxyStack = new Stack();
arrayPropertyStack = new Stack();
dictionaryStack = new Stack();
strictArrayIndexStack = new Stack();
ecmaArrayIndexStack = new Stack();
traitsStack = new Stack();
text = new StringBuffer(32);
}
/**
* Reset the AmfxInput object.
*/
public void reset()
{
stringTable.clear();
objectTable.clear();
traitsTable.clear();
objectStack.clear();
proxyStack.clear();
arrayPropertyStack.clear();
dictionaryStack.clear();
traitsStack.clear();
currentBody = null;
currentHeader = null;
TypeMarshallingContext marshallingContext = TypeMarshallingContext.getTypeMarshallingContext();
marshallingContext.reset();
}
/**
* Set Debug trace.
*
* @param trace current AmfTrace
setting
*/
public void setDebugTrace(AmfTrace trace)
{
this.trace = trace;
isDebug = this.trace != null;
}
/**
* Set Action Message.
*
* @param msg current ActionMessage
*/
public void setActionMessage(ActionMessage msg)
{
message = msg;
}
/**
* Read object from the AmfxInput object.
*
* @return currently return null, not supported
* @throws IOException when reading the object has the IOException
*/
public Object readObject() throws IOException
{
return null;
}
/**
* Append a string to text.
* XML Considerations
*
* @param s the String to append
*/
public void text(String s)
{
text.append(s);
}
//
// AMFX Message Structure
//
/**
* Start the amfx process by setting the ActionMessage version.
*
* @param attributes current Attributes
*/
public void start_amfx(Attributes attributes)
{
String ver = attributes.getValue("ver");
int version = ActionMessage.CURRENT_VERSION;
if (ver != null)
{
try
{
version = Integer.parseInt(ver);
}
catch (NumberFormatException ex)
{
throw new MessageException("Unknown version: " + ver);
}
}
if (isDebug)
trace.version(version);
message.setVersion(version);
}
/**
* End the Amfx process.
*
*/
public void end_amfx()
{
}
/**
* Start the process of message headers.
*
* @param attributes current Attributes
*/
public void start_header(Attributes attributes)
{
if (currentHeader != null || currentBody != null)
throw new MessageException("Unexpected header tag.");
currentHeader = new MessageHeader();
String name = attributes.getValue("name");
currentHeader.setName(name);
String mu = attributes.getValue("mustUnderstand");
boolean mustUnderstand = false;
if (mu != null)
{
mustUnderstand = Boolean.valueOf(mu).booleanValue();
currentHeader.setMustUnderstand(mustUnderstand);
}
if (isDebug)
trace.startHeader(name, mustUnderstand, message.getHeaderCount());
}
/**
* End process of message headers.
*
*/
public void end_header()
{
message.addHeader(currentHeader);
currentHeader = null;
if (isDebug)
trace.endHeader();
}
/**
* Start process of the message body.
*
* @param attributes current Attributes
*/
public void start_body(Attributes attributes)
{
if (currentBody != null || currentHeader != null)
throw new MessageException("Unexpected body tag.");
currentBody = new MessageBody();
if (isDebug)
trace.startMessage("", "", message.getBodyCount());
}
/**
* End process of the message body.
*
*/
public void end_body()
{
message.addBody(currentBody);
currentBody = null;
if (isDebug)
trace.endMessage();
}
//
// ActionScript Types
//
/**
* Start process of the Action Script type Array.
*
* @param attributes current Attributes
*/
public void start_array(Attributes attributes)
{
int length = 10;
String len = attributes.getValue("length");
if (len != null)
{
try
{
len = len.trim();
length = Integer.parseInt(len);
if (length < 0)
throw new NumberFormatException();
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid array length: " + len);
}
}
String ecma = attributes.getValue("ecma");
boolean isECMA = "true".equalsIgnoreCase(ecma);
Object array;
boolean useListTemporarily = false;
if (isECMA)
{
array = ClassUtil.createDefaultInstance(HashMap.class, null, true /*validate*/);
}
else
{
// Don't instantiate List/Array right away with the supplied size if it is more than
// INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered. This at least
// requires the user to pass in the actual objects for the List/Array to grow beyond.
if (context.legacyCollection || length > INITIAL_ARRAY_CAPACITY)
{
useListTemporarily = !context.legacyCollection;
ClassUtil.validateCreation(ArrayList.class);
int initialCapacity = length < INITIAL_ARRAY_CAPACITY? length : INITIAL_ARRAY_CAPACITY;
array = new ArrayList(initialCapacity);
}
else
{
ClassUtil.validateCreation(Object[].class);
array = new Object[length];
}
}
array = setValue(array);
ecmaArrayIndexStack.push(new int[]{0});
strictArrayIndexStack.push(new int[]{0});
objectTable.add(array);
// Don't add the array to the object stack if the List is being used temporarily
// for the length tampering detection. In that case, setValue method will add
// an ObjectPropertyValueTuple to the object stack instead.
if (!useListTemporarily)
objectStack.push(array);
proxyStack.push(null);
if (isECMA)
{
if (isDebug)
trace.startECMAArray(objectTable.size() - 1);
}
else
{
if (isDebug)
trace.startAMFArray(objectTable.size() - 1);
}
}
/**
* End process of Action Script type Array.
*
*/
public void end_array()
{
try
{
Object obj = objectStack.pop();
if (obj instanceof ObjectPropertyValueTuple)
{
// Means List was being used temporarily to guard against array length tampering.
// Convert back to Object array and set it on the parent object using the proxy
// and property saved in the tuple.
ObjectPropertyValueTuple tuple = (ObjectPropertyValueTuple)obj;
int objectId = objectTable.indexOf(tuple.value);
Object newValue = ((ArrayList)tuple.value).toArray();
objectTable.set(objectId, newValue);
tuple.proxy.setValue(tuple.obj, tuple.property, newValue);
}
proxyStack.pop();
ecmaArrayIndexStack.pop();
strictArrayIndexStack.pop();
}
catch (EmptyStackException ex)
{
throw new MessageException("Unexpected end of array");
}
if (isDebug)
trace.endAMFArray();
}
public void start_dictionary(Attributes attributes)
{
int length = 10;
String len = attributes.getValue("length");
if (len != null)
{
try
{
len = len.trim();
length = Integer.parseInt(len);
if (length < 0)
throw new NumberFormatException();
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid array length: " + len);
}
}
Hashtable dictionary = (Hashtable)ClassUtil.createDefaultInstance(Hashtable.class, null, true /*validate*/);
setValue(dictionary);
objectTable.add(dictionary);
objectStack.push(dictionary);
proxyStack.push(null);
if (isDebug)
trace.startAMFDictionary(objectTable.size() - 1);
}
public void end_dictionary()
{
try
{
objectStack.pop();
proxyStack.pop();
}
catch (EmptyStackException ex)
{
throw new MessageException("Unexpected end of dictionary");
}
if (isDebug)
trace.endAMFDictionary();
}
// 010F0A
/**
* Start process of the Action Script type ByteArray.
*
* @param attributes current Attributes
*/
public void start_bytearray(Attributes attributes)
{
text.delete(0, text.length());
}
/**
* End process of the Action Script type ByteArray.
*
*/
public void end_bytearray()
{
ClassUtil.validateCreation(byte[].class);
String bs = text.toString().trim();
Hex.Decoder decoder = new Hex.Decoder();
decoder.decode(bs);
byte[] value = decoder.drain();
setValue(value);
if (isDebug)
trace.startByteArray(objectTable.size() - 1, bs.length());
}
/**
* Start process of the Action Script type Date.
*
* @param attributes current Attributes
*/
public void start_date(Attributes attributes)
{
text.delete(0, text.length());
}
/**
* End process of the Action Script type Date.
*
*/
public void end_date()
{
ClassUtil.validateCreation(Date.class);
String d = text.toString().trim();
try
{
long l = Long.parseLong(d);
Date date = new Date(l);
setValue(date);
objectTable.add(date); //Dates can be sent by reference
if (isDebug)
trace.write(date);
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid date: " + d);
}
}
/**
* Start process of the Action Script type Double.
*
* @param attributes current Attributes
*/
public void start_double(Attributes attributes)
{
text.delete(0, text.length());
}
/**
* End process of the Action Script type Double.
*
*/
public void end_double()
{
ClassUtil.validateCreation(Double.class);
String ds = text.toString().trim();
try
{
Double d = Double.valueOf(ds);
setValue(d);
if (isDebug)
trace.write(d.doubleValue());
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid double: " + ds);
}
}
/**
* Start process of the Action Script type False.
*
* @param attributes current Attributes
*/
public void start_false(Attributes attributes)
{
ClassUtil.validateCreation(Boolean.class);
setValue(Boolean.FALSE);
if (isDebug)
trace.write(false);
}
/**
* Start process of the Action Script type False.
*
*/
public void end_false()
{
}
/**
* Start process of Item.
*
* @param attributes current Attributes
*/
public void start_item(Attributes attributes)
{
String name = attributes.getValue("name");
if (name != null)
{
name = name.trim();
if (name.length() <= 0)
throw new MessageException("Array item names cannot be the empty string.");
char c = name.charAt(0);
if (!(Character.isLetterOrDigit(c) || c == '_'))
throw new MessageException("Invalid item name: " + name +
". Array item names must start with a letter, a digit or the underscore '_' character.");
}
else
{
throw new MessageException("Array item must have a name attribute.");
}
//Check that we're expecting an ECMA array
Object o = objectStackPeek();
if (!(o instanceof Map))
{
throw new MessageException("Unexpected array item name: " + name +
". Please set the ecma attribute to 'true'.");
}
arrayPropertyStack.push(name);
}
/**
* End process of Item.
*
*/
public void end_item()
{
arrayPropertyStack.pop();
}
/**
* Start process of the Action Script type Int.
*
* @param attributes current Attributes
*/
public void start_int(Attributes attributes)
{
text.delete(0, text.length());
}
/**
* End process of the Action Script type Int.
*
*/
public void end_int()
{
ClassUtil.validateCreation(Integer.class);
String is = text.toString().trim();
try
{
Integer i = Integer.valueOf(is);
setValue(i);
if (isDebug)
trace.write(i.intValue());
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid int: " + is);
}
}
/**
* Start process of the Action Script type NULL.
*
* @param attributes current Attributes
*/
public void start_null(Attributes attributes)
{
setValue(null);
if (isDebug)
trace.writeNull();
}
/**
* Start process of the Action Script type NULL.
*
*/
public void end_null()
{
}
//
/**
* End process of type Object.
*
*/
public void end_object()
{
if (!traitsStack.empty())
traitsStack.pop();
if (!objectStack.empty())
{
Object obj = objectStack.pop();
PropertyProxy proxy = (PropertyProxy) proxyStack.pop();
Object newObj = proxy == null ? obj : proxy.instanceComplete(obj);
if (newObj != obj)
{
int i;
// Find the index in the list of the old objct and replace it with
// the new one.
for (i = 0; i < objectTable.size(); i++)
if (objectTable.get(i) == obj)
break;
if (i != objectTable.size())
objectTable.set(i, newObj);
obj = newObj;
}
setValue(obj);
}
else
{
throw new MessageException("Unexpected end of object.");
}
if (isDebug)
trace.endAMFObject();
}
/**
* Start process of reference.
*
* @param attributes current Attributes
*/
public void start_ref(Attributes attributes)
{
String id = attributes.getValue("id");
if (id != null)
{
try
{
int i = Integer.parseInt(id);
Object o = objectTable.get(i);
setValue(o);
if (isDebug)
trace.writeRef(i);
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid object reference: " + id);
}
catch (IndexOutOfBoundsException ex)
{
throw new MessageException("Unknown object reference: " + id);
}
}
else
{
throw new MessageException("Unknown object reference: " + id);
}
}
/**
* End process of reference.
*
*/
public void end_ref()
{
}
/**
* Start process of the Action Script type String.
*
* @param attributes current Attributes
*/
public void start_string(Attributes attributes)
{
String id = attributes.getValue("id");
if (id != null)
{
isStringReference = true;
try
{
int i = Integer.parseInt(id);
String s = (String)stringTable.get(i);
if (isTraitProperty)
{
TraitsContext traitsContext = (TraitsContext)traitsStack.peek();
traitsContext.add(s);
}
else
{
ClassUtil.validateCreation(String.class);
setValue(s);
}
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid string reference: " + id);
}
catch (IndexOutOfBoundsException ex)
{
throw new MessageException("Unknown string reference: " + id);
}
}
else
{
text.delete(0, text.length());
isStringReference = false;
}
}
/**
* End process of the Action Script type String.
*
*/
public void end_string()
{
if (!isStringReference)
{
String s = text.toString();
// Special case the empty string as it isn't counted as in
// the string reference table
if (s.length() > 0)
{
// Traits won't contain CDATA
if (!isTraitProperty)
s = unescapeCloseCDATA(s);
stringTable.add(s);
}
if (isTraitProperty)
{
TraitsContext traitsContext = (TraitsContext)traitsStack.peek();
traitsContext.add(s);
}
else
{
ClassUtil.validateCreation(String.class);
setValue(s);
if (isDebug)
trace.writeString(s);
}
}
}
/**
* Start process of Traits.
*
* @param attributes current Attributes
*/
public void start_traits(Attributes attributes)
{
if (!objectStack.empty())
{
List traitsList = new ArrayList();
TraitsContext traitsContext = new TraitsContext(traitsList);
traitsStack.push(traitsContext);
String id = attributes.getValue("id");
if (id != null)
{
try
{
int i = Integer.parseInt(id);
List l = (List)traitsTable.get(i);
Iterator it = l.iterator();
while (it.hasNext())
{
String prop = (String)it.next();
traitsList.add(prop);
}
}
catch (NumberFormatException ex)
{
throw new MessageException("Invalid traits reference: " + id);
}
catch (IndexOutOfBoundsException ex)
{
throw new MessageException("Unknown traits reference: " + id);
}
}
else
{
boolean externalizable = false;
String ext = attributes.getValue("externalizable");
if (ext != null)
{
externalizable = "true".equals(ext.trim());
}
Object obj = objectStackPeek();
if (externalizable && !(obj instanceof Externalizable))
{
//Class '{className}' must implement java.io.Externalizable to receive client IExternalizable instances.
SerializationException ex = new SerializationException();
ex.setMessage(10305, new Object[] {obj.getClass().getName()});
throw ex;
}
traitsTable.add(traitsList);
}
isTraitProperty = true;
}
else
{
throw new MessageException("Unexpected traits");
}
}
/**
* End process of Traits.
*
*/
public void end_traits()
{
isTraitProperty = false;
}
/**
* Start process of the Action Script type True.
*
* @param attributes current Attributes
*/
public void start_true(Attributes attributes)
{
ClassUtil.validateCreation(Boolean.class);
setValue(Boolean.TRUE);
if (isDebug)
trace.write(true);
}
/**
* Start process of the Action Script type True.
*
*/
public void end_true()
{
}
/**
* Start process of the Action Script type undefined.
*
* @param attributes current Attributes
*/
public void start_undefined(Attributes attributes)
{
setValue(null);
if (isDebug)
trace.writeUndefined();
}
/**
* End process of the Action Script type undefined.
*
*/
public void end_undefined()
{
}
/**
* Start process of XML.
*
* @param attributes current Attributes
*/
public void start_xml(Attributes attributes)
{
text.delete(0, text.length());
}
/**
* End process of XML.
*
*/
public void end_xml()
{
String xml = text.toString();
xml = unescapeCloseCDATA(xml);
// Validation performed in XMLUtil#stringToDocument.
Object value = XMLUtil.stringToDocument(xml, !(context.legacyXMLNamespaces));
setValue(value);
}
private String unescapeCloseCDATA(String s)
{
//Only check if string could possibly have an encoded closing for a CDATA "]]>"
if (s.length() > 5 && s.indexOf("]]>") != -1)
{
s = s.replaceAll("]]>", "]]>");
}
return s;
}
private Object setValue(Object value)
{
if (objectStack.empty())
{
if (currentHeader != null)
currentHeader.setData(value);
else if (currentBody != null)
currentBody.setData(value);
else
throw new MessageException("Unexpected value: " + value);
return value;
}
// ActionScript Data
Object obj = objectStackPeek();
//
© 2015 - 2024 Weber Informatics LLC | Privacy Policy