1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
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.
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.
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.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "pastebindotcomprotocol.h"
34 #include "pastebindotcomsettings.h"
36 #include <coreplugin/icore.h>
38 #include <utils/qtcassert.h>
40 #include <QtCore/QDebug>
41 #include <QtCore/QTextStream>
42 #include <QtCore/QXmlStreamReader>
43 #include <QtCore/QXmlStreamAttributes>
44 #include <QtCore/QByteArray>
46 #include <QtNetwork/QNetworkReply>
50 static const char pastePhpScriptpC[] = "api_public.php";
51 static const char fetchPhpScriptpC[] = "raw.php";
53 namespace CodePaster {
54 PasteBinDotComProtocol::PasteBinDotComProtocol(const NetworkAccessManagerProxyPtr &nw) :
56 m_settings(new PasteBinDotComSettings),
66 QString PasteBinDotComProtocol::protocolName()
68 return QLatin1String("Pastebin.Com");
71 unsigned PasteBinDotComProtocol::capabilities() const
73 return ListCapability;
76 bool PasteBinDotComProtocol::checkConfiguration(QString *errorMessage)
78 if (m_hostChecked) // Check the host once.
80 const bool ok = httpStatus(hostName(false), errorMessage);
86 QString PasteBinDotComProtocol::hostName(bool withSubDomain) const
91 rc = m_settings->hostPrefix();
93 rc.append(QLatin1Char('.'));
95 rc.append(QLatin1String("pastebin.com"));
99 static inline QByteArray format(Protocol::ContentType ct)
105 return "paste_format=cpp";
107 case Protocol::JavaScript:
108 return "paste_format=javascript";
111 return "paste_format=diff"; // v3.X 'dff' -> 'diff'
114 return "paste_format=xml";
120 void PasteBinDotComProtocol::paste(const QString &text,
122 const QString &username,
123 const QString & /* comment */,
124 const QString & /* description */)
126 QTC_ASSERT(!m_pasteReply, return;)
129 QByteArray pasteData = format(ct);
130 if (!pasteData.isEmpty())
131 pasteData.append('&');
132 pasteData += "paste_name=";
133 pasteData += QUrl::toPercentEncoding(username);
135 const QString subDomain = m_settings->hostPrefix();
136 if (!subDomain.isEmpty()) {
137 pasteData += "&paste_subdomain=";
138 pasteData += QUrl::toPercentEncoding(subDomain);
141 pasteData += "&paste_code=";
142 pasteData += QUrl::toPercentEncoding(fixNewLines(text));
146 QTextStream(&link) << "http://" << hostName(false) << '/' << pastePhpScriptpC;
148 m_pasteReply = httpPost(link, pasteData);
149 connect(m_pasteReply, SIGNAL(finished()), this, SLOT(pasteFinished()));
151 qDebug() << "paste: sending " << m_pasteReply << link << pasteData;
154 void PasteBinDotComProtocol::pasteFinished()
156 if (m_pasteReply->error()) {
157 qWarning("Pastebin.com protocol error: %s", qPrintable(m_pasteReply->errorString()));
159 emit pasteDone(QString::fromAscii(m_pasteReply->readAll()));
162 m_pasteReply->deleteLater();
166 void PasteBinDotComProtocol::fetch(const QString &id)
168 const QString httpProtocolPrefix = QLatin1String("http://");
170 QTC_ASSERT(!m_fetchReply, return;)
172 // Did we get a complete URL or just an id. Insert a call to the php-script
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);
180 // format "http://host/script?i=id".
181 QTextStream(&link) << "http://" << hostName(true) << '/' << fetchPhpScriptpC<< "?i=" << id;
185 qDebug() << "fetch: sending " << link;
187 m_fetchReply = httpGet(link);
188 connect(m_fetchReply, SIGNAL(finished()), this, SLOT(fetchFinished()));
192 void PasteBinDotComProtocol::fetchFinished()
196 const bool error = m_fetchReply->error();
198 content = m_fetchReply->errorString();
200 qDebug() << "fetchFinished: error" << m_fetchId << content;
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>"));
207 content.truncate(preEnd);
208 const int preStart = content.indexOf(QLatin1String("<pre>"));
210 content.remove(0, preStart + 5);
211 // Make applicable as patch.
212 content = Protocol::textFromHtml(content);
213 content += QLatin1Char('\n');
216 QDebug nsp = qDebug().nospace();
217 nsp << "fetchFinished: " << content.size() << " Bytes";
222 m_fetchReply->deleteLater();
224 emit fetchDone(title, content, error);
227 void PasteBinDotComProtocol::list()
229 QTC_ASSERT(!m_listReply, return;)
232 const QString url = QLatin1String("http://") + hostName(true) + QLatin1String("/archive");
233 m_listReply = httpGet(url);
234 connect(m_listReply, SIGNAL(finished()), this, SLOT(listFinished()));
236 qDebug() << "list: sending " << url << m_listReply;
239 static inline void padString(QString *s, int len)
241 const int missing = len - s->length();
243 s->append(QString(missing, QLatin1Char(' ')));
246 /* Quick & dirty: Parse out the 'archive' table as of 16.3.2011:
248 <table class="maintable" cellspacing="0">
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>
258 <td class="icon"><a href="/8ZRqkcaP">Untitled</a></td>
262 <td><a href="/archive/text">None</a></td>
270 OutSideTable, WithinTable, WithinTableRow, WithinTableHeaderElement,
271 WithinTableElement, WithinTableElementAnchor, ParseError
274 static inline ParseState nextOpeningState(ParseState current, const QStringRef &element)
278 if (element == QLatin1String("table"))
282 if (element == QLatin1String("tr"))
283 return WithinTableRow;
286 if (element == QLatin1String("td"))
287 return WithinTableElement;
288 if (element == QLatin1String("th"))
289 return WithinTableHeaderElement;
291 case WithinTableElement:
292 if (element == QLatin1String("a"))
293 return WithinTableElementAnchor;
295 case WithinTableHeaderElement:
296 case WithinTableElementAnchor:
303 static inline ParseState nextClosingState(ParseState current, const QStringRef &element)
309 if (element == QLatin1String("table"))
313 if (element == QLatin1String("tr"))
316 case WithinTableElement:
317 if (element == QLatin1String("td"))
318 return WithinTableRow;
320 case WithinTableHeaderElement:
321 if (element == QLatin1String("th"))
322 return WithinTableRow;
324 case WithinTableElementAnchor:
325 if (element == QLatin1String("a"))
326 return WithinTableElement;
334 static inline QStringList parseLists(QIODevice *io)
336 enum { maxEntries = 200 }; // Limit the archive, which can grow quite large.
339 QXmlStreamReader reader(io);
340 ParseState state = OutSideTable;
344 const QString hrefAttribute = QLatin1String("href");
350 while (!reader.atEnd()) {
351 switch(reader.readNext()) {
352 case QXmlStreamReader::StartElement:
353 state = nextOpeningState(state, reader.name());
360 case WithinTableHeaderElement:
361 case WithinTableElement:
363 case WithinTableElementAnchor: // 'href="/svb5K8wS"'
364 if (tableColumn == 0) {
365 link = reader.attributes().value(hrefAttribute).toString();
366 if (link.startsWith(QLatin1Char('/')))
372 } // switch startelement state
374 case QXmlStreamReader::EndElement:
375 state = nextClosingState(state, reader.name());
378 if (tableRow) // Seen the table, bye.
382 if (tableRow && !user.isEmpty() && !link.isEmpty() && !description.isEmpty()) {
383 QString entry = link;
384 entry += QLatin1Char(' ');
386 entry += QLatin1Char(' ');
387 entry += description;
389 if (rc.size() >= maxEntries)
400 case WithinTableHeaderElement:
401 case WithinTableElement:
402 case WithinTableElementAnchor:
406 } // switch endelement state
408 case QXmlStreamReader::Characters:
411 case WithinTableElement:
412 if (tableColumn == 5)
413 user = reader.text().toString();
415 case WithinTableElementAnchor:
416 if (tableColumn == 0)
417 description = reader.text().toString();
421 } // switch characters read state
425 } // switch reader state
430 void PasteBinDotComProtocol::listFinished()
432 const bool error = m_listReply->error();
435 qDebug() << "listFinished: error" << m_listReply->errorString();
437 QStringList list = parseLists(m_listReply);
438 emit listDone(name(), list);
442 m_listReply->deleteLater();
446 Core::IOptionsPage *PasteBinDotComProtocol::settingsPage() const
450 } // namespace CodePaster