diff options
Diffstat (limited to 'mediagoblin/plugins/piwigo')
-rw-r--r-- | mediagoblin/plugins/piwigo/README.rst | 23 | ||||
-rw-r--r-- | mediagoblin/plugins/piwigo/__init__.py | 37 | ||||
-rw-r--r-- | mediagoblin/plugins/piwigo/forms.py | 28 | ||||
-rw-r--r-- | mediagoblin/plugins/piwigo/tools.py | 107 | ||||
-rw-r--r-- | mediagoblin/plugins/piwigo/views.py | 167 |
5 files changed, 362 insertions, 0 deletions
diff --git a/mediagoblin/plugins/piwigo/README.rst b/mediagoblin/plugins/piwigo/README.rst new file mode 100644 index 00000000..0c71ffbc --- /dev/null +++ b/mediagoblin/plugins/piwigo/README.rst @@ -0,0 +1,23 @@ +=================== + piwigo api plugin +=================== + +.. danger:: + This plugin does not work. + It might make your instance unstable or even insecure. + So do not use it, unless you want to help to develop it. + +.. warning:: + You should not depend on this plugin in any way for now. + It might even go away without any notice. + +Okay, so if you still want to test this plugin, +add the following to your mediagoblin_local.ini: + +.. code-block:: ini + + [plugins] + [[mediagoblin.plugins.piwigo]] + +Then try to connect using some piwigo client. +There should be some logging, that might help. diff --git a/mediagoblin/plugins/piwigo/__init__.py b/mediagoblin/plugins/piwigo/__init__.py new file mode 100644 index 00000000..73326e9e --- /dev/null +++ b/mediagoblin/plugins/piwigo/__init__.py @@ -0,0 +1,37 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from mediagoblin.tools import pluginapi + +_log = logging.getLogger(__name__) + + +def setup_plugin(): + _log.info('Setting up piwigo...') + + routes = [ + ('mediagoblin.plugins.piwigo.wsphp', + '/api/piwigo/ws.php', + 'mediagoblin.plugins.piwigo.views:ws_php'), + ] + + pluginapi.register_routes(routes) + +hooks = { + 'setup': setup_plugin +} diff --git a/mediagoblin/plugins/piwigo/forms.py b/mediagoblin/plugins/piwigo/forms.py new file mode 100644 index 00000000..5bb12e62 --- /dev/null +++ b/mediagoblin/plugins/piwigo/forms.py @@ -0,0 +1,28 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import wtforms + + +class AddSimpleForm(wtforms.Form): + image = wtforms.FileField() + name = wtforms.TextField( + validators=[wtforms.validators.Length(min=0, max=500)]) + comment = wtforms.TextField() + # tags = wtforms.FieldList(wtforms.TextField()) + category = wtforms.IntegerField() + level = wtforms.IntegerField() diff --git a/mediagoblin/plugins/piwigo/tools.py b/mediagoblin/plugins/piwigo/tools.py new file mode 100644 index 00000000..85d77310 --- /dev/null +++ b/mediagoblin/plugins/piwigo/tools.py @@ -0,0 +1,107 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +import lxml.etree as ET +from werkzeug.exceptions import MethodNotAllowed + +from mediagoblin.tools.response import Response + + +_log = logging.getLogger(__name__) + + +class PwgNamedArray(list): + def __init__(self, l, item_name, as_attrib=()): + self.item_name = item_name + self.as_attrib = as_attrib + list.__init__(self, l) + + def fill_element_xml(self, el): + for it in self: + n = ET.SubElement(el, self.item_name) + if isinstance(it, dict): + _fill_element_dict(n, it, self.as_attrib) + else: + _fill_element(n, it) + + +def _fill_element_dict(el, data, as_attr=()): + for k, v in data.iteritems(): + if k in as_attr: + if not isinstance(v, basestring): + v = str(v) + el.set(k, v) + else: + n = ET.SubElement(el, k) + _fill_element(n, v) + + +def _fill_element(el, data): + if isinstance(data, bool): + if data: + el.text = "1" + else: + el.text = "0" + elif isinstance(data, basestring): + el.text = data + elif isinstance(data, int): + el.text = str(data) + elif isinstance(data, dict): + _fill_element_dict(el, data) + elif isinstance(data, PwgNamedArray): + data.fill_element_xml(el) + else: + _log.warn("Can't convert to xml: %r", data) + + +def response_xml(result): + r = ET.Element("rsp") + r.set("stat", "ok") + _fill_element(r, result) + return Response(ET.tostring(r, encoding="utf-8", xml_declaration=True), + mimetype='text/xml') + + +class CmdTable(object): + _cmd_table = {} + + def __init__(self, cmd_name, only_post=False): + assert not cmd_name in self._cmd_table + self.cmd_name = cmd_name + self.only_post = only_post + + def __call__(self, to_be_wrapped): + assert not self.cmd_name in self._cmd_table + self._cmd_table[self.cmd_name] = (to_be_wrapped, self.only_post) + return to_be_wrapped + + @classmethod + def find_func(cls, request): + if request.method == "GET": + cmd_name = request.args.get("method") + else: + cmd_name = request.form.get("method") + entry = cls._cmd_table.get(cmd_name) + if not entry: + return entry + _log.debug("Found method %s", cmd_name) + func, only_post = entry + if only_post and request.method != "POST": + _log.warn("Method %s only allowed for POST", cmd_name) + raise MethodNotAllowed() + return func diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py new file mode 100644 index 00000000..3dee09cd --- /dev/null +++ b/mediagoblin/plugins/piwigo/views.py @@ -0,0 +1,167 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import re + +from werkzeug.exceptions import MethodNotAllowed, BadRequest +from werkzeug.wrappers import BaseResponse + +from mediagoblin import mg_globals +from mediagoblin.meddleware.csrf import csrf_exempt +from mediagoblin.tools.response import render_404 +from .tools import CmdTable, PwgNamedArray, response_xml +from .forms import AddSimpleForm + + +_log = logging.getLogger(__name__) + + +@CmdTable("pwg.session.login", True) +def pwg_login(request): + username = request.form.get("username") + password = request.form.get("password") + _log.info("Login for %r/%r...", username, password) + return True + + +@CmdTable("pwg.session.logout") +def pwg_logout(request): + _log.info("Logout") + return True + + +@CmdTable("pwg.getVersion") +def pwg_getversion(request): + return "2.5.0 (MediaGoblin)" + + +@CmdTable("pwg.session.getStatus") +def pwg_session_getStatus(request): + return {'username': "fake_user"} + + +@CmdTable("pwg.categories.getList") +def pwg_categories_getList(request): + catlist = ({'id': -29711, + 'uppercats': "-29711", + 'name': "All my images"},) + return { + 'categories': PwgNamedArray( + catlist, + 'category', + ( + 'id', + 'url', + 'nb_images', + 'total_nb_images', + 'nb_categories', + 'date_last', + 'max_date_last', + ) + ) + } + + +@CmdTable("pwg.images.exist") +def pwg_images_exist(request): + return {} + + +@CmdTable("pwg.images.addSimple", True) +def pwg_images_addSimple(request): + form = AddSimpleForm(request.form) + if not form.validate(): + _log.error("addSimple: form failed") + raise BadRequest() + dump = [] + for f in form: + dump.append("%s=%r" % (f.name, f.data)) + _log.info("addimple: %r %s %r", request.form, " ".join(dump), request.files) + + return {'image_id': 123456, 'url': ''} + + +md5sum_matcher = re.compile(r"^[0-9a-fA-F]{32}$") + +def fetch_md5(request, parm_name, optional_parm=False): + val = request.form.get(parm_name) + if (val is None) and (not optional_parm): + _log.error("Parameter %s missing", parm_name) + raise BadRequest("Parameter %s missing" % parm_name) + if not md5sum_matcher.match(val): + _log.error("Parameter %s=%r has no valid md5 value", parm_name, val) + raise BadRequest("Parameter %s is not md5" % parm_name) + return val + + +@CmdTable("pwg.images.addChunk", True) +def pwg_images_addChunk(request): + o_sum = fetch_md5(request, 'original_sum') + typ = request.form.get('type') + pos = request.form.get('position') + data = request.form.get('data') + + # Validate params: + pos = int(pos) + if not typ in ("file", "thumb"): + _log.error("type %r not allowed for now", typ) + return False + + _log.info("addChunk for %r, type %r, position %d, len: %d", + o_sum, typ, pos, len(data)) + if typ == "thumb": + _log.info("addChunk: Ignoring thumb, because we create our own") + return True + + return True + + +def possibly_add_cookie(request, response): + # TODO: We should only add a *real* cookie, if + # authenticated. And if there is no cookie already. + if True: + response.set_cookie( + 'pwg_id', + "some_fake_for_now", + path=request.environ['SCRIPT_NAME'], + domain=mg_globals.app_config.get('csrf_cookie_domain'), + secure=(request.scheme.lower() == 'https'), + httponly=True) + + +@csrf_exempt +def ws_php(request): + if request.method not in ("GET", "POST"): + _log.error("Method %r not supported", request.method) + raise MethodNotAllowed() + + func = CmdTable.find_func(request) + if not func: + _log.warn("wsphp: Unhandled %s %r %r", request.method, + request.args, request.form) + return render_404(request) + + result = func(request) + + if isinstance(result, BaseResponse): + return result + + response = response_xml(result) + + possibly_add_cookie(request, response) + + return response |