diff --git a/.afignore b/.afignore deleted file mode 100644 index a8b40cae30..0000000000 --- a/.afignore +++ /dev/null @@ -1,24 +0,0 @@ -#ignore database -b-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz - -pids -logs -results - -npm-debug.log - -.idea/* -*.iml -projectFilesBackup - -.DS_Store - -# Ghost DB file -*.db \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d7c484c85..9de7cda391 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,12 @@ projectFilesBackup /core/server/data/export/exported* /docs /_site +/content/data/* +/content/plugins/**/* +/content/themes/**/* /content/images/**/* +!/content/themes/casper/** +!README.md # Changelog, which is autogenerated, not committed CHANGELOG.md diff --git a/Gruntfile.js b/Gruntfile.js index 5d783e5c34..618ab19654 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -12,7 +12,16 @@ var path = require('path'), buildGlob = [ '**', '!docs/**', + '!_site/**', + '!content/images/**', + 'content/images/README.md', + '!content/themes/**', + 'content/themes/casper/**', + '!content/plugins/**', + 'content/plugins/README.md', '!node_modules/**', + '!core/test/**', + '!core/client/assets/sass/**', '!**/*.db*', '!*.db*', '!.sass*', @@ -21,6 +30,8 @@ var path = require('path'), '!.groc*', '!*.iml', '!config.js', + '!CONTRIBUTING.md', + '!SECURITY.md', '!.travis.yml' ], @@ -40,7 +51,8 @@ var path = require('path'), dist: distDirectory, nightlyDist: path.join(distDirectory, 'nightly'), weeklyDist: path.join(distDirectory, 'weekly'), - buildDist: path.join(distDirectory, 'build') + buildDist: path.join(distDirectory, 'build'), + releaseDist: path.join(distDirectory, 'release') }, buildType: 'Build', pkg: grunt.file.readJSON('package.json'), @@ -340,6 +352,14 @@ var path = require('path'), expand: true, cwd: '<%= paths.buildBuild %>/', src: ['**'] + }, + release: { + options: { + archive: '<%= paths.releaseDist %>/Ghost-<%= pkg.version %>.zip' + }, + expand: true, + cwd: '<%= paths.buildBuild %>/', + src: ['**'] } }, @@ -813,6 +833,18 @@ var path = require('path'), "compress:build" ]); + grunt.registerTask('release', [ + 'shell:bourbon', + 'sass:admin', + 'handlebars', + 'concat', + 'uglify', + 'changelog', + 'clean:build', + 'copy:build', + 'compress:release' + ]); + // Dev Mode; watch files and restart server on changes grunt.registerTask("dev", [ "sass:admin", diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..be9b0a86cc --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Reporting Security Issues + +If you discover a security issue in Ghost, please report it by sending an email to security[at]ghost[dot]org + +This will allow us to assess the risk, and make a fix available before we add a bug report to the Github repo. + +Thanks for helping make Ghost safe for everyone. \ No newline at end of file diff --git a/core/client/assets/lib/uploader.js b/core/client/assets/lib/uploader.js index 18120bc85f..ee8fde8b9e 100644 --- a/core/client/assets/lib/uploader.js +++ b/core/client/assets/lib/uploader.js @@ -7,7 +7,7 @@ UploadUi = function ($dropzone, settings) { var source, - $url = '
', + $url = '
', $cancel = '', $progress = $('
', { "class" : "js-upload-progress progress progress-success active", @@ -57,7 +57,6 @@ }).attr('src', result); } preLoadImage(); - }, bindFileUpload: function () { @@ -168,17 +167,21 @@ $dropzone.find('div.description').before($url); $dropzone.find('.js-button-accept').on('click', function () { - val = $('#uploadurl').val(); - $dropzone.trigger('uploadstart', [$dropzone.attr('id')]); + val = $dropzone.find('.js-upload-url').val(); $dropzone.find('div.description').hide(); $dropzone.find('.js-fileupload').removeClass('right'); $dropzone.find('.js-url').remove(); $dropzone.find('button.centre').remove(); - self.complete(val); + if (val === "") { + $dropzone.trigger("uploadsuccess", 'http://'); + self.initWithDropzone(); + } else { + self.complete(val); + } }); }, initWithImage: function () { - var self = this; + var self = this, val; // This is the start point if an image already exists source = $dropzone.find('img.js-upload-target').attr('src'); $dropzone.removeClass('image-uploader image-uploader-url').addClass('pre-image-uploader'); @@ -187,6 +190,11 @@ $dropzone.find('.js-cancel').on('click', function () { $dropzone.find('img.js-upload-target').attr({'src': ''}); $dropzone.find('div.description').show(); + $dropzone.delay(2500).animate({opacity: 100}, 1000, function () { + self.init(); + }); + + $dropzone.trigger("uploadsuccess", 'http://'); self.initWithDropzone(); }); }, diff --git a/core/client/views/editor.js b/core/client/views/editor.js index 4ea3165c91..063ca2f076 100644 --- a/core/client/views/editor.js +++ b/core/client/views/editor.js @@ -443,10 +443,9 @@ var filestorage = $('#entry-markdown-content').data('filestorage'); this.$('.js-drop-zone').upload({editor: true, fileStorage: filestorage}); this.$('.js-drop-zone').on('uploadstart', $.proxy(this.disableEditor, this)); - this.$('.js-drop-zone').on('uploadstart', this.uploadMgr.handleDownloadStart); this.$('.js-drop-zone').on('uploadfailure', $.proxy(this.enableEditor, this)); this.$('.js-drop-zone').on('uploadsuccess', $.proxy(this.enableEditor, this)); - this.$('.js-drop-zone').on('uploadsuccess', this.uploadMgr.handleDownloadSuccess); + this.$('.js-drop-zone').on('uploadsuccess', this.uploadMgr.handleUpload); }, enableEditor: function () { @@ -611,7 +610,7 @@ // TODO: hasMarker but no image? } - function handleDownloadStart(e) { + function handleUpload(e, result_src) { /*jslint regexp: true, bitwise: true */ var line = findLine($(e.currentTarget).attr('id')), lineNumber = editor.getLineNumber(line), @@ -636,9 +635,6 @@ ); } } - } - - function handleDownloadSuccess(e, result_src) { editor.replaceSelection(result_src); } @@ -655,8 +651,7 @@ // Public API _.extend(this, { getEditorValue: getEditorValue, - handleDownloadStart: handleDownloadStart, - handleDownloadSuccess: handleDownloadSuccess + handleUpload: handleUpload }); // initialise diff --git a/core/server.js b/core/server.js index 927fe3f153..775efbab28 100644 --- a/core/server.js +++ b/core/server.js @@ -14,6 +14,7 @@ var express = require('express'), hbs = require('express-hbs'), Ghost = require('./ghost'), helpers = require('./server/helpers'), + middleware = require('./server/middleware'), packageInfo = require('../package.json'), // Variables @@ -193,7 +194,7 @@ function activateTheme() { server.set('activeTheme', ghost.settings('activeTheme')); server.enable(server.get('activeTheme')); if (stackLocation) { - server.stack[stackLocation].handle = whenEnabled(server.get('activeTheme'), express['static'](ghost.paths().activeTheme)); + server.stack[stackLocation].handle = whenEnabled(server.get('activeTheme'), middleware.staticTheme(ghost)); } } @@ -265,7 +266,7 @@ when(ghost.init()).then(function () { server.use('/ghost', whenEnabled('admin', express['static'](path.join(__dirname, '/client/assets')))); // Theme only config - server.use(whenEnabled(server.get('activeTheme'), express['static'](ghost.paths().activeTheme))); + server.use(whenEnabled(server.get('activeTheme'), middleware.staticTheme(ghost))); // Add in all trailing slashes server.use(slashes()); diff --git a/core/server/mail.js b/core/server/mail.js index 990bc0608d..a944b22390 100644 --- a/core/server/mail.js +++ b/core/server/mail.js @@ -95,7 +95,7 @@ GhostMailer.prototype.send = function (message) { return when.reject(new Error('Email Error: Incomplete message data.')); } - var from = 'ghost-mailer@' + url.parse(this.ghost.config().url).hostname, + var from = this.ghost.config().mail.fromaddress || this.ghost.settings('email'), to = message.to || this.ghost.settings('email'), sendMail = nodefn.lift(this.transport.sendMail.bind(this.transport)); diff --git a/core/server/middleware.js b/core/server/middleware.js new file mode 100644 index 0000000000..73833da61f --- /dev/null +++ b/core/server/middleware.js @@ -0,0 +1,31 @@ + +var _ = require('underscore'), + express = require('express'), + path = require('path'); + +function isBlackListedFileType(file) { + var blackListedFileTypes = ['.hbs', '.md', '.txt', '.json'], + ext = path.extname(file); + return _.contains(blackListedFileTypes, ext); +} + +var middleware = { + + staticTheme: function (g) { + var ghost = g; + return function blackListStatic(req, res, next) { + if (isBlackListedFileType(req.url)) { + return next(); + } + + return middleware.forwardToExpressStatic(ghost, req, res, next); + }; + }, + + // to allow unit testing + forwardToExpressStatic: function (ghost, req, res, next) { + return express['static'](ghost.paths().activeTheme)(req, res, next); + } +}; + +module.exports = middleware; diff --git a/core/test/unit/middleware_spec.js b/core/test/unit/middleware_spec.js new file mode 100644 index 0000000000..27324ad7a9 --- /dev/null +++ b/core/test/unit/middleware_spec.js @@ -0,0 +1,88 @@ +/*globals describe, beforeEach, it*/ +var assert = require('assert'), + should = require('should'), + sinon = require('sinon'), + when = require('when'), + express = require('express'), + middleware = require('../../server/middleware'); + +describe('Middleware', function () { + describe('staticTheme', function () { + var realExpressStatic = express.static; + + beforeEach(function () { + sinon.stub(middleware, 'forwardToExpressStatic').yields(); + }); + + afterEach(function () { + middleware.forwardToExpressStatic.restore(); + }); + + it('should call next if hbs file type', function (done) { + var req = { + url: 'mytemplate.hbs' + }; + + middleware.staticTheme(null)(req, null, function (a) { + should.not.exist(a); + middleware.forwardToExpressStatic.calledOnce.should.be.false; + return done(); + }); + }); + + it('should call next if md file type', function (done) { + var req = { + url: 'README.md' + }; + + middleware.staticTheme(null)(req, null, function (a) { + should.not.exist(a); + middleware.forwardToExpressStatic.calledOnce.should.be.false; + return done(); + }); + }); + + it('should call next if txt file type', function (done) { + var req = { + url: 'LICENSE.txt' + }; + + middleware.staticTheme(null)(req, null, function (a) { + should.not.exist(a); + middleware.forwardToExpressStatic.calledOnce.should.be.false; + return done(); + }); + }); + + it('should call next if json file type', function (done) { + var req = { + url: 'sample.json' + } + + middleware.staticTheme(null)(req, null, function (a) { + should.not.exist(a); + middleware.forwardToExpressStatic.calledOnce.should.be.false; + return done(); + }); + }); + + it('should call express.static if valid file type', function (done) { + var ghostStub = { + paths: function() { + return {activeTheme: 'ACTIVETHEME'}; + } + }; + + var req = { + url: 'myvalidfile.css' + }; + + middleware.staticTheme(ghostStub)(req, null, function (req, res, next) { + middleware.forwardToExpressStatic.calledOnce.should.be.true; + assert.deepEqual(middleware.forwardToExpressStatic.args[0][0], ghostStub); + return done(); + }); + }); + }); +}); + diff --git a/package.json b/package.json index af42ec5dd4..6ca24b7a18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghost", - "version": "0.3.1", + "version": "0.3.2", "private": true, "scripts": { "start": "node index", @@ -11,8 +11,8 @@ }, "engineStrict": true, "dependencies": { - "express": "3.4.0", - "express-hbs": "0.3.0", + "express": "3.3.4", + "express-hbs": "0.2.2", "connect-slashes": "0.0.9", "node-polyglot": "0.2.1", "moment": "2.1.0", @@ -30,7 +30,10 @@ "downsize": "0.0.2", "validator": "1.4.0", "rss": "0.2.0", - "nodemailer": "~0.5.2" + "nodemailer": "0.5.2" + }, + "optionalDependencies": { + "mysql": "2.0.0-alpha9" }, "devDependencies": { "grunt": "~0.4.1",