1 // as_environment.cpp:  Variable, Sprite, and Movie locators, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 //
20 
21 #include "as_environment.h"
22 
23 #include <string>
24 #include <utility>
25 #include <boost/algorithm/string/case_conv.hpp>
26 #include <boost/format.hpp>
27 
28 #include "MovieClip.h"
29 #include "movie_root.h"
30 #include "as_value.h"
31 #include "VM.h"
32 #include "log.h"
33 #include "Property.h"
34 #include "as_object.h"
35 #include "namedStrings.h"
36 #include "CallStack.h"
37 #include "Global_as.h"
38 
39 // Define this to have find_target() calls trigger debugging output
40 //#define DEBUG_TARGET_FINDING 1
41 
42 // Define this to have get_variable() calls trigger debugging output
43 //#define GNASH_DEBUG_GET_VARIABLE 1
44 
45 namespace gnash {
46 
47 namespace {
48     /// Find a variable in the given as_object
49     //
50     /// @param varname
51     /// Name of the local variable
52     ///
53     /// @param ret
54     /// If a variable is found it's assigned to this parameter.
55     /// Untouched if the variable is not found.
56     ///
57     /// @return true if the variable was found, false otherwise
58     bool getLocal(as_object& locals, const std::string& name, as_value& ret);
59 
60     bool findLocal(as_object& locals, const std::string& varname, as_value& ret,
61             as_object** retTarget);
62 
63     /// Delete a local variable
64     //
65     /// @param varname
66     /// Name of the local variable
67     ///
68     /// @return true if the variable was found and deleted, false otherwise
69     bool deleteLocal(as_object& locals, const std::string& varname);
70 
71     /// Set a variable of the given object, if it exists.
72     //
73     /// @param varname
74     /// Name of the local variable
75     ///
76     /// @param val
77     /// Value to assign to the variable
78     ///
79     /// @return true if the variable was found, false otherwise
80     bool setLocal(as_object& locals, const std::string& varname,
81         const as_value& val);
82 
83     as_object* getElement(as_object* obj, const ObjectURI& uri);
84 
85     /// @param retTarget
86     /// If not NULL, the pointer will be set to the actual object containing the
87     /// found variable (if found).
88     as_value getVariableRaw(const as_environment& env,
89         const std::string& varname,
90         const as_environment::ScopeStack& scope,
91         as_object** retTarget = nullptr);
92 
93     void setVariableRaw(const as_environment& env, const std::string& varname,
94         const as_value& val, const as_environment::ScopeStack& scope);
95 
96     // Search for next '.' or '/' character in this word.  Return
97     // a pointer to it, or null if it wasn't found.
98     static const char* next_slash_or_dot(const char* word);
99 
100     static bool validRawVariableName(const std::string& varname);
101 
102 }
103 
104 as_value as_environment::undefVal;
105 
as_environment(VM & vm)106 as_environment::as_environment(VM& vm)
107     :
108     _vm(vm),
109     _stack(_vm.getStack()),
110     _target(nullptr),
111     _original_target(nullptr)
112 {
113 }
114 
115 as_object*
findObject(const as_environment & ctx,const std::string & path,const as_environment::ScopeStack * scope)116 findObject(const as_environment& ctx, const std::string& path,
117         const as_environment::ScopeStack* scope)
118 {
119     if (path.empty()) {
120         return getObject(ctx.target());
121     }
122 
123     VM& vm = ctx.getVM();
124     string_table& st = vm.getStringTable();
125     const int swfVersion = vm.getSWFVersion();
126     ObjectURI globalURI(NSV::PROP_uGLOBAL);
127 
128     bool firstElementParsed = false;
129     bool dot_allowed = true;
130 
131     // This points to the current object being used for lookup.
132     as_object* env;
133     const char* p = path.c_str();
134 
135     // Check if it's an absolute path
136     if (*p == '/') {
137 
138         MovieClip* root = nullptr;
139         if (ctx.target()) root = ctx.target()->getAsRoot();
140         else {
141             if (ctx.get_original_target()) {
142                 root = ctx.get_original_target()->getAsRoot();
143             }
144             return nullptr;
145         }
146 
147         // If the path is just "/" return the root.
148         if (!*(++p)) return getObject(root);
149 
150         // Otherwise we start at the root for lookup.
151         env = getObject(root);
152         firstElementParsed = true;
153         dot_allowed = false;
154 
155     }
156     else {
157         env = getObject(ctx.target());
158     }
159 
160     assert (*p);
161 
162     std::string subpart;
163 
164     while (1) {
165 
166         // Skip past all colons (why?)
167         while (*p == ':') ++p;
168 
169         if (!*p) {
170             // No more components to scan, so return the currently found
171             // object.
172             return env;
173         }
174 
175         // Search for the next '/', ':' or '.'.
176         const char* next_slash = next_slash_or_dot(p);
177         subpart = p;
178 
179         // Check whether p was pointing to one of those characters already.
180         if (next_slash == p) {
181             IF_VERBOSE_ASCODING_ERRORS(
182                 log_aserror(_("invalid path '%s' (p=next_slash=%s)"),
183                 path, next_slash);
184             );
185             return nullptr;
186         }
187 
188         if (next_slash) {
189             if (*next_slash == '.') {
190 
191                 if (!dot_allowed) {
192                     IF_VERBOSE_ASCODING_ERRORS(
193                         log_aserror(_("invalid path '%s' (dot not allowed "
194                                 "after having seen a slash)"), path);
195                     );
196                     return nullptr;
197                 }
198                 // No dot allowed after a double-dot.
199                 if (next_slash[1] == '.') dot_allowed = false;
200             }
201             else if (*next_slash == '/') {
202                 dot_allowed = false;
203             }
204 
205             // Cut off the slash and everything after it.
206             subpart.resize(next_slash - p);
207         }
208 
209         assert(subpart[0] != ':');
210 
211         // No more components to scan
212         if (subpart.empty()) break;
213 
214         const ObjectURI subpartURI(getURI(vm, subpart));
215 
216         if (!firstElementParsed) {
217             as_object* element(nullptr);
218 
219             do {
220                 // Try scope stack
221                 if (scope) {
222                     for (size_t i = scope->size(); i > 0; --i) {
223                         as_object* obj = (*scope)[i-1];
224 
225                         element = getElement(obj, subpartURI);
226                         if (element) break;
227                     }
228                     if (element) break;
229                 }
230 
231                 // Try current target  (if any)
232                 assert(env == getObject(ctx.target()));
233                 if (env) {
234                     element = getElement(env, subpartURI);
235                     if (element) break;
236                 }
237 
238                 // Looking for _global ?
239                 as_object* global = vm.getGlobal();
240                 const bool nocase = caseless(*global);
241 
242                 if (swfVersion > 5) {
243                     const ObjectURI::CaseEquals ce(st, nocase);
244                     if (ce(subpartURI, globalURI)) {
245                         element = global;
246                         break;
247                     }
248                 }
249 
250                 // Look for globals.
251                 element = getElement(global, subpartURI);
252 
253             } while (0);
254 
255             if (!element) return nullptr;
256 
257             env = element;
258             firstElementParsed = true;
259         }
260         else {
261 
262             assert(env);
263             as_object* element = getElement(env, subpartURI);
264             if (!element) return nullptr;
265             env = element;
266         }
267 
268         if (!next_slash) break;
269 
270         p = next_slash + 1;
271     }
272     return env;
273 }
274 
275 int
get_version() const276 as_environment::get_version() const
277 {
278     return _vm.getSWFVersion();
279 }
280 
281 void
markReachableResources() const282 as_environment::markReachableResources() const
283 {
284     if (_target) _target->setReachable();
285     if (_original_target) _original_target->setReachable();
286 }
287 
288 as_value
getVariable(const as_environment & env,const std::string & varname,const as_environment::ScopeStack & scope,as_object ** retTarget)289 getVariable(const as_environment& env, const std::string& varname,
290         const as_environment::ScopeStack& scope, as_object** retTarget)
291 {
292     // Path lookup rigamarole.
293     std::string path;
294     std::string var;
295 
296     if (parsePath(varname, path, var)) {
297         // TODO: let find_target return generic as_objects, or use 'with' stack,
298         //       see player2.swf or bug #18758 (strip.swf)
299         as_object* target = findObject(env, path, &scope);
300 
301         if (target) {
302             as_value val;
303             target->get_member(getURI(env.getVM(), var), &val);
304             if (retTarget) *retTarget = target;
305             return val;
306         }
307         else {
308             return as_value();
309         }
310     }
311 
312     if (varname.find('/') != std::string::npos &&
313             varname.find(':') == std::string::npos) {
314 
315         // Consider it all a path ...
316         as_object* target = findObject(env, varname, &scope);
317         if (target) {
318             // ... but only if it resolves to a sprite
319             DisplayObject* d = target->displayObject();
320             MovieClip* m = d ? d->to_movie() : nullptr;
321             if (m) return as_value(getObject(m));
322         }
323     }
324     return getVariableRaw(env, varname, scope, retTarget);
325 }
326 
327 void
setVariable(const as_environment & env,const std::string & varname,const as_value & val,const as_environment::ScopeStack & scope)328 setVariable(const as_environment& env, const std::string& varname,
329     const as_value& val, const as_environment::ScopeStack& scope)
330 {
331     IF_VERBOSE_ACTION(
332         log_action(_("-------------- %s = %s"), varname, val);
333     );
334 
335     // Path lookup rigamarole.
336     std::string path;
337     std::string var;
338 
339     if (parsePath(varname, path, var)) {
340         as_object* target = findObject(env, path, &scope);
341         if (target) {
342             target->set_member(getURI(env.getVM(), var), val);
343         }
344         else {
345             IF_VERBOSE_ASCODING_ERRORS(
346             log_aserror(_("Path target '%s' not found while setting %s=%s"),
347                 path, varname, val);
348             );
349         }
350         return;
351     }
352 
353     setVariableRaw(env, varname, val, scope);
354 }
355 
356 bool
delVariable(const as_environment & ctx,const std::string & varname,const as_environment::ScopeStack & scope)357 delVariable(const as_environment& ctx, const std::string& varname,
358     const as_environment::ScopeStack& scope)
359 {
360     // varname must be a plain variable name; no path parsing.
361     assert(varname.find_first_of(":/.") == std::string::npos);
362 
363     VM& vm = ctx.getVM();
364 
365     const ObjectURI& varkey = getURI(vm, varname);
366 
367     // Check the with-stack.
368     for (size_t i = scope.size(); i > 0; --i) {
369         as_object* obj = scope[i - 1];
370 
371         if (obj) {
372             std::pair<bool, bool> ret = obj->delProperty(varkey);
373             if (ret.first) {
374                 return ret.second;
375             }
376         }
377     }
378 
379     // Check locals for deletion.
380     if (vm.calling() && deleteLocal(vm.currentCall().locals(), varname)) {
381         return true;
382     }
383 
384     // Try target
385     std::pair<bool, bool> ret = getObject(ctx.target())->delProperty(varkey);
386     if (ret.first) {
387         return ret.second;
388     }
389 
390     // TODO: try 'this' ? Add a testcase for it !
391 
392     // Try _global
393     return vm.getGlobal()->delProperty(varkey).second;
394 }
395 
396 bool
parsePath(const std::string & var_path_in,std::string & path,std::string & var)397 parsePath(const std::string& var_path_in, std::string& path, std::string& var)
398 {
399 
400     const size_t lastDotOrColon = var_path_in.find_last_of(":.");
401     if (lastDotOrColon == std::string::npos) return false;
402 
403     const std::string p(var_path_in, 0, lastDotOrColon);
404     const std::string v(var_path_in, lastDotOrColon + 1, var_path_in.size());
405 
406 #ifdef DEBUG_TARGET_FINDING
407     log_debug("path: %s, var: %s", p, v);
408 #endif
409 
410     if (p.empty()) return false;
411 
412     // The path may apparently not end with more than one colon.
413     if (p.size() > 1 && !p.compare(p.size() - 2, 2, "::")) return false;
414 
415     path = p;
416     var = v;
417 
418     return true;
419 }
420 
421 namespace {
422 
423 static bool
validRawVariableName(const std::string & varname)424 validRawVariableName(const std::string& varname)
425 {
426     if (varname.empty()) return false;
427 
428     if (varname[0] == '.') return false;
429 
430     if (varname[0] == ':' &&
431             varname.find_first_of(":.", 1) == std::string::npos) {
432         return false;
433     }
434     return (varname.find(":::") == std::string::npos);
435 }
436 
437 // No path rigamarole.
438 void
setVariableRaw(const as_environment & env,const std::string & varname,const as_value & val,const as_environment::ScopeStack & scope)439 setVariableRaw(const as_environment& env, const std::string& varname,
440     const as_value& val, const as_environment::ScopeStack& scope)
441 {
442 
443     if (!validRawVariableName(varname)) {
444         IF_VERBOSE_ASCODING_ERRORS(
445             log_aserror(_("Won't set invalid raw variable name: %s"), varname);
446         );
447         return;
448     }
449 
450     VM& vm = env.getVM();
451     const ObjectURI& varkey = getURI(vm, varname);
452 
453     // in SWF5 and lower, scope stack should just contain 'with' elements
454 
455     // Check the scope stack.
456     for (size_t i = scope.size(); i > 0; --i) {
457         as_object* obj = scope[i - 1];
458         if (obj && obj->set_member(varkey, val, true)) {
459             return;
460         }
461     }
462 
463     const int swfVersion = vm.getSWFVersion();
464     if (swfVersion < 6 && vm.calling()) {
465        if (setLocal(vm.currentCall().locals(), varname, val)) return;
466     }
467 
468     // TODO: shouldn't _target be in the scope chain ?
469     if (env.target()) getObject(env.target())->set_member(varkey, val);
470     else if (env.get_original_target()) {
471         getObject(env.get_original_target())->set_member(varkey, val);
472     }
473     else {
474         log_error(_("as_environment::setVariableRaw(%s, %s): neither current target nor original target are defined, can't set the variable"),
475            varname, val);
476     }
477 }
478 
479 as_value
getVariableRaw(const as_environment & env,const std::string & varname,const as_environment::ScopeStack & scope,as_object ** retTarget)480 getVariableRaw(const as_environment& env, const std::string& varname,
481     const as_environment::ScopeStack& scope, as_object** retTarget)
482 {
483 
484     if (!validRawVariableName(varname)) {
485         IF_VERBOSE_ASCODING_ERRORS(
486             log_aserror(_("Won't get invalid raw variable name: %s"), varname);
487         );
488         return as_value();
489     }
490 
491     as_value val;
492 
493     VM& vm = env.getVM();
494     const int swfVersion = vm.getSWFVersion();
495     const ObjectURI& key = getURI(vm, varname);
496 
497     // Check the scope stack.
498     for (size_t i = scope.size(); i > 0; --i) {
499 
500         as_object* obj = scope[i - 1];
501         if (obj && obj->get_member(key, &val)) {
502             if (retTarget) *retTarget = obj;
503             return val;
504         }
505     }
506 
507     // Check locals for getting them
508     // for SWF6 and up locals should be in the scope stack
509     if (swfVersion < 6 && vm.calling()) {
510        if (findLocal(vm.currentCall().locals(), varname, val, retTarget)) {
511            return val;
512        }
513     }
514 
515     // Check current target members. TODO: shouldn't target be in scope stack ?
516     if (env.target()) {
517         as_object* obj = getObject(env.target());
518         assert(obj);
519         if (obj->get_member(key, &val)) {
520             if (retTarget) *retTarget = obj;
521             return val;
522         }
523     }
524     else if (env.get_original_target()) {
525         as_object* obj = getObject(env.get_original_target());
526         assert(obj);
527         if (obj->get_member(key, &val)) {
528             if (retTarget) *retTarget = obj;
529             return val;
530         }
531     }
532 
533     // AS1 has neither "this" nor any global object.
534     if (swfVersion < 5) return as_value();
535 
536     const ObjectURI::CaseEquals eq(getVM(env).getStringTable(),
537             swfVersion < 7);
538 
539     // Looking for "this"
540     if (eq(key, NSV::PROP_THIS)) {
541         val.set_as_object(getObject(env.get_original_target()));
542         if (retTarget) *retTarget = nullptr;
543         return val;
544     }
545 
546     as_object* global = vm.getGlobal();
547     if (swfVersion > 5 && eq(key, NSV::PROP_uGLOBAL)) {
548 #ifdef GNASH_DEBUG_GET_VARIABLE
549         log_debug("Took %s as _global, returning _global", varname);
550 #endif
551         // The "_global" ref was added in SWF6
552         if (retTarget) *retTarget = nullptr; // correct ??
553         return as_value(global);
554     }
555 
556     if (global->get_member(key, &val)) {
557 #ifdef GNASH_DEBUG_GET_VARIABLE
558         log_debug("Found %s in _global", varname);
559 #endif
560         if (retTarget) *retTarget = global;
561         return val;
562     }
563 
564     // Fallback.
565     // FIXME, should this be log_error?  or log_swferror?
566     IF_VERBOSE_ASCODING_ERRORS(
567         log_aserror(_("reference to non-existent variable '%s'"), varname);
568     );
569 
570     return as_value();
571 }
572 
573 bool
getLocal(as_object & locals,const std::string & name,as_value & ret)574 getLocal(as_object& locals, const std::string& name, as_value& ret)
575 {
576     return locals.get_member(getURI(getVM(locals), name), &ret);
577 }
578 
579 bool
findLocal(as_object & locals,const std::string & varname,as_value & ret,as_object ** retTarget)580 findLocal(as_object& locals, const std::string& varname, as_value& ret,
581         as_object** retTarget)
582 {
583 
584     if (getLocal(locals, varname, ret)) {
585         if (retTarget) *retTarget = &locals;
586         return true;
587     }
588 
589     return false;
590 }
591 
592 bool
deleteLocal(as_object & locals,const std::string & varname)593 deleteLocal(as_object& locals, const std::string& varname)
594 {
595     return locals.delProperty(getURI(getVM(locals), varname)).second;
596 }
597 
598 bool
setLocal(as_object & locals,const std::string & varname,const as_value & val)599 setLocal(as_object& locals, const std::string& varname, const as_value& val)
600 {
601     Property* prop = locals.getOwnProperty(getURI(getVM(locals), varname));
602     if (!prop) return false;
603     prop->setValue(locals, val);
604     return true;
605 }
606 
607 as_object*
getElement(as_object * obj,const ObjectURI & uri)608 getElement(as_object* obj, const ObjectURI& uri)
609 {
610     DisplayObject* d = obj->displayObject();
611     if (d) return d->pathElement(uri);
612     return getPathElement(*obj, uri);
613 }
614 
615 static const char*
next_slash_or_dot(const char * word)616 next_slash_or_dot(const char* word)
617 {
618     for (const char* p = word; *p; p++) {
619         if (*p == '.' && p[1] == '.') {
620             ++p;
621         }
622         else if (*p == '.' || *p == '/' || *p == ':') {
623             return p;
624         }
625     }
626     return nullptr;
627 }
628 
629 } // unnamed namespace
630 
631 DisplayObject*
findTarget(const as_environment & env,const std::string & path)632 findTarget(const as_environment& env, const std::string& path)
633 {
634     return get<DisplayObject>(findObject(env, path));
635 }
636 
637 
638 string_table&
getStringTable(const as_environment & env)639 getStringTable(const as_environment& env)
640 {
641     return env.getVM().getStringTable();
642 }
643 
644 movie_root&
getRoot(const as_environment & env)645 getRoot(const as_environment& env)
646 {
647     return env.getVM().getRoot();
648 }
649 
650 Global_as&
getGlobal(const as_environment & env)651 getGlobal(const as_environment& env)
652 {
653     return *env.getVM().getGlobal();
654 }
655 
656 int
getSWFVersion(const as_environment & env)657 getSWFVersion(const as_environment& env)
658 {
659     return env.getVM().getSWFVersion();
660 }
661 
662 } // end of gnash namespace
663 
664 
665 
666 // Local Variables:
667 // mode: C++
668 // indent-tabs-mode: t
669 // End:
670