1 /* This file is part of the KDE project
2 Copyright (C) 2010 Maksim Orlovich <maksim@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18 */
19
20 #include "kjs_scriptable.h"
21 #include "kjs_binding.h"
22 #include "kjs_proxy.h"
23 #include "kjs_dom.h"
24 #include "khtmlpart_p.h"
25
26 namespace KJS
27 {
28
scriptableNull()29 static QVariant scriptableNull()
30 {
31 return QVariant::fromValue(ScriptableExtension::Null());
32 }
33
isException(const QVariant & v)34 static bool isException(const QVariant &v)
35 {
36 return v.canConvert<ScriptableExtension::Exception>();
37 }
38
isFuncRef(const QVariant & v)39 static bool isFuncRef(const QVariant &v)
40 {
41 return v.canConvert<ScriptableExtension::FunctionRef>();
42 }
43
isForeignObject(const QVariant & v)44 static bool isForeignObject(const QVariant &v)
45 {
46 return v.canConvert<ScriptableExtension::Object>();
47 }
48
exception(const char * msg)49 QVariant exception(const char *msg)
50 {
51 qCWarning(KHTML_LOG) << msg;
52 return QVariant::fromValue(ScriptableExtension::Exception(QString::fromLatin1(msg)));
53 }
54
55 //------------------------------------------------------------------------------
56
57 // will return with owner = 0 on failure.
grabRoot(ScriptableExtension * ext)58 static ScriptableExtension::Object grabRoot(ScriptableExtension *ext)
59 {
60 ScriptableExtension::Object o;
61
62 if (!ext) {
63 return o;
64 }
65
66 // Grab root, make sure it's an actual object.
67 QVariant root = ext->rootObject();
68 if (!isForeignObject(root)) {
69 // Might still need to release it if it's a function
70 ScriptableExtension::releaseValue(root);
71
72 return o;
73 }
74
75 o = root.value<ScriptableExtension::Object>();
76 return o;
77 }
78
79 // a couple helpers for client code.
pluginRootGet(ExecState * exec,ScriptableExtension * ext,const KJS::Identifier & i,PropertySlot & slot)80 bool pluginRootGet(ExecState *exec, ScriptableExtension *ext, const KJS::Identifier &i, PropertySlot &slot)
81 {
82 ScriptableExtension::Object rootObj = grabRoot(ext);
83 if (!rootObj.owner) {
84 return false;
85 }
86
87 QVariant v = rootObj.owner->get(nullptr /* ### we don't expect leaves to check credentials*/,
88 rootObj.objId, i.qstring());
89
90 bool ok = false;
91 if (!isException(v)) {
92 getImmediateValueSlot(nullptr, ScriptableOperations::importValue(exec, v, true), slot);
93 ok = true;
94 }
95
96 rootObj.owner->release(rootObj.objId);
97 return ok;
98 }
99
pluginRootPut(ExecState *,ScriptableExtension * ext,const KJS::Identifier & i,JSValue * v)100 bool pluginRootPut(ExecState * /*exec*/, ScriptableExtension *ext, const KJS::Identifier &i, JSValue *v)
101 {
102 ScriptableExtension::Object rootObj = grabRoot(ext);
103 if (!rootObj.owner) {
104 return false;
105 }
106
107 QVariant qv = ScriptableOperations::exportValue(v, true);
108 bool ok = rootObj.owner->put(nullptr, rootObj.objId, i.qstring(), qv);
109 ScriptableExtension::releaseValue(qv);
110
111 rootObj.owner->release(rootObj.objId);
112 return ok;
113 }
114
115 //------------------------------------------------------------------------------
116 // KJS peer wrapping external objects
117
118 const ClassInfo WrapScriptableObject::info = { " WrapScriptableObject", nullptr, nullptr, nullptr };
119
WrapScriptableObject(ExecState *,Type t,ScriptableExtension * owner,quint64 objId,const QString & field)120 WrapScriptableObject::WrapScriptableObject(ExecState * /*exec*/, Type t,
121 ScriptableExtension *owner, quint64 objId,
122 const QString &field):
123 objExtension(owner), objId(objId), field(field), type(t), refsByUs(1), tableKey(owner)
124 {
125 owner->acquire(objId);
126 }
127
~WrapScriptableObject()128 WrapScriptableObject::~WrapScriptableObject()
129 {
130 if (ScriptableExtension *o = objExtension.data()) {
131 for (int r = 0; r < refsByUs; ++r) {
132 o->release(objId);
133 }
134 }
135
136 ScriptableExtension::Object obj(tableKey, objId);
137 if (type == Object) {
138 ScriptableOperations::importedObjects()->remove(obj);
139 } else {
140 ScriptableOperations::importedFunctions()->remove(ScriptableExtension::FunctionRef(obj, field));
141 }
142 }
143
reportRef()144 void WrapScriptableObject::reportRef()
145 {
146 ++refsByUs;
147 }
148
doGet(ExecState * exec,const ScriptableExtension::Object & o,const QString & field,bool * ok)149 QVariant WrapScriptableObject::doGet(ExecState *exec, const ScriptableExtension::Object &o,
150 const QString &field, bool *ok)
151 {
152 *ok = false;
153
154 if (!o.owner) { // such as when was constructed from null .data();
155 return QVariant();
156 }
157
158 QVariant v = o.owner->get(principal(exec), o.objId, field);
159
160 if (!isException(v)) {
161 *ok = true;
162 }
163 return v;
164 }
165
resolveReferences(ExecState * exec,const ScriptableExtension::FunctionRef & f,bool * ok)166 ScriptableExtension::Object WrapScriptableObject::resolveReferences(
167 ExecState *exec,
168 const ScriptableExtension::FunctionRef &f,
169 bool *ok)
170 {
171 QVariant v = doGet(exec, f.base, f.field, ok);
172 if (*ok) {
173 // See what sort of type we got.
174 if (isForeignObject(v)) {
175 return v.value<ScriptableExtension::Object>();
176 } else if (isFuncRef(v)) {
177 return resolveReferences(exec, v.value<ScriptableExtension::FunctionRef>(), ok);
178 } else {
179 // We got a primitive. We don't care for those.
180 *ok = false;
181 }
182 }
183 return ScriptableExtension::Object();
184 }
185
resolveAnyReferences(ExecState * exec,bool * ok)186 ScriptableExtension::Object WrapScriptableObject::resolveAnyReferences(ExecState *exec, bool *ok)
187 {
188 ScriptableExtension::Object obj(objExtension.data(), objId);
189
190 if (type == FunctionRef) {
191 obj = resolveReferences(exec, ScriptableExtension::FunctionRef(obj, field), ok);
192 }
193
194 if (!obj.owner) {
195 *ok = false;
196 }
197
198 return obj;
199 }
200
getOwnPropertySlot(ExecState * exec,const Identifier & i,PropertySlot & slot)201 bool WrapScriptableObject::getOwnPropertySlot(ExecState *exec, const Identifier &i,
202 PropertySlot &slot)
203 {
204 bool ok;
205 ScriptableExtension::Object actualObj = resolveAnyReferences(exec, &ok);
206
207 if (!ok) {
208 return false;
209 }
210
211 QVariant v = doGet(exec, actualObj, i.qstring(), &ok);
212
213 if (!ok) {
214 return false;
215 }
216
217 return getImmediateValueSlot(this, ScriptableOperations::importValue(exec, v, true), slot);
218 }
219
put(ExecState * exec,const Identifier & i,JSValue * value,int)220 void WrapScriptableObject::put(ExecState *exec, const Identifier &i, JSValue *value, int)
221 {
222 // ### Do we swallow failure, or what?
223 bool ok;
224 ScriptableExtension::Object actualObj = resolveAnyReferences(exec, &ok);
225
226 if (!ok) {
227 return;
228 }
229
230 QVariant sv = ScriptableOperations::exportValue(value, true);
231 actualObj.owner->put(principal(exec), actualObj.objId, i.qstring(), sv);
232 ScriptableExtension::releaseValue(sv);
233 }
234
deleteProperty(ExecState * exec,const Identifier & i)235 bool WrapScriptableObject::deleteProperty(ExecState *exec, const Identifier &i)
236 {
237 bool ok;
238 ScriptableExtension::Object actualObj = resolveAnyReferences(exec, &ok);
239
240 if (!ok) {
241 return false;
242 }
243
244 return actualObj.owner->removeProperty(principal(exec),
245 actualObj.objId, i.qstring());
246 }
247
exportArgs(const List & l)248 ScriptableExtension::ArgList WrapScriptableObject::exportArgs(const List &l)
249 {
250 ScriptableExtension::ArgList ol;
251 for (int p = 0; p < l.size(); ++p) {
252 ol.append(ScriptableOperations::exportValue(l.at(p), true));
253 }
254 return ol;
255 }
256
releaseArgs(ScriptableExtension::ArgList & a)257 void WrapScriptableObject::releaseArgs(ScriptableExtension::ArgList &a)
258 {
259 for (int p = 0; p < a.size(); ++p) {
260 ScriptableOperations::releaseValue(a[p]);
261 }
262 }
263
callAsFunction(ExecState * exec,JSObject *,const List & args)264 JSValue *WrapScriptableObject::callAsFunction(ExecState *exec, JSObject */*thisObj*/, const List &args)
265 {
266 QVariant res;
267
268 if (ScriptableExtension *base = objExtension.data()) {
269 ScriptableExtension::ArgList sargs = exportArgs(args);
270 if (type == Object) {
271 res = base->callAsFunction(principal(exec), objId, sargs);
272 } else {
273 res = base->callFunctionReference(principal(exec), objId, field, sargs);
274 }
275 releaseArgs(sargs);
276 }
277
278 // Problem. Throw an exception.
279 if (!res.isValid() || isException(res)) {
280 return throwError(exec, GeneralError, "Call to plugin function failed");
281 } else {
282 return ScriptableOperations::importValue(exec, res, true);
283 }
284 }
285
construct(ExecState * exec,const List & args)286 JSObject *WrapScriptableObject::construct(ExecState *exec, const List &args)
287 {
288 QVariant res;
289
290 bool ok;
291 ScriptableExtension::Object actualObj = resolveAnyReferences(exec, &ok);
292 if (ok) {
293 ScriptableExtension::ArgList sargs = exportArgs(args);
294 res = actualObj.owner->callAsConstructor(principal(exec), actualObj.objId,
295 sargs);
296 releaseArgs(sargs);
297 }
298
299 if (!res.isValid() || isException(res)) {
300 return throwError(exec, GeneralError, "Call to plugin ctor failed");
301 } else {
302 JSValue *v = ScriptableOperations::importValue(exec, res, true);
303 return v->toObject(exec);
304 }
305 }
306
getOwnPropertyNames(ExecState * exec,PropertyNameArray & a,PropertyMap::PropertyMode mode)307 void WrapScriptableObject::getOwnPropertyNames(ExecState *exec, PropertyNameArray &a, PropertyMap::PropertyMode mode)
308 {
309 JSObject::getOwnPropertyNames(exec, a, mode);
310
311 bool ok;
312 ScriptableExtension::Object actualObj = resolveAnyReferences(exec, &ok);
313 if (ok) {
314 QStringList out;
315 if (actualObj.owner->enumerateProperties(principal(exec), actualObj.objId, &out)) {
316 foreach (const QString &s, out) {
317 a.add(Identifier(s));
318 }
319 }
320 }
321 }
322
toString(ExecState *) const323 UString WrapScriptableObject::toString(ExecState *) const
324 {
325 QString iface;
326 if (ScriptableExtension *se = objExtension.data()) {
327 iface = QString::fromLatin1(se->metaObject()->className());
328 } else {
329 iface = QString::fromLatin1("detached");
330 }
331
332 if (type == FunctionRef) {
333 return QString(QLatin1String("[function ImportedScriptable:") + iface + QLatin1Char('/') + field + QLatin1Char(']'));
334 } else {
335 return QString(QLatin1String("[object ImportedScriptable:") + iface + QLatin1Char(']'));
336 }
337 }
338
principal(ExecState * exec)339 ScriptableExtension *WrapScriptableObject::principal(ExecState *exec)
340 {
341 KJS::ScriptInterpreter *si = static_cast<KJS::ScriptInterpreter *>(exec->dynamicInterpreter());
342 KParts::ReadOnlyPart *part = si->part();
343 if (!part) {
344 return nullptr;
345 }
346
347 return ScriptableExtension::childObject(part);
348 }
349
350 //-----------------------------------------------------------------------------
351 // conversion stuff
352
353 /**
354 SECURITY: For the conversion helpers, it is assumed that 'exec' corresponds
355 to the appropriate principal.
356 */
357
importObject(ExecState * exec,const QVariant & v,bool alreadyRefd)358 JSObject *ScriptableOperations::importObject(ExecState *exec, const QVariant &v, bool alreadyRefd)
359 {
360 ScriptableExtension::Object obj = v.value<ScriptableExtension::Object>();
361 if (JSObject *our = tryGetNativeObject(obj)) {
362 return our;
363 } else {
364 // Create a wrapper, and register the import, this is just for
365 // hashconsing.
366 if (WrapScriptableObject *old = importedObjects()->value(obj)) {
367 if (alreadyRefd) {
368 old->reportRef();
369 }
370 return old;
371 } else {
372 WrapScriptableObject *wrap =
373 new WrapScriptableObject(exec, WrapScriptableObject::Object,
374 obj.owner, obj.objId);
375 importedObjects()->insert(obj, wrap);
376 if (alreadyRefd) {
377 wrap->reportRef();
378 }
379 return wrap;
380 }
381 }
382 }
383
384 // Note: this is used to convert a full function ref to a value,
385 // which loses the this-binding in the native case. We do keep the pair in the
386 // external case, inside the wrapper, since the method might not have an own
387 // name, and we'd like to be able to refer to it.
importFunctionRef(ExecState * exec,const QVariant & v,bool)388 JSValue *ScriptableOperations::importFunctionRef(ExecState *exec, const QVariant &v, bool /*alreadyRefd*/)
389 {
390 ScriptableExtension::FunctionRef fr = v.value<ScriptableExtension::FunctionRef>();
391
392 if (JSObject *base = tryGetNativeObject(fr.base)) {
393 return base->get(exec, Identifier(fr.field));
394 } else {
395 if (WrapScriptableObject *old = importedFunctions()->value(fr)) {
396 return old;
397 } else {
398 WrapScriptableObject *wrap =
399 new WrapScriptableObject(exec, WrapScriptableObject::FunctionRef,
400 fr.base.owner, fr.base.objId, fr.field);
401 importedFunctions()->insert(fr, wrap);
402 return wrap;
403 }
404 }
405 }
406
importValue(ExecState * exec,const QVariant & v,bool alreadyRefd)407 JSValue *ScriptableOperations::importValue(ExecState *exec, const QVariant &v, bool alreadyRefd)
408 {
409 if (v.canConvert<ScriptableExtension::FunctionRef>()) {
410 return importFunctionRef(exec, v, alreadyRefd);
411 }
412 if (v.canConvert<ScriptableExtension::Object>()) {
413 return importObject(exec, v, alreadyRefd);
414 }
415 if (v.canConvert<ScriptableExtension::Null>()) {
416 return jsNull();
417 }
418 if (v.canConvert<ScriptableExtension::Undefined>()) {
419 return jsUndefined();
420 }
421 if (v.type() == QVariant::Bool) {
422 return jsBoolean(v.toBool());
423 }
424 if (v.type() == QVariant::String) {
425 return jsString(v.toString());
426 }
427 if (v.canConvert<double>()) {
428 return jsNumber(v.toDouble());
429 }
430 qCWarning(KHTML_LOG) << "conversion from " << v << "failed";
431 return jsNull();
432 }
433
importArgs(ExecState * exec,const ArgList & args)434 List ScriptableOperations::importArgs(ExecState *exec, const ArgList &args)
435 {
436 // Args are not pre-ref'd for us..
437 List out;
438 for (int i = 0; i < args.size(); ++i) {
439 out.append(importValue(exec, args[i], false));
440 }
441 return out;
442 }
443
exportNativeObject(JSObject * o,bool preRef)444 ScriptableExtension::Object ScriptableOperations::exportNativeObject(JSObject *o, bool preRef)
445 {
446 assert(!o->inherits(&WrapScriptableObject::info));
447 // We're exporting our own. Add to export table if needed.
448 // Use the pointer as an ID.
449 if (!exportedObjects()->contains(o)) {
450 exportedObjects()->insert(o, 0);
451 }
452 if (preRef) {
453 ++(*exportedObjects())[o];
454 }
455
456 return ScriptableExtension::Object(ScriptableOperations::self(),
457 reinterpret_cast<quint64>(o));
458 }
459
exportObject(JSObject * o,bool preRef)460 QVariant ScriptableOperations::exportObject(JSObject *o, bool preRef)
461 {
462 // XSS checks are done at get time, so if we have a value here, we can
463 // export it.
464 if (o->inherits(&WrapScriptableObject::info)) {
465 // Re-exporting external one. That's easy.
466 WrapScriptableObject *wo = static_cast<WrapScriptableObject *>(o);
467 if (ScriptableExtension *owner = wo->objExtension.data()) {
468 QVariant v = QVariant::fromValue(ScriptableExtension::Object(owner, wo->objId));
469 if (preRef) {
470 acquireValue(v);
471 }
472 return v;
473 } else {
474 qCWarning(KHTML_LOG) << "export of an object of a destroyed extension. Returning null";
475 return scriptableNull();
476 }
477 } else {
478 return QVariant::fromValue(exportNativeObject(o, preRef));
479 }
480 }
481
exportFuncRef(JSObject * base,const QString & field,bool preRef)482 QVariant ScriptableOperations::exportFuncRef(JSObject *base, const QString &field, bool preRef)
483 {
484 ScriptableExtension::Object exportBase = exportNativeObject(base, preRef);
485 return QVariant::fromValue(ScriptableExtension::FunctionRef(exportBase, field));
486 }
487
exportValue(JSValue * v,bool preRef)488 QVariant ScriptableOperations::exportValue(JSValue *v, bool preRef)
489 {
490 switch (v->type()) {
491 case NumberType:
492 return QVariant::fromValue(v->getNumber());
493 case BooleanType:
494 return QVariant::fromValue(v->getBoolean());
495 case NullType:
496 return QVariant::fromValue(ScriptableExtension::Null());
497 case StringType:
498 return QVariant::fromValue(v->getString().qstring());
499 case ObjectType:
500 return exportObject(v->getObject(), preRef);
501 case UndefinedType:
502 default:
503 return QVariant::fromValue(ScriptableExtension::Undefined());
504 }
505 }
506
507 //-----------------------------------------------------------------------------
508 // operations
509 QHash<JSObject *, int> *ScriptableOperations::s_exportedObjects = nullptr;
510 QHash<ScriptableExtension::Object, WrapScriptableObject *> *ScriptableOperations::s_importedObjects = nullptr;
511 QHash<ScriptableExtension::FunctionRef, WrapScriptableObject *> *ScriptableOperations::s_importedFunctions = nullptr;
512 ScriptableOperations *ScriptableOperations::s_instance = nullptr;
513
importedObjects()514 QHash<ScriptableExtension::Object, WrapScriptableObject *> *ScriptableOperations::importedObjects()
515 {
516 if (!s_importedObjects) {
517 s_importedObjects = new QHash<Object, WrapScriptableObject *>;
518 }
519 return s_importedObjects;
520 }
521
importedFunctions()522 QHash<ScriptableExtension::FunctionRef, WrapScriptableObject *> *ScriptableOperations::importedFunctions()
523 {
524 if (!s_importedFunctions) {
525 s_importedFunctions = new QHash<FunctionRef, WrapScriptableObject *>();
526 }
527 return s_importedFunctions;
528 }
529
530 // A little helper for marking exportedObjects. We need this since we have
531 // an unclear runtime with respect to interpreter.
532 class ScriptableOperationsMarker: public JSObject
533 {
534 public:
mark()535 void mark() override
536 {
537 JSObject::mark();
538 ScriptableOperations::self()->mark();
539 }
540 };
541
exportedObjects()542 QHash<JSObject *, int> *ScriptableOperations::exportedObjects()
543 {
544 if (!s_exportedObjects) {
545 s_exportedObjects = new QHash<JSObject *, int>;
546
547 gcProtect(new ScriptableOperationsMarker());
548 }
549 return s_exportedObjects;
550 }
551
self()552 ScriptableOperations *ScriptableOperations::self()
553 {
554 if (!s_instance) {
555 s_instance = new ScriptableOperations;
556 }
557 return s_instance;
558 }
559
ScriptableOperations()560 ScriptableOperations::ScriptableOperations(): ScriptableExtension(nullptr)
561 {}
562
~ScriptableOperations()563 ScriptableOperations::~ScriptableOperations()
564 {
565 assert(false);
566 }
567
tryGetNativeObject(const Object & sObj)568 JSObject *ScriptableOperations::tryGetNativeObject(const Object &sObj)
569 {
570 if (ScriptableOperations *o = qobject_cast<ScriptableOperations *>(sObj.owner)) {
571 return o->objectForId(sObj.objId);
572 } else {
573 return nullptr;
574 }
575 }
576
handleReturn(ExecState * exec,JSValue * v)577 QVariant ScriptableOperations::handleReturn(ExecState *exec, JSValue *v)
578 {
579 if (exec->hadException()) {
580 JSValue *e = exec->exception();
581 exec->clearException();
582
583 QString msg = QLatin1String("KJS exception");
584
585 if (JSObject *eo = e->getObject()) {
586 JSValue *msgVal = eo->get(exec, exec->propertyNames().message);
587 if (!msgVal->isUndefined()) {
588 msg = msgVal->toString(exec).qstring();
589 }
590
591 // in case the get failed too.
592 exec->clearException();
593 }
594
595 return QVariant::fromValue(ScriptableExtension::Exception(msg));
596 }
597
598 return exportValue(v, true);
599 }
600
callAsFunction(ScriptableExtension * caller,quint64 objId,const ArgList & args)601 QVariant ScriptableOperations::callAsFunction(ScriptableExtension *caller,
602 quint64 objId, const ArgList &args)
603 {
604 ExecState *exec = execStateForPrincipal(caller);
605 if (!exec) {
606 return exception("No scripting context or frame");
607 }
608
609 JSObject *fn = objectForId(objId);
610 if (!fn || !fn->implementsCall()) {
611 return exception("Call on a non-object or non-calleable");
612 }
613
614 JSValue *res = fn->callAsFunction(exec, exec->dynamicInterpreter()->globalObject(),
615 importArgs(exec, args));
616 return handleReturn(exec, res);
617 }
618
callAsConstructor(ScriptableExtension * caller,quint64 objId,const ArgList & args)619 QVariant ScriptableOperations::callAsConstructor(ScriptableExtension *caller, quint64 objId, const ArgList &args)
620 {
621 ExecState *exec = execStateForPrincipal(caller);
622 if (!exec) {
623 return exception("No scripting context or frame");
624 }
625
626 JSObject *fn = objectForId(objId);
627 if (!fn || !fn->implementsConstruct()) {
628 return exception("new on a non-constructor");
629 }
630
631 JSValue *res = fn->construct(exec, importArgs(exec, args));
632 return handleReturn(exec, res);
633 }
634
callFunctionReference(ScriptableExtension * caller,quint64 objId,const QString & f,const ArgList & args)635 QVariant ScriptableOperations::callFunctionReference(ScriptableExtension *caller,
636 quint64 objId, const QString &f, const ArgList &args)
637 {
638 ExecState *exec = execStateForPrincipal(caller);
639
640 if (!exec) {
641 return exception("No scripting context or frame");
642 }
643
644 JSObject *base = objectForId(objId);
645 if (!base) {
646 return exception("Call with an invalid base");
647 }
648
649 JSValue *kid = base->get(exec, Identifier(f));
650 if (!kid->isObject() || exec->hadException() || !kid->getObject()->implementsCall()) {
651 exec->clearException();
652 return exception("Reference did not resolve to a function");
653 }
654
655 // Whee..
656 JSValue *res = kid->getObject()->callAsFunction(exec, base, importArgs(exec, args));
657 return handleReturn(exec, res);
658 }
659
hasProperty(ScriptableExtension * caller,quint64 objId,const QString & propName)660 bool ScriptableOperations::hasProperty(ScriptableExtension *caller, quint64 objId, const QString &propName)
661 {
662 ExecState *exec = execStateForPrincipal(caller);
663 if (!exec) {
664 exception("No scripting context or frame");
665 return false;
666 }
667
668 JSObject *o = objectForId(objId);
669 if (!o) {
670 exception("hasProperty on a non-object");
671 return false;
672 }
673
674 return o->hasProperty(exec, Identifier(propName));
675 }
676
get(ScriptableExtension * caller,quint64 objId,const QString & propName)677 QVariant ScriptableOperations::get(ScriptableExtension *caller, quint64 objId, const QString &propName)
678 {
679 ExecState *exec = execStateForPrincipal(caller);
680 if (!exec) {
681 return exception("No scripting context or frame");
682 }
683
684 JSObject *o = objectForId(objId);
685 if (!o) {
686 return exception("get on a non-object");
687 }
688
689 JSValue *v = o->get(exec, Identifier(propName));
690 if (!exec->hadException() && v->isObject() && v->getObject()->implementsCall()) {
691 // For a function we got OK, return a reference.
692 return exportFuncRef(o, propName, true);
693 } else {
694 // straight return for other stuff or failure
695 return handleReturn(exec, v);
696 }
697 }
698
put(ScriptableExtension * caller,quint64 objId,const QString & propName,const QVariant & value)699 bool ScriptableOperations::put(ScriptableExtension *caller, quint64 objId,
700 const QString &propName, const QVariant &value)
701 {
702 ExecState *exec = execStateForPrincipal(caller);
703 if (!exec) {
704 return false; // ### debug warn?
705 }
706
707 JSObject *o = objectForId(objId);
708 if (!o) {
709 return false;
710 }
711
712 o->put(exec, Identifier(propName), importValue(exec, value, false));
713 if (!exec->hadException()) {
714 return true;
715 } else {
716 exec->clearException();
717 return false;
718 }
719 }
720
removeProperty(ScriptableExtension * caller,quint64 objId,const QString & propName)721 bool ScriptableOperations::removeProperty(ScriptableExtension *caller,
722 quint64 objId, const QString &propName)
723 {
724 ExecState *exec = execStateForPrincipal(caller);
725 if (!exec) {
726 return false; // ### debug warn?
727 }
728
729 JSObject *o = objectForId(objId);
730 if (!o) {
731 return false;
732 }
733
734 bool ok = o->deleteProperty(exec, Identifier(propName));
735 if (exec->hadException()) {
736 exec->clearException();
737 ok = false;
738 }
739 return ok;
740 }
741
enumerateProperties(ScriptableExtension * caller,quint64 objId,QStringList * result)742 bool ScriptableOperations::enumerateProperties(ScriptableExtension *caller,
743 quint64 objId, QStringList *result)
744 {
745 ExecState *exec = execStateForPrincipal(caller);
746 if (!exec) {
747 return false; // ### debug warn?
748 }
749
750 JSObject *o = objectForId(objId);
751 if (!o) {
752 return false;
753 }
754
755 PropertyNameArray pa;
756 o->getPropertyNames(exec, pa);
757
758 for (int i = 0; i < pa.size(); ++i) {
759 result->append(pa[i].qstring());
760 }
761 return true;
762 }
763
partForPrincipal(ScriptableExtension * caller)764 KHTMLPart *ScriptableOperations::partForPrincipal(ScriptableExtension *caller)
765 {
766 // We implement our security checks by delegating to the KHTMLPart corresponding
767 // to the given plugin's principal (which is the KHTMLPart owning it), and letting
768 // the underlying implementation perform them (which it has to anyway)
769
770 if (KHTMLPartScriptable *o = qobject_cast<KHTMLPartScriptable *>(caller)) {
771 return o->m_part;
772 } else {
773 // We always set the host on child extensions.
774 return partForPrincipal(caller->host());
775 }
776 }
777
execStateForPrincipal(ScriptableExtension * caller)778 ExecState *ScriptableOperations::execStateForPrincipal(ScriptableExtension *caller)
779 {
780 KHTMLPart *part = partForPrincipal(caller);
781
782 if (!part) {
783 return nullptr;
784 }
785
786 KJSProxy *proxy = KJSProxy::proxy(part);
787 if (!proxy) {
788 return nullptr;
789 }
790
791 KJS::Interpreter *i = proxy->interpreter();
792 if (!i) {
793 return nullptr;
794 }
795
796 return i->globalExec();
797 }
798
acquire(quint64 objId)799 void ScriptableOperations::acquire(quint64 objId)
800 {
801 JSObject *ptr = objectForId(objId);
802
803 if (ptr) {
804 ++(*exportedObjects())[ptr];
805 } else {
806 assert(false);
807 }
808 }
809
release(quint64 objId)810 void ScriptableOperations::release(quint64 objId)
811 {
812 JSObject *ptr = objectForId(objId);
813
814 if (ptr) {
815 int newRC = --(*exportedObjects())[ptr];
816 if (newRC == 0) {
817 exportedObjects()->remove(ptr);
818 }
819 } else {
820 assert(false);
821 }
822 }
823
objectForId(quint64 objId)824 JSObject *ScriptableOperations::objectForId(quint64 objId)
825 {
826 JSObject *ptr = reinterpret_cast<JSObject *>(objId);
827
828 // Verify the pointer against the exports table for paranoia.
829 if (exportedObjects()->contains(ptr)) {
830 return ptr;
831 } else {
832 assert(false);
833 return nullptr;
834 }
835 }
836
mark()837 void ScriptableOperations::mark()
838 {
839 QHash<JSObject *, int> *exp = exportedObjects();
840
841 for (QHash<JSObject *, int>::iterator i = exp->begin(); i != exp->end(); ++i) {
842 JSObject *o = i.key();
843 if (i.value() && !o->marked()) {
844 o->mark();
845 }
846 }
847 }
848
849 //-----------------------------------------------------------------------------
850 // per-part stuff.
851
KHTMLPartScriptable(KHTMLPart * part)852 KHTMLPartScriptable::KHTMLPartScriptable(KHTMLPart *part):
853 ScriptableExtension(part), m_part(part)
854 {
855 }
856
interpreter()857 KJS::Interpreter *KHTMLPartScriptable::interpreter()
858 {
859 KJSProxy *proxy = KJSProxy::proxy(m_part);
860 if (!proxy) {
861 return nullptr;
862 }
863
864 return proxy->interpreter();
865 }
866
rootObject()867 QVariant KHTMLPartScriptable::rootObject()
868 {
869 if (KJS::Interpreter *i = interpreter()) {
870 return ScriptableOperations::exportObject(i->globalObject(), true);
871 }
872
873 return scriptableNull();
874 }
875
encloserForKid(KParts::ScriptableExtension * kid)876 QVariant KHTMLPartScriptable::encloserForKid(KParts::ScriptableExtension *kid)
877 {
878 ReadOnlyPart *childPart = qobject_cast<ReadOnlyPart *>(kid->parent());
879
880 KJS::Interpreter *i = interpreter();
881 if (!childPart || !i) {
882 return scriptableNull();
883 }
884
885 khtml::ChildFrame *f = m_part->frame(childPart);
886
887 if (!f) {
888 qCWarning(KHTML_LOG) << "unable to find frame. Huh?";
889 return scriptableNull();
890 }
891
892 // ### should this deal with fake window objects for iframes?
893 // ### this should never actually get an iframe once iframes are fixed
894 if (!f->m_partContainerElement.isNull()) {
895 return ScriptableOperations::exportValue(
896 getDOMNode(i->globalExec(), f->m_partContainerElement.data()), true);
897 }
898
899 qCWarning(KHTML_LOG) << "could not find the part container";
900 return scriptableNull();
901 }
902
903 // For paranoia: forward to ScriptOperations
acquire(quint64 objid)904 void KHTMLPartScriptable::acquire(quint64 objid)
905 {
906 ScriptableOperations::self()->acquire(objid);
907 }
908
release(quint64 objid)909 void KHTMLPartScriptable::release(quint64 objid)
910 {
911 ScriptableOperations::self()->release(objid);
912 }
913
evaluateScript(ScriptableExtension * caller,quint64 contextObjectId,const QString & code,ScriptLanguage lang)914 QVariant KHTMLPartScriptable::evaluateScript(ScriptableExtension *caller,
915 quint64 contextObjectId,
916 const QString &code,
917 ScriptLanguage lang)
918 {
919 // qCDebug(KHTML_LOG) << code;
920
921 if (lang != ECMAScript) {
922 return exception("unsupported language");
923 }
924
925 KHTMLPart *callingHtmlPart = ScriptableOperations::partForPrincipal(caller);
926 if (!callingHtmlPart) {
927 return exception("failed to resolve principal");
928 }
929
930 // Figure out the object we want to access, and its corresponding part.
931 JSObject *o = ScriptableOperations::objectForId(contextObjectId);
932 if (!o) {
933 return exception("invalid object");
934 }
935
936 DOM::NodeImpl *node = toNode(o);
937
938 // Presently, we only permit node contexts here.
939 // ### TODO: window contexts?
940 if (!node) {
941 return exception("non-Node context");
942 }
943
944 KHTMLPart *targetPart = node->document()->part();
945
946 if (!targetPart) {
947 return exception("failed to resolve destination principal");
948 }
949
950 if (!targetPart->checkFrameAccess(callingHtmlPart)) {
951 return exception("XSS check failed");
952 }
953
954 // wheee..
955 targetPart->executeScript(DOM::Node(node), code);
956
957 // ### TODO: Return value. Which is a completely different kind of QVariant.
958 // might just want to go to kJSProxy directly
959
960 return scriptableNull();
961 }
962
isScriptLanguageSupported(ScriptLanguage lang) const963 bool KHTMLPartScriptable::isScriptLanguageSupported(ScriptLanguage lang) const
964 {
965 return lang == ECMAScript;
966 }
967
setException(ScriptableExtension *,const QString & message)968 bool KHTMLPartScriptable::setException(ScriptableExtension * /*caller*/,
969 const QString &message)
970 {
971 qCWarning(KHTML_LOG) << "ignoring:" << message;
972 return false;
973 }
974
975 } // namespace KJS
976
977