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

KNewStuff

  • knewstuff
  • knewstuff3
  • core
knewstuff3/core/installation.cpp
Go to the documentation of this file.
1 /*
2  This file is part of KNewStuff2.
3  Copyright (c) 2007 Josef Spillner <spillner@kde.org>
4  Copyright (C) 2009 Frederik Gladhorn <gladhorn@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either
9  version 2.1 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public
17  License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "installation.h"
21 
22 #include <QDir>
23 #include <QFile>
24 
25 #include "kstandarddirs.h"
26 #include "kmimetype.h"
27 #include "karchive.h"
28 #include "kzip.h"
29 #include "ktar.h"
30 #include "kprocess.h"
31 #include "kio/job.h"
32 #include "krandom.h"
33 #include "kshell.h"
34 #include "kmessagebox.h" // TODO get rid of message box
35 #include "ktoolinvocation.h" // TODO remove, this was only for my playing round
36 #include "klocalizedstring.h"
37 #include "kdebug.h"
38 
39 #include "core/security.h"
40 #ifdef Q_OS_WIN
41 #include <windows.h>
42 #include <shlobj.h>
43 #endif
44 
45 using namespace KNS3;
46 
47 Installation::Installation(QObject* parent)
48  : QObject(parent)
49  , checksumPolicy(Installation::CheckIfPossible)
50  , signaturePolicy(Installation::CheckIfPossible)
51  , scope(Installation::ScopeUser)
52  , customName(false)
53  , acceptHtml(false)
54 {
55 }
56 
57 bool Installation::readConfig(const KConfigGroup& group)
58 {
59  // FIXME: add support for several categories later on
60  // FIXME: read out only when actually installing as a performance improvement?
61  QString uncompresssetting = group.readEntry("Uncompress", QString("never"));
62  // support old value of true as equivalent of always
63  if (uncompresssetting == "true") {
64  uncompresssetting = "always";
65  }
66  if (uncompresssetting != "always" && uncompresssetting != "archive" && uncompresssetting != "never") {
67  kError() << "invalid Uncompress setting chosen, must be one of: always, archive, or never" << endl;
68  return false;
69  }
70  uncompression = uncompresssetting;
71  postInstallationCommand = group.readEntry("InstallationCommand", QString());
72  uninstallCommand = group.readEntry("UninstallCommand", QString());
73  standardResourceDirectory = group.readEntry("StandardResource", QString());
74  targetDirectory = group.readEntry("TargetDir", QString());
75  xdgTargetDirectory = group.readEntry("XdgTargetDir", QString());
76  installPath = group.readEntry("InstallPath", QString());
77  absoluteInstallPath = group.readEntry("AbsoluteInstallPath", QString());
78  customName = group.readEntry("CustomName", false);
79  acceptHtml = group.readEntry("AcceptHtmlDownloads", false);
80 
81  if (standardResourceDirectory.isEmpty() &&
82  targetDirectory.isEmpty() &&
83  xdgTargetDirectory.isEmpty() &&
84  installPath.isEmpty() &&
85  absoluteInstallPath.isEmpty()) {
86  kError() << "No installation target set";
87  return false;
88  }
89 
90  QString checksumpolicy = group.readEntry("ChecksumPolicy", QString());
91  if (!checksumpolicy.isEmpty()) {
92  if (checksumpolicy == "never")
93  checksumPolicy = Installation::CheckNever;
94  else if (checksumpolicy == "ifpossible")
95  checksumPolicy = Installation::CheckIfPossible;
96  else if (checksumpolicy == "always")
97  checksumPolicy = Installation::CheckAlways;
98  else {
99  kError() << "The checksum policy '" + checksumpolicy + "' is unknown." << endl;
100  return false;
101  }
102  }
103 
104  QString signaturepolicy = group.readEntry("SignaturePolicy", QString());
105  if (!signaturepolicy.isEmpty()) {
106  if (signaturepolicy == "never")
107  signaturePolicy = Installation::CheckNever;
108  else if (signaturepolicy == "ifpossible")
109  signaturePolicy = Installation::CheckIfPossible;
110  else if (signaturepolicy == "always")
111  signaturePolicy = Installation::CheckAlways;
112  else {
113  kError() << "The signature policy '" + signaturepolicy + "' is unknown." << endl;
114  return false;
115  }
116  }
117 
118  QString scopeString = group.readEntry("Scope", QString());
119  if (!scopeString.isEmpty()) {
120  if (scopeString == "user")
121  scope = ScopeUser;
122  else if (scopeString == "system")
123  scope = ScopeSystem;
124  else {
125  kError() << "The scope '" + scopeString + "' is unknown." << endl;
126  return false;
127  }
128 
129  if (scope == ScopeSystem) {
130  if (!installPath.isEmpty()) {
131  kError() << "System installation cannot be mixed with InstallPath." << endl;
132  return false;
133  }
134  }
135  }
136  return true;
137 }
138 
139 bool Installation::isRemote() const
140 {
141  if (!installPath.isEmpty()) return false;
142  if (!targetDirectory.isEmpty()) return false;
143  if (!xdgTargetDirectory.isEmpty()) return false;
144  if (!absoluteInstallPath.isEmpty()) return false;
145  if (!standardResourceDirectory.isEmpty()) return false;
146  return true;
147 }
148 
149 void Installation::install(EntryInternal entry)
150 {
151  downloadPayload(entry);
152 }
153 
154 void Installation::downloadPayload(const KNS3::EntryInternal& entry)
155 {
156  if(!entry.isValid()) {
157  emit signalInstallationFailed(i18n("Invalid item."));
158  return;
159  }
160  KUrl source = KUrl(entry.payload());
161 
162  if (!source.isValid()) {
163  kError() << "The entry doesn't have a payload." << endl;
164  emit signalInstallationFailed(i18n("Download of item failed: no download URL for \"%1\".", entry.name()));
165  return;
166  }
167 
168  // FIXME no clue what this is supposed to do
169  if (isRemote()) {
170  // Remote resource
171  //kDebug() << "Relaying remote payload '" << source << "'";
172  install(entry, source.pathOrUrl());
173  emit signalPayloadLoaded(source);
174  // FIXME: we still need registration for eventual deletion
175  return;
176  }
177 
178  QString fileName(source.fileName());
179  KUrl destination = QString(KGlobal::dirs()->saveLocation("tmp") + KRandom::randomString(10) + '-' + fileName);
180  kDebug() << "Downloading payload '" << source << "' to '" << destination << "'";
181 
182  // FIXME: check for validity
183  KIO::FileCopyJob *job = KIO::file_copy(source, destination, -1, KIO::Overwrite | KIO::HideProgressInfo);
184  connect(job,
185  SIGNAL(result(KJob*)),
186  SLOT(slotPayloadResult(KJob*)));
187 
188  entry_jobs[job] = entry;
189 }
190 
191 
192 void Installation::slotPayloadResult(KJob *job)
193 {
194  // for some reason this slot is getting called 3 times on one job error
195  if (entry_jobs.contains(job)) {
196  EntryInternal entry = entry_jobs[job];
197  entry_jobs.remove(job);
198 
199  if (job->error()) {
200  emit signalInstallationFailed(i18n("Download of \"%1\" failed, error: %2", entry.name(), job->errorString()));
201  } else {
202  KIO::FileCopyJob *fcjob = static_cast<KIO::FileCopyJob*>(job);
203 
204  // check if the app likes html files - disabled by default as too many bad links have been submitted to opendesktop.org
205  if (!acceptHtml) {
206  KMimeType::Ptr mimeType = KMimeType::findByPath(fcjob->destUrl().toLocalFile());
207  if (mimeType->is("text/html") || mimeType->is("application/x-php")) {
208  if (KMessageBox::questionYesNo(0, i18n("The downloaded file is a html file. This indicates a link to a website instead of the actual download. Would you like to open the site with a browser instead?"), i18n("Possibly bad download link"))
209  == KMessageBox::Yes) {
210  KToolInvocation::invokeBrowser(fcjob->srcUrl().url());
211  emit signalInstallationFailed(i18n("Downloaded file was a HTML file. Opened in browser."));
212  entry.setStatus(Entry::Invalid);
213  emit signalEntryChanged(entry);
214  return;
215  }
216  }
217  }
218 
219  install(entry, fcjob->destUrl().toLocalFile());
220  emit signalPayloadLoaded(fcjob->destUrl());
221  }
222  }
223 }
224 
225 
226 void Installation::install(KNS3::EntryInternal entry, const QString& downloadedFile)
227 {
228  kDebug() << "Install: " << entry.name() << " from " << downloadedFile;
229 
230  if (entry.payload().isEmpty()) {
231  kDebug() << "No payload associated with: " << entry.name();
232  return;
233  }
234 
235  // FIXME: first of all, do the security stuff here
236  // this means check sum comparison and signature verification
237  // signature verification might take a long time - make async?!
238  /*
239  if (checksumPolicy() != Installation::CheckNever) {
240  if (entry.checksum().isEmpty()) {
241  if (checksumPolicy() == Installation::CheckIfPossible) {
242  //kDebug() << "Skip checksum verification";
243  } else {
244  kError() << "Checksum verification not possible" << endl;
245  return false;
246  }
247  } else {
248  //kDebug() << "Verify checksum...";
249  }
250  }
251  if (signaturePolicy() != Installation::CheckNever) {
252  if (entry.signature().isEmpty()) {
253  if (signaturePolicy() == Installation::CheckIfPossible) {
254  //kDebug() << "Skip signature verification";
255  } else {
256  kError() << "Signature verification not possible" << endl;
257  return false;
258  }
259  } else {
260  //kDebug() << "Verify signature...";
261  }
262  }
263  */
264 
265  QString targetPath = targetInstallationPath(downloadedFile);
266  QStringList installedFiles = installDownloadedFileAndUncompress(entry, downloadedFile, targetPath);
267 
268  if (installedFiles.isEmpty()) {
269  if (entry.status() == Entry::Installing) {
270  entry.setStatus(Entry::Downloadable);
271  } else if (entry.status() == Entry::Updating) {
272  entry.setStatus(Entry::Updateable);
273  }
274  emit signalEntryChanged(entry);
275  emit signalInstallationFailed(i18n("Could not install \"%1\": file not found.", entry.name()));
276  return;
277  }
278 
279  entry.setInstalledFiles(installedFiles);
280 
281  if (!postInstallationCommand.isEmpty()) {
282  QString target;
283  if (installedFiles.size() == 1) {
284  runPostInstallationCommand(installedFiles.first());
285  } else {
286  runPostInstallationCommand(targetPath);
287  }
288  }
289 
290  // ==== FIXME: security code below must go above, when async handling is complete ====
291 
292  // FIXME: security object lifecycle - it is a singleton!
293  Security *sec = Security::ref();
294 
295  connect(sec,
296  SIGNAL(validityResult(int)),
297  SLOT(slotInstallationVerification(int)));
298 
299  // FIXME: change to accept filename + signature
300  sec->checkValidity(QString());
301 
302  // update version and release date to the new ones
303  if (entry.status() == Entry::Updating) {
304  if (!entry.updateVersion().isEmpty()) {
305  entry.setVersion(entry.updateVersion());
306  }
307  if (entry.updateReleaseDate().isValid()) {
308  entry.setReleaseDate(entry.updateReleaseDate());
309  }
310  }
311 
312  entry.setStatus(Entry::Installed);
313  emit signalEntryChanged(entry);
314  emit signalInstallationFinished();
315 }
316 
317 QString Installation::targetInstallationPath(const QString& payloadfile)
318 {
319  QString installpath(payloadfile);
320  QString installdir;
321 
322  if (!isRemote()) {
323  // installdir is the target directory
324 
325  // installpath also contains the file name if it's a single file, otherwise equal to installdir
326  int pathcounter = 0;
327  if (!standardResourceDirectory.isEmpty()) {
328  if (scope == ScopeUser) {
329  installdir = KStandardDirs::locateLocal(standardResourceDirectory.toUtf8(), "/");
330  } else { // system scope
331  installdir = KStandardDirs::installPath(standardResourceDirectory.toUtf8());
332  }
333  pathcounter++;
334  }
335  if (!targetDirectory.isEmpty()) {
336  if (scope == ScopeUser) {
337  installdir = KStandardDirs::locateLocal("data", targetDirectory + '/');
338  } else { // system scope
339  installdir = KStandardDirs::installPath("data") + targetDirectory + '/';
340  }
341  pathcounter++;
342  }
343  if (!xdgTargetDirectory.isEmpty()) {
344  installdir = KStandardDirs().localxdgdatadir() + '/' + xdgTargetDirectory + '/';
345  pathcounter++;
346  }
347  if (!installPath.isEmpty()) {
348 #if defined(Q_WS_WIN)
349 #ifndef _WIN32_WCE
350  WCHAR wPath[MAX_PATH+1];
351  if ( SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, wPath) == S_OK) {
352  installdir = QString::fromUtf16((const ushort *) wPath) + QLatin1Char('/') + installpath + QLatin1Char('/');
353  } else {
354 #endif
355  installdir = QDir::home().path() + QLatin1Char('/') + installPath + QLatin1Char('/');
356 #ifndef _WIN32_WCE
357  }
358 #endif
359 #else
360  installdir = QDir::home().path() + '/' + installPath + '/';
361 #endif
362  pathcounter++;
363  }
364  if (!absoluteInstallPath.isEmpty()) {
365  installdir = absoluteInstallPath + '/';
366  pathcounter++;
367  }
368  if (pathcounter != 1) {
369  kError() << "Wrong number of installation directories given." << endl;
370  return QString();
371  }
372 
373  kDebug() << "installdir: " << installdir;
374 
375  }
376 
377  return installdir;
378 }
379 
380 QStringList Installation::installDownloadedFileAndUncompress(const KNS3::EntryInternal& entry, const QString& payloadfile, const QString installdir)
381 {
382  QString installpath(payloadfile);
383  // Collect all files that were installed
384  QStringList installedFiles;
385 
386  if (!isRemote()) {
387  bool isarchive = true;
388 
389  // respect the uncompress flag in the knsrc
390  if (uncompression == "always" || uncompression == "archive") {
391  // this is weird but a decompression is not a single name, so take the path instead
392  installpath = installdir;
393  KMimeType::Ptr mimeType = KMimeType::findByPath(payloadfile);
394  //kDebug() << "Postinstallation: uncompress the file";
395 
396  // FIXME: check for overwriting, malicious archive entries (../foo) etc.
397  // FIXME: KArchive should provide "safe mode" for this!
398  KArchive *archive = 0;
399 
400 
401  if (mimeType->is("application/zip")) {
402  archive = new KZip(payloadfile);
403  } else if (mimeType->is("application/tar")
404  || mimeType->is("application/x-gzip")
405  || mimeType->is("application/x-bzip")
406  || mimeType->is("application/x-lzma")
407  || mimeType->is("application/x-xz")
408  || mimeType->is("application/x-bzip-compressed-tar")
409  || mimeType->is("application/x-compressed-tar") ) {
410  archive = new KTar(payloadfile);
411  } else {
412  delete archive;
413  kError() << "Could not determine type of archive file '" << payloadfile << "'";
414  if (uncompression == "always") {
415  return QStringList();
416  }
417  isarchive = false;
418  }
419 
420  if (isarchive) {
421  bool success = archive->open(QIODevice::ReadOnly);
422  if (!success) {
423  kError() << "Cannot open archive file '" << payloadfile << "'";
424  if (uncompression == "always") {
425  return QStringList();
426  }
427  // otherwise, just copy the file
428  isarchive = false;
429  }
430 
431  if (isarchive) {
432  const KArchiveDirectory *dir = archive->directory();
433  dir->copyTo(installdir);
434 
435  installedFiles << archiveEntries(installdir, dir);
436  installedFiles << installdir + '/';
437 
438  archive->close();
439  QFile::remove(payloadfile);
440  delete archive;
441  }
442  }
443  }
444 
445  kDebug() << "isarchive: " << isarchive;
446 
447  if (uncompression == "never" || (uncompression == "archive" && !isarchive)) {
448  // no decompress but move to target
449 
451  // FIXME: make naming convention configurable through *.knsrc? e.g. for kde-look.org image names
452  KUrl source = KUrl(entry.payload());
453  kDebug() << "installing non-archive from " << source.url();
454  QString installfile;
455  QString ext = source.fileName().section('.', -1);
456  if (customName) {
457  installfile = entry.name();
458  installfile += '-' + entry.version();
459  if (!ext.isEmpty()) installfile += '.' + ext;
460  } else {
461  installfile = source.fileName();
462  }
463  installpath = installdir + '/' + installfile;
464 
465  //kDebug() << "Install to file " << installpath;
466  // FIXME: copy goes here (including overwrite checking)
467  // FIXME: what must be done now is to update the cache *again*
468  // in order to set the new payload filename (on root tag only)
469  // - this might or might not need to take uncompression into account
470  // FIXME: for updates, we might need to force an overwrite (that is, deleting before)
471  QFile file(payloadfile);
472  bool success = true;
473  const bool update = ((entry.status() == Entry::Updateable) || (entry.status() == Entry::Updating));
474 
475  if (QFile::exists(installpath)) {
476  if (!update) {
477  if (KMessageBox::warningContinueCancel(0, i18n("Overwrite existing file?") + "\n'" + installpath + '\'', i18n("Download File:")) == KMessageBox::Cancel) {
478  return QStringList();
479  }
480  }
481  success = QFile::remove(installpath);
482  }
483  if (success) {
484  success = file.rename(KUrl(installpath).toLocalFile());
485  kDebug() << "move: " << file.fileName() << " to " << installpath;
486  }
487  if (!success) {
488  kError() << "Cannot move file '" << payloadfile << "' to destination '" << installpath << "'";
489  return QStringList();
490  }
491  installedFiles << installpath;
492  }
493  }
494  return installedFiles;
495 }
496 
497 void Installation::runPostInstallationCommand(const QString& installPath)
498 {
499  KProcess process;
500  QString command(postInstallationCommand);
501  QString fileArg(KShell::quoteArg(installPath));
502  command.replace("%f", fileArg);
503 
504  kDebug() << "Run command: " << command;
505 
506  process.setShellCommand(command);
507  int exitcode = process.execute();
508 
509  if (exitcode) {
510  kError() << "Command failed" << endl;
511  }
512 }
513 
514 
515 void Installation::uninstall(EntryInternal entry)
516 {
517  entry.setStatus(Entry::Deleted);
518 
519  if (!uninstallCommand.isEmpty()) {
520  KProcess process;
521  foreach (const QString& file, entry.installedFiles()) {
522  QFileInfo info(file);
523  if (info.isFile()) {
524  QString fileArg(KShell::quoteArg(file));
525  QString command(uninstallCommand);
526  command.replace("%f", fileArg);
527 
528  process.setShellCommand(command);
529  int exitcode = process.execute();
530 
531  if (exitcode) {
532  kError() << "Command failed" << endl;
533  } else {
534  //kDebug() << "Command executed successfully";
535  }
536  }
537  }
538  }
539 
540  foreach(const QString &file, entry.installedFiles()) {
541  if (file.endsWith('/')) {
542  QDir dir;
543  bool worked = dir.rmdir(file);
544  if (!worked) {
545  // Maybe directory contains user created files, ignore it
546  continue;
547  }
548  } else {
549  QFileInfo info(file);
550  if (info.exists() || info.isSymLink()) {
551  bool worked = QFile::remove(file);
552  if (!worked) {
553  kWarning() << "unable to delete file " << file;
554  return;
555  }
556  } else {
557  kWarning() << "unable to delete file " << file << ". file does not exist.";
558  }
559  }
560  }
561  entry.setUnInstalledFiles(entry.installedFiles());
562  entry.setInstalledFiles(QStringList());
563 
564  emit signalEntryChanged(entry);
565 }
566 
567 
568 void Installation::slotInstallationVerification(int result)
569 {
570  //kDebug() << "SECURITY result " << result;
571 
572  //FIXME do something here ??? and get the right entry again
573  EntryInternal entry;
574 
575  if (result & Security::SIGNED_OK)
576  emit signalEntryChanged(entry);
577  else
578  emit signalEntryChanged(entry);
579 }
580 
581 
582 QStringList Installation::archiveEntries(const QString& path, const KArchiveDirectory * dir)
583 {
584  QStringList files;
585  foreach(const QString &entry, dir->entries()) {
586  QString childPath = path + '/' + entry;
587  if (dir->entry(entry)->isFile()) {
588  files << childPath;
589  }
590 
591  if (dir->entry(entry)->isDirectory()) {
592  const KArchiveDirectory* childDir = static_cast<const KArchiveDirectory*>(dir->entry(entry));
593  files << archiveEntries(childPath, childDir);
594  files << childPath + '/';
595  }
596  }
597  return files;
598 }
599 
600 
601 #include "installation.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Fri Nov 16 2012 15:16:21 by doxygen 1.8.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KNewStuff

Skip menu "KNewStuff"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • 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