diff options
Diffstat (limited to 'bskylink/src/routes')
-rw-r--r-- | bskylink/src/routes/createShortLink.ts | 13 | ||||
-rw-r--r-- | bskylink/src/routes/redirect.ts | 55 |
2 files changed, 57 insertions, 11 deletions
diff --git a/bskylink/src/routes/createShortLink.ts b/bskylink/src/routes/createShortLink.ts index db7c3f809..629119059 100644 --- a/bskylink/src/routes/createShortLink.ts +++ b/bskylink/src/routes/createShortLink.ts @@ -1,9 +1,9 @@ import assert from 'node:assert' import bodyParser from 'body-parser' -import {Express, Request} from 'express' +import {type Express, type Request} from 'express' -import {AppContext} from '../context.js' +import {type AppContext} from '../context.js' import {LinkType} from '../db/schema.js' import {randomId} from '../util.js' import {handler} from './util.js' @@ -83,18 +83,21 @@ const getUrl = (ctx: AppContext, req: Request, id: string) => { : `https://${req.headers.host}` return `${baseUrl}/${id}` } - const baseUrl = ctx.cfg.service.hostnames.includes(req.headers.host) - ? `https://${req.headers.host}` + const host = req.headers.host ?? '' + const baseUrl = ctx.cfg.service.hostnamesSet.has(host) + ? `https://${host}` : `https://${ctx.cfg.service.hostnames[0]}` return `${baseUrl}/${id}` } const normalizedPathFromParts = (parts: string[]): string => { + // When given ['path1', 'path2', 'te:fg'], output should be + // /path1/path2/te:fg return ( '/' + parts .map(encodeURIComponent) - .map(part => part.replaceAll('%3A', ':')) // preserve colons + .map(part => part.replace(/%3A/g, ':')) // preserve colons .join('/') ) } diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 7d68e4245..681dc0bb9 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -1,10 +1,13 @@ import assert from 'node:assert' import {DAY, SECOND} from '@atproto/common' -import escapeHTML from 'escape-html' import {type Express} from 'express' import {type AppContext} from '../context.js' +import {linkRedirectContents} from '../html/linkRedirectContents.js' +import {linkWarningContents} from '../html/linkWarningContents.js' +import {linkWarningLayout} from '../html/linkWarningLayout.js' +import {redirectLogger} from '../logger.js' import {handler} from './util.js' const INTERNAL_IP_REGEX = new RegExp( @@ -39,14 +42,54 @@ export default function (ctx: AppContext, app: Express) { return res.status(302).end() } + // Default to a max age header res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`) - res.type('html') res.status(200) + res.type('html') - const escaped = escapeHTML(url.href) - return res.send( - `<html><head><meta http-equiv="refresh" content="0; URL='${escaped}'" /><style>:root { color-scheme: light dark; }</style></head></html>`, - ) + let html: string | undefined + + if (ctx.cfg.service.safelinkEnabled) { + const rule = await ctx.safelinkClient.tryFindRule(link) + if (rule !== 'ok') { + switch (rule.action) { + case 'whitelist': + redirectLogger.info({rule}, 'Whitelist rule matched') + break + case 'block': + html = linkWarningLayout( + 'Blocked Link Warning', + linkWarningContents(req, { + type: 'block', + link: url.href, + }), + ) + res.setHeader('Cache-Control', 'no-store') + redirectLogger.info({rule}, 'Block rule matched') + break + case 'warn': + html = linkWarningLayout( + 'Malicious Link Warning', + linkWarningContents(req, { + type: 'warn', + link: url.href, + }), + ) + res.setHeader('Cache-Control', 'no-store') + redirectLogger.info({rule}, 'Warn rule matched') + break + default: + redirectLogger.warn({rule}, 'Unknown rule matched') + } + } + } + + // If there is no html defined yet, we will create a redirect html + if (!html) { + html = linkRedirectContents(url.href) + } + + return res.end(html) }), ) } |