💾 Archived View for iich.space › src › modules › gemini › request.ts captured on 2022-03-01 at 16:04:18.
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
import { TLSSocket } from 'tls'; import { URL } from 'url'; import { createLogger } from '@/log'; import { MAX_FILE_SIZE } from '~/constants'; import { generateHash } from '~/util/hash'; export interface File { mime: string; size: number; data: Buffer; token: string; } const log = createLogger(); export class Request { url: URL; fingerprint: string | null; remote: string; query: string | null; file: File | null = null; constructor( url: URL, remote: string, fingerprint: string | null, file: File | null, ) { this.url = url; this.remote = remote; this.fingerprint = fingerprint; this.file = file; const query = decodeURIComponent(url.search.slice(1)); this.query = query !== '' ? query : null; } static async fromTitanRequest( socket: TLSSocket, url: URL, remote: string, fingerprint: string | null, initialBuffer: Buffer, ): Promise<Request> { const meta = { mime: '', size: 0, token: '', }; const [href, ...vars] = url.href.split(';'); vars .map((item) => item.split('=')) .forEach(([key, value]) => { switch (key) { case 'token': { meta.token = decodeURIComponent(value); break; } case 'mime': { meta.mime = value; break; } case 'size': meta.size = parseInt(value, 10); break; } }); if (meta.size >= MAX_FILE_SIZE) { const size = (MAX_FILE_SIZE / 1024 / 1024).toFixed(2); return Promise.reject(new Error(`File Too Large (${size}mb Limit)`)); } const data = await new Promise<Buffer>((ok, err) => { let buffer = initialBuffer; let timeout: NodeJS.Timer; const clearTimer = () => { if (timeout !== undefined) { clearTimeout(timeout); } }; const resetTimer = () => { clearTimer(); timeout = setTimeout(() => err(new Error('Timeout')), 2000); }; const onData = (data: Buffer) => { resetTimer(); buffer = Buffer.concat([buffer, data]); if (buffer.length === meta.size) { socket.removeListener('data', onData); clearTimer(); ok(buffer); } else if (buffer.length > meta.size) { err(new Error(`Upload Size Mismatch ${buffer.length}/${meta.size}`)); } }; socket.on('data', onData); onData(Buffer.from([])); }); const file = { ...meta, data }; return new Request(new URL(href), remote, fingerprint, file); } static async fromSocket(socket: TLSSocket): Promise<Request> { let buffer = Buffer.from([]); const url = await new Promise<URL>((ok) => { const onData = (data: Buffer) => { buffer = Buffer.concat([buffer, data]); const eol = buffer.indexOf('\r\n'); if (eol >= 0) { const url = buffer.slice(0, eol); buffer = buffer.slice(eol + 2); socket.removeListener('data', onData); ok(new URL(url.toString())); } }; socket.on('data', onData); }); const remote = generateHash(socket.remoteAddress!); const certificate = socket.getPeerCertificate(true); let fingerprint: string | null = null; if (certificate && certificate.fingerprint) { fingerprint = certificate.fingerprint; } log.info(remote, fingerprint, url.toString()); if (url.protocol === 'titan:') { return this.fromTitanRequest(socket, url, remote, fingerprint, buffer); } return new Request(url, remote, fingerprint, null); } }