From 2546769fb40a36ee3c88169dc39c37dd9ef308bb Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 22 Jul 2015 21:30:57 -0500 Subject: [PATCH 1/5] Cleanup readme --- README.md | 54 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c0527ff..83a0492 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,52 @@ # 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. -###Configuration -Configuration happens in config.yml and is pretty straight forward. +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). -```jid``` and ```secret``` have to match the corresponding entries in your XMPP server config (Refer to the documentation of your server). +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). -```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). +### Configuration -```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. +Configuration happens in `config.yml` and is pretty straight forward. -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 build in TLS encryption by setting the ```keyfile``` and ```certfile``` in the config file. +```jid``` and ```secret``` have to match the corresponding entries in your XMPP +server config (Refer to the documentation of your server). +```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 the configuration on the XMPP server side have a look into the contrib directory or check the server documentation. +```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. + +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 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``` -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 +creating a pull request. -Feel free to write your own init scripts if necessary and contribute them back by 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. From 50f0a6fec66cde0b05d86ac9e168b08bfc7bbed5 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 22 Jul 2015 22:01:17 -0500 Subject: [PATCH 2/5] Add proper error for FileNotFoundError in Python 2 --- server.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/server.py b/server.py index 1288a53..a661cfa 100755 --- a/server.py +++ b/server.py @@ -1,18 +1,24 @@ #!/usr/bin/env python -import yaml -import sys +import argparse +import errno +import hashlib +import logging +import mimetypes +import os +import random import shutil import signal -import logging -import string -import hashlib -import random -import os +import sleekxmpp import ssl -import argparse -from threading import Thread +import string +import sys +import yaml + +from sleekxmpp.componentxmpp import ComponentXMPP from threading import Lock +from threading import Thread + try: # Python 3 from http.server import HTTPServer, BaseHTTPRequestHandler @@ -21,9 +27,20 @@ except ImportError: # Python 2 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from SocketServer import ThreadingMixIn - FileNotFoundError = IOError -import sleekxmpp -from sleekxmpp.componentxmpp import ComponentXMPP + +try: + 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 From f397219c2700298f61fde9158156126135a6094e Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 22 Jul 2015 22:21:28 -0500 Subject: [PATCH 3/5] Fix a file handle leak --- server.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/server.py b/server.py index 1288a53..c2457ff 100755 --- a/server.py +++ b/server.py @@ -103,13 +103,12 @@ class HttpHandler(BaseHTTPRequestHandler): filename = os.path.join(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() + with open(filename,'wb') as f: + data = self.rfile.read(4096) + while data and remaining >= 0: + remaining -= len(data) + f.write(data) + data = self.rfile.read(min(4096,remaining)) self.send_response(200,'ok') self.end_headers() else: From 63124a30264e87cb3a0f2d400bf9260c71b5ed6a Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 22 Jul 2015 22:41:44 -0500 Subject: [PATCH 4/5] Set content-type header based on mime-type Fixes #7 --- server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 1288a53..8548154 100755 --- a/server.py +++ b/server.py @@ -130,7 +130,10 @@ class HttpHandler(BaseHTTPRequestHandler): try: with open(filename,'rb') as f: 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))) fs = os.fstat(f.fileno()) self.send_header("Content-Length", str(fs.st_size)) From 5a0566688ae8d1a527a5989f2d95628e35914507 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Thu, 23 Jul 2015 00:42:24 -0500 Subject: [PATCH 5/5] Don't duplicate HEAD/GET response code --- server.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/server.py b/server.py index 1288a53..f942b11 100755 --- a/server.py +++ b/server.py @@ -117,7 +117,7 @@ class HttpHandler(BaseHTTPRequestHandler): self.send_response(403,'invalid slot') self.end_headers() - def do_GET(self): + def do_GET(self, body=True): global config path = normalize_path(self.path[1:]) slashcount = path.count('/') @@ -135,29 +135,14 @@ class HttpHandler(BaseHTTPRequestHandler): fs = os.fstat(f.fileno()) self.send_header("Content-Length", str(fs.st_size)) self.end_headers() - shutil.copyfileobj(f, self.wfile) + if body: + shutil.copyfileobj(f, self.wfile) except FileNotFoundError: self.send_response(404,'file not found') self.end_headers() def do_HEAD(self): - global config - 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() + self.do_GET(body=False) class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):