Skip to content

Commit

Permalink
Merge pull request #7889 from borysiasty/plugins_from_encrypted_zips
Browse files Browse the repository at this point in the history
[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 ec2ddb4
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 35 deletions.
78 changes: 52 additions & 26 deletions python/pyplugin_installer/installer.py
Expand Up @@ -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,
Expand Down Expand Up @@ -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]

Expand All @@ -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()
Expand All @@ -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)
32 changes: 23 additions & 9 deletions python/pyplugin_installer/unzip.py
Expand Up @@ -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.