• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.5 API Reference
  • KDE Home
  • Contact Us
 

KParts

  • kparts
browserrun.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE project
2  *
3  * Copyright (C) 2002 David Faure <faure@kde.org>
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 version 2, as published by the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  * Library General Public License for more details.
12  *
13  * You should have received a copy of the GNU Library General Public License
14  * along with this library; see the file COPYING.LIB. If not, write to
15  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  * Boston, MA 02110-1301, USA.
17  */
18 
19 #include "browserrun.h"
20 #include "browserrun_p.h"
21 
22 #include <kmessagebox.h>
23 #include <kfiledialog.h>
24 #include <kio/job.h>
25 #include <kio/jobuidelegate.h>
26 #include <kio/scheduler.h>
27 #include <kio/copyjob.h>
28 #include <klocale.h>
29 #include <kshell.h>
30 #include <kstringhandler.h>
31 #include <kmimetypetrader.h>
32 #include <ktemporaryfile.h>
33 #include <kdebug.h>
34 #include <kde_file.h>
35 #include <kstandarddirs.h>
36 #include <kdatetime.h>
37 #include "browseropenorsavequestion.h"
38 #include <kprotocolmanager.h>
39 
40 using namespace KParts;
41 
42 class BrowserRun::BrowserRunPrivate
43 {
44 public:
45  bool m_bHideErrorDialog;
46  bool m_bRemoveReferrer;
47  bool m_bTrustedSource;
48  KParts::OpenUrlArguments m_args;
49  KParts::BrowserArguments m_browserArgs;
50 
51  KParts::ReadOnlyPart *m_part; // QGuardedPtr?
52  QPointer<QWidget> m_window;
53  QString m_mimeType;
54  QString m_contentDisposition;
55 };
56 
57 BrowserRun::BrowserRun( const KUrl& url, const KParts::OpenUrlArguments& args,
58  const KParts::BrowserArguments& browserArgs,
59  KParts::ReadOnlyPart *part, QWidget* window,
60  bool removeReferrer, bool trustedSource, bool hideErrorDialog )
61  : KRun( url, window, 0 /*mode*/, false /*is_local_file known*/, false /* no GUI */ ),
62  d(new BrowserRunPrivate)
63 {
64  d->m_bHideErrorDialog = hideErrorDialog;
65  d->m_bRemoveReferrer = removeReferrer;
66  d->m_bTrustedSource = trustedSource;
67  d->m_args = args;
68  d->m_browserArgs = browserArgs;
69  d->m_part = part;
70  d->m_window = window;
71 }
72 
73 BrowserRun::~BrowserRun()
74 {
75  delete d;
76 }
77 
78 KParts::ReadOnlyPart* BrowserRun::part() const
79 {
80  return d->m_part;
81 }
82 
83 KUrl BrowserRun::url() const
84 {
85  return KRun::url();
86 }
87 
88 void BrowserRun::init()
89 {
90  if ( d->m_bHideErrorDialog )
91  {
92  // ### KRun doesn't call a virtual method when it finds out that the URL
93  // is either malformed, or points to a non-existing local file...
94  // So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog
95  if ( !KRun::url().isValid() ) {
96  redirectToError( KIO::ERR_MALFORMED_URL, KRun::url().url() );
97  return;
98  }
99  if ( !isLocalFile() && !hasError() && KRun::url().isLocalFile() )
100  setIsLocalFile( true );
101 
102  if ( isLocalFile() ) {
103  KDE_struct_stat buff;
104  if ( KDE::stat( KRun::url().toLocalFile(), &buff ) == -1 )
105  {
106  kDebug(1000) << KRun::url().toLocalFile() << "doesn't exist.";
107  redirectToError( KIO::ERR_DOES_NOT_EXIST, KRun::url().toLocalFile() );
108  return;
109  }
110  setMode( buff.st_mode ); // while we're at it, save it for KRun::init() to use it
111  }
112  }
113  KRun::init();
114 }
115 
116 void BrowserRun::scanFile()
117 {
118  kDebug(1000) << KRun::url();
119 
120  // Let's check for well-known extensions
121  // Not when there is a query in the URL, in any case.
122  // Optimization for http/https, findByURL doesn't trust extensions over http.
123  QString protocol = KRun::url().protocol();
124 
125  if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) {
126  QString dummy;
127  protocol = KProtocolManager::slaveProtocol(KRun::url(), dummy);
128  }
129 
130  if ( KRun::url().query().isEmpty() && !protocol.startsWith(QLatin1String("http")))
131  {
132  KMimeType::Ptr mime = KMimeType::findByUrl( KRun::url() );
133  Q_ASSERT( mime );
134  if ( !mime->isDefault() || isLocalFile() )
135  {
136  kDebug(1000) << "MIME TYPE is" << mime->name();
137  mimeTypeDetermined( mime->name() );
138  return;
139  }
140  }
141 
142  QMap<QString, QString>& metaData = d->m_args.metaData();
143  if ( d->m_part ) {
144  const QString proto = d->m_part->url().protocol().toLower();
145 
146  if (proto == "https" || proto == "webdavs") {
147  metaData.insert("main_frame_request", "TRUE" );
148  metaData.insert("ssl_was_in_use", "TRUE" );
149  // metaData.insert("ssl_activate_warnings", "TRUE" );
150  } else if (proto == "http" || proto == "webdav") {
151  // metaData.insert("ssl_activate_warnings", "TRUE" );
152  metaData.insert("ssl_was_in_use", "FALSE" );
153  }
154 
155  // Set the PropagateHttpHeader meta-data if it has not already been set...
156  if (!metaData.contains("PropagateHttpHeader"))
157  metaData.insert("PropagateHttpHeader", "TRUE");
158  }
159 
160  KIO::TransferJob *job;
161  if ( d->m_browserArgs.doPost() && KRun::url().protocol().startsWith(QLatin1String("http"))) {
162  job = KIO::http_post( KRun::url(), d->m_browserArgs.postData, KIO::HideProgressInfo );
163  job->addMetaData( "content-type", d->m_browserArgs.contentType() );
164  } else {
165  job = KIO::get(KRun::url(),
166  d->m_args.reload() ? KIO::Reload : KIO::NoReload,
167  KIO::HideProgressInfo);
168  }
169 
170  if ( d->m_bRemoveReferrer )
171  metaData.remove("referrer");
172 
173  job->addMetaData( metaData );
174  job->ui()->setWindow( d->m_window );
175  connect( job, SIGNAL(result(KJob*)),
176  this, SLOT(slotBrowserScanFinished(KJob*)));
177  connect( job, SIGNAL(mimetype(KIO::Job*,QString)),
178  this, SLOT(slotBrowserMimetype(KIO::Job*,QString)));
179  setJob( job );
180 }
181 
182 void BrowserRun::slotBrowserScanFinished(KJob *job)
183 {
184  kDebug(1000) << job->error();
185  if ( job->error() == KIO::ERR_IS_DIRECTORY )
186  {
187  // It is in fact a directory. This happens when HTTP redirects to FTP.
188  // Due to the "protocol doesn't support listing" code in BrowserRun, we
189  // assumed it was a file.
190  kDebug(1000) << "It is in fact a directory!";
191  // Update our URL in case of a redirection
192  KRun::setUrl( static_cast<KIO::TransferJob *>(job)->url() );
193  setJob( 0 );
194  mimeTypeDetermined( "inode/directory" );
195  }
196  else
197  {
198  if ( job->error() )
199  handleError( job );
200  else
201  KRun::slotScanFinished(job);
202  }
203 }
204 
205 void BrowserRun::slotBrowserMimetype( KIO::Job *_job, const QString &type )
206 {
207  Q_ASSERT( _job == KRun::job() ); Q_UNUSED(_job)
208  KIO::TransferJob *job = static_cast<KIO::TransferJob *>(KRun::job());
209  // Update our URL in case of a redirection
210  //kDebug(1000) << "old URL=" << KRun::url();
211  //kDebug(1000) << "new URL=" << job->url();
212  setUrl( job->url() );
213 
214  if (job->isErrorPage()) {
215  d->m_mimeType = type;
216  handleError(job);
217  setJob( 0 );
218  } else {
219  kDebug(1000) << "found" << type << "for" << KRun::url();
220 
221  // Suggested filename given by the server (e.g. HTTP content-disposition)
222  // When set, we should really be saving instead of embedding
223  const QString suggestedFileName = job->queryMetaData("content-disposition-filename");
224  setSuggestedFileName(suggestedFileName); // store it (in KRun)
225  //kDebug(1000) << "suggestedFileName=" << suggestedFileName;
226  d->m_contentDisposition = job->queryMetaData("content-disposition-type");
227 
228  const QString modificationTime = job->queryMetaData("content-disposition-modification-date");
229  if (!modificationTime.isEmpty()) {
230  d->m_args.metaData().insert(QLatin1String("content-disposition-modification-date"), modificationTime);
231  }
232 
233  QMapIterator<QString,QString> it (job->metaData());
234  while (it.hasNext()) {
235  it.next();
236  if (it.key().startsWith(QLatin1String("ssl_"), Qt::CaseInsensitive))
237  d->m_args.metaData().insert(it.key(), it.value());
238  }
239 
240  // Make a copy to avoid a dead reference
241  QString _type = type;
242  job->putOnHold();
243  setJob( 0 );
244 
245  mimeTypeDetermined( _type );
246  }
247 }
248 
249 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString& mimeType)
250 {
251  KService::Ptr dummy;
252  return handleNonEmbeddable(mimeType, &dummy);
253 }
254 
255 BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString& _mimeType, KService::Ptr* selectedService)
256 {
257  QString mimeType( _mimeType );
258  Q_ASSERT( !hasFinished() ); // only come here if the mimetype couldn't be embedded
259  // Support for saving remote files.
260  if ( mimeType != "inode/directory" && // dirs can't be saved
261  !KRun::url().isLocalFile() )
262  {
263  if ( isTextExecutable(mimeType) )
264  mimeType = QLatin1String("text/plain"); // view, don't execute
265  // ... -> ask whether to save
266  BrowserOpenOrSaveQuestion question(d->m_window, KRun::url(), mimeType);
267  question.setSuggestedFileName(suggestedFileName());
268  if (selectedService)
269  question.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection);
270  BrowserOpenOrSaveQuestion::Result res = question.askOpenOrSave();
271  if (res == BrowserOpenOrSaveQuestion::Save) {
272  save( KRun::url(), suggestedFileName() );
273  kDebug(1000) << "Save: returning Handled";
274  setFinished( true );
275  return Handled;
276  }
277  else if (res == BrowserOpenOrSaveQuestion::Cancel) {
278  // saving done or canceled
279  kDebug(1000) << "Cancel: returning Handled";
280  setFinished( true );
281  return Handled;
282  }
283  else // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled)
284  {
285  // If we were in a POST, we can't just pass a URL to an external application.
286  // We must save the data to a tempfile first.
287  if ( d->m_browserArgs.doPost() )
288  {
289  kDebug(1000) << "request comes from a POST, can't pass a URL to another app, need to save";
290  d->m_mimeType = mimeType;
291  QString extension;
292  QString fileName = suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName();
293  int extensionPos = fileName.lastIndexOf( '.' );
294  if ( extensionPos != -1 )
295  extension = fileName.mid( extensionPos ); // keep the '.'
296  KTemporaryFile tempFile;
297  tempFile.setSuffix(extension);
298  tempFile.setAutoRemove(false);
299  tempFile.open();
300  KUrl destURL;
301  destURL.setPath( tempFile.fileName() );
302  KIO::Job *job = KIO::file_copy( KRun::url(), destURL, 0600, KIO::Overwrite );
303  job->ui()->setWindow(d->m_window);
304  connect( job, SIGNAL(result(KJob*)),
305  this, SLOT(slotCopyToTempFileResult(KJob*)) );
306  return Delayed; // We'll continue after the job has finished
307  }
308  if (selectedService)
309  *selectedService = question.selectedService();
310  }
311  }
312 
313  // Check if running is allowed
314  if ( !d->m_bTrustedSource && // ... and untrusted source...
315  !allowExecution( mimeType, KRun::url() ) ) // ...and the user said no (for executables etc.)
316  {
317  setFinished( true );
318  return Handled;
319  }
320 
321  KIO::Scheduler::publishSlaveOnHold(); // publish any slave on hold so it can be reused.
322  return NotHandled;
323 }
324 
325 //static
326 bool BrowserRun::allowExecution( const QString &mimeType, const KUrl &url )
327 {
328  if ( !KRun::isExecutable( mimeType ) )
329  return true;
330 
331  if ( !url.isLocalFile() ) // Don't permit to execute remote files
332  return false;
333 
334  return ( KMessageBox::warningContinueCancel( 0,
335  i18n( "Do you really want to execute '%1'?", url.prettyUrl() ),
336  i18n("Execute File?"), KGuiItem(i18n("Execute")) ) == KMessageBox::Continue );
337 }
338 
339 //static, deprecated
340 #ifndef KDE_NO_DEPRECATED
341 BrowserRun::AskSaveResult BrowserRun::askSave( const KUrl & url, KService::Ptr offer, const QString& mimeType, const QString & suggestedFileName )
342 {
343  Q_UNUSED(offer);
344  BrowserOpenOrSaveQuestion question(0, url, mimeType);
345  question.setSuggestedFileName(suggestedFileName);
346  const BrowserOpenOrSaveQuestion::Result result = question.askOpenOrSave();
347  return result == BrowserOpenOrSaveQuestion::Save ? Save
348  : BrowserOpenOrSaveQuestion::Open ? Open
349  : Cancel;
350 }
351 #endif
352 
353 //static, deprecated
354 #ifndef KDE_NO_DEPRECATED
355 BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave( const KUrl & url, const QString& mimeType, const QString & suggestedFileName, int flags )
356 {
357  BrowserOpenOrSaveQuestion question(0, url, mimeType);
358  question.setSuggestedFileName(suggestedFileName);
359  const BrowserOpenOrSaveQuestion::Result result = question.askEmbedOrSave(flags);
360  return result == BrowserOpenOrSaveQuestion::Save ? Save
361  : BrowserOpenOrSaveQuestion::Embed ? Open
362  : Cancel;
363 }
364 #endif
365 
366 // Default implementation, overridden in KHTMLRun
367 void BrowserRun::save( const KUrl & url, const QString & suggestedFileName )
368 {
369  saveUrl(url, suggestedFileName, d->m_window, d->m_args);
370 }
371 
372 // static
373 void BrowserRun::simpleSave( const KUrl & url, const QString & suggestedFileName,
374  QWidget* window )
375 {
376  saveUrl(url, suggestedFileName, window, KParts::OpenUrlArguments());
377 }
378 
379 void KParts::BrowserRun::saveUrl(const KUrl & url, const QString & suggestedFileName,
380  QWidget* window, const KParts::OpenUrlArguments& args)
381 {
382  // DownloadManager <-> konqueror integration
383  // find if the integration is enabled
384  // the empty key means no integration
385  // only use the downloadmanager for non-local urls
386  if ( !url.isLocalFile() )
387  {
388  KConfigGroup cfg = KSharedConfig::openConfig("konquerorrc", KConfig::NoGlobals)->group("HTML Settings");
389  QString downloadManger = cfg.readPathEntry("DownloadManager", QString());
390  if (!downloadManger.isEmpty())
391  {
392  // then find the download manager location
393  kDebug(1000) << "Using: "<<downloadManger <<" as Download Manager";
394  QString cmd=KStandardDirs::findExe(downloadManger);
395  if (cmd.isEmpty())
396  {
397  QString errMsg=i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManger);
398  QString errMsgEx= i18n("Try to reinstall it \n\nThe integration with Konqueror will be disabled.");
399  KMessageBox::detailedSorry(0,errMsg,errMsgEx);
400  cfg.writePathEntry("DownloadManager",QString());
401  cfg.sync ();
402  }
403  else
404  {
405  // ### suggestedFileName not taken into account. Fix this (and
406  // the duplicated code) with shiny new KDownload class for 3.2 (pfeiffer)
407  // Until the shiny new class comes about, send the suggestedFileName
408  // along with the actual URL to download. (DA)
409  cmd += ' ' + KShell::quoteArg(url.url());
410  if ( !suggestedFileName.isEmpty() )
411  cmd += ' ' + KShell::quoteArg(suggestedFileName);
412 
413  kDebug(1000) << "Calling command" << cmd;
414  // slave is already on hold (slotBrowserMimetype())
415  KIO::Scheduler::publishSlaveOnHold();
416  KRun::runCommand(cmd, window);
417  return;
418  }
419  }
420  }
421 
422  // no download manager available, let's do it ourself
423  KFileDialog *dlg = new KFileDialog( QString(), QString() /*all files*/,
424  window);
425  dlg->setOperationMode( KFileDialog::Saving );
426  dlg->setCaption(i18n("Save As"));
427  dlg->setConfirmOverwrite(true);
428 
429  QString name;
430  if ( !suggestedFileName.isEmpty() )
431  name = suggestedFileName;
432  else
433  name = url.fileName(KUrl::ObeyTrailingSlash); // can be empty, e.g. in case http://www.kde.org/
434 
435  dlg->setSelection(name);
436  if ( dlg->exec() )
437  {
438  KUrl destURL( dlg->selectedUrl() );
439  if ( destURL.isValid() )
440  {
441  saveUrlUsingKIO(url, destURL, window, args.metaData());
442  }
443  }
444  delete dlg;
445 }
446 
447 void BrowserRun::saveUrlUsingKIO(const KUrl & srcUrl, const KUrl& destUrl,
448  QWidget* window, const QMap<QString, QString> &metaData)
449 {
450  KIO::FileCopyJob *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite);
451 
452  const QString modificationTime = metaData[QLatin1String("content-disposition-modification-date")];
453  if (!modificationTime.isEmpty()) {
454  job->setModificationTime(KDateTime::fromString(modificationTime, KDateTime::RFCDate).dateTime());
455  }
456  job->setMetaData(metaData);
457  job->addMetaData("MaxCacheSize", "0"); // Don't store in http cache.
458  job->addMetaData("cache", "cache"); // Use entry from cache if available.
459  job->ui()->setWindow(window);
460  job->ui()->setAutoErrorHandlingEnabled( true );
461  new DownloadJobWatcher(job, metaData);
462 }
463 
464 void BrowserRun::slotStatResult( KJob *job )
465 {
466  if ( job->error() ) {
467  kDebug(1000) << job->errorString();
468  handleError( job );
469  } else
470  KRun::slotStatResult( job );
471 }
472 
473 void BrowserRun::handleError( KJob * job )
474 {
475  if ( !job ) { // Shouldn't happen, see docu.
476  kWarning(1000) << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog;
477  return;
478  }
479 
480  KIO::TransferJob *tjob = qobject_cast<KIO::TransferJob *>(job);
481  if (tjob && tjob->isErrorPage() && !job->error()) {
482  // The default handling of error pages is to show them like normal pages
483  // But this is done here in handleError so that KHTMLRun can reimplement it
484  tjob->putOnHold();
485  setJob(0);
486  if (!d->m_mimeType.isEmpty())
487  mimeTypeDetermined(d->m_mimeType);
488  return;
489  }
490 
491  if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT)
492  {
493  redirectToError( job->error(), job->errorText() );
494  return;
495  }
496 
497  // Reuse code in KRun, to benefit from d->m_showingError etc.
498  KRun::slotStatResult( job );
499 }
500 
501 // static
502 KUrl BrowserRun::makeErrorUrl(int error, const QString& errorText, const QString& initialUrl)
503 {
504  /*
505  * The format of the error:/ URL is error:/?query#url,
506  * where two variables are passed in the query:
507  * error = int kio error code, errText = QString error text from kio
508  * The sub-url is the URL that we were trying to open.
509  */
510  KUrl newURL(QString("error:/?error=%1&errText=%2")
511  .arg( error )
512  .arg( QString::fromUtf8( QUrl::toPercentEncoding( errorText ) ) ) );
513 
514  QString cleanedOrigUrl = initialUrl;
515  KUrl runURL = cleanedOrigUrl;
516  if (runURL.isValid()) {
517  runURL.setPass( QString() ); // don't put the password in the error URL
518  cleanedOrigUrl = runURL.url();
519  }
520 
521  newURL.setFragment(cleanedOrigUrl);
522  return newURL;
523 
524  // The kde3 approach broke with invalid urls, now that they become empty in qt4.
525  //KUrl::List lst;
526  //lst << newURL << runURL;
527  //return KUrl::join(lst);
528 }
529 
530 void BrowserRun::redirectToError( int error, const QString& errorText )
531 {
537  KRun::setUrl(makeErrorUrl(error, errorText, url().url()));
538  setJob( 0 );
539  mimeTypeDetermined( "text/html" );
540 }
541 
542 void BrowserRun::slotCopyToTempFileResult(KJob *job)
543 {
544  if ( job->error() ) {
545  job->uiDelegate()->showErrorMessage();
546  } else {
547  // Same as KRun::foundMimeType but with a different URL
548  (void) (KRun::runUrl( static_cast<KIO::FileCopyJob *>(job)->destUrl(), d->m_mimeType, d->m_window ));
549  }
550  setError( true ); // see above
551  setFinished( true );
552 }
553 
554 bool BrowserRun::isTextExecutable( const QString &mimeType )
555 {
556  return ( mimeType == "application/x-desktop" ||
557  mimeType == "application/x-shellscript" );
558 }
559 
560 bool BrowserRun::hideErrorDialog() const
561 {
562  return d->m_bHideErrorDialog;
563 }
564 
565 QString BrowserRun::contentDisposition() const
566 {
567  return d->m_contentDisposition;
568 }
569 
570 bool BrowserRun::serverSuggestsSave() const
571 {
572  // RfC 2183, section 2.8:
573  // Unrecognized disposition types should be treated as `attachment'.
574  return !contentDisposition().isEmpty() && (contentDisposition() != "inline");
575 }
576 
577 KParts::OpenUrlArguments& KParts::BrowserRun::arguments()
578 {
579  return d->m_args;
580 }
581 
582 KParts::BrowserArguments& KParts::BrowserRun::browserArguments()
583 {
584  return d->m_browserArgs;
585 }
586 
587 #include "browserrun.moc"
588 #include "browserrun_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Fri Dec 7 2012 16:11:51 by doxygen 1.8.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KParts

Skip menu "KParts"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal