From cfd9ff3993b41c991d079f1dfa262930ee6c5d5c Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Sun, 12 Aug 2018 14:57:19 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Koenig=20-=20Added=20support=20f?= =?UTF-8?q?or=20shortened=20URLs=20in=20embed=20card=20(#9781)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs https://github.com/TryGhost/Ghost/issues/9724 - perform a HEAD request on a url if we don't find a matching provider, following any redirects until we hit success response before looking up providers for the resulting url --- core/server/api/oembed.js | 78 ++++++++++++++++++++++--------- core/test/unit/api/oembed_spec.js | 36 ++++++++++++++ 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/core/server/api/oembed.js b/core/server/api/oembed.js index 4252ab80f6..0f9deab03d 100644 --- a/core/server/api/oembed.js +++ b/core/server/api/oembed.js @@ -1,6 +1,31 @@ const common = require('../lib/common'); const {extract, hasProvider} = require('oembed-parser'); const Promise = require('bluebird'); +const request = require('../lib/request'); + +const findUrlWithProvider = function findUrlWithProvider(url) { + let provider; + + // build up a list of URL variations to test against because the oembed + // providers list is not always up to date with scheme or www vs non-www + let baseUrl = url.replace(/^\/\/|^https?:\/\/(?:www\.)?/, ''); + let testUrls = [ + `http://${baseUrl}`, + `https://${baseUrl}`, + `http://www.${baseUrl}`, + `https://www.${baseUrl}` + ]; + + for (let testUrl of testUrls) { + provider = hasProvider(testUrl); + if (provider) { + url = testUrl; + break; + } + } + + return {url, provider}; +}; let oembed = { read(options) { @@ -12,34 +37,41 @@ let oembed = { })); } - // build up a list of URL variations to test against because the oembed - // providers list is not always up to date with scheme or www vs non-www - let base = url.replace(/^\/\/|^https?:\/\/(?:www\.)?/, ''); - let testUrls = [ - `http://${base}`, - `https://${base}`, - `http://www.${base}`, - `https://www.${base}` - ]; - let provider; - for (let testUrl of testUrls) { - provider = hasProvider(testUrl); - if (provider) { - url = testUrl; - break; - } - } - - if (!provider) { + function unknownProvider() { return Promise.reject(new common.errors.ValidationError({ message: common.i18n.t('errors.api.oembed.unknownProvider') })); } - return extract(url).catch((err) => { - return Promise.reject(new common.errors.InternalServerError({ - message: err.message - })); + function knownProvider(url) { + return extract(url).catch((err) => { + return Promise.reject(new common.errors.InternalServerError({ + message: err.message + })); + }); + } + + let provider; + ({url, provider} = findUrlWithProvider(url)); + + if (provider) { + return knownProvider(url); + } + + // see if the URL is a redirect to cater for shortened urls + return request(url, { + method: 'HEAD', + timeout: 2 * 1000, + followRedirect: true + }).then((response) => { + if (response.url !== url) { + ({url, provider} = findUrlWithProvider(response.url)); + return provider ? knownProvider(url) : unknownProvider(); + } + + return unknownProvider(); + }).catch(() => { + return unknownProvider(); }); } }; diff --git a/core/test/unit/api/oembed_spec.js b/core/test/unit/api/oembed_spec.js index 3cf0936de6..4ef3c1a942 100644 --- a/core/test/unit/api/oembed_spec.js +++ b/core/test/unit/api/oembed_spec.js @@ -39,6 +39,38 @@ describe('API: oembed', function () { }).catch(done); }); + it('follows redirects to get base url', function (done) { + let redirectMock = nock('https://youtu.be') + .intercept('/yHohwmrxrto', 'HEAD') + .reply(302, undefined, { + // eslint-disable-next-line + 'Location': 'https://www.youtube.com/watch?v=yHohwmrxrto&feature=youtu.be' + }); + + let videoMock = nock('https://www.youtube.com') + .intercept('/watch', 'HEAD') + .query({v: 'yHohwmrxrto', feature: 'youtu.be'}) + .reply(200); + + let requestMock = nock('https://www.youtube.com') + .get('/oembed') + .query(true) + .reply(200, { + html: 'test' + }); + + OembedAPI.read({url: 'https://youtu.be/yHohwmrxrto'}) + .then((results) => { + redirectMock.isDone().should.be.true; + videoMock.isDone().should.be.true; + requestMock.isDone().should.be.true; + should.exist(results); + should.exist(results.html); + results.html.should.eql('test'); + done(); + }).catch(done); + }); + it('returns error for missing url', function (done) { OembedAPI.read({url: ''}) .then(() => { @@ -50,6 +82,10 @@ describe('API: oembed', function () { }); it('returns error for unsupported provider', function (done) { + nock('http://example.com') + .intercept('/unknown', 'HEAD') + .reply(200); + OembedAPI.read({url: 'http://example.com/unknown'}) .then(() => { done(new Error('Fetch oembed with unknown url provider should error'));