From bec124bf8be1bee61c11b802c864a069d27a4b2b Mon Sep 17 00:00:00 2001 From: syn Date: Wed, 18 Mar 2020 20:24:53 +0300 Subject: More exceptions --- app/templates/404.html.j2 | 6 ++++++ app/util.py | 36 ++++++++++++++++++++++++-------- app/views.py | 52 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 app/templates/404.html.j2 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 %} +
+

404

+
+{% 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/') 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/') 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/') 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) -- cgit v1.2.1-18-gbd029