diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index 64199e9a..35f38447 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -40,6 +40,13 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, octoprint.plugin.EventHandlerPlugin): ARCHIVE_EXTENSIONS = (".zip", ".tar.gz", ".tgz", ".tar") + + # valid pip install URL schemes according to https://pip.pypa.io/en/stable/reference/pip_install/ + URL_SCHEMES = ("http", "https", "git", + "git+http", "git+https", "git+ssh", "git+git", + "hg+http", "hg+https", "hg+static-http", "hg+ssh", + "svn", "svn+svn", "svn+http", "svn+https", "svn+ssh", + "bzr+http", "bzr+https", "bzr+ssh", "bzr+sftp", "bzr+ftp", "bzr+lp") OPERATING_SYSTEMS = dict(windows=["win32"], linux=lambda x: x.startswith("linux"), @@ -299,6 +306,9 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, def command_install(self, url=None, path=None, force=False, reinstall=None, dependency_links=False): if url is not None: + if not any(map(lambda scheme: url.starts_with(scheme + "://"), self.URL_SCHEMES)): + raise ValueError("Invalid URL to pip install from") + source = url source_type = "url" already_installed_check = lambda line: url in line diff --git a/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js b/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js index 6ac2e718..9663cc61 100644 --- a/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js +++ b/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js @@ -239,23 +239,57 @@ $(function() { }; self.invalidUrl = ko.pureComputed(function() { + // supported pip install URL schemes, according to https://pip.pypa.io/en/stable/reference/pip_install/ + var allowedUrlSchemes = ["http", "https", + "git", "git+http", "git+https", "git+ssh", "git+git", + "hg+http", "hg+https", "hg+static-http", "hg+ssh", + "svn", "svn+svn", "svn+http", "svn+https", "svn+ssh", + "bzr+http", "bzr+https", "bzr+ssh", "bzr+sftp", "brz+ftp", "bzr+lp"]; + var url = self.installUrl(); - return url !== undefined && url.trim() != "" && !(_.startsWith(url.toLocaleLowerCase(), "http://") || _.startsWith(url.toLocaleLowerCase(), "https://")); + var lowerUrl = url !== undefined ? url.toLocaleLowerCase() : undefined; + + var lowerUrlStartsWithScheme = function(scheme) { + return _.startsWith(lowerUrl, scheme + "://"); + }; + + return url !== undefined && url.trim() !== "" + && !(_.any(allowedUrlSchemes, lowerUrlStartsWithScheme)); }); self.enableUrlInstall = ko.pureComputed(function() { var url = self.installUrl(); - return self.enableManagement() && self.pipAvailable() && !self.safeMode() && self.online() && url !== undefined && url.trim() != "" && !self.invalidUrl(); + return self.enableManagement() + && self.pipAvailable() + && !self.safeMode() + && self.online() + && url !== undefined + && url.trim() !== "" + && !self.invalidUrl(); }); self.invalidArchive = ko.pureComputed(function() { + var allowedArchiveExtensions = [".zip", ".tar.gz", ".tgz", ".tar"]; + var name = self.uploadFilename(); - return name !== undefined && !(_.endsWith(name.toLocaleLowerCase(), ".zip") || _.endsWith(name.toLocaleLowerCase(), ".tar.gz") || _.endsWith(name.toLocaleLowerCase(), ".tgz") || _.endsWith(name.toLocaleLowerCase(), ".tar")); + var lowerName = name !== undefined ? name.toLocaleLowerCase() : undefined; + + var lowerNameHasExtension = function(extension) { + return _.endsWith(lowerName, extension); + }; + + return name !== undefined + && !(_.any(allowedArchiveExtensions, lowerNameHasExtension)); }); self.enableArchiveInstall = ko.pureComputed(function() { var name = self.uploadFilename(); - return self.enableManagement() && self.pipAvailable() && !self.safeMode() && name !== undefined && name.trim() != "" && !self.invalidArchive(); + return self.enableManagement() + && self.pipAvailable() + && !self.safeMode() + && name !== undefined + && name.trim() !== "" + && !self.invalidArchive(); }); self.uploadElement.fileupload({ diff --git a/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 b/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 index 10f921db..b8505295 100644 --- a/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 +++ b/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 @@ -197,7 +197,7 @@ - {{ _('This does not look like a valid "http://" or "https://" URL.') }} + {{ _('This does not look like a valid URL. Expected http, https or any of the supported VCS URLs.', url='https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support') }}

{{ _('... from an uploaded archive') }}