Skip to content

Commit ec2ddb4

Browse files
authored
Merge pull request #7889 from borysiasty/plugins_from_encrypted_zips
[FEATURE][Plugin installer] Support for encrypted zips when installing plugins from local files
2 parents b75f9f3 + 9bd532f commit ec2ddb4

File tree

2 files changed

+75
-35
lines changed

2 files changed

+75
-35
lines changed

python/pyplugin_installer/installer.py

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
import zipfile
2929

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

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

542-
error = False
543-
infoString = None
544-
545542
with zipfile.ZipFile(filePath, 'r') as zf:
546543
pluginName = os.path.split(zf.namelist()[0])[0]
547544

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

551+
pluginDirectory = QDir.cleanPath(os.path.join(pluginsDirectory, pluginName))
552+
554553
# If the target directory already exists as a link,
555554
# remove the link without resolving
556-
QFile(os.path.join(pluginsDirectory, pluginFileName)).remove()
555+
QFile(pluginDirectory).remove()
557556

558-
try:
559-
# Test extraction. If fails, then exception will be raised
560-
# and no removing occurs
561-
unzip(str(filePath), str(pluginsDirectory))
562-
# Removing old plugin files if exist
563-
removeDir(QDir.cleanPath(os.path.join(pluginsDirectory, pluginFileName)))
564-
# Extract new files
565-
unzip(str(filePath), str(pluginsDirectory))
566-
except:
567-
error = True
568-
infoString = (self.tr("Plugin installation failed"),
569-
self.tr("Failed to unzip the plugin package\n{}.\nProbably it is broken".format(filePath)))
557+
password = None
558+
infoString = None
559+
success = False
560+
keepTrying = True
561+
562+
while keepTrying:
563+
try:
564+
# Test extraction. If fails, then exception will be raised and no removing occurs
565+
unzip(filePath, pluginsDirectory, password)
566+
# Removing old plugin files if exist
567+
removeDir(pluginDirectory)
568+
# Extract new files
569+
unzip(filePath, pluginsDirectory, password)
570+
keepTrying = False
571+
success = True
572+
except Exception as e:
573+
success = False
574+
if 'password' in str(e):
575+
infoString = self.tr('Aborted by user')
576+
if 'Bad password' in str(e):
577+
msg = self.tr('Wrong password. Please enter a correct password to the zip file.')
578+
else:
579+
msg = self.tr('The zip file is encrypted. Please enter password.')
580+
# Display a password dialog with QgsPasswordLineEdit
581+
dlg = QDialog()
582+
dlg.setWindowTitle(self.tr('Enter password'))
583+
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal)
584+
buttonBox.rejected.connect(dlg.reject)
585+
buttonBox.accepted.connect(dlg.accept)
586+
lePass = QgsPasswordLineEdit()
587+
layout = QVBoxLayout()
588+
layout.addWidget(QLabel(msg))
589+
layout.addWidget(lePass)
590+
layout.addWidget(buttonBox)
591+
dlg.setLayout(layout)
592+
keepTrying = dlg.exec_()
593+
password = lePass.text()
594+
else:
595+
infoString = self.tr("Failed to unzip the plugin package\n{}.\nProbably it is broken".format(filePath))
596+
keepTrying = False
570597

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

590-
if infoString[0]:
591-
level = error and Qgis.Critical or Qgis.Info
592-
msg = "<b>%s</b>" % infoString[0]
593-
if infoString[1]:
594-
msg += "<b>:</b> %s" % infoString[1]
595-
iface.pluginManagerInterface().pushMessage(msg, level)
616+
msg = "<b>%s</b>" % self.tr("Plugin installed successfully")
617+
else:
618+
msg = "<b>%s:</b> %s" % (self.tr("Plugin installation failed"), infoString)
619+
620+
level = Qgis.Info if success else Qgis.Critical
621+
iface.pluginManagerInterface().pushMessage(msg, level)

python/pyplugin_installer/unzip.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,41 @@
2424
import os
2525

2626

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

34+
# convert password to bytes
35+
if isinstance(password, str):
36+
password = bytes(password, 'utf8')
37+
3338
# create destination directory if doesn't exist
3439
if not targetDir.endswith(':') and not os.path.exists(targetDir):
3540
os.makedirs(targetDir)
3641

3742
zf = zipfile.ZipFile(file)
3843
for name in zf.namelist():
44+
# Skip directories - they will be created when necessary by os.makedirs
45+
if name.endswith('/'):
46+
continue
47+
48+
# Read the source file before creating any output,
49+
# so no directories are created if user doesn't know the password
50+
memberContent = zf.read(name, password)
51+
3952
# create directory if doesn't exist
4053
localDir = os.path.split(name)[0]
4154
fullDir = os.path.normpath(os.path.join(targetDir, localDir))
4255
if not os.path.exists(fullDir):
4356
os.makedirs(fullDir)
4457
# extract file
45-
if not name.endswith('/'):
46-
fullPath = os.path.normpath(os.path.join(targetDir, name))
47-
outfile = open(fullPath, 'wb')
48-
outfile.write(zf.read(name))
49-
outfile.flush()
50-
outfile.close()
58+
fullPath = os.path.normpath(os.path.join(targetDir, name))
59+
outfile = open(fullPath, 'wb')
60+
outfile.write(memberContent)
61+
outfile.flush()
62+
outfile.close()
63+
64+
zf.close()

0 commit comments

Comments
 (0)