aa
This commit is contained in:
+76
@@ -1281,6 +1281,31 @@ function isProtectedDataPath(req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rawRequestPath(req) {
|
||||||
|
return String(req.url || '/').split(/[?#]/, 1)[0] || '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnsafeProtectedPath(req) {
|
||||||
|
const rawPath = rawRequestPath(req)
|
||||||
|
const lower = rawPath.toLowerCase()
|
||||||
|
const protectedPrefix =
|
||||||
|
lower === '/api' ||
|
||||||
|
lower.startsWith('/api/') ||
|
||||||
|
lower.startsWith('/api%2f') ||
|
||||||
|
lower.startsWith('/api%5c') ||
|
||||||
|
lower === '/data' ||
|
||||||
|
lower.startsWith('/data/') ||
|
||||||
|
lower.startsWith('/data%2f') ||
|
||||||
|
lower.startsWith('/data%5c')
|
||||||
|
|
||||||
|
if (!protectedPrefix) return false
|
||||||
|
|
||||||
|
const canonicalPrefix = rawPath.startsWith('/api/') || rawPath.startsWith('/data/')
|
||||||
|
if (!canonicalPrefix) return true
|
||||||
|
|
||||||
|
return /%(?:00|2e|2f|5c)/i.test(rawPath)
|
||||||
|
}
|
||||||
|
|
||||||
function isSiteSessionBootstrapPath(req) {
|
function isSiteSessionBootstrapPath(req) {
|
||||||
if (!req.url) return false
|
if (!req.url) return false
|
||||||
try {
|
try {
|
||||||
@@ -2536,6 +2561,45 @@ ${urls.map((url) => ` <url>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldBypassSpaFallback(requestPath) {
|
||||||
|
const lower = requestPath.toLowerCase()
|
||||||
|
const basename = path.posix.basename(lower)
|
||||||
|
const ext = path.posix.extname(lower)
|
||||||
|
|
||||||
|
if (lower.startsWith('/.')) return true
|
||||||
|
if (lower.includes('/.git/') || lower.includes('/.svn/') || lower.includes('/.hg/')) return true
|
||||||
|
if (lower.startsWith('/api') || lower.startsWith('/data')) return true
|
||||||
|
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'.cjs',
|
||||||
|
'.mjs',
|
||||||
|
'.js',
|
||||||
|
'.jsx',
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
'.json',
|
||||||
|
'.lock',
|
||||||
|
'.toml',
|
||||||
|
'.yaml',
|
||||||
|
'.yml',
|
||||||
|
'.env',
|
||||||
|
'.sqlite',
|
||||||
|
'.db',
|
||||||
|
'.sql',
|
||||||
|
'.zip',
|
||||||
|
'.gz',
|
||||||
|
'.tgz',
|
||||||
|
'.tar',
|
||||||
|
'.bak',
|
||||||
|
].includes(ext)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return /(?:^|[._-])(?:env|backup|dump|secret|credential|token|database|sqlite|db)(?:$|[._-])/.test(basename)
|
||||||
|
}
|
||||||
|
|
||||||
function serveStatic(req, res) {
|
function serveStatic(req, res) {
|
||||||
let requestPath = '/'
|
let requestPath = '/'
|
||||||
try {
|
try {
|
||||||
@@ -2553,6 +2617,13 @@ function serveStatic(req, res) {
|
|||||||
|
|
||||||
fs.stat(filePath, (error, stat) => {
|
fs.stat(filePath, (error, stat) => {
|
||||||
if (error || !stat.isFile()) {
|
if (error || !stat.isFile()) {
|
||||||
|
if (shouldBypassSpaFallback(requestPath)) {
|
||||||
|
return send(res, 404, 'Not found', {
|
||||||
|
'content-type': 'text/plain; charset=utf-8',
|
||||||
|
'cache-control': 'no-store',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fs.readFile(path.join(DIST_DIR, 'index.html'), (indexError, indexData) => {
|
fs.readFile(path.join(DIST_DIR, 'index.html'), (indexError, indexData) => {
|
||||||
if (indexError) {
|
if (indexError) {
|
||||||
return send(res, 404, 'Build not found. Run npm run build first.', {
|
return send(res, 404, 'Build not found. Run npm run build first.', {
|
||||||
@@ -2865,6 +2936,11 @@ function serveReplayMinimap(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
|
if (hasUnsafeProtectedPath(req)) {
|
||||||
|
sendJson(res, 404, { error: 'API route not found' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method === 'GET' && req.url === '/robots.txt') {
|
if (req.method === 'GET' && req.url === '/robots.txt') {
|
||||||
sendRobotsTxt(req, res)
|
sendRobotsTxt(req, res)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user