// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "stickynotespasteprotocol.h" #include "cpastertr.h" #include #include #include #include #include #include #include #include #include #include #include enum { debug = 0 }; namespace CodePaster { static QByteArray expiryParameter(int daysRequested) { // Obtained by 'pastebin.kde.org/api/json/parameter/expire' on 26.03.2014 static const int expiryTimesSec[] = {1800, 21600, 86400, 604800, 2592000, 31536000}; const int *end = expiryTimesSec + sizeof(expiryTimesSec) / sizeof(expiryTimesSec[0]); // Find the first element >= requested span, search up to n - 1 such that 'end' defaults to last value. const int *match = std::lower_bound(expiryTimesSec, end - 1, 24 * 60 * 60 * daysRequested); return QByteArray("expire=") + QByteArray::number(*match); } void StickyNotesPasteProtocol::setHostUrl(const QString &hostUrl) { m_hostUrl = hostUrl; if (!m_hostUrl.endsWith(QLatin1Char('/'))) m_hostUrl.append(QLatin1Char('/')); } unsigned StickyNotesPasteProtocol::capabilities() const { return ListCapability | PostDescriptionCapability; } bool StickyNotesPasteProtocol::checkConfiguration(QString *errorMessage) { if (m_hostChecked) // Check the host once. return true; const bool ok = httpStatus(m_hostUrl, errorMessage, true); if (ok) m_hostChecked = true; return ok; } // Query 'http://pastebin.kde.org/api/json/parameter/language' to obtain the valid values. static inline QByteArray pasteLanguage(Protocol::ContentType ct) { switch (ct) { case Protocol::Text: break; case Protocol::C: return "language=c"; case Protocol::Cpp: return "language=cpp-qt"; case Protocol::JavaScript: return "language=javascript"; case Protocol::Diff: return "language=diff"; case Protocol::Xml: return "language=xml"; } return QByteArray("language=text"); } void StickyNotesPasteProtocol::paste( const QString &text, ContentType ct, int expiryDays, const QString &username, const QString &comment, const QString &description ) { enum { maxDescriptionLength = 30 }; // Length of description is limited. Q_UNUSED(username) Q_UNUSED(comment) QTC_ASSERT(!m_pasteReply, return); // Format body QByteArray pasteData = "&data="; pasteData += QUrl::toPercentEncoding(fixNewLines(text)); pasteData += '&'; pasteData += pasteLanguage(ct); pasteData += '&'; pasteData += expiryParameter(expiryDays); if (!description.isEmpty()) { pasteData += "&title="; pasteData += QUrl::toPercentEncoding(description.left(maxDescriptionLength)); } m_pasteReply = httpPost(m_hostUrl + QLatin1String("api/json/create"), pasteData, true); connect(m_pasteReply, &QNetworkReply::finished, this, &StickyNotesPasteProtocol::pasteFinished); if (debug) qDebug() << "paste: sending " << m_pasteReply << pasteData; } // Parse for an element and return its contents static QString parseElement(QIODevice *device, const QString &elementName) { const QJsonDocument doc = QJsonDocument::fromJson(device->readAll()); if (doc.isEmpty() || !doc.isObject()) return QString(); QJsonObject obj= doc.object(); const QString resultKey = QLatin1String("result"); if (obj.contains(resultKey)) { QJsonValue value = obj.value(resultKey); if (value.isObject()) { obj = value.toObject(); if (obj.contains(elementName)) { value = obj.value(elementName); return value.toString(); } } else if (value.isArray()) { qWarning() << "JsonArray not expected."; } } return QString(); } void StickyNotesPasteProtocol::pasteFinished() { if (m_pasteReply->error()) { qWarning("%s protocol error: %s", qPrintable(name()),qPrintable(m_pasteReply->errorString())); } else { // Parse id from '143204' // No useful error reports have been observed. const QString id = parseElement(m_pasteReply, QLatin1String("id")); if (id.isEmpty()) qWarning("%s protocol error: Could not send entry.", qPrintable(name())); else emit pasteDone(m_hostUrl + id); } m_pasteReply->deleteLater(); m_pasteReply = nullptr; } void StickyNotesPasteProtocol::fetch(const QString &id) { QTC_ASSERT(!m_fetchReply, return); // Did we get a complete URL or just an id? m_fetchId = id; const int lastSlashPos = m_fetchId.lastIndexOf(QLatin1Char('/')); if (lastSlashPos != -1) m_fetchId.remove(0, lastSlashPos + 1); QString url = m_hostUrl + QLatin1String("api/json/show/") + m_fetchId; if (debug) qDebug() << "fetch: sending " << url; m_fetchReply = httpGet(url); connect(m_fetchReply, &QNetworkReply::finished, this, &StickyNotesPasteProtocol::fetchFinished); } // Parse: '143228foo1320661026text // bar' void StickyNotesPasteProtocol::fetchFinished() { const QString title = name() + QLatin1String(": ") + m_fetchId; QString content; const bool error = m_fetchReply->error(); if (error) { content = m_fetchReply->errorString(); if (debug) qDebug() << "fetchFinished: error" << m_fetchId << content; } else { content = parseElement(m_fetchReply, QLatin1String("data")); content.remove(QLatin1Char('\r')); } m_fetchReply->deleteLater(); m_fetchReply = nullptr; emit fetchDone(title, content, error); } void StickyNotesPasteProtocol::list() { QTC_ASSERT(!m_listReply, return); // Trailing slash is important to prevent redirection. QString url = m_hostUrl + QLatin1String("api/json/list"); m_listReply = httpGet(url); connect(m_listReply, &QNetworkReply::finished, this, &StickyNotesPasteProtocol::listFinished); if (debug) qDebug() << "list: sending " << url << m_listReply; } // Parse 'result>id1id2...' static inline QStringList parseList(QIODevice *device) { QStringList result; const QJsonDocument doc = QJsonDocument::fromJson(device->readAll()); if (doc.isEmpty() || !doc.isObject()) return result; QJsonObject obj= doc.object(); const QString resultKey = QLatin1String("result"); const QString pastesKey = QLatin1String("pastes"); if (obj.contains(resultKey)) { QJsonValue value = obj.value(resultKey); if (value.isObject()) { obj = value.toObject(); if (obj.contains(pastesKey)) { value = obj.value(pastesKey); if (value.isArray()) { const QJsonArray array = value.toArray(); for (const QJsonValue &val : array) result.append(val.toString()); } } } } return result; } void StickyNotesPasteProtocol::listFinished() { const bool error = m_listReply->error(); if (error) { if (debug) qDebug() << "listFinished: error" << m_listReply->errorString(); } else { emit listDone(name(), parseList(m_listReply)); } m_listReply->deleteLater(); m_listReply = nullptr; } } // CodePaster