Skip to content
Permalink
Browse files

Merge pull request #7889 from borysiasty/plugins_from_encrypted_zips

[FEATURE][Plugin installer] Support for encrypted zips when installing plugins from local files
  • Loading branch information
borysiasty committed Sep 14, 2018
2 parents b75f9f3 + 9bd532f commit ec2ddb4295badd6953237e5e9182b7903f429e5a
Showing with 75 additions and 35 deletions.
  1. +52 −26 python/pyplugin_installer/installer.py
  2. +23 −9 python/pyplugin_installer/unzip.py
@@ -28,12 +28,12 @@
import zipfile

from qgis.PyQt.QtCore import Qt, QObject, QDir, QUrl, QFileInfo, QFile
from qgis.PyQt.QtWidgets import QMessageBox, QLabel, QFrame, QApplication, QFileDialog
from qgis.PyQt.QtWidgets import QApplication, QDialog, QDialogButtonBox, QFrame, QMessageBox, QLabel, QVBoxLayout
from qgis.PyQt.QtNetwork import QNetworkRequest

import qgis
from qgis.core import Qgis, QgsApplication, QgsNetworkAccessManager, QgsSettings
from qgis.gui import QgsMessageBar
from qgis.gui import QgsMessageBar, QgsPasswordLineEdit
from qgis.utils import (iface, startPlugin, unloadPlugin, loadPlugin,
reloadPlugin, updateAvailablePlugins)
from .installer_data import (repositories, plugins, officialRepo,
@@ -539,9 +539,6 @@ def installFromZipFile(self, filePath):
settings.setValue(settingsGroup + '/lastZipDirectory',
QFileInfo(filePath).absoluteDir().absolutePath())

error = False
infoString = None

with zipfile.ZipFile(filePath, 'r') as zf:
pluginName = os.path.split(zf.namelist()[0])[0]

@@ -551,24 +548,54 @@ def installFromZipFile(self, filePath):
if not QDir(pluginsDirectory).exists():
QDir().mkpath(pluginsDirectory)

pluginDirectory = QDir.cleanPath(os.path.join(pluginsDirectory, pluginName))

# If the target directory already exists as a link,
# remove the link without resolving
QFile(os.path.join(pluginsDirectory, pluginFileName)).remove()
QFile(pluginDirectory).remove()

try:
# Test extraction. If fails, then exception will be raised
# and no removing occurs
unzip(str(filePath), str(pluginsDirectory))
# Removing old plugin files if exist
removeDir(QDir.cleanPath(os.path.join(pluginsDirectory, pluginFileName)))
# Extract new files
unzip(str(filePath), str(pluginsDirectory))
except:
error = True
infoString = (self.tr("Plugin installation failed"),
self.tr("Failed to unzip the plugin package\n{}.\nProbably it is broken".format(filePath)))
password = None
infoString = None
success = False
keepTrying = True

while keepTrying:
try:
# Test extraction. If fails, then exception will be raised and no removing occurs
unzip(filePath, pluginsDirectory, password)
# Removing old plugin files if exist
removeDir(pluginDirectory)
# Extract new files
unzip(filePath, pluginsDirectory, password)
keepTrying = False
success = True
except Exception as e:
success = False
if 'password' in str(e):
infoString = self.tr('Aborted by user')
if 'Bad password' in str(e):
msg = self.tr('Wrong password. Please enter a correct password to the zip file.')
else:
msg = self.tr('The zip file is encrypted. Please enter password.')
# Display a password dialog with QgsPasswordLineEdit
dlg = QDialog()
dlg.setWindowTitle(self.tr('Enter password'))
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal)
buttonBox.rejected.connect(dlg.reject)
buttonBox.accepted.connect(dlg.accept)
lePass = QgsPasswordLineEdit()
layout = QVBoxLayout()
layout.addWidget(QLabel(msg))
layout.addWidget(lePass)
layout.addWidget(buttonBox)
dlg.setLayout(layout)
keepTrying = dlg.exec_()
password = lePass.text()
else:
infoString = self.tr("Failed to unzip the plugin package\n{}.\nProbably it is broken".format(filePath))
keepTrying = False

if infoString is None:
if success:
updateAvailablePlugins()
loadPlugin(pluginName)
plugins.getAllInstalled()
@@ -585,11 +612,10 @@ def installFromZipFile(self, filePath):
else:
if startPlugin(pluginName):
settings.setValue('/PythonPlugins/' + pluginName, True)
infoString = (self.tr("Plugin installed successfully"), "")

if infoString[0]:
level = error and Qgis.Critical or Qgis.Info
msg = "<b>%s</b>" % infoString[0]
if infoString[1]:
msg += "<b>:</b> %s" % infoString[1]
iface.pluginManagerInterface().pushMessage(msg, level)
msg = "<b>%s</b>" % self.tr("Plugin installed successfully")
else:
msg = "<b>%s:</b> %s" % (self.tr("Plugin installation failed"), infoString)

level = Qgis.Info if success else Qgis.Critical
iface.pluginManagerInterface().pushMessage(msg, level)
@@ -24,27 +24,41 @@
import os


def unzip(file, targetDir):
def unzip(file, targetDir, password=None):
""" Creates directory structure and extracts the zip contents to it.
file - the zip file to extract
targetDir - target location
file (file object) - the zip file to extract
targetDir (str) - target location
password (str; optional) - password to decrypt the zip file (if encrypted)
"""

# convert password to bytes
if isinstance(password, str):
password = bytes(password, 'utf8')

# create destination directory if doesn't exist
if not targetDir.endswith(':') and not os.path.exists(targetDir):
os.makedirs(targetDir)

zf = zipfile.ZipFile(file)
for name in zf.namelist():
# Skip directories - they will be created when necessary by os.makedirs
if name.endswith('/'):
continue

# Read the source file before creating any output,
# so no directories are created if user doesn't know the password
memberContent = zf.read(name, password)

# create directory if doesn't exist
localDir = os.path.split(name)[0]
fullDir = os.path.normpath(os.path.join(targetDir, localDir))
if not os.path.exists(fullDir):
os.makedirs(fullDir)
# extract file
if not name.endswith('/'):
fullPath = os.path.normpath(os.path.join(targetDir, name))
outfile = open(fullPath, 'wb')
outfile.write(zf.read(name))
outfile.flush()
outfile.close()
fullPath = os.path.normpath(os.path.join(targetDir, name))
outfile = open(fullPath, 'wb')
outfile.write(memberContent)
outfile.flush()
outfile.close()

zf.close()

0 comments on commit ec2ddb4

Please sign in to comment.
You can’t perform that action at this time.