diff --git a/core/server/api/authentication.js b/core/server/api/authentication.js index b76224b9d1..e15e825024 100644 --- a/core/server/api/authentication.js +++ b/core/server/api/authentication.js @@ -216,8 +216,8 @@ authentication = { } function sendResetNotification(data) { - var baseUrl = config.get('forceAdminSSL') ? (config.get('urlSSL') || config.get('url')) : config.get('url'), - resetUrl = globalUtils.url.urlJoin(baseUrl, 'ghost/reset', globalUtils.encodeBase64URLsafe(data.resetToken), '/'); + var baseUrl = config.get('forceAdminSSL') ? globalUtils.url.urlFor('home', {secure: true}, true) : globalUtils.url.urlFor('home', true), + resetUrl = globalUtils.url.urlJoin(baseUrl, '/ghost/reset/', globalUtils.encodeBase64URLsafe(data.resetToken), '/'); return mail.utils.generateContent({ data: { diff --git a/core/server/api/configuration.js b/core/server/api/configuration.js index f44d48e083..2c1b1b5d8c 100644 --- a/core/server/api/configuration.js +++ b/core/server/api/configuration.js @@ -5,6 +5,7 @@ var _ = require('lodash'), ghostVersion = require('../utils/ghost-version'), models = require('../models'), Promise = require('bluebird'), + utils = require('../utils'), configuration; @@ -27,7 +28,7 @@ function getBaseConfig() { fileStorage: config.get('fileStorage') !== false, useGravatar: !config.isPrivacyDisabled('useGravatar'), publicAPI: config.get('publicAPI') === true, - blogUrl: config.get('url').replace(/\/$/, ''), + blogUrl: utils.url.urlFor('home', true), blogTitle: config.get('theme').title, routeKeywords: config.get('routeKeywords') }; diff --git a/core/server/api/invites.js b/core/server/api/invites.js index a9676e34b9..02cc110ab2 100644 --- a/core/server/api/invites.js +++ b/core/server/api/invites.js @@ -106,7 +106,7 @@ invites = { return settings.read({key: 'title'}); }) .then(function (response) { - var baseUrl = config.get('forceAdminSSL') ? (config.get('urlSSL') || config.get('url')) : config.get('url'); + var baseUrl = config.get('forceAdminSSL') ? globalUtils.url.urlFor('home', {secure: true}, true) : globalUtils.url.urlFor('home', true); emailData = { blogName: response.settings[0].value, diff --git a/core/server/api/settings.js b/core/server/api/settings.js index 7aa5bd24bb..35f463d88d 100644 --- a/core/server/api/settings.js +++ b/core/server/api/settings.js @@ -9,7 +9,7 @@ var _ = require('lodash'), logging = require('../logging'), utils = require('./utils'), i18n = require('../i18n'), - generalUtils = require('../utils'), + globalUtils = require('../utils'), docName = 'settings', settings, @@ -65,7 +65,7 @@ updateConfigCache = function () { config.set('theme:twitter', (settingsCache.twitter && settingsCache.twitter.value) || ''); config.set('theme:facebook', (settingsCache.facebook && settingsCache.facebook.value) || ''); config.set('theme:timezone', (settingsCache.activeTimezone && settingsCache.activeTimezone.value) || config.get('theme').timezone); - config.set('theme:url', config.get('url') ? generalUtils.url.urlJoin(config.get('url'), '/') : ''); + config.set('theme:url', globalUtils.url.urlFor('home', true)); config.set('theme:amp', (settingsCache.amp && settingsCache.amp.value === 'true')); _.each(labsValue, function (value, key) { diff --git a/core/server/apps/amp/lib/helpers/amp_content.js b/core/server/apps/amp/lib/helpers/amp_content.js index 9259c41e67..73acc6c95f 100644 --- a/core/server/apps/amp/lib/helpers/amp_content.js +++ b/core/server/apps/amp/lib/helpers/amp_content.js @@ -11,11 +11,11 @@ var hbs = require('express-hbs'), Amperize = require('amperize'), moment = require('moment'), sanitizeHtml = require('sanitize-html'), - config = require('../../../../config'), logging = require('../../../../logging'), i18n = require('../../../../i18n'), errors = require('../../../../errors'), makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'), + utils = require('../../../../utils'), cheerio = require('cheerio'), amperize = new Amperize(), amperizeCache = {}, @@ -121,7 +121,7 @@ function getAmperizeHTML(html, post) { } // make relative URLs abolute - html = makeAbsoluteUrl(html, config.get('url'), post.url).html(); + html = makeAbsoluteUrl(html, utils.url.urlFor('home', true), post.url).html(); if (!amperizeCache[post.id] || moment(new Date(amperizeCache[post.id].updated_at)).diff(new Date(post.updated_at)) < 0) { return new Promise(function (resolve) { diff --git a/core/server/apps/subscribers/lib/helpers/subscribe_form.js b/core/server/apps/subscribers/lib/helpers/subscribe_form.js index af346a3899..d133e10d96 100644 --- a/core/server/apps/subscribers/lib/helpers/subscribe_form.js +++ b/core/server/apps/subscribers/lib/helpers/subscribe_form.js @@ -4,7 +4,6 @@ // We use the name subscribe_form to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers var _ = require('lodash'), - path = require('path'), // Dirty requires hbs = require('express-hbs'), @@ -43,7 +42,7 @@ subscribeScript = subscribe_form = function (options) { var root = options.data.root, data = _.merge({}, options.hash, _.pick(root, params), { - action: path.join('/', globalUtils.url.getSubdir(), config.get('routeKeywords').subscribe, '/'), + action: globalUtils.url.urlJoin('/', globalUtils.url.getSubdir(), config.get('routeKeywords').subscribe, '/'), script: new hbs.handlebars.SafeString(subscribeScript), hidden: new hbs.handlebars.SafeString( makeHidden('confirm') + diff --git a/core/server/ghost-server.js b/core/server/ghost-server.js index 1e506e08dc..60d07b62ff 100644 --- a/core/server/ghost-server.js +++ b/core/server/ghost-server.js @@ -8,6 +8,7 @@ var debug = require('debug')('ghost:server'), _ = require('lodash'), errors = require('./errors'), config = require('./config'), + utils = require('./utils'), i18n = require('./i18n'), moment = require('moment'); @@ -202,7 +203,7 @@ GhostServer.prototype.logStartMessages = function () { chalk.green(i18n.t('notices.httpServer.ghostIsRunningIn', {env: process.env.NODE_ENV})), i18n.t('notices.httpServer.listeningOn'), config.get('server').socket || config.get('server').host + ':' + config.get('server').port, - i18n.t('notices.httpServer.urlConfiguredAs', {url: config.get('url')}), + i18n.t('notices.httpServer.urlConfiguredAs', {url: utils.url.urlFor('home', true)}), chalk.gray(i18n.t('notices.httpServer.ctrlCToShutDown')) ); } diff --git a/core/server/logging/index.js b/core/server/logging/index.js index a706686df2..08a5b72f93 100644 --- a/core/server/logging/index.js +++ b/core/server/logging/index.js @@ -1,7 +1,8 @@ var config = require('../config'), GhostLogger = require('./GhostLogger'), + utils = require('../utils'), adapter = new GhostLogger({ - domain: config.get('url').replace(/[^\w]/gi, '_'), + domain: utils.url.urlFor('home', true).replace(/[^\w]/gi, '_'), env: config.get('env'), mode: process.env.NODE_MODE || process.env.MODE || config.get('logging:mode'), level: process.env.NODE_LEVEL || process.env.LEVEL || config.get('logging:level'), diff --git a/core/server/mail/GhostMailer.js b/core/server/mail/GhostMailer.js index 3222bb5822..a9da262e6c 100644 --- a/core/server/mail/GhostMailer.js +++ b/core/server/mail/GhostMailer.js @@ -5,7 +5,8 @@ var _ = require('lodash'), nodemailer = require('nodemailer'), validator = require('validator'), config = require('../config'), - i18n = require('../i18n'); + i18n = require('../i18n'), + utils = require('../utils'); function GhostMailer() { var transport = config.get('mail') && config.get('mail').transport || 'direct', @@ -40,7 +41,7 @@ GhostMailer.prototype.from = function () { // Moved it to its own module GhostMailer.prototype.getDomain = function () { - var domain = config.get('url').match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i')); + var domain = utils.url.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i')); return domain && domain[1]; }; diff --git a/core/server/mail/utils.js b/core/server/mail/utils.js index 7dae473fc1..7ae4efb62c 100644 --- a/core/server/mail/utils.js +++ b/core/server/mail/utils.js @@ -4,6 +4,7 @@ var _ = require('lodash').runInContext(), path = require('path'), htmlToText = require('html-to-text'), config = require('../config'), + utils = require('../utils'), templatesDir = path.resolve(__dirname, '..', 'mail', 'templates'); _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; @@ -13,7 +14,7 @@ exports.generateContent = function generateContent(options) { data; defaults = { - siteUrl: config.get('forceAdminSSL') ? (config.get('urlSSL') || config.get('url')) : config.get('url') + siteUrl: config.get('forceAdminSSL') ? utils.url.urlFor('home', {secure: true}, true) : utils.url.urlFor('home', true) }; data = _.defaults(defaults, options.data); diff --git a/core/server/middleware/api/cors.js b/core/server/middleware/api/cors.js index 8c166eb96a..77173523b0 100644 --- a/core/server/middleware/api/cors.js +++ b/core/server/middleware/api/cors.js @@ -2,6 +2,7 @@ var cors = require('cors'), _ = require('lodash'), url = require('url'), os = require('os'), + utils = require('../../utils'), config = require('../../config'), whitelist = [], ENABLE_CORS = {origin: true, maxAge: 86400}, @@ -32,7 +33,7 @@ function getIPs() { } function getUrls() { - var urls = [url.parse(config.get('url')).hostname]; + var urls = [url.parse(utils.url.urlFor('home', true)).hostname]; if (config.get('urlSSL')) { urls.push(url.parse(config.get('urlSSL')).hostname); diff --git a/core/server/middleware/check-ssl.js b/core/server/middleware/check-ssl.js index 6652a87ad7..c07db32727 100644 --- a/core/server/middleware/check-ssl.js +++ b/core/server/middleware/check-ssl.js @@ -1,5 +1,6 @@ var config = require('../config'), url = require('url'), + utils = require('../utils'), checkSSL; function isSSLrequired(isAdmin, configUrl, forceAdminSSL) { @@ -38,12 +39,12 @@ function sslForbiddenOrRedirect(opt) { // Check to see if we should use SSL // and redirect if needed checkSSL = function checkSSL(req, res, next) { - if (isSSLrequired(res.isAdmin, config.get('url'), config.get('forceAdminSSL'))) { + if (isSSLrequired(res.isAdmin, utils.url.urlFor('home', true), config.get('forceAdminSSL'))) { if (!req.secure) { var response = sslForbiddenOrRedirect({ forceAdminSSL: config.get('forceAdminSSL'), configUrlSSL: config.get('urlSSL'), - configUrl: config.get('url'), + configUrl: utils.url.urlFor('home', true), reqUrl: req.originalUrl || req.url }); diff --git a/core/server/middleware/serve-shared-file.js b/core/server/middleware/serve-shared-file.js index f8d19044ca..6c91166cfd 100644 --- a/core/server/middleware/serve-shared-file.js +++ b/core/server/middleware/serve-shared-file.js @@ -27,7 +27,7 @@ function serveSharedFile(file, type, maxAge) { } if (type === 'text/xsl' || type === 'text/plain' || type === 'application/javascript') { - buf = buf.toString().replace(blogRegex, config.get('url').replace(/\/$/, '')); + buf = buf.toString().replace(blogRegex, utils.url.urlFor('home', true).replace(/\/$/, '')); buf = buf.toString().replace(apiRegex, utils.url.apiUrl({cors: true})); } content = { diff --git a/core/server/update-check.js b/core/server/update-check.js index 433beb6113..2b99689575 100644 --- a/core/server/update-check.js +++ b/core/server/update-check.js @@ -30,6 +30,7 @@ var crypto = require('crypto'), url = require('url'), api = require('./api'), config = require('./config'), + utils = require('./utils'), logging = require('./logging'), errors = require('./errors'), i18n = require('./i18n'), @@ -116,7 +117,7 @@ function updateCheckData() { posts = descriptors.posts.value(), users = descriptors.users.value(), npm = descriptors.npm.value(), - blogUrl = url.parse(config.get('url')), + blogUrl = url.parse(utils.url.urlFor('home', true)), blogId = blogUrl.hostname + blogUrl.pathname.replace(/\//, '') + hash.value; data.blog_id = crypto.createHash('md5').update(blogId).digest('hex'); diff --git a/core/server/utils/url.js b/core/server/utils/url.js index a23a419b1a..3c85e2e515 100644 --- a/core/server/utils/url.js +++ b/core/server/utils/url.js @@ -8,18 +8,38 @@ var moment = require('moment-timezone'), // @TODO: unify this with routes.apiBaseUrl apiPath = '/ghost/api/v0.1'; +/** getBaseUrl + * Returns the base URL of the blog as set in the config. If called with secure options, returns the ssl URL. + * @param {boolean} secure + * @return {string} URL returns the url as defined in config, but always with a trailing `/` + */ function getBaseUrl(secure) { + var base; + + // CASE: a specified SSL URL is configured (e. g. https://secure.blog.org/) + // see: https://github.com/TryGhost/Ghost/issues/6270#issuecomment-168939865 if (secure && config.get('urlSSL')) { - return config.get('urlSSL'); + base = config.get('urlSSL'); } else { + // CASE: no specified SSL URL configured, but user request is secure. In this case we force SSL + // and therefore replace the protocol. if (secure) { - return config.get('url').replace('http://', 'https://'); + base = config.get('url').replace('http://', 'https://'); } else { - return config.get('url'); + base = config.get('url'); } } + + if (!base.match(/\/$/)) { + base += '/'; + } + return base; } +/** getSubdir + * Returns a subdirectory URL, if defined so in the config. + * @return {string} URL a subdirectory if configured. + */ function getSubdir() { var localPath, subdir; @@ -47,14 +67,11 @@ function getProtectedSlugs() { } } -// ## urlJoin -// concats arguments to a path/URL -// Usage: -// urlJoin(getBaseUrl(), 'content', '/') -> http://my-ghost-blog.com/content/ -// Returns a URL or relative path -// Only to use for Ghost URLs and paths -// TODO: urlJoin needs to be optimised and to validate the URL/path properly. -// e. g. URLs should end with a trailing `/` at the end of the pathname. +/** urlJoin + * Returns a URL/path for internal use in Ghost. + * @param {string} arguments takes arguments and concats those to a valid path/URL. + * @return {string} URL concatinated URL/path of arguments. + */ function urlJoin() { var args = Array.prototype.slice.call(arguments), prefixDoubleSlash = false, @@ -245,6 +262,8 @@ function urlFor(context, data, absolute) { absolute = true; } } + } else if (context === 'home' && absolute) { + urlPath = getBaseUrl(secure); // other objects are recognised but not yet supported } else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) { // trying to create a url for a named path @@ -273,7 +292,7 @@ function apiUrl(options) { var url; if (config.get('forceAdminSSL')) { - url = (config.get('urlSSL') || config.get('url')).replace(/^.*?:\/\//g, 'https://'); + url = (config.get('urlSSL') || getBaseUrl(true)).replace(/^.*?:\/\//g, 'https://'); } else if (config.get('urlSSL')) { url = config.get('urlSSL').replace(/^.*?:\/\//g, 'https://'); } else if (config.get('url').match(/^https:/)) { diff --git a/core/test/integration/api/api_configuration_spec.js b/core/test/integration/api/api_configuration_spec.js index 0e6490bbb8..1e7935167e 100644 --- a/core/test/integration/api/api_configuration_spec.js +++ b/core/test/integration/api/api_configuration_spec.js @@ -28,7 +28,7 @@ describe('Configuration API', function () { response.configuration.should.be.an.Array().with.lengthOf(1); props = response.configuration[0]; - props.blogUrl.should.eql('http://127.0.0.1:2369'); + props.blogUrl.should.eql('http://127.0.0.1:2369/'); props.routeKeywords.should.eql({ tag: 'tag', author: 'author', diff --git a/core/test/unit/utils/url_spec.js b/core/test/unit/utils/url_spec.js index 8d2e3083ee..5078aa8f3b 100644 --- a/core/test/unit/utils/url_spec.js +++ b/core/test/unit/utils/url_spec.js @@ -100,21 +100,25 @@ describe('Url', function () { it('should return home url when asked for', function () { var testContext = 'home'; - configUtils.set({url: 'http://my-ghost-blog.com'}); + configUtils.set({url: 'http://my-ghost-blog.com', urlSSL: 'https://my-ghost-blog.com'}); utils.url.urlFor(testContext).should.equal('/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/'); + utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/'); - configUtils.set({url: 'http://my-ghost-blog.com/'}); + configUtils.set({url: 'http://my-ghost-blog.com/', urlSSL: 'https://my-ghost-blog.com/'}); utils.url.urlFor(testContext).should.equal('/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/'); + utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/'); - configUtils.set({url: 'http://my-ghost-blog.com/blog'}); + configUtils.set({url: 'http://my-ghost-blog.com/blog', urlSSL: 'https://my-ghost-blog.com/blog'}); utils.url.urlFor(testContext).should.equal('/blog/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/'); + utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/blog/'); - configUtils.set({url: 'http://my-ghost-blog.com/blog/'}); + configUtils.set({url: 'http://my-ghost-blog.com/blog/', urlSSL: 'https://my-ghost-blog.com/blog/'}); utils.url.urlFor(testContext).should.equal('/blog/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/'); + utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/blog/'); }); it('should return rss url when asked for', function () {