aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorroot <root@fucktheforce.de>2017-05-03 09:58:22 +0200
committerroot <root@fucktheforce.de>2017-05-03 09:58:22 +0200
commit732fc99a23364a525c428c279173abd460065488 (patch)
treebd23c6e8d73338a51e8f167df51e0a74f52366d9
parent9827ec6d450b71ade7c19740b46274fff6fbc682 (diff)
parentbcfe8b2d97520e699377b41a1e024185fc7813dc (diff)
Merge branch 'master' of http://git.fucktheforce.de/cgi-bin/git/httpupload
-rw-r--r--gajim-plugin/httpupload/__init__.py2
-rw-r--r--gajim-plugin/httpupload/httpupload.pngbin0 -> 780 bytes
-rw-r--r--gajim-plugin/httpupload/httpupload.py702
-rw-r--r--gajim-plugin/httpupload/image.pngbin0 -> 666 bytes
-rw-r--r--gajim-plugin/httpupload/manifest.ini16
-rw-r--r--gajim-plugin/httpupload/upload_progress_dialog.ui105
-rw-r--r--prosody-module/mod_http_upload_external/mod_http_upload_external.lua59
-rw-r--r--storage-backend/index.php100
-rw-r--r--storage-backend/lib/functions.common.inc.php18
-rw-r--r--storage-backend/lib/functions.filetransfer.inc.php23
-rw-r--r--storage-backend/lib/functions.http.inc.php64
11 files changed, 981 insertions, 108 deletions
diff --git a/gajim-plugin/httpupload/__init__.py b/gajim-plugin/httpupload/__init__.py
new file mode 100644
index 0000000..23c0eff
--- /dev/null
+++ b/gajim-plugin/httpupload/__init__.py
@@ -0,0 +1,2 @@
+# simple redirect
+from httpupload import HttpuploadPlugin
diff --git a/gajim-plugin/httpupload/httpupload.png b/gajim-plugin/httpupload/httpupload.png
new file mode 100644
index 0000000..c3b3733
--- /dev/null
+++ b/gajim-plugin/httpupload/httpupload.png
Binary files differ
diff --git a/gajim-plugin/httpupload/httpupload.py b/gajim-plugin/httpupload/httpupload.py
new file mode 100644
index 0000000..b2bb3fc
--- /dev/null
+++ b/gajim-plugin/httpupload/httpupload.py
@@ -0,0 +1,702 @@
+# -*- coding: utf-8 -*-
+##
+## This file is part of Gajim.
+##
+## Gajim is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## Gajim 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 General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+##
+
+from common import demandimport
+demandimport.enable()
+demandimport.ignore += ['builtins', '__builtin__', 'PIL', '_imp']
+
+import gtk
+import gobject
+import os
+import time
+import base64
+import tempfile
+import urllib2
+import mimetypes # better use the magic packet, but that's not a standard lib
+import gtkgui_helpers
+from Queue import Queue
+try:
+ from PIL import Image
+ pil_available = True
+except:
+ pil_available = False
+from io import BytesIO
+
+import binascii
+from common import gajim
+from common import ged
+import chat_control
+from plugins import GajimPlugin
+from plugins.helpers import log_calls
+import logging
+from dialogs import FileChooserDialog, ImageChooserDialog, ErrorDialog
+import nbxmpp
+
+log = logging.getLogger('gajim.plugin_system.httpupload')
+
+try:
+ if os.name == 'nt':
+ from cryptography.hazmat.backends.openssl import backend
+ else:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import Cipher
+ from cryptography.hazmat.primitives.ciphers import algorithms
+ from cryptography.hazmat.primitives.ciphers.modes import GCM
+ encryption_available = True
+except Exception as e:
+ DEP_MSG = 'For encryption of files, ' \
+ 'please install python-cryptography!'
+ log.debug('Cryptography Import Error: ' + str(e))
+ log.info('Decryption/Encryption disabled due to errors')
+ encryption_available = False
+
+NS_HINTS = 'urn:xmpp:hints'
+# XEP-0363 (http://xmpp.org/extensions/xep-0363.html)
+NS_HTTPUPLOAD = 'urn:xmpp:http:upload'
+TAGSIZE = 16
+
+jid_to_servers = {}
+iq_ids_to_callbacks = {}
+last_info_query = {}
+max_thumbnail_size = 2048
+max_thumbnail_dimension = 160
+httpuploadurls = {}
+
+
+class HttpuploadPlugin(GajimPlugin):
+
+ @log_calls('HttpuploadPlugin')
+ def init(self):
+ if not encryption_available:
+ self.available_text = DEP_MSG
+ self.config_dialog = None # HttpuploadPluginConfigDialog(self)
+ self.controls = []
+ self.events_handlers = {}
+ self.events_handlers['agent-info-received'] = (ged.PRECORE,
+ self.handle_agent_info_received)
+ self.events_handlers['raw-iq-received'] = (ged.PRECORE,
+ self.handle_iq_received)
+ self.events_handlers['stanza-message-outgoing'] = (ged.PRECORE,
+ self.handle_message_stanza_out)
+ self.gui_extension_points = {
+ 'chat_control_base': (self.connect_with_chat_control,
+ self.disconnect_from_chat_control),
+ 'chat_control_base_update_toolbar': (self.update_button_state,
+ None)}
+ self.first_run = True
+
+ def handle_iq_received(self, event):
+ global iq_ids_to_callbacks
+ id_ = event.stanza.getAttr("id")
+ if str(id_) in iq_ids_to_callbacks:
+ try:
+ iq_ids_to_callbacks[str(id_)](event.stanza)
+ except:
+ raise
+ finally:
+ del iq_ids_to_callbacks[str(id_)]
+
+ def handle_agent_info_received(self, event):
+ global jid_to_servers
+ if NS_HTTPUPLOAD in event.features and gajim.jid_is_transport(event.jid):
+ own_jid = gajim.get_jid_without_resource(str(event.stanza.getTo()))
+ jid_to_servers[own_jid] = event.jid # map own jid to upload component's jid
+ log.info(own_jid + " can do http uploads via component " + event.jid)
+ # update all buttons
+ for base in self.controls:
+ self.update_button_state(base.chat_control)
+
+ @log_calls('HttpuploadPlugin')
+ def handle_message_stanza_out(self, event):
+ try:
+ global httpuploadurls
+ if not event.msg_iq.getTag('body'):
+ return
+ url = event.msg_iq.getBody()
+ if url in httpuploadurls:
+ # httpupload Hint
+ event.msg_iq.addChild('httpupload', namespace=NS_HINTS)
+ del httpuploadurls[url]
+ except Exception as e:
+ log.error(e)
+
+ @log_calls('HttpuploadPlugin')
+ def connect_with_chat_control(self, control):
+ self.chat_control = control
+ base = Base(self, self.chat_control)
+ self.controls.append(base)
+ if self.first_run:
+ # ALT + U
+ gtk.binding_entry_add_signal(control.msg_textview,
+ gtk.keysyms.u, gtk.gdk.MOD1_MASK, 'mykeypress',
+ int, gtk.keysyms.u, gtk.gdk.ModifierType, gtk.gdk.MOD1_MASK)
+ self.first_run = False
+ self.update_button_state(self.chat_control)
+
+ @log_calls('HttpuploadPlugin')
+ def disconnect_from_chat_control(self, chat_control):
+ for control in self.controls:
+ control.disconnect_from_chat_control()
+ self.controls = []
+
+ @log_calls('HttpuploadPlugin')
+ def update_button_state(self, chat_control):
+ global jid_to_servers
+ global iq_ids_to_callbacks
+ global last_info_query
+
+ if gajim.connections[chat_control.account].connection == None and \
+ gajim.get_jid_from_account(chat_control.account) in jid_to_servers:
+ # maybe don't delete this and detect vanished upload components when actually trying to upload something
+ log.info("Deleting %s from jid_to_servers (disconnected)" % gajim.get_jid_from_account(chat_control.account))
+ del jid_to_servers[gajim.get_jid_from_account(chat_control.account)]
+ #pass
+
+ # query info at most every 60 seconds in case something goes wrong
+ if (not chat_control.account in last_info_query or \
+ last_info_query[chat_control.account] + 60 < time.time()) and \
+ not gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \
+ gajim.account_is_connected(chat_control.account):
+ log.info("Account %s: Using dicovery to find jid of httpupload component" % chat_control.account)
+ id_ = gajim.get_an_id()
+ iq = nbxmpp.Iq(
+ typ='get',
+ to=gajim.get_server_from_jid(gajim.get_jid_from_account(chat_control.account)),
+ queryNS="http://jabber.org/protocol/disco#items"
+ )
+ iq.setID(id_)
+ def query_info(stanza):
+ global last_info_query
+ for item in stanza.getTag("query").getTags("item"):
+ id_ = gajim.get_an_id()
+ iq = nbxmpp.Iq(
+ typ='get',
+ to=item.getAttr("jid"),
+ queryNS="http://jabber.org/protocol/disco#info"
+ )
+ iq.setID(id_)
+ last_info_query[chat_control.account] = time.time()
+ gajim.connections[chat_control.account].connection.send(iq)
+ iq_ids_to_callbacks[str(id_)] = query_info
+ gajim.connections[chat_control.account].connection.send(iq)
+ #send disco query to main server jid
+ id_ = gajim.get_an_id()
+ iq = nbxmpp.Iq(
+ typ='get',
+ to=gajim.get_server_from_jid(gajim.get_jid_from_account(chat_control.account)),
+ queryNS="http://jabber.org/protocol/disco#info"
+ )
+ iq.setID(id_)
+ last_info_query[chat_control.account] = time.time()
+ gajim.connections[chat_control.account].connection.send(iq)
+
+ for base in self.controls:
+ if base.chat_control == chat_control:
+ is_supported = gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \
+ gajim.connections[chat_control.account].connection != None
+ log.info("Account %s: httpupload is_supported: %s" % (str(chat_control.account), str(is_supported)))
+ if not is_supported:
+ text = _('Your server does not support http uploads')
+ image_text = text
+ else:
+ text = _('Send file via http upload')
+ image_text = _('Send image via http upload')
+ base.button.set_sensitive(is_supported)
+ base.button.set_tooltip_text(text)
+ base.image_button.set_sensitive(is_supported)
+ base.image_button.set_tooltip_text(image_text)
+
+
+class Base(object):
+ def __init__(self, plugin, chat_control):
+ self.dlg = None
+ self.dialog_type = 'file'
+ self.keypress_id = chat_control.msg_textview.connect('mykeypress',
+ self.on_key_press)
+ self.plugin = plugin
+ self.encrypted_upload = False
+ self.chat_control = chat_control
+ actions_hbox = chat_control.xml.get_object('actions_hbox')
+ self.button = gtk.Button(label=None, stock=None, use_underline=True)
+ self.button.set_property('relief', gtk.RELIEF_NONE)
+ self.button.set_property('can-focus', False)
+ self.button.set_sensitive(False)
+ img = gtk.Image()
+ img.set_from_file(self.plugin.local_file_path('httpupload.png'))
+ self.button.set_image(img)
+ self.button.set_tooltip_text(_('Your server does not support http uploads'))
+ self.image_button = gtk.Button(label=None, stock=None, use_underline=True)
+ self.image_button.set_property('relief', gtk.RELIEF_NONE)
+ self.image_button.set_property('can-focus', False)
+ self.image_button.set_sensitive(False)
+ img = gtk.Image()
+ img.set_from_file(self.plugin.local_file_path('image.png'))
+ self.image_button.set_image(img)
+ self.image_button.set_tooltip_text(_('Your server does not support http uploads'))
+ send_button = chat_control.xml.get_object('send_button')
+ send_button_pos = actions_hbox.child_get_property(send_button,
+ 'position')
+ actions_hbox.add_with_properties(self.button, 'position',
+ send_button_pos - 2, 'expand', False)
+
+ actions_hbox.add_with_properties(self.image_button, 'position',
+ send_button_pos - 1, 'expand', False)
+
+ file_id = self.button.connect('clicked', self.on_file_button_clicked)
+ image_id = self.image_button.connect('clicked', self.on_image_button_clicked)
+ chat_control.handlers[file_id] = self.button
+ chat_control.handlers[image_id] = self.image_button
+ chat_control.handlers[self.keypress_id] = chat_control.msg_textview
+ self.button.show()
+ self.image_button.show()
+
+ def on_key_press(self, widget, event_keyval, event_keymod):
+ # construct event instance from binding
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0 # assign current time
+
+ if event.keyval != gtk.keysyms.u:
+ return
+ if event.state != gtk.gdk.MOD1_MASK: # ALT+u
+ return
+ is_supported = gajim.get_jid_from_account(self.chat_control.account) in jid_to_servers and \
+ gajim.connections[self.chat_control.account].connection != None
+ if not is_supported:
+ from dialogs import WarningDialog
+ WarningDialog('Warning', _('Your server does not support http uploads'),
+ transient_for=self.chat_control.parent_win.window)
+ return
+ self.on_file_button_clicked(widget)
+
+ def disconnect_from_chat_control(self):
+ actions_hbox = self.chat_control.xml.get_object('actions_hbox')
+ actions_hbox.remove(self.button)
+ actions_hbox.remove(self.image_button)
+ if self.keypress_id in self.chat_control.handlers and \
+ self.chat_control.handlers[self.keypress_id].handler_is_connected(self.keypress_id):
+ self.chat_control.handlers[self.keypress_id].disconnect(self.keypress_id)
+ del self.chat_control.handlers[self.keypress_id]
+
+ def encryption_activated(self):
+ if not encryption_available:
+ return False
+ jid = self.chat_control.contact.jid
+ account = self.chat_control.account
+ for plugin in gajim.plugin_manager.active_plugins:
+ if type(plugin).__name__ == 'OmemoPlugin':
+ omemo = plugin
+ break
+ if omemo:
+ state = omemo.get_omemo_state(account)
+ log.info('Encryption is: ' +
+ str(state.encryption.is_active(jid)))
+ return state.encryption.is_active(jid)
+ log.info('Encryption is: False / OMEMO not found')
+ return False
+
+ def on_file_dialog_ok(self, widget, path_to_file=None):
+ global jid_to_servers
+
+ try:
+ self.encrypted_upload = self.encryption_activated()
+ except Exception as e:
+ log.debug(e)
+ self.encrypted_upload = False
+
+ if not path_to_file:
+ path_to_file = self.dlg.get_filename()
+ if not path_to_file:
+ self.dlg.destroy()
+ return
+ path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
+ (path_to_file,))[0]
+ self.dlg.destroy()
+ if not os.path.exists(path_to_file):
+ return
+ if self.encrypted_upload:
+ filesize = os.path.getsize(path_to_file) + TAGSIZE # in bytes
+ else:
+ filesize = os.path.getsize(path_to_file)
+ invalid_file = False
+ msg = ''
+ if os.path.isfile(path_to_file):
+ stat = os.stat(path_to_file)
+ if stat[6] == 0:
+ invalid_file = True
+ msg = _('File is empty')
+ else:
+ invalid_file = True
+ msg = _('File does not exist')
+ if invalid_file:
+ ErrorDialog(_('Could not open file'), msg, transient_for=self.chat_control.parent_win.window)
+ return
+
+ mime_type = mimetypes.MimeTypes().guess_type(path_to_file)[0]
+ if not mime_type:
+ mime_type = 'application/octet-stream' # fallback mime type
+ log.info("Detected MIME Type of file: " + str(mime_type))
+ progress_messages = Queue(8)
+ progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), progress_messages, self.plugin)
+ def upload_file(stanza):
+ slot = stanza.getTag("slot")
+ if not slot:
+ progress_window.close_dialog()
+ log.error("got unexpected stanza: "+str(stanza))
+ error = stanza.getTag("error")
+ if error and error.getTag("text"):
+ ErrorDialog(_('Could not request upload slot'),
+ _('Got unexpected response from server: %s') % str(error.getTagData("text")),
+ transient_for=self.chat_control.parent_win.window)
+ else:
+ ErrorDialog(_('Could not request upload slot'),
+ _('Got unexpected response from server (protocol mismatch??)'),
+ transient_for=self.chat_control.parent_win.window)
+ return
+
+ try:
+ if self.encrypted_upload:
+ key = os.urandom(32)
+ iv = os.urandom(16)
+ data = StreamFileWithProgress(path_to_file,
+ "rb",
+ progress_window.update_progress,
+ self.encrypted_upload, key, iv)
+ else:
+ data = StreamFileWithProgress(path_to_file,
+ "rb",
+ progress_window.update_progress)
+ except:
+ progress_window.close_dialog()
+ ErrorDialog(_('Could not open file'),
+ _('Exception raised while opening file (see error log for more information)'),
+ transient_for=self.chat_control.parent_win.window)
+ raise # fill error log with useful information
+
+ put = slot.getTag("put")
+ get = slot.getTag("get")
+ if not put or not get:
+ progress_window.close_dialog()
+ log.error("got unexpected stanza: " + str(stanza))
+ ErrorDialog(_('Could not request upload slot'),
+ _('Got unexpected response from server (protocol mismatch??)'),
+ transient_for=self.chat_control.parent_win.window)
+ return
+
+ def upload_complete(response_code):
+ if response_code == 0:
+ return # Upload was aborted
+ if response_code >= 200 and response_code < 300:
+ log.info("Upload completed successfully")
+ xhtml = None
+ is_image = mime_type.split('/', 1)[0] == 'image'
+ if (not isinstance(self.chat_control, chat_control.ChatControl) or not self.chat_control.gpg_is_active) and \
+ self.dialog_type == 'image' and is_image and not self.encrypted_upload:
+
+ progress_messages.put(_('Calculating (possible) image thumbnail...'))
+ thumb = None
+ quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
+ with open(path_to_file, 'rb') as content_file:
+ thumb = urllib2.quote(base64.standard_b64encode(content_file.read()), '')
+ if thumb and len(thumb) < max_thumbnail_size:
+ quality = 100
+ log.info("Image small enough (%d bytes), not resampling" % len(thumb))
+ elif pil_available:
+ log.info("PIL available, using it for image downsampling")
+ try:
+ for quality in quality_steps:
+ thumb = Image.open(path_to_file)
+ thumb.thumbnail((max_thumbnail_dimension, max_thumbnail_dimension), Image.ANTIALIAS)
+ output = BytesIO()
+ thumb.save(output, format='JPEG', quality=quality, optimize=True)
+ thumb = output.getvalue()
+ output.close()
+ thumb = urllib2.quote(base64.standard_b64encode(thumb), '')
+ log.debug("pil thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
+ if len(thumb) < max_thumbnail_size:
+ break
+ except:
+ thumb = None
+ else:
+ thumb = None
+ if not thumb:
+ log.info("PIL not available, using GTK for image downsampling")
+ temp_file = None
+ try:
+ with open(path_to_file, 'rb') as content_file:
+ thumb = content_file.read()
+ loader = gtk.gdk.PixbufLoader()
+ loader.write(thumb)
+ loader.close()
+ pixbuf = loader.get_pixbuf()
+ scaled_pb = self.get_pixbuf_of_size(pixbuf, max_thumbnail_dimension)
+ handle, temp_file = tempfile.mkstemp(suffix='.jpeg', prefix='gajim_httpupload_scaled_tmp', dir=gajim.TMP)
+ log.debug("Saving temporary jpeg image to '%s'..." % temp_file)
+ os.close(handle)
+ for quality in quality_steps:
+ scaled_pb.save(temp_file, "jpeg", {"quality": str(quality)})
+ with open(temp_file, 'rb') as content_file:
+ thumb = content_file.read()
+ thumb = urllib2.quote(base64.standard_b64encode(thumb), '')
+ log.debug("gtk thumbnail jpeg quality %d produces an image of size %d..." % (quality, len(thumb)))
+ if len(thumb) < max_thumbnail_size:
+ break
+ except:
+ thumb = None
+ finally:
+ if temp_file:
+ os.unlink(temp_file)
+ if thumb:
+ if len(thumb) > max_thumbnail_size:
+ log.info("Couldn't compress image enough, not sending any thumbnail")
+ else:
+ log.info("Using thumbnail jpeg quality %d (image size: %d bytes)" % (quality, len(thumb)))
+ xhtml = '<body><br/><a href="%s"> <img alt="%s" src="data:image/png;base64,%s"/> </a></body>' % \
+ (get.getData(), get.getData(), thumb)
+ progress_window.close_dialog()
+ id_ = gajim.get_an_id()
+ def add_oob_tag():
+ pass
+ if self.encrypted_upload:
+ keyAndIv = '#' + binascii.hexlify(iv) + binascii.hexlify(key)
+ self.chat_control.send_message(message=get.getData() + keyAndIv, xhtml=None)
+ else:
+ global httpuploadurls
+ url = get.getData()
+ httpuploadurls[url] = True
+ self.chat_control.send_message(message=url, xhtml=xhtml)
+ self.chat_control.msg_textview.grab_focus()
+ else:
+ progress_window.close_dialog()
+ log.error("got unexpected http upload response code: " + str(response_code))
+ ErrorDialog(_('Could not upload file'),
+ _('Got unexpected http response code from server: ') + str(response_code),
+ transient_for=self.chat_control.parent_win.window)
+
+ def uploader():
+ progress_messages.put(_('Uploading file via HTTP...'))
+ try:
+ headers = {'User-Agent': 'Gajim %s' % gajim.version,
+ 'Content-Type': mime_type}
+ request = urllib2.Request(put.getData().encode("utf-8"), data=data, headers=headers)
+ request.get_method = lambda: 'PUT'
+ log.debug("opening urllib2 upload request...")
+ transfer = urllib2.urlopen(request, timeout=30)
+ log.debug("urllib2 upload request done, response code: " + str(transfer.getcode()))
+ return transfer.getcode()
+ except UploadAbortedException:
+ log.info("Upload aborted")
+ except:
+ progress_window.close_dialog()
+ ErrorDialog(_('Could not upload file'),
+ _('Got unexpected exception while uploading file (see error log for more information)'),
+ transient_for=self.chat_control.parent_win.window)
+ raise # fill error log with useful information
+ return 0
+
+ log.info("Uploading file to '%s'..." % str(put.getData()))
+ log.info("Please download from '%s' later..." % str(get.getData()))
+
+ gajim.thread_interface(uploader, [], upload_complete)
+
+ is_supported = gajim.get_jid_from_account(self.chat_control.account) in jid_to_servers and \
+ gajim.connections[self.chat_control.account].connection != None
+ log.info("jid_to_servers of %s: %s ; connection: %s" % (gajim.get_jid_from_account(self.chat_control.account), str(jid_to_servers[gajim.get_jid_from_account(self.chat_control.account)]), str(gajim.connections[self.chat_control.account].connection)))
+ if not is_supported:
+ progress_window.close_dialog()
+ log.error("upload component vanished, account got disconnected??")
+ ErrorDialog(_('Your server does not support http uploads or you just got disconnected.\nPlease try to reconnect or reopen the chat window to fix this.'),
+ transient_for=self.chat_control.parent_win.window)
+ return
+
+ # create iq for slot request
+ id_ = gajim.get_an_id()
+ iq = nbxmpp.Iq(
+ typ='get',
+ to=jid_to_servers[gajim.get_jid_from_account(self.chat_control.account)],
+ queryNS=None
+ )
+ iq.setID(id_)
+ request = iq.addChild(
+ name="request",
+ namespace=NS_HTTPUPLOAD
+ )
+ filename = request.addChild(
+ name="filename",
+ )
+ filename.addData(os.path.basename(path_to_file))
+ size = request.addChild(
+ name="size",
+ )
+ size.addData(filesize)
+ content_type = request.addChild(
+ name="content-type",
+ )
+ content_type.addData(mime_type)
+
+ # send slot request and register callback
+ log.debug("sending httpupload slot request iq...")
+ iq_ids_to_callbacks[str(id_)] = upload_file
+ gajim.connections[self.chat_control.account].connection.send(iq)
+
+ self.chat_control.msg_textview.grab_focus()
+
+ def on_file_button_clicked(self, widget):
+ self.dialog_type = 'file'
+ self.dlg = FileChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None,
+ title_text = _('Choose file to send'), action = gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK),
+ default_response = gtk.RESPONSE_OK,)
+
+ def on_image_button_clicked(self, widget):
+ self.dialog_type = 'image'
+ self.dlg = ImageChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None)
+
+ def get_pixbuf_of_size(self, pixbuf, size):
+ # Creates a pixbuf that fits in the specified square of sizexsize
+ # while preserving the aspect ratio
+ # Returns scaled_pixbuf
+ image_width = pixbuf.get_width()
+ image_height = pixbuf.get_height()
+
+ if image_width > image_height:
+ if image_width > size:
+ image_height = int(size / float(image_width) * image_height)
+ image_width = int(size)
+ else:
+ if image_height > size:
+ image_width = int(size / float(image_height) * image_width)
+ image_height = int(size)
+
+ crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
+ gtk.gdk.INTERP_BILINEAR)
+ return crop_pixbuf
+
+
+class StreamFileWithProgress(file):
+ def __init__(self, path, mode, callback=None,
+ encrypted_upload=False, key=None, iv=None, *args):
+ file.__init__(self, path, mode)
+ self.encrypted_upload = encrypted_upload
+ self.seek(0, os.SEEK_END)
+ if self.encrypted_upload:
+ if os.name == 'nt':
+ self.backend = backend
+ else:
+ self.backend = default_backend()
+ self.encryptor = Cipher(
+ algorithms.AES(key),
+ GCM(iv),
+ backend=self.backend).encryptor()
+ self._total = self.tell() + TAGSIZE
+ else:
+ self._total = self.tell()
+ self.seek(0)
+ self._callback = callback
+ self._args = args
+ self._seen = 0
+
+ def __len__(self):
+ return self._total
+
+ def read(self, size):
+ if self.encrypted_upload:
+ data = file.read(self, size)
+ if len(data) > 0:
+ data = self.encryptor.update(data)
+ self._seen += len(data)
+ if (self._seen + TAGSIZE) == self._total:
+ self.encryptor.finalize()
+ data += self.encryptor.tag
+ self._seen += TAGSIZE
+ if self._callback:
+ self._callback(self._seen, self._total, *self._args)
+ return data
+ else:
+ data = file.read(self, size)
+ self._seen += len(data)
+ if self._callback:
+ self._callback(self._seen, self._total, *self._args)
+ return data
+
+
+class ProgressWindow:
+ def __init__(self, title_text, during_text, messages_queue, plugin):
+ self.plugin = plugin
+ self.xml = gtkgui_helpers.get_gtk_builder(self.plugin.local_file_path('upload_progress_dialog.ui'))
+ self.messages_queue = messages_queue
+ self.dialog = self.xml.get_object('progress_dialog')
+ self.label = self.xml.get_object('label')
+ self.cancel_button = self.xml.get_object('close_button')
+ self.label.set_markup('<big>' + during_text + '</big>')
+ self.progressbar = self.xml.get_object('progressbar')
+ self.progressbar.set_text("")
+ self.dialog.set_title(title_text)
+ self.dialog.set_geometry_hints(min_width=400, min_height=96)
+ self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+ self.dialog.show_all()
+ self.xml.connect_signals(self)
+
+ self.stopped = False
+ self.pulse_progressbar_timeout_id = gobject.timeout_add(100, self.pulse_progressbar)
+ self.process_messages_queue_timeout_id = gobject.timeout_add(100, self.process_messages_queue)
+
+
+ def pulse_progressbar(self):
+ if self.dialog:
+ self.progressbar.pulse()
+ return True # loop forever
+ return False
+
+ def process_messages_queue(self):
+ if not self.messages_queue.empty():
+ self.label.set_markup('<big>' + self.messages_queue.get() + '</big>')
+ if self.dialog:
+ return True # loop forever
+ return False
+
+ def on_progress_dialog_delete_event(self, widget, event):
+ self.stopped = True
+ if self.pulse_progressbar_timeout_id:
+ gobject.source_remove(self.pulse_progressbar_timeout_id)
+ gobject.source_remove(self.process_messages_queue_timeout_id)
+
+ def on_cancel(self, widget):
+ self.stopped = True
+ if self.pulse_progressbar_timeout_id:
+ gobject.source_remove(self.pulse_progressbar_timeout_id)
+ gobject.source_remove(self.process_messages_queue_timeout_id)
+ self.dialog.destroy()
+
+ def update_progress(self, seen, total):
+ if self.stopped == True:
+ raise UploadAbortedException
+ if self.pulse_progressbar_timeout_id:
+ gobject.source_remove(self.pulse_progressbar_timeout_id)
+ self.pulse_progressbar_timeout_id = None
+ pct = (float(seen) / total) * 100.0
+ self.progressbar.set_fraction(float(seen) / total)
+ self.progressbar.set_text(str(int(pct)) + "%")
+ log.debug('upload progress: %.2f%% (%d of %d bytes)' % (pct, seen, total))
+
+ def close_dialog(self):
+ self.on_cancel(None)
+
+class UploadAbortedException(Exception):
+ def __str__(self):
+ return "Upload Aborted"
diff --git a/gajim-plugin/httpupload/image.png b/gajim-plugin/httpupload/image.png
new file mode 100644
index 0000000..32dda52
--- /dev/null
+++ b/gajim-plugin/httpupload/image.png
Binary files differ
diff --git a/gajim-plugin/httpupload/manifest.ini b/gajim-plugin/httpupload/manifest.ini
new file mode 100644
index 0000000..fa21628
--- /dev/null
+++ b/gajim-plugin/httpupload/manifest.ini
@@ -0,0 +1,16 @@
+[info]
+name: HttpUpload
+short_name: httpupload
+version: 0.4.1-thedevstack
+description: This plugin is designed to send a file to a contact or muc by using httpupload.<br/>
+ Your server must support <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP Upload</a>.<br/>
+ Conversations supported this.<br/>
+ If the receiving side supports <a href="http://xmpp.org/extensions/xep-0071.html">XEP-0071: XHTML-IM</a>
+ and maintains the scheme data: URI, a thumbnail image is send along the link to the full size image.
+ If the receiving side doesn't support this, only a text message containing the link to the image is send.
+ This plugin adds additionally to this a hint for processing: <httpupload xmlns="urn:xmpp:hints"/>
+authors: Thilo Molitor <thilo@eightysoft.de>
+ Philipp Hörist <philipp@hoerist.com>
+homepage: https://trac-plugins.gajim.org/wiki/HttpUploadPlugin
+min_gajim_version: 0.16.5
+max_gajim_version: 0.16.9
diff --git a/gajim-plugin/httpupload/upload_progress_dialog.ui b/gajim-plugin/httpupload/upload_progress_dialog.ui
new file mode 100644
index 0000000..b50c3c2
--- /dev/null
+++ b/gajim-plugin/httpupload/upload_progress_dialog.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <object class="GtkDialog" id="progress_dialog">
+ <property name="can_focus">True</property>
+ <property name="resizable">False</property>
+ <property name="icon_name">upload-media</property>
+ <property name="type_hint">dialog</property>
+ <signal name="delete-event" handler="on_progress_dialog_delete_event" swapped="no"/>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">4</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">3</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_cancel" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">8</property>
+ <property name="bottom_padding">4</property>
+ <property name="left_padding">8</property>
+ <property name="right_padding">8</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">4</property>
+ <property name="bottom_padding">4</property>
+ <property name="left_padding">8</property>
+ <property name="right_padding">8</property>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pulse_step">0.10000000149</property>
+ <property name="show_text">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/prosody-module/mod_http_upload_external/mod_http_upload_external.lua b/prosody-module/mod_http_upload_external/mod_http_upload_external.lua
index 11175ee..a9b4551 100644
--- a/prosody-module/mod_http_upload_external/mod_http_upload_external.lua
+++ b/prosody-module/mod_http_upload_external/mod_http_upload_external.lua
@@ -12,11 +12,13 @@
-- configuration
local external_url = module:get_option("http_upload_external_url");
local xmpp_server_key = module:get_option("http_upload_external_server_key");
+local filetransfer_manager_ui_url = module:get_option("filetransfer_manager_ui_url");
-- imports
local st = require"util.stanza";
local http = require"socket.http";
local json = require"util.json";
+local dataform = require "util.dataforms".new;
-- depends
module:depends("disco");
@@ -26,6 +28,12 @@ local xmlns_http_upload = "urn:xmpp:filetransfer:http";
module:add_feature(xmlns_http_upload);
+-- add additional disco info to advertise managing UI
+module:add_extension(dataform {
+ { name = "FORM_TYPE", type = "hidden", value = xmlns_http_upload },
+ { name = "filetransfer-manager-ui-url", type = "text-single" },
+}:form({ ["filetransfer-manager-ui-url"] = filetransfer_manager_ui_url }, "result"));
+
-- hooks
module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
local stanza, origin = event.stanza, event.origin;
@@ -43,7 +51,12 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
return true;
end
local slot_type = request.attr.type;
- module:log("debug", "incoming request is of type " .. slot_type);
+ if slot_type then
+ module:log("debug", "incoming request is of type " .. slot_type);
+ else
+ module:log("debug", "incoming request has no type - using default type 'upload'");
+ end
+
if not slot_type or slot_type == "upload" then
-- validate
local filename = request:get_child_text("filename");
@@ -67,7 +80,10 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
-- the request
local respbody, statuscode = http.request(external_url, reqbody);
- respbody = string.gsub(respbody, "\\/", "/")
+ -- respbody is nil in case the server is not reachable
+ if respbody ~= nil then
+ respbody = string.gsub(respbody, "\\/", "/");
+ end
local get_url = nil;
local put_url = nil;
@@ -87,19 +103,19 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg));
return true;
elseif errobj.err_code == 2 then
- origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg,
- st.stanza("file-too-large", {xmlns=xmlns_http_upload})
- :tag("max-size"):text(errobj.parameters.max_file_size)));
+ origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg)
+ :tag("file-too-large", {xmlns=xmlns_http_upload})
+ :tag("max-size"):text(errobj.parameters.max_file_size));
return true;
elseif errobj.err_code == 3 then
- origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg,
- st.stanza("invalid-character", {xmlns=xmlns_http_upload})
- :text(errobj.parameters.invalid_character)));
+ origin.send(st.error_reply(stanza, "modify", "not-acceptable", errobj.msg)
+ :tag("invalid-character", {xmlns=xmlns_http_upload})
+ :text(errobj.parameters.invalid_character));
return true;
elseif errobj.err_code == 4 then
- origin.send(st.error_reply(stanza, "cancel", "internal-server-error", errobj.msg,
- st.stanza("missing-parameter", {xmlns=xmlns_http_upload})
- :text(errobj.parameters.missing_parameter)));
+ origin.send(st.error_reply(stanza, "cancel", "internal-server-error", errobj.msg)
+ :tag("missing-parameter", {xmlns=xmlns_http_upload})
+ :text(errobj.parameters.missing_parameter));
return true;
else
origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "unknown err_code"));
@@ -127,9 +143,12 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
return true;
end
end
- else
+ elseif respbody ~= nil then
origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "status code: " .. statuscode .. " response: " ..respbody));
return true;
+ else
+ -- http file service not reachable
+ origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "status code: " .. statuscode));
end
local reply = st.reply(stanza);
@@ -148,7 +167,10 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
local reqbody = "xmpp_server_key=" .. xmpp_server_key .. "&slot_type=delete&file_url=" .. fileurl .. "&user_jid=" .. orig_from;
-- the request
local respbody, statuscode = http.request(external_url, reqbody);
- respbody = string.gsub(respbody, "\\/", "/")
+ -- respbody is nil in case the server is not reachable
+ if respbody ~= nil then
+ respbody = string.gsub(respbody, "\\/", "/");
+ end
local delete_token = nil;
-- check the response
@@ -163,9 +185,9 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
else
if errobj["err_code"] ~= nil and errobj["msg"] ~= nil then
if errobj.err_code == 4 then
- origin.send(st.error_reply(stanza, "cancel", "internal-server-error", errobj.msg,
- st.stanza("missing-parameter", {xmlns=xmlns_http_upload})
- :text(errobj.parameters.missing_parameter)));
+ origin.send(st.error_reply(stanza, "cancel", "internal-server-error", errobj.msg)
+ :tag("missing-parameter", {xmlns=xmlns_http_upload})
+ :text(errobj.parameters.missing_parameter));
return true;
else
origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "unknown err_code"));
@@ -192,9 +214,12 @@ module:hook("iq/host/"..xmlns_http_upload..":request", function (event)
return true;
end
end
- else
+ elseif respbody ~= nil then
origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "status code: " .. statuscode .. " response: " ..respbody));
return true;
+ else
+ -- http file service not reachable
+ origin.send(st.error_reply(stanza, "cancel", "undefined-condition", "status code: " .. statuscode));
end
local reply = st.reply(stanza);
diff --git a/storage-backend/index.php b/storage-backend/index.php
index a7c79d3..d153e63 100644
--- a/storage-backend/index.php
+++ b/storage-backend/index.php
@@ -11,6 +11,7 @@
* size
* content_type
* user_jid
+ * receipient_jid
* 403: In case the XMPP Server Key is not valid
* 406:
* File is empty (error code: 1)
@@ -46,7 +47,9 @@
* The slot's delete token does not match the header field "X-FILETRANSFER-HTTP-DELETE-TOKEN"
* The slot's delete token is not valid any more
*/
-
+include_once(__DIR__.'/lib/functions.common.inc.php');
+include_once(__DIR__.'/lib/functions.http.inc.php');
+include_once(__DIR__.'/lib/functions.filetransfer.inc.php');
$method = $_SERVER['REQUEST_METHOD'];
// Load configuration
@@ -101,6 +104,7 @@ switch ($method) {
$filename = rawurlencode(getMandatoryPostParameter('filename'));
$filesize = getMandatoryPostParameter('size');
$mimeType = getOptionalPostParameter('content_type');
+ $receipientJid = getMandatoryPostParameter('receipient_jid');
// check file name - return 406 (not acceptable) if file contains invalid characters
foreach ($config['invalid_characters_in_filename'] as $invalidCharacter) {
@@ -118,7 +122,7 @@ switch ($method) {
}
// generate slot uuid, register slot uuid and expected file size and expected mime type
$slotUUID = generate_uuid();
- registerSlot($slotUUID, $filename, $filesize, $mimeType, $userJid, $config);
+ registerSlot($slotUUID, $filename, $filesize, $mimeType, $userJid, $receipientJid, $config);
if (!mkdir(getUploadFilePath($slotUUID, $config))) {
sendHttpReturnCodeAndJson(500, "Could not create directory for upload.");
}
@@ -217,13 +221,6 @@ function checkFilenameParameter($filename, $slotParameters) {
return $slotParameters['filename'] == $filename;
}
-function loadSlotParameters($slotUUID, $config) {
- $slotParameters = require(getSlotFilePath($slotUUID, $config));
- $slotParameters['filename'] = $slotParameters['filename'];
-
- return $slotParameters;
-}
-
function getMandatoryPostParameter($parameterName) {
$parameter = $_POST[$parameterName];
if (!isset($parameter) || is_null($parameter) || empty($parameter)) {
@@ -232,26 +229,6 @@ function getMandatoryPostParameter($parameterName) {
return $parameter;
}
-function getOptionalPostParameter($parameterName, $default = NULL) {
- $parameter = $_POST[$parameterName];
- if (!isset($parameter) || is_null($parameter) || empty($parameter)) {
- $parameter = $default;
- }
- return $parameter;
-}
-
-function sendHttpReturnCodeAndJson($code, $data) {
- if (!is_array($data)) {
- $data = ['msg' => $data];
- }
- sendHttpReturnCodeAndMessage($code, json_encode($data));
-}
-
-function sendHttpReturnCodeAndMessage($code, $text = '') {
- http_response_code($code);
- exit($text);
-}
-
function getUUIDFromUri($uri) {
$pattern = "/[a-f0-9]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/";
preg_match($pattern, $uri, $matches);
@@ -263,10 +240,11 @@ function getFilenameFromUri($uri) {
return substr($uri, $lastSlash);
}
-function registerSlot($slotUUID, $filename, $filesize, $contentType, $userJid, $config) {
+function registerSlot($slotUUID, $filename, $filesize, $contentType, $userJid, $receipientJid, $config) {
$contents = "<?php\n/*\n * This is an autogenerated file - do not edit\n */\n\n";
$contents .= 'return [\'filename\' => \''.$filename.'\', \'filesize\' => \''.$filesize.'\', ';
- $contents .= '\'content_type\' => \''.$contentType.'\', \'user_jid\' => \''.$userJid.'\'];\n?>';
+ $contents .= '\'content_type\' => \''.$contentType.'\', \'user_jid\' => \''.$userJid.'\', \'receipient_jid\' => \''.$receipientJid.'\'];';
+ $contents .= "\n?>";
if (!file_put_contents(getSlotFilePath($slotUUID, $config), $contents)) {
sendHttpReturnCodeAndMessage(500, "Could not create slot registry entry.");
}
@@ -285,64 +263,4 @@ function registerDeleteToken($slotUUID, $filename, $deleteToken, $config) {
function slotExists($slotUUID, $config) {
return file_exists(getSlotFilePath($slotUUID, $config));
}
-
-function getSlotFilePath($slotUUID, $config) {
- return $config['slot_registry_dir'].$slotUUID;
-}
-
-function getUploadFilePath($slotUUID, $config, $filename = NULL) {
- $path = $config['storage_base_path'].$slotUUID;
- if (!is_null($filename)) {
- $path .= '/'.$filename;
- }
- return $path;
-}
-
-/**
- * Inspired by https://github.com/owncloud/core/blob/master/lib/private/appframework/http/request.php#L523
- */
-function getServerProtocol() {
- if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
- if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
- $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_PROTO']);
- $proto = strtolower(trim($parts[0]));
- } else {
- $proto = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
- }
- // Verify that the protocol is always HTTP or HTTPS
- // default to http if an invalid value is provided
- return $proto === 'https' ? 'https' : 'http';
- }
- if (isset($_SERVER['HTTPS'])
- && $_SERVER['HTTPS'] !== null
- && $_SERVER['HTTPS'] !== 'off'
- && $_SERVER['HTTPS'] !== '') {
- return 'https';
- }
- return 'http';
-}
-
-function getRequestHostname() {
- if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
- return strtolower($_SERVER['HTTP_X_FORWARDED_HOST']);
- }
- return strtolower($_SERVER['HTTP_HOST']);
-}
-
-function getRequestUriWithoutFilename() {
- return strtolower(substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '/') + 1));
-}
-
-/**
- * Copied from http://rogerstringer.com/2013/11/15/generate-uuids-php/
- */
-function generate_uuid() {
- return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
- mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
- mt_rand( 0, 0xffff ),
- mt_rand( 0, 0x0fff ) | 0x4000,
- mt_rand( 0, 0x3fff ) | 0x8000,
- mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
- );
-}
?>
diff --git a/storage-backend/lib/functions.common.inc.php b/storage-backend/lib/functions.common.inc.php
new file mode 100644
index 0000000..b47268e
--- /dev/null
+++ b/storage-backend/lib/functions.common.inc.php
@@ -0,0 +1,18 @@
+<?php
+/*
+ * This file contains functions commonly used.
+ */
+
+/**
+ * Copied from http://rogerstringer.com/2013/11/15/generate-uuids-php/
+ */
+function generate_uuid() {
+ return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+ mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
+ mt_rand( 0, 0xffff ),
+ mt_rand( 0, 0x0fff ) | 0x4000,
+ mt_rand( 0, 0x3fff ) | 0x8000,
+ mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
+ );
+}
+?> \ No newline at end of file
diff --git a/storage-backend/lib/functions.filetransfer.inc.php b/storage-backend/lib/functions.filetransfer.inc.php
new file mode 100644
index 0000000..679cef1
--- /dev/null
+++ b/storage-backend/lib/functions.filetransfer.inc.php
@@ -0,0 +1,23 @@
+<?php
+/*
+ * This file contains the functions for the storage-backend.
+ */
+
+function getSlotFilePath($slotUUID, $config) {
+ return $config['slot_registry_dir'].$slotUUID;
+}
+
+function getUploadFilePath($slotUUID, $config, $filename = NULL) {
+ $path = $config['storage_base_path'].$slotUUID;
+ if (!is_null($filename)) {
+ $path .= '/'.$filename;
+ }
+ return $path;
+}
+
+function loadSlotParameters($slotUUID, $config) {
+ $slotParameters = require(getSlotFilePath($slotUUID, $config));
+ $slotParameters['filename'] = $slotParameters['filename'];
+
+ return $slotParameters;
+} \ No newline at end of file
diff --git a/storage-backend/lib/functions.http.inc.php b/storage-backend/lib/functions.http.inc.php
new file mode 100644
index 0000000..c508b20
--- /dev/null
+++ b/storage-backend/lib/functions.http.inc.php
@@ -0,0 +1,64 @@
+<?php
+/*
+ *
+ * This file contains functions to be used to
+ * extract information based on http request information.
+ *
+ */
+
+/**
+ * Inspired by https://github.com/owncloud/core/blob/master/lib/private/appframework/http/request.php#L523
+ */
+function getServerProtocol() {
+ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
+ if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], ',') !== false) {
+ $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_PROTO']);
+ $proto = strtolower(trim($parts[0]));
+ } else {
+ $proto = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
+ }
+ // Verify that the protocol is always HTTP or HTTPS
+ // default to http if an invalid value is provided
+ return $proto === 'https' ? 'https' : 'http';
+ }
+ if (isset($_SERVER['HTTPS'])
+ && $_SERVER['HTTPS'] !== null
+ && $_SERVER['HTTPS'] !== 'off'
+ && $_SERVER['HTTPS'] !== '') {
+ return 'https';
+ }
+ return 'http';
+}
+
+function getRequestHostname() {
+ if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
+ return strtolower($_SERVER['HTTP_X_FORWARDED_HOST']);
+ }
+ return strtolower($_SERVER['HTTP_HOST']);
+}
+
+function getRequestUriWithoutFilename() {
+ return strtolower(substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '/') + 1));
+}
+
+function sendHttpReturnCodeAndJson($code, $data) {
+ if (!is_array($data)) {
+ $data = ['msg' => $data];
+ }
+ header('Content-Type: application/json');
+ sendHttpReturnCodeAndMessage($code, json_encode($data));
+}
+
+function sendHttpReturnCodeAndMessage($code, $text = '') {
+ http_response_code($code);
+ exit($text);
+}
+
+function getOptionalPostParameter($parameterName, $default = NULL) {
+ $parameter = $_POST[$parameterName];
+ if (!isset($parameter) || is_null($parameter) || empty($parameter)) {
+ $parameter = $default;
+ }
+ return $parameter;
+}
+?> \ No newline at end of file