File indexing completed on 2025-01-05 03:53:35

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2011-03-22
0007  * Description : a Iface C++ interface
0008  *
0009  * SPDX-FileCopyrightText: 2011-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2011      by Alexandre Mendes <alex dot mendes1988 at gmail dot com>
0011  * SPDX-FileCopyrightText: 2011      by Hormiere Guillaume <hormiere dot guillaume at gmail dot com>
0012  * SPDX-FileCopyrightText: 2011      by Manuel Campomanes <campomanes dot manuel at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "mediawiki_edit.h"
0019 
0020 // Qt includes
0021 
0022 #include <QTimer>
0023 #include <QUrlQuery>
0024 #include <QXmlStreamReader>
0025 #include <QCryptographicHash>
0026 #include <QStringList>
0027 
0028 #include <QNetworkCookie>
0029 #include <QNetworkReply>
0030 #include <QNetworkRequest>
0031 
0032 // Local includes
0033 
0034 #include "mediawiki_iface.h"
0035 #include "mediawiki_job_p.h"
0036 
0037 namespace MediaWiki
0038 {
0039 
0040 class Q_DECL_HIDDEN Result
0041 {
0042 public:
0043 
0044     explicit Result()
0045       : m_captchaId(-1)
0046     {
0047     }
0048 
0049     unsigned int m_captchaId;
0050     QVariant     m_captchaQuestion;
0051     QString      m_captchaAnswer;
0052 };
0053 
0054 class Q_DECL_HIDDEN EditPrivate : public JobPrivate
0055 {
0056 public:
0057 
0058     explicit EditPrivate(Iface& MediaWiki)
0059         : JobPrivate(MediaWiki)
0060     {
0061     }
0062 
0063     static int error(const QString& error)
0064     {
0065         QString temp = error;
0066         int ret      = 0;
0067         QStringList list;
0068         list    << QStringLiteral("notext")
0069                 << QStringLiteral("invalidsection")
0070                 << QStringLiteral("protectedtitle")
0071                 << QStringLiteral("cantcreate")
0072                 << QStringLiteral("cantcreateanon")
0073                 << QStringLiteral("articleexists")
0074                 << QStringLiteral("noimageredirectanon")
0075                 << QStringLiteral("noimageredirect")
0076                 << QStringLiteral("spamdetected")
0077                 << QStringLiteral("filtered")
0078                 << QStringLiteral("contenttoobig")
0079                 << QStringLiteral("noeditanon")
0080                 << QStringLiteral("noedit")
0081                 << QStringLiteral("pagedeleted")
0082                 << QStringLiteral("emptypage")
0083                 << QStringLiteral("emptynewsection")
0084                 << QStringLiteral("editconflict")
0085                 << QStringLiteral("revwrongpage")
0086                 << QStringLiteral("undofailure");
0087 
0088         ret = list.indexOf(temp.remove(QChar::fromLatin1('-')));
0089 
0090         if (ret == -1)
0091         {
0092             ret = 0;
0093         }
0094 
0095         return  ret + (int)Edit::TextMissing ;
0096     }
0097 
0098     QUrl                   baseUrl;
0099     QMap<QString, QString> requestParameter;
0100     Result                 result;
0101 };
0102 
0103 Edit::Edit(Iface& media, QObject* const parent)
0104     : Job(*new EditPrivate(media), parent)
0105 {
0106 }
0107 
0108 void Edit::setUndoAfter(int undoafter)
0109 {
0110     Q_D(Edit);
0111     d->requestParameter[QStringLiteral("undoafter")] = QString::number(undoafter);
0112 }
0113 
0114 void Edit::setUndo(int undo)
0115 {
0116     Q_D(Edit);
0117     d->requestParameter[QStringLiteral("undo")] = QString::number(undo);
0118 }
0119 
0120 void Edit::setPrependText(const QString& prependText)
0121 {
0122     Q_D(Edit);
0123     d->requestParameter[QStringLiteral("prependtext")] = prependText;
0124     d->requestParameter[QStringLiteral("md5")]         = QString();
0125 }
0126 
0127 void Edit::setAppendText(const QString& appendText)
0128 {
0129     Q_D(Edit);
0130     d->requestParameter[QStringLiteral("appendtext")] = appendText;
0131     d->requestParameter[QStringLiteral("md5")]        = QString();
0132 }
0133 
0134 void Edit::setPageName(const QString& pageName)
0135 {
0136     Q_D(Edit);
0137     d->requestParameter[QStringLiteral("title")] = pageName;
0138 }
0139 
0140 void Edit::setToken(const QString& token)
0141 {
0142     Q_D(Edit);
0143     d->requestParameter[QStringLiteral("token")] = token;
0144 }
0145 
0146 void Edit::setBaseTimestamp(const QDateTime& baseTimestamp)
0147 {
0148     Q_D(Edit);
0149     d->requestParameter[QStringLiteral("basetimestamp")] = baseTimestamp.toString(QStringLiteral("yyyy-MM-ddThh:mm:ssZ"));
0150 }
0151 
0152 void Edit::setStartTimestamp(const QDateTime& startTimestamp)
0153 {
0154     Q_D(Edit);
0155     d->requestParameter[QStringLiteral("starttimestamp")] = startTimestamp.toString(QStringLiteral("yyyy-MM-ddThh:mm:ssZ"));
0156 }
0157 
0158 void Edit::setText(const QString& text)
0159 {
0160     Q_D(Edit);
0161     d->requestParameter[QStringLiteral("text")] = text;
0162     d->requestParameter[QStringLiteral("md5")]  = QString();
0163 }
0164 
0165 void Edit::setRecreate(bool recreate)
0166 {
0167     Q_D(Edit);
0168 
0169     if (recreate)
0170     {
0171         d->requestParameter[QStringLiteral("recreate")] = QStringLiteral("on");
0172         d->requestParameter[QStringLiteral("md5")]      = QString();
0173     }
0174 }
0175 
0176 void Edit::setCreateonly(bool createonly)
0177 {
0178     Q_D(Edit);
0179 
0180     if (createonly)
0181     {
0182         d->requestParameter[QStringLiteral("createonly")] = QStringLiteral("on");
0183         d->requestParameter[QStringLiteral("md5")]        = QString();
0184     }
0185 }
0186 
0187 void Edit::setNocreate(bool norecreate)
0188 {
0189     Q_D(Edit);
0190 
0191     if (norecreate)
0192     {
0193         d->requestParameter[QStringLiteral("nocreate")] = QStringLiteral("on");
0194         d->requestParameter[QStringLiteral("md5")]      = QString();
0195     }
0196 }
0197 
0198 void Edit::setMinor(bool minor)
0199 {
0200     Q_D(Edit);
0201 
0202     if (minor)
0203     {
0204         d->requestParameter[QStringLiteral("minor")]    = QStringLiteral("on");
0205     }
0206     else
0207     {
0208         d->requestParameter[QStringLiteral("notminor")] = QStringLiteral("on");
0209     }
0210 }
0211 
0212 void Edit::setSection(const QString& section)
0213 {
0214     Q_D(Edit);
0215     d->requestParameter[QStringLiteral("section")] = section;
0216 }
0217 
0218 void Edit::setSummary(const QString& summary)
0219 {
0220     Q_D(Edit);
0221     d->requestParameter[QStringLiteral("summary")] = summary;
0222 }
0223 
0224 void Edit::setWatchList(Edit::Watchlist watchlist)
0225 {
0226     Q_D(Edit);
0227 
0228     switch (watchlist)
0229     {
0230         case Edit::watch:
0231             d->requestParameter[QStringLiteral("watchlist")] = QString(QStringLiteral("watch"));
0232             break;
0233 
0234         case Edit::unwatch:
0235             d->requestParameter[QStringLiteral("watchlist")] = QString(QStringLiteral("unwatch"));
0236             break;
0237 
0238         case Edit::nochange:
0239             d->requestParameter[QStringLiteral("watchlist")] = QString(QStringLiteral("nochange"));
0240             break;
0241 
0242         case Edit::preferences:
0243             d->requestParameter[QStringLiteral("watchlist")] = QString(QStringLiteral("preferences"));
0244             break;
0245     }
0246 }
0247 
0248 Edit::~Edit()
0249 {
0250 }
0251 
0252 void Edit::start()
0253 {
0254     Q_D(Edit);
0255     QueryInfo* const info = new QueryInfo(d->MediaWiki,this);
0256     info->setPageName(d->requestParameter[QStringLiteral("title")]);
0257     info->setToken(QStringLiteral("tokens"));
0258 
0259     connect(info, SIGNAL(page(Page)),
0260             this, SLOT(doWorkSendRequest(Page)));
0261 
0262     info->start();
0263 }
0264 
0265 void Edit::doWorkSendRequest(const Page& page)
0266 {
0267     Q_D(Edit);
0268     d->requestParameter[QStringLiteral("token")] = page.pageEditToken();
0269 
0270     // Set the url
0271 
0272     QUrl    url                                  = d->MediaWiki.url();
0273     QUrlQuery query;
0274     query.addQueryItem(QStringLiteral("format"), QStringLiteral("xml"));
0275     query.addQueryItem(QStringLiteral("action"), QStringLiteral("edit"));
0276 
0277     // Add params
0278 
0279     if (d->requestParameter.contains(QStringLiteral("md5")))
0280     {
0281         QString text;
0282 
0283         if (d->requestParameter.contains(QStringLiteral("prependtext")))
0284         {
0285             text += d->requestParameter[QStringLiteral("prependtext")];
0286         }
0287 
0288         if (d->requestParameter.contains(QStringLiteral("appendtext")))
0289         {
0290             text += d->requestParameter[QStringLiteral("appendtext")];
0291         }
0292 
0293         if (d->requestParameter.contains(QStringLiteral("text")))
0294         {
0295             text = d->requestParameter[QStringLiteral("text")];
0296         }
0297 
0298         QByteArray hash                            = QCryptographicHash::hash(text.toUtf8(),QCryptographicHash::Md5);
0299         d->requestParameter[QStringLiteral("md5")] = QString::fromLatin1(hash.toHex());
0300     }
0301 
0302     QMapIterator<QString, QString> it(d->requestParameter);
0303 
0304     while (it.hasNext())
0305     {
0306         it.next();
0307 
0308         if (it.key() != QStringLiteral("token"))
0309         {
0310             query.addQueryItem(it.key(), it.value());
0311         }
0312     }
0313 
0314     QByteArray cookie;
0315     QList<QNetworkCookie> MediaWikiCookies = d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url());
0316 
0317     for (int i = 0 ; i < MediaWikiCookies.size() ; ++i)
0318     {
0319         cookie += MediaWikiCookies.at(i).toRawForm(QNetworkCookie::NameAndValueOnly);
0320         cookie += ';';
0321     }
0322 
0323     // Add the token
0324 
0325     query.addQueryItem(QStringLiteral("token"), d->requestParameter[QStringLiteral("token")]);
0326     url.setQuery(query);
0327     d->baseUrl = url;
0328 
0329     // Set the request
0330 
0331     QNetworkRequest request( url );
0332     request.setRawHeader("User-Agent", d->MediaWiki.userAgent().toUtf8());
0333     request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
0334     request.setRawHeader("Cookie", cookie);
0335 
0336     setPercent(25); // Request ready.
0337 
0338     // Send the request
0339 
0340     d->reply = d->manager->post( request, url.toString().toUtf8() );
0341     connectReply();
0342 
0343     connect( d->reply, SIGNAL(finished()),
0344              this, SLOT(finishedEdit()) );
0345 
0346     setPercent(50); // Request sent.
0347 }
0348 
0349 void Edit::finishedEdit()
0350 {
0351     Q_D(Edit);
0352 
0353     disconnect(d->reply, SIGNAL(finished()),
0354                this, SLOT(finishedEdit()));
0355 
0356     setPercent(75); // Response received.
0357 
0358     if (d->reply->error() != QNetworkReply::NoError)
0359     {
0360         this->setError(this->NetworkError);
0361         d->reply->close();
0362         d->reply->deleteLater();
0363 
0364         emitResult();
0365 
0366         return;
0367     }
0368 
0369     QXmlStreamReader reader( d->reply );
0370 
0371     while (!reader.atEnd() && !reader.hasError())
0372     {
0373         QXmlStreamReader::TokenType token = reader.readNext();
0374 
0375         if (token == QXmlStreamReader::StartElement)
0376         {
0377             QXmlStreamAttributes attrs = reader.attributes();
0378 
0379             if (reader.name() == QStringLiteral("edit"))
0380             {
0381                 if      (attrs.value( QStringLiteral("result") ).toString() == QLatin1String("Success"))
0382                 {
0383                     setPercent(100); // Response parsed successfully.
0384                     this->setError(KJob::NoError);
0385                     d->reply->close();
0386                     d->reply->deleteLater();
0387 
0388                     emitResult();
0389 
0390                     return;
0391                 }
0392                 else if (attrs.value(QStringLiteral("result")).toString() == QLatin1String("Failure"))
0393                 {
0394                     this->setError(KJob::NoError);
0395                     reader.readNext();
0396                     attrs = reader.attributes();
0397                     d->result.m_captchaId = attrs.value( QStringLiteral("id") ).toString().toUInt();
0398 
0399                     if      (!attrs.value( QStringLiteral("question") ).isEmpty())
0400                     {
0401                         d->result.m_captchaQuestion = QVariant(attrs.value( QStringLiteral("question") ).toString());
0402                     }
0403                     else if (!attrs.value( QStringLiteral("url") ).isEmpty())
0404                     {
0405                         d->result.m_captchaQuestion = QVariant(attrs.value( QStringLiteral("url") ).toString());
0406                     }
0407                 }
0408             }
0409             else if (reader.name() == QStringLiteral("error"))
0410             {
0411                 this->setError(EditPrivate::error(attrs.value( QStringLiteral("code") ).toString()));
0412                 d->reply->close();
0413                 d->reply->deleteLater();
0414 
0415                 emitResult();
0416 
0417                 return;
0418             }
0419         }
0420         else if ((token == QXmlStreamReader::Invalid) && (reader.error() != QXmlStreamReader::PrematureEndOfDocumentError))
0421         {
0422             this->setError(this->XmlError);
0423             d->reply->close();
0424             d->reply->deleteLater();
0425 
0426             emitResult();
0427 
0428             return;
0429         }
0430     }
0431 
0432     d->reply->close();
0433     d->reply->deleteLater();
0434     Q_EMIT resultCaptcha(d->result.m_captchaQuestion);
0435 }
0436 
0437 void Edit::finishedCaptcha(const QString& captcha)
0438 {
0439     Q_D(Edit);
0440     d->result.m_captchaAnswer = captcha;
0441     QUrl url                  = d->baseUrl;
0442     QUrlQuery query;
0443     query.addQueryItem(QStringLiteral("CaptchaId"),     QString::number(d->result.m_captchaId));
0444     query.addQueryItem(QStringLiteral("CaptchaAnswer"), d->result.m_captchaAnswer);
0445     url.setQuery(query);
0446     QString data              = url.toString();
0447     QByteArray cookie;
0448     QList<QNetworkCookie> MediaWikiCookies = d->manager->cookieJar()->cookiesForUrl(d->MediaWiki.url());
0449 
0450     for (int i = 0 ; i < MediaWikiCookies.size() ; ++i)
0451     {
0452         cookie += MediaWikiCookies.at(i).toRawForm(QNetworkCookie::NameAndValueOnly);
0453         cookie += ';';
0454     }
0455 
0456     // Set the request
0457 
0458     QNetworkRequest request(url);
0459     request.setRawHeader("User-Agent", d->MediaWiki.userAgent().toUtf8());
0460     request.setRawHeader("Cookie", cookie);
0461     request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
0462 
0463     // Send the request
0464 
0465     d->reply = d->manager->post(request, data.toUtf8());
0466 
0467     connect( d->reply, SIGNAL(finished()),
0468              this, SLOT(finishedEdit()) );
0469 }
0470 
0471 } // namespace MediaWiki
0472 
0473 #include "moc_mediawiki_edit.cpp"