8 #include <common_helpers_p.h>
9 #include <ktranscript_p.h>
11 #include <ktranscript_export.h>
22 #include <QJSValueIterator>
25 #include <QStandardPaths>
26 #include <QStringList>
27 #include <QTextStream>
38 class KTranscriptImp :
public KTranscript
42 ~KTranscriptImp()
override;
55 bool &fallback)
override;
64 void setupInterpreter(
const QString &lang);
72 class Scriptface :
public QObject
109 QJSValue loadProps(
const QJSValueList &names);
130 bool *fallbackRequest;
146 struct UnparsedPropInfo {
147 QFile *pmapFile =
nullptr;
162 #define DBGP "KTranscript: "
163 void dbgout(
const char *str)
166 fprintf(stderr, DBGP
"%s\n", str);
171 template<
typename T1>
172 void dbgout(
const char *str,
const T1 &a1)
175 fprintf(stderr, DBGP
"%s\n",
QString::fromUtf8(str).arg(a1).toLocal8Bit().data());
181 template<
typename T1,
typename T2>
182 void dbgout(
const char *str,
const T1 &a1,
const T2 &a2)
185 fprintf(stderr, DBGP
"%s\n",
QString::fromUtf8(str).arg(a1).arg(a2).toLocal8Bit().data());
192 template<
typename T1,
typename T2,
typename T3>
193 void dbgout(
const char *str,
const T1 &a1,
const T2 &a2,
const T3 &a3)
196 fprintf(stderr, DBGP
"%s\n",
QString::fromUtf8(str).arg(a1).arg(a2).arg(a3).toLocal8Bit().data());
205 #define WARNP "KTranscript: "
206 void warnout(
const char *str)
208 fprintf(stderr, WARNP
"%s\n", str);
210 template<
typename T1>
211 void warnout(
const char *str,
const T1 &a1)
213 fprintf(stderr, WARNP
"%s\n",
QString::fromUtf8(str).arg(a1).toLocal8Bit().data());
224 return QStringLiteral(
"Error: %1").arg(
message.toString());
229 return QStringLiteral(
"Caught exception: %1").
arg(strexpt);
235 int countLines(
const QString &s,
int p)
239 for (
int i = 0; i < p && i < len; ++i) {
260 for (
int i = 0; i < len; ++i) {
270 key = removeAcceleratorMarker(key);
293 while (is < len && raw[is].isSpace() && raw[is] !=
QLatin1Char(
'\n')) {
301 while (ie >= 0 && raw[ie].isSpace() && raw[ie] !=
QLatin1Char(
'\n')) {
308 return raw.
mid(is + 1, ie - is - 1);
348 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
349 stream.setCodec(
"UTF-8");
351 while (!stream.atEnd()) {
352 QString line = stream.readLine();
374 configGroup =
config.find(group);
375 if (configGroup ==
config.end()) {
407 qCritical() <<
"Script error" <<
message;
411 #ifdef KTRANSCRIPT_TESTBUILD
415 static KTranscriptImp *s_transcriptInstance =
nullptr;
417 KTranscriptImp *globalKTI()
419 return s_transcriptInstance;
422 KTranscript *autotestCreateKTranscriptImp()
424 Q_ASSERT(s_transcriptInstance ==
nullptr);
425 s_transcriptInstance =
new KTranscriptImp;
426 return s_transcriptInstance;
429 void autotestDestroyKTranscriptImp()
431 Q_ASSERT(s_transcriptInstance !=
nullptr);
432 delete s_transcriptInstance;
433 s_transcriptInstance =
nullptr;
442 KTRANSCRIPT_EXPORT KTranscript *load_transcript()
452 KTranscriptImp::KTranscriptImp()
463 KTranscriptImp::~KTranscriptImp()
491 if (geteuid() == 0 && getuid() != 0) {
495 error =
"Security block: trying to execute a script in suid environment.";
502 loadModules(mods, error);
504 if (!
error.isEmpty()) {
513 if (!m_sface.contains(lang)) {
514 setupInterpreter(lang);
518 Scriptface *sface = m_sface[lang];
524 sface->msgcontext = &msgctxt;
525 sface->dyncontext = &dynctxt;
526 sface->msgId = &msgid;
527 sface->subList = &subs;
528 sface->valList = &vals;
529 sface->ftrans = &ftrans;
530 sface->fallbackRequest = &fallback;
534 int argc = argv.
size();
541 QString funcName = argv[0].toString();
542 if (!sface->funcs.contains(funcName)) {
543 error = QStringLiteral(
"Unregistered call to '%1'.").arg(funcName);
547 QJSValue func = sface->funcs[funcName];
548 QJSValue fval = sface->fvals[funcName];
552 currentModulePath = sface->fpaths[funcName];
555 QJSValueList arglist;
556 arglist.reserve(argc - 1);
557 for (
int i = 1; i < argc; ++i) {
582 error = QStringLiteral(
"Non-string return value: %1").arg(strval);
588 error = expt2str(val);
598 if (!m_sface.contains(lang)) {
603 Scriptface *sface = m_sface[lang];
605 return sface->nameForalls;
617 if (!m_sface.contains(mlang)) {
618 setupInterpreter(mlang);
625 modErrors.
append(QStringLiteral(
"Funny module path '%1', skipping.").arg(mpath));
628 currentModulePath = mpath.
left(posls);
637 m_sface[mlang]->load(alist);
641 currentModulePath.
clear();
643 for (
const QString &merr : std::as_const(modErrors)) {
649 void KTranscriptImp::setupInterpreter(
const QString &lang)
655 Scriptface *sface =
new Scriptface(config[lang]);
658 m_sface[lang] = sface;
666 , fallbackRequest(nullptr)
669 QJSValue object = scriptEngine->newQObject(
this);
670 scriptEngine->globalObject().
setProperty(QStringLiteral(SFNAME),
object);
671 scriptEngine->evaluate(QStringLiteral(
"Ts.acall = function() { return Ts.acallInternal(Array.prototype.slice.call(arguments)); };"));
674 Scriptface::~Scriptface()
676 qDeleteAll(loadedPmapHandles);
677 scriptEngine->deleteLater();
682 QJSValue internalObject = scriptEngine->globalObject().
property(QStringLiteral(
"ScriptfaceInternal"));
684 internalObject = scriptEngine->newObject();
685 scriptEngine->globalObject().
setProperty(QStringLiteral(
"ScriptfaceInternal"), internalObject);
696 #define SPREF(X) QString::fromLatin1(SFNAME "." X)
698 #define SPREF(X) QStringLiteral(SFNAME "." X)
710 if (!
name.isString()) {
711 return throwError(scriptEngine, SPREF(
"setcall: expected string as first argument"));
714 return throwError(scriptEngine, SPREF(
"setcall: expected function as second argument"));
717 return throwError(scriptEngine, SPREF(
"setcall: expected object or null as third argument"));
725 put(QStringLiteral(
"#:f<%1>").arg(qname), func);
726 put(QStringLiteral(
"#:o<%1>").arg(qname), fval);
730 fpaths[qname] = globalKTI()->currentModulePath;
737 return QJSValue(funcs.contains(qname));
745 return throwError(scriptEngine, SPREF(
"acall: expected at least one argument (call name)"));
747 if (!it.value().isString()) {
748 return throwError(scriptEngine, SPREF(
"acall: expected string as first argument (call name)"));
751 QString callname = it.value().toString();
752 if (!funcs.contains(callname)) {
753 return throwError(scriptEngine, SPREF(
"acall: unregistered call to '%1'").arg(callname));
760 globalKTI()->currentModulePath = fpaths[callname];
763 QJSValueList arglist;
765 arglist.append(it.value());
781 if (!
name.isString()) {
782 return throwError(scriptEngine, SPREF(
"setcallForall: expected string as first argument"));
785 return throwError(scriptEngine, SPREF(
"setcallForall: expected function as second argument"));
788 return throwError(scriptEngine, SPREF(
"setcallForall: expected object or null as third argument"));
796 put(QStringLiteral(
"#:fall<%1>").arg(qname), func);
797 put(QStringLiteral(
"#:oall<%1>").arg(qname), fval);
801 fpaths[qname] = globalKTI()->currentModulePath;
804 nameForalls.append(qname);
811 if (fallbackRequest) {
812 *fallbackRequest =
true;
819 return QJSValue(
static_cast<int>(subList->size()));
825 return throwError(scriptEngine, SPREF(
"subs: expected number as first argument"));
829 if (i < 0 || i >= subList->size()) {
830 return throwError(scriptEngine, SPREF(
"subs: index out of range"));
839 return throwError(scriptEngine, SPREF(
"vals: expected number as first argument"));
843 if (i < 0 || i >= valList->size()) {
844 return throwError(scriptEngine, SPREF(
"vals: index out of range"));
847 return scriptEngine->toScriptValue(valList->at(i));
858 auto valIt = dyncontext->constFind(qkey);
859 if (valIt != dyncontext->constEnd()) {
880 void Scriptface::dbgputs(
const QString &qstr)
882 dbgout(
"[JS-debug] %1", qstr);
885 void Scriptface::warnputs(
const QString &qstr)
887 warnout(
"[JS-warning] %1", qstr);
890 QJSValue Scriptface::localeCountry()
898 return throwError(scriptEngine, SPREF(
"normKey: expected string as argument"));
909 return loadProps(fnames);
912 QJSValue Scriptface::loadProps(
const QJSValueList &fnames)
914 if (globalKTI()->currentModulePath.isEmpty()) {
915 return throwError(scriptEngine, SPREF(
"loadProps: no current module path, aiiie..."));
918 for (
int i = 0; i < fnames.size(); ++i) {
919 if (!fnames[i].isString()) {
920 return throwError(scriptEngine, SPREF(
"loadProps: expected string as file name"));
924 for (
int i = 0; i < fnames.size(); ++i) {
925 QString qfname = fnames[i].toString();
931 bool haveCompiled =
true;
932 QFile file_check(qfpath);
934 haveCompiled =
false;
936 QFile file_check(qfpath);
938 return throwError(scriptEngine, SPREF(
"loadProps: cannot read map '%1'").arg(qfpath));
944 if (!loadedPmapPaths.contains(qfpath)) {
947 errorString = loadProps_bin(qfpath);
949 errorString = loadProps_text(qfpath);
952 return throwError(scriptEngine, errorString);
954 dbgout(
"Loaded property map: %1", qfpath);
955 loadedPmapPaths.insert(qfpath);
965 return throwError(scriptEngine, SPREF(
"getProp: expected string as first argument"));
968 return throwError(scriptEngine, SPREF(
"getProp: expected string as second argument"));
974 props = resolveUnparsedProps(qphrase);
989 return throwError(scriptEngine, SPREF(
"setProp: expected string as first argument"));
992 return throwError(scriptEngine, SPREF(
"setProp: expected string as second argument"));
995 return throwError(scriptEngine, SPREF(
"setProp: expected string as third argument"));
1002 phraseProps[qphrase][qprop] = qvalue;
1006 static QString toCaseFirst(
const QString &qstr,
int qnalt,
bool toupper)
1009 static const int hlen = 2;
1014 const int len = qstr.
length();
1016 int remainingAlts = 0;
1017 bool checkCase =
true;
1023 if (qnalt && !remainingAlts &&
QStringView(qstr).mid(i, hlen) == head) {
1032 remainingAlts = qnalt;
1034 }
else if (remainingAlts && c == altSep) {
1039 }
else if (checkCase && c.
isLetter()) {
1053 if (numChcased > 0 && remainingAlts == 0) {
1067 return throwError(scriptEngine, SPREF(
"toUpperFirst: expected string as first argument"));
1070 return throwError(scriptEngine, SPREF(
"toUpperFirst: expected number as second argument"));
1076 QString qstruc = toCaseFirst(qstr, qnalt,
true);
1084 return throwError(scriptEngine, SPREF(
"toLowerFirst: expected string as first argument"));
1087 return throwError(scriptEngine, SPREF(
"toLowerFirst: expected number as second argument"));
1093 QString qstrlc = toCaseFirst(qstr, qnalt,
false);
1101 return throwError(scriptEngine, QStringLiteral(
"getConfString: expected string as first argument"));
1104 return throwError(scriptEngine, SPREF(
"getConfString: expected string as second argument (when given)"));
1108 auto valIt =
config.constFind(qkey);
1109 if (valIt !=
config.constEnd()) {
1119 return throwError(scriptEngine, SPREF(
"getConfBool: expected string as first argument"));
1122 return throwError(scriptEngine, SPREF(
"getConfBool: expected boolean as second argument (when given)"));
1128 falsities.
append(QStringLiteral(
"no"));
1129 falsities.
append(QStringLiteral(
"false"));
1133 auto valIt =
config.constFind(qkey);
1134 if (valIt !=
config.constEnd()) {
1145 return throwError(scriptEngine,
1146 SPREF(
"getConfNumber: expected string "
1147 "as first argument"));
1150 return throwError(scriptEngine,
1151 SPREF(
"getConfNumber: expected number "
1152 "as second argument (when given)"));
1156 auto valIt =
config.constFind(qkey);
1157 if (valIt !=
config.constEnd()) {
1160 double qnum = qval.
toDouble(&convOk);
1172 QJSValue Scriptface::load(
const QJSValueList &fnames)
1174 if (globalKTI()->currentModulePath.isEmpty()) {
1175 return throwError(scriptEngine, SPREF(
"load: no current module path, aiiie..."));
1178 for (
int i = 0; i < fnames.size(); ++i) {
1179 if (!fnames[i].isString()) {
1180 return throwError(scriptEngine, SPREF(
"load: expected string as file name"));
1184 for (
int i = 0; i < fnames.size(); ++i) {
1185 QString qfname = fnames[i].toString();
1190 return throwError(scriptEngine, SPREF(
"load: cannot read file '%1'").arg(qfpath));
1194 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1195 stream.setCodec(
"UTF-8");
1197 QString source = stream.readAll();
1200 QJSValue comp = scriptEngine->evaluate(source, qfpath, 0);
1213 return throwError(scriptEngine, QStringLiteral(
"at %1:%2: %3").arg(qfpath, line, msg));
1215 dbgout(
"Loaded module: %1", qfpath);
1224 return SPREF(
"loadProps_text: cannot read file '%1'").arg(fpath);
1227 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1228 stream.setCodec(
"UTF-8");
1236 enum { s_nextEntry, s_nextKey, s_nextValue };
1240 int state = s_nextEntry;
1246 int i_checkpoint = i;
1248 if (state == s_nextEntry) {
1249 while (s[i].isSpace()) {
1252 goto END_PROP_PARSE;
1255 if (i + 1 >= slen) {
1256 return SPREF(
"loadProps_text: unexpected end of file in %1").arg(fpath);
1261 prop_sep = s[i + 1];
1263 return SPREF(
"loadProps_text: separator characters must not be letters at %1:%2").arg(fpath).arg(countLines(s, i));
1278 goto END_PROP_PARSE;
1282 }
else if (state == s_nextKey) {
1285 while (s[i] != key_sep && s[i] != prop_sep) {
1288 goto END_PROP_PARSE;
1291 if (s[i] == key_sep) {
1294 pkey = normKeystr(s.
mid(ip, i - ip),
false);
1297 state = s_nextValue;
1309 if (ekeys.
size() < 1) {
1310 return SPREF(
"loadProps_text: no entry key for entry ending at %1:%2").arg(fpath).arg(countLines(s, i));
1315 for (
const QByteArray &ekey : std::as_const(ekeys)) {
1316 phraseProps[ekey] = props;
1320 state = s_nextEntry;
1323 }
else if (state == s_nextValue) {
1326 while (s[i] != prop_sep) {
1329 goto END_PROP_PARSE;
1331 if (s[i] == key_sep) {
1332 return SPREF(
"loadProps_text: property separator inside property value at %1:%2").arg(fpath).arg(countLines(s, i));
1342 return SPREF(
"loadProps: internal error 10 at %1:%2").arg(fpath).arg(countLines(s, i));
1346 if (i == i_checkpoint || i >= slen) {
1347 return SPREF(
"loadProps: internal error 20 at %1:%2").arg(fpath).arg(countLines(s, i));
1353 if (state != s_nextEntry) {
1354 return SPREF(
"loadProps: unexpected end of file in %1").arg(fpath);
1364 template<
typename T>
1365 static int bin_read_int_nbytes(
const char *fc, qlonglong len, qlonglong &pos,
int nbytes)
1367 if (pos + nbytes > len) {
1371 T num = qFromBigEndian<T>((uchar *)fc + pos);
1377 static quint64 bin_read_int64(
const char *fc, qlonglong len, qlonglong &pos)
1379 return bin_read_int_nbytes<quint64>(fc, len, pos, 8);
1383 static quint32 bin_read_int(
const char *fc, qlonglong len, qlonglong &pos)
1385 return bin_read_int_nbytes<quint32>(fc, len, pos, 4);
1392 static QByteArray bin_read_string(
const char *fc, qlonglong len, qlonglong &pos)
1396 int nbytes = bin_read_int(fc, len, pos);
1400 if (nbytes < 0 || pos + nbytes > len) {
1413 return SPREF(
"loadProps: cannot read file '%1'").arg(fpath);
1417 file.read(head.data(), head.size());
1421 if (head ==
"TSPMAP00") {
1422 return loadProps_bin_00(fpath);
1423 }
else if (head ==
"TSPMAP01") {
1424 return loadProps_bin_01(fpath);
1426 return SPREF(
"loadProps: unknown version of compiled map '%1'").arg(fpath);
1434 return SPREF(
"loadProps: cannot read file '%1'").arg(fpath);
1438 const char *fc = fctmp.
data();
1439 const int fclen = fctmp.
size();
1447 if (head !=
"TSPMAP00") {
1448 goto END_PROP_PARSE;
1453 nentries = bin_read_int(fc, fclen, pos);
1455 goto END_PROP_PARSE;
1459 for (
int i = 0; i < nentries; ++i) {
1462 int nekeys = bin_read_int(fc, fclen, pos);
1464 goto END_PROP_PARSE;
1467 for (
int j = 0; j < nekeys; ++j) {
1468 QByteArray ekey = bin_read_string(fc, fclen, pos);
1470 goto END_PROP_PARSE;
1478 int nprops = bin_read_int(fc, fclen, pos);
1480 goto END_PROP_PARSE;
1482 for (
int j = 0; j < nprops; ++j) {
1483 QByteArray pkey = bin_read_string(fc, fclen, pos);
1485 goto END_PROP_PARSE;
1487 QByteArray pval = bin_read_string(fc, fclen, pos);
1489 goto END_PROP_PARSE;
1496 for (
const QByteArray &ekey : std::as_const(ekeys)) {
1497 phraseProps[ekey] = props;
1504 return SPREF(
"loadProps: corrupt compiled map '%1'").arg(fpath);
1514 return SPREF(
"loadProps: cannot read file '%1'").arg(fpath);
1521 fstr = file->
read(8 + 4 + 8);
1525 if (head !=
"TSPMAP01") {
1526 return SPREF(
"loadProps: corrupt compiled map '%1'").arg(fpath);
1528 quint32 numekeys = bin_read_int(fstr, fstr.
size(), pos);
1529 quint64 lenekeys = bin_read_int64(fstr, fstr.
size(), pos);
1532 fstr = file->
read(lenekeys);
1534 for (quint32 i = 0; i < numekeys; ++i) {
1535 QByteArray ekey = bin_read_string(fstr, lenekeys, pos);
1536 quint64 offset = bin_read_int64(fstr, lenekeys, pos);
1537 phraseUnparsedProps[ekey] = {file, offset};
1543 loadedPmapHandles.insert(file);
1549 auto [file, offset] = phraseUnparsedProps.
value(phrase);
1551 if (file && file->
seek(offset)) {
1554 quint32 numpkeys = bin_read_int(fstr, fstr.
size(), pos);
1555 quint32 lenpkeys = bin_read_int(fstr, fstr.
size(), pos);
1556 fstr = file->
read(lenpkeys);
1558 for (quint32 i = 0; i < numpkeys; ++i) {
1559 QByteArray pkey = bin_read_string(fstr, lenpkeys, pos);
1560 QByteArray pval = bin_read_string(fstr, lenpkeys, pos);
1563 phraseProps[phrase] = props;
1564 phraseUnparsedProps.
remove(phrase);
1569 #include "ktranscript.moc"