Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.red5.io.amf3.Output Maven / Gradle / Ivy
/*
* RED5 Open Source Media Server - https://github.com/Red5/ Copyright 2006-2016 by respective authors (see below). All rights reserved. Licensed 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 org.red5.io.amf3;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.beanutils.BeanMap;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.annotations.Anonymous;
import org.red5.compatibility.flex.messaging.io.ObjectProxy;
import org.red5.io.amf.AMF;
import org.red5.io.object.RecordSet;
import org.red5.io.object.Serializer;
import org.red5.io.object.UnsignedInt;
import org.red5.io.utils.HexDump;
import org.red5.io.utils.XMLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
/**
* AMF3 output writer
*
* @see org.red5.io.amf3.AMF3
* @see org.red5.io.amf3.Input
* @author The Red5 Project
* @author Joachim Bauch ([email protected] )
* @author Harald Radi ([email protected] )
*/
public class Output extends org.red5.io.amf.Output implements org.red5.io.object.Output {
protected static Logger log = LoggerFactory.getLogger(Output.class);
/**
* Set to a value above 0 to disable writing of the AMF3 object tag.
*/
private int amf3_mode;
/**
* List of strings already written.
* */
private ConcurrentMap stringReferences;
/**
* Constructor of AMF3 output.
*
* @param buf
* instance of IoBuffer
* @see IoBuffer
*/
public Output(IoBuffer buf) {
super(buf);
amf3_mode = 0;
stringReferences = new ConcurrentHashMap(8, 0.9f, 2);
}
/**
* Force using AMF3 everywhere
*/
public void enforceAMF3() {
amf3_mode++;
}
/**
* Provide access to raw data.
*
* @return IoBuffer
*/
protected IoBuffer getBuffer() {
return buf;
}
protected void writeAMF3() {
if (amf3_mode == 0) {
buf.put(AMF.TYPE_AMF3_OBJECT);
}
}
/** {@inheritDoc} */
@Override
public void writeBoolean(Boolean bol) {
writeAMF3();
buf.put(bol ? AMF3.TYPE_BOOLEAN_TRUE : AMF3.TYPE_BOOLEAN_FALSE);
}
/** {@inheritDoc} */
@Override
public void writeNull() {
writeAMF3();
buf.put(AMF3.TYPE_NULL);
}
protected void putInteger(long value) {
if ((value >= -268435456) && (value <= 268435455)) {
value &= 0x1FFFFFFF;
}
if (value < 128) {
buf.put((byte) value);
} else if (value < 16384) {
buf.put((byte) (((value >> 7) & 0x7F) | 0x80));
buf.put((byte) (value & 0x7F));
} else if (value < 2097152) {
buf.put((byte) (((value >> 14) & 0x7F) | 0x80));
buf.put((byte) (((value >> 7) & 0x7F) | 0x80));
buf.put((byte) (value & 0x7F));
} else if (value < 1073741824) {
buf.put((byte) (((value >> 22) & 0x7F) | 0x80));
buf.put((byte) (((value >> 15) & 0x7F) | 0x80));
buf.put((byte) (((value >> 8) & 0x7F) | 0x80));
buf.put((byte) (value & 0xFF));
} else {
log.error("Integer out of range: {}", value);
}
}
protected static byte[] encodeString(String string) {
//I've removed the caching library(ehcache) because ehcache is not up to date and old versions has critical vulnerabilities
//newer versions(3.10.8) has still java.xml dependencies java 11, we use java 17 - mekya
ByteBuffer buf = AMF.CHARSET.encode(string);
byte[] encoded = new byte[buf.limit()];
buf.get(encoded);
return encoded;
}
protected void putString(String str, byte[] encoded) {
final int len = encoded.length;
Integer pos = stringReferences.get(str);
if (pos != null) {
// Reference to existing string
putInteger(pos << 1);
return;
}
putInteger(len << 1 | 1);
buf.put(encoded);
stringReferences.put(str, stringReferences.size());
}
/** {@inheritDoc} */
@Override
public void putString(String string) {
// empty string
if ("".equals(string)) {
putInteger(1);
return;
}
final byte[] encoded = encodeString(string);
putString(string, encoded);
}
/** {@inheritDoc} */
@Override
public void writeNumber(Number num) {
writeAMF3();
if (num.longValue() < AMF3.MIN_INTEGER_VALUE || num.longValue() > AMF3.MAX_INTEGER_VALUE) {
// out of range for integer encoding
buf.put(AMF3.TYPE_NUMBER);
buf.putDouble(num.doubleValue());
} else if (num instanceof Long || num instanceof Integer || num instanceof Short || num instanceof Byte) {
buf.put(AMF3.TYPE_INTEGER);
putInteger(num.longValue());
} else {
buf.put(AMF3.TYPE_NUMBER);
buf.putDouble(num.doubleValue());
}
}
/** {@inheritDoc} */
@Override
public void writeString(String string) {
writeAMF3();
buf.put(AMF3.TYPE_STRING);
if ("".equals(string)) {
putInteger(1);
} else {
final byte[] encoded = encodeString(string);
putString(string, encoded);
}
}
/** {@inheritDoc} */
@Override
public void writeDate(Date date) {
writeAMF3();
buf.put(AMF3.TYPE_DATE);
if (hasReference(date)) {
putInteger(getReferenceId(date) << 1);
return;
}
storeReference(date);
putInteger(1);
buf.putDouble(date.getTime());
}
/** {@inheritDoc} */
@Override
public void writeArray(Collection> array) {
writeAMF3();
buf.put(AMF3.TYPE_ARRAY);
if (hasReference(array)) {
putInteger(getReferenceId(array) << 1);
return;
}
storeReference(array);
amf3_mode += 1;
int count = array.size();
putInteger(count << 1 | 1);
putString("");
for (Object item : array) {
Serializer.serialize(this, item);
}
amf3_mode -= 1;
}
/** {@inheritDoc} */
@Override
public void writeArray(Object[] array) {
writeAMF3();
buf.put(AMF3.TYPE_ARRAY);
if (hasReference(array)) {
putInteger(getReferenceId(array) << 1);
return;
}
storeReference(array);
amf3_mode += 1;
int count = array.length;
putInteger(count << 1 | 1);
putString("");
for (Object item : array) {
Serializer.serialize(this, item);
}
amf3_mode -= 1;
}
/** {@inheritDoc} */
@Override
public void writeArray(Object array) {
Class> componentType = array.getClass().getComponentType();
if (componentType.equals(Character.TYPE)) {
// write the char[] as a string
writeString(new String((char[]) array));
} else if (componentType.equals(Byte.TYPE)) {
writePrimitiveByteArray((byte[]) array);
} else {
writePrimitiveArrayFallback(array);
}
}
/**
* Use the specialized BYTEARRAY type.
*/
private void writePrimitiveByteArray(byte[] bytes) {
writeAMF3();
this.buf.put(AMF3.TYPE_BYTEARRAY);
if (hasReference(bytes)) {
putInteger(getReferenceId(bytes) << 1);
return;
}
storeReference(bytes);
int length = bytes.length;
putInteger(length << 1 | 0x1);
this.buf.put(bytes);
}
/**
* Use the general ARRAY type, writing the primitive array as an array of objects (the boxed primitives) instead.
*/
private void writePrimitiveArrayFallback(Object array) {
writeAMF3();
buf.put(AMF3.TYPE_ARRAY);
if (hasReference(array)) {
putInteger(getReferenceId(array) << 1);
return;
}
storeReference(array);
amf3_mode += 1;
int count = Array.getLength(array);
putInteger(count << 1 | 1);
putString("");
for (int i = 0; i < count; i++) {
Serializer.serialize(this, Array.get(array, i));
}
amf3_mode -= 1;
}
/** {@inheritDoc} */
@Override
public void writeMap(Map map) {
writeAMF3();
buf.put(AMF3.TYPE_ARRAY);
if (hasReference(map)) {
putInteger(getReferenceId(map) << 1);
return;
}
storeReference(map);
// Search number of starting integer keys
int count = 0;
for (int i = 0; i < map.size(); i++) {
try {
if (!map.containsKey(i)) {
break;
}
} catch (ClassCastException err) {
// Map has non-number keys.
break;
}
count++;
}
amf3_mode += 1;
if (count == map.size()) {
// All integer keys starting from zero: serialize as regular array
putInteger(count << 1 | 1);
putString("");
for (int i = 0; i < count; i++) {
Serializer.serialize(this, map.get(i));
}
amf3_mode -= 1;
return;
}
putInteger(count << 1 | 1);
// Serialize key-value pairs first
for (Map.Entry entry : map.entrySet()) {
Object key = entry.getKey();
if ((key instanceof Number) && !(key instanceof Float) && !(key instanceof Double) && ((Number) key).longValue() >= 0 && ((Number) key).longValue() < count) {
// Entry will be serialized later
continue;
}
putString(key.toString());
Serializer.serialize(this, entry.getValue());
}
putString("");
// Now serialize integer keys starting from zero
for (int i = 0; i < count; i++) {
Serializer.serialize(this, map.get(i));
}
amf3_mode -= 1;
}
/** {@inheritDoc} */
@Override
public void writeMap(Collection> array) {
writeAMF3();
buf.put(AMF3.TYPE_ARRAY);
if (hasReference(array)) {
putInteger(getReferenceId(array) << 1);
return;
}
storeReference(array);
// TODO: we could optimize this by storing the first integer
// keys after the key-value pairs
amf3_mode += 1;
putInteger(1);
int idx = 0;
for (Object item : array) {
if (item != null) {
putString(String.valueOf(idx));
Serializer.serialize(this, item);
}
idx++;
}
amf3_mode -= 1;
putString("");
}
/** {@inheritDoc} */
@Override
protected void writeArbitraryObject(Object object) {
log.debug("writeArbitraryObject: {}", object);
Class> objectClass = object.getClass();
// If we need to serialize class information...
if (!objectClass.isAnnotationPresent(Anonymous.class)) {
putString(Serializer.getClassName(objectClass));
} else {
putString("");
}
// Store key/value pairs
amf3_mode += 1;
// Iterate thru fields of an object to build "name-value" map from it
for (Field field : objectClass.getFields()) {
String fieldName = field.getName();
log.debug("Field: {} class: {}", field, objectClass);
// Check if the Field corresponding to the getter/setter pair is transient
if (!Serializer.serializeField(fieldName, field, null)) {
continue;
}
Object value;
try {
// Get field value
value = field.get(object);
} catch (IllegalAccessException err) {
// Swallow on private and protected properties access exception
continue;
}
// Write out prop name
putString(fieldName);
// Write out
Serializer.serialize(this, field, null, object, value);
}
amf3_mode -= 1;
// Write out end of object marker
putString("");
}
/** {@inheritDoc} */
@SuppressWarnings({ "rawtypes" })
@Override
public void writeObject(Object object) {
log.debug("writeObject: {} {}", object.getClass().getName(), object);
writeAMF3();
buf.put(AMF3.TYPE_OBJECT);
if (hasReference(object)) {
log.debug("Object reference found");
putInteger(getReferenceId(object) << 1);
return;
}
log.debug("Storing object reference");
storeReference(object);
if (object instanceof IExternalizable) {
log.debug("Object is IExternalizable");
// the object knows how to serialize itself
int type = 1 << 1 | 1;
if (object instanceof ObjectProxy) {
type |= AMF3.TYPE_OBJECT_PROXY << 2;
} else {
type |= AMF3.TYPE_OBJECT_EXTERNALIZABLE << 2;
}
putInteger(type);
putString(Serializer.getClassName(object.getClass()));
amf3_mode += 1;
((IExternalizable) object).writeExternal(new DataOutput(this));
amf3_mode -= 1;
return;
} else {
log.debug("Object is NOT IExternalizable");
}
// we have an inline class that is not a reference, store the properties using key/value pairs
int type = AMF3.TYPE_OBJECT_VALUE << 2 | 1 << 1 | 1;
putInteger(type);
// create new map out of bean properties
BeanMap beanMap = new BeanMap(object);
// set of bean attributes
Set set = beanMap.keySet();
if ((set.size() == 0) || (set.size() == 1 && beanMap.containsKey("class"))) {
// beanMap is empty or can only access "class" attribute, skip it
writeArbitraryObject(object);
return;
}
// write out either start of object marker for class name or "empty" start of object marker
Class> objectClass = object.getClass();
if (!objectClass.isAnnotationPresent(Anonymous.class)) {
log.debug("Object is annotated as Anonymous");
putString(Serializer.getClassName(object.getClass()));
} else {
putString("");
}
// store key/value pairs
amf3_mode += 1;
for (Object key : set) {
String fieldName = key.toString();
log.debug("Field name: {} class: {}", fieldName, objectClass);
Field field = getField(objectClass, fieldName);
Method getter = getGetter(objectClass, beanMap, fieldName);
// check if the Field corresponding to the getter/setter pair is transient
if (!Serializer.serializeField(fieldName, field, getter)) {
continue;
}
putString(fieldName);
Serializer.serialize(this, field, getter, object, beanMap.get(key));
}
amf3_mode -= 1;
// end of object marker
putString("");
}
/** {@inheritDoc} */
@Override
public void writeObject(Map map) {
log.debug("writeObject: {}", map);
writeAMF3();
buf.put(AMF3.TYPE_OBJECT);
if (hasReference(map)) {
putInteger(getReferenceId(map) << 1);
return;
}
storeReference(map);
// we have an inline class that is not a reference, store the properties using key/value pairs
int type = AMF3.TYPE_OBJECT_VALUE << 2 | 1 << 1 | 1;
putInteger(type);
// no classname
putString("");
// store key/value pairs
amf3_mode += 1;
for (Map.Entry entry : map.entrySet()) {
putString(entry.getKey().toString());
Serializer.serialize(this, entry.getValue());
}
amf3_mode -= 1;
// end of object marker
putString("");
}
/** {@inheritDoc} */
@Override
public void writeRecordSet(RecordSet recordset) {
writeString("Not implemented.");
}
/** {@inheritDoc} */
@Override
public void writeXML(Document xml) {
writeAMF3();
buf.put(AMF3.TYPE_XML);
if (hasReference(xml)) {
putInteger(getReferenceId(xml) << 1);
return;
}
final byte[] encoded = encodeString(XMLUtils.docToString(xml));
putInteger(encoded.length << 1 | 1);
buf.put(encoded);
storeReference(xml);
}
/** {@inheritDoc} */
@Override
public void writeByteArray(ByteArray array) {
writeAMF3();
buf.put(AMF3.TYPE_BYTEARRAY);
if (hasReference(array)) {
putInteger(getReferenceId(array) << 1);
return;
}
storeReference(array);
IoBuffer data = array.getData();
putInteger(data.limit() << 1 | 1);
byte[] tmp = new byte[data.limit()];
int old = data.position();
try {
data.position(0);
data.get(tmp);
buf.put(tmp);
} finally {
data.position(old);
}
}
/**
* Write a Vector<int>.
*
* @param vector
* vector
*/
@Override
public void writeVectorInt(Vector vector) {
log.debug("writeVectorInt: {}", vector);
writeAMF3();
buf.put(AMF3.TYPE_VECTOR_INT);
if (hasReference(vector)) {
putInteger(getReferenceId(vector) << 1);
return;
}
storeReference(vector);
putInteger(vector.size() << 1 | 1);
buf.put((byte) 0x00);
for (Integer v : vector) {
buf.putInt(v);
}
// debug
if (log.isDebugEnabled()) {
int pos = buf.position();
buf.position(0);
StringBuilder sb = new StringBuilder();
HexDump.dumpHex(sb, buf.array());
log.debug("\n{}", sb);
buf.position(pos);
}
}
/**
* Write a Vector<uint>.
*
* @param vector
* vector
*/
@Override
public void writeVectorUInt(Vector vector) {
log.debug("writeVectorUInt: {}", vector);
writeAMF3();
buf.put(AMF3.TYPE_VECTOR_UINT);
if (hasReference(vector)) {
putInteger(getReferenceId(vector) << 1);
return;
}
storeReference(vector);
putInteger(vector.size() << 1 | 1);
buf.put((byte) 0x00);
for (Long v : vector) {
// update this class to implement valueOf like Long.valueOf
UnsignedInt uint = new UnsignedInt(v);
byte[] arr = uint.getBytes();
buf.put(arr);
}
}
/**
* Write a Vector<Number>.
*
* @param vector
* vector
*/
@Override
public void writeVectorNumber(Vector vector) {
log.debug("writeVectorNumber: {}", vector);
buf.put(AMF3.TYPE_VECTOR_NUMBER);
if (hasReference(vector)) {
putInteger(getReferenceId(vector) << 1);
return;
}
storeReference(vector);
putInteger(vector.size() << 1 | 1);
putInteger(0);
buf.put((byte) 0x00);
for (Double v : vector) {
buf.putDouble(v);
}
}
/**
* Write a Vector<Object>.
*
* @param vector
* vector
*/
@Override
public void writeVectorObject(Vector vector) {
log.debug("writeVectorObject: {}", vector);
buf.put(AMF3.TYPE_VECTOR_OBJECT);
if (hasReference(vector)) {
putInteger(getReferenceId(vector) << 1);
return;
}
storeReference(vector);
putInteger(vector.size() << 1 | 1);
putInteger(0);
buf.put((byte) 0x01);
for (Object v : vector) {
Serializer.serialize(this, v);
}
}
}