// // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 // 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 #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 std::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(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(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 std::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(*_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(nullptr); 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 std::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 std::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 std::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(_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 std::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 std::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