mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Upgraded got package from v9.6.0 to v11.8.6 (#16261)
Refs TryGhost/Team#2459 -upgraded got from v9.6.0 to v11.8.6 to support following redirects (and other fixes) -got v12+ requires ESM, so we do not want to upgrade further at this time -required changes to a few libraries that use externalRequests -mention discovery service tests updated to test for follow redirects
This commit is contained in:
parent
52a26a7f80
commit
2d84b7d990
12 changed files with 117 additions and 139 deletions
|
@ -20,22 +20,32 @@ function isPrivateIp(addr) {
|
||||||
async function errorIfHostnameResolvesToPrivateIp(options) {
|
async function errorIfHostnameResolvesToPrivateIp(options) {
|
||||||
// allow requests through to local Ghost instance
|
// allow requests through to local Ghost instance
|
||||||
const siteUrl = new URL(config.get('url'));
|
const siteUrl = new URL(config.get('url'));
|
||||||
const requestUrl = new URL(options.href);
|
const requestUrl = new URL(options.url.href);
|
||||||
if (requestUrl.host === siteUrl.host) {
|
if (requestUrl.host === siteUrl.host) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await dnsPromises.lookup(options.hostname);
|
const result = await dnsPromises.lookup(options.url.hostname);
|
||||||
|
|
||||||
if (isPrivateIp(result.address)) {
|
if (isPrivateIp(result.address)) {
|
||||||
return Promise.reject(new errors.InternalServerError({
|
return Promise.reject(new errors.InternalServerError({
|
||||||
message: 'URL resolves to a non-permitted private IP block',
|
message: 'URL resolves to a non-permitted private IP block',
|
||||||
code: 'URL_PRIVATE_INVALID',
|
code: 'URL_PRIVATE_INVALID',
|
||||||
context: options.href
|
context: options.url.href
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function errorIfInvalidUrl(options) {
|
||||||
|
if (!options.url.hostname || !validator.isURL(options.url.hostname)) {
|
||||||
|
throw new errors.InternalServerError({
|
||||||
|
message: 'URL invalid.',
|
||||||
|
code: 'URL_MISSING_INVALID',
|
||||||
|
context: options.url.href
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// same as our normal request lib but if any request in a redirect chain resolves
|
// same as our normal request lib but if any request in a redirect chain resolves
|
||||||
// to a private IP address it will be blocked before the request is made.
|
// to a private IP address it will be blocked before the request is made.
|
||||||
const externalRequest = got.extend({
|
const externalRequest = got.extend({
|
||||||
|
@ -44,16 +54,7 @@ const externalRequest = got.extend({
|
||||||
},
|
},
|
||||||
timeout: 10000, // default is no timeout
|
timeout: 10000, // default is no timeout
|
||||||
hooks: {
|
hooks: {
|
||||||
init: [(options) => {
|
beforeRequest: [errorIfInvalidUrl,errorIfHostnameResolvesToPrivateIp],
|
||||||
if (!options.hostname || !validator.isURL(options.hostname)) {
|
|
||||||
throw new errors.InternalServerError({
|
|
||||||
message: 'URL empty or invalid.',
|
|
||||||
code: 'URL_MISSING_INVALID',
|
|
||||||
context: options.href
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
beforeRequest: [errorIfHostnameResolvesToPrivateIp],
|
|
||||||
beforeRedirect: [errorIfHostnameResolvesToPrivateIp]
|
beforeRedirect: [errorIfHostnameResolvesToPrivateIp]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,9 +42,8 @@ class NFTOEmbedProvider {
|
||||||
headers['X-API-KEY'] = this.dependencies.config.apiKey;
|
headers['X-API-KEY'] = this.dependencies.config.apiKey;
|
||||||
}
|
}
|
||||||
const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/?format=json`, {
|
const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/?format=json`, {
|
||||||
json: true,
|
|
||||||
headers
|
headers
|
||||||
});
|
}).json();
|
||||||
return {
|
return {
|
||||||
version: '1.0',
|
version: '1.0',
|
||||||
type: 'nft',
|
type: 'nft',
|
||||||
|
|
|
@ -170,7 +170,7 @@
|
||||||
"fs-extra": "11.1.0",
|
"fs-extra": "11.1.0",
|
||||||
"ghost-storage-base": "1.0.0",
|
"ghost-storage-base": "1.0.0",
|
||||||
"glob": "8.1.0",
|
"glob": "8.1.0",
|
||||||
"got": "9.6.0",
|
"got": "11.8.6",
|
||||||
"gscan": "4.36.0",
|
"gscan": "4.36.0",
|
||||||
"human-number": "2.0.1",
|
"human-number": "2.0.1",
|
||||||
"image-size": "1.0.2",
|
"image-size": "1.0.2",
|
||||||
|
|
|
@ -128,6 +128,12 @@ describe('Oembed API', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors when fetched url is an IP address', async function () {
|
it('errors when fetched url is an IP address', async function () {
|
||||||
|
// in order to follow the 302, we need to stub differently; externalRequest will block the internal IP
|
||||||
|
dnsPromises.lookup.restore();
|
||||||
|
let dnsStub = sinon.stub(dnsPromises, 'lookup');
|
||||||
|
dnsStub.onCall(0).returns(Promise.resolve({address: '123.123.123.123'}));
|
||||||
|
dnsStub.onCall(1).returns(Promise.resolve({address: '0.0.0.0'}));
|
||||||
|
|
||||||
const redirectMock = nock('http://test.com/')
|
const redirectMock = nock('http://test.com/')
|
||||||
.get('/')
|
.get('/')
|
||||||
.reply(302, undefined, {Location: 'http://0.0.0.0:8080'});
|
.reply(302, undefined, {Location: 'http://0.0.0.0:8080'});
|
||||||
|
@ -147,7 +153,7 @@ describe('Oembed API', function () {
|
||||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
.expect(422);
|
.expect(422);
|
||||||
|
|
||||||
pageMock.isDone().should.be.true();
|
pageMock.isDone().should.be.false(); // we shouldn't hit this; blocked by externalRequest
|
||||||
should.exist(res.body.errors);
|
should.exist(res.body.errors);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -376,6 +382,15 @@ describe('Oembed API', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips fetching IPv4 addresses', async function () {
|
it('skips fetching IPv4 addresses', async function () {
|
||||||
|
dnsPromises.lookup.restore();
|
||||||
|
sinon.stub(dnsPromises, 'lookup').callsFake(function (hostname) {
|
||||||
|
if (hostname === '192.168.0.1') {
|
||||||
|
return Promise.resolve({address: '192.168.0.1'});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({address: '123.123.123.123'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const pageMock = nock('http://test.com')
|
const pageMock = nock('http://test.com')
|
||||||
.get('/')
|
.get('/')
|
||||||
.reply(200, '<html><head><link rel="alternate" type="application/json+oembed" href="http://192.168.0.1/oembed"></head></html>');
|
.reply(200, '<html><head><link rel="alternate" type="application/json+oembed" href="http://192.168.0.1/oembed"></head></html>');
|
||||||
|
@ -399,6 +414,15 @@ describe('Oembed API', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips fetching IPv6 addresses', async function () {
|
it('skips fetching IPv6 addresses', async function () {
|
||||||
|
dnsPromises.lookup.restore();
|
||||||
|
sinon.stub(dnsPromises, 'lookup').callsFake(function (hostname) {
|
||||||
|
if (hostname === '[2607:f0d0:1002:51::4]') {
|
||||||
|
return Promise.resolve({address: '192.168.0.1'});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({address: '123.123.123.123'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const pageMock = nock('http://test.com')
|
const pageMock = nock('http://test.com')
|
||||||
.get('/')
|
.get('/')
|
||||||
.reply(200, '<html><head><link rel="alternate" type="application/json+oembed" href="http://[2607:f0d0:1002:51::4]:9999/oembed"></head></html>');
|
.reply(200, '<html><head><link rel="alternate" type="application/json+oembed" href="http://[2607:f0d0:1002:51::4]:9999/oembed"></head></html>');
|
||||||
|
@ -422,6 +446,14 @@ describe('Oembed API', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips fetching localhost', async function () {
|
it('skips fetching localhost', async function () {
|
||||||
|
dnsPromises.lookup.restore();
|
||||||
|
sinon.stub(dnsPromises, 'lookup').callsFake(function (hostname) {
|
||||||
|
if (hostname === 'localhost') {
|
||||||
|
return Promise.resolve({address: '127.0.0.1'});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({address: '123.123.123.123'});
|
||||||
|
}
|
||||||
|
});
|
||||||
const pageMock = nock('http://test.com')
|
const pageMock = nock('http://test.com')
|
||||||
.get('/')
|
.get('/')
|
||||||
.reply(200, '<html><head><link rel="alternate" type="application/json+oembed" href="http://localhost:9999/oembed"></head></html>');
|
.reply(200, '<html><head><link rel="alternate" type="application/json+oembed" href="http://localhost:9999/oembed"></head></html>');
|
||||||
|
|
|
@ -263,7 +263,7 @@ describe('External Request', function () {
|
||||||
throw new Error('Request should have rejected with invalid url message');
|
throw new Error('Request should have rejected with invalid url message');
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
should.exist(err);
|
should.exist(err);
|
||||||
err.message.should.be.equal('URL empty or invalid.');
|
err.code.should.be.equal('ERR_INVALID_URL');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -279,7 +279,8 @@ describe('External Request', function () {
|
||||||
throw new Error('Request should have rejected with invalid url message');
|
throw new Error('Request should have rejected with invalid url message');
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
should.exist(err);
|
should.exist(err);
|
||||||
err.message.should.be.equal('URL empty or invalid.');
|
// got v11+ throws an error instead of the external requests lib
|
||||||
|
err.message.should.be.equal('No URL protocol specified');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -300,7 +301,7 @@ describe('External Request', function () {
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
requestMock.isDone().should.be.true();
|
requestMock.isDone().should.be.true();
|
||||||
should.exist(err);
|
should.exist(err);
|
||||||
err.statusMessage.should.be.equal('Not Found');
|
err.response.statusMessage.should.be.equal('Not Found');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -322,9 +323,7 @@ describe('External Request', function () {
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
requestMock.isDone().should.be.true();
|
requestMock.isDone().should.be.true();
|
||||||
should.exist(err);
|
should.exist(err);
|
||||||
err.statusMessage.should.be.equal('Internal Server Error');
|
err.response.statusMessage.should.be.equal(`Internal Server Error`);
|
||||||
err.body.should.match(/something awful happened/);
|
|
||||||
err.body.should.match(/AWFUL_ERROR/);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,9 +7,8 @@ module.exports = class GeolocationService {
|
||||||
if (!ipAddress || (!IPV4_REGEX.test(ipAddress) && !IPV6_REGEX.test(ipAddress))) {
|
if (!ipAddress || (!IPV4_REGEX.test(ipAddress) && !IPV6_REGEX.test(ipAddress))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const geojsUrl = `https://get.geojs.io/v1/ip/geo/${encodeURIComponent(ipAddress)}.json`;
|
const geojsUrl = `https://get.geojs.io/v1/ip/geo/${encodeURIComponent(ipAddress)}.json`;
|
||||||
const response = await got(geojsUrl, {json: true, timeout: 500});
|
const response = await got(geojsUrl, {timeout: 500}).json();
|
||||||
return response.body;
|
return response;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
"body-parser": "1.20.1",
|
"body-parser": "1.20.1",
|
||||||
"bson-objectid": "2.0.4",
|
"bson-objectid": "2.0.4",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"got": "9.6.0",
|
"got": "11.8.6",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"moment": "2.29.4",
|
"moment": "2.29.4",
|
||||||
|
|
|
@ -4,7 +4,6 @@ const logging = require('@tryghost/logging');
|
||||||
const {extract, hasProvider} = require('oembed-parser');
|
const {extract, hasProvider} = require('oembed-parser');
|
||||||
const cheerio = require('cheerio');
|
const cheerio = require('cheerio');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const {CookieJar} = require('tough-cookie');
|
|
||||||
const charset = require('charset');
|
const charset = require('charset');
|
||||||
const iconv = require('iconv-lite');
|
const iconv = require('iconv-lite');
|
||||||
|
|
||||||
|
@ -68,16 +67,7 @@ class OEmbed {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
/** @type {IExternalRequest} */
|
/** @type {IExternalRequest} */
|
||||||
this.externalRequest = async (url, requestConfig) => {
|
this.externalRequest = externalRequest;
|
||||||
if (this.isIpOrLocalhost(url)) {
|
|
||||||
return this.unknownProvider(url);
|
|
||||||
}
|
|
||||||
const response = await externalRequest(url, requestConfig);
|
|
||||||
if (this.isIpOrLocalhost(response.url)) {
|
|
||||||
return this.unknownProvider(url);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {ICustomProvider[]} */
|
/** @type {ICustomProvider[]} */
|
||||||
this.customProviders = [];
|
this.customProviders = [];
|
||||||
|
@ -117,16 +107,13 @@ class OEmbed {
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
*
|
*
|
||||||
* @returns {Promise<{url: string, body: any, headers: any}>}
|
* @returns {GotPromise<any>}
|
||||||
*/
|
*/
|
||||||
async fetchPage(url, options) {
|
fetchPage(url, options) {
|
||||||
const cookieJar = new CookieJar();
|
|
||||||
return this.externalRequest(
|
return this.externalRequest(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
cookieJar,
|
timeout: 2000,
|
||||||
method: 'GET',
|
|
||||||
timeout: 2 * 1000,
|
|
||||||
followRedirect: true,
|
followRedirect: true,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
|
@ -140,7 +127,7 @@ class OEmbed {
|
||||||
async fetchPageHtml(url) {
|
async fetchPageHtml(url) {
|
||||||
// Fetch url and get response as binary buffer to
|
// Fetch url and get response as binary buffer to
|
||||||
// avoid implicit cast
|
// avoid implicit cast
|
||||||
const {headers, body, url: responseUrl} = await this.fetchPage(
|
let {headers, body, url: responseUrl} = await this.fetchPage(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
encoding: 'binary',
|
encoding: 'binary',
|
||||||
|
@ -162,7 +149,7 @@ class OEmbed {
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodedBody = iconv.decode(
|
const decodedBody = iconv.decode(
|
||||||
Buffer.from(body, 'binary'), encoding);
|
body, encoding);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
body: decodedBody,
|
body: decodedBody,
|
||||||
|
@ -184,12 +171,9 @@ class OEmbed {
|
||||||
* @returns {Promise<{url: string, body: Object}>}
|
* @returns {Promise<{url: string, body: Object}>}
|
||||||
*/
|
*/
|
||||||
async fetchPageJson(url) {
|
async fetchPageJson(url) {
|
||||||
const {body, url: pageUrl} = await this.fetchPage(
|
const res = await this.fetchPage(url, {responseType: 'json'});
|
||||||
url,
|
const body = res.body;
|
||||||
{
|
const pageUrl = res.url;
|
||||||
json: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
body,
|
body,
|
||||||
url: pageUrl
|
url: pageUrl
|
||||||
|
@ -247,34 +231,6 @@ class OEmbed {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} url
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isIpOrLocalhost(url) {
|
|
||||||
try {
|
|
||||||
const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
||||||
const IPV6_REGEX = /:/; // fqdns will not have colons
|
|
||||||
const HTTP_REGEX = /^https?:/i;
|
|
||||||
|
|
||||||
const siteUrl = new URL(this.config.get('url'));
|
|
||||||
const {protocol, hostname, host} = new URL(url);
|
|
||||||
|
|
||||||
// allow requests to Ghost's own url through
|
|
||||||
if (siteUrl.host === host) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HTTP_REGEX.test(protocol) || hostname === 'localhost' || IPV4_REGEX.test(hostname) || IPV6_REGEX.test(hostname)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {string} html
|
* @param {string} html
|
||||||
|
@ -300,7 +256,6 @@ class OEmbed {
|
||||||
|
|
||||||
// fetch oembed response from embedded rel="alternate" url
|
// fetch oembed response from embedded rel="alternate" url
|
||||||
const oembedResponse = await this.fetchPageJson(oembedUrl);
|
const oembedResponse = await this.fetchPageJson(oembedUrl);
|
||||||
|
|
||||||
// validate the fetched json against the oembed spec to avoid
|
// validate the fetched json against the oembed spec to avoid
|
||||||
// leaking non-oembed responses
|
// leaking non-oembed responses
|
||||||
const body = oembedResponse.body;
|
const body = oembedResponse.body;
|
||||||
|
|
|
@ -17,7 +17,7 @@ module.exports = class MentionDiscoveryService {
|
||||||
try {
|
try {
|
||||||
const response = await this.#externalRequest(url.href, {
|
const response = await this.#externalRequest(url.href, {
|
||||||
throwHttpErrors: false,
|
throwHttpErrors: false,
|
||||||
followRedirects: true,
|
followRedirect: true,
|
||||||
maxRedirects: 10
|
maxRedirects: 10
|
||||||
});
|
});
|
||||||
if (response.statusCode === 404) {
|
if (response.statusCode === 404) {
|
||||||
|
|
|
@ -79,22 +79,24 @@ module.exports = class MentionSendingService {
|
||||||
|
|
||||||
async send({source, target, endpoint}) {
|
async send({source, target, endpoint}) {
|
||||||
logging.info('[Webmention] Sending webmention from ' + source.href + ' to ' + target.href + ' via ' + endpoint.href);
|
logging.info('[Webmention] Sending webmention from ' + source.href + ' to ' + target.href + ' via ' + endpoint.href);
|
||||||
|
|
||||||
|
// default content type is application/x-www-form-encoded which is what we need for the webmentions spec
|
||||||
const response = await this.#externalRequest.post(endpoint.href, {
|
const response = await this.#externalRequest.post(endpoint.href, {
|
||||||
body: {
|
form: {
|
||||||
source: source.href,
|
source: source.href,
|
||||||
target: target.href,
|
target: target.href,
|
||||||
source_is_ghost: true
|
source_is_ghost: true
|
||||||
},
|
},
|
||||||
form: true,
|
|
||||||
throwHttpErrors: false,
|
throwHttpErrors: false,
|
||||||
maxRedirects: 10,
|
maxRedirects: 10,
|
||||||
followRedirect: true,
|
followRedirect: true,
|
||||||
methodRewriting: false, // WARNING! this setting has a different meaning in got v12!
|
|
||||||
timeout: 10000
|
timeout: 10000
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new errors.BadRequestError({
|
throw new errors.BadRequestError({
|
||||||
message: 'Webmention sending failed with status code ' + response.statusCode,
|
message: 'Webmention sending failed with status code ' + response.statusCode,
|
||||||
statusCode: response.statusCode
|
statusCode: response.statusCode
|
||||||
|
|
|
@ -37,22 +37,20 @@ describe('MentionDiscoveryService', function () {
|
||||||
assert.equal(endpoint, null);
|
assert.equal(endpoint, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: need to support redirects
|
it('Follows redirects', async function () {
|
||||||
// it('Follows redirects', async function () {
|
let url = new URL('http://redirector.io/');
|
||||||
|
let nextUrl = new URL('http://testpage.com/');
|
||||||
|
|
||||||
// let url = new URL('http://redirector.io/');
|
nock(url.href)
|
||||||
// let nextUrl = new URL('http://testpage.com/');
|
.intercept('/', 'HEAD')
|
||||||
|
.reply(301, undefined, {location: nextUrl.href})
|
||||||
|
.get('/')
|
||||||
|
.reply(200, '<link rel="webmention" href="http://valid.site.org" />Very cool site', {'content-type': 'text/html'});
|
||||||
|
|
||||||
// let mockRedirect = nock(url.href)
|
let endpoint = await service.getEndpoint(url);
|
||||||
// .intercept("/", "HEAD")
|
|
||||||
// .reply(301, undefined, { location: nextUrl.href })
|
|
||||||
// .get('/')
|
|
||||||
// .reply(200, '<link rel="webmention" href="http://valid.site.org" />Very cool site', { 'content-type': 'text/html' });
|
|
||||||
|
|
||||||
// let endpoint = await service.getEndpoint(url);
|
assert(endpoint instanceof URL);
|
||||||
|
});
|
||||||
// assert(endpoint instanceof URL);
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('Can parse headers', function () {
|
describe('Can parse headers', function () {
|
||||||
it('Returns null for a valid non-html site', async function () {
|
it('Returns null for a valid non-html site', async function () {
|
||||||
|
|
|
@ -380,6 +380,9 @@ describe('MentionSendingService', function () {
|
||||||
|
|
||||||
describe('send', function () {
|
describe('send', function () {
|
||||||
it('Can handle 202 accepted responses', async function () {
|
it('Can handle 202 accepted responses', async function () {
|
||||||
|
const source = new URL('https://example.com/source');
|
||||||
|
const target = new URL('https://target.com/target');
|
||||||
|
const endpoint = new URL('https://example.org/webmentions-test');
|
||||||
const scope = nock('https://example.org')
|
const scope = nock('https://example.org')
|
||||||
.persist()
|
.persist()
|
||||||
.post('/webmentions-test', `source=${encodeURIComponent('https://example.com/source')}&target=${encodeURIComponent('https://target.com/target')}&source_is_ghost=true`)
|
.post('/webmentions-test', `source=${encodeURIComponent('https://example.com/source')}&target=${encodeURIComponent('https://target.com/target')}&source_is_ghost=true`)
|
||||||
|
@ -387,14 +390,17 @@ describe('MentionSendingService', function () {
|
||||||
|
|
||||||
const service = new MentionSendingService({externalRequest});
|
const service = new MentionSendingService({externalRequest});
|
||||||
await service.send({
|
await service.send({
|
||||||
source: new URL('https://example.com/source'),
|
source: source,
|
||||||
target: new URL('https://target.com/target'),
|
target: target,
|
||||||
endpoint: new URL('https://example.org/webmentions-test')
|
endpoint: endpoint
|
||||||
});
|
});
|
||||||
assert(scope.isDone());
|
assert(scope.isDone());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can handle 201 created responses', async function () {
|
it('Can handle 201 created responses', async function () {
|
||||||
|
const source = new URL('https://example.com/source');
|
||||||
|
const target = new URL('https://target.com/target');
|
||||||
|
const endpoint = new URL('https://example.org/webmentions-test');
|
||||||
const scope = nock('https://example.org')
|
const scope = nock('https://example.org')
|
||||||
.persist()
|
.persist()
|
||||||
.post('/webmentions-test', `source=${encodeURIComponent('https://example.com/source')}&target=${encodeURIComponent('https://target.com/target')}&source_is_ghost=true`)
|
.post('/webmentions-test', `source=${encodeURIComponent('https://example.com/source')}&target=${encodeURIComponent('https://target.com/target')}&source_is_ghost=true`)
|
||||||
|
@ -402,9 +408,9 @@ describe('MentionSendingService', function () {
|
||||||
|
|
||||||
const service = new MentionSendingService({externalRequest});
|
const service = new MentionSendingService({externalRequest});
|
||||||
await service.send({
|
await service.send({
|
||||||
source: new URL('https://example.com/source'),
|
source: source,
|
||||||
target: new URL('https://target.com/target'),
|
target: target,
|
||||||
endpoint: new URL('https://example.org/webmentions-test')
|
endpoint: endpoint
|
||||||
});
|
});
|
||||||
assert(scope.isDone());
|
assert(scope.isDone());
|
||||||
});
|
});
|
||||||
|
@ -439,6 +445,28 @@ describe('MentionSendingService', function () {
|
||||||
assert(scope.isDone());
|
assert(scope.isDone());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Can handle redirect responses', async function () {
|
||||||
|
const scope = nock('https://example.org')
|
||||||
|
.persist()
|
||||||
|
.post('/webmentions-test')
|
||||||
|
.reply(302, '', {
|
||||||
|
Location: 'https://example.org/webmentions-test-2'
|
||||||
|
});
|
||||||
|
const scope2 = nock('https://example.org')
|
||||||
|
.persist()
|
||||||
|
.post('/webmentions-test-2')
|
||||||
|
.reply(201);
|
||||||
|
|
||||||
|
const service = new MentionSendingService({externalRequest});
|
||||||
|
await service.send({
|
||||||
|
source: new URL('https://example.com'),
|
||||||
|
target: new URL('https://example.com'),
|
||||||
|
endpoint: new URL('https://example.org/webmentions-test')
|
||||||
|
});
|
||||||
|
assert(scope.isDone());
|
||||||
|
assert(scope2.isDone());
|
||||||
|
});
|
||||||
|
|
||||||
it('Can handle network errors', async function () {
|
it('Can handle network errors', async function () {
|
||||||
const scope = nock('https://example.org')
|
const scope = nock('https://example.org')
|
||||||
.persist()
|
.persist()
|
||||||
|
@ -454,41 +482,6 @@ describe('MentionSendingService', function () {
|
||||||
assert(scope.isDone());
|
assert(scope.isDone());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redirects are currently not supported by got for POST requests!
|
|
||||||
//it('Can handle redirect responses', async function () {
|
|
||||||
// const scope = nock('https://example.org')
|
|
||||||
// .persist()
|
|
||||||
// .post('/webmentions-test')
|
|
||||||
// .reply(302, '', {
|
|
||||||
// headers: {
|
|
||||||
// Location: 'https://example.org/webmentions-test-2'
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// const scope2 = nock('https://example.org')
|
|
||||||
// .persist()
|
|
||||||
// .post('/webmentions-test-2')
|
|
||||||
// .reply(201);
|
|
||||||
//
|
|
||||||
// const service = new MentionSendingService({externalRequest});
|
|
||||||
// await service.send({
|
|
||||||
// source: new URL('https://example.com'),
|
|
||||||
// target: new URL('https://example.com'),
|
|
||||||
// endpoint: new URL('https://example.org/webmentions-test')
|
|
||||||
// });
|
|
||||||
// assert(scope.isDone());
|
|
||||||
// assert(scope2.isDone());
|
|
||||||
//});
|
|
||||||
// TODO: also check if we don't follow private IPs after redirects
|
|
||||||
|
|
||||||
it('Does not send to private IP', async function () {
|
|
||||||
const service = new MentionSendingService({externalRequest});
|
|
||||||
await assert.rejects(service.send({
|
|
||||||
source: new URL('https://example.com/source'),
|
|
||||||
target: new URL('https://target.com/target'),
|
|
||||||
endpoint: new URL('http://localhost/webmentions')
|
|
||||||
}), /non-permitted private IP/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Does not send to private IP behind DNS', async function () {
|
it('Does not send to private IP behind DNS', async function () {
|
||||||
// Test that we don't make a request when a domain resolves to a private IP
|
// Test that we don't make a request when a domain resolves to a private IP
|
||||||
// domaincontrol.com -> 127.0.0.1
|
// domaincontrol.com -> 127.0.0.1
|
||||||
|
|
Loading…
Add table
Reference in a new issue