GCSBOSS/nodecaf

View on GitHub
lib/body.js

Summary

Maintainability
A
0 mins
Test Coverage

const { getDataTypeFromContentType, parseBuffer } = require('./types');

function readStream(){
    let complete, buffer = [];

    const fbto = setTimeout(() => {
        this.emit('error', new Error('Client took too long before starting to send request body'), 408)
    }, this.reqBodyTimeout);

    this.on('data', chunk => {
        buffer.push(chunk);
    });

    this.on('close', () => {
        buffer = null;
        this.removeAllListeners();
    });

    this.on('aborted', () =>
        this.emit('error', new Error('Request aborted by the client')));

    return new Promise((resolve, reject) => {

        this.on('end', () => {
            clearTimeout(fbto);
            !complete && resolve(Buffer.concat(buffer));
            complete = true;
        });

        this.on('error', (err, code) => {
            clearTimeout(fbto);
            !complete && reject(this.res.error(code ?? 400, err.message));
            complete = true;
        });
    });
}

async function read(parse){
    if(!this.length && !this.type)
        return;

    let out = await readStream.call(this);

    try{
        if(typeof parse == 'function')
            out = parse(out);
    }
    catch(err){
        throw this.res.error(400, 'Invalid format');
    }

    return out;
}

const decorator = {

    raw(){
        return read.call(this);
    },

    text(){
        this.res.badType(!this.charset && this.type != 'text');
        return read.call(this, raw => raw.toString(this.charset));
    },

    json(){
        this.res.badType(this.type != 'json');
        return read.call(this, raw => JSON.parse(raw.toString(this.charset)));
    },

    urlencoded(){
        this.res.badType(this.type != 'urlencoded');
        return read.call(this, raw =>
            Object.fromEntries(new URLSearchParams(raw.toString(this.charset)).entries()));
    },

    parse(){
        return read.call(this, raw => parseBuffer(raw, this.type, this.charset));
    }

};

module.exports.getDecoratedBody = function({ res, headers, body }, app) {

    const t = getDataTypeFromContentType(headers['content-type']);

    if(!headers['content-type'] && headers['transfer-encoding'] == 'chunked')
        t.type = 'chunked';

    body.type = t.type;
    body.charset = t.charset;
    body.res = res;
    body.length = Number(headers['content-length']);
    body.reqBodyTimeout = app._reqBodyTimeout;

    Object.assign(body, decorator);

    return body;
}