From 77074a9f48b916aa14a51f085bef6267574ae22d Mon Sep 17 00:00:00 2001 From: ccd0 Date: Sat, 28 Feb 2015 20:28:14 -0800 Subject: [PATCH] Improve pre-post file checks. Always allow the files to be posted, but provide convienient links to delete the posts in the error messages. --- src/Posting/QR.coffee | 92 ++++---------------------------------- src/Posting/QR.post.coffee | 82 +++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 86 deletions(-) diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index b301abe69..e66b5d594 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -338,93 +338,19 @@ QR = @value = null return unless files.length QR.cleanNotifications() - for file, i in files - QR.handleFile file, i, files.length + for file in files + QR.handleFile file, files.length $.addClass QR.nodes.el, 'dump' unless files.length is 1 - handleFile: (file, index, nfiles) -> - isSingle = nfiles is 1 - if /^text\//.test file.type - if isSingle - post = QR.selected - else if index isnt 0 or (post = QR.posts[QR.posts.length - 1]).com - post = new QR.post() - post.pasteText file - return - unless file.type in QR.mimeTypes - QR.error "#{file.name}: Unsupported file type." - return unless isSingle - max = QR.nodes.fileInput.max - max = Math.min(max, QR.max_size_video) if /^video\//.test file.type - if file.size > max - QR.error "#{file.name}: File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString max})." - return unless isSingle - isNewPost = false - if isSingle + handleFile: (file, nfiles) -> + isText = /^text\//.test file.type + if nfiles is 1 post = QR.selected - else if index isnt 0 or (post = QR.posts[QR.posts.length - 1]).file - isNewPost = true - post = new QR.post() - QR.checkDimensions file, (pass, el) -> - if pass or isSingle - post.setFile file, el - else if isNewPost - post.rm() - URL.revokeObjectURL el.src if el - - checkDimensions: (file, cb) -> - if /^image\//.test file.type - img = new Image() - img.onload = -> - {height, width} = img - pass = true - if height > QR.max_height or width > QR.max_width - QR.error "#{file.name}: Image too large (image: #{height}x#{width}px, max: #{QR.max_height}x#{QR.max_width}px)" - pass = false - if height < QR.min_height or width < QR.min_width - QR.error "#{file.name}: Image too small (image: #{height}x#{width}px, min: #{QR.min_height}x#{QR.min_width}px)" - pass = false - cb pass, img - img.onerror = -> - cb false, null - img.src = URL.createObjectURL file - else if /^video\//.test file.type - video = $.el 'video' - $.on video, 'loadeddata', -> - return unless cb - {videoHeight, videoWidth, duration} = video - max_height = Math.min(QR.max_height, QR.max_height_video) - max_width = Math.min(QR.max_width, QR.max_width_video) - pass = true - if videoHeight > max_height or videoWidth > max_width - QR.error "#{file.name}: Video too large (video: #{videoHeight}x#{videoWidth}px, max: #{max_height}x#{max_width}px)" - pass = false - if videoHeight < QR.min_height or videoWidth < QR.min_width - QR.error "#{file.name}: Video too small (video: #{videoHeight}x#{videoWidth}px, min: #{QR.min_height}x#{QR.min_width}px)" - pass = false - unless isFinite duration - QR.error "#{file.name}: Video lacks duration metadata (try remuxing)" - pass = false - else if duration > QR.max_duration_video - QR.error "#{file.name}: Video too long (video: #{duration}s, max: #{QR.max_duration_video}s)" - pass = false - if g.BOARD.ID not in ['gif', 'wsg'] and $.hasAudio video - QR.error "#{file.name}: Audio not allowed" - pass = false - cb pass, video - cb = null - $.on video, 'error', -> - return unless cb - if file.type in QR.mimeTypes - # only report error here if we should have been able to play the video - # otherwise "unsupported type" should already have been shown - QR.error "#{file.name}: Video appears corrupt" - URL.revokeObjectURL file - cb false, null - cb = null - video.src = URL.createObjectURL file else - cb true, null + post = QR.posts[QR.posts.length - 1] + if post[if isText then 'com' else 'file'] + post = new QR.post() + post[if isText then 'pasteText' else 'setFile'] file openFileInput: -> QR.nodes.fileInput.click() diff --git a/src/Posting/QR.post.coffee b/src/Posting/QR.post.coffee index d32b72adc..16f36c7ed 100644 --- a/src/Posting/QR.post.coffee +++ b/src/Posting/QR.post.coffee @@ -75,6 +75,7 @@ QR.post = class delete: -> $.rm @nodes.el URL.revokeObjectURL @URL + @dismissErrors() lock: (lock=true) -> @isLocked = lock @@ -151,9 +152,35 @@ QR.post = class @save node return - setFile: (@file, el) -> + @rmErrored: -> + for post in QR.posts by -1 when errors = post.errors + for error in errors when doc.contains error + post.rm() + break + return + + error: (className, message) -> + div = $.el 'div', {className} + $.extend div, <%= html('${message}
[delete] [delete all]') %> + (@errors or= []).push div + [rm, rmAll] = $$ 'a', div + $.on rm, 'click', => @rm() if @ in QR.posts + $.on rmAll, 'click', QR.post.rmErrored + QR.error div + + fileError: (message) -> + @error 'file-error', "#{@filename}: #{message}" + + dismissErrors: (test = -> true) -> + if @errors + for error in @errors when doc.contains(error) and test error + error.parentNode.previousElementSibling.click() + return + + setFile: (@file) -> @filename = @file.name @filesize = $.bytesToString @file.size + @checkSize() @nodes.label.hidden = false if QR.spoiler QR.captcha.onPostChange() URL.revokeObjectURL @URL @@ -161,10 +188,57 @@ QR.post = class @showFileData() else @updateFilename() - if el + @nodes.el.style.backgroundImage = null + unless @file.type in QR.mimeTypes + @fileError 'Unsupported file type.' + else if /^(image|video)\//.test @file.type + @readFile() + + checkSize: -> + max = QR.nodes.fileInput.max + max = Math.min(max, QR.max_size_video) if /^video\//.test @file.type + if @file.size > max + @fileError "File too large (file: #{@filesize}, max: #{$.bytesToString max})." + + readFile: -> + isVideo = /^video\//.test @file.type + el = $.el(if isVideo then 'video' else 'img') + event = if isVideo then 'loadeddata' else 'load' + onload = => + $.off el, event, onload + $.off el, 'error', onerror + @checkDimensions el @setThumbnail el + onerror = => + $.off el, event, onload + $.off el, 'error', onerror + @fileError "#{if isVideo then 'Video' else 'Image'} appears corrupt" + URL.revokeObjectURL el.src + $.on el, event, onload + $.on el, 'error', onerror + el.src = URL.createObjectURL @file + + checkDimensions: (el) -> + if el.tagName is 'IMG' + {height, width} = el + if height > QR.max_height or width > QR.max_width + @fileError "Image too large (image: #{height}x#{width}px, max: #{QR.max_height}x#{QR.max_width}px)" + if height < QR.min_height or width < QR.min_width + @fileError "Image too small (image: #{height}x#{width}px, min: #{QR.min_height}x#{QR.min_width}px)" else - @nodes.el.style.backgroundImage = null + {videoHeight, videoWidth, duration} = el + max_height = Math.min(QR.max_height, QR.max_height_video) + max_width = Math.min(QR.max_width, QR.max_width_video) + if videoHeight > max_height or videoWidth > max_width + @fileError "Video too large (video: #{videoHeight}x#{videoWidth}px, max: #{max_height}x#{max_width}px)" + if videoHeight < QR.min_height or videoWidth < QR.min_width + @fileError "Video too small (video: #{videoHeight}x#{videoWidth}px, min: #{QR.min_height}x#{QR.min_width}px)" + unless isFinite duration + @fileError 'Video lacks duration metadata (try remuxing)' + else if duration > QR.max_duration_video + @fileError "Video too long (video: #{duration}s, max: #{QR.max_duration_video}s)" + if g.BOARD.ID not in ['gif', 'wsg'] and $.hasAudio el + @fileError 'Audio not allowed' setThumbnail: (el) -> # Create a redimensioned thumbnail. @@ -185,6 +259,7 @@ QR.post = class @URL = el.src @nodes.el.style.backgroundImage = "url(#{@URL})" return + if height <= width width = s / height * width height = s @@ -211,6 +286,7 @@ QR.post = class @nodes.label.hidden = true if QR.spoiler @showFileData() URL.revokeObjectURL @URL + @dismissErrors (error) -> $.hasClass error, 'file-error' updateFilename: -> long = "#{@filename} (#{@filesize})"