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