/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- _error
- success
- accept
- writePropertyName
- writeObject
- writeString
- writeNumber
- writeBoolean
- writeUndefined
- writeNull
- writeData
- readXML
- readStrictArray
- readArray
- readObject
- readReference
- readDate
//
// Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
// 2011 Free Software Foundation, Inc
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "AMFConverter.h"
#include <map>
#include "SimpleBuffer.h"
#include "AMF.h"
#include "namedStrings.h"
#include "as_value.h"
#include "as_object.h"
#include "ObjectURI.h"
#include "VM.h"
#include "Date_as.h"
#include "XML_as.h"
#include "Array_as.h"
#include "Global_as.h"
#include "fn_call.h"
#include "as_function.h"
#include "PropertyList.h"
// Define this macro to make AMF parsing verbose
//#define GNASH_DEBUG_AMF_DESERIALIZE 1
// Define this macto to make AMF writing verbose
// #define GNASH_DEBUG_AMF_SERIALIZE 1
namespace gnash {
namespace amf {
namespace {
/// Class used to serialize properties of an object to a buffer
class ObjectSerializer : public PropertyVisitor
{
public:
ObjectSerializer(Writer& w, VM& vm)
:
_writer(w),
_st(vm.getStringTable()),
_error(false)
{}
bool success() const { return !_error; }
virtual bool accept(const ObjectURI& uri, const as_value& val) {
if (_error) return true;
// Tested with SharedObject and AMFPHP
if (val.is_function()) {
log_debug("AMF0: skip serialization of FUNCTION property");
return true;
}
const string_table::key key = getName(uri);
// Test conducted with AMFPHP:
// '__proto__' and 'constructor' members
// of an object don't get back from an 'echo-service'.
// Dunno if they are not serialized or just not sent back.
// A '__constructor__' member gets back, but only if
// not a function. Actually no function gets back.
if (key == NSV::PROP_uuPROTOuu || key == NSV::PROP_CONSTRUCTOR)
{
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(" skip serialization of specially-named property %s",
_st.value(key));
#endif
return true;
}
// write property name
const std::string& name = _st.value(key);
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(" serializing property %s", name);
#endif
_writer.writePropertyName(name);
if (!val.writeAMF0(_writer)) {
log_error("Problems serializing an object's member");
_error = true;
}
return true;
}
private:
Writer& _writer;
string_table& _st;
mutable bool _error;
};
}
bool
Writer::writePropertyName(const std::string& name)
{
writePlainString(_buf, name, STRING_AMF0);
return true;
}
bool
Writer::writeObject(as_object* obj)
{
assert(obj);
// This probably shouldn't happen.
if (obj->to_function()) return false;
OffsetTable::iterator it = _offsets.find(obj);
// Handle references first.
if (it != _offsets.end()) {
const size_t idx = it->second;
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing object (or function) "
"as reference to %d"), idx);
#endif
_buf.appendByte(REFERENCE_AMF0);
_buf.appendNetworkShort(idx);
return true;
}
// 1 for the first, etc...
const size_t idx = _offsets.size() + 1;
_offsets[obj] = idx;
/// Native objects are handled specially.
if (obj->relay()) {
Date_as* date;
if (isNativeType(obj, date)) {
double d = date->getTimeValue();
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing date object "
"with index %d and value %g"), idx, d);
#endif
_buf.appendByte(DATE_AMF0);
writePlainNumber(_buf, d);
// This should be timezone
boost::uint16_t tz = 0;
_buf.appendNetworkShort(tz);
return true;
}
/// XML is written like a long string (but with an XML marker).
XML_as* xml;
if (isNativeType(obj, xml)) {
_buf.appendByte(XML_OBJECT_AMF0);
std::ostringstream s;
xml->toString(s, true);
const std::string& xmlstr = s.str();
writePlainString(_buf, xmlstr, LONG_STRING_AMF0);
return true;
}
// Any native objects not explicitly handled are unsupported (this
// is just a guess).
_buf.appendByte(UNSUPPORTED_AMF0);
return true;
}
VM& vm = getVM(*obj);
// Arrays are handled specially.
if (obj->array()) {
const size_t len = arrayLength(*obj);
if (_strictArray) {
IsStrictArray s(vm);
// Check if any non-hidden properties are non-numeric.
obj->visitProperties<IsEnumerable>(s);
if (s.strict()) {
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing array of %d "
"elements as STRICT_ARRAY (index %d)"),
len, idx);
#endif
_buf.appendByte(STRICT_ARRAY_AMF0);
_buf.appendNetworkLong(len);
as_value elem;
for (size_t i = 0; i < len; ++i) {
elem = getMember(*obj,arrayKey(vm, i));
if (!elem.writeAMF0(*this)) {
log_error("Problems serializing strict array "
"member %d=%s", i, elem);
return false;
}
}
return true;
}
}
// A normal array.
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing array of %d "
"elements as ECMA_ARRAY (index %d) "),
len, idx);
#endif
_buf.appendByte(ECMA_ARRAY_AMF0);
_buf.appendNetworkLong(len);
}
else {
// It's a simple object
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing object (or function) "
"with index %d"), idx);
#endif
_buf.appendByte(OBJECT_AMF0);
}
ObjectSerializer props(*this, vm);
obj->visitProperties<IsEnumerable>(props);
if (!props.success()) {
log_error("Could not serialize object");
return false;
}
_buf.appendNetworkShort(0);
_buf.appendByte(OBJECT_END_AMF0);
return true;
}
bool
Writer::writeString(const std::string& str)
{
write(_buf, str);
return true;
}
bool
Writer::writeNumber(double d)
{
write(_buf, d);
return true;
}
bool
Writer::writeBoolean(bool b)
{
write(_buf, b);
return true;
}
bool
Writer::writeUndefined()
{
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing undefined"));
#endif
_buf.appendByte(UNDEFINED_AMF0);
return true;
}
bool
Writer::writeNull()
{
#ifdef GNASH_DEBUG_AMF_SERIALIZE
log_debug(_("amf: serializing null"));
#endif
_buf.appendByte(NULL_AMF0);
return true;
}
void
Writer::writeData(const boost::uint8_t* data, size_t length)
{
_buf.append(data, length);
}
bool
Reader::operator()(as_value& val, Type t)
{
// No more reads possible.
if (_pos == _end) {
return false;
}
// This may leave the read position at the _end of the buffer, but
// some types are complete with the type byte (null, undefined).
if (t == NOTYPE) {
t = static_cast<Type>(*_pos);
++_pos;
}
try {
switch (t) {
default:
log_error("Unknown AMF type %s! Cannot proceed", t);
// A fatal error, since we don't know how much to parse
return false;
// Simple types.
case BOOLEAN_AMF0:
val = readBoolean(_pos, _end);
return true;
case STRING_AMF0:
val = readString(_pos, _end);
return true;
case LONG_STRING_AMF0:
val = readLongString(_pos, _end);
return true;
case NUMBER_AMF0:
val = readNumber(_pos, _end);
return true;
case UNSUPPORTED_AMF0:
case UNDEFINED_AMF0:
val = as_value();
return true;
case NULL_AMF0:
val = static_cast<as_object*>(0);
return true;
// Object types need access to Global_as to create objects.
case REFERENCE_AMF0:
val = readReference();
return true;
case OBJECT_AMF0:
val = readObject();
return true;
case ECMA_ARRAY_AMF0:
val = readArray();
return true;
case STRICT_ARRAY_AMF0:
val = readStrictArray();
return true;
case DATE_AMF0:
val = readDate();
return true;
case XML_OBJECT_AMF0:
val = readXML();
return true;
}
}
catch (const AMFException& e) {
log_error("AMF parsing error: %s", e.what());
return false;
}
}
/// Construct an XML object.
//
/// Note that the pp seems not to call the constructor or parseXML, but
/// rather to create it magically. It could do this by calling an ASNative
/// function.
as_value
Reader::readXML()
{
as_value str = readLongString(_pos, _end);
as_function* ctor = getMember(_global, NSV::CLASS_XML).to_function();
as_value xml;
if (ctor) {
fn_call::Args args;
args += str;
VM& vm = getVM(_global);
xml = constructInstance(*ctor, as_environment(vm), args);
}
return xml;
}
as_value
Reader::readStrictArray()
{
if (_end - _pos < 4) {
throw AMFException("Read past _end of buffer for strict array length");
}
const boost::uint32_t li = readNetworkLong(_pos);
_pos += 4;
#ifdef GNASH_DEBUG_AMF_DESERIALIZE
log_debug("amf0 starting read of STRICT_ARRAY with %i elements", li);
#endif
as_object* array = _global.createArray();
_objectRefs.push_back(array);
as_value arrayElement;
for (size_t i = 0; i < li; ++i) {
// Recurse.
if (!operator()(arrayElement)) {
throw AMFException("Unable to read array elements");
}
callMethod(array, NSV::PROP_PUSH, arrayElement);
}
return as_value(array);
}
// TODO: this function is inconsistent about when it interrupts parsing
// if the AMF is truncated. If it doesn't interrupt, the next read will
// fail.
as_value
Reader::readArray()
{
if (_end - _pos < 4) {
throw AMFException("Read past _end of buffer for array length");
}
const boost::uint32_t li = readNetworkLong(_pos);
_pos += 4;
as_object* array = _global.createArray();
_objectRefs.push_back(array);
// the count specifies array size, so to have that even if none
// of the members are indexed
// if short, will be incremented everytime an indexed member is
// found
array->set_member(NSV::PROP_LENGTH, li);
#ifdef GNASH_DEBUG_AMF_DESERIALIZE
log_debug("amf0 starting read of ECMA_ARRAY with %i elements", li);
#endif
as_value objectElement;
VM& vm = getVM(_global);
for (;;) {
// It seems we don't mind about this situation, although it means
// the next read will fail.
if (_end - _pos < 2) {
log_error("MALFORMED AMF: premature _end of ECMA_ARRAY "
"block");
break;
}
const boost::uint16_t strlen = readNetworkShort(_pos);
_pos += 2;
// _end of ECMA_ARRAY is signalled by an empty string
// followed by an OBJECT_END_AMF0 (0x09) byte
if (!strlen) {
// expect an object terminator here
if (*_pos != OBJECT_END_AMF0) {
log_error("MALFORMED AMF: empty member name not "
"followed by OBJECT_END_AMF0 byte");
}
++_pos;
break;
}
// Throw exception instead?
if (_end - _pos < strlen) {
log_error("MALFORMED AMF: premature _end of ECMA_ARRAY "
"block");
break;
}
const std::string name(reinterpret_cast<const char*>(_pos), strlen);
#ifdef GNASH_DEBUG_AMF_DESERIALIZE
log_debug("amf0 ECMA_ARRAY prop name is %s", name);
#endif
_pos += strlen;
// Recurse to read element.
if (!operator()(objectElement)) {
throw AMFException("Unable to read array element");
}
array->set_member(getURI(vm, name), objectElement);
}
return as_value(array);
}
as_value
Reader::readObject()
{
VM& vm = getVM(_global);
as_object* obj = createObject(_global);
#ifdef GNASH_DEBUG_AMF_DESERIALIZE
log_debug("amf0 starting read of OBJECT");
#endif
_objectRefs.push_back(obj);
as_value tmp;
std::string keyString;
for (;;) {
if (!operator()(tmp, STRING_AMF0)) {
throw AMFException("Could not read object property name");
}
keyString = tmp.to_string();
if (keyString.empty()) {
if (_pos < _end) {
// AMF0 has a redundant "object _end" byte
++_pos;
}
else {
// What is the point?
log_error("AMF buffer terminated just before "
"object _end byte. continuing anyway.");
}
return as_value(obj);
}
if (!operator()(tmp)) {
throw AMFException("Unable to read object member");
}
obj->set_member(getURI(vm, keyString), tmp);
}
}
as_value
Reader::readReference()
{
if (_end - _pos < 2) {
throw AMFException("Read past _end of buffer for reference index");
}
const boost::uint16_t si = readNetworkShort(_pos);
_pos += 2;
#ifdef GNASH_DEBUG_AMF_DESERIALIZE
log_debug("readAMF0: reference #%d", si);
#endif
if (si < 1 || si > _objectRefs.size()) {
log_error("readAMF0: invalid reference to object %d (%d known "
"objects)", si, _objectRefs.size());
throw AMFException("Reference to invalid object reference");
}
return as_value(_objectRefs[si - 1]);
}
as_value
Reader::readDate()
{
const double d = readNumber(_pos, _end);
#ifdef GNASH_DEBUG_AMF_DESERIALIZE
log_debug("amf0 read date: %e", dub);
#endif
as_function* ctor = getMember(_global, NSV::CLASS_DATE).to_function();
VM& vm = getVM(_global);
as_value date;
if (ctor) {
fn_call::Args args;
args += d;
date = constructInstance(*ctor, as_environment(vm), args);
if (_end - _pos < 2) {
throw AMFException("premature _end of input reading "
"timezone from Date type");
}
const boost::uint16_t tz = readNetworkShort(_pos);
if (tz != 0) {
log_error(_("Date type encoded timezone info %1%, even though "
"this field should not be used."), tz);
}
_pos += 2;
}
return date;
}
} // namespace amf
} // namespace gnash