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' +
' ');