UNPKG

@eighty4/c2

Version:

Cross platform cloud config tooling for cloud-init

111 lines (97 loc) 3.15 kB
import { randomUUID } from 'node:crypto' import { createServer, type ServerResponse } from 'node:http' import { type Attachment, collectAttachments } from './attachments.ts' export function startUserDataHttp(port: number, userDataDir: string) { const server = createServer((req, res) => { console.log(req.method, req.url) if (req.method !== 'GET') { res.writeHead(405) res.end() } else if (req.url === '/user-data') { sendUserData(res, userDataDir).then() } else { res.writeHead( req.url === '/meta-data' || req.url === '/network-config' ? 200 : 404, ) res.end() } }) server.listen(port, () => { console.log(userDataDir, `up @ http://localhost:${port}/user-data`) }) } async function sendUserData(res: ServerResponse, userDataDir: string) { try { const message = new MultipartMessage( await collectAttachments(userDataDir), ) res.writeHead(200) res.write(message.toHTTP()) } catch (e: any) { console.error(500, '/user-data', e.message) res.writeHead(500) } finally { res.end() } } export class MultipartMessage { static createBoundary(): string { return randomUUID() } readonly #attachments: Array<MultipartAttachment> readonly #boundary: string constructor( attachments: Array<Attachment>, boundary: string = MultipartMessage.createBoundary(), ) { this.#attachments = attachments.map(a => new MultipartAttachment(a)) this.#boundary = boundary } get attachments(): Array<MultipartAttachment> { return this.#attachments } get boundary(): string { return this.#boundary } get headers(): Record<string, string> { return { 'Content-Type': `multipart/mixed; boundary=${this.#boundary}`, 'MIME-Version': '1.0', 'Number-Attachments': `${this.#attachments.length}`, } } toHTTP(): string { return [ `Content-Type: multipart/mixed; boundary=${this.#boundary}`, 'MIME-Version: 1.0', `Number-Attachments: ${this.#attachments.length}`, this.responseBody(), ].join('\r\n') } responseBody(): string { return [ '--' + this.#boundary, this.#attachments .map(a => a.toHTTP()) .join('\r\n--' + this.#boundary + '\r\n'), '--' + this.#boundary + '\r\n', ].join('\r\n') } } export class MultipartAttachment { #attachment: Attachment constructor(attachment: Attachment) { this.#attachment = attachment } toHTTP(): string { return [ `Content-Type: text/${this.#attachment.type}; charset="us-ascii"`, 'Content-Transfer-Encoding: 7bit', `Content-Disposition: attachment; filename="${this.#attachment.filename}"`, '', this.#attachment.content, ].join('\r\n') } }