'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;