phase one of pingback receive XML-RPC implementation

This commit is contained in:
Wouter Groeneveld 2021-03-23 21:04:17 +01:00
parent f3e2d1d1c9
commit a7a4714ea0
4 changed files with 261 additions and 0 deletions

43
src/pingback/receive.js Normal file
View File

@ -0,0 +1,43 @@
const config = require('./../config')
const parser = require("fast-xml-parser")
/**
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>https://brainbaking.com/kristien.html</string></value>
</param>
<param>
<value><string>https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/</string></value>
</param>
</params>
</methodCall>
*/
const isValidDomain = (url) => {
return config.allowedWebmentionSources.some(domain => {
return url.indexOf(domain) !== -1
})
}
function validate(body) {
const xml = parser.parse(body)
if(!xml) return false
if(!xml.methodCall || xml.methodCall.methodName !== "pingback.ping") return false
if(!xml.methodCall.params || !xml.methodCall.params.param || xml.methodCall.params.param.length !== 2) return false
if(!xml.methodCall.params.param.every(param => param?.value?.string?.startsWith('http'))) return false
if(!isValidDomain(xml.methodCall.params.param[1].value.string)) return false
return true
}
async function receive(body) {
}
module.exports = {
receive,
validate
}

74
src/pingback/route.js Normal file
View File

@ -0,0 +1,74 @@
const pingbackReceiver = require('./receive')
function success(msg) {
return `<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param>
<value>
<string>
${msg}
</string>
</value>
</param>
</params>
</methodResponse>
`
}
function err(e) {
console.err(` -- pingback receive went wrong: ${e.message}`)
return `<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>
faultCode
</name>
<value>
<int>
0
</int>
</value>
</member>
<member>
<name>
faultString
</name>
<value>
<string>
${e.message}
</string>
</value>
</member>
</struct>
</value>
</fault>
</methodResponse>`
}
function route(router) {
router.post("pingback receive endpoint", "/pingback", async (ctx) => {
try {
if(!pingbackReceiver.validate(ctx.request.body)) {
throw "malformed pingback request"
}
// we do NOT await this on purpose.
pingbackReceiver.receive(ctx.request.body)
ctx.status = 200
ctx.body = success("Thanks, bro. Will process this pingback soon, pinky swear!")
} catch(e) {
ctx.status = 200
ctx.body = err(e)
}
});
}
module.exports = {
route
}

View File

@ -29,6 +29,7 @@ app.use(bodyParser({
// route docs: https://github.com/koajs/router/blob/HEAD/API.md#module_koa-router--Router+get%7Cput%7Cpost%7Cpatch%7Cdelete%7Cdel
require("./webmention/route").route(router);
require("./pingback/route").route(router);
const config = require("./config");
config.setupDataDirs();

View File

@ -0,0 +1,143 @@
describe("pingback receive validation tests", () => {
const { validate } = require('../../src/pingback/receive')
test("not valid if malformed XML as body", () => {
const result = validate("ola pola")
expect(result).toBe(false)
})
test("not valid if methodName is not pingback.ping", () => {
const result = validate(`
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>ka.tsjing</methodName>
<params>
<param>
<value><string>https://cool.site</string></value>
</param>
<param>
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`)
expect(result).toBe(false)
})
test("not valid if less than two parameters", () => {
const result = validate(`
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`)
expect(result).toBe(false)
})
test("not valid if more than two parameters", () => {
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>https://cool.site</string></value>
</param>
<param>
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
<param>
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`
expect(validate(xml)).toBe(false)
})
test("not valid if target is not in trusted domains from config", () => {
const result = validate(`
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>https://cool.site</string></value>
</param>
<param>
<value><string>https://flashballz.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`)
expect(result).toBe(false)
})
test("not valid if target is not http(s)", () => {
const result = validate(`
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>https://cool.site</string></value>
</param>
<param>
<value><string>gemini://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`)
expect(result).toBe(false)
})
test("not valid if source is not http(s)", () => {
const result = validate(`
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>gemini://cool.site</string></value>
</param>
<param>
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`)
expect(result).toBe(false)
})
test("is valid if pingback.ping and two http(s) parameters of which target is trusted", () => {
const result = validate(`
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param>
<value><string>https://cool.site</string></value>
</param>
<param>
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
</param>
</params>
</methodCall>
`)
expect(result).toBe(true)
})
})