kozukata-toa/src/servers/allnet/middleware/dfi.ts

85 lines
2.2 KiB
TypeScript

import { Base64Encode } from "base64-stream";
import iconv from "iconv-lite";
import CreateLogCtx from "lib/logger/logger";
import getRawBody from "raw-body";
import { TransformResponse } from "utils/transform-response";
import { Transform } from "stream";
import { promisify } from "util";
import zlib, { inflate } from "zlib";
import type { RequestHandler } from "express";
const logger = CreateLogCtx(__filename);
const inflateAsync = promisify(inflate);
export const DFIRequestResponse: (mustUseDfi: boolean) => RequestHandler = (mustUseDfi) => {
return async (req, res, next) => {
if (Number(req.headers["content-length"] ?? 0) === 0) {
next();
return;
}
const isUsingDfi = req.headers.pragma?.toUpperCase() === "DFI";
if (mustUseDfi && !isUsingDfi) {
logger.error("Received request that was not DFI-encoded.", {
pragma: req.headers.pragma ?? null,
});
return res.status(200).send("");
}
const rawBody = await getRawBody(req, { encoding: "utf-8" });
let body: string;
if (isUsingDfi) {
const compressedBuffer = Buffer.from(rawBody, "base64");
const buffer = await inflateAsync(compressedBuffer);
body = buffer.toString("utf-8").trim();
} else {
body = rawBody.trim();
}
// Keys and values are not URL escaped, but interpolated in (why)
// This should be fine. I think.
// eslint-disable-next-line require-atomic-updates
req.body = Object.fromEntries(body.split("&").map((s) => s.split("=")));
req.safeBody = req.body as Record<string, unknown>;
const transformers: Array<NodeJS.ReadWriteStream> = [
iconv.decodeStream("utf-8"),
iconv.encodeStream(req.body.encode ?? "EUC-JP"),
];
if (req.headers.pragma?.toUpperCase() === "DFI") {
transformers.push(
zlib.createDeflate({ flush: zlib.constants.Z_SYNC_FLUSH }),
new Base64Encode()
);
}
transformers.push(
new Transform({
transform(chunk, _encoding, callback) {
callback(null, chunk);
},
final(callback) {
this.push(Buffer.from("\r\n", "utf-8"));
callback(null);
},
})
);
TransformResponse(res, ...transformers);
const _send = res.send;
res.send = (params) => {
const body = `${new URLSearchParams(params).toString()}`;
return _send.call(res, body);
};
next();
};
};