use the VERSION instead of master in the revno.h generated from a src tarball
[gnash.git] / libcore / AMFConverter.cpp
blob704d37fda6297b784cfe35dfe702965bcabee202
1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
3 // Foundation, Inc
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 #include "AMFConverter.h"
21 #include <map>
23 #include "SimpleBuffer.h"
24 #include "AMF.h"
25 #include "namedStrings.h"
26 #include "as_value.h"
27 #include "as_object.h"
28 #include "ObjectURI.h"
29 #include "VM.h"
30 #include "Date_as.h"
31 #include "XML_as.h"
32 #include "Array_as.h"
33 #include "Global_as.h"
34 #include "fn_call.h"
35 #include "as_function.h"
36 #include "PropertyList.h"
38 // Define this macro to make AMF parsing verbose
39 //#define GNASH_DEBUG_AMF_DESERIALIZE 1
41 // Define this macto to make AMF writing verbose
42 // #define GNASH_DEBUG_AMF_SERIALIZE 1
44 namespace gnash {
45 namespace amf {
47 namespace {
49 /// Class used to serialize properties of an object to a buffer
50 class ObjectSerializer : public PropertyVisitor
53 public:
54 ObjectSerializer(Writer& w, VM& vm)
56 _writer(w),
57 _st(vm.getStringTable()),
58 _error(false)
61 bool success() const { return !_error; }
63 virtual bool accept(const ObjectURI& uri, const as_value& val) {
65 if (_error) return true;
67 // Tested with SharedObject and AMFPHP
68 if (val.is_function()) {
69 log_debug("AMF0: skip serialization of FUNCTION property");
70 return true;
73 const string_table::key key = getName(uri);
75 // Test conducted with AMFPHP:
76 // '__proto__' and 'constructor' members
77 // of an object don't get back from an 'echo-service'.
78 // Dunno if they are not serialized or just not sent back.
79 // A '__constructor__' member gets back, but only if
80 // not a function. Actually no function gets back.
81 if (key == NSV::PROP_uuPROTOuu || key == NSV::PROP_CONSTRUCTOR)
83 #ifdef GNASH_DEBUG_AMF_SERIALIZE
84 log_debug(" skip serialization of specially-named property %s",
85 _st.value(key));
86 #endif
87 return true;
90 // write property name
91 const std::string& name = _st.value(key);
93 #ifdef GNASH_DEBUG_AMF_SERIALIZE
94 log_debug(" serializing property %s", name);
95 #endif
96 _writer.writePropertyName(name);
97 if (!val.writeAMF0(_writer)) {
98 log_error("Problems serializing an object's member");
99 _error = true;
101 return true;
104 private:
106 Writer& _writer;
107 string_table& _st;
108 mutable bool _error;
114 bool
115 Writer::writePropertyName(const std::string& name)
117 writePlainString(_buf, name, STRING_AMF0);
118 return true;
121 bool
122 Writer::writeObject(as_object* obj)
124 assert(obj);
126 // This probably shouldn't happen.
127 if (obj->to_function()) return false;
129 OffsetTable::iterator it = _offsets.find(obj);
131 // Handle references first.
132 if (it != _offsets.end()) {
133 const size_t idx = it->second;
134 #ifdef GNASH_DEBUG_AMF_SERIALIZE
135 log_debug(_("amf: serializing object (or function) "
136 "as reference to %d"), idx);
137 #endif
138 _buf.appendByte(REFERENCE_AMF0);
139 _buf.appendNetworkShort(idx);
140 return true;
143 // 1 for the first, etc...
144 const size_t idx = _offsets.size() + 1;
145 _offsets[obj] = idx;
147 /// Native objects are handled specially.
148 if (obj->relay()) {
150 Date_as* date;
151 if (isNativeType(obj, date)) {
153 double d = date->getTimeValue();
154 #ifdef GNASH_DEBUG_AMF_SERIALIZE
155 log_debug(_("amf: serializing date object "
156 "with index %d and value %g"), idx, d);
157 #endif
158 _buf.appendByte(DATE_AMF0);
159 writePlainNumber(_buf, d);
161 // This should be timezone
162 boost::uint16_t tz = 0;
163 _buf.appendNetworkShort(tz);
165 return true;
168 /// XML is written like a long string (but with an XML marker).
169 XML_as* xml;
170 if (isNativeType(obj, xml)) {
171 _buf.appendByte(XML_OBJECT_AMF0);
172 std::ostringstream s;
173 xml->toString(s, true);
175 const std::string& xmlstr = s.str();
176 writePlainString(_buf, xmlstr, LONG_STRING_AMF0);
178 return true;
181 // Any native objects not explicitly handled are unsupported (this
182 // is just a guess).
183 _buf.appendByte(UNSUPPORTED_AMF0);
184 return true;
187 VM& vm = getVM(*obj);
189 // Arrays are handled specially.
190 if (obj->array()) {
192 const size_t len = arrayLength(*obj);
193 if (_strictArray) {
194 IsStrictArray s(vm);
195 // Check if any non-hidden properties are non-numeric.
196 obj->visitProperties<IsEnumerable>(s);
198 if (s.strict()) {
200 #ifdef GNASH_DEBUG_AMF_SERIALIZE
201 log_debug(_("amf: serializing array of %d "
202 "elements as STRICT_ARRAY (index %d)"),
203 len, idx);
204 #endif
205 _buf.appendByte(STRICT_ARRAY_AMF0);
206 _buf.appendNetworkLong(len);
208 as_value elem;
209 for (size_t i = 0; i < len; ++i) {
210 elem = getMember(*obj,arrayKey(vm, i));
211 if (!elem.writeAMF0(*this)) {
212 log_error("Problems serializing strict array "
213 "member %d=%s", i, elem);
214 return false;
217 return true;
221 // A normal array.
222 #ifdef GNASH_DEBUG_AMF_SERIALIZE
223 log_debug(_("amf: serializing array of %d "
224 "elements as ECMA_ARRAY (index %d) "),
225 len, idx);
226 #endif
227 _buf.appendByte(ECMA_ARRAY_AMF0);
228 _buf.appendNetworkLong(len);
230 else {
231 // It's a simple object
232 #ifdef GNASH_DEBUG_AMF_SERIALIZE
233 log_debug(_("amf: serializing object (or function) "
234 "with index %d"), idx);
235 #endif
236 _buf.appendByte(OBJECT_AMF0);
239 ObjectSerializer props(*this, vm);
240 obj->visitProperties<IsEnumerable>(props);
241 if (!props.success()) {
242 log_error("Could not serialize object");
243 return false;
245 _buf.appendNetworkShort(0);
246 _buf.appendByte(OBJECT_END_AMF0);
247 return true;
250 bool
251 Writer::writeString(const std::string& str)
253 write(_buf, str);
254 return true;
257 bool
258 Writer::writeNumber(double d)
260 write(_buf, d);
261 return true;
264 bool
265 Writer::writeBoolean(bool b)
267 write(_buf, b);
268 return true;
272 bool
273 Writer::writeUndefined()
275 #ifdef GNASH_DEBUG_AMF_SERIALIZE
276 log_debug(_("amf: serializing undefined"));
277 #endif
278 _buf.appendByte(UNDEFINED_AMF0);
279 return true;
282 bool
283 Writer::writeNull()
285 #ifdef GNASH_DEBUG_AMF_SERIALIZE
286 log_debug(_("amf: serializing null"));
287 #endif
288 _buf.appendByte(NULL_AMF0);
289 return true;
292 void
293 Writer::writeData(const boost::uint8_t* data, size_t length)
295 _buf.append(data, length);
298 bool
299 Reader::operator()(as_value& val, Type t)
302 // No more reads possible.
303 if (_pos == _end) {
304 return false;
307 // This may leave the read position at the _end of the buffer, but
308 // some types are complete with the type byte (null, undefined).
309 if (t == NOTYPE) {
310 t = static_cast<Type>(*_pos);
311 ++_pos;
314 try {
316 switch (t) {
318 default:
319 log_error("Unknown AMF type %s! Cannot proceed", t);
320 // A fatal error, since we don't know how much to parse
321 return false;
323 // Simple types.
324 case BOOLEAN_AMF0:
325 val = readBoolean(_pos, _end);
326 return true;
328 case STRING_AMF0:
329 val = readString(_pos, _end);
330 return true;
332 case LONG_STRING_AMF0:
333 val = readLongString(_pos, _end);
334 return true;
336 case NUMBER_AMF0:
337 val = readNumber(_pos, _end);
338 return true;
340 case UNSUPPORTED_AMF0:
341 case UNDEFINED_AMF0:
342 val = as_value();
343 return true;
345 case NULL_AMF0:
346 val = static_cast<as_object*>(0);
347 return true;
349 // Object types need access to Global_as to create objects.
350 case REFERENCE_AMF0:
351 val = readReference();
352 return true;
354 case OBJECT_AMF0:
355 val = readObject();
356 return true;
358 case ECMA_ARRAY_AMF0:
359 val = readArray();
360 return true;
362 case STRICT_ARRAY_AMF0:
363 val = readStrictArray();
364 return true;
366 case DATE_AMF0:
367 val = readDate();
368 return true;
370 case XML_OBJECT_AMF0:
371 val = readXML();
372 return true;
375 catch (const AMFException& e) {
376 log_error("AMF parsing error: %s", e.what());
377 return false;
382 /// Construct an XML object.
384 /// Note that the pp seems not to call the constructor or parseXML, but
385 /// rather to create it magically. It could do this by calling an ASNative
386 /// function.
387 as_value
388 Reader::readXML()
390 as_value str = readLongString(_pos, _end);
391 as_function* ctor = getMember(_global, NSV::CLASS_XML).to_function();
393 as_value xml;
394 if (ctor) {
395 fn_call::Args args;
396 args += str;
397 VM& vm = getVM(_global);
398 xml = constructInstance(*ctor, as_environment(vm), args);
400 return xml;
403 as_value
404 Reader::readStrictArray()
406 if (_end - _pos < 4) {
407 throw AMFException("Read past _end of buffer for strict array length");
410 const boost::uint32_t li = readNetworkLong(_pos);
411 _pos += 4;
413 #ifdef GNASH_DEBUG_AMF_DESERIALIZE
414 log_debug("amf0 starting read of STRICT_ARRAY with %i elements", li);
415 #endif
417 as_object* array = _global.createArray();
418 _objectRefs.push_back(array);
420 as_value arrayElement;
421 for (size_t i = 0; i < li; ++i) {
423 // Recurse.
424 if (!operator()(arrayElement)) {
425 throw AMFException("Unable to read array elements");
428 callMethod(array, NSV::PROP_PUSH, arrayElement);
431 return as_value(array);
434 // TODO: this function is inconsistent about when it interrupts parsing
435 // if the AMF is truncated. If it doesn't interrupt, the next read will
436 // fail.
437 as_value
438 Reader::readArray()
441 if (_end - _pos < 4) {
442 throw AMFException("Read past _end of buffer for array length");
445 const boost::uint32_t li = readNetworkLong(_pos);
446 _pos += 4;
448 as_object* array = _global.createArray();
449 _objectRefs.push_back(array);
451 // the count specifies array size, so to have that even if none
452 // of the members are indexed
453 // if short, will be incremented everytime an indexed member is
454 // found
455 array->set_member(NSV::PROP_LENGTH, li);
457 #ifdef GNASH_DEBUG_AMF_DESERIALIZE
458 log_debug("amf0 starting read of ECMA_ARRAY with %i elements", li);
459 #endif
461 as_value objectElement;
462 VM& vm = getVM(_global);
463 for (;;) {
465 // It seems we don't mind about this situation, although it means
466 // the next read will fail.
467 if (_end - _pos < 2) {
468 log_error("MALFORMED AMF: premature _end of ECMA_ARRAY "
469 "block");
470 break;
472 const boost::uint16_t strlen = readNetworkShort(_pos);
473 _pos += 2;
475 // _end of ECMA_ARRAY is signalled by an empty string
476 // followed by an OBJECT_END_AMF0 (0x09) byte
477 if (!strlen) {
478 // expect an object terminator here
479 if (*_pos != OBJECT_END_AMF0) {
480 log_error("MALFORMED AMF: empty member name not "
481 "followed by OBJECT_END_AMF0 byte");
483 ++_pos;
484 break;
487 // Throw exception instead?
488 if (_end - _pos < strlen) {
489 log_error("MALFORMED AMF: premature _end of ECMA_ARRAY "
490 "block");
491 break;
494 const std::string name(reinterpret_cast<const char*>(_pos), strlen);
496 #ifdef GNASH_DEBUG_AMF_DESERIALIZE
497 log_debug("amf0 ECMA_ARRAY prop name is %s", name);
498 #endif
500 _pos += strlen;
502 // Recurse to read element.
503 if (!operator()(objectElement)) {
504 throw AMFException("Unable to read array element");
506 array->set_member(getURI(vm, name), objectElement);
508 return as_value(array);
511 as_value
512 Reader::readObject()
514 VM& vm = getVM(_global);
515 as_object* obj = createObject(_global);
517 #ifdef GNASH_DEBUG_AMF_DESERIALIZE
518 log_debug("amf0 starting read of OBJECT");
519 #endif
521 _objectRefs.push_back(obj);
523 as_value tmp;
524 std::string keyString;
525 for (;;) {
527 if (!operator()(tmp, STRING_AMF0)) {
528 throw AMFException("Could not read object property name");
530 keyString = tmp.to_string();
532 if (keyString.empty()) {
533 if (_pos < _end) {
534 // AMF0 has a redundant "object _end" byte
535 ++_pos;
537 else {
538 // What is the point?
539 log_error("AMF buffer terminated just before "
540 "object _end byte. continuing anyway.");
542 return as_value(obj);
545 if (!operator()(tmp)) {
546 throw AMFException("Unable to read object member");
548 obj->set_member(getURI(vm, keyString), tmp);
552 as_value
553 Reader::readReference()
556 if (_end - _pos < 2) {
557 throw AMFException("Read past _end of buffer for reference index");
559 const boost::uint16_t si = readNetworkShort(_pos);
560 _pos += 2;
562 #ifdef GNASH_DEBUG_AMF_DESERIALIZE
563 log_debug("readAMF0: reference #%d", si);
564 #endif
565 if (si < 1 || si > _objectRefs.size()) {
566 log_error("readAMF0: invalid reference to object %d (%d known "
567 "objects)", si, _objectRefs.size());
568 throw AMFException("Reference to invalid object reference");
570 return as_value(_objectRefs[si - 1]);
573 as_value
574 Reader::readDate()
576 const double d = readNumber(_pos, _end);
578 #ifdef GNASH_DEBUG_AMF_DESERIALIZE
579 log_debug("amf0 read date: %e", dub);
580 #endif
582 as_function* ctor = getMember(_global, NSV::CLASS_DATE).to_function();
583 VM& vm = getVM(_global);
585 as_value date;
586 if (ctor) {
587 fn_call::Args args;
588 args += d;
589 date = constructInstance(*ctor, as_environment(vm), args);
591 if (_end - _pos < 2) {
592 throw AMFException("premature _end of input reading "
593 "timezone from Date type");
595 const boost::uint16_t tz = readNetworkShort(_pos);
596 if (tz != 0) {
597 log_error(_("Date type encoded timezone info %1%, even though "
598 "this field should not be used."), tz);
600 _pos += 2;
602 return date;
605 } // namespace amf
606 } // namespace gnash