/**
* Request Q4S module. Implements the Request Q4S class that allow requests
* manipulation.
* @module ReqQ4S
* @license Apache-2.0
*/
const crypto = require('crypto');
require('url');
const Util = require('./util');
/**
* Request Q4S class. Options to build , generate, parse and validate Q4S
* requests
*/
class ReqQ4S {
/**
* Constructor for the ReqQ4S class. Does not validate input data coherence.
* @param {string} method - The string containing the method.
* @param {URL} requestURI - The URI of the request.
* @param {string} q4sVersion - The Q4S version.
* @param {Object} headers - Object containing all the headers.
* @param {string} headers.headername - One entry in the object for each
* header.
* @param {string} body - The body of the request. Normally a sdp message.
*/
constructor(method, requestURI, q4sVersion, headers, body) {
/**
* Method of the request.
* @member {String}
*/
this.method = method;
/**
* The URI field of the request
* @member {String}
*/
this.requestURI = requestURI;
/**
* The version of the Q4S protocol.
* @member {String}
*/
this.q4sVersion = q4sVersion;
/**
* Object containing the headers. Each property name os the name of the
* header. Its value is the field.
* @member {Object}
*/
this.headers = headers;
/**
* The body of the request.
* @member {String}
*/
this.body = body;
}
/**
* Factory method to build a new ReqQ4S from an string. Validates the the
* created request object is correct.
* @param {string} message - The mesage which has to be parsed into a ReqQ4S.
* @return {Object} On sucess an object of the class ReqQ4S, otherwise an
* error will be trown
*/
static fromString(message) {
let method; let requestURI; let q4sVersion;
const headers = {};
const body = message.substring(message.indexOf('\r\n\r\n') + 4);
const headerPart = message.substring(0, message.indexOf('\r\n\r\n'));
const headLine = headerPart.split('\r\n');
headLine.forEach((item, index) => {
if (index === 0) {
const values = item.split(' ');
method = values[0];
requestURI = new URL(values[1]);
q4sVersion = values[2];
} else {
const header = item.split(': ');
headers[header[0]] = header[1];
}
});
const req = new ReqQ4S(method, requestURI, q4sVersion, headers, body);
req.validate();
return req;
}
/**
* Factory method to build a new ReqQ4S abstracting part of the details.
* @param {string} method - The string containing the method.
* @param {URL} requestURI - The URI of the request.
* @param {Object} headers - Object containing all the headers.
* @param {string} headers.headername - One entry in the object for each
* header.
* @param {string} body - The body of the request. Normally a sdp message.
* returns an error.
* @return {ReqQ4SQ}
*/
static genReq(method, requestURI, headers, body) {
return new ReqQ4S(method, requestURI, Util.Q4SVERSION, headers, body);
}
/**
* Check that the data in this is correct and returns true if all is ok or
* false if data is malformed.If there is anything malformed an error will be
* trown.
*/
validate() { // TODO: Improve this comprobation when you have the class clear
if (!this.q4sVersion) {
throw new Error('Missing an argument in the start line');
}
if (!METHODS.includes(this.method)) {
throw new Error('Method field does not contain a valid verb.');
}
if (this.method === 'READY' && typeof this.headers.Stage === 'undefined') {
throw new Error('READY method without stage header');
}
if (this.headers.signature !== undefined) {
if (crypto.createHash('md5').update(this.body).digest('hex') !=
this.headers.signature) {
throw new Error('Signature MD5 does not match the body.');
}
}
if (this.headers['Content-Encoding'] !== undefined) {
throw new Error('Body in the request is compressed');
}
if (this.body && this.headers['Content-Type'] === undefined) {
throw new Error('Content-Type header is not present');
}
if (this.headers['Transfer-Encoding'] !== undefined &&
this.headers['Transfer-Encoding'] !== 'indentity') {
throw new Error('Transfer-Encoding header can only be identity');
}
return;
}
/**
* Introduces a new header signature in this containing the MD5 of the body
* of this.
*/
signBody() {
this.headers.signature = crypto.createHash('md5')
.update(this.body)
.digest('hex');
}
/**
* Form the message format of this request. Use it to send it over TCP/UDP.
* @return {string} - String format of this response.
*/
toString() {
let message = '';
message = message + this.method + ' ' + this.requestURI + ' ';
message = message + this.q4sVersion + '\r\n';
if (this.headers) {
const entries = Object.entries(this.headers);
entries.forEach((item) => {
message = message + item[0] + ': ' + item[1] + '\r\n';
});
}
message = message + '\r\n';
if (this.body) {
message = message + this.body;
}
return message;
}
};
const METHODS = [
'BEGIN',
'READY',
'PING',
'Q4S-ALERT',
'Q4S-RECOVERY',
'CANCEL',
'PING',
'BWIDTH',
];
module.exports = ReqQ4S;