diff --git a/100unix2web.conf b/100unix2web.conf new file mode 100644 index 0000000..12dae2c --- /dev/null +++ b/100unix2web.conf @@ -0,0 +1,11 @@ + + WSGIDaemonProcess app + WSGIScriptAlias / /var/www/unix2web/unix2web.wsgi + + + WSGIProcessGroup app + WSGIApplicationGroup %{GLOBAL} + Order deny,allow + Allow from all + + \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..aef6184 --- /dev/null +++ b/README.rst @@ -0,0 +1,102 @@ +================ +unix2web +================ +---------- +Expose a UNIX filter on a web page +---------- + +Purpose +============= + +You have an UNIX filter that does one job and do it well. For example it converts a .doc file to PDF : + doc2pdf < file.doc >file.pdf + +This tool lets you host a nice web page where people can upload the input file and download the output file. This is +useful if UNIX-disabled people need to use your filter. + +In a matter of minutes you can make an unix script available to the world on e.g. an AWS EC2 instance. + +Installation +============= + +We assume you have a UNIX-like server where your filter works (this tool does not handle the installation of your filter +or its dependencies). + +Clone the repo somewhere : + + git clone https://github.com/edouardklein/unix2web/ + +Change the index.html file to your liking (most notably the title and description). + +Modify unix2web.wsgi if you want the webapp to live somewhere else than /var/www/unix2web/ (not useful if you intend +to only run one filter on the server). + +Same procedure for 100web2unix.conf + +Same procedure for unix2web.py + +Review the install.sh file, modify if necessary, then upload all the files on your server and run install.sh + scp -i private_key.pem *.* login@example.com:/tmp/ + ssh -i private_key.pem login@example.com 'cd /tmp/ && sudo sh install.sh' + + +Example use cases +========= + +Converting .doc to .pdf from anywhere. + +Compiling LaTeX files. + +I'd be happy to hear about your uses. If you deploy this, please send me a word. + +Security +======== + +Security is hard, so we did not do it beyond the obvious (not using the user's filename). + +Easy to DDoS (just send a big file). + +No password protection. + +We advise to deploy this behind a hard-to-find, non-indexable URL and to share the link wisely. + + +Bugs and Todo +============ + +Near future +---------- + +This probably scales badly (untested). + +The error output comes all at once, would be nice if, for long processes, it came as it appears server-side. + +Security is bad. At least we should check input size and kill long processes. + +Output files are not cleaned up (easily doable with a cron-job, though). + +Distant future +------------- + +Parsing the --help string à la docopt and automatically creating an interface for the options would be wonderful. + +Author +====== +See http://rdklein.fr + +License +======= + + unix2web 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. + + Foobar 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 unix2web. If not, see . + diff --git a/index.css b/index.css new file mode 100644 index 0000000..bcb1578 --- /dev/null +++ b/index.css @@ -0,0 +1,19 @@ +.btn-file { + position: relative; + overflow: hidden; + } +.btn-file input[type=file] { + position: absolute; + top: 0; + right: 0; + min-width: 100%; + min-height: 100%; + font-size: 100px; + text-align: right; + filter: alpha(opacity=0); + opacity: 0; + outline: none; + background: white; + cursor: inherit; + display: block; + } \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..f3525b1 --- /dev/null +++ b/index.html @@ -0,0 +1,113 @@ + + + + + + + Test unix2web page + + + + + + + +
+

unix2web

+

Make any UNIX filter accessible wia the web. For demo purposes we use a simple sed one liner that changes + all instances of "the web" to "UNIX" and echoes the date on stderr : + (date>&2; sed s/the web/UNIX/gI output.

+
+
+
+
+
+ + + Input file… + + + +
+
+
+ +
+
+
+
+
+

+    
+
+
+
+ Output file : +
+
+
+
+ Output file : +
+
+Fork me on GitHub + + \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..66c2947 --- /dev/null +++ b/install.sh @@ -0,0 +1,17 @@ +# This will install the web-app on a ubuntu server +# intallation instructions follow recipe given here http://blog.garethdwyer.co.za/2013/07/getting-simple-flask-app-running-on.html . Thanks ! + +APPNAME=unix2web +INSTALLDIR=/var/www/$APPNAME +sudo yes | apt-get install apache2 libapache2-mod-wsgi python-flask +sudo mkdir -p $INSTALLDIR +sudo mv /tmp/index.html $INSTALLDIR/ +sudo mv /tmp/index.css $INSTALLDIR/ +sudo mv /tmp/$APPNAME.py $INSTALLDIR/ +sudo mv /tmp/$APPNAME.wsgi $INSTALLDIR/ +sudo mv /tmp/100$APPNAME.conf /etc/apache2/sites-available +sudo a2dissite 000-default +sudo a2ensite 100$APPNAME +sudo /etc/init.d/apache2 restart + + diff --git a/unix2web.py b/unix2web.py new file mode 100644 index 0000000..f42ca31 --- /dev/null +++ b/unix2web.py @@ -0,0 +1,151 @@ +from __future__ import print_function +"""Run a UNIX filter on an uploaded file, download the result + +Code on https://github.com/edouardklein/unix2web/ + + This file is part of unix2web. + + unix2web 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. + + Foobar 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 unix2web. If not, see . +""" +__author__ = 'Edouard Klein ' + +import os +from flask import Flask, request, Response, jsonify +import logging +logging.basicConfig(filename='/tmp/debug.log', level=logging.DEBUG) +from subprocess import Popen, PIPE + +app = Flask(__name__) + +# Back port of the with TemporaryDirectory()... syntax from +# https://stackoverflow.com/questions/19296146/with-tempfile-temporarydirectory +# Thanks. +import warnings as _warnings +import os as _os +from tempfile import mkdtemp, mkstemp + +class TemporaryDirectory(object): + """Create and return a temporary directory. This has the same + behavior as mkdtemp but can be used as a context manager. For + example: + + with TemporaryDirectory() as tmpdir: + ... + + Upon exiting the context, the directory and everything contained + in it are removed. + """ + + def __init__(self, suffix="", prefix="tmp", dir=None): + self._closed = False + self.name = None # Handle mkdtemp raising an exception + self.name = mkdtemp(suffix, prefix, dir) + + def __repr__(self): + return "<{} {!r}>".format(self.__class__.__name__, self.name) + + def __enter__(self): + return self.name + + def cleanup(self, _warn=False): + if self.name and not self._closed: + try: + self._rmtree(self.name) + except (TypeError, AttributeError) as ex: + # Issue #10188: Emit a warning on stderr + # if the directory could not be cleaned + # up due to missing globals + if "None" not in str(ex): + raise + print("ERROR: {!r} while cleaning up {!r}".format(ex, self,),file=_sys.stderr) + return + self._closed = True + if _warn: + self._warn("Implicitly cleaning up {!r}".format(self), + ResourceWarning) + + def __exit__(self, exc, value, tb): + self.cleanup() + + def __del__(self): + # Issue a ResourceWarning if implicit cleanup needed + self.cleanup(_warn=True) + + # XXX (ncoghlan): The following code attempts to make + # this class tolerant of the module nulling out process + # that happens during CPython interpreter shutdown + # Alas, it doesn't actually manage it. See issue #10188 + _listdir = staticmethod(_os.listdir) + _path_join = staticmethod(_os.path.join) + _isdir = staticmethod(_os.path.isdir) + _islink = staticmethod(_os.path.islink) + _remove = staticmethod(_os.remove) + _rmdir = staticmethod(_os.rmdir) + _warn = _warnings.warn + + def _rmtree(self, path): + # Essentially a stripped down version of shutil.rmtree. We can't + # use globals because they may be None'ed out at shutdown. + for name in self._listdir(path): + fullname = self._path_join(path, name) + try: + isdir = self._isdir(fullname) and not self._islink(fullname) + except OSError: + isdir = False + if isdir: + self._rmtree(fullname) + else: + try: + self._remove(fullname) + except OSError: + pass + try: + self._rmdir(path) + except OSError: + pass +#End of Backport + + +@app.route('/tmp/') +def get_output(file): + logging.debug('GET on file '+file) + return open('/tmp/'+file, 'r').read() # Not platform independent FIXME. + +@app.route('/', methods=['GET', 'POST']) +def upload_file(): + logging.debug('Request on /') + if request.method == 'POST': + logging.debug('POST') + file = request.files['file'] + if file: + with TemporaryDirectory() as tmpdir: + logging.debug('saving on '+os.path.join(tmpdir, 'input')) + file.save(os.path.join(tmpdir, 'input')) + (_,output_file_name) = mkstemp() + p = Popen("(date>&2; sed s/the web/UNIX/gI "+output_file_name, shell=True, cwd=tmpdir, stderr=PIPE) + (_,stderr) = p.communicate() + logging.debug('Got stderr : '+stderr) + return jsonify(stderr=stderr, outputfile_url=output_file_name) + logging.debug('GET') + return open("/var/www/unix2web/index.html", 'r').read() + +@app.route('/index.css') +def wrapper(): + resp = Response(response=open('/var/www/unix2web/index.css', 'r').read(), + status=200, + mimetype="text/css") + return resp + +if __name__ == '__main__': + app.run() diff --git a/unix2web.wsgi b/unix2web.wsgi new file mode 100644 index 0000000..8620039 --- /dev/null +++ b/unix2web.wsgi @@ -0,0 +1,5 @@ +import sys +sys.path.insert(0, '/var/www/unix2web') + +from unix2web import app as application +