diff --git a/Gruntfile.js b/Gruntfile.js index a70f0afe02..b2f7d28a8c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -443,6 +443,7 @@ var path = require('path'), 'bower_components/nprogress/nprogress.js', 'bower_components/ember-simple-auth/simple-auth.js', 'bower_components/ember-simple-auth/simple-auth-oauth2.js', + 'bower_components/google-caja/html-css-sanitizer-bundle.js', 'core/shared/lib/showdown/extensions/ghostimagepreview.js', 'core/shared/lib/showdown/extensions/ghostgfm.js' @@ -475,8 +476,9 @@ var path = require('path'), 'bower_components/jquery-file-upload/js/jquery.fileupload.js', 'bower_components/fastclick/lib/fastclick.js', 'bower_components/nprogress/nprogress.js', - 'bower_components/ember-simple-auth/ember-simple-auth.js', - 'bower_components/ember-simple-auth/ember-simple-auth-oauth2.js', + 'bower_components/ember-simple-auth/simple-auth.js', + 'bower_components/ember-simple-auth/simple-auth-oauth2.js', + 'bower_components/google-caja/html-css-sanitizer-bundle.js', 'core/shared/lib/showdown/extensions/ghostimagepreview.js', 'core/shared/lib/showdown/extensions/ghostgfm.js' diff --git a/bower.json b/bower.json index 0d818a1649..3774b1bf87 100644 --- a/bower.json +++ b/bower.json @@ -23,6 +23,7 @@ "moment": "2.4.0", "nprogress": "0.1.2", "showdown": "git://github.com/ErisDS/showdown.git#v0.3.2-ghost", - "validator-js": "3.4.0" + "validator-js": "3.4.0", + "google-caja": "5669.0.0" } } diff --git a/core/client/helpers/gh-format-html.js b/core/client/helpers/gh-format-html.js new file mode 100644 index 0000000000..436a6e7960 --- /dev/null +++ b/core/client/helpers/gh-format-html.js @@ -0,0 +1,18 @@ +/* global Handlebars, html_sanitize*/ +import cajaSanitizers from 'ghost/utils/caja-sanitizers'; + +var formatHTML = Ember.Handlebars.makeBoundHelper(function (html) { + var escapedhtml = html || ''; + + // replace script and iFrame + escapedhtml = escapedhtml.replace(/)<[^<]*)*<\/script>/gi, + '
Embedded JavaScript
'); + escapedhtml = escapedhtml.replace(/)<[^<]*)*<\/iframe>/gi, + '
Embedded IFrame
'); + + // sanitize HTML + escapedhtml = html_sanitize(escapedhtml, cajaSanitizers.url, cajaSanitizers.id); + return new Handlebars.SafeString(escapedhtml); +}); + +export default formatHTML; \ No newline at end of file diff --git a/core/client/helpers/gh-format-markdown.js b/core/client/helpers/gh-format-markdown.js index 6370ac1d4d..e3cdfe1d62 100644 --- a/core/client/helpers/gh-format-markdown.js +++ b/core/client/helpers/gh-format-markdown.js @@ -1,8 +1,21 @@ -/* global Showdown, Handlebars */ +/* global Showdown, Handlebars, html_sanitize*/ +import cajaSanitizers from 'ghost/utils/caja-sanitizers'; + var showdown = new Showdown.converter({extensions: ['ghostimagepreview', 'ghostgfm']}); var formatMarkdown = Ember.Handlebars.makeBoundHelper(function (markdown) { - return new Handlebars.SafeString(showdown.makeHtml(markdown || '')); + var html = ''; + + // replace script and iFrame + markdown = markdown.replace(/)<[^<]*)*<\/script>/gi, '```\nEmbedded JavaScript\n```'); + markdown = markdown.replace(/)<[^<]*)*<\/iframe>/gi, '```\nEmbedded IFrame\n```'); + + // convert markdown to HTML + html = showdown.makeHtml(markdown || ''); + + // sanitize html + html = html_sanitize(html, cajaSanitizers.url, cajaSanitizers.id); + return new Handlebars.SafeString(html); }); export default formatMarkdown; \ No newline at end of file diff --git a/core/client/templates/posts/post.hbs b/core/client/templates/posts/post.hbs index cdf761a84b..c396441bc4 100644 --- a/core/client/templates/posts/post.hbs +++ b/core/client/templates/posts/post.hbs @@ -2,7 +2,7 @@ {{#view "content-preview-content-view" tagName="section"}}
-

{{{title}}}

- {{{html}}} +

{{title}}

+ {{gh-format-html html}}
{{/view}} diff --git a/core/client/utils/caja-sanitizers.js b/core/client/utils/caja-sanitizers.js new file mode 100644 index 0000000000..1bcdc7a20c --- /dev/null +++ b/core/client/utils/caja-sanitizers.js @@ -0,0 +1,29 @@ +/** + * google-caja uses url() and id() to verify if the values are allowed. + */ +var url, + id; + +/** + * Check if URL is allowed + * URLs are allowed if they start with http://, https://, or /. + */ +var url = function (url) { + url = url.toString().replace(/['"]+/g, ''); + if (/^https?:\/\//.test(url) || /^\//.test(url)) { + return url; + } +}; + +/** + * Check if ID is allowed + * All ids are allowed at the moment. + */ +var id = function (id) { + return id; +}; + +export default { + url: url, + id: id +}; \ No newline at end of file diff --git a/core/test/functional/client/editor_test.js b/core/test/functional/client/editor_test.js index 91b9ff8372..d71b8e1106 100644 --- a/core/test/functional/client/editor_test.js +++ b/core/test/functional/client/editor_test.js @@ -172,7 +172,7 @@ CasperTest.begin('Image Uploads', 17, function suite(test) { test.assertUrlMatch(/ghost\/editor\/$/, 'Landed on the correct URL'); }); - var testFileLocation = 'test/file/location'; + var testFileLocation = '/test/file/location'; casper.then(function () { var markdownImageString = '![](' + testFileLocation + ')'; @@ -203,7 +203,7 @@ CasperTest.begin('Image Uploads', 17, function suite(test) { casper.thenClick('.entry-preview .image-uploader a.image-url'); }); - var imageURL = 'random.url'; + var imageURL = 'http://www.random.url'; casper.waitForSelector('.image-uploader-url', function onSuccess() { casper.sendKeys('.image-uploader-url input.url.js-upload-url', imageURL); casper.thenClick('.js-button-accept.button-save');