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