OSDN Git Service

111f6a4a07c653a02f75105017939dab37eb4c21
[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=diff"; // v3.X 'dff' -> 'diff'
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     const QString url = QLatin1String("http://") + hostName(true) + QLatin1String("/archive");
234     m_listReply = httpGet(url);
235     connect(m_listReply, SIGNAL(finished()), this, SLOT(listFinished()));
236     if (debug)
237         qDebug() << "list: sending " << url << m_listReply;
238 }
239
240 static inline void padString(QString *s, int len)
241 {
242     const int missing = len - s->length();
243     if (missing > 0)
244         s->append(QString(missing, QLatin1Char(' ')));
245 }
246
247 /* Quick & dirty: Parse out the 'archive' table as of 16.3.2011:
248 \code
249  <table class="maintable" cellspacing="0">
250    <tr class="top">
251     <th scope="col" align="left">Name / Title</th>
252     <th scope="col" align="left">Posted</th>
253     <th scope="col" align="left">Expires</th>
254     <th scope="col" align="left">Size</th>
255     <th scope="col" align="left">Syntax</th>
256     <th scope="col" align="left">User</th>
257    </tr>
258    <tr class="g">
259     <td class="icon"><a href="/8ZRqkcaP">Untitled</a></td>
260     <td>2 sec ago</td>
261     <td>Never</td>
262     <td>9.41 KB</td>
263     <td><a href="/archive/text">None</a></td>
264     <td>a guest</td>
265    </tr>
266    <tr>
267 \endcode */
268
269 enum ParseState
270 {
271     OutSideTable, WithinTable, WithinTableRow, WithinTableHeaderElement,
272     WithinTableElement, WithinTableElementAnchor, ParseError
273 };
274
275 static inline ParseState nextOpeningState(ParseState current, const QStringRef &element)
276 {
277     switch (current) {
278     case OutSideTable:
279         if (element == QLatin1String("table"))
280             return WithinTable;
281         return OutSideTable;
282     case WithinTable:
283         if (element == QLatin1String("tr"))
284             return WithinTableRow;
285         break;
286     case WithinTableRow:
287         if (element == QLatin1String("td"))
288             return WithinTableElement;
289         if (element == QLatin1String("th"))
290             return WithinTableHeaderElement;
291         break;
292     case WithinTableElement:
293         if (element == QLatin1String("a"))
294             return WithinTableElementAnchor;
295         break;
296     case WithinTableHeaderElement:
297     case WithinTableElementAnchor:
298     case ParseError:
299         break;
300     }
301     return ParseError;
302 }
303
304 static inline ParseState nextClosingState(ParseState current, const QStringRef &element)
305 {
306     switch (current) {
307     case OutSideTable:
308         return OutSideTable;
309     case WithinTable:
310         if (element == QLatin1String("table"))
311             return OutSideTable;
312         break;
313     case WithinTableRow:
314         if (element == QLatin1String("tr"))
315             return WithinTable;
316         break;
317     case WithinTableElement:
318         if (element == QLatin1String("td"))
319             return WithinTableRow;
320         break;
321     case WithinTableHeaderElement:
322         if (element == QLatin1String("th"))
323             return WithinTableRow;
324         break;
325     case WithinTableElementAnchor:
326         if (element == QLatin1String("a"))
327             return WithinTableElement;
328         break;
329     case ParseError:
330         break;
331     }
332     return ParseError;
333 }
334
335 static inline QStringList parseLists(QIODevice *io)
336 {
337     enum { maxEntries = 200 }; // Limit the archive, which can grow quite large.
338
339     QStringList rc;
340     QXmlStreamReader reader(io);
341     ParseState state = OutSideTable;
342     int tableRow = 0;
343     int tableColumn = 0;
344
345     const QString hrefAttribute = QLatin1String("href");
346
347     QString link;
348     QString user;
349     QString description;
350
351     while (!reader.atEnd()) {
352         switch(reader.readNext()) {
353         case QXmlStreamReader::StartElement:
354             state = nextOpeningState(state, reader.name());
355             switch (state) {
356             case WithinTableRow:
357                 tableColumn = 0;
358                 break;
359             case OutSideTable:
360             case WithinTable:
361             case WithinTableHeaderElement:
362             case WithinTableElement:
363                 break;
364             case WithinTableElementAnchor: // 'href="/svb5K8wS"'
365                 if (tableColumn == 0) {
366                     link = reader.attributes().value(hrefAttribute).toString();
367                     if (link.startsWith(QLatin1Char('/')))
368                         link.remove(0, 1);
369                 }
370                 break;
371             case ParseError:
372                 return rc;
373             } // switch startelement state
374             break;
375         case QXmlStreamReader::EndElement:
376             state = nextClosingState(state, reader.name());
377             switch (state) {
378             case OutSideTable:
379                 if (tableRow) // Seen the table, bye.
380                     return rc;
381                 break;
382             case WithinTable:
383                 if (tableRow && !user.isEmpty() && !link.isEmpty() && !description.isEmpty()) {
384                     QString entry = link;
385                     entry += QLatin1Char(' ');
386                     entry += user;
387                     entry += QLatin1Char(' ');
388                     entry += description;
389                     rc.push_back(entry);
390                     if (rc.size() >= maxEntries)
391                         return rc;
392                 }
393                 tableRow++;
394                 user.clear();
395                 link.clear();
396                 description.clear();
397                 break;
398             case WithinTableRow:
399                 tableColumn++;
400                 break;
401             case WithinTableHeaderElement:
402             case WithinTableElement:
403             case WithinTableElementAnchor:
404                 break;
405             case ParseError:
406                 return rc;
407             } // switch endelement state
408             break;
409         case QXmlStreamReader::Characters:
410             switch (state) {
411                 break;
412             case WithinTableElement:
413                 if (tableColumn == 5)
414                     user = reader.text().toString();
415                 break;
416             case WithinTableElementAnchor:
417                 if (tableColumn == 0)
418                     description = reader.text().toString();
419                 break;
420             default:
421                 break;
422             } // switch characters read state
423             break;
424        default:
425             break;
426         } // switch reader state
427     }
428     return rc;
429 }
430
431 void PasteBinDotComProtocol::listFinished()
432 {
433     const bool error = m_listReply->error();
434     if (error) {
435         if (debug)
436             qDebug() << "listFinished: error" << m_listReply->errorString();
437     } else {
438         QStringList list = parseLists(m_listReply);
439         emit listDone(name(), list);
440         if (debug)
441             qDebug() << list;
442     }
443     m_listReply->deleteLater();
444     m_listReply = 0;
445 }
446
447 Core::IOptionsPage *PasteBinDotComProtocol::settingsPage() const
448 {
449     return m_settings;
450 }
451 } // namespace CodePaster