1 #include "scriptengine.h"
2 #include "filechooser.h"
3 #include "smallUsefulFunctions.h"
4 #include "qdocumentsearch.h"
5 #include "scriptobject.h"
6 #include "buildmanager.h"
7 #include "latexdocument.h"
8 #include "texstudio.h"
9 #include "PDFDocument.h"
10 #include "usermacro.h"
11 #include <QCryptographicHash>
12 
13 //Q_DECLARE_METATYPE(QDocument *)
14 //Q_DECLARE_METATYPE(LatexDocuments *)
15 Q_DECLARE_METATYPE(BuildManager *)
16 Q_DECLARE_METATYPE(RunCommandFlags)
17 //Q_DECLARE_METATYPE(QAction *)
18 //Q_DECLARE_METATYPE(QMenu *)
19 
20 Q_DECLARE_METATYPE(SubScriptObject *)
21 //Q_DECLARE_METATYPE(QEditor *)
22 Q_DECLARE_METATYPE(QList<LatexDocument *>)
23 #ifndef NO_POPPLER_PREVIEW
24 //Q_DECLARE_METATYPE(PDFDocument *)
25 //Q_DECLARE_METATYPE(PDFWidget *)
26 Q_DECLARE_METATYPE(QList<PDFDocument *>)
27 #endif
28 //Q_DECLARE_METATYPE(QString *)
29 
30 BuildManager *scriptengine::buildManager = nullptr;
31 Texstudio *scriptengine::app = nullptr;
32 
33 QList<Macro> *scriptengine::macros = nullptr;
34 
35 int scriptengine::writeSecurityMode=2;
36 int scriptengine::readSecurityMode=2;
37 QStringList scriptengine::privilegedReadScripts=QStringList();
38 QStringList scriptengine::privilegedWriteScripts=QStringList();
39 
qScriptValueFromQObject(QJSEngine * engine,Tp const & qobject)40 template <typename Tp> QJSValue qScriptValueFromQObject(QJSEngine *engine, Tp const &qobject)
41 {
42 	return engine->newQObject(qobject);
43 }
44 
qScriptValueToQObject(const QJSValue & value,Tp & qobject)45 template <typename Tp> void qScriptValueToQObject(const QJSValue &value, Tp &qobject)
46 {
47 	qobject = qobject_cast<Tp>(value.toQObject());
48 }
49 
50 //template <typename Tp> int qScriptRegisterQObjectMetaType( QJSEngine *engine, const QJSValue &prototype = QJSValue(), Tp * /* dummy */ = nullptr)
51 //{
52 //	return qScriptRegisterMetaType<Tp>(engine, qScriptValueFromQObject, qScriptValueToQObject, prototype);
53 //}
54 
qScriptValueFromDocumentCursor(QJSEngine * engine,QDocumentCursor const & cursor)55 QJSValue qScriptValueFromDocumentCursor(QJSEngine *engine, QDocumentCursor const &cursor)
56 {
57 	return engine->newQObject(new QDocumentCursor(cursor));
58 }
qScriptValueToDocumentCursor(const QJSValue & value,QDocumentCursor & qobject)59 void qScriptValueToDocumentCursor(const QJSValue &value, QDocumentCursor &qobject)
60 {
61 	Q_ASSERT(value.toQObject());
62 	qobject = *qobject_cast<QDocumentCursor *>(value.toQObject());
63 }
64 
qScriptValueFromQList(QJSEngine * engine,QList<Tp> const & list)65 template <typename Tp> QJSValue qScriptValueFromQList(QJSEngine *engine, QList<Tp> const &list)
66 {
67 	QJSValue result = engine->newArray(list.size());
68 	for (int i = 0; i < list.size(); i++)
69 		result.setProperty(i,  engine->newQObject(list[i])); //engine->newVariant(QVariant::fromValue<Tp>(list[i])));
70 	return result;
71 }
72 
cursorFromValue(const QJSValue & value)73 QDocumentCursor cursorFromValue(const QJSValue &value)
74 {
75 	QDocumentCursor *c = qobject_cast<QDocumentCursor *> (value.toQObject());
76 	if (!c) {
77 #if (QT_VERSION<QT_VERSION_CHECK(6,0,0) && QT_VERSION>=QT_VERSION_CHECK(5,12,0))
78         //if (value.engine() ) value.engine()->throwError(scriptengine::tr("Expected cursor object")); //TODO Qt6 ?
79 #endif
80 		return QDocumentCursor();
81 	}
82 	return *c;
83 }
84 
qScriptValueFromQFileInfo(QJSEngine * engine,QFileInfo const & fi)85 QJSValue qScriptValueFromQFileInfo(QJSEngine *engine, QFileInfo const &fi)
86 {
87 	return engine->toScriptValue(fi.absoluteFilePath());
88 }
89 
qScriptValueToQFileInfo(const QJSValue & value,QFileInfo & fi)90 void qScriptValueToQFileInfo(const QJSValue &value, QFileInfo &fi)
91 {
92 	fi = QFileInfo(value.toString());
93 }
94 
95 
96 //map qstring* to scriptvalue object, for callbacks
97 
98 static quintptr PointerObsfuscationKey = 0;
99 
pointerObsfuscationKey()100 quintptr pointerObsfuscationKey()   //hide true pointers from scripts
101 {
102 	while (PointerObsfuscationKey == 0) {
103 		for (unsigned int i = 0; i < sizeof(PointerObsfuscationKey); i++)
104 			PointerObsfuscationKey = (PointerObsfuscationKey << 8) | (rand() & 0xFF);
105 	}
106 	return PointerObsfuscationKey;
107 }
108 
getStrPtr(QJSValue value)109 QString *getStrPtr(QJSValue value)
110 {
111 	if (value.property("dataStore").isUndefined())
112 		return nullptr;
113 	bool ok = false;
114 	quintptr ptr = value.property("dataStore").toVariant().toULongLong(&ok);
115 	if (!ok || ptr == 0 || ptr == pointerObsfuscationKey())
116 		return nullptr;
117 	return reinterpret_cast<QString *>(ptr ^ pointerObsfuscationKey());
118 }
119 
getSetStrValue(QJSEngine *)120 QJSValue getSetStrValue(QJSEngine */*engine*/)
121 {
122 	/*bool setterMode = context->argumentCount() == 1;
123 	QString *s = getStrPtr(context->thisObject());
124 	if (setterMode && !s) {
125 		s = new QString();
126 		context->thisObject().setProperty("dataStore", engine->newVariant((quintptr)(s) ^ pointerObsfuscationKey()));
127 	}
128 	if (!s) return engine->undefinedValue();
129 	if (setterMode) {
130 		if (!needPrivileges(engine, "string setting", context->argument(0).toString())){
131 			delete s;
132 			return engine->undefinedValue();
133 		}
134 		*s = context->argument(0).toString();
135 	}
136 	return engine->newVariant(*s);*/
137 	return QJSValue();
138 }
139 
scriptengine(QObject * parent)140 scriptengine::scriptengine(QObject *parent) : QObject(parent), triggerId(-1), m_editor(nullptr), m_allowWrite(false)
141 {
142 	engine = new QJSEngine(this);
143     qmlRegisterType<QDocument>("com.txs.qmlcomponents", 1, 0, "QDocument");
144     //qmlRegisterAnonymousType<QDocumentCursor>("com.txs.qmlcomponents",1);
145     qmlRegisterType<QDocumentCursor>("com.txs.qmlcomponents",1,0,"QDocumentCursor");
146 	//qmlRegisterType<QFileInfo>();
147     qmlRegisterType<ProcessX>("com.txs.qmlcomponents", 1, 0, "ProcessX");
148     //qmlRegisterType<SubScriptObject>();
149     qmlRegisterType<Texstudio>("com.txs.qmlcomponents", 1, 0, "Texstudio");
150     qmlRegisterType<QAction>("com.txs.qmlcomponents", 1, 0, "QAction");
151     qmlRegisterType<QMenu>("com.txs.qmlcomponents", 1, 0, "QMenu");
152     //qmlRegisterType<LatexEditorView>("com.txs.qmlcomponents", 1, 0, "LatexEditorView");
153     qmlRegisterType<LatexDocument>("com.txs.qmlcomponents", 1, 0, "LatexDocument");
154     qmlRegisterType<LatexDocuments>("com.txs.qmlcomponents", 1, 0, "LatexDocuments");
155     qmlRegisterType<QEditor>("com.txs.qmlcomponents", 1, 0, "QEditor");
156 #ifndef NO_POPPLER_PREVIEW
157     //qmlRegisterType<PDFDocument>("com.txs.qmlcomponents", 1, 0, "PDFDocument");
158     //qmlRegisterType<PDFWidget>("com.txs.qmlcomponents", 1, 0, "PDFWidget");
159 #endif
160 
161 	//qmlRegisterType<QList<LatexDocument *> >();
162 #ifndef NO_POPPLER_PREVIEW
163     //qmlRegisterType<QList<PDFDocument *> >("com.txs.qmlcomponents", 1, 0, "ListPDF");
164 #endif
165 
166 	qRegisterMetaType<RunCommandFlags>();
167 
168     qmlRegisterType<BuildManager>("com.txs.qmlcomponents", 1, 0, "BuildManager");
169 
170     //qmlRegisterType<QKeySequence>("com.txs.qmlcomponents", 1, 0, "QKeySequence");
171 	//qmlRegisterType<QUILoader>();
172 
173     // use config
174     ConfigManagerInterface::getInstance()->registerOption("Scripts/Privileged Read Scripts", &privilegedReadScripts);
175     ConfigManagerInterface::getInstance()->registerOption("Scripts/Read Security Mode", &readSecurityMode, 1);
176     ConfigManagerInterface::getInstance()->registerOption("Scripts/Privileged Write Scripts", &privilegedWriteScripts);
177     ConfigManagerInterface::getInstance()->registerOption("Scripts/Write Security Mode", &writeSecurityMode, 1);
178 }
179 
~scriptengine()180 scriptengine::~scriptengine()
181 {
182 	engine->collectGarbage();
183 	delete engine;
184 	engine=nullptr;
185 	//don't delete globalObject, it has either been destroyed in run, or by another script
186 }
187 
setScript(const QString & script,bool allowWrite)188 void scriptengine::setScript(const QString &script, bool allowWrite)
189 {
190 	m_script = script;
191 	m_allowWrite = allowWrite;
192 }
193 
setEditorView(LatexEditorView * edView)194 void scriptengine::setEditorView(LatexEditorView *edView)
195 {
196 	REQUIRE(edView);
197 	m_editor = edView->editor;
198 	m_editorView = edView;
199 }
200 
run(const bool quiet)201 void scriptengine::run(const bool quiet)
202 {
203 	QJSValue jsApp=engine->newQObject(app);
204 	engine->globalObject().setProperty("app",jsApp);
205 	QQmlEngine::setObjectOwnership(app, QQmlEngine::CppOwnership);
206 
207 	QDocumentCursor c( m_editor ? m_editor->cursorHandle() : nullptr); //create from handle, so modifying the cursor in the script directly affects the actual cursor
208 	QJSValue cursorValue;
209 	if (m_editorView)
210 		engine->globalObject().setProperty("editorView", engine->newQObject(m_editorView));
211 
212     QJSValue scriptJS=engine->newQObject(this);
213     // add general debug/warn functions
214     engine->globalObject().setProperty("alert", scriptJS.property("alert"));
215     engine->globalObject().setProperty("information", scriptJS.property("information"));
216     engine->globalObject().setProperty("critical", scriptJS.property("critical"));
217     engine->globalObject().setProperty("warning", scriptJS.property("warning"));
218     engine->globalObject().setProperty("confirm", scriptJS.property("confirm"));
219     engine->globalObject().setProperty("confirmWarning", scriptJS.property("confirmWarning"));
220     engine->globalObject().setProperty("debug", scriptJS.property("debug"));
221 #ifndef QT_NO_DEBUG
222     engine->globalObject().setProperty("crash_assert", scriptJS.property("crash_assert"));
223 #endif
224     engine->globalObject().setProperty("crash_sigsegv", scriptJS.property("crash_sigsegv"));
225     engine->globalObject().setProperty("crash_sigfpe", scriptJS.property("crash_sigfpe"));
226     engine->globalObject().setProperty("crash_sigstack", scriptJS.property("crash_sigstack"));
227     engine->globalObject().setProperty("crash_loop", scriptJS.property("crash_loop"));
228     engine->globalObject().setProperty("crash_throw", scriptJS.property("crash_throw"));
229 
230     engine->globalObject().setProperty("readFile", scriptJS.property("readFile"));
231     engine->globalObject().setProperty("writeFile", scriptJS.property("writeFile"));
232     engine->globalObject().setProperty("hasPersistent", scriptJS.property("hasPersistent"));
233     engine->globalObject().setProperty("setPersistent", scriptJS.property("setPersistent"));
234     engine->globalObject().setProperty("getPersistent", scriptJS.property("getPersistent"));
235     engine->globalObject().setProperty("registerAsBackgroundScript", scriptJS.property("registerAsBackgroundScript"));
236     engine->globalObject().setProperty("system", scriptJS.property("system"));
237 
238 
239 	if (m_editor) {
240 		QJSValue editorValue = engine->newQObject(m_editor);
241 		QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
242 		QQmlEngine::setObjectOwnership(m_editor, QQmlEngine::CppOwnership);
243 		editorValue.setProperty("cutBuffer", m_editor->cutBuffer);
244 		editorValue.setProperty("insertSnippet", scriptJS.property("insertSnippet"));
245 		editorValue.setProperty("replaceSelectedText", scriptJS.property("replaceSelectedText"));
246 		editorValue.setProperty("search", scriptJS.property("searchFunction"));
247 		editorValue.setProperty("replace", scriptJS.property("replaceFunction"));
248 		editorValue.setProperty("save", scriptJS.property("save"));
249 		editorValue.setProperty("saveCopy", scriptJS.property("saveCopy"));
250 		engine->globalObject().setProperty("editor", editorValue);
251 
252 		cursorValue = engine->newQObject(&c);
253 		engine->globalObject().setProperty("cursor", cursorValue);
254 		QQmlEngine::setObjectOwnership(&c, QQmlEngine::CppOwnership);
255 
256 		QJSValue matches = engine->newArray(triggerMatches.size());
257 		for (int i = 0; i < triggerMatches.size(); i++) matches.setProperty(i, triggerMatches[i]);
258 		engine->globalObject().setProperty("triggerMatches", matches);
259 	}
260 	engine->globalObject().setProperty("triggerId", QJSValue(triggerId));
261 
262 	//engine->globalObject().setProperty("include", engine->newFunction(include));
263 	//QJSValue script= engine->newQObject(this);
264 
265     engine->globalObject().setProperty("setTimeout", scriptJS.property("setTimeout"));
266 
267     QJSValue qsMetaObject = engine->newQMetaObject(&QDocumentCursor::staticMetaObject);
268     engine->globalObject().setProperty("cursorEnums", qsMetaObject);
269     engine->globalObject().setProperty("QDocumentCursor", qsMetaObject);
270 
271 	QJSValue uidClass = engine->newQMetaObject(&UniversalInputDialogScript::staticMetaObject);
272 	engine->globalObject().setProperty("UniversalInputDialog", uidClass);
273 
274 	FileChooser flchooser(nullptr, scriptengine::tr("File Chooser"));
275 	engine->globalObject().setProperty("fileChooser", engine->newQObject(&flchooser));
276 
277 	engine->globalObject().setProperty("documentManager", engine->newQObject(&app->documents));
278 	QQmlEngine::setObjectOwnership(&app->documents, QQmlEngine::CppOwnership);
279 	engine->globalObject().setProperty("documents", qScriptValueFromQList(engine, app->documents.documents));
280 #ifndef NO_POPPLER_PREVIEW
281 	engine->globalObject().setProperty("pdfs", qScriptValueFromQList(engine, PDFDocument::documentList()));
282 #endif
283 	QJSValue bm = engine->newQObject(&app->buildManager);
284 	QQmlEngine::setObjectOwnership(&app->buildManager, QQmlEngine::CppOwnership);
285 
286 	//bm.setProperty("runCommand", engine->newFunction(buildManagerRunCommandWrapper));
287 	//bm.setProperty("commandLineRequested", engine->globalObject().property("buildManagerCommandLineRequestedWrapper"));
288 	engine->globalObject().setProperty("buildManager", bm);
289 	//connect(buildManager, SIGNAL(commandLineRequested(QString,QString*)), SLOT(buildManagerCommandLineRequestedWrapperSlot(const QString&, QString*)));
290 
291 	QJSValue result=engine->evaluate(m_script);
292 
293 	if (result.isError()) {
294 		QString error = QString(tr("Uncaught exception at line %1: %2\n")).arg(result.property("lineNumber").toInt()).arg(result.toString());
295 		qDebug() << error;
296         if(!quiet)
297             QMessageBox::critical(nullptr, tr("Script-Error"), error);
298 	}
299 
300 	if (m_editor) {
301 		if (!engine->globalObject().property("cursor").strictlyEquals(cursorValue))
302 			m_editor->setCursor(cursorFromValue(engine->globalObject().property("cursor")));
303 	}
304 
305 }
306 
insertSnippet(const QString & arg)307 void scriptengine::insertSnippet(const QString& arg)
308 {
309 	CodeSnippet cs(arg);
310 
311 	if (!m_editor) return;
312 	foreach (QDocumentCursor c, m_editor->cursors()) {
313 		cs.insertAt(m_editor, &c);
314 	}
315 }
316 
317 #if ( QT_VERSION >= QT_VERSION_CHECK(5,12,0) )
318     #define SCRIPT_ASSERT(condition,message)                     \
319         if(!(condition)){                                     \
320             engine -> throwError(scriptengine::tr(message));  \
321             return QJSValue();                                \
322         }
323 #else
324     #define SCRIPT_ASSERT(condition,message)           \
325         if(!(condition)){                           \
326             qDebug() << scriptengine::tr(message);  \
327             return QJSValue();                      \
328         }
329 #endif
330 
replaceSelectedText(QJSValue replacementText,QJSValue options)331 QJSValue scriptengine::replaceSelectedText(QJSValue replacementText,QJSValue options)
332 {
333 	bool noEmpty = false;
334 	bool onlyEmpty = false;
335 	bool append = false;
336 	bool prepend = false;
337 	bool macro = false;
338 
339 	if (!options.isUndefined() ) {
340         SCRIPT_ASSERT(options.isObject(), "2nd value needs to be an object")
341 		noEmpty = options.property("noEmpty").toBool();
342 		onlyEmpty = options.property("onlyEmpty").toBool();
343 		append = options.property("append").toBool();
344 		prepend = options.property("prepend").toBool();
345 		macro = options.property("macro").toBool();
346         SCRIPT_ASSERT(!macro || !(append || prepend), "Macro option cannot be combined with append or prepend option.") //well it could, but there is no good way to	define what should happen to the selection
347 	}
348 
349 
350 	QList<QDocumentCursor> cursors = m_editor->cursors();
351 	QList<PlaceHolder> newMacroPlaceholder = macro ? m_editor->getPlaceHolders() : QList<PlaceHolder>();
352 	QList<QDocumentCursor> newMacroCursors;
353 
354 	m_editor->document()->beginMacro();
355 	foreach (QDocumentCursor c, cursors) {
356 		QString st = c.selectedText();
357 		if (noEmpty && st.isEmpty()) continue;
358 		if (onlyEmpty && !st.isEmpty()) continue;
359 		QString newst;
360 		if (replacementText.isCallable()) {
361 			QJSValue cb = replacementText.call(QJSValueList() << engine->toScriptValue(st) << engine->newQObject(&c));
362 			newst = cb.toString();
363 		} else {
364 			newst = replacementText.toString();
365 		}
366 		if (!macro) {
367 			if (append && prepend) newst = newst + st + newst;
368 			else if (append) newst = st + newst;
369 			else if (prepend) newst = newst + st;
370 			c.replaceSelectedText(newst);
371 		} else {
372 			m_editor->clearPlaceHolders();
373 			m_editor->clearCursorMirrors();
374 			CodeSnippet cs(newst);
375 			cs.insertAt(m_editor, &c);
376 			newMacroPlaceholder << m_editor->getPlaceHolders();
377 			if (m_editor->cursor().isValid()) {
378 				newMacroCursors << m_editor->cursor();
379 				newMacroCursors.last().setAutoUpdated(true);
380 			} else newMacroCursors << c; //CodeSnippet does not select without placeholder. But here we do, since it is called replaceSelectedText
381 		}
382 	}
383 	m_editor->document()->endMacro();
384 	if (macro && (cursors.size() > 0 /*|| (append && prepend) disallowed*/)) { //inserting multiple macros destroyed the new cursors, we need to insert them again
385         if (noEmpty) {
386             foreach (QDocumentCursor c, cursors){
387                 if (c.isValid() && c.selectedText().isEmpty()) newMacroCursors << c;
388             }
389         }
390         if (onlyEmpty){
391             foreach (QDocumentCursor c, cursors) {
392                 if (c.isValid() && !c.selectedText().isEmpty()) newMacroCursors << c;
393             }
394         }
395         if (newMacroCursors.size()){
396 			m_editor->setCursor(newMacroCursors.first());
397         }
398         for (int i = 1; i < newMacroCursors.size(); i++){
399 			m_editor->addCursorMirror(newMacroCursors[i]);
400         }
401 		m_editor->replacePlaceHolders(newMacroPlaceholder);
402 	}
403 	return QJSValue();
404 }
405 
searchReplaceFunction(QJSValue searchText,QJSValue arg1,QJSValue arg2,QJSValue arg3,bool replace)406 QJSValue scriptengine::searchReplaceFunction(QJSValue searchText, QJSValue arg1, QJSValue arg2, QJSValue arg3, bool replace)
407 {
408 	//read arguments
409     SCRIPT_ASSERT(m_editor, "invalid object")
410     SCRIPT_ASSERT(!replace || !arg1.isUndefined(), "at least two arguments are required")
411     SCRIPT_ASSERT(!searchText.isUndefined() , "at least one argument is required")
412     SCRIPT_ASSERT(searchText.isString() || searchText.isRegExp(), "first argument must be a string or regexp")
413 	QDocumentSearch::Options flags = QDocumentSearch::Silent;
414     bool global = false, caseInsensitive = false;
415 	QString searchFor;
416 	if (searchText.isRegExp()) {
417 		flags |= QDocumentSearch::RegExp;
418 #if QT_VERSION<QT_VERSION_CHECK(5,14,0)
419         QRegExp r2=searchText.toVariant().toRegExp(); // toRegularExpression seems not to work <5.14
420         QRegularExpression r(r2.pattern());
421         caseInsensitive = (r2.caseSensitivity() == Qt::CaseInsensitive);
422         if(caseInsensitive){
423             r.setPatternOptions(r.patternOptions()|QRegularExpression::CaseInsensitiveOption);
424         }
425 #else
426         QRegularExpression r = searchText.toVariant().toRegularExpression();
427 #endif
428 		searchFor = r.pattern();
429         caseInsensitive = (r.patternOptions() & QRegularExpression::CaseInsensitiveOption)!=QRegularExpression::NoPatternOption;
430         //Q_ASSERT(caseInsensitive == searchText.property("ignoreCase").toBool()); //check assumption about javascript core
431 		global = searchText.property("global").toBool();
432 	} else searchFor = searchText.toString();
433 	QJSValue handler;
434 	QDocumentCursor m_scope = m_editor->document()->cursor(0, 0, m_editor->document()->lineCount(), 0);
435 	int handlerCount = 0;
436 	for (int i = 1; i < 4; i++){
437 		QJSValue args;
438 		switch (i)
439 		{
440 		case 1:
441 			args=arg1;
442 			break;
443 		case 2:
444 			args=arg2;
445 			break;
446 		case 3:
447 			args=arg3;
448 			break;
449 		}
450 		if(args.isUndefined())
451 			break;
452 		if (args.isString() || args.isCallable()) handlerCount++;
453 	}
454     SCRIPT_ASSERT(handlerCount <= (replace ? 3 : 2), "too many string or function arguments")
455 	for (int i = 1; i < 4; i++) {
456 		QJSValue a;
457 		switch (i)
458 		{
459 		case 1:
460 			a=arg1;
461 			break;
462 		case 2:
463 			a=arg2;
464 			break;
465 		case 3:
466 			a=arg3;
467 			break;
468 		}
469 		if(a.isUndefined())
470 			break;
471 		if (a.isCallable()) {
472             SCRIPT_ASSERT(handler.isUndefined(), "Multiple callbacks")
473 			handler = a;
474 		} else if (a.isString()) {
475 			if (!replace || handlerCount > 1) {
476 				QString s = a.toString().toLower();
477 				global = s.contains("g");
478 				caseInsensitive = s.contains("i");
479 				if (s.contains("w")) flags |= QDocumentSearch::WholeWords;
480 			} else {
481                 SCRIPT_ASSERT(handler.isUndefined(), "Multiple callbacks")
482 				handler = a;
483 			}
484 			handlerCount--;
485 		} else if (a.isNumber()) flags |= QDocumentSearch::Options((int)a.toNumber());
486         else if (a.isObject()) m_scope = cursorFromValue(a);
487         else SCRIPT_ASSERT(false, "Invalid argument")
488 	}
489     SCRIPT_ASSERT(!handler.isUndefined() || !replace, "No callback given")
490 	if (!caseInsensitive) flags |= QDocumentSearch::CaseSensitive;
491 	//search/replace
492 	QDocumentSearch search(m_editor, searchFor, flags);
493 	search.setScope(m_scope);
494 	if (replace && handler.isString()) {
495 		search.setReplaceText(handler.toString());
496 		search.setOption(QDocumentSearch::Replace, true);
497 		return search.next(false, global, false, false);
498 	}
499 	if (handler.isUndefined())
500 		return search.next(false, global, true, false);
501 	int count = 0;
502 	while (search.next(false, false, true, false) && search.cursor().isValid()) {
503 		count++;
504 		QDocumentCursor temp = search.cursor();
505 		QJSValue cb = handler.call(QJSValueList() << engine->newQObject(&temp));
506 		if (replace && !cb.isError()) {
507 			QDocumentCursor tmp = search.cursor();
508 			tmp.replaceSelectedText(cb.toString());
509 			search.setCursor(tmp.selectionEnd());
510 		}
511 		if (!global) break;
512 	}
513 	return count;
514 }
515 
searchFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)516 QJSValue scriptengine::searchFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)
517 {
518 	return searchReplaceFunction(searchFor,arg1,arg2,arg3, false);
519 }
520 
replaceFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)521 QJSValue scriptengine::replaceFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)
522 {
523 	return searchReplaceFunction(searchFor,arg1,arg2,arg3, true);
524 }
525 
alert(const QString & message)526 void scriptengine::alert(const QString &message)
527 {
528     UtilsUi::txsInformation(message);
529 }
530 
information(const QString & message)531 void scriptengine::information(const QString &message)
532 {
533     UtilsUi::txsInformation(message);
534 }
535 
critical(const QString & message)536 void scriptengine::critical(const QString &message)
537 {
538     UtilsUi::txsCritical(message);
539 }
540 
warning(const QString & message)541 void scriptengine::warning(const QString &message)
542 {
543     UtilsUi::txsWarning(message);
544 }
545 
confirm(const QString & message)546 bool scriptengine::confirm(const QString &message)
547 {
548     return UtilsUi::txsConfirm(message);
549 }
550 
confirmWarning(const QString & message)551 bool scriptengine::confirmWarning(const QString &message)
552 {
553     return UtilsUi::txsConfirmWarning(message);
554 }
555 
debug(const QString & message)556 void scriptengine::debug(const QString &message)
557 {
558     qDebug() << message;
559 }
560 #ifndef QT_NO_DEBUG
crash_assert()561 void scriptengine::crash_assert()
562 {
563     Q_ASSERT(false);
564 }
565 #endif
566 
crash_sigsegv()567 void scriptengine::crash_sigsegv()
568 {
569     if (!confirmWarning("Do you want to let txs crash with a SIGSEGV?")) return;
570     char *c = nullptr;
571     *c = 'A';
572 }
573 
574 int global0 = 0;
crash_sigfpe()575 void scriptengine::crash_sigfpe()
576 {
577     if (!confirmWarning("Do you want to let txs crash with a SIGFPE?")) return;
578     int x = 1 / global0;
579     Q_UNUSED(x)
580 }
581 
crash_stack()582 void scriptengine::crash_stack()
583 {
584     if (!confirmWarning("Do you want to let txs crash with a stack overflow?")) return;
585     int temp = global0;
586     crash_stack();
587     Q_UNUSED(temp)
588 }
589 
crash_loop()590 void scriptengine::crash_loop()
591 {
592     if (!confirmWarning("Do you want to let txs freeze with an endless loop?")) return;
593 //#ifndef QT_NO_DEBUG // only used in the Q_ASSERT statements: prevent unused variable warning in release build
594     int a = 1, b = 2, c = 3, d = 4;
595 //#endif
596     while (true) {
597         void *x = malloc(16);
598         free(x);
599         Q_ASSERT(a == 1);
600         Q_ASSERT(b == 2);
601         Q_ASSERT(c == 3);
602         Q_ASSERT(d == 4); //make sure, no register suddenly change
603     };
604 }
605 
crash_throw()606 void scriptengine::crash_throw()
607 {
608     if (!confirmWarning("Do you want to let txs crash with an exception?")) return;
609     throw "debug crash";
610 }
611 
612 
save(const QString fn)613 void scriptengine::save(const QString fn)
614 {
615     if(fn.isEmpty()){
616         m_editor->save();
617     }else{
618         m_editor->save(fn);
619     }
620 }
621 
saveCopy(const QString & fileName)622 void scriptengine::saveCopy(const QString &fileName)
623 {
624     m_editor->saveCopy(fileName);
625 }
626 
system(const QString & commandline,const QString & workingDirectory)627 ProcessX * scriptengine::system(const QString &commandline, const QString &workingDirectory)
628 {
629     if (!buildManager || !needWritePrivileges("system", commandline))
630         return nullptr;
631     ProcessX *p = nullptr;
632     if (commandline.contains(BuildManager::TXS_CMD_PREFIX) || !commandline.contains("|"))
633         p = buildManager->firstProcessOfDirectExpansion(commandline, QFileInfo());
634     else
635         p = buildManager->newProcessInternal(commandline, QFileInfo()); //use internal, so people can pass | to sh
636     if (!p) return nullptr;
637     connect(p, SIGNAL(finished(int, QProcess::ExitStatus)), p, SLOT(deleteLater()));
638     QMetaObject::invokeMethod(reinterpret_cast<QObject *>(app), "connectSubCommand", Q_ARG(ProcessX *, p), Q_ARG(bool, true));
639     if (!workingDirectory.isEmpty())
640         p->setWorkingDirectory(workingDirectory);
641     QString *buffer=new QString;
642     p->setStdoutBuffer(buffer);
643     p->startCommand();
644     p->waitForStarted();
645     return p;
646 }
647 
writeFile(const QString & filename,const QString & content)648 void scriptengine::writeFile(const QString &filename, const QString &content)
649 {
650     if (!needWritePrivileges("writeFile", filename))
651         return;
652     QFile f(filename);
653     if (!f.open(QFile::WriteOnly))
654         return;
655     f.write(content.toUtf8());
656     f.close();
657 }
658 
readFile(const QString & filename)659 QVariant scriptengine::readFile(const QString &filename)
660 {
661     if (!needReadPrivileges("readFile", filename))
662         return QVariant();
663     QFile f(filename);
664     if (!f.open(QFile::ReadOnly))
665         return QVariant();
666     QTextStream ts(&f);
667     ts.setAutoDetectUnicode(true);
668     return ts.readAll();
669 }
670 
hasReadPrivileges()671 bool scriptengine::hasReadPrivileges()
672 {
673     if (readSecurityMode == 0)
674         return false;
675     if (readSecurityMode == 2
676             || privilegedReadScripts.contains(getScriptHash())
677             || privilegedWriteScripts.contains(getScriptHash()))
678         return true;
679     return false;
680 
681 }
682 
hasWritePrivileges()683 bool scriptengine::hasWritePrivileges()
684 {
685     if (writeSecurityMode == 0)
686         return false;
687     if (writeSecurityMode == 2
688             || privilegedWriteScripts.contains(getScriptHash()))
689         return true;
690     return false;
691 
692 }
693 
getScriptHash()694 QByteArray scriptengine::getScriptHash()
695 {
696     QByteArray m_scriptHash = QCryptographicHash::hash(m_script.toLatin1(), QCryptographicHash::Sha1).toHex();
697     return m_scriptHash;
698 }
699 
registerAllowedWrite()700 void scriptengine::registerAllowedWrite()
701 {
702     QByteArray hash = getScriptHash();
703     if (!privilegedWriteScripts.contains(hash))
704         privilegedWriteScripts.append(hash);
705 }
706 
needWritePrivileges(const QString & fn,const QString & param)707 bool scriptengine::needWritePrivileges(const QString &fn, const QString &param)
708 {
709     if (writeSecurityMode == 0) return false;
710     if (hasWritePrivileges()) return true;
711     int t = QMessageBox::question(nullptr, "TeXstudio script watcher",
712                                   tr("The current script has requested to enter privileged write mode and call following function:\n%1\n\nDo you trust this script?").arg(fn + "(\"" + param + "\")"), tr("Yes, allow this call"),
713                                   tr("Yes, allow all calls it will ever make"), tr("No, abort the call"), 0, 2);
714     if (t == 0) return true; //only now
715     if (t != 1) return false;
716     privilegedWriteScripts.append(getScriptHash());
717     return true;
718 }
719 
needReadPrivileges(const QString & fn,const QString & param)720 bool scriptengine::needReadPrivileges(const QString &fn, const QString &param)
721 {
722     if (readSecurityMode == 0) return false;
723     if (hasReadPrivileges()) return true;
724     int t = QMessageBox::question(nullptr, "TeXstudio script watcher",
725                                   tr("The current script has requested to enter privileged mode and read the following value:\n%1\n\nDo you trust this script?").arg(fn + "(\"" + param + "\")"), tr("Yes, allow this reading"),
726                                   tr("Yes, grant permanent read access to everything"), tr("No, abort the call"), 0, 2);
727     if (t == 0) return true; //only now
728     if (t != 1) return false;
729     privilegedReadScripts.append(getScriptHash());
730     return true;
731 }
732 
hasPersistent(const QString & name)733 bool scriptengine::hasPersistent(const QString &name)
734 {
735     return ConfigManagerInterface::getInstance()->existsOption(name);
736 }
737 
setPersistent(const QString & name,const QVariant & value)738 void scriptengine::setPersistent(const QString &name, const QVariant &value)
739 {
740     if (!needWritePrivileges("setPersistent", name + "=" + value.toString())) return;
741     ConfigManagerInterface::getInstance()->setOption(name, value);
742 }
743 
getPersistent(const QString & name)744 QVariant scriptengine::getPersistent(const QString &name)
745 {
746     if (!needReadPrivileges("getPersistent", name)) return QVariant();
747     return ConfigManagerInterface::getInstance()->getOption(name);
748 }
749 
registerAsBackgroundScript(const QString & name)750 void scriptengine::registerAsBackgroundScript(const QString &name)
751 {
752     static QMap<QString, QPointer<scriptengine> > backgroundScripts;
753 
754     QString realName = name.isEmpty() ? getScriptHash() : name;
755     if (!backgroundScripts.value(realName, QPointer<scriptengine>(nullptr)).isNull())
756         delete backgroundScripts.value(realName, QPointer<scriptengine>(nullptr)).data();
757     backgroundScripts.insert(realName, this);
758     //backgroundScript = true;
759 }
760 
setTimeout(const QString & fun,const int timeout)761 bool scriptengine::setTimeout(const QString &fun,const int timeout)
762 {
763     QTimer *tm=new QTimer();
764     QStringList parts=fun.split(" ");
765     tm->singleShot(timeout,this,std::bind(&scriptengine::runTimed,this,parts.value(1)));
766 
767     return true;
768 }
769 
runTimed(const QString fun)770 void scriptengine::runTimed(const QString fun)
771 {
772     if(!fun.isEmpty()){
773         engine->evaluate(fun);
774     }
775 }
776 
UniversalInputDialogScript(QWidget * parent)777 UniversalInputDialogScript::UniversalInputDialogScript(QWidget *parent): UniversalInputDialog(parent)
778 {
779 }
780 
~UniversalInputDialogScript()781 UniversalInputDialogScript::~UniversalInputDialogScript()
782 {
783 	for (int i = 0; i < properties.size(); i++) properties[i].deallocate();
784 }
785 
add(const QJSValue & def,const QJSValue & description,const QJSValue & id)786 QWidget* UniversalInputDialogScript::add(const QJSValue &def, const QJSValue &description, const QJSValue &id)
787 {
788 	QWidget *w = nullptr;
789 	if (def.isArray()) {
790 		QStringList options;
791 		QJSValueIterator it(def);
792 		while (it.hasNext()) {
793 			it.next();
794             if(it.name()=="length") continue; // skip length property
795 			if (it.value().isString() || it.value().isNumber()) options << it.value().toString();
796 			//else engine->throwError("Invalid default value in array (must be string or number): " + it.value().toString());
797 		}
798 		w = addComboBox(ManagedProperty::fromValue(options), description.toString());
799 	} else if (def.isBool()) {
800 		w = addCheckBox(ManagedProperty::fromValue(def.toBool()), description.toString());
801 	} else if (def.isNumber()) {
802 		w = addDoubleSpinBox(ManagedProperty::fromValue(def.toNumber()), description.toString());
803 	} else if (def.isString()) {
804 		w = addLineEdit(ManagedProperty::fromValue(def.toString()), description.toString());
805 	} else {
806 
807 		//engine->throwError(tr("Invalid default value: %1").arg(def.toString()));
808 		return nullptr;
809 	}
810 	if (!id.isUndefined()) properties.last().name = id.toString();
811 	return w;
812 }
813 
814 
execDialog()815 QJSValue UniversalInputDialogScript::execDialog()
816 {
817 	if (!UniversalInputDialog::exec()) return QJSValue();
818 	return getAll();
819 }
820 
821 
getAll()822 QJSValue UniversalInputDialogScript::getAll()
823 {
824 	QJSValue res = engine->newArray(properties.size());
825 	for (int i = 0; i < properties.size(); i++) {
826 		res.setProperty(i, engine->toScriptValue(properties[i].valueToQVariant()));
827 		if (!properties[i].name.isEmpty())
828 			res.setProperty(properties[i].name, engine->toScriptValue(properties[i].valueToQVariant()));
829 	}
830 	return res;
831 }
832 
get(const QJSValue & id)833 QVariant UniversalInputDialogScript::get(const QJSValue &id)
834 {
835 	if (id.isNumber()) {
836 		int i = id.toInt();
837 		if (i < 0 || i > properties.size()) return QVariant();
838 		return properties[i].valueToQVariant();
839 	}
840 	if (id.isString()) {
841 		QString sid = id.toString();
842 		foreach (const ManagedProperty &mp, properties)
843 			if (mp.name == sid)
844 				return mp.valueToQVariant();
845 		return QVariant();
846 	}
847 	//engine->throwError(tr("Unknown variable %1").arg(id.toString()));
848 	return QVariant();
849 }
850 
851 #undef SCRIPT_ASSERT
852