commit 5bda28719cd34854cd30f643d4a32482cfd1b9fc Author: Daniel Bulant Date: Wed Jul 15 19:47:30 2020 +0200 Initial working state diff --git a/cgi.ts b/cgi.ts new file mode 100644 index 0000000..6fb346b --- /dev/null +++ b/cgi.ts @@ -0,0 +1,68 @@ +#!/home/dan/.deno/bin/deno run + +import FCGI from "./fcgi.ts"; +import Record, { RecordTypes } from "./records/index.ts"; +import * as dejs from 'https://deno.land/x/dejs/mod.ts'; + +const server = Deno.listen({ port: 8990 }); + +const te = new TextEncoder; + +function concatTypedArrays(a: any, b: any) { // a, b TypedArray of same type + var c = new (a.constructor)(a.length + b.length); + c.set(a, 0); + c.set(b, a.length); + return c; +} + +for await(const con of server) { + var response = new Uint8Array(0); + + var data: { + records: Record[], + done: boolean + } = { + records: [], + done: false + }; + + for await(var chunk of Deno.iter(con)) { + response = concatTypedArrays(response, chunk); + data = FCGI.parse(response); + if(data.done) break; + } + + if(!(data.done)) continue; + + var params = FCGI.parseNameValue(FCGI.getFullStream(data.records.filter(val => val.type === RecordTypes.PARAMS))); + + var stdin = FCGI.getFullStream(data.records.filter(val => val.type === RecordTypes.STDIN)); + // var inputData = FCGI.getFullStream(data.records.filter(val => val.type === RecordTypes.DATA)); + + var status = 200; + var headers = new Map(); + headers.set("Content-type", "text/html"); + headers.set("X-powered-by", "Dejs-FCGI"); + var dataOutput = await dejs.renderFileToString(params.get("SCRIPT_FILENAME")!, { + PARAMS: params, + STDIN: stdin, + + setStatus(statusNumber: number) { + status = statusNumber; + }, + + setHeader(header: string, value: string) { + headers.set(header, value); + } + }); + + var output = `${params.get("SERVER_PROTOCOL")!} ${status}\r\n${Array.from(headers).map(([a, b]) => `${a}: ${b}`).join("\r\n")}\r\n\r\n${dataOutput}`; + + var responseData = FCGI.write(te.encode(output),1); + + for(var buf of responseData.toBufferArray()) { + await con.write(buf); + } + + con.close(); +} \ No newline at end of file diff --git a/fcgi.ts b/fcgi.ts new file mode 100644 index 0000000..f4331b7 --- /dev/null +++ b/fcgi.ts @@ -0,0 +1,101 @@ +import Record, {RecordTypes} from "./records/index.ts"; +const td = new TextDecoder; + +function concatTypedArrays(a: any, b: any) { // a, b TypedArray of same type + var c = new (a.constructor)(a.length + b.length); + c.set(a, 0); + c.set(b, a.length); + return c; +} + +export default class FCGI { + static FCGI_HEADER_LEN = 8; + static FCGI_VERSION = 1; + + static FCGI_KEEP_CONN = 1; + static FCGI_RESPONDER = 1; + + static parse(buffer: Uint8Array): { + records: Record[], + done: boolean + } { + var done = false; + var records = []; + while(buffer.length) { + if(buffer[0] !== 1) throw new Error("Unsupported version" + buffer[0]); + var type = buffer[1]; + var requestId = (buffer[2] << 8) + buffer[3]; + var contentLength = (buffer[4] << 8) + buffer[5]; + var paddingLength = buffer[6]; + // 7 reserved + var contentData = buffer.slice(8, 8 + contentLength); + + records.push(new Record({ + type, + requestId, + contentLength, + paddingLength, + contentData + })); + + if(type === RecordTypes.STDIN && contentLength === 0) done = true; // now app can respond + + buffer = buffer.slice(8 + contentLength + paddingLength); + } + return { + records, + done + }; + } + + static write(buffer: Uint8Array, rid: number, error: boolean = false) { + return new Record({ + requestId: rid, + contentLength: buffer.length, + paddingLength: 0, + contentData: buffer, + type: error ? RecordTypes.STDERR : RecordTypes.STDOUT + }); + } + + static getFullStream(records: Record[]): Uint8Array { + var content; + for(var record of records) { + if(content) { + concatTypedArrays(content, record.contentData); + } else { + content = record.contentData; + } + } + + return content; + } + + static parseNameValue(content: Uint8Array) { + var data = new Map(); + + while(content.length) { + var nameLength = content[0]; + if(nameLength >> 7) { + nameLength += ((content[3] & 0x7f) << 24) + (content[2] << 16) + (content[1] << 8); + content = content.slice(4); + } else { + content = content.slice(1); + } + var valueLength = content[0]; + if(valueLength >> 7) { + valueLength += ((content[3] & 0x7f) << 24) + (content[2] << 16) + (content[1] << 8); + content = content.slice(4); + } else { + content = content.slice(1); + } + var nameData = content.slice(0, nameLength); + var valueData = content.slice(nameLength, nameLength + valueLength); + data.set(td.decode(nameData), td.decode(valueData)); + + content = content.slice(nameLength + valueLength); + } + + return data; + } +} \ No newline at end of file diff --git a/records/index.ts b/records/index.ts new file mode 100644 index 0000000..7292182 --- /dev/null +++ b/records/index.ts @@ -0,0 +1,80 @@ + +export enum RecordTypes { + BEGIN_REQUEST = 1, + ABORT_REQUEST, + END_REQUEST, + PARAMS, + STDIN, + STDOUT, + STDERR, + DATA, + GET_VALUES, + GET_VALUES_RESULT, + UNKNOWN_TYPE +} + + +export default class Record { + //1 byte + version: 1 = 1; + + //1 byte + type: RecordTypes; + + //2 bytes + requestId: number; + + //2bytes + contentLength: number; + //1 byte + paddingLength: number; + + //contentLength bytes; + contentData: any; + + constructor(data: { + version?: 1, + type: RecordTypes, + requestId: number, + contentLength: number, + paddingLength: number, + contentData: any + }) { + this.type = data.type; + this.requestId = data.requestId; + this.contentLength = data.contentLength; + this.paddingLength = data.paddingLength; + this.contentData = data.contentData; + } + + get isManagement() { + return this.requestId === 0; + } + + toBufferArray(): Uint8Array[] { + var bufArray = []; + var content = this.contentData; + + while(true) { + var small = content.slice(0, 65535); + var buffer = new Uint8Array(8 + this.contentLength); + buffer.set([ + this.version, + this.type, + (this.requestId >> 8) & 0xFF, + this.requestId & 0xFF, + (small.length >> 8) & 0xFF, + small.length & 0xFF, + 0, + 0 + ]); + buffer.set(small, 8); + bufArray.push(buffer); + + content = content.slice(65535, content.length); + + if(!content.slice(65535, content.length).length) break; + } + return bufArray; + } +} \ No newline at end of file