OSDN Git Service

Update license.
[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 (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "pastebindotcomprotocol.h"
34 #include "pastebindotcomsettings.h"
35
36 #include <coreplugin/icore.h>
37
38 #include <utils/qtcassert.h>
39
40 #include <QtCore/QDebug>
41 #include <QtCore/QTextStream>
42 #include <QtCore/QXmlStreamReader>
43 #include <QtCore/QXmlStreamAttributes>
44 #include <QtCore/QByteArray>
45
46 #include <QtNetwork/QNetworkReply>
47
48 enum { debug = 0 };
49
50 static const char pastePhpScriptpC[] = "api_public.php";
51 static const char fetchPhpScriptpC[] = "raw.php";
52
53 namespace CodePaster {
54 PasteBinDotComProtocol::PasteBinDotComProtocol(const NetworkAccessManagerProxyPtr &nw) :
55     NetworkProtocol(nw),
56     m_settings(new PasteBinDotComSettings),
57     m_fetchReply(0),
58     m_pasteReply(0),
59     m_listReply(0),
60     m_fetchId(-1),
61     m_postId(-1),
62     m_hostChecked(false)
63 {
64 }
65
66 QString PasteBinDotComProtocol::protocolName()
67 {
68     return QLatin1String("Pastebin.Com");
69 }
70
71 unsigned PasteBinDotComProtocol::capabilities() const
72 {
73     return ListCapability;
74 }
75
76 bool PasteBinDotComProtocol::checkConfiguration(QString *errorMessage)
77 {
78     if (m_hostChecked)  // Check the host once.
79         return true;
80     const bool ok = httpStatus(hostName(false), errorMessage);
81     if (ok)
82         m_hostChecked = true;
83     return ok;
84 }
85
86 QString PasteBinDotComProtocol::hostName(bool withSubDomain) const
87 {
88
89     QString rc;
90     if (withSubDomain) {
91         rc = m_settings->hostPrefix();
92         if (!rc.isEmpty())
93             rc.append(QLatin1Char('.'));
94     }
95     rc.append(QLatin1String("pastebin.com"));
96     return rc;
97 }
98
99 static inline QByteArray format(Protocol::ContentType ct)
100 {
101     switch (ct) {
102     case Protocol::Text:
103         break;
104     case Protocol::C:
105         return "paste_format=cpp";
106         break;
107     case Protocol::JavaScript:
108         return "paste_format=javascript";
109         break;
110     case Protocol::Diff:
111         return "paste_format=diff"; // v3.X 'dff' -> 'diff'
112         break;
113     case Protocol::Xml:
114         return "paste_format=xml";
115         break;
116     }
117     return QByteArray();
118 }
119
120 void PasteBinDotComProtocol::paste(const QString &text,
121                                    ContentType ct,
122                                    const QString &username,
123                                    const QString & /* comment */,
124                                    const QString & /* description */)
125 {
126     QTC_ASSERT(!m_pasteReply, return;)
127
128     // Format body
129     QByteArray pasteData = format(ct);
130     if (!pasteData.isEmpty())
131         pasteData.append('&');
132     pasteData += "paste_name=";
133     pasteData += QUrl::toPercentEncoding(username);
134
135     const QString subDomain = m_settings->hostPrefix();
136     if (!subDomain.isEmpty()) {
137         pasteData += "&paste_subdomain=";
138         pasteData += QUrl::toPercentEncoding(subDomain);
139     }
140
141     pasteData += "&paste_code=";
142     pasteData += QUrl::toPercentEncoding(fixNewLines(text));
143
144     // fire request
145     QString link;
146     QTextStream(&link) << "http://" << hostName(false) << '/' << pastePhpScriptpC;
147
148     m_pasteReply = httpPost(link, pasteData);
149     connect(m_pasteReply, SIGNAL(finished()), this, SLOT(pasteFinished()));
150     if (debug)
151         qDebug() << "paste: sending " << m_pasteReply << link << pasteData;
152 }
153
154 void PasteBinDotComProtocol::pasteFinished()
155 {
156     if (m_pasteReply->error()) {
157         qWarning("Pastebin.com protocol error: %s", qPrintable(m_pasteReply->errorString()));
158     } else {
159         emit pasteDone(QString::fromAscii(m_pasteReply->readAll()));
160     }
161
162     m_pasteReply->deleteLater();
163     m_pasteReply = 0;
164 }
165
166 void PasteBinDotComProtocol::fetch(const QString &id)
167 {
168     const QString httpProtocolPrefix = QLatin1String("http://");
169
170     QTC_ASSERT(!m_fetchReply, return;)
171
172     // Did we get a complete URL or just an id. Insert a call to the php-script
173     QString link;
174     if (id.startsWith(httpProtocolPrefix)) {
175         // Change "http://host/id" -> "http://host/script?i=id".
176         const int lastSlashPos = id.lastIndexOf(QLatin1Char('/'));
177         link = id.mid(0, lastSlashPos);
178         QTextStream(&link) << '/' << fetchPhpScriptpC<< "?i=" << id.mid(lastSlashPos + 1);
179     } else {
180         // format "http://host/script?i=id".
181         QTextStream(&link) << "http://" << hostName(true) << '/' << fetchPhpScriptpC<< "?i=" << id;
182     }
183
184     if (debug)
185         qDebug() << "fetch: sending " << link;
186
187     m_fetchReply = httpGet(link);
188     connect(m_fetchReply, SIGNAL(finished()), this, SLOT(fetchFinished()));
189     m_fetchId = id;
190 }
191
192 void PasteBinDotComProtocol::fetchFinished()
193 {
194     QString title;
195     QString content;
196     const bool error = m_fetchReply->error();
197     if (error) {
198         content = m_fetchReply->errorString();
199         if (debug)
200             qDebug() << "fetchFinished: error" << m_fetchId << content;
201     } else {
202         title = QString::fromLatin1("Pastebin.com: %1").arg(m_fetchId);
203         content = QString::fromAscii(m_fetchReply->readAll());
204         // Cut out from '<pre>' formatting
205         const int preEnd = content.lastIndexOf(QLatin1String("</pre>"));
206         if (preEnd != -1)
207             content.truncate(preEnd);
208         const int preStart = content.indexOf(QLatin1String("<pre>"));
209         if (preStart != -1)
210             content.remove(0, preStart + 5);
211         // Make applicable as patch.
212         content = Protocol::textFromHtml(content);
213         content += QLatin1Char('\n');
214         // -------------
215         if (debug) {
216             QDebug nsp = qDebug().nospace();
217             nsp << "fetchFinished: " << content.size() << " Bytes";
218             if (debug > 1)
219                 nsp << content;
220         }
221     }
222     m_fetchReply->deleteLater();
223     m_fetchReply = 0;
224     emit fetchDone(title, content, error);
225 }
226
227 void PasteBinDotComProtocol::list()
228 {
229     QTC_ASSERT(!m_listReply, return;)
230
231     // fire request
232     const QString url = QLatin1String("http://") + hostName(true) + QLatin1String("/archive");
233     m_listReply = httpGet(url);
234     connect(m_listReply, SIGNAL(finished()), this, SLOT(listFinished()));
235     if (debug)
236         qDebug() << "list: sending " << url << 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 out the 'archive' table as of 16.3.2011:
247 \code
248  <table class="maintable" cellspacing="0">
249    <tr class="top">
250     <th scope="col" align="left">Name / Title</th>
251     <th scope="col" align="left">Posted</th>
252     <th scope="col" align="left">Expires</th>
253     <th scope="col" align="left">Size</th>
254     <th scope="col" align="left">Syntax</th>
255     <th scope="col" align="left">User</th>
256    </tr>
257    <tr class="g">
258     <td class="icon"><a href="/8ZRqkcaP">Untitled</a></td>
259     <td>2 sec ago</td>
260     <td>Never</td>
261     <td>9.41 KB</td>
262     <td><a href="/archive/text">None</a></td>
263     <td>a guest</td>
264    </tr>
265    <tr>
266 \endcode */
267
268 enum ParseState
269 {
270     OutSideTable, WithinTable, WithinTableRow, WithinTableHeaderElement,
271     WithinTableElement, WithinTableElementAnchor, ParseError
272 };
273
274 static inline ParseState nextOpeningState(ParseState current, const QStringRef &element)
275 {
276     switch (current) {
277     case OutSideTable:
278         if (element == QLatin1String("table"))
279             return WithinTable;
280         return OutSideTable;
281     case WithinTable:
282         if (element == QLatin1String("tr"))
283             return WithinTableRow;
284         break;
285     case WithinTableRow:
286         if (element == QLatin1String("td"))
287             return WithinTableElement;
288         if (element == QLatin1String("th"))
289             return WithinTableHeaderElement;
290         break;
291     case WithinTableElement:
292         if (element == QLatin1String("a"))
293             return WithinTableElementAnchor;
294         break;
295     case WithinTableHeaderElement:
296     case WithinTableElementAnchor:
297     case ParseError:
298         break;
299     }
300     return ParseError;
301 }
302
303 static inline ParseState nextClosingState(ParseState current, const QStringRef &element)
304 {
305     switch (current) {
306     case OutSideTable:
307         return OutSideTable;
308     case WithinTable:
309         if (element == QLatin1String("table"))
310             return OutSideTable;
311         break;
312     case WithinTableRow:
313         if (element == QLatin1String("tr"))
314             return WithinTable;
315         break;
316     case WithinTableElement:
317         if (element == QLatin1String("td"))
318             return WithinTableRow;
319         break;
320     case WithinTableHeaderElement:
321         if (element == QLatin1String("th"))
322             return WithinTableRow;
323         break;
324     case WithinTableElementAnchor:
325         if (element == QLatin1String("a"))
326             return WithinTableElement;
327         break;
328     case ParseError:
329         break;
330     }
331     return ParseError;
332 }
333
334 static inline QStringList parseLists(QIODevice *io)
335 {
336     enum { maxEntries = 200 }; // Limit the archive, which can grow quite large.
337
338     QStringList rc;
339     QXmlStreamReader reader(io);
340     ParseState state = OutSideTable;
341     int tableRow = 0;
342     int tableColumn = 0;
343
344     const QString hrefAttribute = QLatin1String("href");
345
346     QString link;
347     QString user;
348     QString description;
349
350     while (!reader.atEnd()) {
351         switch(reader.readNext()) {
352         case QXmlStreamReader::StartElement:
353             state = nextOpeningState(state, reader.name());
354             switch (state) {
355             case WithinTableRow:
356                 tableColumn = 0;
357                 break;
358             case OutSideTable:
359             case WithinTable:
360             case WithinTableHeaderElement:
361             case WithinTableElement:
362                 break;
363             case WithinTableElementAnchor: // 'href="/svb5K8wS"'
364                 if (tableColumn == 0) {
365                     link = reader.attributes().value(hrefAttribute).toString();
366                     if (link.startsWith(QLatin1Char('/')))
367                         link.remove(0, 1);
368                 }
369                 break;
370             case ParseError:
371                 return rc;
372             } // switch startelement state
373             break;
374         case QXmlStreamReader::EndElement:
375             state = nextClosingState(state, reader.name());
376             switch (state) {
377             case OutSideTable:
378                 if (tableRow) // Seen the table, bye.
379                     return rc;
380                 break;
381             case WithinTable:
382                 if (tableRow && !user.isEmpty() && !link.isEmpty() && !description.isEmpty()) {
383                     QString entry = link;
384                     entry += QLatin1Char(' ');
385                     entry += user;
386                     entry += QLatin1Char(' ');
387                     entry += description;
388                     rc.push_back(entry);
389                     if (rc.size() >= maxEntries)
390                         return rc;
391                 }
392                 tableRow++;
393                 user.clear();
394                 link.clear();
395                 description.clear();
396                 break;
397             case WithinTableRow:
398                 tableColumn++;
399                 break;
400             case WithinTableHeaderElement:
401             case WithinTableElement:
402             case WithinTableElementAnchor:
403                 break;
404             case ParseError:
405                 return rc;
406             } // switch endelement state
407             break;
408         case QXmlStreamReader::Characters:
409             switch (state) {
410                 break;
411             case WithinTableElement:
412                 if (tableColumn == 5)
413                     user = reader.text().toString();
414                 break;
415             case WithinTableElementAnchor:
416                 if (tableColumn == 0)
417                     description = reader.text().toString();
418                 break;
419             default:
420                 break;
421             } // switch characters read state
422             break;
423        default:
424             break;
425         } // switch reader state
426     }
427     return rc;
428 }
429
430 void PasteBinDotComProtocol::listFinished()
431 {
432     const bool error = m_listReply->error();
433     if (error) {
434         if (debug)
435             qDebug() << "listFinished: error" << m_listReply->errorString();
436     } else {
437         QStringList list = parseLists(m_listReply);
438         emit listDone(name(), list);
439         if (debug)
440             qDebug() << list;
441     }
442     m_listReply->deleteLater();
443     m_listReply = 0;
444 }
445
446 Core::IOptionsPage *PasteBinDotComProtocol::settingsPage() const
447 {
448     return m_settings;
449 }
450 } // namespace CodePaster