OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / cpaster / pastebindotcomprotocol.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "pastebindotcomprotocol.h"
35 #include "pastebindotcomsettings.h"
36
37 #include <coreplugin/icore.h>
38
39 #include <utils/qtcassert.h>
40
41 #include <QtCore/QDebug>
42 #include <QtCore/QTextStream>
43 #include <QtCore/QXmlStreamReader>
44 #include <QtCore/QXmlStreamAttributes>
45 #include <QtCore/QByteArray>
46
47 #include <QtNetwork/QNetworkReply>
48
49 enum { debug = 0 };
50
51 static const char pastePhpScriptpC[] = "api_public.php";
52 static const char fetchPhpScriptpC[] = "raw.php";
53
54 namespace CodePaster {
55 PasteBinDotComProtocol::PasteBinDotComProtocol(const NetworkAccessManagerProxyPtr &nw) :
56     NetworkProtocol(nw),
57     m_settings(new PasteBinDotComSettings),
58     m_fetchReply(0),
59     m_pasteReply(0),
60     m_listReply(0),
61     m_fetchId(-1),
62     m_postId(-1),
63     m_hostChecked(false)
64 {
65 }
66
67 QString PasteBinDotComProtocol::protocolName()
68 {
69     return QLatin1String("Pastebin.Com");
70 }
71
72 unsigned PasteBinDotComProtocol::capabilities() const
73 {
74     return ListCapability;
75 }
76
77 bool PasteBinDotComProtocol::checkConfiguration(QString *errorMessage)
78 {
79     if (m_hostChecked)  // Check the host once.
80         return true;
81     const bool ok = httpStatus(hostName(false), errorMessage);
82     if (ok)
83         m_hostChecked = true;
84     return ok;
85 }
86
87 QString PasteBinDotComProtocol::hostName(bool withSubDomain) const
88 {
89
90     QString rc;
91     if (withSubDomain) {
92         rc = m_settings->hostPrefix();
93         if (!rc.isEmpty())
94             rc.append(QLatin1Char('.'));
95     }
96     rc.append(QLatin1String("pastebin.com"));
97     return rc;
98 }
99
100 static inline QByteArray format(Protocol::ContentType ct)
101 {
102     switch (ct) {
103     case Protocol::Text:
104         break;
105     case Protocol::C:
106         return "paste_format=cpp";
107         break;
108     case Protocol::JavaScript:
109         return "paste_format=javascript";
110         break;
111     case Protocol::Diff:
112         return "paste_format=dff";
113         break;
114     case Protocol::Xml:
115         return "paste_format=xml";
116         break;
117     }
118     return QByteArray();
119 }
120
121 void PasteBinDotComProtocol::paste(const QString &text,
122                                    ContentType ct,
123                                    const QString &username,
124                                    const QString & /* comment */,
125                                    const QString & /* description */)
126 {
127     QTC_ASSERT(!m_pasteReply, return;)
128
129     // Format body
130     QByteArray pasteData = format(ct);
131     if (!pasteData.isEmpty())
132         pasteData.append('&');
133     pasteData += "paste_name=";
134     pasteData += QUrl::toPercentEncoding(username);
135
136     const QString subDomain = m_settings->hostPrefix();
137     if (!subDomain.isEmpty()) {
138         pasteData += "&paste_subdomain=";
139         pasteData += QUrl::toPercentEncoding(subDomain);
140     }
141
142     pasteData += "&paste_code=";
143     pasteData += QUrl::toPercentEncoding(fixNewLines(text));
144
145     // fire request
146     QString link;
147     QTextStream(&link) << "http://" << hostName(false) << '/' << pastePhpScriptpC;
148
149     m_pasteReply = httpPost(link, pasteData);
150     connect(m_pasteReply, SIGNAL(finished()), this, SLOT(pasteFinished()));
151     if (debug)
152         qDebug() << "paste: sending " << m_pasteReply << link << pasteData;
153 }
154
155 void PasteBinDotComProtocol::pasteFinished()
156 {
157     if (m_pasteReply->error()) {
158         qWarning("Pastebin.com protocol error: %s", qPrintable(m_pasteReply->errorString()));
159     } else {
160         emit pasteDone(QString::fromAscii(m_pasteReply->readAll()));
161     }
162
163     m_pasteReply->deleteLater();
164     m_pasteReply = 0;
165 }
166
167 void PasteBinDotComProtocol::fetch(const QString &id)
168 {
169     const QString httpProtocolPrefix = QLatin1String("http://");
170
171     QTC_ASSERT(!m_fetchReply, return;)
172
173     // Did we get a complete URL or just an id. Insert a call to the php-script
174     QString link;
175     if (id.startsWith(httpProtocolPrefix)) {
176         // Change "http://host/id" -> "http://host/script?i=id".
177         const int lastSlashPos = id.lastIndexOf(QLatin1Char('/'));
178         link = id.mid(0, lastSlashPos);
179         QTextStream(&link) << '/' << fetchPhpScriptpC<< "?i=" << id.mid(lastSlashPos + 1);
180     } else {
181         // format "http://host/script?i=id".
182         QTextStream(&link) << "http://" << hostName(true) << '/' << fetchPhpScriptpC<< "?i=" << id;
183     }
184
185     if (debug)
186         qDebug() << "fetch: sending " << link;
187
188     m_fetchReply = httpGet(link);
189     connect(m_fetchReply, SIGNAL(finished()), this, SLOT(fetchFinished()));
190     m_fetchId = id;
191 }
192
193 void PasteBinDotComProtocol::fetchFinished()
194 {
195     QString title;
196     QString content;
197     const bool error = m_fetchReply->error();
198     if (error) {
199         content = m_fetchReply->errorString();
200         if (debug)
201             qDebug() << "fetchFinished: error" << m_fetchId << content;
202     } else {
203         title = QString::fromLatin1("Pastebin.com: %1").arg(m_fetchId);
204         content = QString::fromAscii(m_fetchReply->readAll());
205         // Cut out from '<pre>' formatting
206         const int preEnd = content.lastIndexOf(QLatin1String("</pre>"));
207         if (preEnd != -1)
208             content.truncate(preEnd);
209         const int preStart = content.indexOf(QLatin1String("<pre>"));
210         if (preStart != -1)
211             content.remove(0, preStart + 5);
212         // Make applicable as patch.
213         content = Protocol::textFromHtml(content);
214         content += QLatin1Char('\n');
215         // -------------
216         if (debug) {
217             QDebug nsp = qDebug().nospace();
218             nsp << "fetchFinished: " << content.size() << " Bytes";
219             if (debug > 1)
220                 nsp << content;
221         }
222     }
223     m_fetchReply->deleteLater();
224     m_fetchReply = 0;
225     emit fetchDone(title, content, error);
226 }
227
228 void PasteBinDotComProtocol::list()
229 {
230     QTC_ASSERT(!m_listReply, return;)
231
232     // fire request
233     m_listReply = httpGet(QLatin1String("http://") + hostName(true));
234     connect(m_listReply, SIGNAL(finished()), this, SLOT(listFinished()));
235     if (debug)
236         qDebug() << "list: sending " << m_listReply;
237 }
238
239 static inline void padString(QString *s, int len)
240 {
241     const int missing = len - s->length();
242     if (missing > 0)
243         s->append(QString(missing, QLatin1Char(' ')));
244 }
245
246 /* Quick & dirty: Parse the <div>-elements with the "Recent Posts" listing
247  * out of the page.
248 \code
249 <div class="content_left_title">Recent Posts</div>
250     <div class="content_left_box">
251         <div class="clb_top"><a href="http://pastebin.com/id">User</a></div>
252         <div class="clb_bottom"><span>Title</div>
253 \endcode */
254
255 static inline QStringList parseLists(QIODevice *io)
256 {
257     enum State { OutsideRecentPostList, InsideRecentPostList,
258                  InsideRecentPostBox, InsideRecentPost };
259
260     QStringList rc;
261     QXmlStreamReader reader(io);
262
263     const QString classAttribute = QLatin1String("class");
264     const QString divElement = QLatin1String("div");
265     const QString anchorElement = QLatin1String("a");
266     const QString spanElement = QLatin1String("span");
267     State state = OutsideRecentPostList;
268     while (!reader.atEnd()) {
269         switch(reader.readNext()) {
270         case QXmlStreamReader::StartElement:
271             // Inside a <div> of an entry: Anchor or description
272             if (state == InsideRecentPost) {
273                 if (reader.name() == anchorElement) { // Anchor
274                     // Strip host from link
275                     QString link = reader.attributes().value(QLatin1String("href")).toString();
276                     const int slashPos = link.lastIndexOf(QLatin1Char('/'));
277                     if (slashPos != -1)
278                         link.remove(0, slashPos + 1);
279                     const QString user = reader.readElementText();
280                     rc.push_back(link + QLatin1Char(' ') + user);
281                 } else if (reader.name() == spanElement) { // <span> with description
282                     const QString description = reader.readElementText();
283                     QTC_ASSERT(!rc.isEmpty(), return rc; )
284                     padString(&rc.back(), 25);
285                     rc.back() += QLatin1Char(' ');
286                     rc.back() += description;
287                 }
288             } else if (reader.name() == divElement) { // "<div>" state switching
289                 switch (state) {
290                 // Check on the contents as there are several lists.
291                 case OutsideRecentPostList:
292                     if (reader.attributes().value(classAttribute) == QLatin1String("content_left_title")
293                             && reader.readElementText() == QLatin1String("Recent Posts"))
294                         state = InsideRecentPostList;
295                     break;
296                 case InsideRecentPostList:
297                     if (reader.attributes().value(classAttribute) == QLatin1String("content_left_box"))
298                         state = InsideRecentPostBox;
299                     break;
300                 case InsideRecentPostBox:
301                     state = InsideRecentPost;
302                     break;
303                 default:
304                     break;
305                 }
306             } // divElement
307             break;
308         case QXmlStreamReader::EndElement:
309             if (reader.name() == divElement) {
310                 switch (state) {
311                 case InsideRecentPost:
312                     state = InsideRecentPostBox;
313                     break;
314                 case InsideRecentPostBox: // Stop parsing  when leaving the box.
315                     return rc;
316                     break;
317                 default:
318                     break;
319                 }
320             }
321             break;
322        default:
323             break;
324         }
325     }
326     return rc;
327 }
328
329 void PasteBinDotComProtocol::listFinished()
330 {
331     const bool error = m_listReply->error();
332     if (error) {
333         if (debug)
334             qDebug() << "listFinished: error" << m_listReply->errorString();
335     } else {
336         QStringList list = parseLists(m_listReply);
337         emit listDone(name(), list);
338         if (debug)
339             qDebug() << list;
340     }
341     m_listReply->deleteLater();
342     m_listReply = 0;
343 }
344
345 Core::IOptionsPage *PasteBinDotComProtocol::settingsPage() const
346 {
347     return m_settings;
348 }
349 } // namespace CodePaster