diff --git a/core/server/helpers/img_url.js b/core/server/helpers/img_url.js index a41e4f17db..2527fde7fc 100644 --- a/core/server/helpers/img_url.js +++ b/core/server/helpers/img_url.js @@ -8,61 +8,96 @@ // `absolute` flag outputs absolute URL, else URL is relative. const url = require('url'); +const _ = require('lodash'); const proxy = require('./proxy'); const urlService = proxy.urlService; const STATIC_IMAGE_URL_PREFIX = `/${urlService.utils.STATIC_IMAGE_URL_PREFIX}`; -module.exports = function imgUrl(attr, options) { - // CASE: if no attribute is passed, e.g. `{{img_url}}` we show a warning +module.exports = function imgUrl(requestedImageUrl, options) { + // CASE: if no url is passed, e.g. `{{img_url}}` we show a warning if (arguments.length < 2) { proxy.logging.warn(proxy.i18n.t('warnings.helpers.img_url.attrIsRequired')); return; } - const absolute = options && options.hash && options.hash.absolute && options.hash.absolute !== 'false'; - - const size = options && options.hash && options.hash.size; - const imageSizes = options && options.data && options.data.config && options.data.config.image_sizes; - - const image = getImageWithSize(attr, size, imageSizes); - - // CASE: if attribute is passed, but it is undefined, then the attribute was + // CASE: if url is passed, but it is undefined, then the attribute was // an unknown value, e.g. {{img_url feature_img}} and we also show a warning - if (image === undefined) { + if (requestedImageUrl === undefined) { proxy.logging.warn(proxy.i18n.t('warnings.helpers.img_url.attrIsRequired')); return; } - if (image) { - return urlService.utils.urlFor('image', {image}, absolute); + // CASE: if you pass e.g. cover_image, but it is not set, then requestedImageUrl is null! + // in this case we don't show a warning + if (requestedImageUrl === null) { + return; } - // CASE: if you pass e.g. cover_image, but it is not set, then attr is null! - // in this case we don't show a warning + // CASE: if you pass an external image, there is nothing we want to do to it! + const isInternalImage = detectInternalImage(requestedImageUrl); + if (!isInternalImage) { + return requestedImageUrl; + } + + const {requestedSize, imageSizes} = getImageSizeOptions(options); + const absoluteUrlRequested = getAbsoluteOption(options); + + function applyImageSizes(image) { + return getImageWithSize(image, requestedSize, imageSizes); + } + + function getImageUrl(image) { + return urlService.utils.urlFor('image', {image}, absoluteUrlRequested); + } + + function ensureRelativePath(image) { + return urlService.utils.absoluteToRelative(image); + } + + // CASE: only make paths relative if we didn't get a request for an absolute url + const maybeEnsureRelativePath = !absoluteUrlRequested ? ensureRelativePath : _.identity; + + return maybeEnsureRelativePath( + getImageUrl( + applyImageSizes(requestedImageUrl) + ) + ); }; +function getAbsoluteOption(options) { + const absoluteOption = options && options.hash && options.hash.absolute; + + return absoluteOption ? !!absoluteOption && absoluteOption !== 'false' : false; +} + +function getImageSizeOptions(options) { + const requestedSize = options && options.hash && options.hash.size; + const imageSizes = options && options.data && options.data.config && options.data.config.image_sizes; + + return { + requestedSize, + imageSizes + }; +} + +function detectInternalImage(requestedImageUrl) { + const siteUrl = urlService.utils.getBlogUrl(); + const isAbsoluteImage = /https?:\/\//.test(requestedImageUrl); + const isAbsoluteInternalImage = isAbsoluteImage && requestedImageUrl.startsWith(siteUrl); + + // CASE: imagePath is a "protocol relative" url e.g. "//www.gravatar.com/ava..." + // by resolving the the imagePath relative to the blog url, we can then + // detect if the imagePath is external, or internal. + const isRelativeInternalImage = !isAbsoluteImage && url.resolve(siteUrl, requestedImageUrl).startsWith(siteUrl); + + return isAbsoluteInternalImage || isRelativeInternalImage; +} + function getImageWithSize(imagePath, requestedSize, imageSizes) { - if (!imagePath) { - return imagePath; - } if (!requestedSize) { return imagePath; } - const blogUrl = urlService.utils.getBlogUrl(); - - if (/https?:\/\//.test(imagePath) && !imagePath.startsWith(blogUrl)) { - return imagePath; - } else { - // CASE: imagePath is a "protocol relative" url e.g. "//www.gravatar.com/ava..." - // by resolving the the imagePath relative to the blog url, we can then - // detect if the imagePath is external, or internal. - const resolvedUrl = url.resolve(blogUrl, imagePath); - if (!resolvedUrl.startsWith(blogUrl)) { - return imagePath; - } - } - if (!imageSizes || !imageSizes[requestedSize]) { return imagePath; } diff --git a/core/test/unit/helpers/img_url_spec.js b/core/test/unit/helpers/img_url_spec.js index 2662b83bac..aa7cfec88a 100644 --- a/core/test/unit/helpers/img_url_spec.js +++ b/core/test/unit/helpers/img_url_spec.js @@ -32,6 +32,13 @@ describe('{{image}} helper', function () { logWarnStub.called.should.be.false(); }); + it('should output relative url of image if the input is absolute', function () { + var rendered = helpers.img_url('http://localhost:82832/content/images/image-relative-url.png', {}); + should.exist(rendered); + rendered.should.equal('/content/images/image-relative-url.png'); + logWarnStub.called.should.be.false(); + }); + it('should output absolute url of image if the option is present ', function () { var rendered = helpers.img_url('/content/images/image-relative-url.png', {hash: {absolute: 'true'}}); should.exist(rendered); @@ -120,7 +127,7 @@ describe('{{image}} helper', function () { } }); should.exist(rendered); - rendered.should.equal('http://localhost:82832/content/images/size/w400/my-coole-img.jpg'); + rendered.should.equal('/content/images/size/w400/my-coole-img.jpg'); }); it('should output the correct url for protocol relative urls', function () { var rendered = helpers.img_url('//website.com/whatever/my-coole-img.jpg', {