diff --git a/src/command-control/cc.js b/src/command-control/cc.js index ce5894e1fd..d68544449b 100644 --- a/src/command-control/cc.js +++ b/src/command-control/cc.js @@ -84,7 +84,7 @@ export class CommandControl { if (pathSplit.length >= 3) { b64UserFlag = pathSplit[2]; } - response.data.httpResponse = configRedirect( + response.data.httpResponse = configRedirect( b64UserFlag, reqUrl.origin, this.latestTimestamp @@ -103,13 +103,7 @@ export class CommandControl { response.isException = true; response.exceptionStack = e.stack; response.exceptionFrom = "CommandControl commandOperation"; - response.data.httpResponse = new Response( - JSON.stringify(response.exceptionStack), - ); - response.data.httpResponse.headers.set( - "Content-Type", - "application/json", - ); + response.data.httpResponse = jsonResponse(response.exceptionStack); } return response; } @@ -144,9 +138,7 @@ function domainNameToList(queryString, blocklistFilter, latestTimestamp) { returndata.list = false; } - let response = new Response(JSON.stringify(returndata)); - response.headers.set("Content-Type", "application/json"); - return response; + return jsonResponse(returndata); } function domainNameToUint(queryString, blocklistFilter) { @@ -163,9 +155,7 @@ function domainNameToUint(queryString, blocklistFilter) { returndata.list = false; } - let response = new Response(JSON.stringify(returndata)); - response.headers.set("Content-Type", "application/json"); - return response; + return jsonResponse(returndata); } function listToB64(queryString, blocklistFilter) { @@ -179,9 +169,7 @@ function listToB64(queryString, blocklistFilter) { list.split(","), flagVersion, ); - let response = new Response(JSON.stringify(returndata)); - response.headers.set("Content-Type", "application/json"); - return response; + return jsonResponse(returndata); } function b64ToList(queryString, blocklistFilter) { @@ -200,7 +188,13 @@ function b64ToList(queryString, blocklistFilter) { } else { returndata.list = "Invalid B64 String"; } - response = new Response(JSON.stringify(returndata)); - response.headers.set("Content-Type", "application/json"); - return response; + return jsonResponse(returndata); +} + +function jsonResponse(obj) { + return new Response( + JSON.stringify(obj), + { headers : util.jsonHeaders() }, + ); } + diff --git a/src/dns-operation/dnsAggCache.js b/src/dns-operation/dnsAggCache.js index 26169b6f2e..3334e45568 100644 --- a/src/dns-operation/dnsAggCache.js +++ b/src/dns-operation/dnsAggCache.js @@ -41,8 +41,7 @@ export default class DNSAggCache { response.isException = true; response.exceptionStack = e.stack; response.exceptionFrom = "DNSAggCache RethinkModule"; - console.error("Error At : DNSAggCache -> RethinkModule"); - console.error(e.stack); + console.error("Error At : DNSAggCache -> RethinkModule", e); } return response; } @@ -65,8 +64,7 @@ export default class DNSAggCache { : "").trim().toLowerCase() + ":" + response.reqDecodedDnsPacket.questions[0].type; let cacheResponse = await getCacheapi(this.wCache, param.request.url, dn); - console.debug("Cache Api Response"); - console.debug(cacheResponse); + log.d("Cache Api Response", cacheResponse); if (cacheResponse) { response.aggCacheResponse = await parseCacheapiResponse( cacheResponse, diff --git a/src/dns-operation/dnsResolver.js b/src/dns-operation/dnsResolver.js index 3b0a5ea84f..f21b2f68e7 100644 --- a/src/dns-operation/dnsResolver.js +++ b/src/dns-operation/dnsResolver.js @@ -94,13 +94,14 @@ export default class DNSResolver { async resolveFromCache(param) { const key = this.cacheKey(param.requestDecodedDnsPacket); const qid = param.requestDecodedDnsPacket.id; + const url = param.request.url; if (!key) return null; let cacheRes = this.resolveFromLocalCache(qid, key); if (!cacheRes) { - cacheRes = await this.resolveFromHttpCache(qid, key); + cacheRes = await this.resolveFromHttpCache(qid, url, key); this.updateLocalCacheIfNeeded(key, cacheRes); } @@ -114,10 +115,10 @@ export default class DNSResolver { return this.makeCacheResponse(queryId, cacheRes.dnsPacket, cacheRes.ttlEndTime); } - async resolveFromHttpCache(queryId, key) { + async resolveFromHttpCache(queryId, url, key) { if (!this.httpCache) return false; // no http-cache - const hKey = this.httpCacheKey(param.request.url, key); + const hKey = this.httpCacheKey(url, key); const resp = await this.httpCache.match(hKey); if (!resp) return false; // cache-miss @@ -196,19 +197,24 @@ export default class DNSResolver { const cacheUrl = this.httpCacheKey(param.request.url, k); const value = new Response(cacheRes.dnsPacket, { - headers: { - "Content-Length": cacheRes.dnsPacket.byteLength, - "x-rethink-metadata": JSON.stringify( - this.httpCacheMetadata(cacheRes, param.blocklistFilter) - ), - }, - cf: { cacheTtl: httpCacheTtl }, + headers: this.httpCacheHeaders(cacheRes, param.blocklistFilter), }); - util.dnsHeaders(value); param.event.waitUntil(this.httpCache.put(cacheUrl, value)); } + httpCacheHeaders(cres, blFilter) { + return util.concatHeaders( + { + "x-rethink-metadata": JSON.stringify( + this.httpCacheMetadata(cres, blFilter)) + }, + util.contentLengthHeader(cres.dnsPacket), + util.dnsHeaders(), + { cf: { cacheTtl: httpCacheTtl } }, + ); + } + /** * @param {Object} param * @param {Object} cacheRes @@ -360,16 +366,16 @@ DNSResolver.prototype.resolveDnsUpstream = async function ( } else if (request.method === "POST") { newRequest = new Request(u.href, { method: "POST", - headers: { - "Content-Length": requestBodyBuffer.byteLength, - }, + headers: util.concatHeaders( + util.contentLengthHeader(requestBodyBuffer), + util.dnsHeaders(), + ), body: requestBodyBuffer, }); } else { throw new Error("get/post requests only"); } - util.dnsHeaders(newRequest); return this.http2 ? this.doh2(newRequest) : fetch(newRequest); } catch (e) { diff --git a/src/helpers/currentRequest.js b/src/helpers/currentRequest.js index 1547fde880..9c6d7da15e 100644 --- a/src/helpers/currentRequest.js +++ b/src/helpers/currentRequest.js @@ -8,6 +8,7 @@ import { DNSParserWrap as DnsParser } from "../dns-operation/dnsOperation.js"; import * as dnsutil from "../helpers/dnsutil.js"; +import * as util from "../helpers/util.js"; export default class CurrentRequest { constructor() { @@ -28,15 +29,26 @@ export default class CurrentRequest { const singleLog = {}; singleLog.exceptionFrom = this.exceptionFrom; singleLog.exceptionStack = this.exceptionStack; - this.httpResponse = new Response(dnsutil.servfail); - this.setHeaders(); - this.httpResponse.headers.set("x-err", JSON.stringify(singleLog)); + this.httpResponse = new Response( + dnsutil.servfail, + { + headers : util.concatHeaders( + this.headers(), + this.additionalHeader(JSON.stringify(singleLog)), + ) + }, + ); } customResponse(data) { - this.httpResponse = new Response(dnsutil); - this.setHeaders(); - this.httpResponse.headers.set("x-err", JSON.stringify(data)); + this.httpResponse = new Response(null, + { + headers : util.concatHeaders( + this.headers(), + this.additionalHeader(JSON.stringify(data)), + ) + }, + ); } /** @@ -44,9 +56,12 @@ export default class CurrentRequest { * @returns Web API Response */ dnsResponse(arrayBuffer) { - this.httpResponse = new Response(arrayBuffer); - this.setHeaders(); + this.httpResponse = new Response( + arrayBuffer, + { headers : this.headers() }, + ); } + dnsBlockResponse() { try { this.decodedDnsPacket.type = "response"; @@ -75,8 +90,10 @@ export default class CurrentRequest { this.decodedDnsPacket.answers[0].data.svcParams = {}; } this.decodedDnsPacket.authorities = [] - this.httpResponse = new Response(this.dnsParser.Encode(this.decodedDnsPacket)); - this.setHeaders(); + this.httpResponse = new Response( + this.dnsParser.Encode(this.decodedDnsPacket), + { headers : this.headers() }, + ); } catch (e) { log.e(JSON.stringify(this.decodedDnsPacket)) this.isException = true; @@ -85,17 +102,25 @@ export default class CurrentRequest { } } - setHeaders() { - this.httpResponse.headers.set("Content-Type", "application/dns-message"); - this.httpResponse.headers.append("Vary", "Origin"); - this.httpResponse.headers.delete("expect-ct"); - this.httpResponse.headers.delete("cf-ray"); - if(this.isDnsBlock){ - this.httpResponse.headers.set("x-nile-flags", this.blockedB64Flag); - } - else if(this.blockedB64Flag !== ""){ - this.httpResponse.headers.set('x-nile-flag-notblocked', this.blockedB64Flag) - } + headers() { + const xNileFlags = (this.isDnsBlock) ? + { "x-nile-flags" : this.blockedB64Flag } : null; + const xNileFlagsAllowed = (this.blockedB64Flag) ? + { "x-nile-flags-allowed" : this.blockedB64Flag } : null; + + return util.concatHeaders( + util.dnsHeaders(), + xNileFlags, + xNileFlagsAllowed, + ); + } + + additionalHeader(json) { + if (!json) return null; + + return { + "x-nile-add" : json, + }; } } diff --git a/src/helpers/dnsutil.js b/src/helpers/dnsutil.js index 593358c9b6..05ddf4f667 100644 --- a/src/helpers/dnsutil.js +++ b/src/helpers/dnsutil.js @@ -9,6 +9,7 @@ import { DNSParserWrap as Dns } from "../dns-operation/dnsOperation.js"; // dns packet constants (in bytes) +// A dns message over TCP stream has a header indicating length. export const dnsHeaderSize = 2 export const dnsPacketHeaderSize = 12 export const minDNSPacketSize = dnsPacketHeaderSize + 5 @@ -32,9 +33,12 @@ export function truncated(ans) { } export function validResponseSize(r) { - return r && - r.byteLength >= minDNSPacketSize && - r.byteLength <= maxDNSPacketSize + return r && validateSize(r.byteLength) +} + +export function validateSize(sz) { + return sz >= minDNSPacketSize && + sz <= maxDNSPacketSize } export function hasAnswers(packet) { @@ -57,3 +61,10 @@ export function optAnswer(a) { // github.com/serverless-dns/dns-parser/blob/7de73303/index.js#L1770 return a && a.type && a.type.toUpperCase() === "OPT" } + +export function dohStatusCode(b) { + if (!b || !b.byteLength) return 412 + if (b.byteLength > maxDNSPacketSize) return 413 + if (b.byteLength < minDNSPacketSize) return 400 + return 200 +} diff --git a/src/helpers/plugin.js b/src/helpers/plugin.js index af16dd4f77..2aa904e1bc 100644 --- a/src/helpers/plugin.js +++ b/src/helpers/plugin.js @@ -14,6 +14,7 @@ import { DNSResolver, DNSResponseBlock, } from "../dns-operation/dnsOperation.js"; +import * as util from "./util.js"; const blocklistWrapper = new BlocklistWrapper(); const commandControl = new CommandControl(); @@ -34,11 +35,7 @@ export default class RethinkPlugin { this.parameter = new Map(envManager.getMap()); this.registerParameter("request", event.request); this.registerParameter("event", event); - this.registerParameter( - "isDnsMsg", - (event.request.headers.get("Accept") == "application/dns-message" || - event.request.headers.get("Content-Type") == "application/dns-message"), - ); + this.registerParameter("isDnsMsg", util.isDnsMsg(event.request)); this.plugin = []; @@ -82,7 +79,12 @@ export default class RethinkPlugin { this.registerPlugin( "commandControl", commandControl, - ["request", "blocklistFilter", "latestTimestamp"], + [ + "request", + "blocklistFilter", + "latestTimestamp", + "isDnsMsg", + ], commandControlCallBack, false, ); @@ -241,6 +243,7 @@ function dnsAggCacheCallBack(response, currentRequest) { if (response.isException) { loadException(response, currentRequest); } else if (response.data !== null) { + this.registerParameter( "requestDecodedDnsPacket", response.data.reqDecodedDnsPacket, diff --git a/src/helpers/util.js b/src/helpers/util.js index bec7750e1f..921b0b8fcb 100644 --- a/src/helpers/util.js +++ b/src/helpers/util.js @@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Buffer } from "buffer"; +import { Buffer } from "buffer" /** * Encodes a number to an Uint8Array of length `n` in Big Endian byte order. @@ -17,54 +17,101 @@ import { Buffer } from "buffer"; export function encodeUint8ArrayBE(n, len) { const o = n; - if (!n) return new Uint8Array(len); + if (!n) return new Uint8Array(len) const a = []; - a.unshift(n & 255); + a.unshift(n & 255) while (n >= 256) { n = n >>> 8; - a.unshift(n & 255); + a.unshift(n & 255) } if (a.length > len) { - throw new RangeError(`Cannot encode ${o} in ${len} len Uint8Array`); + throw new RangeError(`Cannot encode ${o} in ${len} len Uint8Array`) } - let fill = len - a.length; - while (fill--) a.unshift(0); + let fill = len - a.length + while (fill--) a.unshift(0) - return new Uint8Array(a); + return new Uint8Array(a) } export function fromBrowser(req) { - if (!req || !req.headers) return false; - const ua = req.headers.get("User-Agent"); - return ua && ua.startsWith("Mozilla/5.0"); -} + if (!req || !req.headers) return false -export function jsonHeaders(res) { - res.headers.set("Content-Type", "application/json"); + const ua = req.headers.get("User-Agent") + return ua && ua.startsWith("Mozilla/5.0") } -export function dnsHeaders(res) { - res.headers.set("Accept", "application/dns-message"); - res.headers.set("Content-Type", "application/dns-message"); +export function jsonHeaders() { + return { + "Content-Type": "application/json", + } } -export function corsHeaders(res) { - res.headers.set("Access-Control-Allow-Origin", "*"); - res.headers.set("Access-Control-Allow-Headers", "*"); +export function dnsHeaders() { + return { + "Accept": "application/dns-message", + "Content-Type": "application/dns-message", + } } -export function browserHeaders(res) { - jsonHeaders(res); - corsHeaders(res); +export function corsHeaders() { + return { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "*", + } } -export function dohHeaders(req, res) { - dnsHeaders(res); +export function corsHeadersIfNeeded(req) { // allow cors when reqs from agents claiming to be browsers - if (fromBrowser(req)) corsHeaders(res); + return (fromBrowser(req)) ? corsHeaders() : {} +} + +export function browserHeaders() { + return Object.assign( + jsonHeaders(), + corsHeaders(), + ); +} + +export function dohHeaders(req) { + return Object.assign( + dnsHeaders(), + corsHeadersIfNeeded(req), + ) +} + +export function contentLengthHeader(b) { + const len = (!b || !b.byteLength) ? "0" : b.byteLength.toString() + return { "Content-Length" : len } +} + +export function concatHeaders() { + return Object.assign(...arguments) +} + +export function copyHeaders(req) { + const headers = {} + if (!req || !req.headers) return headers + + // Object.assign, Object spread, etc don't work + req.headers.forEach((val, name) => { + headers[name] = val + }) + return headers +} + +export function copyNonPseudoHeaders(req) { + const headers = {} + if (!req || !req.headers) return headers + + // drop http/2 pseudo-headers + for (const name in req.headers) { + if (name.startsWith(":")) continue + headers[name] = req.headers[name] + } + return headers } /** @@ -74,33 +121,33 @@ export function dohHeaders(req, res) { */ export function sleep(ms) { return new Promise((resolve) => { - setTimeout(resolve, ms); + setTimeout(resolve, ms) }); } export function objOf(map) { - return map.entries ? Object.fromEntries(map) : false; + return map.entries ? Object.fromEntries(map) : false } // stackoverflow.com/a/31394257 export function arrayBufferOf(buf) { - const offset = buf.byteOffset; - const len = buf.byteLength; + const offset = buf.byteOffset + const len = buf.byteLength return buf.buffer.slice(offset, offset + len) } // stackoverflow.com/a/17064149 export function bufferOf(arrayBuf) { - return Buffer.from(new Uint8Array(arrayBuf)); + return Buffer.from(new Uint8Array(arrayBuf)) } export function recycleBuffer(b) { - b.fill(0); - return 0; + b.fill(0) + return 0 } export function createBuffer(size) { - return Buffer.allocUnsafe(size); + return Buffer.allocUnsafe(size) } export function timedOp(op, ms, cleanup) { @@ -141,13 +188,13 @@ export function timedOp(op, ms, cleanup) { } export function timeout(ms, callback) { - return setTimeout(callback, ms); + return setTimeout(callback, ms) } // stackoverflow.com/a/8084248 export function uid() { // ex: ".ww8ja208it" - return (Math.random() + 1).toString(36).slice(1); + return (Math.random() + 1).toString(36).slice(1) } export function safeBox(fn, defaultResponse = null) { @@ -157,6 +204,11 @@ export function safeBox(fn, defaultResponse = null) { return defaultResponse } +export function isDnsMsg(req) { + return req.headers.get("Accept") === "application/dns-message" || + req.headers.get("Content-Type") === "application/dns-message" +} + export function emptyResponse() { return { isException: false, diff --git a/src/index.js b/src/index.js index 6df9994476..1ef2dd1301 100644 --- a/src/index.js +++ b/src/index.js @@ -51,8 +51,11 @@ export function handleRequest(event) { async function proxyRequest(event) { try { if (event.request.method === "OPTIONS") { - const res = new Response(null, { status: 204 }); - util.corsHeaders(res); + const res = new Response(null, { + status: 204, + headers: util.corsHeaders(), + }); + return res; } @@ -60,8 +63,6 @@ async function proxyRequest(event) { const plugin = new RethinkPlugin(event); await plugin.executePlugin(currentRequest); - util.dohHeaders(event.request, currentRequest.httpResponse); - return currentRequest.httpResponse; } catch (err) { log.e(err.stack); @@ -71,15 +72,19 @@ async function proxyRequest(event) { function errorOrServfail(event, err) { if (util.fromBrowser(event)) { - const res = new Response(JSON.stringify(e.stack)); - util.browserHeaders(res); + const res = new Response( + JSON.stringify(e.stack), + { headers: util.browserHeaders() } + ); return res; } return servfail(event); } function servfail(event) { - const res = new Response(dnsutil.servfail); - util.dohHeaders(event.request, res); + const res = new Response( + dnsutil.servfail, + { headers: util.dohHeaders(event.request) } + ); return res; } diff --git a/src/server.js b/src/server.js index a07904d736..1ee1031ef6 100644 --- a/src/server.js +++ b/src/server.js @@ -12,7 +12,8 @@ import http2, { Http2ServerRequest, Http2ServerResponse } from "http2"; import { V1ProxyProtocol } from "proxy-protocol-js"; import { handleRequest } from "./index.js"; -import { encodeUint8ArrayBE, sleep } from "./helpers/util.js"; +import * as dnsutil from "./helpers/dnsutil.js"; +import * as util from "./helpers/util.js"; import { TLS_CRT, TLS_KEY } from "./helpers/node/config.js"; // Ports which the services are exposed on. Corresponds to fly.toml ports. @@ -32,17 +33,6 @@ const tlsOptions = { cert: TLS_CRT, }; -const minDNSPacketSize = 12 + 5; -const maxDNSPacketSize = 4096; - -// A dns message over TCP stream has a header indicating length. -const dnsHeaderSize = 2; - -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "*", -}; - let OUR_RG_DN_RE = null; // regular dns name match let OUR_WC_DN_RE = null; // wildcard dns name match @@ -152,18 +142,9 @@ function serveDoTProxyProto(clientSocket) { }); } -function recycleBuffer(b) { - b.fill(0); - return 0; -} - -function createBuffer(size) { - return Buffer.allocUnsafe(size); -} - function makeScratchBuffer() { - const qlenBuf = createBuffer(dnsHeaderSize); - const qlenBufOffset = recycleBuffer(qlenBuf); + const qlenBuf = util.createBuffer(dnsutil.dnsHeaderSize); + const qlenBufOffset = util.recycleBuffer(qlenBuf); return { qlenBuf: qlenBuf, @@ -277,7 +258,6 @@ function serveTLS(socket) { handleTCPData(socket, data, sb, host, flag); }); socket.on("end", () => { - log.d("TLS socket clean half shutdown"); socket.end(); }); socket.on("error", (e) => { @@ -295,7 +275,7 @@ function handleTCPData(socket, chunk, sb, host, flag) { if (cl <= 0) return; // read header first which contains length(dns-query) - const rem = dnsHeaderSize - sb.qlenBufOffset; + const rem = dnsutil.dnsHeaderSize - sb.qlenBufOffset; if (rem > 0) { const seek = Math.min(rem, cl); const read = chunk.slice(0, seek); @@ -304,10 +284,10 @@ function handleTCPData(socket, chunk, sb, host, flag) { } // header has not been read fully, yet - if (sb.qlenBufOffset !== dnsHeaderSize) return; + if (sb.qlenBufOffset !== dnsutil.dnsHeaderSize) return; const qlen = sb.qlenBuf.readUInt16BE(); - if (qlen < minDNSPacketSize || qlen > maxDNSPacketSize) { + if (!dnsutil.validateSize(qlen)) { log.w(`query range err: ql:${qlen} cl:${cl} rem:${rem}`); close(socket); return; @@ -322,8 +302,8 @@ function handleTCPData(socket, chunk, sb, host, flag) { const data = chunk.slice(rem); if (sb.qBuf === null) { - sb.qBuf = createBuffer(qlen); - sb.qBufOffset = recycleBuffer(sb.qBuf); + sb.qBuf = util.createBuffer(qlen); + sb.qBufOffset = util.recycleBuffer(sb.qBuf); } sb.qBuf.fill(data, sb.qBufOffset); @@ -333,7 +313,7 @@ function handleTCPData(socket, chunk, sb, host, flag) { if (sb.qBufOffset === qlen) { handleTCPQuery(sb.qBuf, socket, host, flag); // reset qBuf and qlenBuf states - sb.qlenBufOffset = recycleBuffer(sb.qlenBuf); + sb.qlenBufOffset = util.recycleBuffer(sb.qlenBuf); sb.qBuf = null; sb.qBufOffset = 0; } else if (sb.qBufOffset > qlen) { @@ -356,7 +336,7 @@ async function handleTCPQuery(q, socket, host, flag) { const t = log.startTime("handle-tcp-query"); try { const r = await resolveQuery(q, host, flag); - const rlBuf = encodeUint8ArrayBE(r.byteLength, 2); + const rlBuf = util.encodeUint8ArrayBE(r.byteLength, 2); const chunk = new Uint8Array([...rlBuf, ...r]); // Don't write to a closed socket, else it will crash nodejs @@ -386,11 +366,10 @@ async function resolveQuery(q, host, flag) { const r = await handleRequest({ request: new Request(`https://${host}/${flag}`, { method: "POST", - headers: { - Accept: "application/dns-message", - "Content-Type": "application/dns-message", - "Content-Length": q.byteLength.toString(), - }, + headers: util.concatHeaders( + util.dnsHeaders(), + util.contentLengthHeader(q), + ), body: q, }), }); @@ -404,7 +383,6 @@ async function resolveQuery(q, host, flag) { * @param {Http2ServerResponse} res */ async function serveHTTPS(req, res) { - const ua = req.headers["user-agent"]; const buffers = []; const t = log.startTime("recv-https"); @@ -417,13 +395,10 @@ async function serveHTTPS(req, res) { log.endTime(t); - if ( - req.method == "POST" && - (bLen < minDNSPacketSize || bLen > maxDNSPacketSize) - ) { + if (req.method == "POST" && !dnsutil.validReponseSize(b)) { res.writeHead( - bLen > maxDNSPacketSize ? 413 : 400, - ua && ua.startsWith("Mozilla/5.0") ? corsHeaders : {} + dnsutil.dohStatusCode(b), + util.corsHeadersIfNeeded(req), ); res.end(); log.w(`HTTP req body length out of bounds: ${bLen}`); @@ -445,18 +420,11 @@ async function handleHTTPRequest(b, req, res) { let host = req.headers.host || req.headers[":authority"]; if (isIPv6(host)) host = `[${host}]`; - let reqHeaders = {}; - // Drop http/2 pseudo-headers - for (const key in req.headers) { - if (key.startsWith(":")) continue; - reqHeaders[key] = req.headers[key]; - } - const fReq = new Request(new URL(req.url, `https://${host}`), { // Note: In VM container, Object spread may not be working for all // properties, especially of "hidden" Symbol values!? like "headers"? ...req, - headers: reqHeaders, + headers: util.copyNonPseudoHeaders(req), method: req.method, body: req.method == "POST" ? b : null, }); @@ -467,13 +435,7 @@ async function handleHTTPRequest(b, req, res) { log.lapTime(t, "upstream-end"); - // Object.assign, Object spread, etc doesn't work with `node-fetch` Headers - const resHeaders = {}; - fRes.headers.forEach((v, k) => { - resHeaders[k] = v; - }); - - res.writeHead(fRes.status, resHeaders); + res.writeHead(fRes.status, util.copyHeaders(fRes)); log.lapTime(t, "send-head");