diff --git a/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 b/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 index 539171bc9a1..fd5bac3c0e0 100644 --- a/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/avatar-uploader.js.es6 @@ -11,6 +11,10 @@ export default Em.Component.extend(UploadMixin, { return uploading ? I18n.t("uploading") : I18n.t("user.change_avatar.upload_picture"); }, + validateUploadedFilesOptions() { + return { imagesOnly: true }; + }, + uploadDone(upload) { this.setProperties({ imageIsNotASquare: upload.width !== upload.height, diff --git a/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 b/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 index b43bea04103..0afeb1a239c 100644 --- a/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 @@ -11,6 +11,10 @@ export default Em.Component.extend(UploadMixin, { return Ember.isBlank(this.get("name")) ? {} : { name: this.get("name") }; }.property("name"), + validateUploadedFilesOptions() { + return { imagesOnly: true }; + }, + uploadDone(upload) { this.set("name", null); this.sendAction("done", upload); diff --git a/app/assets/javascripts/discourse/components/image-uploader.js.es6 b/app/assets/javascripts/discourse/components/image-uploader.js.es6 index afa00fcfb30..642ba03b3e8 100644 --- a/app/assets/javascripts/discourse/components/image-uploader.js.es6 +++ b/app/assets/javascripts/discourse/components/image-uploader.js.es6 @@ -10,6 +10,10 @@ export default Em.Component.extend(UploadMixin, { return `background-image: url(${imageUrl})`.htmlSafe(); }, + validateUploadedFilesOptions() { + return { imagesOnly: true }; + }, + uploadDone(upload) { this.set("imageUrl", upload.url); this.set("imageId", upload.id); diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6 index 27b55bbdd74..154f92553b5 100644 --- a/app/assets/javascripts/discourse/lib/utilities.js.es6 +++ b/app/assets/javascripts/discourse/lib/utilities.js.es6 @@ -158,7 +158,7 @@ export function setCaretPosition(ctrl, pos) { } } -export function validateUploadedFiles(files, bypassNewUserRestriction) { +export function validateUploadedFiles(files, opts) { if (!files || files.length === 0) { return false; } if (files.length > 1) { @@ -166,29 +166,43 @@ export function validateUploadedFiles(files, bypassNewUserRestriction) { return false; } - var upload = files[0]; + const upload = files[0]; // CHROME ONLY: if the image was pasted, sets its name to a default one if (typeof Blob !== "undefined" && typeof File !== "undefined") { if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; } } - var type = uploadTypeFromFileName(upload.name); + opts = opts || {}; + opts["type"] = uploadTypeFromFileName(upload.name); - return validateUploadedFile(upload, type, bypassNewUserRestriction); + return validateUploadedFile(upload, opts); } -export function validateUploadedFile(file, type, bypassNewUserRestriction) { +export function validateUploadedFile(file, opts) { + opts = opts || {}; + + const name = file && file.name; + + if (!name) { return false; } + // check that the uploaded file is authorized - if (!authorizesAllExtensions() && !isAuthorizedUpload(file)) { - bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() })); - return false; + if (opts["imagesOnly"]) { + if (!isAnImage(name) && !isAuthorizedImage(name)) { + bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() })); + return false; + } + } else { + if (!authorizesAllExtensions() && !isAuthorizedFile(name)) { + bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() })); + return false; + } } - if (!bypassNewUserRestriction) { + if (!opts["bypassNewUserRestriction"]) { // ensures that new users can upload a file - if (!Discourse.User.current().isAllowedToUploadAFile(type)) { - bootbox.alert(I18n.t('post.errors.' + type + '_upload_not_allowed_for_new_user')); + if (!Discourse.User.current().isAllowedToUploadAFile(opts["type"])) { + bootbox.alert(I18n.t(`post.errors.${opts["type"]}_upload_not_allowed_for_new_user`)); return false; } } @@ -197,13 +211,7 @@ export function validateUploadedFile(file, type, bypassNewUserRestriction) { return true; } -export function uploadTypeFromFileName(fileName) { - return isAnImage(fileName) ? 'image' : 'attachment'; -} - -export function authorizesAllExtensions() { - return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0; -} +const IMAGES_EXTENSIONS_REGEX = /(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)/i; function extensions() { return Discourse.SiteSettings.authorized_extensions @@ -213,16 +221,52 @@ function extensions() { .filter(ext => ext.indexOf("*") === -1); } +function imagesExtensions() { + return extensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext)); +} + function extensionsRegex() { return new RegExp("\\.(" + extensions().join("|") + ")$", "i"); } -export function isAuthorizedUpload(file) { - return file && file.name && extensionsRegex().test(file.name); +function imagesExtensionsRegex() { + return new RegExp("\\.(" + imagesExtensions().join("|") + ")$", "i"); +} + +function isAuthorizedFile(fileName) { + return extensionsRegex().test(fileName); +} + +function isAuthorizedImage(fileName){ + return imagesExtensionsRegex().test(fileName); } export function authorizedExtensions() { - return extensions().join(", "); + return authorizesAllExtensions() ? "*" : extensions().join(", "); +} + +export function authorizedImagesExtensions() { + return authorizesAllExtensions() ? "png, jpg, jpeg, gif, bmp, tiff, svg, webp, ico" : imagesExtensions().join(", "); +} + +export function authorizesAllExtensions() { + return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0; +} + +export function isAnImage(path) { + return (/\.(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)$/i).test(path); +} + +function uploadTypeFromFileName(fileName) { + return isAnImage(fileName) ? 'image' : 'attachment'; +} + +export function allowsImages() { + return authorizesAllExtensions() || IMAGES_EXTENSIONS_REGEX.test(authorizedExtensions()); +} + +export function allowsAttachments() { + return authorizesAllExtensions() || extensions().length > imagesExtensions().length; } export function uploadLocation(url) { @@ -243,27 +287,12 @@ export function getUploadMarkdown(upload) { if (isAnImage(upload.original_filename)) { return ''; } else if (!Discourse.SiteSettings.prevent_anons_from_downloading_files && (/\.(mov|mp4|webm|ogv|mp3|ogg|wav|m4a)$/i).test(upload.original_filename)) { - // is Audio/Video return uploadLocation(upload.url); } else { return '' + upload.original_filename + ' (' + I18n.toHumanSize(upload.filesize) + ')\n'; } } -export function isAnImage(path) { - return (/\.(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)$/i).test(path); -} - -export function allowsImages() { - return authorizesAllExtensions() || - (/(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)/i).test(authorizedExtensions()); -} - -export function allowsAttachments() { - return authorizesAllExtensions() || - !/^((png|jpe?g|gif|bmp|tiff?|svg|webp|ico)(,\s)?)+$/i.test(authorizedExtensions()); -} - export function displayErrorForUpload(data) { // deal with meaningful errors first if (data.jqXHR) { diff --git a/app/assets/javascripts/discourse/mixins/upload.js.es6 b/app/assets/javascripts/discourse/mixins/upload.js.es6 index 648d18dfd98..ec76aa1e660 100644 --- a/app/assets/javascripts/discourse/mixins/upload.js.es6 +++ b/app/assets/javascripts/discourse/mixins/upload.js.es6 @@ -8,6 +8,10 @@ export default Em.Mixin.create({ Em.warn("You should implement `uploadDone`"); }, + validateUploadedFilesOptions() { + return {}; + }, + _initialize: function() { const $upload = this.$(), csrf = Discourse.Session.currentProp("csrfToken"), @@ -40,7 +44,8 @@ export default Em.Mixin.create({ }); $upload.on("fileuploadsubmit", (e, data) => { - const isValid = validateUploadedFiles(data.files, true); + const opts = _.merge({ bypassNewUserRestriction: true }, this.validateUploadedFilesOptions()); + const isValid = validateUploadedFiles(data.files, opts); let form = { type: this.get("type") }; if (this.get("data")) { form = $.extend(form, this.get("data")); } data.formData = form; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 811a806778c..70d62b11e97 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1737,7 +1737,7 @@ en: file_too_large: "Sorry, that file is too big (maximum size is {{max_size_kb}}kb). Why not upload your large file to a cloud sharing service, then share the link?" too_many_uploads: "Sorry, you can only upload one file at a time." too_many_dragged_and_dropped_files: "Sorry, you can only upload 10 files at a time." - upload_not_authorized: "Sorry, the file you are trying to upload is not authorized (authorized extension: {{authorized_extensions}})." + upload_not_authorized: "Sorry, the file you are trying to upload is not authorized (authorized extensions: {{authorized_extensions}})." image_upload_not_allowed_for_new_user: "Sorry, new users can not upload images." attachment_upload_not_allowed_for_new_user: "Sorry, new users can not upload attachments." attachment_download_requires_login: "Sorry, you need to be logged in to download attachments."