diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 8075c367d6..ca42a5a432 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -52,7 +52,7 @@ from seahub.group.views import group_check, remove_group_common, \ rename_group_with_new_name from seahub.group.utils import BadGroupNameError, ConflictGroupNameError, \ validate_group_name -from seahub.thumbnail.utils import allow_generate_thumbnail, generate_thumbnail +from seahub.thumbnail.utils import generate_thumbnail from seahub.message.models import UserMessage from seahub.notifications.models import UserNotification from seahub.options.models import UserOptions @@ -90,7 +90,7 @@ if HAS_OFFICE_CONVERTER: from seahub.utils import query_office_convert_status, prepare_converted_html import seahub.settings as settings from seahub.settings import THUMBNAIL_EXTENSION, THUMBNAIL_ROOT, \ - ENABLE_GLOBAL_ADDRESSBOOK, FILE_LOCK_EXPIRATION_DAYS + ENABLE_GLOBAL_ADDRESSBOOK, FILE_LOCK_EXPIRATION_DAYS, ENABLE_THUMBNAIL try: from seahub.settings import CLOUD_MODE except ImportError: @@ -4361,7 +4361,6 @@ class ThumbnailView(APIView): return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.') size = request.GET.get('size', None) - path = request.GET.get('p', None) if size is None: return api_error(status.HTTP_400_BAD_REQUEST, 'Size is missing.') @@ -4371,27 +4370,32 @@ class ThumbnailView(APIView): logger.error(e) return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid size.') + path = request.GET.get('p', None) obj_id = get_file_id_by_path(repo_id, path) if path is None or obj_id is None: return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong path.') - if check_folder_permission(request, repo_id, path) is None: + if repo.encrypted or not ENABLE_THUMBNAIL or \ + check_folder_permission(request, repo_id, path) is None: return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.') - if not allow_generate_thumbnail(request, repo_id, path): - return api_error(status.HTTP_403_FORBIDDEN, 'Not allowed to generate thumbnail.') + success, status_code = generate_thumbnail(request, repo_id, size, path) + if success: + thumbnail_dir = os.path.join(THUMBNAIL_ROOT, str(size)) + thumbnail_file = os.path.join(thumbnail_dir, obj_id) + try: + with open(thumbnail_file, 'rb') as f: + thumbnail = f.read() + return HttpResponse(thumbnail, 'image/' + THUMBNAIL_EXTENSION) + except IOError as e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Failed to get thumbnail.') else: - if generate_thumbnail(request, repo_id, size, path): - thumbnail_dir = os.path.join(THUMBNAIL_ROOT, str(size)) - thumbnail_file = os.path.join(thumbnail_dir, obj_id) - try: - with open(thumbnail_file, 'rb') as f: - thumbnail = f.read() - return HttpResponse(thumbnail, 'image/' + THUMBNAIL_EXTENSION) - except IOError as e: - logger.error(e) - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Failed to get thumbnail.') - else: + if status_code == 400: + return api_error(status.HTTP_400_BAD_REQUEST, "Invalid argument") + if status_code == 403: + return api_error(status.HTTP_403_FORBIDDEN, 'Forbidden') + if status_code == 500: return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Failed to generate thumbnail.') _REPO_ID_PATTERN = re.compile(r'[-0-9a-f]{36}') diff --git a/seahub/settings.py b/seahub/settings.py index c13879d67c..fc310e6667 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -463,7 +463,7 @@ THUMBNAIL_EXTENSION = 'png' THUMBNAIL_DEFAULT_SIZE = 48 # size(MB) limit for generate thumbnail -THUMBNAIL_IMAGE_COMPRESSED_SIZE_LIMIT = 1 +THUMBNAIL_IMAGE_SIZE_LIMIT = 20 THUMBNAIL_IMAGE_ORIGINAL_SIZE_LIMIT = 256 ##################### diff --git a/seahub/templates/view_shared_dir.html b/seahub/templates/view_shared_dir.html index e7d07b9f60..ca5f597e10 100644 --- a/seahub/templates/view_shared_dir.html +++ b/seahub/templates/view_shared_dir.html @@ -56,7 +56,7 @@ {% for dirent in file_list %} - {% if dirent.allow_generate_thumbnail %} + {% if dirent.is_img %} {% if dirent.encoded_thumbnail_src %} {% trans {% else %} diff --git a/seahub/thumbnail/utils.py b/seahub/thumbnail/utils.py index e46bebca69..35e3d1fc01 100644 --- a/seahub/thumbnail/utils.py +++ b/seahub/thumbnail/utils.py @@ -8,12 +8,10 @@ from PIL import Image from seaserv import get_file_id_by_path, get_repo, get_file_size, \ seafile_api -from seahub.utils import get_file_type_and_ext, gen_inner_file_get_url -from seahub.utils.file_types import IMAGE +from seahub.utils import gen_inner_file_get_url -from seahub.settings import ENABLE_THUMBNAIL, THUMBNAIL_EXTENSION, \ - THUMBNAIL_IMAGE_COMPRESSED_SIZE_LIMIT, THUMBNAIL_ROOT, \ - THUMBNAIL_IMAGE_ORIGINAL_SIZE_LIMIT +from seahub.settings import THUMBNAIL_IMAGE_SIZE_LIMIT, \ + THUMBNAIL_EXTENSION, THUMBNAIL_ROOT, THUMBNAIL_IMAGE_ORIGINAL_SIZE_LIMIT # Get an instance of a logger logger = logging.getLogger(__name__) @@ -24,60 +22,20 @@ def get_thumbnail_src(repo_id, size, path): def get_share_link_thumbnail_src(token, size, path): return posixpath.join("thumbnail", token, str(size), path.lstrip('/')) -def allow_generate_thumbnail(request, repo_id, path): - """check if thumbnail is allowed - """ - - # get file type - obj_name = os.path.basename(path) - file_type, file_ext = get_file_type_and_ext(obj_name) - - # get file size - file_id = get_file_id_by_path(repo_id, path) - if not file_id: - return False - - repo = get_repo(repo_id) - file_size = get_file_size(repo.store_id, repo.version, file_id) - - if repo.encrypted or file_type != IMAGE or not ENABLE_THUMBNAIL: - return False - - # check image compressed size limit - if file_size < THUMBNAIL_IMAGE_COMPRESSED_SIZE_LIMIT * 1024**2: - return True - - # get image memory cost - token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'view', - '', use_onetime = True) - - inner_path = gen_inner_file_get_url(token, obj_name) - try: - image_file = urllib2.urlopen(inner_path) - f = StringIO(image_file.read()) - image = Image.open(f) - width, height = image.size - - # check image memory cost size limit - # use RGBA as default mode(4x8-bit pixels, true colour with transparency mask) - # every pixel will cost 4 byte in RGBA mode - image_memory_cost = width * height * 4 / 1024 / 1024 - if image_memory_cost < THUMBNAIL_IMAGE_ORIGINAL_SIZE_LIMIT: - return True - - except Exception as e: - logger.error(e) - return False - def generate_thumbnail(request, repo_id, size, path): """ generate and save thumbnail if not exist + + before generate thumbnail, you should check: + 1. if repo exist: should exist; + 2. if repo is encrypted: not encrypted; + 3. if ENABLE_THUMBNAIL: enabled; """ try: size = int(size) except ValueError as e: logger.error(e) - return False + return (False, 400) thumbnail_dir = os.path.join(THUMBNAIL_ROOT, str(size)) if not os.path.exists(thumbnail_dir): @@ -85,12 +43,16 @@ def generate_thumbnail(request, repo_id, size, path): file_id = get_file_id_by_path(repo_id, path) if not file_id: - return False + return (False, 400) thumbnail_file = os.path.join(thumbnail_dir, file_id) - if os.path.exists(thumbnail_file): - return True + return (True, 200) + + repo = get_repo(repo_id) + file_size = get_file_size(repo.store_id, repo.version, file_id) + if file_size > THUMBNAIL_IMAGE_SIZE_LIMIT * 1024**2: + return (False, 403) token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'view', '', use_onetime = True) @@ -100,11 +62,20 @@ def generate_thumbnail(request, repo_id, size, path): image_file = urllib2.urlopen(inner_path) f = StringIO(image_file.read()) image = Image.open(f) + + # check image memory cost size limit + # use RGBA as default mode(4x8-bit pixels, true colour with transparency mask) + # every pixel will cost 4 byte in RGBA mode + width, height = image.size + image_memory_cost = width * height * 4 / 1024 / 1024 + if image_memory_cost > THUMBNAIL_IMAGE_ORIGINAL_SIZE_LIMIT: + return (False, 403) + if image.mode not in ["1", "L", "P", "RGB", "RGBA"]: image = image.convert("RGB") image.thumbnail((size, size), Image.ANTIALIAS) image.save(thumbnail_file, THUMBNAIL_EXTENSION) - return True + return (True, 200) except Exception as e: logger.error(e) - return False + return (False, 500) diff --git a/seahub/thumbnail/views.py b/seahub/thumbnail/views.py index 034c10bfc6..d861e0b4d1 100644 --- a/seahub/thumbnail/views.py +++ b/seahub/thumbnail/views.py @@ -9,14 +9,14 @@ from django.utils.http import urlquote from django.http import HttpResponse from django.views.decorators.http import condition -from seaserv import get_repo, get_file_id_by_path, seafile_api +from seaserv import get_repo, get_file_id_by_path from seahub.auth.decorators import login_required_ajax, login_required from seahub.views import check_folder_permission from seahub.settings import THUMBNAIL_DEFAULT_SIZE, THUMBNAIL_EXTENSION, \ - THUMBNAIL_ROOT -from seahub.thumbnail.utils import allow_generate_thumbnail, \ - generate_thumbnail, get_thumbnail_src, get_share_link_thumbnail_src + THUMBNAIL_ROOT, ENABLE_THUMBNAIL +from seahub.thumbnail.utils import generate_thumbnail, \ + get_thumbnail_src, get_share_link_thumbnail_src from seahub.share.models import FileShare # Get an instance of a logger @@ -33,37 +33,33 @@ def thumbnail_create(request, repo_id): result = {} repo = get_repo(repo_id) - path = request.GET.get('path', None) - if not repo: err_msg = _(u"Library does not exist.") - return HttpResponse(json.dumps({"error": err_msg}), status=403, + return HttpResponse(json.dumps({"error": err_msg}), status=400, content_type=content_type) + path = request.GET.get('path', None) if not path: err_msg = _(u"Invalid arguments.") - return HttpResponse(json.dumps({"error": err_msg}), status=403, + return HttpResponse(json.dumps({"error": err_msg}), status=400, content_type=content_type) - if check_folder_permission(request, repo_id, path) is None: + if repo.encrypted or not ENABLE_THUMBNAIL or \ + check_folder_permission(request, repo_id, path) is None: err_msg = _(u"Permission denied.") return HttpResponse(json.dumps({"error": err_msg}), status=403, content_type=content_type) - if not allow_generate_thumbnail(request, repo_id, path): - err_msg = _(u"Not allowed to generate thumbnail.") - return HttpResponse(json.dumps({"error": err_msg}), status=403, - content_type=content_type) - size = request.GET.get('size', THUMBNAIL_DEFAULT_SIZE) - if generate_thumbnail(request, repo_id, size, path): + success, status_code = generate_thumbnail(request, repo_id, size, path) + if success: src = get_thumbnail_src(repo_id, size, path) result['encoded_thumbnail_src'] = urlquote(src) return HttpResponse(json.dumps(result), content_type=content_type) else: err_msg = _('Failed to create thumbnail.') - return HttpResponse(json.dumps({'err_msg': err_msg}), status=500, - content_type=content_type) + return HttpResponse(json.dumps({'err_msg': err_msg}), + status=status_code, content_type=content_type) def latest_entry(request, repo_id, size, path): obj_id = get_file_id_by_path(repo_id, path) @@ -88,28 +84,38 @@ def thumbnail_get(request, repo_id, size, path): return thumbnail file to web """ + repo = get_repo(repo_id) + obj_id = get_file_id_by_path(repo_id, path) + + # check if file exist + if not repo or not obj_id: + return HttpResponse() + + # check if is allowed + if repo.encrypted or not ENABLE_THUMBNAIL or \ + check_folder_permission(request, repo_id, path) is None: + return HttpResponse() + try: size = int(size) except ValueError as e: logger.error(e) return HttpResponse() - obj_id = get_file_id_by_path(repo_id, path) - if check_folder_permission(request, repo_id, path) is None \ - or obj_id is None: - return HttpResponse() - + success = True thumbnail_file = os.path.join(THUMBNAIL_ROOT, str(size), obj_id) + if not os.path.exists(thumbnail_file): + success, status_code = generate_thumbnail(request, repo_id, size, path) - if not os.path.exists(thumbnail_file) and \ - allow_generate_thumbnail(request, repo_id, path): - generate_thumbnail(request, repo_id, size, path) - try: - with open(thumbnail_file, 'rb') as f: - thumbnail = f.read() - return HttpResponse(content=thumbnail, mimetype='image/'+THUMBNAIL_EXTENSION) - except IOError as e: - logger.error(e) + if success: + try: + with open(thumbnail_file, 'rb') as f: + thumbnail = f.read() + return HttpResponse(content=thumbnail, mimetype='image/'+THUMBNAIL_EXTENSION) + except IOError as e: + logger.error(e) + return HttpResponse() + else: return HttpResponse() def share_link_thumbnail_create(request, token): @@ -124,20 +130,25 @@ def share_link_thumbnail_create(request, token): fileshare = FileShare.objects.get_valid_file_link_by_token(token) if not fileshare: err_msg = _(u"Invalid token.") - return HttpResponse(json.dumps({"error": err_msg}), status=403, + return HttpResponse(json.dumps({"error": err_msg}), status=400, content_type=content_type) repo_id = fileshare.repo_id repo = get_repo(repo_id) if not repo: err_msg = _(u"Library does not exist.") + return HttpResponse(json.dumps({"error": err_msg}), status=400, + content_type=content_type) + + if repo.encrypted or not ENABLE_THUMBNAIL: + err_msg = _(u"Permission denied.") return HttpResponse(json.dumps({"error": err_msg}), status=403, content_type=content_type) req_path = request.GET.get('path', None) if not req_path or '../' in req_path: err_msg = _(u"Invalid arguments.") - return HttpResponse(json.dumps({"error": err_msg}), status=403, + return HttpResponse(json.dumps({"error": err_msg}), status=400, content_type=content_type) if fileshare.path == '/': @@ -145,20 +156,16 @@ def share_link_thumbnail_create(request, token): else: real_path = posixpath.join(fileshare.path, req_path.lstrip('/')) - if not allow_generate_thumbnail(request, repo_id, real_path): - err_msg = _(u"Not allowed to generate thumbnail.") - return HttpResponse(json.dumps({"error": err_msg}), status=403, - content_type=content_type) - size = request.GET.get('size', THUMBNAIL_DEFAULT_SIZE) - if generate_thumbnail(request, repo_id, size, real_path): + success, status_code = generate_thumbnail(request, repo_id, size, real_path) + if success: src = get_share_link_thumbnail_src(token, size, req_path) result['encoded_thumbnail_src'] = urlquote(src) return HttpResponse(json.dumps(result), content_type=content_type) else: err_msg = _('Failed to create thumbnail.') - return HttpResponse(json.dumps({'err_msg': err_msg}), status=500, - content_type=content_type) + return HttpResponse(json.dumps({'err_msg': err_msg}), + status=status_code, content_type=content_type) def share_link_latest_entry(request, token, size, path): fileshare = FileShare.objects.get_valid_file_link_by_token(token) @@ -209,18 +216,27 @@ def share_link_thumbnail_get(request, token, size, path): repo_id = fileshare.repo_id repo = get_repo(repo_id) obj_id = get_file_id_by_path(repo_id, image_path) + + # check if file exist if not repo or not obj_id: return HttpResponse() - thumbnail_file = os.path.join(THUMBNAIL_ROOT, str(size), obj_id) - - if not os.path.exists(thumbnail_file) and \ - allow_generate_thumbnail(request, repo_id, image_path): - generate_thumbnail(request, repo_id, size, image_path) - try: - with open(thumbnail_file, 'rb') as f: - thumbnail = f.read() - return HttpResponse(content=thumbnail, mimetype='image/'+THUMBNAIL_EXTENSION) - except IOError as e: - logger.error(e) + # check if is allowed + if repo.encrypted or not ENABLE_THUMBNAIL: + return HttpResponse() + + success = True + thumbnail_file = os.path.join(THUMBNAIL_ROOT, str(size), obj_id) + if not os.path.exists(thumbnail_file): + success, status_code = generate_thumbnail(request, repo_id, size, image_path) + + if success: + try: + with open(thumbnail_file, 'rb') as f: + thumbnail = f.read() + return HttpResponse(content=thumbnail, mimetype='image/'+THUMBNAIL_EXTENSION) + except IOError as e: + logger.error(e) + return HttpResponse() + else: return HttpResponse() diff --git a/seahub/views/ajax.py b/seahub/views/ajax.py index 3353ccf87a..a636f69188 100644 --- a/seahub/views/ajax.py +++ b/seahub/views/ajax.py @@ -60,7 +60,7 @@ from seahub.utils import check_filename_with_rename, EMPTY_SHA1, \ from seahub.utils.repo import get_sub_repo_abbrev_origin_path from seahub.utils.star import star_file, unstar_file from seahub.base.accounts import User -from seahub.thumbnail.utils import get_thumbnail_src, allow_generate_thumbnail +from seahub.thumbnail.utils import get_thumbnail_src from seahub.utils.file_types import IMAGE from seahub.base.templatetags.seahub_tags import translate_seahub_time, \ file_icon_filter, email2nickname, tsstr_sec @@ -481,7 +481,6 @@ def list_lib_dir(request, repo_id): file_type, file_ext = get_file_type_and_ext(f.obj_name) if file_type == IMAGE: f_['is_img'] = True - if not repo.encrypted and ENABLE_THUMBNAIL and \ os.path.exists(os.path.join(THUMBNAIL_ROOT, str(size), f.obj_id)): file_path = posixpath.join(path, f.obj_name) diff --git a/seahub/views/repo.py b/seahub/views/repo.py index 932a498bec..a4a2a9f1c5 100644 --- a/seahub/views/repo.py +++ b/seahub/views/repo.py @@ -38,8 +38,7 @@ from seahub.settings import ENABLE_SUB_LIBRARY, FORCE_SERVER_CRYPTO, \ ENABLE_UPLOAD_FOLDER, ENABLE_RESUMABLE_FILEUPLOAD, ENABLE_THUMBNAIL, THUMBNAIL_ROOT, THUMBNAIL_DEFAULT_SIZE from seahub.utils import gen_file_get_url from seahub.utils.file_types import IMAGE -from seahub.thumbnail.utils import get_thumbnail_src, \ - allow_generate_thumbnail, get_share_link_thumbnail_src +from seahub.thumbnail.utils import get_thumbnail_src, get_share_link_thumbnail_src # Get an instance of a logger logger = logging.getLogger(__name__) @@ -244,9 +243,10 @@ def render_repo(request, repo): dir_shared_upload_link = get_dir_shared_upload_link(uploadlink) for f in file_list: - file_path = posixpath.join(path, f.obj_name) - if allow_generate_thumbnail(request, repo.id, file_path): - f.allow_generate_thumbnail = True + file_type, file_ext = get_file_type_and_ext(f.obj_name) + if file_type == IMAGE: + f.is_img = True + file_path = posixpath.join(path, f.obj_name) if os.path.exists(os.path.join(THUMBNAIL_ROOT, str(THUMBNAIL_DEFAULT_SIZE), f.obj_id)): src = get_thumbnail_src(repo.id, THUMBNAIL_DEFAULT_SIZE, file_path) f.encoded_thumbnail_src = urlquote(src) @@ -480,19 +480,15 @@ def view_shared_dir(request, token): traffic_over_limit = user_traffic_over_limit(fileshare.username) - for f in file_list: - - file_type, file_ext = get_file_type_and_ext(f.obj_name) - if file_type == IMAGE: - f.is_img = True - - real_image_path = posixpath.join(real_path, f.obj_name) - if allow_generate_thumbnail(request, repo_id, real_image_path): - f.allow_generate_thumbnail = True - if os.path.exists(os.path.join(THUMBNAIL_ROOT, str(THUMBNAIL_DEFAULT_SIZE), f.obj_id)): - req_image_path = posixpath.join(req_path, f.obj_name) - src = get_share_link_thumbnail_src(token, THUMBNAIL_DEFAULT_SIZE, req_image_path) - f.encoded_thumbnail_src = urlquote(src) + if not repo.encrypted and ENABLE_THUMBNAIL: + for f in file_list: + file_type, file_ext = get_file_type_and_ext(f.obj_name) + if file_type == IMAGE: + f.is_img = True + if os.path.exists(os.path.join(THUMBNAIL_ROOT, str(THUMBNAIL_DEFAULT_SIZE), f.obj_id)): + req_image_path = posixpath.join(req_path, f.obj_name) + src = get_share_link_thumbnail_src(token, THUMBNAIL_DEFAULT_SIZE, req_image_path) + f.encoded_thumbnail_src = urlquote(src) return render_to_response('view_shared_dir.html', { 'repo': repo,