diff --git a/client/js/libs/handlebars/ircmessageparser/findLinks.js b/client/js/libs/handlebars/ircmessageparser/findLinks.js index 2c162f50..fadcb2f2 100644 --- a/client/js/libs/handlebars/ircmessageparser/findLinks.js +++ b/client/js/libs/handlebars/ircmessageparser/findLinks.js @@ -25,11 +25,18 @@ function findLinks(text) { return []; } - return matches.map((url) => ({ - start: url.index, - end: url.lastIndex, - link: url.url, - })); + return matches.map((url) => { + // Prefix protocol to protocol-aware urls + if (url.schema === "//") { + url.url = `http:${url.url}`; + } + + return { + start: url.index, + end: url.lastIndex, + link: url.url, + }; + }); } module.exports = findLinks; diff --git a/src/plugins/irc-events/link.js b/src/plugins/irc-events/link.js index aec71826..9029716e 100644 --- a/src/plugins/irc-events/link.js +++ b/src/plugins/irc-events/link.js @@ -11,7 +11,6 @@ const findLinks = require("../../../client/js/libs/handlebars/ircmessageparser/f const storage = require("../storage"); const mediaTypeRegex = /^(audio|video)\/.+/; -const linkRegex = /^https?:\/\//; // Fix ECDH curve client compatibility in Node v8/v9 // This is fixed in Node 10, but The Lounge supports LTS versions @@ -34,7 +33,7 @@ module.exports = function(client, chan, msg) { const cleanText = cleanIrcMessage(msg.text); // We will only try to prefetch http(s) links - const links = findLinks(cleanText).filter((w) => linkRegex.test(w.link)); + const links = findLinks(cleanText).filter((w) => isValidLink(w.link)); if (links.length === 0) { return; @@ -99,7 +98,7 @@ function parseHtml(preview, res, client) { } // Make sure thumbnail is a valid url - if (!linkRegex.test(preview.thumb)) { + if (!isValidLink(preview.thumb)) { preview.thumb = ""; } @@ -364,3 +363,24 @@ function fetch(uri, headers, cb) { function normalizeURL(header) { return URI(header).normalize().toString(); } + +function isValidLink(link) { + try { + const uri = URI(link); + const protocol = uri.protocol(); + + // Only fetch http and https links + if (protocol !== "http" && protocol !== "https") { + return false; + } + + // Do not fetch links without hostname or ones that contain authorization + if (!uri.hostname() || uri.username() || uri.password()) { + return false; + } + } catch (e) { + return false; + } + + return true; +} diff --git a/test/client/js/libs/handlebars/ircmessageparser/findLinks.js b/test/client/js/libs/handlebars/ircmessageparser/findLinks.js index ad04580c..e6e8420f 100644 --- a/test/client/js/libs/handlebars/ircmessageparser/findLinks.js +++ b/test/client/js/libs/handlebars/ircmessageparser/findLinks.js @@ -290,4 +290,17 @@ describe("findLinks", () => { expect(actual).to.deep.equal(expected); }); + + it("should add protocol to protocol-aware urls", () => { + const input = "//example.com"; + const expected = [{ + link: "http://example.com", + start: 0, + end: 13, + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); }); diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js index 2e0dea8b..801d92f0 100644 --- a/test/client/js/libs/handlebars/parse.js +++ b/test/client/js/libs/handlebars/parse.js @@ -7,7 +7,7 @@ describe("parse Handlebars helper", () => { it("should not introduce xss", () => { const testCases = [{ input: "", - expected: "<img onerror='location.href="//youtube.com"'>", + expected: "<img onerror='location.href="//youtube.com"'>", }, { input: '#&">bug', expected: '#&">bug', diff --git a/test/plugins/link.js b/test/plugins/link.js index 22a9c633..82278f41 100644 --- a/test/plugins/link.js +++ b/test/plugins/link.js @@ -371,4 +371,33 @@ describe("Link plugin", function() { } }); }); + + it("should fetch protocol-aware links", function(done) { + const message = this.irc.createMessage({ + text: "//localhost:9002", + }); + + link(this.irc, this.network.channels[0], message); + + this.irc.once("msg:preview", function(data) { + expect(data.preview.link).to.equal("http://localhost:9002"); + done(); + }); + }); + + it("should not try to fetch links with wrong protocol", function() { + const message = this.irc.createMessage({ + text: "ssh://example.com ftp://example.com irc://example.com http:////////example.com", + }); + + expect(message.previews).to.be.empty; + }); + + it("should not try to fetch links with username or password", function() { + const message = this.irc.createMessage({ + text: "http://root:'some%pass'@hostname/database http://a:%p@c http://a:%p@example.com http://test@example.com", + }); + + expect(message.previews).to.be.empty; + }); });