about summary refs log tree commit diff
path: root/bskylink/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'bskylink/src/routes')
-rw-r--r--bskylink/src/routes/createShortLink.ts13
-rw-r--r--bskylink/src/routes/redirect.ts55
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)
     }),
   )
 }