hedgeagent/1.12.0 #1
28
lib/links.js
Normal file
28
lib/links.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
module.exports = {
|
||||||
|
serveLinkWarningPage: function (req, res) {
|
||||||
|
let targetURL = typeof req.query.url === 'string' ? req.query.url : ''
|
||||||
|
let noteURL = typeof req.query.note === 'string' ? req.query.note : ''
|
||||||
|
if (noteURL !== '' && !/^[\w-]+$/.test(noteURL)) {
|
||||||
|
noteURL = ''
|
||||||
|
}
|
||||||
|
let valid = false
|
||||||
|
try {
|
||||||
|
targetURL = decodeURIComponent(targetURL)
|
||||||
|
const parsed = new URL(targetURL)
|
||||||
|
valid = ['http:', 'https:'].includes(parsed.protocol)
|
||||||
|
targetURL = parsed.href
|
||||||
|
} catch (err) {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
res.set({
|
||||||
|
'Cache-Control': 'no-store'
|
||||||
|
})
|
||||||
|
res.render('link.ejs', {
|
||||||
|
title: 'External link',
|
||||||
|
valid,
|
||||||
|
noteURL,
|
||||||
|
targetURL,
|
||||||
|
opengraph: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ const baseRouter = module.exports = Router()
|
|||||||
|
|
||||||
const errors = require('../errors')
|
const errors = require('../errors')
|
||||||
|
|
||||||
|
const links = require('../links')
|
||||||
|
|
||||||
// get index
|
// get index
|
||||||
baseRouter.get('/', response.showIndex)
|
baseRouter.get('/', response.showIndex)
|
||||||
// get 403 forbidden
|
// get 403 forbidden
|
||||||
@@ -22,3 +24,5 @@ baseRouter.get('/404', function (req, res) {
|
|||||||
baseRouter.get('/500', function (req, res) {
|
baseRouter.get('/500', function (req, res) {
|
||||||
errors.errorInternalError(res)
|
errors.errorInternalError(res)
|
||||||
})
|
})
|
||||||
|
// get external link warning page
|
||||||
|
baseRouter.get('/_link', links.serveLinkWarningPage)
|
||||||
|
|||||||
@@ -126,5 +126,13 @@
|
|||||||
"Protected - Only owner can edit (forbid guests)": "Geschützt - Nur Besitzer kann bearbeiten (Gäste verboten)",
|
"Protected - Only owner can edit (forbid guests)": "Geschützt - Nur Besitzer kann bearbeiten (Gäste verboten)",
|
||||||
"Private - Only owner can view & edit": "Privat - Nur Besitzer kann anschauen & bearbeiten",
|
"Private - Only owner can view & edit": "Privat - Nur Besitzer kann anschauen & bearbeiten",
|
||||||
"changed": "verändert",
|
"changed": "verändert",
|
||||||
"created": "erstellt"
|
"created": "erstellt",
|
||||||
|
"External link": "Externer Link",
|
||||||
|
"You've clicked a link that points to an external page.": "Es wurde ein Link zu einer externen Seite angeklickt.",
|
||||||
|
"The content of that page is unrelated to HedgeDoc. Do not enter your HedgeDoc credentials there.": "Der Inhalt dieser Seite ist unabhängig von HedgeDoc. Dort keine HedgeDoc-Zugangsdaten eingeben.",
|
||||||
|
"Do you want to continue?": "Fortfahren?",
|
||||||
|
"Continue to external page": "Weiter zur externen Seite",
|
||||||
|
"Back to your note": "Zurück zur Notiz",
|
||||||
|
"Invalid link": "Ungültiger Link",
|
||||||
|
"The provided link is not a valid URL.": "Die angegebene URL ist nicht gültig."
|
||||||
}
|
}
|
||||||
@@ -126,5 +126,13 @@
|
|||||||
"Protected - Only owner can edit (forbid guests)": "Protected - Only owner can edit (forbid guests)",
|
"Protected - Only owner can edit (forbid guests)": "Protected - Only owner can edit (forbid guests)",
|
||||||
"Private - Only owner can view & edit": "Private - Only owner can view & edit",
|
"Private - Only owner can view & edit": "Private - Only owner can view & edit",
|
||||||
"changed": "changed",
|
"changed": "changed",
|
||||||
"created": "created"
|
"created": "created",
|
||||||
|
"You've clicked a link that points to an external page.": "You've clicked a link that points to an external page.",
|
||||||
|
"External link": "External link",
|
||||||
|
"Invalid link": "Invalid link",
|
||||||
|
"Do you want to continue?": "Do you want to continue?",
|
||||||
|
"Continue to external page": "Continue to external page",
|
||||||
|
"Back to your note": "Back to your note",
|
||||||
|
"The provided link is not a valid URL.": "The provided link is not a valid URL.",
|
||||||
|
"The content of that page is unrelated to HedgeDoc. Do not enter your HedgeDoc credentials there.": "The content of that page is unrelated to HedgeDoc. Do not enter your HedgeDoc credentials there."
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## <i class="fa fa-tag"></i> 1.x <i class="fa fa-calendar-o"></i> UNRELEASED
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- Added a warning page when clicking external links
|
||||||
|
|
||||||
## <i class="fa fa-tag"></i> 1.10.8 <i class="fa fa-calendar-o"></i> 2026-04-15
|
## <i class="fa fa-tag"></i> 1.10.8 <i class="fa fa-calendar-o"></i> 2026-04-15
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|||||||
@@ -557,10 +557,6 @@ export function postProcess (code) {
|
|||||||
html = html.replace(/@import url\(([^)]*)\);?/gi, '')
|
html = html.replace(/@import url\(([^)]*)\);?/gi, '')
|
||||||
$(value).html(html)
|
$(value).html(html)
|
||||||
})
|
})
|
||||||
// link should open in new window or tab
|
|
||||||
// also add noopener to prevent clickjacking
|
|
||||||
// See details: https://mathiasbynens.github.io/rel-noopener/
|
|
||||||
result.find('a:not([href^="#"]):not([target])').attr('target', '_blank').attr('rel', 'noopener')
|
|
||||||
|
|
||||||
// If it's hashtag link then make it base uri independent
|
// If it's hashtag link then make it base uri independent
|
||||||
result.find('a[href^="#"]').each((index, linkTag) => {
|
result.find('a[href^="#"]').each((index, linkTag) => {
|
||||||
@@ -568,6 +564,7 @@ export function postProcess (code) {
|
|||||||
currentLocation.hash = linkTag.hash
|
currentLocation.hash = linkTag.hash
|
||||||
linkTag.href = currentLocation.toString()
|
linkTag.href = currentLocation.toString()
|
||||||
})
|
})
|
||||||
|
rewriteExternalLinks(result)
|
||||||
|
|
||||||
// update continue line numbers
|
// update continue line numbers
|
||||||
const linenumberdivs = result.find('.gutter.linenumber').toArray()
|
const linenumberdivs = result.find('.gutter.linenumber').toArray()
|
||||||
@@ -593,6 +590,30 @@ export function postProcess (code) {
|
|||||||
}
|
}
|
||||||
window.postProcess = postProcess
|
window.postProcess = postProcess
|
||||||
|
|
||||||
|
// rewrite external links to go through the /_link warning page
|
||||||
|
export function rewriteExternalLinks (view) {
|
||||||
|
view.find('a[href]').each((index, linkTag) => {
|
||||||
|
const anchor = $(linkTag)
|
||||||
|
const href = anchor.attr('href')
|
||||||
|
if (!href || href.startsWith('#')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let parsed
|
||||||
|
try {
|
||||||
|
parsed = new URL(href)
|
||||||
|
} catch (err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// only rewrite links that have an absolute http(s) URL on a different origin
|
||||||
|
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.origin === window.location.origin) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const noteURL = window.location.pathname.split('/').pop()
|
||||||
|
anchor.attr('href', `${window.location.origin}/_link?url=${encodeURIComponent(parsed.href)}¬e=${noteURL}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
window.rewriteExternalLinks = rewriteExternalLinks
|
||||||
|
|
||||||
const domevents = Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function (i) {
|
const domevents = Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function (i) {
|
||||||
return !i.indexOf('on') && (document[i] === null || typeof document[i] === 'function')
|
return !i.indexOf('on') && (document[i] === null || typeof document[i] === 'function')
|
||||||
}).filter(function (elem, pos, self) {
|
}).filter(function (elem, pos, self) {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import {
|
|||||||
renderTOC,
|
renderTOC,
|
||||||
renderTags,
|
renderTags,
|
||||||
renderTitle,
|
renderTitle,
|
||||||
|
rewriteExternalLinks,
|
||||||
scrollToHash,
|
scrollToHash,
|
||||||
smoothHashScroll,
|
smoothHashScroll,
|
||||||
updateLastChange,
|
updateLastChange,
|
||||||
@@ -3498,6 +3499,7 @@ function updateViewInner () {
|
|||||||
}
|
}
|
||||||
removeDOMEvents(ui.area.markdown)
|
removeDOMEvents(ui.area.markdown)
|
||||||
finishView(ui.area.markdown)
|
finishView(ui.area.markdown)
|
||||||
|
rewriteExternalLinks(ui.area.markdown)
|
||||||
autoLinkify(ui.area.markdown)
|
autoLinkify(ui.area.markdown)
|
||||||
deduplicatedHeaderId(ui.area.markdown)
|
deduplicatedHeaderId(ui.area.markdown)
|
||||||
renderTOC(ui.area.markdown)
|
renderTOC(ui.area.markdown)
|
||||||
|
|||||||
41
public/views/link.ejs
Normal file
41
public/views/link.ejs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<%- include('hedgedoc/head') %>
|
||||||
|
<link rel="stylesheet" href="<%- serverURL %>/css/center.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<%- include('hedgedoc/header') %>
|
||||||
|
<div class="container-fluid text-center">
|
||||||
|
<div class="vertical-center-row">
|
||||||
|
<h1>
|
||||||
|
<% if (valid) { %>
|
||||||
|
<%= __('External link') %>
|
||||||
|
<% } else { %>
|
||||||
|
<%= __('Invalid link') %>
|
||||||
|
<% } %>
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
<%= __('You\'ve clicked a link that points to an external page.') %>
|
||||||
|
<br>
|
||||||
|
<%= __('The content of that page is unrelated to HedgeDoc. Do not enter your HedgeDoc credentials there.') %>
|
||||||
|
</p>
|
||||||
|
<% if (valid) { %>
|
||||||
|
<p><code class="text-break"><%- targetURL %></code></p>
|
||||||
|
<p><%= __('Do you want to continue?') %></p>
|
||||||
|
<p>
|
||||||
|
<a class="btn btn-primary" href="<%- targetURL %>"><%= __('Continue to external page') %></a>
|
||||||
|
<a class="btn btn-default" href="<%- serverURL %>/<%- noteURL %>"><%= __('Back to your note') %></a>
|
||||||
|
</p>
|
||||||
|
<% } else { %>
|
||||||
|
<p class="text-danger"><strong><%= __('The provided link is not a valid URL.') %></strong></p>
|
||||||
|
<p>
|
||||||
|
<a class="btn btn-default" href="<%- serverURL %>/<%- noteURL %>"><%= __('Back to your note') %></a>
|
||||||
|
</p>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user