API Docs for: 0.8.0
Show:

File: include/http/parsers/index.js

/*
 Copyright (C) 2016  PencilBlue, LLC

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//dependencies
var formidable = require('formidable');
var util       = require('../../util.js');

module.exports = function(pb) {

    /**
     * Provides function to construct the structure needed to display the navigation
     * in the Admin section of the application.
     *
     * @class BaseBodyParser
     * @constructor
     */
    function BaseBodyParser() {}

    /**
     * The prefix in the content-type header that indicates the charset used in
     * the encoding
     * @static
     * @private
     * @readonly
     * @property CHARSET_HEADER_PREFIX
     * @type {String}
     */
    var CHARSET_HEADER_PREFIX = 'charset=';

    /**
     * A mapping that converts the HTTP standard for content-type encoding and
     * what the Buffer prototype expects
     * @static
     * @private
     * @readonly
     * @property ENCODING_MAPPING
     * @type {Object}
     */
    var ENCODING_MAPPING = Object.freeze({
        'UTF-8': 'utf8',
        'US-ASCII': 'ascii',
        'UTF-16LE': 'utf16le'
    });

    /**
     * Attempts to retrieve the payload body as a string
     * @method parse
     * @param {Request} The incoming request whose payload should be parsed
     * @param {Function} A callback that taks two parameters: An Error, if occurred
     * and the parsed body content as an object
     */
    BaseBodyParser.prototype.parse = function(req, cb) {
        this.getRawData(req, cb);
    };

    /**
     * Retrieves the raw payload data as a string
     * @method getRawData
     * @param {Function} cb
     */
    BaseBodyParser.prototype.getRawData = function(req, cb) {
        var buffers     = [];
        var totalLength = 0;

        req.on('data', function (data) {
            buffers.push(data);
            totalLength += data.length;

            if (totalLength > pb.config.media.max_upload_size) {
                // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST

                var error = new Error('POST limit reached! Maximum of '+(pb.config.media.max_upload_size / 1000000)+'MB');
                error.code = 400;
                cb(error);
            }
        });
        req.on('end', function () {

            //create one big buffer.
            var body = Buffer.concat (buffers, totalLength);
            cb(null, body);
        });
        req.once('error', cb);
    };

    /**
     * Attempts to extract charset attribute from the content-type header.
     * @static
     * @method getcontentEncoding
     * @param {Request} req
     * @return {String} the charset encoding
     */
    BaseBodyParser.getContentEncoding = function(req) {
        var rawContentEncoding = req.headers['content-type'];
        if (!util.isString(rawContentEncoding)) {
            return null;
        }

        //find the charset in the header
        var index = rawContentEncoding.indexOf(CHARSET_HEADER_PREFIX);
        if (index < 0) {
            return null;
        }

        //parse it out and look it up.  Default to UTF-8 if unrecognized
        return rawContentEncoding.substring(index + CHARSET_HEADER_PREFIX.length);
    };

    /**
     * Parser for incoming form bodies.  The parser handles
     * application/x-www-form-urlencoded and multipart/form-data encoded data. Any
     * uploaded files are stored locally and a descriptor object is passed as the
     * parsed result.
     *
     * @class FormBodyParser
     * @constructor
     * @extends BaseBodyParser
     */
    function FormBodyParser() {}
    util.inherits(FormBodyParser, BaseBodyParser);

    /**
     * Attempts to parse the request body as multi-part or form/url encoded content
     * @method parse
     * @param {Request} The incoming request whose payload should be parsed
     * @param {Function} A callback that taks two parameters: An Error, if occurred
     * and the parsed body content as an object
     */
    FormBodyParser.prototype.parse = function(req, cb) {
        var didError = false;
        var form = new formidable.IncomingForm();
        form.maxFieldSize = pb.config.media.max_upload_size;
        form.on('progress', function(bytesReceived, bytesExpected) {
            if ( (bytesReceived > pb.config.media.max_upload_size || bytesExpected > pb.config.max_upload_size) && !didError) {
                didError = true;
                var err = new Error('The request payload is too large');
                err.code = 413;
                this.emit(err);
            }
        });

        //parse the form out and let us know when its done
        form.parse(req, function(err, fields, files) {
            util.forEach(files, function(val, key) {
                fields[key] = val;
            });
            cb(err, fields);
        });
    };

    /**
     * Provides function to construct the structure needed to display the navigation
     * in the Admin section of the application.
     *
     * @class JsonBodyParser
     * @constructor
     * @extends BaseBodyParser
     */
    function JsonBodyParser() {}
    util.inherits(JsonBodyParser, BaseBodyParser);

    /**
     * Attempts to parse the request body as JSON content
     * @method parse
     * @param {Request} req The incoming request whose payload should be parsed
     * @param {Function} cb A callback that taks two parameters: An Error, if occurred
     * and the parsed body content as an object
     */
    JsonBodyParser.prototype.parse = function(req, cb) {
        var self = this;

        this.getRawData(req, function(err, raw){
            if (util.isError(err)) {
                return cb(err);
            }

            //lookup encoding
            var encoding = BaseBodyParser.getContentEncoding(req);
            encoding = ENCODING_MAPPING[encoding] ? ENCODING_MAPPING[encoding] : 'utf8';

            var error      = null;
            var postParams = null;
            try {
                postParams = JSON.parse(raw.toString(encoding));
            }
            catch(err) {
                error = err;
            }
            cb(error, postParams);
        });
    };

    return {
        BaseBodyParser: BaseBodyParser,
        FormBodyParser: FormBodyParser,
        JsonBodyParser: JsonBodyParser
    };
};