1 /*
2  *  This file is part of the KDE libraries
3  *  Copyright (C) 1999-2003 Harri Porten (porten@kde.org)
4  *  Copyright (C) 2001-2003 David Faure (faure@kde.org)
5  *  Copyright (C) 2003 Apple Computer, Inc.
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Library General Public
9  *  License as published by the Free Software Foundation; either
10  *  version 2 of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Library General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Library General Public
18  *  License along with this library; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "kjs_binding.h"
23 
24 #include <config-khtml.h>
25 #if HAVE_VALGRIND_MEMCHECK_H
26 
27 #include <valgrind/memcheck.h>
28 #define VALGRIND_SUPPORT
29 
30 #endif
31 
32 #include "kjs_dom.h"
33 #include "kjs_range.h"
34 
35 #include <dom/css_stylesheet.h>
36 #include <dom/dom_exception.h>
37 #include <dom/dom2_range.h>
38 #include <dom/dom3_xpath.h>
39 #include <xml/dom2_eventsimpl.h>
40 #include <khtmlpart_p.h>
41 
42 #include "khtml_debug.h"
43 #include <kparts/browserextension.h>
44 #include <kmessagebox.h>
45 #include <QTextDocument> // Qt::escape
46 
47 #ifdef KJS_DEBUGGER
48 #include "debugger/debugwindow.h"
49 #endif
50 
51 #include <QList>
52 
53 #include <assert.h>
54 
55 using namespace KJSDebugger;
56 
57 namespace KJS
58 {
59 
toString(ExecState *) const60 UString DOMObject::toString(ExecState *) const
61 {
62     return "[object " + className() + "]";
63 }
64 
65 HashMap<void *, DOMObject *> *ScriptInterpreter::s_allDomObjects;
66 
67 typedef QList<ScriptInterpreter *> InterpreterList;
68 static InterpreterList *interpreterList;
69 
ScriptInterpreter(JSGlobalObject * global,khtml::ChildFrame * frame)70 ScriptInterpreter::ScriptInterpreter(JSGlobalObject *global, khtml::ChildFrame *frame)
71     : Interpreter(global), m_frame(frame),
72       m_evt(nullptr), m_inlineCode(false), m_timerCallback(false)
73 {
74 #ifdef KJS_VERBOSE
75     qCDebug(KHTML_LOG) << "ScriptInterpreter::ScriptInterpreter " << this << " for part=" << m_frame;
76 #endif
77     if (!interpreterList) {
78         interpreterList = new InterpreterList;
79     }
80     interpreterList->append(this);
81 }
82 
~ScriptInterpreter()83 ScriptInterpreter::~ScriptInterpreter()
84 {
85 #ifdef KJS_VERBOSE
86     qCDebug(KHTML_LOG) << "ScriptInterpreter::~ScriptInterpreter " << this << " for part=" << m_frame;
87 #endif
88     assert(interpreterList && interpreterList->contains(this));
89     interpreterList->removeAll(this);
90     if (interpreterList->isEmpty()) {
91         delete interpreterList;
92         interpreterList = nullptr;
93     }
94 }
95 
forgetDOMObject(void * objectHandle)96 void ScriptInterpreter::forgetDOMObject(void *objectHandle)
97 {
98     if (!interpreterList) {
99         return;
100     }
101 
102     for (int i = 0; i < interpreterList->size(); ++i) {
103         interpreterList->at(i)->m_domObjects.remove(objectHandle);
104     }
105     allDomObjects()->remove(objectHandle);
106 }
107 
mark(bool isMain)108 void ScriptInterpreter::mark(bool isMain)
109 {
110     Interpreter::mark(isMain);
111 #ifdef KJS_VERBOSE
112     qCDebug(KHTML_LOG) << "ScriptInterpreter::mark " << this << " marking " << m_domObjects.size() << " DOM objects";
113 #endif
114     HashMap<void *, DOMObject *>::iterator it = m_domObjects.begin();
115     while (it != m_domObjects.end()) {
116         DOMObject *obj = it->second;
117         if (obj->shouldMark()) {
118             obj->mark();
119         }
120         ++it;
121     }
122 }
123 
part() const124 KParts::ReadOnlyPart *ScriptInterpreter::part() const
125 {
126     return m_frame->m_part.data();
127 }
128 
isWindowOpenAllowed() const129 bool ScriptInterpreter::isWindowOpenAllowed() const
130 {
131     if (m_evt) {
132         int id = m_evt->handle()->id();
133         bool eventOk = ( // mouse events
134                            id == DOM::EventImpl::CLICK_EVENT ||
135                            id == DOM::EventImpl::MOUSEUP_EVENT || id == DOM::EventImpl::MOUSEDOWN_EVENT ||
136                            id == DOM::EventImpl::KHTML_ECMA_CLICK_EVENT || id == DOM::EventImpl::KHTML_ECMA_DBLCLICK_EVENT ||
137                            // keyboard events
138                            id == DOM::EventImpl::KEYDOWN_EVENT || id == DOM::EventImpl::KEYPRESS_EVENT ||
139                            id == DOM::EventImpl::KEYUP_EVENT ||
140                            // other accepted events
141                            id == DOM::EventImpl::SELECT_EVENT || id == DOM::EventImpl::CHANGE_EVENT ||
142                            id == DOM::EventImpl::SUBMIT_EVENT);
143         // qCDebug(KHTML_LOG) << "Window.open, smart policy: id=" << id << " eventOk=" << eventOk;
144         if (eventOk) {
145             return true;
146         }
147     } else { // no event
148         if (m_inlineCode && !m_timerCallback) {
149             // This is the <a href="javascript:window.open('...')> case -> we let it through
150             return true;
151             // qCDebug(KHTML_LOG) << "Window.open, smart policy, no event, inline code -> ok";
152         } else { // This is the <script>window.open(...)</script> case or a timer callback -> block it
153             // qCDebug(KHTML_LOG) << "Window.open, smart policy, no event, <script> tag -> refused";
154         }
155     }
156     return false;
157 }
158 
159 bool ScriptInterpreter::s_disableCPUGuard = false;
160 
startCPUGuard()161 void ScriptInterpreter::startCPUGuard()
162 {
163     if (s_disableCPUGuard) {
164         return;
165     }
166 
167     unsigned time = 5000;
168 #ifdef VALGRIND_SUPPORT
169     if (RUNNING_ON_VALGRIND) {
170         time *= 50;
171     }
172 #endif
173 
174     setTimeoutTime(time);
175     startTimeoutCheck();
176 }
177 
stopCPUGuard()178 void ScriptInterpreter::stopCPUGuard()
179 {
180     if (s_disableCPUGuard) {
181         return;
182     }
183     stopTimeoutCheck();
184 }
185 
shouldInterruptScript() const186 bool ScriptInterpreter::shouldInterruptScript() const
187 {
188 #ifdef KJS_DEBUGGER
189     if (DebugWindow::isBlocked()) {
190         return false;
191     }
192 #endif
193 
194     // qCDebug(KHTML_LOG) << "alarmhandler";
195     return KMessageBox::warningYesNo(nullptr, i18n("A script on this page is causing KHTML to freeze. If it continues to run, other applications may become less responsive.\nDo you want to stop the script?"), i18n("JavaScript"), KGuiItem(i18n("&Stop Script")), KStandardGuiItem::cont(), "kjscupguard_alarmhandler") == KMessageBox::Yes;
196 }
197 
UString(const QString & d)198 UString::UString(const QString &d)
199 {
200     if (d.length() > UString::maxUChars()) {
201         m_rep = &Rep::null;
202         return;
203     }
204 
205     unsigned int len = d.length();
206     UChar *dat = static_cast<UChar *>(fastMalloc(sizeof(UChar) * len));
207     memcpy(dat, d.unicode(), len * sizeof(UChar));
208     m_rep = UString::Rep::create(dat, len);
209 }
210 
UString(const DOM::DOMString & d)211 UString::UString(const DOM::DOMString &d)
212 {
213     if (d.isNull()) {
214         // we do a conversion here as null DOMStrings shouldn't cross
215         // the boundary to kjs. They should either be empty strings
216         // or explicitly converted to KJS::Null via getString().
217         m_rep = &Rep::empty;
218         return;
219     }
220     if (d.length() > UString::maxUChars()) {
221         m_rep = &Rep::null;
222         return;
223     }
224 
225     unsigned int len = d.length();
226     UChar *dat = static_cast<UChar *>(fastMalloc(sizeof(UChar) * len));
227     memcpy(dat, d.unicode(), len * sizeof(UChar));
228     m_rep = UString::Rep::create(dat, len);
229 }
230 
domString() const231 DOM::DOMString UString::domString() const
232 {
233     return DOM::DOMString((QChar *) data(), size());
234 }
235 
qstring() const236 QString UString::qstring() const
237 {
238     return QString((QChar *) data(), size());
239 }
240 
domString() const241 DOM::DOMString Identifier::domString() const
242 {
243     return DOM::DOMString((QChar *) data(), size());
244 }
245 
qstring() const246 QString Identifier::qstring() const
247 {
248     return QString((QChar *) data(), size());
249 }
250 
valueGetterAdapter(ExecState * exec,JSObject *,const Identifier &,const PropertySlot & slot)251 JSValue *valueGetterAdapter(ExecState *exec, JSObject *, const Identifier &, const PropertySlot &slot)
252 {
253     Q_UNUSED(exec);
254     return static_cast<JSValue *>(slot.customValue());
255 }
256 
toNode(JSValue * val)257 DOM::NodeImpl *toNode(JSValue *val)
258 {
259     JSObject *obj = val->getObject();
260     if (!obj || !obj->inherits(&DOMNode::info)) {
261         return nullptr;
262     }
263 
264     const DOMNode *dobj = static_cast<const DOMNode *>(obj);
265     return dobj->impl();
266 }
267 
getStringOrNull(DOM::DOMString s)268 JSValue *getStringOrNull(DOM::DOMString s)
269 {
270     if (s.isNull()) {
271         return jsNull();
272     } else {
273         return jsString(s);
274     }
275 }
276 
valueToStringWithNullCheck(ExecState * exec,JSValue * val)277 DOM::DOMString valueToStringWithNullCheck(ExecState *exec, JSValue *val)
278 {
279     if (val->isNull()) {
280         return DOM::DOMString();
281     }
282     return val->toString(exec).domString();
283 }
284 
ValueToVariant(ExecState * exec,JSValue * val)285 QVariant ValueToVariant(ExecState *exec, JSValue *val)
286 {
287     QVariant res;
288     switch (val->type()) {
289     case BooleanType:
290         res = QVariant(val->toBoolean(exec));
291         break;
292     case NumberType:
293         res = QVariant(val->toNumber(exec));
294         break;
295     case StringType:
296         res = QVariant(val->toString(exec).qstring());
297         break;
298     default:
299         // everything else will be 'invalid'
300         break;
301     }
302     return res;
303 }
304 
setDOMException(ExecState * exec,int internalCode)305 void setDOMException(ExecState *exec, int internalCode)
306 {
307     if (internalCode == 0 || exec->hadException()) {
308         return;
309     }
310 
311     const char *type = nullptr;
312 
313     DOMString name;
314     DOMString exceptionString;
315     JSObject *errorObject = nullptr;
316     int code = -1; // this will get the public exception code,
317     // as opposed to the internal one
318 
319     // ### we should probably introduce classes for things other than range + core
320     if (DOM::RangeException::isRangeExceptionCode(internalCode)) {
321         type = "DOM Range";
322         code = internalCode - DOM::RangeException::_EXCEPTION_OFFSET;
323         name = DOM::RangeException::codeAsString(code);
324         errorObject = new RangeException(exec);
325     } else if (DOM::CSSException::isCSSExceptionCode(internalCode)) {
326         type = "CSS";
327         code = internalCode - DOM::CSSException::_EXCEPTION_OFFSET;
328         name = DOM::CSSException::codeAsString(code);
329     } else if (DOM::EventException::isEventExceptionCode(internalCode)) {
330         type = "DOM Events";
331         code = internalCode - DOM::EventException::_EXCEPTION_OFFSET;
332         name = DOM::EventException::codeAsString(code);
333     } else if (DOM::XPathException::isXPathExceptionCode(internalCode)) {
334         type = "XPath";
335         code = internalCode - DOM::XPathException::_EXCEPTION_OFFSET;
336         name = DOM::XPathException::codeAsString(code);
337     } else {
338         // Generic DOM.
339         type = "DOM";
340         code = internalCode;
341         name = DOM::DOMException::codeAsString(code);
342         errorObject = new JSDOMException(exec);
343     }
344 
345     if (!errorObject) {
346         // 100 characters is a big enough buffer, because there are:
347         //   13 characters in the message
348         //   10 characters in the longest type, "DOM Events"
349         //   27 characters in the longest name, "NO_MODIFICATION_ALLOWED_ERR"
350         //   20 or so digits in the longest integer's ASCII form (even if int is 64-bit)
351         //   1 byte for a null character
352         // That adds up to about 70 bytes.
353         char buffer[100];
354 
355         if (!name.isEmpty()) {
356             qsnprintf(buffer, 99, "%s: %s Exception %d", name.string().toLatin1().data(), type, code);
357         } else {
358             qsnprintf(buffer, 99, "%s Exception %d", type, code);
359         }
360         errorObject = throwError(exec, GeneralError, buffer);
361     } else {
362         exec->setException(errorObject);
363     }
364 
365     errorObject->put(exec, exec->propertyNames().name, jsString(UString(type) + " Exception"));
366     errorObject->put(exec, exec->propertyNames().message, jsString(name));
367     errorObject->put(exec, "code", jsNumber(code));
368 }
369 
valueToString(KJS::JSValue * value)370 QString valueToString(KJS::JSValue *value)
371 {
372     switch (value->type()) {
373     case KJS::NumberType: {
374         double v = 0.0;
375         value->getNumber(v);
376         return QString::number(v);
377     }
378     case KJS::BooleanType:
379         return value->getBoolean() ? "true" : "false";
380     case KJS::StringType: {
381         KJS::UString s;
382         value->getString(s);
383         return '"' + s.qstring() + '"';
384     }
385     case KJS::UndefinedType:
386         return "undefined";
387     case KJS::NullType:
388         return "null";
389     case KJS::ObjectType:
390         return "[object " + static_cast<KJS::JSObject *>(value)->className().qstring() + "]";
391     case KJS::GetterSetterType:
392     case KJS::UnspecifiedType:
393     default:
394         return QString();
395     }
396 }
397 
exceptionToString(ExecState * exec,JSValue * exceptionObj)398 QString exceptionToString(ExecState *exec, JSValue *exceptionObj)
399 {
400     QString exceptionMsg = valueToString(exceptionObj);
401 
402     // Since we purposefully bypass toString, we need to figure out
403     // string serialization ourselves.
404     //### might be easier to export class info for ErrorInstance ---
405 
406     JSObject *valueObj = exceptionObj->getObject();
407     JSValue  *protoObj = valueObj ? valueObj->prototype() : nullptr;
408 
409     bool exception   = false;
410     bool syntaxError = false;
411     if (protoObj == exec->lexicalInterpreter()->builtinSyntaxErrorPrototype()) {
412         exception   = true;
413         syntaxError = true;
414     }
415 
416     if (protoObj == exec->lexicalInterpreter()->builtinErrorPrototype()          ||
417             protoObj == exec->lexicalInterpreter()->builtinEvalErrorPrototype()      ||
418             protoObj == exec->lexicalInterpreter()->builtinReferenceErrorPrototype() ||
419             protoObj == exec->lexicalInterpreter()->builtinRangeErrorPrototype()     ||
420             protoObj == exec->lexicalInterpreter()->builtinTypeErrorPrototype()      ||
421             protoObj == exec->lexicalInterpreter()->builtinURIErrorPrototype()) {
422         exception = true;
423     }
424 
425     if (!exception) {
426         return exceptionMsg;
427     }
428 
429     // Clear exceptions temporarily so we can get/call a few things.
430     // We memorize the old exception first, of course. Note that
431     // This is not always the same as exceptionObj since we may be
432     //  asked to translate a non-active exception
433     JSValue *oldExceptionObj = exec->exception();
434     exec->clearException();
435 
436     // We want to serialize the syntax errors ourselves, to provide the line number.
437     // The URL is in "sourceURL" and the line is in "line"
438     // ### TODO: Perhaps we want to use 'sourceId' in case of eval contexts.
439     if (syntaxError) {
440         JSValue *lineValue = valueObj->get(exec, "line");
441         JSValue *urlValue  = valueObj->get(exec, "sourceURL");
442 
443         int      line = lineValue->toNumber(exec);
444         QString  url  = urlValue->toString(exec).qstring();
445         exceptionMsg = i18n("Parse error at %1 line %2",
446                             url.toHtmlEscaped(), line + 1);
447     } else {
448         // ### it's still not 100% safe to call toString here, even on
449         // native exception objects, since someone might have changed the toString property
450         // of the exception prototype, but I'll punt on this case for now.
451         exceptionMsg = exceptionObj->toString(exec).qstring();
452     }
453     exec->setException(oldExceptionObj);
454     return exceptionMsg;
455 }
456 
457 } //namespace KJS
458