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;
+ });
});