multipageloader.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. // -*- mode: c++; tab-width: 4; indent-tabs-mode: t; eval: (progn (c-set-style "stroustrup") (c-set-offset 'innamespace 0)); -*-
  2. // vi:set ts=4 sts=4 sw=4 noet :
  3. //
  4. // Copyright 2010, 2011 wkhtmltopdf authors
  5. //
  6. // This file is part of wkhtmltopdf.
  7. //
  8. // wkhtmltopdf is free software: you can redistribute it and/or modify
  9. // it under the terms of the GNU Lesser General Public License as published by
  10. // the Free Software Foundation, either version 3 of the License, or
  11. // (at your option) any later version.
  12. //
  13. // wkhtmltopdf is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Lesser General Public License
  19. // along with wkhtmltopdf. If not, see <http://www.gnu.org/licenses/>.
  20. #include "multipageloader_p.hh"
  21. #include <QFile>
  22. #include <QFileInfo>
  23. #include <QNetworkCookie>
  24. #include <QNetworkDiskCache>
  25. #include <QTimer>
  26. #include <QUuid>
  27. #if QT_VERSION >= 0x050000
  28. #include <QUrlQuery>
  29. #endif
  30. namespace wkhtmltopdf {
  31. /*!
  32. \file multipageloader.hh
  33. \brief Defines the MultiPageLoader class
  34. */
  35. /*!
  36. \file multipageloader_p.hh
  37. \brief Defines the MultiPageLoaderPrivate class
  38. */
  39. LoaderObject::LoaderObject(QWebPage & p): page(p), skip(false) {};
  40. MyNetworkAccessManager::MyNetworkAccessManager(const settings::LoadPage & s):
  41. disposed(false),
  42. settings(s) {
  43. if ( !s.cacheDir.isEmpty() ){
  44. QNetworkDiskCache *cache = new QNetworkDiskCache(this);
  45. cache->setCacheDirectory(s.cacheDir);
  46. QNetworkAccessManager::setCache(cache);
  47. }
  48. }
  49. void MyNetworkAccessManager::dispose() {
  50. disposed = true;
  51. }
  52. void MyNetworkAccessManager::allow(QString path) {
  53. QString x = QFileInfo(path).canonicalFilePath();
  54. if (x.isEmpty()) return;
  55. allowed.insert(x);
  56. }
  57. QNetworkReply * MyNetworkAccessManager::createRequest(Operation op, const QNetworkRequest & req, QIODevice * outgoingData) {
  58. if (disposed)
  59. {
  60. emit warning("Received createRequest signal on a disposed ResourceObject's NetworkAccessManager. "
  61. "This might be an indication of an iframe taking too long to load.");
  62. // Needed to avoid race conditions by spurious network requests
  63. // by scripts or iframes taking too long to load.
  64. QNetworkRequest r2 = req;
  65. r2.setUrl(QUrl("about:blank"));
  66. return QNetworkAccessManager::createRequest(op, r2, outgoingData);
  67. }
  68. bool isLocalFileAccess = req.url().scheme().length() <= 1 || req.url().scheme() == "file";
  69. if (isLocalFileAccess && settings.blockLocalFileAccess) {
  70. bool ok=false;
  71. QString path = QFileInfo(req.url().toLocalFile()).canonicalFilePath();
  72. QString old = "";
  73. while (path != old) {
  74. if (allowed.contains(path)) {
  75. ok=true;
  76. break;
  77. }
  78. old = path;
  79. path = QFileInfo(path).path();
  80. }
  81. if (!ok) {
  82. QNetworkRequest r2 = req;
  83. emit warning(QString("Blocked access to file %1").arg(QFileInfo(req.url().toLocalFile()).canonicalFilePath()));
  84. r2.setUrl(QUrl("about:blank"));
  85. return QNetworkAccessManager::createRequest(op, r2, outgoingData);
  86. }
  87. }
  88. QNetworkRequest r3 = req;
  89. if (settings.repeatCustomHeaders) {
  90. typedef QPair<QString, QString> HT;
  91. foreach (const HT & j, settings.customHeaders)
  92. r3.setRawHeader(j.first.toLatin1(), j.second.toLatin1());
  93. }
  94. return QNetworkAccessManager::createRequest(op, r3, outgoingData);
  95. }
  96. MyQWebPage::MyQWebPage(ResourceObject & res): resource(res) {}
  97. void MyQWebPage::javaScriptAlert(QWebFrame *, const QString & msg) {
  98. resource.warning(QString("Javascript alert: %1").arg(msg));
  99. }
  100. bool MyQWebPage::javaScriptConfirm(QWebFrame *, const QString & msg) {
  101. resource.warning(QString("Javascript confirm: %1 (answered yes)").arg(msg));
  102. return true;
  103. }
  104. bool MyQWebPage::javaScriptPrompt(QWebFrame *, const QString & msg, const QString & defaultValue, QString * result) {
  105. resource.warning(QString("Javascript prompt: %1 (answered %2)").arg(msg,defaultValue));
  106. result = (QString*)&defaultValue;
  107. Q_UNUSED(result);
  108. return true;
  109. }
  110. void MyQWebPage::javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID) {
  111. if (resource.settings.debugJavascript)
  112. resource.warning(QString("%1:%2 %3").arg(sourceID).arg(lineNumber).arg(message));
  113. }
  114. bool MyQWebPage::shouldInterruptJavaScript() {
  115. if (resource.settings.stopSlowScripts) {
  116. resource.warning("A slow script was stopped");
  117. return true;
  118. }
  119. return false;
  120. }
  121. ResourceObject::ResourceObject(MultiPageLoaderPrivate & mpl, const QUrl & u, const settings::LoadPage & s):
  122. networkAccessManager(s),
  123. url(u),
  124. loginTry(0),
  125. progress(0),
  126. finished(false),
  127. signalPrint(false),
  128. multiPageLoader(mpl),
  129. webPage(*this),
  130. lo(webPage),
  131. httpErrorCode(0),
  132. settings(s) {
  133. connect(&networkAccessManager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator *)),this,
  134. SLOT(handleAuthenticationRequired(QNetworkReply *, QAuthenticator *)));
  135. foreach (const QString & path, s.allowed)
  136. networkAccessManager.allow(path);
  137. if (url.scheme() == "file")
  138. networkAccessManager.allow(url.toLocalFile());
  139. connect(&webPage, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
  140. connect(&webPage, SIGNAL(loadProgress(int)), this, SLOT(loadProgress(int)));
  141. connect(&webPage, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
  142. connect(&webPage, SIGNAL(printRequested(QWebFrame*)), this, SLOT(printRequested(QWebFrame*)));
  143. //If some ssl error occurs we want sslErrors to be called, so the we can ignore it
  144. connect(&networkAccessManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)),this,
  145. SLOT(sslErrors(QNetworkReply*, const QList<QSslError>&)));
  146. connect(&networkAccessManager, SIGNAL(finished (QNetworkReply *)),
  147. this, SLOT(amfinished (QNetworkReply *) ) );
  148. connect(&networkAccessManager, SIGNAL(warning(const QString &)),
  149. this, SLOT(warning(const QString &)));
  150. networkAccessManager.setCookieJar(multiPageLoader.cookieJar);
  151. //If we must use a proxy, create a host of objects
  152. if (!settings.proxy.host.isEmpty()) {
  153. QNetworkProxy proxy;
  154. proxy.setHostName(settings.proxy.host);
  155. proxy.setPort(settings.proxy.port);
  156. proxy.setType(settings.proxy.type);
  157. // to retrieve a web page, it's not needed to use a fully transparent
  158. // http proxy. Moreover, the CONNECT() method is frequently disabled
  159. // by proxies administrators.
  160. if (settings.proxy.type == QNetworkProxy::HttpProxy)
  161. proxy.setCapabilities(QNetworkProxy::CachingCapability |
  162. QNetworkProxy::TunnelingCapability);
  163. if (!settings.proxy.user.isEmpty())
  164. proxy.setUser(settings.proxy.user);
  165. if (!settings.proxy.password.isEmpty())
  166. proxy.setPassword(settings.proxy.password);
  167. networkAccessManager.setProxy(proxy);
  168. }
  169. webPage.setNetworkAccessManager(&networkAccessManager);
  170. webPage.mainFrame()->setZoomFactor(settings.zoomFactor);
  171. }
  172. /*!
  173. * Once loading starting, this is called
  174. */
  175. void ResourceObject::loadStarted() {
  176. if (finished == true) {
  177. ++multiPageLoader.loading;
  178. finished = false;
  179. }
  180. if (multiPageLoader.loadStartedEmitted) return;
  181. multiPageLoader.loadStartedEmitted=true;
  182. emit multiPageLoader.outer.loadStarted();
  183. }
  184. /*!
  185. * Called when the page is loading, display some progress to the using
  186. * \param progress the loading progress in percent
  187. */
  188. void ResourceObject::loadProgress(int p) {
  189. // If we are finished, ignore this signal.
  190. if (finished || multiPageLoader.resources.size() <= 0) {
  191. warning("A finished ResourceObject received a loading progress signal. "
  192. "This might be an indication of an iframe taking too long to load.");
  193. return;
  194. }
  195. multiPageLoader.progressSum -= progress;
  196. progress = p;
  197. multiPageLoader.progressSum += progress;
  198. emit multiPageLoader.outer.loadProgress(multiPageLoader.progressSum / multiPageLoader.resources.size());
  199. }
  200. void ResourceObject::loadFinished(bool ok) {
  201. // If we are finished, this might be a potential bug.
  202. if (finished || multiPageLoader.resources.size() <= 0) {
  203. warning("A finished ResourceObject received a loading finished signal. "
  204. "This might be an indication of an iframe taking too long to load.");
  205. return;
  206. }
  207. multiPageLoader.hasError = multiPageLoader.hasError || (!ok && settings.loadErrorHandling == settings::LoadPage::abort);
  208. if (!ok) {
  209. if (settings.loadErrorHandling == settings::LoadPage::abort)
  210. error(QString("Failed loading page ") + url.toString() + " (sometimes it will work just to ignore this error with --load-error-handling ignore)");
  211. else if (settings.loadErrorHandling == settings::LoadPage::skip) {
  212. warning(QString("Failed loading page ") + url.toString() + " (skipped)");
  213. lo.skip = true;
  214. } else
  215. warning(QString("Failed loading page ") + url.toString() + " (ignored)");
  216. }
  217. if (!multiPageLoader.isMainLoader) {
  218. loadDone();
  219. return;
  220. }
  221. // Evaluate extra user supplied javascript
  222. foreach (const QString & str, settings.runScript)
  223. webPage.mainFrame()->evaluateJavaScript(str);
  224. // XXX: If loading failed there's no need to wait
  225. // for javascript on this resource.
  226. if (!ok || signalPrint || settings.jsdelay == 0) loadDone();
  227. else if (!settings.windowStatus.isEmpty()) waitWindowStatus();
  228. else QTimer::singleShot(settings.jsdelay, this, SLOT(loadDone()));
  229. }
  230. void ResourceObject::waitWindowStatus() {
  231. QString windowStatus = webPage.mainFrame()->evaluateJavaScript("window.status").toString();
  232. //warning(QString("window.status:" + windowStatus + " settings.windowStatus:" + settings.windowStatus));
  233. if (windowStatus != settings.windowStatus) {
  234. QTimer::singleShot(50, this, SLOT(waitWindowStatus()));
  235. } else {
  236. QTimer::singleShot(settings.jsdelay, this, SLOT(loadDone()));
  237. }
  238. }
  239. void ResourceObject::printRequested(QWebFrame *) {
  240. signalPrint=true;
  241. loadDone();
  242. }
  243. void ResourceObject::loadDone() {
  244. if (finished) return;
  245. finished=true;
  246. // Ensure no more loading goes..
  247. webPage.triggerAction(QWebPage::Stop);
  248. webPage.triggerAction(QWebPage::StopScheduledPageRefresh);
  249. networkAccessManager.dispose();
  250. //disconnect(this, 0, 0, 0);
  251. --multiPageLoader.loading;
  252. if (multiPageLoader.loading == 0)
  253. multiPageLoader.loadDone();
  254. }
  255. /*!
  256. * Called when the page requires authentication, fills in the username
  257. * and password supplied on the command line
  258. */
  259. void ResourceObject::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) {
  260. Q_UNUSED(reply);
  261. // XXX: Avoid calling 'reply->abort()' from within this signal.
  262. // As stated by doc, request would be finished when no
  263. // user/pass properties are assigned to authenticator object.
  264. // See: http://qt-project.org/doc/qt-5.0/qtnetwork/qnetworkaccessmanager.html#authenticationRequired
  265. if (settings.username.isEmpty()) {
  266. //If no username is given, complain the such is required
  267. error("Authentication Required");
  268. } else if (loginTry >= 2) {
  269. //If the login has failed a sufficient number of times,
  270. //the username or password must be wrong
  271. error("Invalid username or password");
  272. } else {
  273. authenticator->setUser(settings.username);
  274. authenticator->setPassword(settings.password);
  275. ++loginTry;
  276. }
  277. }
  278. void ResourceObject::warning(const QString & str) {
  279. emit multiPageLoader.outer.warning(str);
  280. }
  281. void ResourceObject::error(const QString & str) {
  282. emit multiPageLoader.outer.error(str);
  283. }
  284. /*!
  285. * Track and handle network errors
  286. * \param reply The networkreply that has finished
  287. */
  288. void ResourceObject::amfinished(QNetworkReply * reply) {
  289. int networkStatus = reply->error();
  290. int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  291. if ((networkStatus != 0 && networkStatus != 5) || (httpStatus > 399 && httpErrorCode == 0))
  292. {
  293. QFileInfo fi(reply->url().toString());
  294. bool mediaFile = settings::LoadPage::mediaFilesExtensions.contains(fi.completeSuffix().toLower());
  295. if ( ! mediaFile) {
  296. // XXX: Notify network errors as higher priority than HTTP errors.
  297. // QT's QNetworkReply::NetworkError enum uses values overlapping
  298. // HTTP status codes, so adding 1000 to QT's codes will avoid
  299. // confusion. Also a network error at this point will probably mean
  300. // no HTTP access at all, so we want network errors to be reported
  301. // with a higher priority than HTTP ones.
  302. // See: http://doc-snapshot.qt-project.org/4.8/qnetworkreply.html#NetworkError-enum
  303. httpErrorCode = networkStatus > 0 ? (networkStatus + 1000) : httpStatus;
  304. return;
  305. }
  306. if (settings.mediaLoadErrorHandling == settings::LoadPage::abort)
  307. {
  308. httpErrorCode = networkStatus > 0 ? (networkStatus + 1000) : httpStatus;
  309. error(QString("Failed to load ") + reply->url().toString() + ", with code: " + QString::number(httpErrorCode) +
  310. " (sometimes it will work just to ignore this error with --load-media-error-handling ignore)");
  311. }
  312. else {
  313. warning(QString("Failed to load %1 (%2)")
  314. .arg(reply->url().toString())
  315. .arg(settings::loadErrorHandlingToStr(settings.mediaLoadErrorHandling))
  316. );
  317. }
  318. }
  319. }
  320. /*!
  321. * Handle any ssl error by ignoring
  322. */
  323. void ResourceObject::sslErrors(QNetworkReply *reply, const QList<QSslError> &) {
  324. //We ignore any ssl error, as it is next to impossible to send or receive
  325. //any private information with wkhtmltopdf anyhow, seeing as you cannot authenticate
  326. reply->ignoreSslErrors();
  327. warning("SSL error ignored");
  328. }
  329. void ResourceObject::load() {
  330. finished=false;
  331. ++multiPageLoader.loading;
  332. bool hasFiles=false;
  333. foreach (const settings::PostItem & pi, settings.post) hasFiles |= pi.file;
  334. QByteArray postData;
  335. QString boundary;
  336. if (hasFiles) {
  337. boundary = QUuid::createUuid().toString().remove('-').remove('{').remove('}');
  338. foreach (const settings::PostItem & pi, settings.post) {
  339. //TODO escape values here
  340. postData.append("--");
  341. postData.append(boundary);
  342. postData.append("\ncontent-disposition: form-data; name=\"");
  343. postData.append(pi.name);
  344. postData.append('\"');
  345. if (pi.file) {
  346. QFile f(pi.value);
  347. if (!f.open(QIODevice::ReadOnly) ) {
  348. error(QString("Unable to open file ")+pi.value);
  349. multiPageLoader.fail();
  350. }
  351. postData.append("; filename=\"");
  352. postData.append( QFileInfo(pi.value).fileName());
  353. postData.append("\"\n\n");
  354. postData.append( f.readAll() );
  355. //TODO ADD MIME TYPE
  356. } else {
  357. postData.append("\n\n");
  358. postData.append(pi.value);
  359. }
  360. postData.append('\n');
  361. }
  362. if (!postData.isEmpty()) {
  363. postData.append("--");
  364. postData.append(boundary);
  365. postData.append("--\n");
  366. }
  367. } else {
  368. #if QT_VERSION >= 0x050000
  369. QUrlQuery q;
  370. foreach (const settings::PostItem & pi, settings.post)
  371. q.addQueryItem(pi.name, pi.value);
  372. postData = q.query(QUrl::FullyEncoded).toLocal8Bit();
  373. #else
  374. QUrl u;
  375. foreach (const settings::PostItem & pi, settings.post)
  376. u.addQueryItem(pi.name, pi.value);
  377. postData = u.encodedQuery();
  378. #endif
  379. }
  380. typedef QPair<QString, QString> SSP;
  381. foreach (const SSP & pair, settings.cookies)
  382. multiPageLoader.cookieJar->useCookie(url, pair.first, pair.second);
  383. QNetworkRequest r = QNetworkRequest(url);
  384. typedef QPair<QString, QString> HT;
  385. foreach (const HT & j, settings.customHeaders)
  386. r.setRawHeader(j.first.toLatin1(), j.second.toLatin1());
  387. if (postData.isEmpty())
  388. webPage.mainFrame()->load(r);
  389. else {
  390. if (hasFiles)
  391. r.setHeader(QNetworkRequest::ContentTypeHeader, QString("multipart/form-data, boundary=")+boundary);
  392. webPage.mainFrame()->load(r, QNetworkAccessManager::PostOperation, postData);
  393. }
  394. }
  395. void MyCookieJar::useCookie(const QUrl &, const QString & name, const QString & value) {
  396. extraCookies.push_back(QNetworkCookie(name.toUtf8(), value.toUtf8()));
  397. }
  398. QList<QNetworkCookie> MyCookieJar::cookiesForUrl(const QUrl & url) const {
  399. QList<QNetworkCookie> list = QNetworkCookieJar::cookiesForUrl(url);
  400. list.append(extraCookies);
  401. return list;
  402. }
  403. void MyCookieJar::loadFromFile(const QString & path) {
  404. QFile cookieJar(path);
  405. if (cookieJar.open(QIODevice::ReadOnly | QIODevice::Text) )
  406. setAllCookies(QNetworkCookie::parseCookies(cookieJar.readAll()));
  407. }
  408. void MyCookieJar::saveToFile(const QString & path) {
  409. QFile cookieJar(path);
  410. if (cookieJar.open(QIODevice::WriteOnly | QIODevice::Text) )
  411. foreach (const QNetworkCookie & cookie, allCookies()) {
  412. cookieJar.write(cookie.toRawForm());
  413. cookieJar.write(";\n");
  414. }
  415. }
  416. void MultiPageLoaderPrivate::loadDone() {
  417. if (!settings.cookieJar.isEmpty())
  418. cookieJar->saveToFile(settings.cookieJar);
  419. if (!finishedEmitted) {
  420. finishedEmitted = true;
  421. emit outer.loadFinished(!hasError);
  422. }
  423. }
  424. /*!
  425. * Copy a file from some place to another
  426. * \param src The source to copy from
  427. * \param dst The destination to copy to
  428. */
  429. bool MultiPageLoader::copyFile(QFile & src, QFile & dst) {
  430. // TODO enable again when
  431. // http://bugreports.qt.nokia.com/browse/QTBUG-6894
  432. // is fixed
  433. // QByteArray buf(1024*1024*5,0);
  434. // while ( qint64 r=src.read(buf.data(),buf.size())) {
  435. // if (r == -1) return false;
  436. // if (dst.write(buf.data(),r) != r) return false;
  437. // }
  438. if (dst.write( src.readAll() ) == -1) return false;
  439. src.close();
  440. dst.close();
  441. return true;
  442. }
  443. MultiPageLoaderPrivate::MultiPageLoaderPrivate(const settings::LoadGlobal & s, MultiPageLoader & o):
  444. outer(o), settings(s) {
  445. cookieJar = new MyCookieJar();
  446. if (!settings.cookieJar.isEmpty())
  447. cookieJar->loadFromFile(settings.cookieJar);
  448. }
  449. MultiPageLoaderPrivate::~MultiPageLoaderPrivate() {
  450. clearResources();
  451. }
  452. LoaderObject * MultiPageLoaderPrivate::addResource(const QUrl & url, const settings::LoadPage & page) {
  453. ResourceObject * ro = new ResourceObject(*this, url, page);
  454. resources.push_back(ro);
  455. return &ro->lo;
  456. }
  457. void MultiPageLoaderPrivate::load() {
  458. progressSum=0;
  459. loadStartedEmitted=false;
  460. finishedEmitted=false;
  461. hasError=false;
  462. loading=0;
  463. for (int i=0; i < resources.size(); ++i)
  464. resources[i]->load();
  465. if (resources.size() == 0) loadDone();
  466. }
  467. void MultiPageLoaderPrivate::clearResources() {
  468. while (resources.size() > 0)
  469. {
  470. // XXX: Using deleteLater() to dispose
  471. // resources, to avoid race conditions with
  472. // pending signals reaching a deleted resource.
  473. // Also, and we must avoid calling clear()
  474. // on resources list, is it tries to delete
  475. // each objet on removal.
  476. ResourceObject *tmp = resources.takeFirst();
  477. tmp->deleteLater();
  478. }
  479. tempIn.removeAll();
  480. }
  481. void MultiPageLoaderPrivate::cancel() {
  482. //foreach (QWebPage * page, pages)
  483. // page->triggerAction(QWebPage::Stop);
  484. }
  485. void MultiPageLoaderPrivate::fail() {
  486. hasError = true;
  487. cancel();
  488. clearResources();
  489. }
  490. /*!
  491. \brief Construct a multipage loader object, load settings read from the supplied settings
  492. \param s The settings to be used while loading pages
  493. */
  494. MultiPageLoader::MultiPageLoader(settings::LoadGlobal & s, bool mainLoader):
  495. d(new MultiPageLoaderPrivate(s, *this)) {
  496. d->isMainLoader = mainLoader;
  497. }
  498. MultiPageLoader::~MultiPageLoader() {
  499. MultiPageLoaderPrivate *tmp = d;
  500. d = 0;
  501. tmp->deleteLater();
  502. }
  503. /*!
  504. \brief Add a resource, to be loaded described by a string
  505. @param string Url describing the resource to load
  506. */
  507. LoaderObject * MultiPageLoader::addResource(const QString & string, const settings::LoadPage & s, const QString * data) {
  508. QString url=string;
  509. if (data && !data->isEmpty()) {
  510. url = d->tempIn.create(".html");
  511. QFile tmp(url);
  512. if (!tmp.open(QIODevice::WriteOnly) || tmp.write(data->toUtf8())==0) {
  513. emit error("Unable to create temporary file");
  514. return NULL;
  515. }
  516. } else if (url == "-") {
  517. QFile in;
  518. in.open(stdin,QIODevice::ReadOnly);
  519. url = d->tempIn.create(".html");
  520. QFile tmp(url);
  521. if (!tmp.open(QIODevice::WriteOnly) || !copyFile(in, tmp)) {
  522. emit error("Unable to create temporary file");
  523. return NULL;
  524. }
  525. }
  526. return addResource(guessUrlFromString(url), s);
  527. }
  528. /*!
  529. \brief Add a page to be loaded
  530. @param url Url of the page to load
  531. */
  532. LoaderObject * MultiPageLoader::addResource(const QUrl & url, const settings::LoadPage & s) {
  533. return d->addResource(url, s);
  534. }
  535. /*!
  536. \brief Guess a url, by looking at a string
  537. (shamelessly copied from Arora Project)
  538. \param string The string the is suppose to be some kind of url
  539. */
  540. QUrl MultiPageLoader::guessUrlFromString(const QString &string) {
  541. QString urlStr = string.trimmed();
  542. // check if the string is just a host with a port
  543. QRegExp hostWithPort(QLatin1String("^[a-zA-Z\\.]+\\:[0-9]*$"));
  544. if (hostWithPort.exactMatch(urlStr))
  545. urlStr = QLatin1String("http://") + urlStr;
  546. // Check if it looks like a qualified URL. Try parsing it and see.
  547. QRegExp test(QLatin1String("^[a-zA-Z]+\\://.*"));
  548. bool hasSchema = test.exactMatch(urlStr);
  549. if (hasSchema) {
  550. bool isAscii = true;
  551. foreach (const QChar &c, urlStr) {
  552. if (c >= 0x80) {
  553. isAscii = false;
  554. break;
  555. }
  556. }
  557. QUrl url;
  558. if (isAscii) {
  559. url = QUrl::fromEncoded(urlStr.toLatin1(), QUrl::TolerantMode);
  560. } else {
  561. url = QUrl(urlStr, QUrl::TolerantMode);
  562. }
  563. if (url.isValid())
  564. return url;
  565. }
  566. // Might be a file.
  567. if (QFile::exists(urlStr)) {
  568. QFileInfo info(urlStr);
  569. return QUrl::fromLocalFile(info.absoluteFilePath());
  570. }
  571. // Might be a shorturl - try to detect the schema.
  572. if (!hasSchema) {
  573. int dotIndex = urlStr.indexOf(QLatin1Char('.'));
  574. if (dotIndex != -1) {
  575. QString prefix = urlStr.left(dotIndex).toLower();
  576. QString schema = (prefix == QLatin1String("ftp")) ? prefix : QLatin1String("http");
  577. QUrl url(schema + QLatin1String("://") + urlStr, QUrl::TolerantMode);
  578. if (url.isValid())
  579. return url;
  580. }
  581. }
  582. // Fall back to QUrl's own tolerant parser.
  583. QUrl url = QUrl(string, QUrl::TolerantMode);
  584. // finally for cases where the user just types in a hostname add http
  585. if (url.scheme().isEmpty())
  586. url = QUrl(QLatin1String("http://") + string, QUrl::TolerantMode);
  587. return url;
  588. }
  589. /*!
  590. \brief Return the most severe http error code returned during loading
  591. */
  592. int MultiPageLoader::httpErrorCode() {
  593. int res=0;
  594. foreach (const ResourceObject * ro, d->resources)
  595. if (ro->httpErrorCode > res) res = ro->httpErrorCode;
  596. return res;
  597. }
  598. /*!
  599. \brief Begin loading all the resources added
  600. */
  601. void MultiPageLoader::load() {
  602. d->load();
  603. }
  604. /*!
  605. \brief Clear all the resources
  606. */
  607. void MultiPageLoader::clearResources() {
  608. d->clearResources();
  609. }
  610. /*!
  611. \brief Cancel the loading of the pages
  612. */
  613. void MultiPageLoader::cancel() {
  614. d->cancel();
  615. }
  616. /*!
  617. \fn MultiPageLoader::loadFinished(bool ok)
  618. \brief Signal emitted when all pages have been loaded
  619. \param ok True if all the pages have been loaded sucessfully
  620. */
  621. /*!
  622. \fn MultiPageLoader::loadProgress(int progress)
  623. \brief Signal emitted once load has progressed
  624. \param progress Progress in percent
  625. */
  626. /*!
  627. \fn MultiPageLoader::loadStarted()
  628. \brief Signal emitted when loading has started
  629. */
  630. /*!
  631. \fn void MultiPageLoader::warning(QString text)
  632. \brief Signal emitted when a none fatal warning has occured
  633. \param text A string describing the warning
  634. */
  635. /*!
  636. \fn void MultiPageLoader::error(QString text)
  637. \brief Signal emitted when a fatal error has occured
  638. \param text A string describing the error
  639. */
  640. }