remove mime-type validations in favor of extension validation (#256)

refs TryGhost/Ghost#7292
- remove accept mime type validations
- validate on file extension only
- fix tests
This commit is contained in:
Austin Burdine 2016-09-14 03:54:16 -05:00 committed by Kevin Ansfield
parent 00e0bc9a3f
commit 321a2970ea
7 changed files with 62 additions and 59 deletions

View File

@ -198,18 +198,12 @@ export default Component.extend({
},
_defaultValidator(file) {
let accept = this._accept;
if (isBlank(file.type)) {
let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name);
let extensions = this._extensions;
if (!extension || extensions.indexOf(extension.toLowerCase()) === -1) {
return new UnsupportedMediaTypeError();
}
} else if (!isBlank(file.type) && !isBlank(accept) && file && accept.indexOf(file.type) === -1) {
return new UnsupportedMediaTypeError();
}
return true;
},

View File

@ -3,6 +3,7 @@ import computed from 'ember-computed';
import injectService from 'ember-service/inject';
import {htmlSafe} from 'ember-string';
import {isBlank} from 'ember-utils';
import {isEmberArray} from 'ember-array/utils';
import run from 'ember-runloop';
import {invokeAction} from 'ember-invoke-action';
@ -24,6 +25,7 @@ export default Component.extend({
altText: '',
saveButton: true,
accept: 'image/gif,image/jpg,image/jpeg,image/png,image/svg+xml',
extensions: ['gif', 'jpg', 'jpeg', 'png', 'svg'],
validate: null,
dragClass: null,
@ -212,9 +214,14 @@ export default Component.extend({
},
_defaultValidator(file) {
let accept = this.get('accept');
let extensions = this.get('extensions');
let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name);
if (!isBlank(accept) && file && accept.indexOf(file.type) === -1) {
if (!isEmberArray(extensions)) {
extensions = extensions.split(',');
}
if (!extension || extensions.indexOf(extension.toLowerCase()) === -1) {
return new UnsupportedMediaTypeError();
}

View File

@ -6,13 +6,13 @@ import {
UnsupportedMediaTypeError,
isThemeValidationError
} from 'ghost-admin/services/ajax';
import {isBlank} from 'ember-utils';
import run from 'ember-runloop';
import injectService from 'ember-service/inject';
export default ModalComponent.extend({
accept: ['application/zip', 'application/x-zip-compressed'],
extensions: ['zip'],
availableThemes: null,
closeDisabled: false,
file: null,
@ -47,13 +47,15 @@ export default ModalComponent.extend({
actions: {
validateTheme(file) {
let accept = this.get('accept');
let themeName = file.name.replace(/\.zip$/, '');
let availableThemeNames = this.get('availableThemeNames');
this.set('file', file);
if (!isBlank(accept) && file && accept.indexOf(file.type) === -1) {
let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name);
let extensions = this.get('extensions');
if (!extension || extensions.indexOf(extension.toLowerCase()) === -1) {
return new UnsupportedMediaTypeError();
}

View File

@ -246,7 +246,7 @@ describe('Acceptance: Subscribers', function() {
});
click('.btn:contains("Import CSV")');
fileUpload('.fullscreen-modal input[type="file"]', ['test'], {type: 'text/csv'});
fileUpload('.fullscreen-modal input[type="file"]', ['test'], {name: 'test.csv'});
andThen(function () {
// modal title changes

View File

@ -104,7 +104,7 @@ describe('Acceptance: Version Mismatch', function() {
visit('/subscribers');
click('.btn:contains("Import CSV")');
fileUpload('.fullscreen-modal input[type="file"]', ['test'], {type: 'text/csv'});
fileUpload('.fullscreen-modal input[type="file"]', ['test'], {name: 'test.csv'});
andThen(() => {
// alert is shown

View File

@ -91,7 +91,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(server.handledRequests.length).to.equal(1);
@ -107,7 +107,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-file-uploader url=uploadUrl uploadSuccess=(action uploadSuccess)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(uploadSuccess.calledOnce).to.be.true;
@ -123,7 +123,7 @@ describeComponent(
stubFailedUpload(server, 500);
this.render(hbs`{{gh-file-uploader url=uploadUrl uploadSuccess=(action uploadSuccess)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(uploadSuccess.calledOnce).to.be.false;
@ -138,7 +138,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-file-uploader url=uploadUrl fileSelected=(action fileSelected)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(fileSelected.calledOnce).to.be.true;
@ -154,7 +154,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-file-uploader url=uploadUrl uploadStarted=(action uploadStarted)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(uploadStarted.calledOnce).to.be.true;
@ -169,7 +169,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-file-uploader url=uploadUrl uploadFinished=(action uploadFinished)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(uploadFinished.calledOnce).to.be.true;
@ -184,7 +184,7 @@ describeComponent(
stubFailedUpload(server);
this.render(hbs`{{gh-file-uploader url=uploadUrl uploadFinished=(action uploadFinished)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(uploadFinished.calledOnce).to.be.true;
@ -195,7 +195,7 @@ describeComponent(
it('displays invalid file type error', function (done) {
stubFailedUpload(server, 415, 'UnsupportedMediaTypeError');
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -209,7 +209,7 @@ describeComponent(
it('displays file too large for server error', function (done) {
stubFailedUpload(server, 413, 'RequestEntityTooLargeError');
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -223,7 +223,7 @@ describeComponent(
return [413, {}, ''];
});
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -235,7 +235,7 @@ describeComponent(
it('displays other server-side error with message', function (done) {
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -249,7 +249,7 @@ describeComponent(
return [500, {'Content-Type': 'application/json'}, ''];
});
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -265,7 +265,7 @@ describeComponent(
stubFailedUpload(server, 400, 'VersionMismatchError');
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(showAPIError.calledOnce).to.be.true;
@ -279,7 +279,7 @@ describeComponent(
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(showAPIError.called).to.be.false;
@ -290,7 +290,7 @@ describeComponent(
it('can be reset after a failed upload', function (done) {
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
run(() => {
@ -311,7 +311,7 @@ describeComponent(
stubSuccessfulUpload(server, 150);
this.render(hbs`{{gh-file-uploader url=uploadUrl uploadFinished=(action done)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
// after 75ms we should have had one progress event
run.later(this, function () {
@ -347,7 +347,7 @@ describeComponent(
let uploadSuccess = sinon.spy();
let drop = $.Event('drop', {
dataTransfer: {
files: [createFile(['test'], {type: 'text/csv'})]
files: [createFile(['test'], {name: 'test.csv'})]
}
});
@ -367,7 +367,7 @@ describeComponent(
});
});
it('validates "accept" mime type by default', function (done) {
it('validates extension by default', function (done) {
let uploadSuccess = sinon.spy();
let uploadFailed = sinon.spy();
@ -381,7 +381,7 @@ describeComponent(
uploadSuccess=(action uploadSuccess)
uploadFailed=(action uploadFailed)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'application/plain'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.txt'});
wait().then(() => {
expect(uploadSuccess.called).to.be.false;
@ -406,7 +406,7 @@ describeComponent(
uploadSuccess=(action uploadSuccess)
validate=(action validate)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;
@ -432,7 +432,7 @@ describeComponent(
uploadFailed=(action uploadFailed)
validate=(action validate)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'text/csv'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.csv'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;

View File

@ -145,7 +145,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(server.handledRequests.length).to.equal(1);
@ -161,7 +161,7 @@ describeComponent(
this.get('sessionService').set('isAuthenticated', true);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
let [request] = server.handledRequests;
@ -177,7 +177,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(update.calledOnce).to.be.true;
@ -193,7 +193,7 @@ describeComponent(
stubFailedUpload(server, 500);
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(update.calledOnce).to.be.false;
@ -208,7 +208,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader url=image fileSelected=(action fileSelected) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(fileSelected.calledOnce).to.be.true;
@ -224,7 +224,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image uploadStarted=(action uploadStarted) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(uploadStarted.calledOnce).to.be.true;
@ -239,7 +239,7 @@ describeComponent(
stubSuccessfulUpload(server);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(uploadFinished.calledOnce).to.be.true;
@ -254,7 +254,7 @@ describeComponent(
stubFailedUpload(server);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action uploadFinished) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(uploadFinished.calledOnce).to.be.true;
@ -265,7 +265,7 @@ describeComponent(
it('displays invalid file type error', function (done) {
stubFailedUpload(server, 415, 'UnsupportedMediaTypeError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -279,7 +279,7 @@ describeComponent(
it('displays file too large for server error', function (done) {
stubFailedUpload(server, 413, 'RequestEntityTooLargeError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -293,7 +293,7 @@ describeComponent(
return [413, {}, ''];
});
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -305,7 +305,7 @@ describeComponent(
it('displays other server-side error with message', function (done) {
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -319,7 +319,7 @@ describeComponent(
return [500, {'Content-Type': 'application/json'}, ''];
});
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(this.$('.failed').length, 'error message is displayed').to.equal(1);
@ -335,7 +335,7 @@ describeComponent(
stubFailedUpload(server, 400, 'VersionMismatchError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(showAPIError.calledOnce).to.be.true;
@ -349,7 +349,7 @@ describeComponent(
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(showAPIError.called).to.be.false;
@ -360,7 +360,7 @@ describeComponent(
it('can be reset after a failed upload', function (done) {
stubFailedUpload(server, 400, 'UnknownError');
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'test.png'});
wait().then(() => {
run(() => {
@ -381,7 +381,7 @@ describeComponent(
stubSuccessfulUpload(server, 150);
this.render(hbs`{{gh-image-uploader image=image uploadFinished=(action done) update=(action update)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
// after 75ms we should have had one progress event
run.later(this, function () {
@ -419,7 +419,7 @@ describeComponent(
let uploadSuccess = sinon.spy();
let drop = $.Event('drop', {
dataTransfer: {
files: [createFile(['test'], {type: 'image/png'})]
files: [createFile(['test'], {name: 'test.png'})]
}
});
@ -439,7 +439,7 @@ describeComponent(
});
});
it('validates "accept" mime type by default', function (done) {
it('validates extension by default', function (done) {
let uploadSuccess = sinon.spy();
let uploadFailed = sinon.spy();
@ -452,7 +452,7 @@ describeComponent(
uploadSuccess=(action uploadSuccess)
uploadFailed=(action uploadFailed)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'application/plain'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.json'});
wait().then(() => {
expect(uploadSuccess.called).to.be.false;
@ -476,7 +476,7 @@ describeComponent(
uploadSuccess=(action uploadSuccess)
validate=(action validate)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'application/plain'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.txt'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;
@ -501,7 +501,7 @@ describeComponent(
uploadFailed=(action uploadFailed)
validate=(action validate)}}`);
fileUpload(this.$('input[type="file"]'), ['test'], {type: 'image/png'});
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
wait().then(() => {
expect(validate.calledOnce).to.be.true;