forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handle-redirects.js
131 lines (106 loc) · 4.74 KB
/
handle-redirects.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import patterns from '../../lib/patterns.js'
import { URL } from 'url'
import { pathLanguagePrefixed } from '../../lib/languages.js'
import getRedirect from '../../lib/get-redirect.js'
import { cacheControlFactory } from '../cache-control.js'
const cacheControl = cacheControlFactory(60 * 60 * 24) // one day
const noCacheControl = cacheControlFactory(0)
export default function handleRedirects(req, res, next) {
// never redirect assets
if (patterns.assetPaths.test(req.path)) return next()
// blanket redirects for languageless homepage
if (req.path === '/') {
const language = getLanguage(req)
// Undo the cookie setting that CSRF sets.
res.removeHeader('set-cookie')
noCacheControl(res)
return res.redirect(302, `/${language}`)
}
// begin redirect handling
let redirect = req.path
let queryParams = req._parsedUrl.query
// update old-style query params (#9467)
if ('q' in req.query) {
const newQueryParams = new URLSearchParams(queryParams)
newQueryParams.set('query', newQueryParams.get('q'))
newQueryParams.delete('q')
return res.redirect(301, `${req.path}?${newQueryParams.toString()}`)
}
// have to do this now because searchPath replacement changes the path as well as the query params
if (queryParams) {
queryParams = '?' + queryParams
redirect = (redirect + queryParams).replace(patterns.searchPath, '$1')
}
// remove query params temporarily so we can find the path in the redirects object
let redirectWithoutQueryParams = removeQueryParams(redirect)
const redirectTo = getRedirect(redirectWithoutQueryParams, req.context)
redirectWithoutQueryParams = redirectTo || redirectWithoutQueryParams
redirect = queryParams ? redirectWithoutQueryParams + queryParams : redirectWithoutQueryParams
if (!redirectTo && !pathLanguagePrefixed(req.path)) {
// No redirect necessary, but perhaps it's to a known page, and the URL
// currently doesn't have a language prefix, then we need to add
// the language prefix.
// We can't always force on the language prefix because some URLs
// aren't pages. They're other middleware endpoints such as
// `/healthz` which should never redirect.
// But for example, a `/authentication/connecting-to-github-with-ssh`
// needs to become `/en/authentication/connecting-to-github-with-ssh`
const possibleRedirectTo = `/en${req.path}`
if (possibleRedirectTo in req.context.pages) {
const language = getLanguage(req)
// Note, it's important to use `req.url` here and not `req.path`
// because the full URL can contain query strings.
// E.g. `/foo?json=breadcrumbs`
redirect = `/${language}${req.url}`
}
}
// do not redirect a path to itself
// req._parsedUrl.path includes query params whereas req.path does not
if (redirect === req._parsedUrl.path) {
return next()
}
// do not redirect if the redirected page can't be found
if (!req.context.pages[removeQueryParams(redirect)]) {
// display error on the page in development, but not in production
// include final full redirect path in the message
if (process.env.NODE_ENV !== 'production' && req.context) {
req.context.redirectNotFound = redirect
}
return next()
}
// Undo the cookie setting that CSRF sets.
res.removeHeader('set-cookie')
// do the redirect if the from-URL already had a language in it
if (pathLanguagePrefixed(req.path)) {
cacheControl(res)
} else {
noCacheControl(res)
}
const permanent = usePermanentRedirect(req)
return res.redirect(permanent ? 301 : 302, redirect)
}
function getLanguage(req, default_ = 'en') {
// req.context.userLanguage, if it truthy, is always a valid supported
// language. It's whatever was in the user's request but filtered
// based on non-WIP languages in lib/languages.js
return req.context.userLanguage || default_
}
function usePermanentRedirect(req) {
// If the redirect was to essentially swap `enterprise-server@latest`
// for `[email protected]` then, we definitely don't want to
// do a permanent redirect.
// When this is the case, we don't want a permanent redirect because
// it could overzealously cache in the users' browser which could
// be bad when whatever "latest" means changes.
if (req.path.includes('/enterprise-server@latest')) return false
// If the redirect involved injecting a language prefix, then don't
// permanently redirect because that could overly cache in users'
// browsers if we some day want to make the language redirect
// depend on a cookie or 'Accept-Language' header.
if (pathLanguagePrefixed(req.path)) return true
// The default is to *not* do a permanent redirect.
return false
}
function removeQueryParams(redirect) {
return new URL(redirect, 'https://docs.github.com').pathname
}