summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/templates/404.html.j26
-rw-r--r--app/util.py36
-rw-r--r--app/views.py52
3 files changed, 74 insertions, 20 deletions
diff --git a/app/templates/404.html.j2 b/app/templates/404.html.j2
new file mode 100644
index 0000000..dd04c11
--- /dev/null
+++ b/app/templates/404.html.j2
@@ -0,0 +1,6 @@
+{% extends "base.html.j2" %}
+{% block content %}
+<center>
+ <p style="font-size: 4.04em;">404</p>
+</center>
+{% endblock %} \ No newline at end of file
diff --git a/app/util.py b/app/util.py
index 69b3ea7..69593ac 100644
--- a/app/util.py
+++ b/app/util.py
@@ -1,10 +1,15 @@
from base64 import b32encode, b32decode
import logging
-from hashlib import blake2b # because it is fast as fuck
+from hashlib import blake2b
+from binascii import Error as Base32DecodeError
log = logging.getLogger('blure')
+class URLDecodeError(ValueError):
+ pass
+
+
class URLCoder:
'''
URLCoder shuffles sequential ids into non-sequential strings,
@@ -16,17 +21,21 @@ class URLCoder:
_endian = 'big'
@classmethod
- def _humanify(self, data: bytes):
+ def _humanify(cls, data: bytes):
b32 = b32encode(data).decode('ascii')
- if self._pad > 0:
- return b32[:-self._pad]
+ if cls._pad > 0:
+ return b32[:-cls._pad]
else:
return b32
@classmethod
- def _dehumanify(self, data: str):
- binary = b32decode(data + '=' * self._pad)
- assert len(binary) == self._bs * 2
+ def _dehumanify(cls, data: str):
+ binary = b32decode(data + '=' * cls._pad)
+ if len(binary) != cls._bs * 2:
+ raise URLDecodeError(
+ f'Padded url len must be exactly {cls._bs * 2} bytes'
+ f'(binary="{binary}", len = {len(binary)})'
+ )
return binary
def __init__(self, secret: bytes):
@@ -39,13 +48,22 @@ class URLCoder:
self.reverse_keys = self.keys[::-1]
def to_url(self, id: int) -> str:
- assert 0 <= id and id <= self._bound
+ if 0 > id or id >= self._bound:
+ raise URLDecodeError(
+ 'id(={id}) is outside valid range [0, {self._bound})'
+ )
+
as_bytes = int.to_bytes(id, self._bs * 2, self._endian)
encoded = self._blake_enc(as_bytes, self.keys)
+
return self._humanify(encoded)
def to_id(self, url: str) -> int:
- binary = self._dehumanify(url.upper())
+ try:
+ binary = self._dehumanify(url.upper())
+ except Base32DecodeError as e:
+ raise URLDecodeError(e)
+
as_bytes = self._blake_enc(binary, self.reverse_keys)
return int.from_bytes(as_bytes, self._endian)
diff --git a/app/views.py b/app/views.py
index 8ffb946..8cb0755 100644
--- a/app/views.py
+++ b/app/views.py
@@ -2,9 +2,21 @@ from io import BytesIO
from .request_routine import db_route
from sanic.response import text, redirect, json
from jinja2_sanic import render_template
-from app import log
+from app import log, blure
from requests import get as fetch_url
from .imutil import NGXImage
+from .util import URLDecodeError
+from sanic.exceptions import NotFound
+
+
+async def is_image_exists(pg, id: int):
+ rec = await pg.fetchval('SELECT 1 FROM pics WHERE id=$1', id)
+ return rec is not None
+
+
+@blure.exception(NotFound)
+async def not_found(req, exc):
+ return render_template('404.html.j2', req, dict())
@db_route('/')
@@ -16,12 +28,13 @@ async def index(ctx):
@db_route('/i/<url>')
async def raw_image(ctx, url):
- id = ctx.app.url.to_id(url)
- if id is None:
+ try:
+ id = ctx.app.url.to_id(url)
+ except URLDecodeError as err:
+ log.warn(f'URL not decoded: {err}')
return NGXImage.not_found()
- name = await ctx.pg.fetchval('SELECT 1 FROM pics WHERE id=$1', id)
- if name is None:
+ if not await is_image_exists(ctx.pg, id):
log.error('Image not in db')
return NGXImage.not_found()
@@ -30,12 +43,12 @@ async def raw_image(ctx, url):
@db_route('/t/<url>')
async def thumb_image(ctx, url):
- id = ctx.app.url.to_id(url)
- if id is None:
+ try:
+ id = ctx.app.url.to_id(url)
+ except URLDecodeError:
return NGXImage.not_found()
- name = await ctx.pg.fetchval('SELECT 1 FROM pics WHERE id=$1', id)
- if name is None:
+ if not await is_image_exists(ctx.pg, id):
log.error('Image not in db')
return NGXImage.not_found()
@@ -44,7 +57,18 @@ async def thumb_image(ctx, url):
@db_route('/p/<url>')
async def pic_profile(ctx, url):
- return render_template('profile.html.j2', ctx.r, dict(url=url, tags=[]))
+ log.warn(url)
+ try:
+ id = ctx.app.url.to_id(url)
+ log.warn(id)
+ if await is_image_exists(ctx.pg, id):
+ return render_template('profile.html.j2',
+ ctx.r,
+ dict(url=url, tags=[]))
+ else:
+ raise NotFound('Url not found')
+ except URLDecodeError:
+ raise NotFound('Invalid url')
@db_route('/c/push', methods=['POST'])
@@ -54,7 +78,13 @@ async def pic_push(ctx):
image_stream = BytesIO(file.body)
ext = file.name.split('.')[-1]
id = await ctx.pg.fetchval(
- 'INSERT INTO pics(src_url, ext) VALUES ($1, $2) RETURNING id', '', ext
+ '''
+ INSERT INTO pics(src_url, ext)
+ VALUES ($1, $2)
+ RETURNING id
+ ''',
+ '',
+ ext
)
im = NGXImage(id)