diff --git a/bespin/controllers.py b/bespin/controllers.py index f7ed5fc..2fb0add 100644 --- a/bespin/controllers.py +++ b/bespin/controllers.py @@ -452,11 +452,12 @@ def validate_url(url): raise BadRequest("Invalid url: " + url) return url -@expose(r'^/project/fromurl/(?P[^/]+)', "POST") -def import_from_url(request, response): - project_name = request.kwargs['project_name'] - - url = validate_url(request.body) +def _download_data(url, request): + """downloads the data to a temporary file, raising BadRequest + if there are issues, doing a HEAD request first to ensure that + the URL is good and also ensuring that the user has enough + space available.""" + url = validate_url(url) try: resp = httplib2.Http().request(url, method="HEAD") except httplib2.HttpLib2Error, e: @@ -478,6 +479,14 @@ def import_from_url(request, response): tempdatafile.write(datafile.read()) datafile.close() tempdatafile.seek(0) + return tempdatafile + + +@expose(r'^/project/fromurl/(?P[^/]+)', "POST") +def import_from_url(request, response): + project_name = request.kwargs['project_name'] + + tempdatafile = _download_data(request.body, request) url_parts = urlparse(url) filename = os.path.basename(url_parts[2]) _perform_import(request.user, project_name, filename, tempdatafile) @@ -1164,6 +1173,35 @@ def _wrap_script(plugin_name, script_path, script_text): ;}); tiki.script('%s:%s');""" % (plugin_name, module_name, script_text, plugin_name, script_path) +urlmatch = re.compile(r'^(http|https)://') + +@expose(r'^/plugin/install/', 'POST') +def install_plugin(request, response): + """Installs a plugin into the user's BespinSettings/plugins directory.""" + user = request.user + url = request.POST.get('url') + plugin_name = request.POST.get('pluginName') + + if not url or not urlmatch.search(url): + raise BadRequest("URL not provided") + + if not plugin_name or "/" in plugin_name or ".." in plugin_name: + raise BadRequest("Invalid plugin name. / and .. are not permitted") + + tempdatafile = _download_data(url, request) + settings_project = get_project(user, user, "BespinSettings") + destination = settings_project.location / "plugins" + path_entry = dict(name="user", chop=len(user.get_location())) + plugin = plugins.install_plugin(tempdatafile, url, destination, + path_entry, plugin_name) + tempdatafile.close() + + plugin_collection = dict() + plugin_collection[plugin.name] = plugin.metadata + response.body = simplejson.dumps(plugin_collection) + response.content_type = "application/json" + return response() + def db_middleware(app): def wrapped(environ, start_response): diff --git a/bespin/plugins.py b/bespin/plugins.py index 8368b59..1de3e55 100644 --- a/bespin/plugins.py +++ b/bespin/plugins.py @@ -34,6 +34,8 @@ # # ***** END LICENSE BLOCK ***** # +import os +from urlparse import urlparse from dryice.plugins import (Plugin as BasePlugin, find_plugins as base_find_plugins, @@ -105,3 +107,22 @@ def lookup_plugin(name, search_path=None): search_path = config.c.plugin_path return base_lookup_plugin(name, search_path, cls=Plugin) + +def install_plugin(f, url, destination, path_entry, plugin_name=None): + if not destination.exists(): + destination.mkdir() + + if plugin_name is None: + url_parts = urlparse(url) + filename = os.path.basename(url_parts[2]) + plugin_name = os.path.splitext(filename)[0] + + + # check for single file plugin + if url.endswith(".js"): + destination = destination / (plugin_name + ".js") + destination.write_bytes(f.read()) + plugin = Plugin(plugin_name, destination, path_entry) + return plugin + + \ No newline at end of file diff --git a/bespin/tests/test_plugins.py b/bespin/tests/test_plugins.py index 9369fd1..7b19934 100644 --- a/bespin/tests/test_plugins.py +++ b/bespin/tests/test_plugins.py @@ -41,6 +41,7 @@ from simplejson import loads from bespin import config, plugins, controllers from bespin.database import User, Base +from bespin.filesystem import get_project from __init__ import BespinTestApp @@ -77,6 +78,32 @@ def _init_data(): macgyver = User.find_user("MacGyver") + +def test_install_single_file_plugin(): + _init_data() + settings_project = get_project(macgyver, macgyver, "BespinSettings") + destination = settings_project.location / "plugins" + path_entry = dict(chop=len(macgyver.get_location()), name="user") + sfp = plugindir / "SingleFilePlugin1.js" + plugin = plugins.install_plugin(open(sfp), "http://somewhere/file.js", + destination, path_entry, "APlugin") + destfile = destination / "APlugin.js" + assert destfile.exists() + desttext = destfile.text() + assert "someFunction" in desttext + assert plugin.name == "APlugin" + assert plugin.location == destfile + assert plugin.relative_location == "BespinSettings/plugins/APlugin.js" + metadata = plugin.metadata + type = metadata['type'] + assert type == "user" + + plugin = plugins.install_plugin(open(sfp), "http://somewhere/Flibber.js", + destination, path_entry) + destfile = destination / "Flibber.js" + assert destfile.exists() + assert plugin.name == "Flibber" + # Web tests def test_default_plugin_registration(): @@ -171,4 +198,3 @@ def test_plugin_reload(): assert '"plugin2": {' in response.body # just need the plugin, not its dependents assert '"depends": ["plugin2"]' not in response.body - \ No newline at end of file