Files
huangserva--3dcellforge/server/http-utils.mjs
T
2026-07-13 13:15:38 +08:00

125 lines
3.6 KiB
JavaScript

import { BODY_LIMIT, MODEL_UPLOAD_LIMIT } from './config.mjs'
export function setCorsHeaders(response) {
response.setHeader('Access-Control-Allow-Origin', process.env.CORS_ORIGIN || '*')
response.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
response.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization')
}
export function assertLocalDiagnosticsRequest(request) {
const remoteAddress = normalizeAddress(request.socket?.remoteAddress)
const origin = request.headers.origin
const referer = request.headers.referer
if (!isLocalHost(remoteAddress)) {
throw Object.assign(new Error('Diagnostics logs are only available from this machine.'), { status: 403 })
}
if (origin && !isLocalUrl(origin)) {
throw Object.assign(new Error('Diagnostics logs are only available to localhost pages.'), { status: 403 })
}
if (!origin && referer && !isLocalUrl(referer)) {
throw Object.assign(new Error('Diagnostics logs are only available to localhost pages.'), { status: 403 })
}
}
export function sendJson(response, status, payload) {
response.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' })
response.end(JSON.stringify(payload))
}
export function readJsonBody(request) {
return new Promise((resolve, reject) => {
const chunks = []
let size = 0
request.on('data', (chunk) => {
size += chunk.length
if (size > BODY_LIMIT) {
reject(Object.assign(new Error('Image payload is too large.'), { status: 413 }))
request.destroy()
return
}
chunks.push(chunk)
})
request.on('end', () => {
try {
const raw = Buffer.concat(chunks).toString('utf8')
resolve(raw ? JSON.parse(raw) : {})
} catch {
reject(Object.assign(new Error('Invalid JSON payload.'), { status: 400 }))
}
})
request.on('error', reject)
})
}
export function readRawBody(request, limit = MODEL_UPLOAD_LIMIT) {
return new Promise((resolve, reject) => {
const chunks = []
let size = 0
request.on('data', (chunk) => {
size += chunk.length
if (size > limit) {
reject(Object.assign(new Error('Model payload is too large.'), { status: 413 }))
request.destroy()
return
}
chunks.push(chunk)
})
request.on('end', () => {
resolve(Buffer.concat(chunks))
})
request.on('error', reject)
})
}
export function parseDataUrl(dataUrl) {
if (typeof dataUrl !== 'string') {
throw Object.assign(new Error('imageDataUrl is required.'), { status: 400 })
}
const match = dataUrl.match(/^data:(image\/(?:png|jpe?g|webp));base64,(.+)$/)
if (!match) {
throw Object.assign(new Error('Only PNG, JPEG, or WebP image data URLs are supported.'), { status: 400 })
}
const mime = match[1]
const buffer = Buffer.from(match[2], 'base64')
const ext = mime.includes('png') ? 'png' : mime.includes('webp') ? 'webp' : 'jpg'
if (buffer.length < 1024) {
throw Object.assign(new Error('Image is too small for 3D generation.'), { status: 400 })
}
return { mime, buffer, ext }
}
export function sanitizeFileName(fileName) {
const baseName = String(fileName).split(/[\\/]/).pop() || ''
return baseName.replace(/[^\w.\- ]+/g, '').replace(/^\.+/, '').trim() || 'asset-reference.png'
}
function normalizeAddress(address = '') {
return String(address).replace(/^::ffff:/, '')
}
function isLocalHost(hostname = '') {
const normalized = String(hostname).toLowerCase()
return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1'
}
function isLocalUrl(value) {
try {
return isLocalHost(new URL(value).hostname)
} catch {
return false
}
}