inital commit

This commit is contained in:
Daniel Gultsch 2015-06-28 11:21:30 +02:00
commit 3b341972b0
4 changed files with 214 additions and 0 deletions

11
config.yml Normal file
View File

@ -0,0 +1,11 @@
jid: upload.siacs.eu
secret: something
storage_path : ./
whitelist:
- jabber.ccc.de
- siacs.eu
- gultsch.de
max_file_size: 20971520 #20MiB
http_port: 8080
get_url : http://upload.siacs.eu:8080
put_url : http://upload.siacs.eu:8080

0
plugins/__init__.py Normal file
View File

38
plugins/upload.py Normal file
View File

@ -0,0 +1,38 @@
from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
from sleekxmpp import Iq
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
class upload(base_plugin):
def plugin_init(self):
self.description = "upload files via http"
self.xep = "0999"
self.xmpp['xep_0030'].add_feature("urn:xmpp:http:upload")
self.xmpp.register_handler(
Callback('Upload request',
MatchXPath('{%s}iq/{urn:xmpp:http:upload}request' % self.xmpp.default_ns),
self._handleUpload))
register_stanza_plugin(Iq, UploadRequest)
register_stanza_plugin(Iq, UploadSlot)
def _handleUpload(self, iq):
if iq['type'] == 'get':
self.xmpp.event('request_upload_slot',iq)
class UploadRequest(ElementBase):
namespace = "urn:xmpp:http:upload"
name = "request"
plugin_attrib = "request"
interfaces = set(('size','filename'))
sub_interfaces = interfaces
class UploadSlot(ElementBase):
namespace = "urn:xmpp:http:upload"
name = "slot"
plugin_attrib = "slot"
interfaces = set(('put','get'))
sub_interfaces = interfaces

165
server.py Normal file
View File

@ -0,0 +1,165 @@
import yaml
import sys
import shutil
import signal
import logging
import string
import hashlib
import random
import os
from threading import Thread
from threading import Lock
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import sleekxmpp
from sleekxmpp.componentxmpp import ComponentXMPP
global files
global files_lock
global config
class MissingComponent(ComponentXMPP):
def __init__(self, jid, secret):
ComponentXMPP.__init__(self, jid, secret, "localhost", 5347)
self.register_plugin('xep_0030')
self.register_plugin('upload',module='plugins.upload')
self.add_event_handler('request_upload_slot',self.request_upload_slot)
def request_upload_slot(self, iq):
global config
global files
global files_lock
request = iq['request']
maxfilesize = int(config['max_file_size'])
if not request['filename'] or not request['size']:
self._sendError(iq,'modify','bad-request','please specify filename and size')
elif maxfilesize < int(request['size']):
self._sendError(iq,'modify','not-acceptable','file too large. max file size is '+str(maxfilesize))
elif 'whitelist' not in config or iq['from'].domain in config['whitelist']:
sender = iq['from'].bare
sender_hash = hashlib.sha1(sender.encode()).hexdigest()
filename = request['filename']
folder = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(len(sender_hash)))
sane_filename = "".join([c for c in filename if c.isalpha() or c.isdigit() or c=="."]).rstrip()
path = sender_hash+'/'+folder
if sane_filename:
path += '/'+sane_filename
with files_lock:
files.add(path)
print(path)
reply = iq.reply()
reply['slot']['get'] = config['get_url'] + '/' + path
reply['slot']['put'] = config['put_url'] + '/' + path
reply.send()
else:
self. _sendError(iq,'cancel','not-allowed','not allowed to request upload slots')
def _sendError(self, iq, error_type, condition, text):
reply = iq.reply()
iq.error()
iq['error']['type'] = error_type
iq['error']['condition'] = condition
iq['error']['text'] = text
iq.send()
class HttpHandler(BaseHTTPRequestHandler):
def do_PUT(self):
print('do put')
global files
global files_lock
global config
path = self.path[1:]
length = int(self.headers['Content-Length'])
maxfilesize = int(config['max_file_size'])
if maxfilesize < length:
self.send_response(400,'file too large')
self.end_headers()
else:
print('path: '+path)
files_lock.acquire()
if path in files:
files.remove(path)
files_lock.release()
filename = config['storage_path'] + path
os.makedirs(os.path.dirname(filename))
remaining = length
f = open(filename,'wb')
data = self.rfile.read(4096)
while data and remaining >= 0:
remaining -= len(data)
f.write(data)
data = self.rfile.read(min(4096,remaining))
f.close()
self.send_response(200,'ok')
self.end_headers()
else:
files_lock.release()
self.send_response(403,'invalid slot')
self.end_headers()
def do_GET(self):
global config
path = self.path[1:].replace('../','').replace('./','')
slashcount = path.count('/')
if slashcount < 1 or slashcount > 2:
self.send_response(404,'file not found')
self.end_headers()
else:
filename = config['storage_path']+'/'+path
print('requesting file: '+filename)
try:
with open(filename,'rb') as f:
self.send_response(200)
self.send_header("Content-Type", 'application/octet-stream')
self.send_header("Content-Disposition", 'attachment; filename="{}"'.format(os.path.basename(filename)))
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs.st_size))
self.end_headers()
shutil.copyfileobj(f, self.wfile)
except FileNotFoundError:
self.send_response(404,'file not found')
self.end_headers()
def do_HEAD(self):
global config
path = self.path[1:].replace('../','').replace('./','')
slashcount = path.count('/')
if slashcount < 1 or slashcount > 2:
self.send_response(404,'file not found')
self.end_headers()
else:
try:
filename = config['storage_path']+'/'+path
self.send_response(200,'OK')
self.send_header("Content-Type", 'application/octet-stream')
self.send_header("Content-Disposition", 'attachment; filename="{}"'.format(os.path.basename(filename)))
self.send_header("Content-Length",str(os.path.getsize(filename)))
self.end_headers()
except FileNotFoundError:
self.send_response(404,'file not found')
self.end_headers()
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
if __name__ == "__main__":
global files
global files_lock
global config
with open('config.yml','r') as ymlfile:
config = yaml.load(ymlfile)
files = set()
files_lock = Lock()
logging.basicConfig(level=logging.DEBUG,
format='%(levelname)-8s %(message)s')
server = ThreadedHTTPServer(('0.0.0.0', config['http_port']), HttpHandler)
xmpp = MissingComponent(config['jid'],config['secret'])
if xmpp.connect():
xmpp.process(block=False)
print("connected")
server.serve_forever()
else:
print("unable to connect")