Co-authored-by: hailey <me@haileyok.com>
Co-authored-by: Stanislas Signoud <signez@stanisoft.net>
Co-authored-by: will berry <wsb@wills-MBP.attlocal.net>
Co-authored-by: BlueSkiesAndGreenPastures <will@blueskyweb.xyz>
Co-authored-by: Chenyu Huang <itschenyu@gmail.com>
This commit is contained in:
hailey
2025-09-02 13:36:20 -07:00
committed by GitHub
parent b2258fb6cb
commit acdc509630
23 changed files with 1120 additions and 47 deletions
+8 -5
View File
@@ -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('/')
)
}
+49 -6
View File
@@ -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)
}),
)
}