1 // XML_as.cpp:  ActionScript "XMLDocument" class, for Gnash.
2 //
3 //   Copyright (C) 2009, 2010, 2011, 2012 Free Software 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
18 //
19 
20 #include "XMLNode_as.h"
21 
22 #include <string>
23 #include <sstream>
24 #include <vector>
25 #include <algorithm>
26 #include <boost/algorithm/string/compare.hpp>
27 #include <boost/algorithm/string/replace.hpp>
28 
29 #include "log.h"
30 #include "as_function.h"
31 #include "fn_call.h"
32 #include "Global_as.h"
33 #include "LoadableObject.h"
34 #include "XML_as.h"
35 #include "NativeFunction.h"
36 #include "VM.h"
37 #include "namedStrings.h"
38 #include "StringPredicates.h"
39 #include "Object.h"
40 
41 namespace gnash {
42 
43 // Forward declarations
44 namespace {
45 
46     as_value xml_new(const fn_call& fn);
47     as_value xml_createElement(const fn_call& fn);
48     as_value xml_createTextNode(const fn_call& fn);
49     as_value xml_parseXML(const fn_call& fn);
50     as_value xml_onData(const fn_call& fn);
51     as_value xml_xmlDecl(const fn_call& fn);
52     as_value xml_docTypeDecl(const fn_call& fn);
53     as_value xml_contentType(const fn_call& fn);
54     as_value xml_escape(const fn_call& fn);
55     as_value xml_loaded(const fn_call& fn);
56     as_value xml_status(const fn_call& fn);
57     as_value xml_ignoreWhite(const fn_call& fn);
58 
59     typedef XML_as::xml_iterator xml_iterator;
60 
61     bool textAfterWhitespace(xml_iterator& it, xml_iterator end);
62     bool textMatch(xml_iterator& it, xml_iterator end,
63             const std::string& match, bool advance = true);
64     bool parseNodeWithTerminator( xml_iterator& it, xml_iterator end,
65             const std::string& terminator, std::string& content);
66 
67     void setIdMap(as_object& xml, XMLNode_as& childNode,
68             const std::string& val);
69 
70 
71     typedef std::map<std::string, std::string> Entities;
72     const Entities& getEntities();
73 
74     void attachXMLProperties(as_object& o);
75 	void attachXMLInterface(as_object& o);
76 }
77 
78 
XML_as(as_object & object)79 XML_as::XML_as(as_object& object)
80     :
81     XMLNode_as(getGlobal(object)),
82     _loaded(XML_LOADED_UNDEFINED),
83     _status(XML_OK),
84     _contentType("application/x-www-form-urlencoded"),
85     _ignoreWhite(false)
86 {
87     setObject(&object);
88 }
89 
90 // Parse the ASCII XML string into an XMLNode tree
XML_as(as_object & object,const std::string & xml)91 XML_as::XML_as(as_object& object, const std::string& xml)
92     :
93     XMLNode_as(getGlobal(object)),
94     _loaded(XML_LOADED_UNDEFINED),
95     _status(XML_OK),
96     _contentType("application/x-www-form-urlencoded"),
97     _ignoreWhite(false)
98 {
99     setObject(&object);
100     parseXML(xml);
101 }
102 
103 void
escapeXML(std::string & text)104 escapeXML(std::string& text)
105 {
106     const Entities& ent = getEntities();
107 
108     for (const auto& entity : ent)
109     {
110         boost::replace_all(text, entity.second, entity.first);
111     }
112 }
113 
114 void
unescapeXML(std::string & text)115 unescapeXML(std::string& text)
116 {
117     const Entities& ent = getEntities();
118 
119     for (const auto& entity : ent) {
120         boost::replace_all(text, entity.first, entity.second);
121     }
122 
123     // Additionally, the &nbsp; entity is unescaped (but never escaped).
124     // Note we do this as UTF-8, which is most likely wrong for SWF5.
125     boost::replace_all(text, "&nbsp;", "\xc2\xa0");
126 }
127 
128 void
toString(std::ostream & o,bool encode) const129 XML_as::toString(std::ostream& o, bool encode) const
130 {
131     if (!_xmlDecl.empty()) o << _xmlDecl;
132     if (!_docTypeDecl.empty()) o << _docTypeDecl;
133 
134     XMLNode_as* i = firstChild();
135     while (i) {
136         i->XMLNode_as::toString(o, encode);
137         i = i->nextSibling();
138     }
139 }
140 
141 void
parseAttribute(XMLNode_as * node,xml_iterator & it,const xml_iterator end,Attributes & attributes)142 XML_as::parseAttribute(XMLNode_as* node, xml_iterator& it,
143         const xml_iterator end, Attributes& attributes)
144 {
145     const std::string terminators("\r\t\n >=");
146 
147     xml_iterator ourend = std::find_first_of(it, end,
148             terminators.begin(), terminators.end());
149 
150     if (ourend == end) {
151         _status = XML_UNTERMINATED_ELEMENT;
152         return;
153     }
154     std::string name(it, ourend);
155 
156     if (name.empty()) {
157         _status = XML_UNTERMINATED_ELEMENT;
158         return;
159     }
160 
161     // Point iterator to the DisplayObject after the name.
162     it = ourend;
163 
164     // Skip any whitespace before the '='. If we reach the end of the string
165     // or don't find an '=', it's a parser error.
166     if (!textAfterWhitespace(it, end) || *it != '=') {
167         _status = XML_UNTERMINATED_ELEMENT;
168         return;
169     }
170 
171     // Point to the DisplayObject after the '='
172     ++it;
173 
174     // Skip any whitespace. If we reach the end of the string, or don't find
175     // a " or ', it's a parser error.
176     if (!textAfterWhitespace(it, end) || (*it != '"' && *it != '\'')) {
177         _status = XML_UNTERMINATED_ELEMENT;
178         return;
179     }
180 
181     // Find the end of the attribute, looking for the opening DisplayObject,
182     // as long as it's not escaped. We begin one after the present position,
183     // which should be the opening DisplayObject. We want to remember what the
184     // iterator is pointing to for a while, so don't advance it.
185     ourend = it;
186     do {
187         ++ourend;
188         ourend = std::find(ourend, end, *it);
189     } while (ourend != end && *(ourend - 1) == '\\');
190 
191     if (ourend == end) {
192         _status = XML_UNTERMINATED_ATTRIBUTE;
193         return;
194     }
195     ++it;
196 
197     std::string value(it, ourend);
198 
199     // Replace entities in the value.
200     unescapeXML(value);
201 
202     // We've already checked that ourend != end, so we can advance at
203     // least once.
204     it = ourend;
205     // Advance past the last attribute DisplayObject
206     ++it;
207 
208     // Handle namespace. This is set once only for each node, and is also
209     // pushed to the attributes list once.
210     StringNoCaseEqual noCaseCompare;
211     if (noCaseCompare(name, "xmlns") || noCaseCompare(name, "xmlns:")) {
212         if (!node->getNamespaceURI().empty()) return;
213         node->setNamespaceURI(value);
214     }
215 
216     // This ensures values are not inserted twice, which is expected
217     // behaviour
218     attributes.insert(std::make_pair(name, value));
219 
220 }
221 
222 /// Parse and set the docTypeDecl. This is stored without any validation and
223 /// with the same case as in the parsed XML.
224 void
parseDocTypeDecl(xml_iterator & it,const xml_iterator end)225 XML_as::parseDocTypeDecl(xml_iterator& it, const xml_iterator end)
226 {
227 
228     xml_iterator ourend;
229     xml_iterator current = it;
230 
231     std::string::size_type count = 1;
232 
233     // Look for angle brackets in the doctype declaration.
234     while (count) {
235 
236         // Find the next closing bracket after the current position.
237         ourend = std::find(current, end, '>');
238         if (ourend == end) {
239             _status = XML_UNTERMINATED_DOCTYPE_DECL;
240             return;
241         }
242         --count;
243 
244         // Count any opening brackets in between.
245         count += std::count(current, ourend, '<');
246         current = ourend;
247         ++current;
248     }
249 
250     const std::string content(it, ourend);
251     std::ostringstream os;
252     os << '<' << content << '>';
253     _docTypeDecl = os.str();
254     it = ourend + 1;
255 }
256 
257 void
parseXMLDecl(xml_iterator & it,const xml_iterator end)258 XML_as::parseXMLDecl(xml_iterator& it, const xml_iterator end)
259 {
260     std::string content;
261     if (!parseNodeWithTerminator(it, end, "?>", content)) {
262         _status = XML_UNTERMINATED_XML_DECL;
263         return;
264     }
265 
266     std::ostringstream os;
267     os << "<" << content << "?>";
268 
269     // This is appended to any xmlDecl already there.
270     _xmlDecl += os.str();
271 }
272 
273 // The iterator should be pointing to the first char after the '<'
274 void
parseTag(XMLNode_as * & node,xml_iterator & it,const xml_iterator end)275 XML_as::parseTag(XMLNode_as*& node, xml_iterator& it,
276         const xml_iterator end)
277 {
278     bool closing = (*it == '/');
279     if (closing) ++it;
280 
281     // These are for terminating the tag name, not (necessarily) the tag.
282     const std::string terminators("\r\n\t >");
283 
284     xml_iterator endName = std::find_first_of(it, end, terminators.begin(),
285             terminators.end());
286 
287     // Check that one of the terminators was found; otherwise it's malformed.
288     if (endName == end) {
289         _status = XML_UNTERMINATED_ELEMENT;
290         return;
291     }
292 
293     // Knock off the "/>" of a self-closing tag.
294     if (std::equal(endName - 1, endName + 1, "/>")) {
295         // This can leave endName before it, e.g when a self-closing tag is
296         // empty ("</>"). This must be checked before trying to construct
297         // a string!
298         --endName;
299     }
300 
301     // If the tag is empty, the XML counts as malformed.
302     if (it >= endName) {
303         _status = XML_UNTERMINATED_ELEMENT;
304         return;
305     }
306 
307     std::string tagName(it, endName);
308 
309     if (!closing) {
310 
311         // Skip to the end of any whitespace after the tag name
312         it = endName;
313 
314         if (!textAfterWhitespace(it, end)) {
315             _status = XML_UNTERMINATED_ELEMENT;
316            return;
317         }
318 
319         XMLNode_as* childNode = new XMLNode_as(_global);
320         childNode->nodeNameSet(tagName);
321         childNode->nodeTypeSet(Element);
322 
323         // Parse any attributes in an opening tag only, stopping at "/>" or
324         // '>'
325         // Attributes are added in reverse order and without any duplicates.
326         Attributes attributes;
327         while (it != end && *it != '>' && _status == XML_OK)
328         {
329             if (end - it > 1 && std::equal(it, it + 2, "/>")) break;
330 
331             // This advances the iterator
332             parseAttribute(childNode, it, end, attributes);
333 
334             // Skip any whitespace. If we reach the end of the string,
335             // it's malformed.
336             if (!textAfterWhitespace(it, end)) {
337                 _status = XML_UNTERMINATED_ELEMENT;
338                 return;
339             }
340         }
341 
342         // Do nothing more if there was an error in attributes parsing.
343         if (_status != XML_OK) {
344             delete childNode;
345             return;
346         }
347 
348         // testsuite/swfdec/xml-id-map.as tests that the node is appended
349         // first.
350         node->appendChild(childNode);
351 
352         for (Attributes::const_reverse_iterator i = attributes.rbegin(),
353                 e = attributes.rend(); i != e; ++i) {
354             childNode->setAttribute(i->first, i->second);
355             if (i->first == "id") setIdMap(*object(), *childNode, i->second);
356         }
357 
358         if (*it == '/') ++it;
359         else node = childNode;
360 
361         if (*it == '>') ++it;
362 
363         return;
364     }
365 
366     // If we reach here, this is a closing tag.
367 
368     it = std::find(endName, end, '>');
369 
370     if (it == end) {
371        _status = XML_UNTERMINATED_ELEMENT;
372        return;
373     }
374     ++it;
375 
376     StringNoCaseEqual noCaseCompare;
377 
378     if (node->getParent() && noCaseCompare(node->nodeName(), tagName)) {
379         node = node->getParent();
380     }
381     else {
382         // Malformed. Search for the parent node.
383         XMLNode_as* s = node;
384         while (s && !noCaseCompare(s->nodeName(), tagName)) {
385             //log_debug("parent: %s, this: %s", s->nodeName(), tagName);
386             s = s->getParent();
387         }
388         if (s) {
389             // If there's a parent, the open tag is orphaned.
390             _status = XML_MISSING_CLOSE_TAG;
391         }
392         else {
393             // If no parent, the close tag is orphaned.
394             _status = XML_MISSING_OPEN_TAG;
395         }
396     }
397 
398 }
399 
400 void
parseText(XMLNode_as * node,xml_iterator & it,const xml_iterator end,bool iw)401 XML_as::parseText(XMLNode_as* node, xml_iterator& it,
402         const xml_iterator end, bool iw)
403 {
404     xml_iterator ourend = std::find(it, end, '<');
405     std::string content(it, ourend);
406 
407     it = ourend;
408 
409     if (iw &&
410         content.find_first_not_of("\t\r\n ") == std::string::npos) return;
411 
412     XMLNode_as* childNode = new XMLNode_as(_global);
413 
414     childNode->nodeTypeSet(XMLNode_as::Text);
415 
416     // Replace any entitites.
417     unescapeXML(content);
418 
419     childNode->nodeValueSet(content);
420     node->appendChild(childNode);
421 
422 }
423 
424 void
parseComment(XMLNode_as *,xml_iterator & it,const xml_iterator end)425 XML_as::parseComment(XMLNode_as* /*node*/, xml_iterator& it,
426         const xml_iterator end)
427 {
428 
429     std::string content;
430 
431     if (!parseNodeWithTerminator(it, end, "-->", content)) {
432         _status = XML_UNTERMINATED_COMMENT;
433         return;
434     }
435     // Comments are discarded at least up to SWF8
436 }
437 
438 void
parseCData(XMLNode_as * node,xml_iterator & it,const xml_iterator end)439 XML_as::parseCData(XMLNode_as* node, xml_iterator& it,
440         const xml_iterator end)
441 {
442     std::string content;
443 
444     if (!parseNodeWithTerminator(it, end, "]]>", content)) {
445         _status = XML_UNTERMINATED_CDATA;
446         return;
447     }
448 
449     XMLNode_as* childNode = new XMLNode_as(_global);
450     childNode->nodeValueSet(content);
451     childNode->nodeTypeSet(Text);
452     node->appendChild(childNode);
453 }
454 
455 
456 // This parses an XML string into a tree of XMLNodes.
457 void
parseXML(const std::string & xml)458 XML_as::parseXML(const std::string& xml)
459 {
460     // Clear current data
461     clear();
462 
463     if (xml.empty()) {
464         log_error(_("XML data is empty"));
465         return;
466     }
467 
468     xml_iterator it = xml.begin();
469     const xml_iterator end = xml.end();
470     XMLNode_as* node = this;
471 
472     const bool iw = ignoreWhite();
473 
474     while (it != end && _status == XML_OK) {
475         if (*it == '<') {
476             ++it;
477             if (textMatch(it, end, "!DOCTYPE", false)) {
478                 // We should not advance past the DOCTYPE label, as
479                 // the case is preserved.
480                 parseDocTypeDecl(it, end);
481             }
482             else if (textMatch(it, end, "?xml", false)) {
483                 // We should not advance past the xml label, as
484                 // the case is preserved.
485                 parseXMLDecl(it, end);
486             }
487             else if (textMatch(it, end, "!--")) {
488                 parseComment(node, it, end);
489             }
490             else if (textMatch(it, end, "![CDATA[")) {
491                 parseCData(node, it, end);
492             }
493             else parseTag(node, it, end);
494         }
495         else parseText(node, it, end, iw);
496     }
497 
498     // If everything parsed correctly, check that we've got back to the
499     // parent node. If not, there is a missing closing tag.
500     if (_status == XML_OK && node != this) {
501         _status = XML_MISSING_CLOSE_TAG;
502     }
503 
504 }
505 
506 void
clear()507 XML_as::clear()
508 {
509     clearChildren();
510     _docTypeDecl.clear();
511     _xmlDecl.clear();
512     _status = XML_OK;
513 }
514 
515 // XML.prototype is assigned after the class has been constructed, so it
516 // replaces the original prototype and does not have a 'constructor'
517 // property.
518 void
xml_class_init(as_object & where,const ObjectURI & uri)519 xml_class_init(as_object& where, const ObjectURI& uri)
520 {
521     Global_as& gl = getGlobal(where);
522     as_object* cl = gl.createClass(&xml_new, nullptr);
523 
524     as_function* ctor = getMember(gl, NSV::CLASS_XMLNODE).to_function();
525 
526     if (ctor) {
527         // XML.prototype is an XMLNode(1, "");
528         fn_call::Args args;
529         args += 1, "";
530         as_object* proto =
531             constructInstance(*ctor, as_environment(getVM(where)), args);
532         attachXMLInterface(*proto);
533         cl->init_member(NSV::PROP_PROTOTYPE, proto);
534     }
535 
536     where.init_member(uri, cl, as_object::DefaultFlags);
537 }
538 
539 void
registerXMLNative(as_object & where)540 registerXMLNative(as_object& where)
541 {
542     VM& vm = getVM(where);
543     vm.registerNative(xml_escape, 100, 5);
544     vm.registerNative(xml_createElement, 253, 10);
545     vm.registerNative(xml_createTextNode, 253, 11);
546     vm.registerNative(xml_parseXML, 253, 12);
547 }
548 
549 namespace {
550 
551 void
attachXMLProperties(as_object & o)552 attachXMLProperties(as_object& o)
553 {
554     as_object* proto = o.get_prototype();
555     if (!proto) return;
556     const int flags = 0;
557     proto->init_property("docTypeDecl", &xml_docTypeDecl, &xml_docTypeDecl,
558             flags);
559     proto->init_property("contentType", &xml_contentType, &xml_contentType,
560             flags);
561     proto->init_property("ignoreWhite", xml_ignoreWhite, xml_ignoreWhite, flags);
562     proto->init_property("loaded", xml_loaded, xml_loaded);
563     proto->init_property("status", xml_status, xml_status, flags);
564     proto->init_property("xmlDecl", &xml_xmlDecl, &xml_xmlDecl, flags);
565 }
566 
567 
568 void
attachXMLInterface(as_object & o)569 attachXMLInterface(as_object& o)
570 {
571     VM& vm = getVM(o);
572     Global_as& gl = getGlobal(o);
573 
574     const int flags = 0;
575 
576     // No flags:
577     o.init_member("createElement", vm.getNative(253, 10), flags);
578     o.init_member("createTextNode", vm.getNative(253, 11), flags);
579     o.init_member("load", vm.getNative(301, 0), flags);
580 
581     /// This handles getBytesLoaded, getBytesTotal, and addRequestHeader
582     attachLoadableInterface(o, flags);
583 
584     o.init_member("parseXML", vm.getNative(253, 12), flags);
585     o.init_member("send", vm.getNative(301, 1), flags);
586     o.init_member("sendAndLoad", vm.getNative(301, 2), flags);
587     o.init_member("onData", gl.createFunction(xml_onData), flags);
588     o.init_member("onLoad", gl.createFunction(emptyFunction), flags);
589 }
590 
591 as_value
xml_new(const fn_call & fn)592 xml_new(const fn_call& fn)
593 {
594     as_object* obj = ensure<ValidThis>(fn);
595 
596     if (fn.nargs && !fn.arg(0).is_undefined()) {
597 
598         // Copy constructor clones nodes.
599         if (fn.arg(0).is_object()) {
600             as_object* other = toObject(fn.arg(0), getVM(fn));
601             XML_as* xml;
602             if (isNativeType(other, xml)) {
603                 as_object* clone = xml->cloneNode(true)->object();
604                 attachXMLProperties(*clone);
605                 return as_value(clone);
606             }
607         }
608 
609         const int version = getSWFVersion(fn);
610         const std::string& xml_in = fn.arg(0).to_string(version);
611         // It doesn't matter if the string is empty.
612         obj->setRelay(new XML_as(*obj, xml_in));
613         attachXMLProperties(*obj);
614         return as_value();
615     }
616 
617     obj->setRelay(new XML_as(*obj));
618     attachXMLProperties(*obj);
619 
620     return as_value();
621 }
622 
623 /// This is attached to the prototype (an XMLNode) on construction of XML
624 //
625 /// It has the curious effect of giving the XML object an inherited 'loaded'
626 /// property that fails when called on the prototype, because the prototype
627 /// is of type XMLNode.
628 as_value
xml_loaded(const fn_call & fn)629 xml_loaded(const fn_call& fn)
630 {
631     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
632 
633     if (!fn.nargs) {
634         XML_as::LoadStatus ls = ptr->loaded();
635         if (ls == XML_as::XML_LOADED_UNDEFINED) return as_value();
636         return as_value(static_cast<bool>(ls));
637     }
638     ptr->setLoaded(
639             static_cast<XML_as::LoadStatus>(toBool(fn.arg(0), getVM(fn))));
640     return as_value();
641 }
642 
643 as_value
xml_status(const fn_call & fn)644 xml_status(const fn_call& fn)
645 {
646     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
647 
648     if (!fn.nargs) {
649         return as_value(ptr->status());
650     }
651 
652     if (fn.arg(0).is_undefined()) {
653         return as_value();
654     }
655 
656     const double status = toNumber(fn.arg(0), getVM(fn));
657     if (isNaN(status) ||
658             status > std::numeric_limits<std::int32_t>::max() ||
659             status < std::numeric_limits<std::int32_t>::min()) {
660 
661         ptr->setStatus(static_cast<XML_as::ParseStatus>(
662                     std::numeric_limits<std::int32_t>::min()));
663     }
664     else ptr->setStatus(static_cast<XML_as::ParseStatus>(int(status)));
665     return as_value();
666 }
667 
668 as_value
xml_ignoreWhite(const fn_call & fn)669 xml_ignoreWhite(const fn_call& fn)
670 {
671     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
672     if (!fn.nargs) {
673         // Getter
674         return as_value(ptr->ignoreWhite());
675     }
676 
677     // Setter
678     if (fn.arg(0).is_undefined()) return as_value();
679     ptr->ignoreWhite(toBool(fn.arg(0), getVM(fn)));
680     return as_value();
681 }
682 
683 /// Only available as ASnative.
684 as_value
xml_escape(const fn_call & fn)685 xml_escape(const fn_call& fn)
686 {
687     if (!fn.nargs) return as_value();
688 
689     std::string escaped = fn.arg(0).to_string();
690     escapeXML(escaped);
691     return as_value(escaped);
692 }
693 
694 /// \brief create a new XML element
695 ///
696 /// Method; creates a new XML element with the name specified in the
697 /// parameter. The new element initially has no parent, no children,
698 /// and no siblings. The method returns a reference to the newly
699 /// created XML object that represents the element. This method and
700 /// the XML.createTextNode() method are the constructor methods for
701 /// creating nodes for an XML object.
702 as_value
xml_createElement(const fn_call & fn)703 xml_createElement(const fn_call& fn)
704 {
705     if (!fn.nargs || fn.arg(0).is_undefined()) {
706         return as_value();
707     }
708 
709     const as_value& arg = fn.arg(0);
710 
711     const std::string& text = arg.to_string(getSWFVersion(fn));
712     XMLNode_as *xml_obj = new XMLNode_as(getGlobal(fn));
713     xml_obj->nodeNameSet(text);
714     if (!text.empty()) xml_obj->nodeTypeSet(XMLNode_as::Text);
715 
716     return as_value(xml_obj->object());
717 }
718 
719 
720 /// \brief Create a new XML node
721 ///
722 /// Method; creates a new XML text node with the specified text. The
723 /// new node initially has no parent, and text nodes cannot have
724 /// children or siblings. This method returns a reference to the XML
725 /// object that represents the new text node. This method and the
726 /// XML.createElement() method are the constructor methods for
727 /// creating nodes for an XML object.
728 as_value
xml_createTextNode(const fn_call & fn)729 xml_createTextNode(const fn_call& fn)
730 {
731     if (fn.nargs > 0) {
732         const std::string& text = fn.arg(0).to_string();
733         XMLNode_as* xml_obj = new XMLNode_as(getGlobal(fn));
734         xml_obj->nodeValueSet(text);
735         xml_obj->nodeTypeSet(XMLNode_as::Text);
736         return as_value(xml_obj->object());
737     }
738     else {
739         log_error(_("no text for text node creation"));
740     }
741     return as_value();
742 }
743 
744 
745 as_value
xml_parseXML(const fn_call & fn)746 xml_parseXML(const fn_call& fn)
747 {
748     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
749 
750     if (fn.nargs < 1) {
751         IF_VERBOSE_ASCODING_ERRORS(
752             log_aserror(_("XML.parseXML() needs one argument"));
753         );
754         return as_value();
755     }
756 
757     const as_value arg = fn.arg(0);
758     if (arg.is_undefined()) return as_value();
759 
760     const std::string& text = arg.to_string(getSWFVersion(fn));
761     ptr->parseXML(text);
762 
763     return as_value();
764 }
765 
766 as_value
xml_xmlDecl(const fn_call & fn)767 xml_xmlDecl(const fn_call& fn)
768 {
769     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
770 
771     if (!fn.nargs) {
772         // Getter
773         const std::string& xml = ptr->getXMLDecl();
774         if (xml.empty()) return as_value();
775         return as_value(xml);
776     }
777 
778     // Setter
779 
780     const std::string& xml = fn.arg(0).to_string();
781     ptr->setXMLDecl(xml);
782 
783     return as_value();
784 }
785 
786 as_value
xml_contentType(const fn_call & fn)787 xml_contentType(const fn_call& fn)
788 {
789     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
790 
791     if (!fn.nargs) {
792         // Getter
793         return as_value(ptr->getContentType());
794     }
795 
796     // Setter
797     const std::string& contentType = fn.arg(0).to_string();
798     ptr->setContentType(contentType);
799 
800     return as_value();
801 }
802 
803 as_value
xml_docTypeDecl(const fn_call & fn)804 xml_docTypeDecl(const fn_call& fn)
805 {
806     XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
807 
808     if (!fn.nargs) {
809         // Getter
810         const std::string& docType = ptr->getDocTypeDecl();
811         if (docType.empty()) return as_value();
812         return as_value(docType);
813     }
814 
815     // Setter
816     const std::string& docType = fn.arg(0).to_string();
817     ptr->setDocTypeDecl(docType);
818 
819     return as_value();
820 }
821 
822 as_value
xml_onData(const fn_call & fn)823 xml_onData(const fn_call& fn)
824 {
825     as_object* thisPtr = fn.this_ptr;
826     assert(thisPtr);
827 
828     // See http://gitweb.freedesktop.org/?p=swfdec/swfdec.git;
829     // a=blob;f=libswfdec/swfdec_initialize.as
830 
831     as_value src;
832     if (fn.nargs) src = fn.arg(0);
833 
834     if (!src.is_undefined()) {
835         thisPtr->set_member(NSV::PROP_LOADED, true);
836         callMethod(thisPtr, NSV::PROP_PARSE_XML, src);
837         callMethod(thisPtr, NSV::PROP_ON_LOAD, true);
838     }
839     else {
840         thisPtr->set_member(NSV::PROP_LOADED, false);
841         callMethod(thisPtr, NSV::PROP_ON_LOAD, false);
842     }
843 
844     return as_value();
845 }
846 
847 /// Case insensitive match of a string, returning false if there too few
848 /// DisplayObjects left or if there is no match. If there is a match, and advance
849 /// is not false, the iterator points to the DisplayObject after the match.
850 bool
textMatch(xml_iterator & it,const xml_iterator end,const std::string & match,bool advance)851 textMatch(xml_iterator& it, const xml_iterator end,
852         const std::string& match, bool advance)
853 {
854     const std::string::size_type len = match.length();
855 
856     if (static_cast<size_t>(end - it) < len) return false;
857 
858     if (!std::equal(it, it + len, match.begin(), boost::is_iequal())) {
859         return false;
860     }
861     if (advance) it += len;
862     return true;
863 }
864 
865 /// Advance past whitespace
866 //
867 /// @return true if there is text after the whitespace, false if we
868 ///         reach the end of the string.
869 bool
textAfterWhitespace(xml_iterator & it,const xml_iterator end)870 textAfterWhitespace(xml_iterator& it, const xml_iterator end)
871 {
872     const std::string whitespace("\r\t\n ");
873     while (it != end && whitespace.find(*it) != std::string::npos) ++it;
874     return (it != end);
875 }
876 
877 /// Parse a complete node up to a specified terminator.
878 //
879 /// @return     false if we reach the end of the text before finding the
880 ///             terminator.
881 /// @param it   The current position of the iterator. If the return is true,
882 ///             this points to the first DisplayObject after the terminator
883 ///             after return
884 /// @param content  If the return is true, this is filled with the content of
885 ///                 the tag.
886 /// @param xml      The complete XML string.
887 bool
parseNodeWithTerminator(xml_iterator & it,const xml_iterator end,const std::string & terminator,std::string & content)888 parseNodeWithTerminator(xml_iterator& it, const xml_iterator end,
889         const std::string& terminator, std::string& content)
890 {
891     xml_iterator ourend = std::search(it, end, terminator.begin(),
892             terminator.end());
893 
894     if (ourend == end) {
895         return false;
896     }
897 
898     content = std::string(it, ourend);
899     it = ourend + terminator.length();
900 
901     return true;
902 }
903 
904 
905 void
setIdMap(as_object & xml,XMLNode_as & childNode,const std::string & val)906 setIdMap(as_object& xml, XMLNode_as& childNode, const std::string& val)
907 {
908     VM& vm = getVM(xml);
909 
910     const ObjectURI& id = getURI(vm, "idMap");
911 
912     if (getSWFVersion(xml) < 8) {
913         // In version 7 or below, properties are added to the XML object.
914         xml.set_member(getURI(vm, val), childNode.object());
915         return;
916     }
917 
918     // In version 8 or above, properties are added to an idMap member.
919     as_value im;
920     as_object* idMap;
921     if (xml.get_member(id, &im)) {
922         // If it's present but not an object just ignore it
923         // and carry on.
924         if (!im.is_object()) return;
925 
926         idMap = toObject(im, vm);
927         assert(idMap);
928     }
929     else {
930         // If it's not there at all create it.
931         idMap = new as_object(getGlobal(xml));
932         xml.set_member(id, idMap);
933     }
934     idMap->set_member(getURI(vm, val), childNode.object());
935 }
936 
937 const Entities&
getEntities()938 getEntities()
939 {
940     static const Entities entities = {
941         {"&amp;", "&"},
942         {"&quot;", "\""},
943         {"&lt;", "<"},
944         {"&gt;", ">"},
945         {"&apos;", "'"}
946     };
947 
948     return entities;
949 }
950 
951 } // anonymous namespace
952 } // gnash namespace
953 
954 // local Variables:
955 // mode: C++
956 // indent-tabs-mode: t
957 // End:
958 
959