File indexing completed on 2024-05-19 04:59:16
0001 /* ============================================================ 0002 * GreaseMonkey plugin for Falkon 0003 * Copyright (C) 2012-2018 David Rosca <nowrep@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 * ============================================================ */ 0018 #include "gm_script.h" 0019 #include "gm_manager.h" 0020 #include "gm_downloader.h" 0021 0022 #include "delayedfilewatcher.h" 0023 #include "mainapplication.h" 0024 #include "webpage.h" 0025 #include "networkmanager.h" 0026 0027 #include <QFile> 0028 #include <QTextStream> 0029 #include <QStringList> 0030 #include <QWebEngineScript> 0031 #include <QCryptographicHash> 0032 #include <QNetworkReply> 0033 #include <QNetworkRequest> 0034 0035 GM_Script::GM_Script(GM_Manager* manager, const QString &filePath) 0036 : QObject(manager) 0037 , m_manager(manager) 0038 , m_fileWatcher(new DelayedFileWatcher(this)) 0039 , m_namespace(QSL("GreaseMonkeyNS")) 0040 , m_startAt(DocumentEnd) 0041 , m_noframes(false) 0042 , m_fileName(filePath) 0043 , m_enabled(true) 0044 , m_valid(false) 0045 , m_updating(false) 0046 { 0047 parseScript(); 0048 0049 connect(m_fileWatcher, &DelayedFileWatcher::delayedFileChanged, this, &GM_Script::watchedFileChanged); 0050 } 0051 0052 bool GM_Script::isValid() const 0053 { 0054 return m_valid; 0055 } 0056 0057 QString GM_Script::name() const 0058 { 0059 return m_name; 0060 } 0061 0062 QString GM_Script::nameSpace() const 0063 { 0064 return m_namespace; 0065 } 0066 0067 QString GM_Script::fullName() const 0068 { 0069 return QSL("%1/%2").arg(m_namespace, m_name); 0070 } 0071 0072 QString GM_Script::description() const 0073 { 0074 return m_description; 0075 } 0076 0077 QString GM_Script::version() const 0078 { 0079 return m_version; 0080 } 0081 0082 QIcon GM_Script::icon() const 0083 { 0084 return m_icon; 0085 } 0086 0087 QUrl GM_Script::iconUrl() const 0088 { 0089 return m_iconUrl; 0090 } 0091 0092 QUrl GM_Script::downloadUrl() const 0093 { 0094 return m_downloadUrl; 0095 } 0096 0097 QUrl GM_Script::updateUrl() const 0098 { 0099 return m_updateUrl; 0100 } 0101 0102 GM_Script::StartAt GM_Script::startAt() const 0103 { 0104 return m_startAt; 0105 } 0106 0107 bool GM_Script::noFrames() const 0108 { 0109 return m_noframes; 0110 } 0111 0112 bool GM_Script::isEnabled() const 0113 { 0114 return m_valid && m_enabled; 0115 } 0116 0117 void GM_Script::setEnabled(bool enable) 0118 { 0119 m_enabled = enable; 0120 } 0121 0122 QStringList GM_Script::include() const 0123 { 0124 return m_include; 0125 } 0126 0127 QStringList GM_Script::exclude() const 0128 { 0129 return m_exclude; 0130 } 0131 0132 QStringList GM_Script::require() const 0133 { 0134 return m_require; 0135 } 0136 0137 QString GM_Script::fileName() const 0138 { 0139 return m_fileName; 0140 } 0141 0142 QWebEngineScript GM_Script::webScript() const 0143 { 0144 QWebEngineScript script; 0145 script.setSourceCode(QSL("%1\n%2").arg(m_manager->bootstrapScript(), m_script)); 0146 script.setName(fullName()); 0147 script.setWorldId(WebPage::SafeJsWorld); 0148 script.setRunsOnSubFrames(!m_noframes); 0149 return script; 0150 } 0151 0152 bool GM_Script::isUpdating() 0153 { 0154 return m_updating; 0155 } 0156 0157 void GM_Script::updateScript() 0158 { 0159 if (!m_downloadUrl.isValid() || m_updating) 0160 return; 0161 0162 m_updating = true; 0163 Q_EMIT updatingChanged(m_updating); 0164 0165 auto *downloader = new GM_Downloader(m_downloadUrl, m_manager); 0166 downloader->updateScript(m_fileName); 0167 connect(downloader, &GM_Downloader::finished, this, [this]() { 0168 m_updating = false; 0169 Q_EMIT updatingChanged(m_updating); 0170 }); 0171 connect(downloader, &GM_Downloader::error, this, [this]() { 0172 m_updating = false; 0173 Q_EMIT updatingChanged(m_updating); 0174 }); 0175 downloadRequires(); 0176 } 0177 0178 void GM_Script::watchedFileChanged(const QString &file) 0179 { 0180 if (m_fileName == file) { 0181 reloadScript(); 0182 } 0183 } 0184 0185 void GM_Script::parseScript() 0186 { 0187 m_name.clear(); 0188 m_namespace = QSL("GreaseMonkeyNS"); 0189 m_description.clear(); 0190 m_version.clear(); 0191 m_include.clear(); 0192 m_exclude.clear(); 0193 m_require.clear(); 0194 m_icon = QIcon(); 0195 m_iconUrl.clear(); 0196 m_downloadUrl.clear(); 0197 m_updateUrl.clear(); 0198 m_startAt = DocumentEnd; 0199 m_noframes = false; 0200 m_script.clear(); 0201 m_enabled = true; 0202 m_valid = false; 0203 0204 QFile file(m_fileName); 0205 if (!file.open(QFile::ReadOnly)) { 0206 qWarning() << "GreaseMonkey: Cannot open file for reading" << m_fileName; 0207 return; 0208 } 0209 0210 if (!m_fileWatcher->files().contains(m_fileName)) { 0211 m_fileWatcher->addPath(m_fileName); 0212 } 0213 0214 const QByteArray fileData = file.readAll(); 0215 0216 bool inMetadata = false; 0217 0218 QTextStream stream(fileData); 0219 QString line; 0220 while (stream.readLineInto(&line)) { 0221 if (line.startsWith(QL1S("// ==UserScript=="))) { 0222 inMetadata = true; 0223 } 0224 if (line.startsWith(QL1S("// ==/UserScript=="))) { 0225 break; 0226 } 0227 if (!inMetadata) { 0228 continue; 0229 } 0230 0231 if (!line.startsWith(QLatin1String("// @"))) { 0232 continue; 0233 } 0234 0235 line = line.mid(3).replace(QLatin1Char('\t'), QLatin1Char(' ')); 0236 int index = line.indexOf(QLatin1Char(' ')); 0237 0238 const QString key = line.left(index).trimmed(); 0239 const QString value = index > 0 ? line.mid(index + 1).trimmed() : QString(); 0240 0241 if (key.isEmpty()) { 0242 continue; 0243 } 0244 0245 if (key == QLatin1String("@name")) { 0246 m_name = value; 0247 } 0248 else if (key == QLatin1String("@namespace")) { 0249 m_namespace = value; 0250 } 0251 else if (key == QLatin1String("@description")) { 0252 m_description = value; 0253 } 0254 else if (key == QLatin1String("@version")) { 0255 m_version = value; 0256 } 0257 else if (key == QLatin1String("@updateURL")) { 0258 m_updateUrl = QUrl(value); 0259 } 0260 else if (key == QLatin1String("@downloadURL")) { 0261 m_downloadUrl = QUrl(value); 0262 } 0263 else if (key == QLatin1String("@include") || key == QLatin1String("@match")) { 0264 m_include.append(value); 0265 } 0266 else if (key == QLatin1String("@exclude") || key == QLatin1String("@exclude_match")) { 0267 m_exclude.append(value); 0268 } 0269 else if (key == QLatin1String("@require")) { 0270 m_require.append(value); 0271 } 0272 else if (key == QLatin1String("@run-at")) { 0273 if (value == QLatin1String("document-end")) { 0274 m_startAt = DocumentEnd; 0275 } 0276 else if (value == QLatin1String("document-start")) { 0277 m_startAt = DocumentStart; 0278 } 0279 else if (value == QLatin1String("document-idle")) { 0280 m_startAt = DocumentIdle; 0281 } 0282 } 0283 else if (key == QL1S("@icon")) { 0284 m_iconUrl = QUrl(value); 0285 } 0286 else if (key == QL1S("@noframes")) { 0287 m_noframes = true; 0288 } 0289 } 0290 0291 if (!inMetadata) { 0292 qWarning() << "GreaseMonkey: File does not contain metadata block" << m_fileName; 0293 return; 0294 } 0295 0296 m_iconUrl = m_downloadUrl.resolved(m_iconUrl); 0297 0298 if (m_include.isEmpty()) { 0299 m_include.append(QSL("*")); 0300 } 0301 0302 const QString nspace = QString::fromLatin1(QCryptographicHash::hash(fullName().toUtf8(), QCryptographicHash::Md4).toHex()); 0303 const QString gmValues = m_manager->valuesScript().arg(nspace); 0304 m_script = QSL("(function(){%1\n%2\n%3\n})();").arg(gmValues, m_manager->requireScripts(m_require), QString::fromUtf8(fileData)); 0305 m_valid = true; 0306 0307 downloadIcon(); 0308 downloadRequires(); 0309 } 0310 0311 void GM_Script::reloadScript() 0312 { 0313 parseScript(); 0314 0315 m_manager->removeScript(this, false); 0316 m_manager->addScript(this); 0317 0318 Q_EMIT scriptChanged(); 0319 } 0320 0321 void GM_Script::downloadIcon() 0322 { 0323 if (m_iconUrl.isValid()) { 0324 QNetworkReply *reply = mApp->networkManager()->get(QNetworkRequest(m_iconUrl)); 0325 connect(reply, &QNetworkReply::finished, this, [=]() { 0326 reply->deleteLater(); 0327 if (reply->error() == QNetworkReply::NoError) { 0328 m_icon = QPixmap::fromImage(QImage::fromData(reply->readAll())); 0329 } 0330 }); 0331 } 0332 } 0333 0334 void GM_Script::downloadRequires() 0335 { 0336 for (const QString &url : std::as_const(m_require)) { 0337 if (m_manager->requireScripts({url}).isEmpty()) { 0338 auto *downloader = new GM_Downloader(QUrl(url), m_manager, GM_Downloader::DownloadRequireScript); 0339 connect(downloader, &GM_Downloader::finished, this, &GM_Script::reloadScript); 0340 } 0341 } 0342 }