KHtml

kmultipart.cpp
1 /* This file is part of the KDE project
2  Copyright (C) 2002 David Faure <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kmultipart.h"
21 
22 #include "httpfiltergzip_p.h"
23 #include <klocalizedstring.h>
24 #include <kjobuidelegate.h>
25 #include <kio/job.h>
26 #include <QFile>
27 #include <qtemporaryfile.h>
28 #include <kmessagebox.h>
29 #include <kmimetypetrader.h>
30 #include <kpluginfactory.h>
31 #include <khtml_part.h>
32 #include <kxmlguifactory.h>
33 #include <QTimer>
34 #include <QVBoxLayout>
35 #include <kaboutdata.h>
36 
37 static KAboutData kmultipartAboutData()
38 {
39  KAboutData aboutData("kmultipart", i18n("KMultiPart"),
40  "0.1",
41  i18n("Embeddable component for multipart/mixed"),
42  KAboutLicense::GPL,
43  i18n("Copyright 2001-2011, David Faure <[email protected]>"));
44  return aboutData;
45 }
46 
47 K_PLUGIN_FACTORY(KMultiPartFactory, registerPlugin<KMultiPart>();)
48 
49 //#define DEBUG_PARSING
50 
51 class KLineParser
52 {
53 public:
54  KLineParser()
55  {
56  m_lineComplete = false;
57  }
58  void addChar(char c, bool storeNewline)
59  {
60  if (!storeNewline && c == '\r') {
61  return;
62  }
63  Q_ASSERT(!m_lineComplete);
64  if (storeNewline || c != '\n') {
65  int sz = m_currentLine.size();
66  m_currentLine.resize(sz + 1);
67  m_currentLine[sz] = c;
68  }
69  if (c == '\n') {
70  m_lineComplete = true;
71  }
72  }
73  bool isLineComplete() const
74  {
75  return m_lineComplete;
76  }
77  QByteArray currentLine() const
78  {
79  return m_currentLine;
80  }
81  void clearLine()
82  {
83  Q_ASSERT(m_lineComplete);
84  reset();
85  }
86  void reset()
87  {
88  m_currentLine.resize(0);
89  m_lineComplete = false;
90  }
91 private:
92  QByteArray m_currentLine;
93  bool m_lineComplete; // true when ending with '\n'
94 };
95 
96 /* testcase:
97  Content-type: multipart/mixed;boundary=ThisRandomString
98 
99 --ThisRandomString
100 Content-type: text/plain
101 
102 Data for the first object.
103 
104 --ThisRandomString
105 Content-type: text/plain
106 
107 Data for the second and last object.
108 
109 --ThisRandomString--
110 */
111 
112 KMultiPart::KMultiPart(QWidget *parentWidget,
113  QObject *parent, const QVariantList &)
114  : KParts::ReadOnlyPart(parent)
115 {
116  m_filter = nullptr;
117 
118  setComponentData(kmultipartAboutData());
119 
120  QWidget *box = new QWidget(parentWidget);
121  box->setLayout(new QVBoxLayout(box));
122  setWidget(box);
123 
124  m_extension = new KParts::BrowserExtension(this);
125 
126  m_part = nullptr;
127  m_isHTMLPart = false;
128  m_job = nullptr;
129  m_lineParser = new KLineParser;
130  m_tempFile = nullptr;
131 
132  m_timer = new QTimer(this);
133  connect(m_timer, SIGNAL(timeout()), this, SLOT(slotProgressInfo()));
134 }
135 
136 KMultiPart::~KMultiPart()
137 {
138  // important: delete the nested part before the part or qobject destructor runs.
139  // we now delete the nested part which deletes the part's widget which makes
140  // _OUR_ m_widget 0 which in turn avoids our part destructor to delete the
141  // widget ;-)
142  // ### additional note: it _can_ be that the part has been deleted before:
143  // when we're in a html frameset and the view dies first, then it will also
144  // kill the htmlpart
145  if (m_part) {
146  delete static_cast<KParts::ReadOnlyPart *>(m_part);
147  }
148  delete m_job;
149  delete m_lineParser;
150  if (m_tempFile) {
151  m_tempFile->setAutoRemove(true);
152  delete m_tempFile;
153  }
154  delete m_filter;
155  m_filter = nullptr;
156 }
157 
158 void KMultiPart::startHeader()
159 {
160  m_bParsingHeader = true; // we expect a header to come first
161  m_bGotAnyHeader = false;
162  m_gzip = false;
163  // just to be sure for now
164  delete m_filter;
165  m_filter = nullptr;
166 }
167 
168 bool KMultiPart::openUrl(const QUrl &url)
169 {
170  setUrl(url);
171  m_lineParser->reset();
172  startHeader();
173 
174  //m_mimeType = arguments().mimeType();
175 
176  // Hmm, args.reload is set to true when reloading, but this doesn't seem to be enough...
177  // I get "HOLD: Reusing held slave for <url>", and the old data
178 
179  m_job = KIO::get(url,
180  arguments().reload() ? KIO::Reload : KIO::NoReload,
181  KIO::HideProgressInfo);
182 
183  emit started(nullptr /*m_job*/); // don't pass the job, it would interfere with our own infoMessage
184 
185  connect(m_job, SIGNAL(result(KJob*)),
186  this, SLOT(slotJobFinished(KJob*)));
187  connect(m_job, SIGNAL(data(KIO::Job*,QByteArray)),
188  this, SLOT(slotData(KIO::Job*,QByteArray)));
189 
190  m_numberOfFrames = 0;
191  m_numberOfFramesSkipped = 0;
192  m_totalNumberOfFrames = 0;
193  m_qtime.start();
194  m_timer->start(1000); //1s
195 
196  return true;
197 }
198 
199 // Yes, libkdenetwork's has such a parser already (MultiPart),
200 // but it works on the complete string, expecting the whole data to be available....
201 // The version here is asynchronous.
202 void KMultiPart::slotData(KIO::Job *job, const QByteArray &data)
203 {
204  if (m_boundary.isNull()) {
205  QString tmp = job->queryMetaData("media-boundary");
206  // qCDebug(KHTML_LOG) << "Got Boundary from kio-http '" << tmp << "'";
207  if (!tmp.isEmpty()) {
208  // as per r437578, sometimes we se something like this:
209  // Content-Type: multipart/x-mixed-replace; boundary=--myboundary
210  // ..
211  // --myboundary
212  // e.g. the hashes specified in the header are extra. However,
213  // we also see the following on the w3c bugzilla:
214  // boundary="------- =_aaaaaaaaaa0"
215  // ..
216  //--------- =_aaaaaaaaaa0
217  // e.g. the hashes are accurate. For now, we consider the quoted
218  // case to be quirk-free, and only apply the -- stripping quirk
219  // when we're unquoted.
220  if (tmp.startsWith(QLatin1String("--")) &&
221  job->queryMetaData("media-boundary-kio-quoted") != "true") {
222  m_boundary = tmp.toLatin1();
223  } else {
224  m_boundary = QByteArray("--") + tmp.toLatin1();
225  }
226  m_boundaryLength = m_boundary.length();
227  }
228  }
229  // Append to m_currentLine until eol
230  for (int i = 0; i < data.size(); ++i) {
231  // Store char. Skip if '\n' and currently parsing a header.
232  m_lineParser->addChar(data[i], !m_bParsingHeader);
233  if (m_lineParser->isLineComplete()) {
234  QByteArray line = m_lineParser->currentLine();
235 #ifdef DEBUG_PARSING
236  // qCDebug(KHTML_LOG) << "line.size()=" << line.size();
237 #endif
238 #ifdef DEBUG_PARSING
239  // qCDebug(KHTML_LOG) << "[" << m_bParsingHeader << "] line='" << line << "'";
240 #endif
241  if (m_bParsingHeader) {
242  if (!line.isEmpty()) {
243  m_bGotAnyHeader = true;
244  }
245  if (m_boundary.isNull()) {
246  if (!line.isEmpty()) {
247 #ifdef DEBUG_PARSING
248  // qCDebug(KHTML_LOG) << "Boundary is " << line;
249 #endif
250  m_boundary = line;
251  m_boundaryLength = m_boundary.length();
252  }
253  } else if (!qstrnicmp(line.data(), "Content-Encoding:", 17)) {
254  QString encoding = QString::fromLatin1(line.data() + 17).trimmed().toLower();
255  if (encoding == "gzip" || encoding == "x-gzip") {
256  m_gzip = true;
257  } else {
258  // qCDebug(KHTML_LOG) << "FIXME: unhandled encoding type in KMultiPart: " << encoding;
259  }
260  }
261  // parse Content-Type
262  else if (!qstrnicmp(line.data(), "Content-Type:", 13)) {
263  Q_ASSERT(m_nextMimeType.isNull());
264  m_nextMimeType = QString::fromLatin1(line.data() + 14).trimmed();
265  int semicolon = m_nextMimeType.indexOf(';');
266  if (semicolon != -1) {
267  m_nextMimeType = m_nextMimeType.left(semicolon);
268  }
269  // qCDebug(KHTML_LOG) << "m_nextMimeType=" << m_nextMimeType;
270  }
271  // Empty line, end of headers (if we had any header line before)
272  else if (line.isEmpty() && m_bGotAnyHeader) {
273  m_bParsingHeader = false;
274 #ifdef DEBUG_PARSING
275  // qCDebug(KHTML_LOG) << "end of headers";
276 #endif
277  startOfData();
278  }
279  // First header (when we know it from kio_http)
280  else if (line == m_boundary)
281  ; // nothing to do
282  else if (!line.isEmpty()) { // this happens with e.g. Set-Cookie:
283  // qCDebug(KHTML_LOG) << "Ignoring header " << line;
284  }
285  } else {
286  if (!qstrncmp(line, m_boundary, m_boundaryLength)) {
287 #ifdef DEBUG_PARSING
288  // qCDebug(KHTML_LOG) << "boundary found!";
289  // qCDebug(KHTML_LOG) << "after it is " << line.data() + m_boundaryLength;
290 #endif
291  // Was it the very last boundary ?
292  if (!qstrncmp(line.data() + m_boundaryLength, "--", 2)) {
293 #ifdef DEBUG_PARSING
294  // qCDebug(KHTML_LOG) << "Completed!";
295 #endif
296  endOfData();
297  emit completed();
298  } else {
299  char nextChar = *(line.data() + m_boundaryLength);
300 #ifdef DEBUG_PARSING
301  // qCDebug(KHTML_LOG) << "KMultiPart::slotData nextChar='" << nextChar << "'";
302 #endif
303  if (nextChar == '\n' || nextChar == '\r') {
304  endOfData();
305  startHeader();
306  } else {
307  // otherwise, false hit, it has trailing stuff
308  sendData(line);
309  }
310  }
311  } else {
312  // send to part
313  sendData(line);
314  }
315  }
316  m_lineParser->clearLine();
317  }
318  }
319 }
320 
321 void KMultiPart::setPart(const QString &mimeType)
322 {
323  KXMLGUIFactory *guiFactory = factory();
324  if (guiFactory) { // seems to be 0 when restoring from SM
325  guiFactory->removeClient(this);
326  }
327  // qCDebug(KHTML_LOG) << "KMultiPart::setPart " << mimeType;
328  delete m_part;
329  // Try to find an appropriate viewer component
330  m_part = KMimeTypeTrader::createPartInstanceFromQuery<KParts::ReadOnlyPart>
331  (m_mimeType, widget(), this);
332  widget()->layout()->addWidget(m_part->widget());
333  if (!m_part) {
334  // TODO launch external app
335  KMessageBox::error(widget(), i18n("No handler found for %1.", m_mimeType));
336  return;
337  }
338  // By making the part a child XMLGUIClient of ours, we get its GUI merged in.
339  insertChildClient(m_part);
340  m_part->widget()->show();
341 
342  connect(m_part, SIGNAL(completed()),
343  this, SLOT(slotPartCompleted()));
344  connect(m_part, SIGNAL(completed(bool)),
345  this, SLOT(slotPartCompleted()));
346 
347  m_isHTMLPart = (mimeType == "text/html");
349 
350  if (childExtension) {
351 
352  // Forward signals from the part's browser extension
353  // this is very related (but not exactly like) KHTMLPart::processObjectRequest
354 
355  connect(childExtension, SIGNAL(openUrlNotify()),
356  m_extension, SIGNAL(openUrlNotify()));
357 
358  connect(childExtension, SIGNAL(openUrlRequestDelayed(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)),
359  m_extension, SIGNAL(openUrlRequest(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)));
360 
361  connect(childExtension, SIGNAL(createNewWindow(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments,KParts::WindowArgs,KParts::ReadOnlyPart**)),
363 
364  // Keep in sync with khtml_part.cpp
369 
370  if (m_isHTMLPart)
371  connect(childExtension, SIGNAL(infoMessage(QString)),
372  m_extension, SIGNAL(infoMessage(QString)));
373  // For non-HTML we prefer to show our infoMessage ourselves.
374 
375  childExtension->setBrowserInterface(m_extension->browserInterface());
376 
377  connect(childExtension, SIGNAL(enableAction(const char*,bool)),
378  m_extension, SIGNAL(enableAction(const char*,bool)));
379  connect(childExtension, SIGNAL(setLocationBarUrl(QString)),
380  m_extension, SIGNAL(setLocationBarUrl(QString)));
381  connect(childExtension, SIGNAL(setIconUrl(QUrl)),
382  m_extension, SIGNAL(setIconUrl(QUrl)));
383  connect(childExtension, SIGNAL(loadingProgress(int)),
384  m_extension, SIGNAL(loadingProgress(int)));
385  if (m_isHTMLPart) // for non-HTML we have our own
386  connect(childExtension, SIGNAL(speedProgress(int)),
387  m_extension, SIGNAL(speedProgress(int)));
388  connect(childExtension, SIGNAL(selectionInfo(KFileItemList)),
389  m_extension, SIGNAL(selectionInfo(KFileItemList)));
390  connect(childExtension, SIGNAL(selectionInfo(QString)),
391  m_extension, SIGNAL(selectionInfo(QString)));
392  connect(childExtension, SIGNAL(selectionInfo(QList<QUrl>)),
393  m_extension, SIGNAL(selectionInfo(QList<QUrl>)));
394  connect(childExtension, SIGNAL(mouseOverInfo(KFileItem)),
395  m_extension, SIGNAL(mouseOverInfo(KFileItem)));
396  connect(childExtension, SIGNAL(moveTopLevelWidget(int,int)),
397  m_extension, SIGNAL(moveTopLevelWidget(int,int)));
398  connect(childExtension, SIGNAL(resizeTopLevelWidget(int,int)),
399  m_extension, SIGNAL(resizeTopLevelWidget(int,int)));
400  }
401 
402  m_partIsLoading = false;
403  // Load the part's plugins too.
404  // ###### This is a hack. The bug is that KHTMLPart doesn't load its plugins
405  // if className != "Browser/View".
406  loadPlugins(this, m_part, m_part->componentData());
407  // Get the part's GUI to appear
408  if (guiFactory) {
409  guiFactory->addClient(this);
410  }
411 }
412 
413 void KMultiPart::startOfData()
414 {
415  // qCDebug(KHTML_LOG) << "KMultiPart::startOfData";
416  Q_ASSERT(!m_nextMimeType.isNull());
417  if (m_nextMimeType.isNull()) {
418  return;
419  }
420 
421  if (m_gzip) {
422  // We can't use KFilterDev because it assumes it can read as much data as necessary
423  // from the underlying device. It's a pull strategy, while KMultiPart has to do
424  // a push strategy.
425  m_filter = new HTTPFilterGZip;
426  connect(m_filter, SIGNAL(output(QByteArray)), this, SLOT(reallySendData(QByteArray)));
427  }
428 
429  if (m_mimeType != m_nextMimeType) {
430  // Need to switch parts (or create the initial one)
431  m_mimeType = m_nextMimeType;
432  setPart(m_mimeType);
433  }
434  Q_ASSERT(m_part);
435  // Pass args (e.g. reload)
436  m_part->setArguments(arguments());
438  if (childExtension) {
439  childExtension->setBrowserArguments(m_extension->browserArguments());
440  }
441 
442  m_nextMimeType.clear();
443  if (m_tempFile) {
444  m_tempFile->setAutoRemove(true);
445  delete m_tempFile;
446  m_tempFile = nullptr;
447  }
448  if (m_isHTMLPart) {
449  KHTMLPart *htmlPart = static_cast<KHTMLPart *>(static_cast<KParts::ReadOnlyPart *>(m_part));
450  htmlPart->begin(url());
451  } else {
452  // ###### TODO use a QByteArray and a data: URL instead
453  m_tempFile = new QTemporaryFile;
454  m_tempFile->open();
455  }
456 }
457 
458 void KMultiPart::sendData(const QByteArray &line)
459 {
460  if (m_filter) {
461  m_filter->slotInput(line);
462  } else {
463  reallySendData(line);
464  }
465 }
466 
467 void KMultiPart::reallySendData(const QByteArray &line)
468 {
469  if (m_isHTMLPart) {
470  KHTMLPart *htmlPart = static_cast<KHTMLPart *>(static_cast<KParts::ReadOnlyPart *>(m_part));
471  htmlPart->write(line.data(), line.size());
472  } else if (m_tempFile) {
473  m_tempFile->write(line.data(), line.size());
474  }
475 }
476 
477 void KMultiPart::endOfData()
478 {
479  Q_ASSERT(m_part);
480  if (m_isHTMLPart) {
481  KHTMLPart *htmlPart = static_cast<KHTMLPart *>(static_cast<KParts::ReadOnlyPart *>(m_part));
482  htmlPart->end();
483  } else if (m_tempFile) {
484  const QString tempFileName = m_tempFile->fileName();
485  m_tempFile->close();
486  if (m_partIsLoading) {
487  // The part is still loading the last data! Let it proceed then
488  // Otherwise we'd keep canceling it, and nothing would ever show up...
489  // qCDebug(KHTML_LOG) << "KMultiPart::endOfData part isn't ready, skipping frame";
490  ++m_numberOfFramesSkipped;
491  m_tempFile->setAutoRemove(true);
492  } else {
493  // qCDebug(KHTML_LOG) << "KMultiPart::endOfData opening " << tempFileName;
494  QUrl url(tempFileName);
495  m_partIsLoading = true;
496  (void) m_part->openUrl(url);
497  }
498  delete m_tempFile;
499  m_tempFile = nullptr;
500  }
501 }
502 
503 void KMultiPart::slotPartCompleted()
504 {
505  if (!m_isHTMLPart) {
506  Q_ASSERT(m_part);
507  // Delete temp file used by the part
508  Q_ASSERT(m_part->url().isLocalFile());
509  // qCDebug(KHTML_LOG) << "slotPartCompleted deleting " << m_part->url().toLocalFile();
510  (void) unlink(QFile::encodeName(m_part->url().toLocalFile()));
511  m_partIsLoading = false;
512  ++m_numberOfFrames;
513  // Do not emit completed from here.
514  }
515 }
516 
517 bool KMultiPart::closeUrl()
518 {
519  m_timer->stop();
520  if (m_part) {
521  return m_part->closeUrl();
522  }
523  return true;
524 }
525 
526 void KMultiPart::guiActivateEvent(KParts::GUIActivateEvent *)
527 {
528  // Not public!
529  //if ( m_part )
530  // m_part->guiActivateEvent( e );
531 }
532 
533 void KMultiPart::slotJobFinished(KJob *job)
534 {
535  if (job->error()) {
536  // TODO use khtml's error:// scheme
537  job->uiDelegate()->showErrorMessage();
538  emit canceled(job->errorString());
539  } else {
540  /*if ( m_khtml->view()->contentsY() == 0 )
541  {
542  const KParts::OpenUrlArguments args = arguments();
543  m_khtml->view()->setContentsPos( args.xOffset(), args.yOffset() );
544  }*/
545 
546  emit completed();
547 
548  //QTimer::singleShot( 0, this, SLOT(updateWindowCaption()) );
549  }
550  m_job = nullptr;
551 }
552 
553 void KMultiPart::slotProgressInfo()
554 {
555  int time = m_qtime.elapsed();
556  if (!time) {
557  return;
558  }
559  if (m_totalNumberOfFrames == m_numberOfFrames + m_numberOfFramesSkipped) {
560  return; // No change, don't overwrite statusbar messages if any
561  }
562  //qCDebug(KHTML_LOG) << m_numberOfFrames << " in " << time << " milliseconds";
563  QString str("%1 frames per second, %2 frames skipped per second");
564  str = str.arg(1000.0 * (double)m_numberOfFrames / (double)time);
565  str = str.arg(1000.0 * (double)m_numberOfFramesSkipped / (double)time);
566  m_totalNumberOfFrames = m_numberOfFrames + m_numberOfFramesSkipped;
567  //qCDebug(KHTML_LOG) << str;
568  emit m_extension->infoMessage(str);
569 }
570 
571 #if 0
572 KMultiPartBrowserExtension::KMultiPartBrowserExtension(KMultiPart *parent, const char *name)
573  : KParts::BrowserExtension(parent, name)
574 {
575  m_imgPart = parent;
576 }
577 
578 int KMultiPartBrowserExtension::xOffset()
579 {
580  return m_imgPart->doc()->view()->contentsX();
581 }
582 
583 int KMultiPartBrowserExtension::yOffset()
584 {
585  return m_imgPart->doc()->view()->contentsY();
586 }
587 
588 void KMultiPartBrowserExtension::print()
589 {
590  static_cast<KHTMLPartBrowserExtension *>(m_imgPart->doc()->browserExtension())->print();
591 }
592 
593 void KMultiPartBrowserExtension::reparseConfiguration()
594 {
595  static_cast<KHTMLPartBrowserExtension *>(m_imgPart->doc()->browserExtension())->reparseConfiguration();
596  m_imgPart->doc()->setAutoloadImages(true);
597 }
598 #endif
599 
600 #include "kmultipart.moc"
void removeClient(KXMLGUIClient *client)
virtual QString errorString() const
void addClient(KXMLGUIClient *client)
bool isEmpty() const const
This class is khtml&#39;s main class.
Definition: khtml_part.h:208
virtual void begin(const QUrl &url=QUrl(), int xOffset=0, int yOffset=0)
Clears the widget and prepares it for new content.
int length() const const
QString queryMetaData(const QString &key)
const QList< QKeySequence > & reload()
#define K_PLUGIN_FACTORY(name, pluginRegistrations)
http://wp.netscape.com/assist/net_sites/pushpull.html
Definition: kmultipart.h:38
void setLayout(QLayout *layout)
static BrowserExtension * childObject(QObject *obj)
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
virtual void write(const char *str, int len=-1)
Writes another part of the HTML code to the widget.
KJobUiDelegate * uiDelegate() const
const QList< QKeySequence > & print()
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QString toLower() const const
QString i18n(const char *text, const TYPE &arg...)
QByteArray toLatin1() const const
virtual void showErrorMessage()
virtual void end()
Call this after your last call to write().
This is the BrowserExtension for a KHTMLPart document.
Definition: khtml_ext.h:45
char * data()
QString fromLatin1(const char *str, int size)
KGuiItem reset()
void reparseConfiguration(const QString &componentName)
int size() const const
virtual void setBrowserArguments(const BrowserArguments &args)
QByteArray encodeName(const QString &fileName)
int error() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 16 2021 22:47:58 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.