186 lines
6.7 KiB
Python
186 lines
6.7 KiB
Python
import base64
|
|
import os
|
|
from shutil import rmtree
|
|
from wsgiref.util import FileWrapper
|
|
|
|
from django.conf import settings
|
|
from django.core.cache import cache
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.http import Http404, HttpResponse, HttpResponseNotModified, StreamingHttpResponse
|
|
from django.shortcuts import get_object_or_404
|
|
from django.views.decorators.http import etag
|
|
|
|
from c3nav.mapdata.middleware import no_language
|
|
from c3nav.mapdata.models import Level, MapUpdate
|
|
from c3nav.mapdata.models.access import AccessPermission
|
|
from c3nav.mapdata.render.engines import ImageRenderEngine
|
|
from c3nav.mapdata.render.renderer import MapRenderer
|
|
from c3nav.mapdata.utils.cache import CachePackage, MapHistory
|
|
from c3nav.mapdata.utils.tiles import (build_access_cache_key, build_base_cache_key, build_tile_access_cookie,
|
|
build_tile_etag, get_tile_bounds, parse_tile_access_cookie)
|
|
|
|
|
|
def set_tile_access_cookie(request, response):
|
|
access_permissions = AccessPermission.get_for_request(request)
|
|
if access_permissions:
|
|
cookie = build_tile_access_cookie(access_permissions, settings.SECRET_TILE_KEY)
|
|
response.set_cookie(settings.TILE_ACCESS_COOKIE_NAME, cookie, max_age=60,
|
|
domain=settings.TILE_ACCESS_COOKIE_DOMAIN,
|
|
httponly=settings.TILE_ACCESS_COOKIE_HTTPONLY,
|
|
secure=settings.TILE_ACCESS_COOKIE_SECURE)
|
|
else:
|
|
response.delete_cookie(settings.TILE_ACCESS_COOKIE_NAME)
|
|
response['Cache-Control'] = 'no-cache'
|
|
|
|
|
|
encoded_tile_secret = base64.b64encode(settings.SECRET_TILE_KEY.encode()).decode()
|
|
|
|
|
|
def enforce_tile_secret_auth(request):
|
|
x_tile_secret = request.META.get('HTTP_X_TILE_SECRET')
|
|
if x_tile_secret:
|
|
if x_tile_secret != encoded_tile_secret:
|
|
raise PermissionDenied
|
|
elif not request.user.is_superuser:
|
|
raise PermissionDenied
|
|
|
|
|
|
@no_language()
|
|
def tile(request, level, zoom, x, y, access_permissions=None):
|
|
if access_permissions is not None:
|
|
enforce_tile_secret_auth(request)
|
|
elif settings.TILE_CACHE_SERVER:
|
|
return HttpResponse('use %s instead of /map/' % settings.TILE_CACHE_SERVER,
|
|
status=400, content_type='text/plain')
|
|
|
|
zoom = int(zoom)
|
|
if not (-2 <= zoom <= 5):
|
|
raise Http404
|
|
|
|
cache_package = CachePackage.open_cached()
|
|
|
|
# check if bounds are valid
|
|
x = int(x)
|
|
y = int(y)
|
|
minx, miny, maxx, maxy = get_tile_bounds(zoom, x, y)
|
|
if not cache_package.bounds_valid(minx, miny, maxx, maxy):
|
|
raise Http404
|
|
|
|
# get level
|
|
level = int(level)
|
|
level_data = cache_package.levels.get(level)
|
|
if level_data is None:
|
|
raise Http404
|
|
|
|
# decode access permissions
|
|
if access_permissions is None:
|
|
try:
|
|
cookie = request.COOKIES[settings.TILE_ACCESS_COOKIE_NAME]
|
|
except KeyError:
|
|
access_permissions = set()
|
|
else:
|
|
access_permissions = parse_tile_access_cookie(cookie, settings.SECRET_TILE_KEY)
|
|
access_permissions &= set(level_data.restrictions[minx:maxx, miny:maxy])
|
|
else:
|
|
access_permissions = set(int(i) for i in access_permissions.split('-')) - {0}
|
|
|
|
# build cache keys
|
|
last_update = level_data.history.last_update(minx, miny, maxx, maxy)
|
|
base_cache_key = build_base_cache_key(last_update)
|
|
access_cache_key = build_access_cache_key(access_permissions)
|
|
|
|
# check browser cache
|
|
tile_etag = build_tile_etag(level, zoom, x, y, base_cache_key, access_cache_key, settings.SECRET_TILE_KEY)
|
|
if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
|
|
if if_none_match == tile_etag:
|
|
return HttpResponseNotModified()
|
|
|
|
data = None
|
|
tile_dirname, last_update_filename, tile_filename, tile_cache_update_cache_key = '', '', '', ''
|
|
|
|
# get tile cache last update
|
|
if settings.CACHE_TILES:
|
|
tile_dirname = settings.TILES_ROOT / str(level) / str(zoom) / str(x) / str(y)
|
|
last_update_filename = tile_dirname / 'last_update'
|
|
tile_filename = tile_dirname / (access_cache_key+'.png')
|
|
|
|
# get tile cache last update
|
|
tile_cache_update_cache_key = 'mapdata:tile-cache-update:%d-%d-%d-%d' % (level, zoom, x, y)
|
|
tile_cache_update = cache.get(tile_cache_update_cache_key, None)
|
|
if tile_cache_update is None:
|
|
try:
|
|
with open(last_update_filename) as f:
|
|
tile_cache_update = f.read()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
if tile_cache_update != base_cache_key:
|
|
rmtree(tile_dirname)
|
|
else:
|
|
try:
|
|
with open(tile_filename, 'rb') as f:
|
|
data = f.read()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
if data is None:
|
|
renderer = MapRenderer(level, minx, miny, maxx, maxy, scale=2 ** zoom, access_permissions=access_permissions)
|
|
image = renderer.render(ImageRenderEngine)
|
|
data = image.render()
|
|
|
|
if settings.CACHE_TILES:
|
|
os.makedirs(tile_dirname, exist_ok=True)
|
|
with open(tile_filename, 'wb') as f:
|
|
f.write(data)
|
|
with open(last_update_filename, 'w') as f:
|
|
f.write(base_cache_key)
|
|
cache.set(tile_cache_update_cache_key, base_cache_key, 60)
|
|
|
|
response = HttpResponse(data, 'image/png')
|
|
response['ETag'] = tile_etag
|
|
response['Cache-Control'] = 'no-cache'
|
|
response['Vary'] = 'Cookie'
|
|
|
|
return response
|
|
|
|
|
|
@etag(lambda *args, **kwargs: MapUpdate.current_processed_cache_key())
|
|
@no_language()
|
|
def map_history(request, level, mode, filetype):
|
|
if not request.user.is_superuser:
|
|
raise PermissionDenied
|
|
level = get_object_or_404(Level, pk=level)
|
|
|
|
if mode == 'composite' and level.on_top_of_id is not None:
|
|
raise Http404
|
|
|
|
history = MapHistory.open_level(level.pk, mode)
|
|
if filetype == 'png':
|
|
response = HttpResponse(content_type='image/png')
|
|
history.to_image().save(response, format='PNG')
|
|
elif filetype == 'data':
|
|
response = HttpResponse(content_type='application/octet-stream')
|
|
history.write(response)
|
|
else:
|
|
raise ValueError
|
|
response['Cache-Control'] = 'no-cache'
|
|
return response
|
|
|
|
|
|
@etag(lambda *args, **kwargs: MapUpdate.current_processed_cache_key())
|
|
@no_language()
|
|
def get_cache_package(request, filetype):
|
|
enforce_tile_secret_auth(request)
|
|
|
|
filename = settings.CACHE_ROOT / ('package.'+filetype)
|
|
f = open(filename, 'rb')
|
|
|
|
f.seek(0, os.SEEK_END)
|
|
size = f.tell()
|
|
f.seek(0)
|
|
|
|
content_type = 'application/' + {'tar': 'x-tar', 'tar.gz': 'gzip', 'tar.xz': 'x-xz'}[filetype]
|
|
|
|
response = StreamingHttpResponse(FileWrapper(f), content_type=content_type)
|
|
response['Content-Length'] = size
|
|
return response
|