1
0
mirror of synced 2025-12-25 11:03:37 -05:00
Files
docs/middleware/handle-invalid-querystrings.js

90 lines
2.5 KiB
JavaScript

import statsd from '../lib/statsd.js'
import { noCacheControl, defaultCacheControl } from './cache-control.js'
const STATSD_KEY = 'middleware.handle_invalid_querystrings'
// Exported for the sake of end-to-end tests
export const MAX_UNFAMILIAR_KEYS_BAD_REQUEST = 15
export const MAX_UNFAMILIAR_KEYS_REDIRECT = 3
const RECOGNIZED_KEYS_BY_PREFIX = {
'/_next/data/': ['versionId', 'productId', 'restPage', 'apiVersion', 'category', 'subcategory'],
'/api/search': ['query', 'language', 'version', 'page', 'product', 'autocomplete', 'limit'],
'/api/anchor-redirect': ['hash', 'path'],
'/api/webhooks': ['category', 'version'],
'/api/pageinfo': ['pathname'],
}
const RECOGNIZED_KEYS_BY_ANY = new Set([
// Learning track pages
'learn',
'learnProduct',
// Platform picker
'platform',
// Tool picker
'tool',
// When apiVersion isn't the only one. E.g. ?apiVersion=XXX&tool=vscode
'apiVersion',
// Search
'query',
// The drop-downs on "Webhook events and payloads"
'actionType',
])
export default function handleInvalidQuerystrings(req, res, next) {
const { method, query, path } = req
if (method === 'GET' || method === 'HEAD') {
const originalKeys = Object.keys(query)
let keys = originalKeys.filter((key) => !RECOGNIZED_KEYS_BY_ANY.has(key))
if (keys.length > 0) {
// Before we judge the number of query strings, strip out all the ones
// we're familiar with.
for (const [prefix, recognizedKeys] of Object.entries(RECOGNIZED_KEYS_BY_PREFIX)) {
if (path.startsWith(prefix)) {
keys = keys.filter((key) => !recognizedKeys.includes(key))
}
}
}
if (keys.length >= MAX_UNFAMILIAR_KEYS_BAD_REQUEST) {
noCacheControl(res)
res.status(400).send('Too many unrecognized query string parameters')
const tags = [
'response:400',
`url:${req.url}`,
`ip:${req.ip}`,
`path:${req.path}`,
`keys:${originalKeys.length}`,
]
statsd.increment(STATSD_KEY, 1, tags)
return
}
if (keys.length >= MAX_UNFAMILIAR_KEYS_REDIRECT) {
defaultCacheControl(res)
const sp = new URLSearchParams(query)
keys.forEach((key) => sp.delete(key))
let newURL = req.path
if (sp.toString()) newURL += `?${sp}`
res.redirect(302, newURL)
const tags = [
'response:302',
`url:${req.url}`,
`ip:${req.ip}`,
`path:${req.path}`,
`keys:${originalKeys.length}`,
]
statsd.increment(STATSD_KEY, 1, tags)
return
}
}
return next()
}