'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var _toArray = require('@babel/runtime/helpers/toArray');
var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _createClass = require('@babel/runtime/helpers/createClass');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var nodeUtils = require('@teclone/node-utils');
var fs = require('fs');
var utils = require('@teclone/utils');
var Constants = require('./Constants');
var uuid = require('uuid');
var path = require('path');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () {
            return e[k];
          }
        });
      }
    });
  }
  n['default'] = e;
  return Object.freeze(n);
}

var _toArray__default = /*#__PURE__*/_interopDefaultLegacy(_toArray);
var _slicedToArray__default = /*#__PURE__*/_interopDefaultLegacy(_slicedToArray);
var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck);
var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
var _defineProperty__default = /*#__PURE__*/_interopDefaultLegacy(_defineProperty);
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);

function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
var BodyParser = /*#__PURE__*/function () {
  function BodyParser(config) {
    _classCallCheck__default['default'](this, BodyParser);

    _defineProperty__default['default'](this, "config", void 0);

    this.config = config; //create file storage dir

    nodeUtils.mkDirSync(path.resolve(this.config.entryPath, this.config.tempDir));
  }
  /**
   * resolves the field name by removing trailing bracket
   */


  _createClass__default['default'](BodyParser, [{
    key: "resolveFieldName",
    value: function resolveFieldName(fieldName) {
      if (/^(.+)\[\]$/.test(fieldName)) {
        return {
          name: RegExp.$1,
          isMultiValue: true
        };
      } else {
        return {
          name: fieldName,
          isMultiValue: false
        };
      }
    }
    /**
     * assigns a single field or multi value field value to the body
     */

  }, {
    key: "assignBodyValue",
    value: function assignBodyValue(body, fieldName, value) {
      var _this$resolveFieldNam = this.resolveFieldName(fieldName),
          name = _this$resolveFieldNam.name,
          isMultiValue = _this$resolveFieldNam.isMultiValue;

      var target = value;

      if (isMultiValue) {
        var previous = body[name];
        target = utils.isArray(previous) ? previous : [];
        target.push(value);
      }

      body[name] = target;
    }
    /**
     * stores a file into the given files object
     */

  }, {
    key: "assignFileValue",
    value: function assignFileValue(files, fieldName, value) {
      var _this$resolveFieldNam2 = this.resolveFieldName(fieldName),
          name = _this$resolveFieldNam2.name,
          isMultiValue = _this$resolveFieldNam2.isMultiValue;

      var target = value;

      if (isMultiValue) {
        var previous = files[name];
        target = utils.isObject(previous) ? previous : {
          path: [],
          key: [],
          size: [],
          type: [],
          name: []
        };

        for (var _i = 0, _Object$entries = Object.entries(value); _i < _Object$entries.length; _i++) {
          var _Object$entries$_i = _slicedToArray__default['default'](_Object$entries[_i], 2),
              key = _Object$entries$_i[0],
              current = _Object$entries$_i[1];

          target[key].push(current);
        }
      }

      files[name] = target;
    }
    /**
     * processes and stores file
     */

  }, {
    key: "processFile",
    value: function processFile(headers, content) {
      var key = uuid.v1() + '.tmp';
      var filePath = path.resolve(this.config.entryPath, this.config.tempDir, key);
      fs__namespace.writeFileSync(filePath, content, headers.encoding);
      return {
        name: headers.fileName.replace(/\.\./g, ''),
        key: key,
        path: filePath,
        size: fs__namespace.statSync(filePath).size,
        type: headers.type
      };
    }
    /**
     * parses a multipart part headers.
     */

  }, {
    key: "parseHeaders",
    value: function parseHeaders(headers) {
      //assume a default value if there are no headers sent
      var result = {
        isFile: false,
        type: 'text/plain',
        fileName: '',
        fieldName: utils.generateRandomText(8),
        encoding: this.config.encoding
      };

      var _iterator = _createForOfIteratorHelper(headers),
          _step;

      try {
        for (_iterator.s(); !(_step = _iterator.n()).done;) {
          var header = _step.value;

          if (header !== '') {
            var _header$split = header.split(/\s*:\s*/),
                _header$split2 = _slicedToArray__default['default'](_header$split, 2),
                name = _header$split2[0],
                value = _header$split2[1];

            switch (name.toLowerCase()) {
              case 'content-disposition':
                //capture file name
                if (/filename="([^"]+)"/.exec(value)) {
                  result.fileName = RegExp.$1;
                } // capture field name


                value = value.replace(/filename="([^"]+)"/, '');
                /* istanbul ignore else */

                if (/name="([^"]+)"/.exec(value)) {
                  result.fieldName = RegExp.$1;
                }

                break;

              case 'content-type':
                result.isFile = true;
                result.type = value.split(/;\s*/)[0];
                break;
            }
          }
        }
      } catch (err) {
        _iterator.e(err);
      } finally {
        _iterator.f();
      }

      return result;
    }
    /**
     * parse multipart form data
     *@see https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
     */

  }, {
    key: "parseMultiPart",
    value: function parseMultiPart(string, boundary) {
      var _this = this;

      var body = {},
          files = {}; //if boundary is null, detect it using the last encapsulation boundary format

      if (utils.isNull(boundary)) {
        if (!/^-{2}(-*[a-z0-9]+)-{2}/gim.test(string)) {
          return {
            body: body,
            files: files
          };
        }

        boundary = RegExp.$1;
      } //some implementations fails to start the first encapsulation boundary
      // with CRLF character when there is no preamble


      var testPattern = new RegExp("^--".concat(boundary), 'gm');
      /* istanbul ignore else */

      if (testPattern.test(string)) {
        string = Constants.CRLF + string;
      }

      var crlfPattern = new RegExp(Constants.CRLF, 'gm');
      var boundaryLinePattern = new RegExp("".concat(Constants.CRLF, "--").concat(boundary), 'gm');
      var blankLinePattern = new RegExp(Constants.BLANK_LINE, 'gm'); //obtain body parts, discard preamble and epilogue

      string.split(boundaryLinePattern).slice(1, -1).forEach(function (bodyPart) {
        var _bodyPart$split = bodyPart.split(blankLinePattern),
            _bodyPart$split2 = _toArray__default['default'](_bodyPart$split),
            header = _bodyPart$split2[0],
            others = _bodyPart$split2.slice(1);

        var content = others.join(Constants.BLANK_LINE);

        var headers = _this.parseHeaders(header.split(crlfPattern));
        /* istanbul ignore else */


        if (headers.isFile && headers.fileName) {
          _this.assignFileValue(files, headers.fieldName, _this.processFile(headers, content));
        } else if (!headers.isFile) {
          _this.assignBodyValue(body, headers.fieldName, content);
        }
      });
      return {
        body: body,
        files: files
      };
    }
    /**
     * parse json content
     */

  }, {
    key: "parseJSON",
    value: function parseJSON(string) {
      try {
        var result = JSON.parse(string);
        return result;
      } catch (ex) {
        return {};
      }
    }
    /**
     * parse url encoded request body
     */

  }, {
    key: "parseUrlEncoded",
    value: function parseUrlEncoded(string) {
      var body = {};

      if (string) {
        var pairs = string.split('&');

        var _iterator2 = _createForOfIteratorHelper(pairs),
            _step2;

        try {
          for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
            var pair = _step2.value;

            var _pair$split = pair.split('='),
                _pair$split2 = _slicedToArray__default['default'](_pair$split, 2),
                name = _pair$split2[0],
                value = _pair$split2[1];

            this.assignBodyValue(body, decodeURIComponent(name), decodeURIComponent(value || ''));
          }
        } catch (err) {
          _iterator2.e(err);
        } finally {
          _iterator2.f();
        }
      }

      return body;
    }
    /**
     * parse the query parameters in the given url
     */

  }, {
    key: "parseQueryString",
    value: function parseQueryString(url) {
      if (url.indexOf('?') > -1) {
        return this.parseUrlEncoded(url.split('?')[1]);
      } else {
        return {};
      }
    }
    /**
     *@param {Buffer} buffer - the buffer data
     *@param {string} contentType - the request content type
     */

  }, {
    key: "parse",
    value: function parse(buffer, contentType) {
      var content = buffer.toString(this.config.encoding);
      var tokens = contentType.split(/;\s*/);
      var boundary = null;

      switch (tokens[0].toLowerCase()) {
        case 'text/plain':
        case 'application/x-www-form-urlencoded':
          return {
            files: {},
            body: this.parseUrlEncoded(content)
          };

        case 'text/json':
        case 'application/json':
          return {
            files: {},
            body: this.parseJSON(content)
          };

        case 'multipart/form-data':
          if (tokens.length === 2 && /boundary\s*=\s*/.test(tokens[1])) {
            boundary = tokens[1].split('=')[1];
          }

          return this.parseMultiPart(content, boundary);

        default:
          return {
            body: {},
            files: {}
          };
      }
    }
    /**
     * clean up temp files
     */

  }, {
    key: "cleanUpTempFiles",
    value: function cleanUpTempFiles(files) {
      var unlink = function unlink(path) {
        if (fs__namespace.existsSync(path)) {
          fs__namespace.unlinkSync(path);
        }
      };

      for (var _i2 = 0, _Object$entries2 = Object.entries(files); _i2 < _Object$entries2.length; _i2++) {
        var _Object$entries2$_i = _slicedToArray__default['default'](_Object$entries2[_i2], 2),
            file = _Object$entries2$_i[1];

        utils.makeArray(file.path).forEach(unlink);
      }
    }
  }]);

  return BodyParser;
}();

exports.BodyParser = BodyParser;
//# sourceMappingURL=BodyParser.js.map
