From 8c20130563a5d1d5aa322826ac1ce1560ba42ad0 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Thu, 8 Apr 2010 10:39:26 +0200
Subject: [PATCH] CodePaster: Make pastebin.ca protocol work.

Replace deprecated QHttp by QNetworkReply.
Implement listing functionality, fix fetch
encoding.
---
 src/plugins/cpaster/codepasterprotocol.cpp    |   9 +-
 src/plugins/cpaster/pastebindotcaprotocol.cpp | 153 ++++++++++++++----
 src/plugins/cpaster/pastebindotcaprotocol.h   |  29 ++--
 3 files changed, 143 insertions(+), 48 deletions(-)

diff --git a/src/plugins/cpaster/codepasterprotocol.cpp b/src/plugins/cpaster/codepasterprotocol.cpp
index 39b21af8e64..f86c9f800b6 100644
--- a/src/plugins/cpaster/codepasterprotocol.cpp
+++ b/src/plugins/cpaster/codepasterprotocol.cpp
@@ -30,7 +30,6 @@
 #include "codepasterprotocol.h"
 #include "codepastersettings.h"
 #include "cpasterplugin.h"
-#include "cgi.h"
 
 #include <coreplugin/coreconstants.h>
 #include <coreplugin/editormanager/editormanager.h>
@@ -130,13 +129,13 @@ void CodePasterProtocol::paste(const QString &text,
         return;
 
     QByteArray data = "command=processcreate&submit=submit&highlight_type=0&description=";
-    data += CGI::encodeURL(description).toLatin1();
+    data += QUrl::toPercentEncoding(description);
     data += "&comment=";
-    data += CGI::encodeURL(comment).toLatin1();
+    data += QUrl::toPercentEncoding(comment);
     data += "&code=";
-    data += CGI::encodeURL(fixNewLines(text)).toLatin1();
+    data += QUrl::toPercentEncoding(fixNewLines(text));
     data += "&poster=";
-    data += CGI::encodeURL(username).toLatin1();
+    data += QUrl::toPercentEncoding(username);
 
     m_pasteReply = httpPost(QLatin1String("http://") + hostName, data);
     connect(m_pasteReply, SIGNAL(finished()), this, SLOT(pasteFinished()));
diff --git a/src/plugins/cpaster/pastebindotcaprotocol.cpp b/src/plugins/cpaster/pastebindotcaprotocol.cpp
index 7984ee64c8a..76bad682df7 100644
--- a/src/plugins/cpaster/pastebindotcaprotocol.cpp
+++ b/src/plugins/cpaster/pastebindotcaprotocol.cpp
@@ -28,24 +28,46 @@
 **************************************************************************/
 
 #include "pastebindotcaprotocol.h"
-#include "cgi.h"
+
+#include <utils/qtcassert.h>
 
 #include <QtNetwork/QNetworkReply>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QXmlStreamAttributes>
+#include <QtCore/QStringList>
+
+static const char urlC[] = "http://pastebin.ca/";
 
 namespace CodePaster {
 PasteBinDotCaProtocol::PasteBinDotCaProtocol(const NetworkAccessManagerProxyPtr &nw) :
     NetworkProtocol(nw),
     m_fetchReply(0),
-    m_postId(-1)
+    m_listReply(0),
+    m_pasteReply(0)
 {
-    connect(&m_http, SIGNAL(requestFinished(int,bool)),
-            this, SLOT(postRequestFinished(int,bool)));
+}
+
+unsigned PasteBinDotCaProtocol::capabilities() const
+{
+    return ListCapability | PostDescriptionCapability;
 }
 
 void PasteBinDotCaProtocol::fetch(const QString &id)
 {
-    QString link = QLatin1String("http://pastebin.ca/raw/");
-    link.append(id);
+    QTC_ASSERT(!m_fetchReply, return)
+    const QString url = QLatin1String(urlC);
+    const QString rawPostFix = QLatin1String("raw/");
+    // Create link as ""http://pastebin.ca/raw/[id]"
+    // If we get a complete URL, just insert 'raw', else build URL.
+    QString link = id;
+    if (link.startsWith(url)) {
+        const int lastSlashPos = link.lastIndexOf(QLatin1Char('/'));
+        if (lastSlashPos != -1)
+            link.insert(lastSlashPos + 1, rawPostFix);
+    } else {
+        link.insert(0, rawPostFix);
+        link.insert(0, url);
+    }
     m_fetchReply = httpGet(link);
     connect(m_fetchReply, SIGNAL(finished()), this, SLOT(fetchFinished()));
     m_fetchId = id;
@@ -54,36 +76,34 @@ void PasteBinDotCaProtocol::fetch(const QString &id)
 void PasteBinDotCaProtocol::paste(const QString &text,
                                   ContentType /* ct */,
                                   const QString &username,
-                                  const QString &comment,
+                                  const QString & /* comment */,
                                   const QString &description)
 {
-    Q_UNUSED(comment);
-    Q_UNUSED(description);
-    QString data = "content=";
-    data += CGI::encodeURL(fixNewLines(text));
+    QTC_ASSERT(!m_pasteReply, return)
+    QByteArray data = "content=";
+    data += QUrl::toPercentEncoding(fixNewLines(text));
     data += "&description=";
-    data += CGI::encodeURL(description);
+    data += QUrl::toPercentEncoding(description);
     data += "&type=1&expiry=1%20day&name=";
-    data += CGI::encodeURL(username);
-    QHttpRequestHeader header("POST", "/quiet-paste.php");
-    header.setValue("host", "pastebin.ca" );
-    header.setContentType("application/x-www-form-urlencoded");
-    m_http.setHost("pastebin.ca", QHttp::ConnectionModeHttp);
-    header.setValue("User-Agent", "CreatorPastebin");
-    m_postId = m_http.request(header, data.toAscii());
+    data += QUrl::toPercentEncoding(username);
+    // fire request
+    const QString link = QLatin1String(urlC) + QLatin1String("quiet-paste.php");
+    m_pasteReply = httpPost(link, data);
+    connect(m_pasteReply, SIGNAL(finished()), this, SLOT(pasteFinished()));
 }
 
-void PasteBinDotCaProtocol::postRequestFinished(int id, bool error)
+void PasteBinDotCaProtocol::pasteFinished()
 {
-    QString link;
-    if (id == m_postId) {
-        if (!error) {
-            QByteArray data = m_http.readAll();
-            link = QString::fromLatin1("http://pastebin.ca/") + QString(data).remove("SUCCESS:");
-        } else
-            link = m_http.errorString();
+    if (m_pasteReply->error()) {
+        qWarning("Pastebin.ca protocol error: %s", qPrintable(m_pasteReply->errorString()));
+    } else {
+        /// returns ""SUCCESS:[id]""
+        const QByteArray data = m_pasteReply->readAll();
+        const QString link = QString::fromLatin1(urlC) + QString::fromAscii(data).remove(QLatin1String("SUCCESS:"));
         emit pasteDone(link);
     }
+    m_pasteReply->deleteLater();
+    m_pasteReply = 0;
 }
 
 void PasteBinDotCaProtocol::fetchFinished()
@@ -95,10 +115,87 @@ void PasteBinDotCaProtocol::fetchFinished()
         content = m_fetchReply->errorString();
     } else {
         title = QString::fromLatin1("Pastebin.ca: %1").arg(m_fetchId);
-        content = m_fetchReply->readAll();
+        const QByteArray data = m_fetchReply->readAll();
+        content = QString::fromUtf8(data);
+        content.remove(QLatin1Char('\r'));
     }
     m_fetchReply->deleteLater();
     m_fetchReply = 0;
     emit fetchDone(title, content, error);
 }
+
+void PasteBinDotCaProtocol::list()
+{
+    QTC_ASSERT(!m_listReply, return);
+    m_listReply = httpGet(QLatin1String(urlC));
+    connect(m_listReply, SIGNAL(finished()), this, SLOT(listFinished()));
+}
+
+/* Quick & dirty: Parse the <div>-elements with the "Recent Posts" listing
+ * out of the page.
+\code
+<div class="menutitle"><h2>Recent Posts</h2></div>
+    <div class="items" id="idmenurecent-collapse">
+        <div class='recentlink'>
+            <a href="/[id]" class="rjt" rel="/preview.php?id=[id]">[nameTitle]</a>
+            <div class='recentdetail'>[time spec]</div>
+        </div>
+ ...<h2>Create a New Pastebin Post</h2>
+\endcode */
+
+static inline QStringList parseLists(QIODevice *io)
+{
+    enum State { OutsideRecentLink, InsideRecentLink };
+
+    QStringList rc;
+
+    const QString classAttribute = QLatin1String("class");
+    const QString divElement = QLatin1String("div");
+    const QString anchorElement = QLatin1String("a");
+
+    // Start parsing at the 'recent posts' entry as the HTML above is not well-formed
+    // as of 8.4.2010. This will then terminate with an error.
+    QByteArray data = io->readAll();
+    const QByteArray recentPosts("<h2>Recent Posts</h2></div>");
+    const int recentPostsPos = data.indexOf(recentPosts);
+    if (recentPostsPos == -1)
+        return rc;
+    data.remove(0, recentPostsPos + recentPosts.size());
+    QXmlStreamReader reader(data);
+    State state = OutsideRecentLink;
+    while (!reader.atEnd()) {
+        switch(reader.readNext()) {
+        case QXmlStreamReader::StartElement:
+            // Inside a <div> of an entry: Anchor or description
+            if (state == InsideRecentLink && reader.name() == anchorElement) { // Anchor
+                // Strip host from link
+                QString link = reader.attributes().value(QLatin1String("href")).toString();
+                if (link.startsWith(QLatin1Char('/')))
+                    link.remove(0, 1);
+                const QString nameTitle = reader.readElementText();
+                rc.push_back(link + QLatin1Char(' ') + nameTitle);
+            } else if (state == OutsideRecentLink && reader.name() == divElement) { // "<div>" state switching
+                if (reader.attributes().value(classAttribute) == QLatin1String("recentlink"))
+                    state = InsideRecentLink;
+            } // divElement
+            break;
+       default:
+            break;
+        } // switch reader
+    } // while reader.atEnd()
+    return rc;
+}
+
+void PasteBinDotCaProtocol::listFinished()
+{
+    const bool error = m_listReply->error();
+    if (error) {
+        qWarning("pastebin.ca list failed: %s", qPrintable(m_listReply->errorString()));
+    } else {
+        emit listDone(name(), parseLists(m_listReply));
+    }
+    m_listReply->deleteLater();
+    m_listReply = 0;
+}
+
 } // namespace CodePaster
diff --git a/src/plugins/cpaster/pastebindotcaprotocol.h b/src/plugins/cpaster/pastebindotcaprotocol.h
index 4cc766d4b01..38e1e12abe2 100644
--- a/src/plugins/cpaster/pastebindotcaprotocol.h
+++ b/src/plugins/cpaster/pastebindotcaprotocol.h
@@ -32,8 +32,6 @@
 
 #include "protocol.h"
 
-#include <QtNetwork/QHttp>
-
 namespace CodePaster {
 class PasteBinDotCaProtocol : public NetworkProtocol
 {
@@ -42,26 +40,27 @@ public:
     explicit PasteBinDotCaProtocol(const NetworkAccessManagerProxyPtr &nw);
     QString name() const { return QLatin1String("Pastebin.Ca"); }
 
-    bool hasSettings() const { return false; }
-    virtual unsigned capabilities() const { return 0; }
+    virtual bool hasSettings() const { return false; }
+    virtual unsigned capabilities() const;
+
+    virtual void fetch(const QString &id);
+    virtual void paste(const QString &text,
+                       ContentType ct = Text,
+                       const QString &username = QString(),
+                       const QString &comment = QString(),
+                       const QString &description = QString());
+    virtual void list();
 
-    void fetch(const QString &id);
-    void paste(const QString &text,
-               ContentType ct = Text,
-               const QString &username = QString(),
-               const QString &comment = QString(),
-               const QString &description = QString());
 public slots:
     void fetchFinished();
-
-    void postRequestFinished(int id, bool error);
+    void listFinished();
+    void pasteFinished();
 
 private:
     QNetworkReply *m_fetchReply;
+    QNetworkReply *m_listReply;
+    QNetworkReply *m_pasteReply;
     QString m_fetchId;
-
-    QHttp m_http;
-    int m_postId;
 };
 
 } // namespace CodePaster
-- 
GitLab