UX: ensure we only allow images when uploading an avatar, user card background, etc...

This commit is contained in:
Régis Hanol 2017-01-02 11:37:56 +01:00
parent 5db57abd7f
commit d8be3e8bb1
6 changed files with 84 additions and 38 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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 '<img src="' + upload.url + '" width="' + upload.width + '" height="' + upload.height + '">';
} 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 '<a class="attachment" href="' + upload.url + '">' + upload.original_filename + '</a> (' + 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) {

View File

@ -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;

View File

@ -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."