diff --git a/core/server/helpers/ghost_head.js b/core/server/helpers/ghost_head.js index 25d7361e8f..27f782e3f2 100644 --- a/core/server/helpers/ghost_head.js +++ b/core/server/helpers/ghost_head.js @@ -34,7 +34,8 @@ ghost_head = function (options) { ops = [], structuredData, coverImage, authorImage, keywords, - schema; + schema, + title = hbs.handlebars.Utils.escapeExpression(blog.title); trimmedVersion = trimmedVersion ? trimmedVersion.match(majorMinor)[0] : '?'; // Push Async calls to an array of promises @@ -49,13 +50,14 @@ ghost_head = function (options) { metaTitle = results[2].value(), publishedDate, modifiedDate, tags = tagsHelper.call(self.post, {hash: {autolink: 'false'}}).string.split(','), - card = 'summary'; + card = 'summary', + type, authorUrl; if (!metaDescription) { metaDescription = excerpt.call(self.post, {hash: {words: '40'}}).string; } if (tags[0] !== '') { - keywords = tagsHelper.call(self.post, {hash: {autolink: 'false', seperator: ', '}}).string; + keywords = hbs.handlebars.Utils.escapeExpression(tagsHelper.call(self.post, {hash: {autolink: 'false', seperator: ', '}}).string); } head.push(''); @@ -81,25 +83,30 @@ ghost_head = function (options) { if (self.post.image) { coverImage = self.post.image; // Test to see if image was linked by url or uploaded - coverImage = coverImage.substring(0, 4) === 'http' ? coverImage : _.escape(blog.url) + coverImage; + coverImage = coverImage.substring(0, 4) === 'http' ? coverImage : hbs.handlebars.Utils.escapeExpression(blog.url + coverImage); card = 'summary_large_image'; } if (self.post.author.image) { authorImage = self.post.author.image; // Test to see if image was linked by url or uploaded - authorImage = authorImage.substring(0, 4) === 'http' ? authorImage : _.escape(blog.url) + authorImage; + authorImage = authorImage.substring(0, 4) === 'http' ? authorImage : hbs.handlebars.Utils.escapeExpression(blog.url + authorImage); } + // escaped data + metaTitle = hbs.handlebars.Utils.escapeExpression(metaTitle); + metaDescription = hbs.handlebars.Utils.escapeExpression(metaDescription + '...'); + authorUrl = hbs.handlebars.Utils.escapeExpression(blog.url + '/author/' + self.post.author.slug); + schema = { '@context': 'http://schema.org', '@type': 'Article', - publisher: _.escape(blog.title), + publisher: title, author: { '@type': 'Person', name: self.post.author.name, image: authorImage, - url: _.escape(blog.url) + '/author/' + self.post.author.slug, + url: authorUrl, sameAs: self.post.author.website }, headline: metaTitle, @@ -112,10 +119,10 @@ ghost_head = function (options) { }; structuredData = { - 'og:site_name': _.escape(blog.title), + 'og:site_name': title, 'og:type': 'article', 'og:title': metaTitle, - 'og:description': metaDescription + '...', + 'og:description': metaDescription, 'og:url': url, 'og:image': coverImage, 'article:published_time': publishedDate, @@ -123,7 +130,7 @@ ghost_head = function (options) { 'article:tag': tags, 'twitter:card': card, 'twitter:title': metaTitle, - 'twitter:description': metaDescription + '...', + 'twitter:description': metaDescription, 'twitter:url': url, 'twitter:image:src': coverImage }; @@ -132,16 +139,14 @@ ghost_head = function (options) { if (property === 'article:tag') { _.each(tags, function (tag) { if (tag !== '') { - head.push(''); + tag = hbs.handlebars.Utils.escapeExpression(tag.trim()); + head.push(''); } }); head.push(''); } else if (content !== null && content !== undefined) { - if (property.substring(0, 7) === 'twitter') { - head.push(''); - } else { - head.push(''); - } + type = property.substring(0, 7) === 'twitter' ? 'name' : 'property'; + head.push(''); } }); head.push(''); @@ -150,7 +155,7 @@ ghost_head = function (options) { head.push(''); head.push(''); + title + '" href="' + config.urlFor('rss') + '" />'); return filters.doFilter('ghost_head', head); }).then(function (head) { var headString = _.reduce(head, function (memo, item) { return memo + '\n ' + item; }, ''); diff --git a/core/test/unit/server_helpers/ghost_head_spec.js b/core/test/unit/server_helpers/ghost_head_spec.js index c6c6d7ab32..2e4e875e35 100644 --- a/core/test/unit/server_helpers/ghost_head_spec.js +++ b/core/test/unit/server_helpers/ghost_head_spec.js @@ -94,7 +94,7 @@ describe('{{ghost_head}} helper', function () { '},\n "headline": "Welcome to Ghost",\n "url": "http://testurl.com/post/",\n' + ' "datePublished": "' + post.published_at + '",\n "dateModified": "' + post.updated_at + '",\n' + ' "image": "http://testurl.com/test-image.png",\n "keywords": "tag1, tag2, tag3",\n' + - ' "description": "blog description"\n}\n \n\n' + + ' "description": "blog description..."\n}\n \n\n' + ' \n' + ' '); @@ -102,7 +102,60 @@ describe('{{ghost_head}} helper', function () { }).catch(done); }); - it('returns structured without tags if there are no tags', function (done) { + it('returns structured data if metaTitle and metaDescription have double quotes', function (done) { + var post = { + meta_description: 'blog "test" description', + title: 'title', + meta_title: 'Welcome to Ghost "test"', + image: '/test-image.png', + published_at: moment('2008-05-31T19:18:15').toISOString(), + updated_at: moment('2014-10-06T15:23:54').toISOString(), + tags: [{name: 'tag1'}, {name: 'tag2'}, {name: 'tag3'}], + author: { + name: 'Author name', + url: 'http//:testauthorurl.com', + slug: 'Author', + image: '/test-author-image.png', + website: 'http://authorwebsite.com' + } + }; + + helpers.ghost_head.call({relativeUrl: '/post/', version: '0.3.0', post: post}).then(function (rendered) { + should.exist(rendered); + rendered.string.should.equal('\n \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n \n' + + ' \n\n' + + ' \n' + + ' '); + + done(); + }).catch(done); + }); + + it('returns structured data without tags if there are no tags', function (done) { var post = { meta_description: 'blog description', title: 'Welcome to Ghost', @@ -143,7 +196,7 @@ describe('{{ghost_head}} helper', function () { '},\n "headline": "Welcome to Ghost",\n "url": "http://testurl.com/post/",\n' + ' "datePublished": "' + post.published_at + '",\n "dateModified": "' + post.updated_at + '",\n' + ' "image": "http://testurl.com/test-image.png",\n' + - ' "description": "blog description"\n}\n \n\n' + + ' "description": "blog description..."\n}\n \n\n' + ' \n' + ' '); @@ -191,7 +244,7 @@ describe('{{ghost_head}} helper', function () { ' "url": "http://testurl.com/author/Author",\n "sameAs": "http://authorwebsite.com"\n ' + '},\n "headline": "Welcome to Ghost",\n "url": "http://testurl.com/post/",\n' + ' "datePublished": "' + post.published_at + '",\n "dateModified": "' + post.updated_at + '",\n' + - ' "keywords": "tag1, tag2, tag3",\n "description": "blog description"\n}\n \n\n' + + ' "keywords": "tag1, tag2, tag3",\n "description": "blog description..."\n}\n \n\n' + ' \n' + ' ');