Source: lib/connector/NodeConnector.js

'use strict';

const Connector = require('./Connector');
const PersistentError = require('../error/PersistentError');

let http;
let https;

/**
 * @alias connector.NodeConnector
 * @extends connector.Connector
 */
class NodeConnector extends Connector {
  static isUsable() {
    if (!http) {
      try {
        // the require call will fail, if we can't require the http module,
        // therefore this connector implementation can't be used
        /* eslint-disable global-require */
        https = require('https');
        http = require('http');
        /* eslint-enable global-require */
      } catch (e) {
        // ignore
      }
    }
    // prevent using when it is shimmed
    return http && http.Server;
  }

  constructor(host, port, secure, basePath) {
    super(host, port, secure, basePath);
    this.cookie = null;
    this.http = secure ? https : http;
  }

  /**
   * @inheritDoc
   */
  doSend(message, request, receive) {
    request.host = this.host;
    request.port = this.port;
    request.path = this.basePath + request.path;

    const entity = request.entity;
    const type = request.type;
    let responseType = message.responseType();

    if (this.cookie && message.withCredentials) {
      request.headers.cookie = this.cookie;
    }

    const req = this.http.request(request, (res) => {
      const cookie = res.headers['set-cookie'];
      if (cookie) {
        // cookie may be an array, convert it to a string
        this.cookie = this.parseCookie(cookie + '');
      }

      const status = res.statusCode;
      if (status >= 400) {
        responseType = 'json';
      }

      if (responseType === 'stream') {
        receive({
          status,
          headers: res.headers,
          entity: res,
        });
        return;
      }

      const binary = responseType && responseType !== 'text' && responseType !== 'json';
      const chunks = [];
      if (!binary) {
        res.setEncoding('utf-8');
      }

      res.on('data', (chunk) => {
        chunks.push(chunk);
      });

      res.on('end', () => {
        receive({
          status,
          headers: res.headers,
          entity: binary ? Buffer.concat(chunks) : chunks.join(''),
        });
      });
    });

    req.on('error', (e) => {
      receive({
        status: 0,
        error: e,
      });
    });

    if (type === 'stream') {
      entity.pipe(req);
    } else if (type === 'buffer') {
      req.end(entity);
    } else if (type) {
      req.end(entity, 'utf8');
    } else {
      req.end();
    }
  }


  /**
   * Parse the cookie header
   * @param {string} header
   * @return {(string|null)}
   */
  parseCookie(header) {
    const parts = header.split(';');

    for (let i = 0, len = parts.length; i < len; i += 1) {
      const part = parts[i];
      if (part.indexOf('Expires=') === 0) {
        const date = Date.parse(part.substring(8));
        if (date < Date.now()) {
          return null;
        }
      }
    }

    return parts[0];
  }

  /**
   * @inheritDoc
   */
  toFormat(message) {
    let type = message.request.type;

    if (type) {
      let entity = message.request.entity;
      let mimeType = message.mimeType();

      switch (type) {
        case 'stream':
          if (!message.contentLength()) {
            throw new PersistentError('You must specify a content length while making a stream based upload.');
          }
          break;
        case 'buffer':
          break;
        case 'arraybuffer':
          type = 'buffer';
          entity = Buffer.from(entity);
          break;
        case 'data-url': {
          const match = entity.match(/^data:(.+?)(;base64)?,(.*)$/);
          const isBase64 = match[2];
          entity = match[3];

          type = 'buffer';
          mimeType = mimeType || match[1];
          if (isBase64) {
            entity = Buffer.from(entity, 'base64');
          } else {
            entity = Buffer.from(decodeURIComponent(entity), 'utf8');
          }

          break;
        }
        case 'base64':
          type = 'buffer';
          entity = Buffer.from(entity, 'base64');
          break;
        case 'json':
          if (typeof entity !== 'string') {
            entity = JSON.stringify(entity);
          }
          break;
        case 'text':
          break;
        default:
          throw new Error('The request type ' + type + ' is not supported');
      }

      message.entity(entity, type).mimeType(mimeType);
    }
  }

  /**
   * @inheritDoc
   */
  fromFormat(response, entity, type) {
    switch (type) {
      case 'json':
        return JSON.parse(entity);
      case 'data-url':
      case 'base64': {
        const base64 = entity.toString('base64');
        if (type === 'base64') {
          return base64;
        }

        return 'data:' + response.headers['content-type'] + ';base64,' + base64;
      }
      case 'arraybuffer':
        return entity.buffer.slice(entity.byteOffset, entity.byteOffset + entity.byteLength);
      default:
        return entity;
    }
  }
}

Connector.connectors.push(NodeConnector);

module.exports = NodeConnector;