diff --git a/ghost/admin/app/components/gh-file-uploader.js b/ghost/admin/app/components/gh-file-uploader.js index 75523c8c47..7aff070130 100644 --- a/ghost/admin/app/components/gh-file-uploader.js +++ b/ghost/admin/app/components/gh-file-uploader.js @@ -4,6 +4,7 @@ import injectService from 'ember-service/inject'; import computed from 'ember-computed'; import {isBlank} from 'ember-utils'; import run from 'ember-runloop'; +import {isEmberArray} from 'ember-array/utils'; import { invoke, invokeAction } from 'ember-invoke-action'; import { @@ -21,7 +22,8 @@ export default Component.extend({ labelText: 'Select or drag-and-drop a file', url: null, paramName: 'file', - accept: 'text/csv', + accept: ['text/csv'], + extensions: ['csv'], validate: null, file: null, @@ -74,6 +76,15 @@ export default Component.extend({ } }, + didReceiveAttrs() { + this._super(...arguments); + let accept = this.get('accept'); + let extensions = this.get('extensions'); + + this._accept = (!isBlank(accept) && !isEmberArray(accept)) ? accept.split(',') : accept; + this._extensions = (!isBlank(extensions) && !isEmberArray(extensions)) ? extensions.split(',') : extensions; + }, + willDestroyElement() { let listenTo = this.get('listenTo'); @@ -187,9 +198,16 @@ export default Component.extend({ }, _defaultValidator(file) { - let accept = this.get('accept'); + let accept = this._accept; - if (!isBlank(accept) && file && accept.indexOf(file.type) === -1) { + 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(); } diff --git a/ghost/admin/app/controllers/settings/labs.js b/ghost/admin/app/controllers/settings/labs.js index 597c86d6c4..b1442e9980 100644 --- a/ghost/admin/app/controllers/settings/labs.js +++ b/ghost/admin/app/controllers/settings/labs.js @@ -1,9 +1,12 @@ import $ from 'jquery'; +import RSVP from 'rsvp'; import Controller from 'ember-controller'; import injectService from 'ember-service/inject'; import {isBlank} from 'ember-utils'; import {isEmberArray} from 'ember-array/utils'; -import {UnsupportedMediaTypeError} from 'ghost-admin/services/ajax'; +import {UnsupportedMediaTypeError, isUnsupportedMediaTypeError} from 'ghost-admin/services/ajax'; + +const {Promise} = RSVP; export default Controller.extend({ uploadButtonText: 'Import', @@ -18,30 +21,67 @@ export default Controller.extend({ session: injectService(), ajax: injectService(), + // TODO: convert to ember-concurrency task + _validate(file) { + // Windows doesn't have mime-types for json files by default, so we + // need to have some additional checking + if (file.type === '') { + // First check file extension so we can early return + let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name); + + if (!extension || extension.toLowerCase() !== 'json') { + return RSVP.reject(new UnsupportedMediaTypeError()); + } + + return new Promise((resolve, reject) => { + // Extension is correct, so check the contents of the file + let reader = new FileReader(); + + reader.onload = function () { + let {result} = reader; + + try { + JSON.parse(result); + + return resolve(); + } catch (e) { + return reject(new UnsupportedMediaTypeError()); + } + }; + + reader.readAsText(file); + }); + } + + let accept = this.get('importMimeType'); + + if (!isBlank(accept) && file && accept.indexOf(file.type) === -1) { + return RSVP.reject(new UnsupportedMediaTypeError()); + } + + return RSVP.resolve(); + }, + actions: { onUpload(file) { let formData = new FormData(); let notifications = this.get('notifications'); let currentUserId = this.get('session.user.id'); let dbUrl = this.get('ghostPaths.url').api('db'); - let accept = this.get('importMimeType'); this.set('uploadButtonText', 'Importing'); this.set('importErrors', ''); - if (!isBlank(accept) && file && accept.indexOf(file.type) === -1) { - this.set('importErrors', [new UnsupportedMediaTypeError()]); - return; - } + return this._validate(file).then(() => { + formData.append('importfile', file); - formData.append('importfile', file); - - this.get('ajax').post(dbUrl, { - data: formData, - dataType: 'json', - cache: false, - contentType: false, - processData: false + return this.get('ajax').post(dbUrl, { + data: formData, + dataType: 'json', + cache: false, + contentType: false, + processData: false + }); }).then(() => { // Clear the store, so that all the new data gets fetched correctly. this.store.unloadAll(); @@ -50,6 +90,11 @@ export default Controller.extend({ // TODO: keep as notification, add link to view content notifications.showNotification('Import successful.', {key: 'import.upload.success'}); }).catch((response) => { + if (isUnsupportedMediaTypeError(response)) { + this.set('importErrors', [response]); + return; + } + if (response && response.errors && isEmberArray(response.errors)) { this.set('importErrors', response.errors); }