implement file size upload limit
This commit is contained in:
parent
7115d5c951
commit
af553c422d
@ -6,6 +6,7 @@ $.fn.imageUpload = function() {
|
|||||||
var originalUrl = $img.prop('src');
|
var originalUrl = $img.prop('src');
|
||||||
var $input = $(this).find('input');
|
var $input = $(this).find('input');
|
||||||
var id = $input.prop('id');
|
var id = $input.prop('id');
|
||||||
|
var maxSize = $(this).data('max-size');
|
||||||
$uploadButton.click(function(){
|
$uploadButton.click(function(){
|
||||||
$uploadButton.prop('disabled', true);
|
$uploadButton.prop('disabled', true);
|
||||||
var input = document.createElement('input');
|
var input = document.createElement('input');
|
||||||
@ -14,9 +15,20 @@ $.fn.imageUpload = function() {
|
|||||||
|
|
||||||
input.onchange = function(e) {
|
input.onchange = function(e) {
|
||||||
var reader = new FileReader()
|
var reader = new FileReader()
|
||||||
// TODO: implement file size check
|
|
||||||
reader.readAsArrayBuffer(e.target.files[0]);
|
reader.readAsArrayBuffer(e.target.files[0]);
|
||||||
|
reader.onprogress = function(e) {
|
||||||
|
if (e.loaded > maxSize) {
|
||||||
|
console.error('maximum file size exceeded, aborting file upload');
|
||||||
|
$uploadButton.prop('disabled', false);
|
||||||
|
reader.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
|
if (e.loaded > maxSize) {
|
||||||
|
console.error('maximum file size exceeded, aborting file upload');
|
||||||
|
$uploadButton.prop('disabled', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/imageupload?id=' + id,
|
url: '/imageupload?id=' + id,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|
||||||
|
class BodySizeError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
def __init__(self, handler, request, options):
|
def __init__(self, handler, request, options):
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
@ -33,10 +37,12 @@ class Controller(object):
|
|||||||
self.handler.send_header("Location", location)
|
self.handler.send_header("Location", location)
|
||||||
self.handler.end_headers()
|
self.handler.end_headers()
|
||||||
|
|
||||||
def get_body(self):
|
def get_body(self, max_size=None):
|
||||||
if "Content-Length" not in self.handler.headers:
|
if "Content-Length" not in self.handler.headers:
|
||||||
return None
|
return None
|
||||||
length = int(self.handler.headers["Content-Length"])
|
length = int(self.handler.headers["Content-Length"])
|
||||||
|
if max_size is not None and length > max_size:
|
||||||
|
raise BodySizeError("HTTP body exceeds maximum allowed size")
|
||||||
return self.handler.rfile.read(length)
|
return self.handler.rfile.read(length)
|
||||||
|
|
||||||
def handle_request(self):
|
def handle_request(self):
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
|
from owrx.controllers import BodySizeError
|
||||||
from owrx.controllers.assets import AssetsController
|
from owrx.controllers.assets import AssetsController
|
||||||
from owrx.controllers.admin import AuthorizationMixin
|
from owrx.controllers.admin import AuthorizationMixin
|
||||||
from owrx.config.core import CoreConfig
|
from owrx.config.core import CoreConfig
|
||||||
|
from owrx.form.input.gfx import AvatarInput, TopPhotoInput
|
||||||
import uuid
|
import uuid
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class ImageUploadController(AuthorizationMixin, AssetsController):
|
class ImageUploadController(AuthorizationMixin, AssetsController):
|
||||||
|
# max upload filesizes
|
||||||
|
max_sizes = {
|
||||||
|
# not the best idea to instantiate inputs, but i didn't want to duplicate the sizes here
|
||||||
|
"receiver_avatar": AvatarInput("id", "label").getMaxSize(),
|
||||||
|
"receiver_top_photo": TopPhotoInput("id", "label").getMaxSize(),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, handler, request, options):
|
def __init__(self, handler, request, options):
|
||||||
super().__init__(handler, request, options)
|
super().__init__(handler, request, options)
|
||||||
self.file = request.query["file"][0] if "file" in request.query else None
|
self.file = request.query["file"][0] if "file" in request.query else None
|
||||||
@ -29,22 +38,37 @@ class ImageUploadController(AuthorizationMixin, AssetsController):
|
|||||||
|
|
||||||
def processImage(self):
|
def processImage(self):
|
||||||
if "id" not in self.request.query:
|
if "id" not in self.request.query:
|
||||||
self.send_response("{}", content_type="application/json", code=400)
|
self.send_json_response({"error": "missing id"}, code=400)
|
||||||
# TODO: limit file size
|
return
|
||||||
contents = self.get_body()
|
file_id = self.request.query["id"][0]
|
||||||
|
|
||||||
|
if file_id not in ImageUploadController.max_sizes:
|
||||||
|
self.send_json_response({"error": "unexpected image id"}, code=400)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
contents = self.get_body(ImageUploadController.max_sizes[file_id])
|
||||||
|
except BodySizeError:
|
||||||
|
self.send_json_response({"error": "file size too large"}, code=400)
|
||||||
|
return
|
||||||
|
|
||||||
filetype = None
|
filetype = None
|
||||||
if self._is_png(contents):
|
if self._is_png(contents):
|
||||||
filetype = "png"
|
filetype = "png"
|
||||||
if self._is_jpg(contents):
|
if self._is_jpg(contents):
|
||||||
filetype = "jpg"
|
filetype = "jpg"
|
||||||
if filetype is None:
|
if filetype is None:
|
||||||
self.send_response("{}", content_type="application/json", code=400)
|
self.send_json_response({"error": "unsupported file type"}, code=400)
|
||||||
else:
|
return
|
||||||
|
|
||||||
self.file = "{id}-{uuid}.{ext}".format(
|
self.file = "{id}-{uuid}.{ext}".format(
|
||||||
id=self.request.query["id"][0],
|
id=file_id,
|
||||||
uuid=uuid.uuid4().hex,
|
uuid=uuid.uuid4().hex,
|
||||||
ext=filetype,
|
ext=filetype,
|
||||||
)
|
)
|
||||||
with open(self.getFilePath(), "wb") as f:
|
with open(self.getFilePath(), "wb") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
self.send_response(json.dumps({"file": self.file}), content_type="application/json")
|
self.send_json_response({"file": self.file}, code=200)
|
||||||
|
|
||||||
|
def send_json_response(self, obj, code):
|
||||||
|
self.send_response(json.dumps(obj), code=code, content_type="application/json")
|
||||||
|
@ -7,7 +7,7 @@ class ImageInput(Input, metaclass=ABCMeta):
|
|||||||
def render_input(self, value, errors):
|
def render_input(self, value, errors):
|
||||||
# TODO display errors
|
# TODO display errors
|
||||||
return """
|
return """
|
||||||
<div class="imageupload">
|
<div class="imageupload" data-max-size="{maxsize}">
|
||||||
<input type="hidden" id="{id}" name="{id}">
|
<input type="hidden" id="{id}" name="{id}">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<img class="{classes}" src="{url}" alt="{label}"/>
|
<img class="{classes}" src="{url}" alt="{label}"/>
|
||||||
@ -16,7 +16,11 @@ class ImageInput(Input, metaclass=ABCMeta):
|
|||||||
<button type="button" class="btn btn-secondary restore">Restore original image</button>
|
<button type="button" class="btn btn-secondary restore">Restore original image</button>
|
||||||
</div>
|
</div>
|
||||||
""".format(
|
""".format(
|
||||||
id=self.id, label=self.label, url=self.cachebuster(self.getUrl()), classes=" ".join(self.getImgClasses())
|
id=self.id,
|
||||||
|
label=self.label,
|
||||||
|
url=self.cachebuster(self.getUrl()),
|
||||||
|
classes=" ".join(self.getImgClasses()),
|
||||||
|
maxsize=self.getMaxSize(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def cachebuster(self, url: str):
|
def cachebuster(self, url: str):
|
||||||
@ -34,6 +38,10 @@ class ImageInput(Input, metaclass=ABCMeta):
|
|||||||
def getImgClasses(self) -> list:
|
def getImgClasses(self) -> list:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def getMaxSize(self) -> int:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AvatarInput(ImageInput):
|
class AvatarInput(ImageInput):
|
||||||
def getUrl(self) -> str:
|
def getUrl(self) -> str:
|
||||||
@ -42,6 +50,10 @@ class AvatarInput(ImageInput):
|
|||||||
def getImgClasses(self) -> list:
|
def getImgClasses(self) -> list:
|
||||||
return ["webrx-rx-avatar"]
|
return ["webrx-rx-avatar"]
|
||||||
|
|
||||||
|
def getMaxSize(self) -> int:
|
||||||
|
# 256 kB
|
||||||
|
return 250 * 1024
|
||||||
|
|
||||||
|
|
||||||
class TopPhotoInput(ImageInput):
|
class TopPhotoInput(ImageInput):
|
||||||
def getUrl(self) -> str:
|
def getUrl(self) -> str:
|
||||||
@ -49,3 +61,7 @@ class TopPhotoInput(ImageInput):
|
|||||||
|
|
||||||
def getImgClasses(self) -> list:
|
def getImgClasses(self) -> list:
|
||||||
return ["webrx-top-photo"]
|
return ["webrx-top-photo"]
|
||||||
|
|
||||||
|
def getMaxSize(self) -> int:
|
||||||
|
# 2 MB
|
||||||
|
return 2 * 1024 * 1024
|
||||||
|
Loading…
Reference in New Issue
Block a user