1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
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
16 ** GNU Lesser General Public License Usage
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.
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.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "pastebindotcomprotocol.h"
35 #include "pastebindotcomsettings.h"
37 #include <coreplugin/icore.h>
39 #include <utils/qtcassert.h>
41 #include <QtCore/QDebug>
42 #include <QtCore/QTextStream>
43 #include <QtCore/QXmlStreamReader>
44 #include <QtCore/QXmlStreamAttributes>
45 #include <QtCore/QByteArray>
47 #include <QtNetwork/QNetworkReply>
51 static const char pastePhpScriptpC[] = "api_public.php";
52 static const char fetchPhpScriptpC[] = "raw.php";
54 namespace CodePaster {
55 PasteBinDotComProtocol::PasteBinDotComProtocol(const NetworkAccessManagerProxyPtr &nw) :
57 m_settings(new PasteBinDotComSettings),
67 QString PasteBinDotComProtocol::protocolName()
69 return QLatin1String("Pastebin.Com");
72 unsigned PasteBinDotComProtocol::capabilities() const
74 return ListCapability;
77 bool PasteBinDotComProtocol::checkConfiguration(QString *errorMessage)
79 if (m_hostChecked) // Check the host once.
81 const bool ok = httpStatus(hostName(false), errorMessage);
87 QString PasteBinDotComProtocol::hostName(bool withSubDomain) const
92 rc = m_settings->hostPrefix();
94 rc.append(QLatin1Char('.'));
96 rc.append(QLatin1String("pastebin.com"));
100 static inline QByteArray format(Protocol::ContentType ct)
106 return "paste_format=cpp";
108 case Protocol::JavaScript:
109 return "paste_format=javascript";
112 return "paste_format=diff"; // v3.X 'dff' -> 'diff'
115 return "paste_format=xml";
121 void PasteBinDotComProtocol::paste(const QString &text,
123 const QString &username,
124 const QString & /* comment */,
125 const QString & /* description */)
127 QTC_ASSERT(!m_pasteReply, return;)
130 QByteArray pasteData = format(ct);
131 if (!pasteData.isEmpty())
132 pasteData.append('&');
133 pasteData += "paste_name=";
134 pasteData += QUrl::toPercentEncoding(username);
136 const QString subDomain = m_settings->hostPrefix();
137 if (!subDomain.isEmpty()) {
138 pasteData += "&paste_subdomain=";
139 pasteData += QUrl::toPercentEncoding(subDomain);
142 pasteData += "&paste_code=";
143 pasteData += QUrl::toPercentEncoding(fixNewLines(text));
147 QTextStream(&link) << "http://" << hostName(false) << '/' << pastePhpScriptpC;
149 m_pasteReply = httpPost(link, pasteData);
150 connect(m_pasteReply, SIGNAL(finished()), this, SLOT(pasteFinished()));
152 qDebug() << "paste: sending " << m_pasteReply << link << pasteData;
155 void PasteBinDotComProtocol::pasteFinished()
157 if (m_pasteReply->error()) {
158 qWarning("Pastebin.com protocol error: %s", qPrintable(m_pasteReply->errorString()));
160 emit pasteDone(QString::fromAscii(m_pasteReply->readAll()));
163 m_pasteReply->deleteLater();
167 void PasteBinDotComProtocol::fetch(const QString &id)
169 const QString httpProtocolPrefix = QLatin1String("http://");
171 QTC_ASSERT(!m_fetchReply, return;)
173 // Did we get a complete URL or just an id. Insert a call to the php-script
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);
181 // format "http://host/script?i=id".
182 QTextStream(&link) << "http://" << hostName(true) << '/' << fetchPhpScriptpC<< "?i=" << id;
186 qDebug() << "fetch: sending " << link;
188 m_fetchReply = httpGet(link);
189 connect(m_fetchReply, SIGNAL(finished()), this, SLOT(fetchFinished()));
193 void PasteBinDotComProtocol::fetchFinished()
197 const bool error = m_fetchReply->error();
199 content = m_fetchReply->errorString();
201 qDebug() << "fetchFinished: error" << m_fetchId << content;
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>"));
208 content.truncate(preEnd);
209 const int preStart = content.indexOf(QLatin1String("<pre>"));
211 content.remove(0, preStart + 5);
212 // Make applicable as patch.
213 content = Protocol::textFromHtml(content);
214 content += QLatin1Char('\n');
217 QDebug nsp = qDebug().nospace();
218 nsp << "fetchFinished: " << content.size() << " Bytes";
223 m_fetchReply->deleteLater();
225 emit fetchDone(title, content, error);
228 void PasteBinDotComProtocol::list()
230 QTC_ASSERT(!m_listReply, return;)
233 const QString url = QLatin1String("http://") + hostName(true) + QLatin1String("/archive");
234 m_listReply = httpGet(url);
235 connect(m_listReply, SIGNAL(finished()), this, SLOT(listFinished()));
237 qDebug() << "list: sending " << url << m_listReply;
240 static inline void padString(QString *s, int len)
242 const int missing = len - s->length();
244 s->append(QString(missing, QLatin1Char(' ')));
247 /* Quick & dirty: Parse out the 'archive' table as of 16.3.2011:
249 <table class="maintable" cellspacing="0">
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>
259 <td class="icon"><a href="/8ZRqkcaP">Untitled</a></td>
263 <td><a href="/archive/text">None</a></td>
271 OutSideTable, WithinTable, WithinTableRow, WithinTableHeaderElement,
272 WithinTableElement, WithinTableElementAnchor, ParseError
275 static inline ParseState nextOpeningState(ParseState current, const QStringRef &element)
279 if (element == QLatin1String("table"))
283 if (element == QLatin1String("tr"))
284 return WithinTableRow;
287 if (element == QLatin1String("td"))
288 return WithinTableElement;
289 if (element == QLatin1String("th"))
290 return WithinTableHeaderElement;
292 case WithinTableElement:
293 if (element == QLatin1String("a"))
294 return WithinTableElementAnchor;
296 case WithinTableHeaderElement:
297 case WithinTableElementAnchor:
304 static inline ParseState nextClosingState(ParseState current, const QStringRef &element)
310 if (element == QLatin1String("table"))
314 if (element == QLatin1String("tr"))
317 case WithinTableElement:
318 if (element == QLatin1String("td"))
319 return WithinTableRow;
321 case WithinTableHeaderElement:
322 if (element == QLatin1String("th"))
323 return WithinTableRow;
325 case WithinTableElementAnchor:
326 if (element == QLatin1String("a"))
327 return WithinTableElement;
335 static inline QStringList parseLists(QIODevice *io)
337 enum { maxEntries = 200 }; // Limit the archive, which can grow quite large.
340 QXmlStreamReader reader(io);
341 ParseState state = OutSideTable;
345 const QString hrefAttribute = QLatin1String("href");
351 while (!reader.atEnd()) {
352 switch(reader.readNext()) {
353 case QXmlStreamReader::StartElement:
354 state = nextOpeningState(state, reader.name());
361 case WithinTableHeaderElement:
362 case WithinTableElement:
364 case WithinTableElementAnchor: // 'href="/svb5K8wS"'
365 if (tableColumn == 0) {
366 link = reader.attributes().value(hrefAttribute).toString();
367 if (link.startsWith(QLatin1Char('/')))
373 } // switch startelement state
375 case QXmlStreamReader::EndElement:
376 state = nextClosingState(state, reader.name());
379 if (tableRow) // Seen the table, bye.
383 if (tableRow && !user.isEmpty() && !link.isEmpty() && !description.isEmpty()) {
384 QString entry = link;
385 entry += QLatin1Char(' ');
387 entry += QLatin1Char(' ');
388 entry += description;
390 if (rc.size() >= maxEntries)
401 case WithinTableHeaderElement:
402 case WithinTableElement:
403 case WithinTableElementAnchor:
407 } // switch endelement state
409 case QXmlStreamReader::Characters:
412 case WithinTableElement:
413 if (tableColumn == 5)
414 user = reader.text().toString();
416 case WithinTableElementAnchor:
417 if (tableColumn == 0)
418 description = reader.text().toString();
422 } // switch characters read state
426 } // switch reader state
431 void PasteBinDotComProtocol::listFinished()
433 const bool error = m_listReply->error();
436 qDebug() << "listFinished: error" << m_listReply->errorString();
438 QStringList list = parseLists(m_listReply);
439 emit listDone(name(), list);
443 m_listReply->deleteLater();
447 Core::IOptionsPage *PasteBinDotComProtocol::settingsPage() const
451 } // namespace CodePaster