Merge branch 'master' of github.com:siacs/HttpUploadComponent

This commit is contained in:
Daniel Gultsch 2015-07-24 17:48:14 +02:00
commit 6d8993145b
2 changed files with 80 additions and 54 deletions

View File

@ -1,30 +1,52 @@
# HttpUploadComponent # HttpUploadComponent
The HttpUploadComponent is a plugin extension to your XMPP server that allows users to upload files to a HTTP host and eventually share the link to those files.
It runs as a stand alone process on the same host as your XMPP server and connects to that server using the [Jabber Component Protocol](http://xmpp.org/extensions/xep-0114.html).
A detailed introduction into the necessity of such a component and the simple protocol can be found on the [XMPP Standards email list](http://mail.jabber.org/pipermail/standards/2015-June/029969.html). The HttpUploadComponent is a plugin extension to your XMPP server that allows
users to upload files to a HTTP host and eventually share the link to those
files.
It runs as a stand alone process on the same host as your XMPP server and
connects to that server using the [Jabber Component
Protocol](http://xmpp.org/extensions/xep-0114.html).
A detailed introduction into the necessity of such a component and the simple
protocol can be found on the [XMPP Standards email
list](http://mail.jabber.org/pipermail/standards/2015-June/029969.html).
### Configuration ### Configuration
Configuration happens in config.yml and is pretty straight forward.
```jid``` and ```secret``` have to match the corresponding entries in your XMPP server config (Refer to the documentation of your server). Configuration happens in `config.yml` and is pretty straight forward.
```whitelist``` should contain a list of domains whos JIDs are allowed to upload files. Remove the entry if you want to allow everyone (not really recommended). ```jid``` and ```secret``` have to match the corresponding entries in your XMPP
server config (Refer to the documentation of your server).
```get_url``` and ```put_url``` are prefixes to the URLs. The put_url is usually a combination of your hostname and the port (Port can be ommited if HTTP default ports are being used) The GET URL can use a different host if you want to serve files using a standard nginx or another HTTP server that might be more suitable for serving files than a python script. ```whitelist``` should contain a list of domains whos JIDs are allowed to
upload files. Remove the entry if you want to allow everyone (not really
recommended).
For security purposes you should put the python script behind an HTTPS proxy or stunnel (remember to adapt the URLs). ```get_url``` and ```put_url``` are prefixes to the URLs. The put_url is
For quicker results you can also use the build in TLS encryption by setting the ```keyfile``` and ```certfile``` in the config file. usually a combination of your hostname and the port (Port can be ommited if
HTTP default ports are being used) The GET URL can use a different host if you
want to serve files using a standard nginx or another HTTP server that might be
more suitable for serving files than a python script.
For security purposes you should put the python script behind an HTTPS proxy or
stunnel (remember to adapt the URLs). For quicker results you can also use the
built in TLS encryption by setting ```keyfile``` and ```certfile``` in the
config file.
For the configuration on the XMPP server side have a look into the contrib directory or check the server documentation. For the configuration of the XMPP server side have a look into the contrib
directory or check the server documentation.
### Run ### Run
Running the component is as easy as invoking ```python server.py``` Running the component is as easy as invoking ```python server.py```
Some (unoffical) init scripts can be found in the contrib directory. Some (unoffical) init scripts can be found in the contrib directory. Feel free
to write your own init scripts if necessary and contribute them back by
Feel free to write your own init scripts if necessary and contribute them back by creating a pull request. creating a pull request.
### Clients ### Clients
Currently the only client with build in support is [Conversations](http://conversations.im) where it is being used to send files to Multi User Conferences and to multiple resources in 1 on 1 chats.
Currently the only client with build in support is
[Conversations](http://conversations.im) where it is being used to send files
to Multi User Conferences and to multiple resources in 1 on 1 chats.

View File

@ -1,18 +1,24 @@
#!/usr/bin/env python #!/usr/bin/env python
import yaml import argparse
import sys import errno
import hashlib
import logging
import mimetypes
import os
import random
import shutil import shutil
import signal import signal
import logging import sleekxmpp
import string
import hashlib
import random
import os
import ssl import ssl
import argparse import string
from threading import Thread import sys
import yaml
from sleekxmpp.componentxmpp import ComponentXMPP
from threading import Lock from threading import Lock
from threading import Thread
try: try:
# Python 3 # Python 3
from http.server import HTTPServer, BaseHTTPRequestHandler from http.server import HTTPServer, BaseHTTPRequestHandler
@ -21,9 +27,20 @@ except ImportError:
# Python 2 # Python 2
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
FileNotFoundError = IOError
import sleekxmpp try:
from sleekxmpp.componentxmpp import ComponentXMPP FileNotFoundError
except NameError:
# Python 2
class FileNotFoundError(IOError):
def __init__(self, message=None, *args):
super(FileNotFoundError, self).__init__(args)
self.message = message
self.errno = errno.ENOENT
def __str__(self):
return self.message or os.strerror(self.errno)
LOGLEVEL=logging.DEBUG LOGLEVEL=logging.DEBUG
@ -103,13 +120,12 @@ class HttpHandler(BaseHTTPRequestHandler):
filename = os.path.join(config['storage_path'], path) filename = os.path.join(config['storage_path'], path)
os.makedirs(os.path.dirname(filename)) os.makedirs(os.path.dirname(filename))
remaining = length remaining = length
f = open(filename,'wb') with open(filename,'wb') as f:
data = self.rfile.read(min(4096,remaining)) data = self.rfile.read(min(4096,remaining))
while data and remaining >= 0: while data and remaining >= 0:
remaining -= len(data) remaining -= len(data)
f.write(data) f.write(data)
data = self.rfile.read(min(4096,remaining)) data = self.rfile.read(min(4096,remaining))
f.close()
self.send_response(200,'ok') self.send_response(200,'ok')
self.end_headers() self.end_headers()
else: else:
@ -117,7 +133,7 @@ class HttpHandler(BaseHTTPRequestHandler):
self.send_response(403,'invalid slot') self.send_response(403,'invalid slot')
self.end_headers() self.end_headers()
def do_GET(self): def do_GET(self, body=True):
global config global config
path = normalize_path(self.path[1:]) path = normalize_path(self.path[1:])
slashcount = path.count('/') slashcount = path.count('/')
@ -130,34 +146,22 @@ class HttpHandler(BaseHTTPRequestHandler):
try: try:
with open(filename,'rb') as f: with open(filename,'rb') as f:
self.send_response(200) self.send_response(200)
self.send_header("Content-Type", 'application/octet-stream') mime, _ = mimetypes.guess_type(filename)
if mime is None:
mime = 'application/octet-stream'
self.send_header("Content-Type", mime)
self.send_header("Content-Disposition", 'attachment; filename="{}"'.format(os.path.basename(filename))) self.send_header("Content-Disposition", 'attachment; filename="{}"'.format(os.path.basename(filename)))
fs = os.fstat(f.fileno()) fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs.st_size)) self.send_header("Content-Length", str(fs.st_size))
self.end_headers() self.end_headers()
if body:
shutil.copyfileobj(f, self.wfile) shutil.copyfileobj(f, self.wfile)
except FileNotFoundError: except FileNotFoundError:
self.send_response(404,'file not found') self.send_response(404,'file not found')
self.end_headers() self.end_headers()
def do_HEAD(self): def do_HEAD(self):
global config self.do_GET(body=False)
path = normalize_path(self.path[1:])
slashcount = path.count('/')
if path[0] in ('/', '\\') or slashcount < 1 or slashcount > 2:
self.send_response(404,'file not found')
self.end_headers()
else:
try:
filename = os.path.join(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): class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):