@ -0,0 +1,146 @@ | |||
# Created by .ignore support plugin (hsz.mobi) | |||
### JetBrains template | |||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm | |||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 | |||
# User-specific stuff: | |||
.idea/**/workspace.xml | |||
.idea/**/tasks.xml | |||
.idea/dictionaries | |||
# Sensitive or high-churn files: | |||
.idea/**/dataSources/ | |||
.idea/**/dataSources.ids | |||
.idea/**/dataSources.xml | |||
.idea/**/dataSources.local.xml | |||
.idea/**/sqlDataSources.xml | |||
.idea/**/dynamic.xml | |||
.idea/**/uiDesigner.xml | |||
# Gradle: | |||
.idea/**/gradle.xml | |||
.idea/**/libraries | |||
# Mongo Explorer plugin: | |||
.idea/**/mongoSettings.xml | |||
## File-based project format: | |||
*.iws | |||
## Plugin-specific files: | |||
# IntelliJ | |||
/out/ | |||
# mpeltonen/sbt-idea plugin | |||
.idea_modules/ | |||
# JIRA plugin | |||
atlassian-ide-plugin.xml | |||
# Crashlytics plugin (for Android Studio and IntelliJ) | |||
com_crashlytics_export_strings.xml | |||
crashlytics.properties | |||
crashlytics-build.properties | |||
fabric.properties | |||
### Python template | |||
# Byte-compiled / optimized / DLL files | |||
__pycache__/ | |||
*.py[cod] | |||
*$py.class | |||
# C extensions | |||
*.so | |||
# Distribution / packaging | |||
.Python | |||
env/ | |||
build/ | |||
develop-eggs/ | |||
dist/ | |||
downloads/ | |||
eggs/ | |||
.eggs/ | |||
lib/ | |||
lib64/ | |||
parts/ | |||
sdist/ | |||
var/ | |||
wheels/ | |||
*.egg-info/ | |||
.installed.cfg | |||
*.egg | |||
# PyInstaller | |||
# Usually these files are written by a python script from a template | |||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||
*.manifest | |||
*.spec | |||
# Installer logs | |||
pip-log.txt | |||
pip-delete-this-directory.txt | |||
# Unit test / coverage reports | |||
htmlcov/ | |||
.tox/ | |||
.coverage | |||
.coverage.* | |||
.cache | |||
nosetests.xml | |||
coverage.xml | |||
*,cover | |||
.hypothesis/ | |||
# Translations | |||
*.mo | |||
*.pot | |||
# Django stuff: | |||
*.log | |||
local_settings.py | |||
# Flask stuff: | |||
instance/ | |||
.webassets-cache | |||
# Scrapy stuff: | |||
.scrapy | |||
# Sphinx documentation | |||
docs/_build/ | |||
# PyBuilder | |||
target/ | |||
# Jupyter Notebook | |||
.ipynb_checkpoints | |||
# pyenv | |||
.python-version | |||
# celery beat schedule file | |||
celerybeat-schedule | |||
# SageMath parsed files | |||
*.sage.py | |||
# dotenv | |||
.env | |||
# virtualenv | |||
.venv | |||
venv/ | |||
ENV/ | |||
# Spyder project settings | |||
.spyderproject | |||
# Rope project settings | |||
.ropeproject | |||
# ==================== | |||
files/* | |||
subdomain_files/* | |||
!.gitkeep |
@ -0,0 +1,113 @@ | |||
from bottle import route, request, run, redirect, template, default_app, abort | |||
import os | |||
from datetime import datetime | |||
from mimetypes import guess_type | |||
base_hosts = [ | |||
'127.0.0.1.xip.io:8080' | |||
] | |||
def format_size(size): | |||
for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB']: | |||
if size < 2048: | |||
return f'{size} {unit}' | |||
else: | |||
size //= 1024 | |||
return f'{size} PiB' | |||
def format_date(ts): | |||
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') | |||
def get_sort_icon(test, sort): | |||
if sort == test: | |||
return '<i class="sort descending icon"></i>' | |||
elif sort == f'-{test}': | |||
return '<i class="sort ascending icon"></i>' | |||
else: | |||
return '' | |||
def get_sort_link(current, sort, hidden): | |||
return f'?sort={"-" if current == sort else ""}{sort}{"&hidden" if hidden else ""}' | |||
def get_file_icon(name): | |||
t = guess_type(name)[0].split('/')[0] | |||
if t in ['text', 'image', 'audio', 'video']: | |||
return t | |||
return '' | |||
class ListEntry: | |||
def __init__(self, dir, name): | |||
path = os.path.join(dir, name) | |||
self.name = name | |||
self.isdir = os.path.isdir(path) | |||
self.size = os.path.getsize(path) | |||
self.created = os.path.getctime(path) | |||
def resolve_path(domain, path): | |||
if domain in base_hosts: | |||
return os.path.join(os.path.dirname(__file__), 'files', *path.split('/')) | |||
else: | |||
for host in base_hosts: | |||
if host in domain: | |||
return os.path.join(os.path.dirname(__file__), 'subdomain_files', | |||
domain[:domain.index(host)-1], *path.split('/')) | |||
raise ValueError | |||
def list_dir(dir, sort=None, hidden=False): | |||
lst = [ListEntry(dir, name) for name in os.listdir(dir) if hidden or not name.startswith('.')] | |||
lst = sorted(lst, key={ | |||
'name': lambda item: (not item.isdir, item.name.lower()), | |||
'-name': lambda item: (item.isdir, item.name.lower()), | |||
'size': lambda item: (not item.isdir, item.size), | |||
'-size': lambda item: (item.isdir, item.size), | |||
'created': lambda item: (not item.isdir, item.created), | |||
'-created': lambda item: (item.isdir, item.created) | |||
}[sort], reverse=sort.startswith('-')) | |||
return lst | |||
@route('/') | |||
@route('/<path:path>') | |||
def index(path=''): | |||
domain = request.urlparts.netloc | |||
query = request.urlparts.query | |||
try: | |||
resolved_path = resolve_path(domain, path) | |||
except ValueError: | |||
return 'GTFO' | |||
if os.path.isdir(resolved_path): | |||
if path and path[-1] != '/': | |||
return redirect(f'/{path}/') | |||
hidden = 'hidden' in request.GET | |||
sort = request.GET.get('sort', 'name') | |||
if sort not in ['name', '-name', 'size', '-size', 'created', '-created']: | |||
sort = 'name' | |||
return template('filelist', | |||
lst=list_dir(resolved_path, sort, hidden), | |||
format_size=format_size, | |||
format_date=format_date, | |||
get_sort_icon=get_sort_icon, | |||
get_sort_link=get_sort_link, | |||
sort=sort, | |||
hidden=hidden, | |||
path=path, | |||
query=query, | |||
guess_type=guess_type, | |||
get_file_icon=get_file_icon) | |||
else: | |||
return abort(404) | |||
application = default_app() | |||
if __name__ == '__main__': | |||
run(host='localhost', port=8080, debug=True, reloader=True) |
@ -0,0 +1,27 @@ | |||
server { | |||
listen 80; | |||
server_name drop.217.182.90.36.xip.io; | |||
location /_ { | |||
alias /srv/apps/drop/files; | |||
} | |||
location / { | |||
include uwsgi_params; | |||
uwsgi_pass unix:///tmp/drop.sock; | |||
} | |||
} | |||
server { | |||
listen 80; | |||
server_name ~^(?<dir>.*)\.drop\.217\.182\.90\.36\.xip\.io$; | |||
location /_ { | |||
alias /srv/apps/drop/subdomain_files/$dir; | |||
} | |||
location / { | |||
include uwsgi_params; | |||
uwsgi_pass unix:///tmp/drop.sock; | |||
} | |||
} |
@ -0,0 +1,9 @@ | |||
[program:drop] | |||
user = www-data | |||
directory = /srv/apps/drop | |||
command = /srv/apps/drop/venv/bin/uwsgi --ini /srv/apps/drop/configs/uwsgi.ini | |||
autostart = true | |||
autorestart = true | |||
stderr_logfile = /srv/apps/drop/logs/uwsgi_err.log | |||
stdout_logfile = /srv/apps/drop/logs/uwsgi_out.log | |||
stopsignal = INT |
@ -0,0 +1,6 @@ | |||
[uwsgi] | |||
socket = /tmp/drop.sock | |||
module = autoindex:application | |||
master = true | |||
processes = 2 | |||
enable-threads = true |
@ -0,0 +1,79 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<title>File list</title> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/components/reset.min.css" rel="stylesheet" /> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/components/site.min.css" rel="stylesheet" /> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/components/icon.min.css" rel="stylesheet" /> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/components/table.min.css" rel="stylesheet" /> | |||
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/components/container.min.css" rel="stylesheet" /> | |||
<style> | |||
table tbody tr { | |||
position: relative; | |||
} | |||
table tbody tr a { | |||
display: block; | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
right: 0; | |||
bottom: 0; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="ui main container" style="padding: 15px 0"> | |||
<h2>Directory Index - /{{ path }}</h2> | |||
<table class="ui selectable unstackable table"> | |||
<thead> | |||
<tr> | |||
<th><a href="{{ get_sort_link(sort, 'name', hidden) }}"> | |||
Name {{ !get_sort_icon('name', sort) }} | |||
</a></th> | |||
<th style="width: 100px;"><a href="{{ get_sort_link(sort, 'size', hidden) }}"> | |||
Size {{ !get_sort_icon('size', sort) }} | |||
</a></th> | |||
<th style="width: 170px;"><a href="{{ get_sort_link(sort, 'created', hidden) }}"> | |||
Created {{ !get_sort_icon('created', sort) }} | |||
</a></th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
% if path: | |||
<tr style="cursor: pointer"> | |||
<td> | |||
<a href="../{{ f'?{query}' if query else '' }}"></a> | |||
<i class="level up icon"></i> | |||
../ | |||
</td> | |||
<td></td> | |||
<td></td> | |||
</tr> | |||
% end | |||
% for item in lst: | |||
<tr style="cursor: pointer"> | |||
% if item.isdir: | |||
<td> | |||
<a href="{{ item.name }}/{{ f'?{query}' if query else '' }}"></a> | |||
<i class="folder outline icon"></i> | |||
{{ item.name }}/ | |||
</td> | |||
<td></td> | |||
<td></td> | |||
% else: | |||
<td> | |||
<a href="/_/{{ path }}{{ item.name }}" target="_blank"></a> | |||
<i class="file {{ get_file_icon(item.name) }} outline icon"></i> | |||
{{ item.name }} [{{ guess_type(item.name)[0] }}] | |||
</td> | |||
<td><span title="{{ item.size }} byte(s)">{{ format_size(item.size) }}</span></td> | |||
<td>{{ format_date(item.created) }}</td> | |||
% end | |||
</tr> | |||
% end | |||
</tbody> | |||
</table> | |||
</div> | |||
</body> | |||
</html> |