amt-wsman-ws-0.2.0.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /**
  2. * @description WSMAN communication using websocket
  3. * @author Ylian Saint-Hilaire
  4. * @version v0.2.0c
  5. */
  6. // Construct a WSMAN communication object
  7. var CreateWsmanComm = function (host, port, user, pass, tls) {
  8. var obj = {};
  9. obj.PendingAjax = []; // List of pending AJAX calls. When one frees up, another will start.
  10. obj.ActiveAjaxCount = 0; // Number of currently active AJAX calls
  11. obj.MaxActiveAjaxCount = 1; // Maximum number of activate AJAX calls at the same time.
  12. obj.FailAllError = 0; // Set this to non-zero to fail all AJAX calls with that error status, 999 causes responses to be silent.
  13. obj.challengeParams = null;
  14. obj.noncecounter = 1;
  15. obj.authcounter = 0;
  16. obj.socket = null;
  17. obj.socketState = 0;
  18. obj.host = host;
  19. obj.port = port;
  20. obj.user = user;
  21. obj.pass = pass;
  22. obj.tls = tls;
  23. obj.tlsv1only = 1;
  24. obj.cnonce = Math.random().toString(36).substring(7); // Generate a random client nonce
  25. // Private method
  26. //obj.Debug = function (msg) { console.log(msg); }
  27. function arrToStr(arr) { return String.fromCharCode.apply(null, arr); }
  28. // Private method
  29. // pri = priority, if set to 1, the call is high priority and put on top of the stack.
  30. obj.PerformAjax = function (postdata, callback, tag, pri, url, action) {
  31. if (obj.ActiveAjaxCount < obj.MaxActiveAjaxCount && obj.PendingAjax.length == 0) {
  32. // There are no pending AJAX calls, perform the call now.
  33. obj.PerformAjaxEx(postdata, callback, tag, url, action);
  34. } else {
  35. // If this is a high priority call, put this call in front of the array, otherwise put it in the back.
  36. if (pri == 1) { obj.PendingAjax.unshift([postdata, callback, tag, url, action]); } else { obj.PendingAjax.push([postdata, callback, tag, url, action]); }
  37. }
  38. }
  39. // Private method
  40. obj.PerformNextAjax = function () {
  41. if (obj.ActiveAjaxCount >= obj.MaxActiveAjaxCount || obj.PendingAjax.length == 0) return;
  42. var x = obj.PendingAjax.shift();
  43. obj.PerformAjaxEx(x[0], x[1], x[2], x[3], x[4]);
  44. obj.PerformNextAjax();
  45. }
  46. // Private method
  47. obj.PerformAjaxEx = function (postdata, callback, tag, url, action) {
  48. if (obj.FailAllError != 0) { obj.gotNextMessagesError({ status: obj.FailAllError }, 'error', null, [postdata, callback, tag, url, action]); return; }
  49. if (!postdata) postdata = "";
  50. //console.log("SEND: " + postdata); // DEBUG
  51. // We are in a websocket relay environment
  52. obj.ActiveAjaxCount++;
  53. return obj.PerformAjaxExNodeJS(postdata, callback, tag, url, action);
  54. }
  55. // Websocket relay specific private method
  56. obj.pendingAjaxCall = [];
  57. // Websocket relay specific private method
  58. obj.PerformAjaxExNodeJS = function (postdata, callback, tag, url, action) { obj.PerformAjaxExNodeJS2(postdata, callback, tag, url, action, 3); }
  59. // Websocket relay specific private method
  60. obj.PerformAjaxExNodeJS2 = function (postdata, callback, tag, url, action, retry) {
  61. if (retry <= 0 || obj.FailAllError != 0) {
  62. // Too many retry, fail here.
  63. obj.ActiveAjaxCount--;
  64. if (obj.FailAllError != 999) obj.gotNextMessages(null, 'error', { status: ((obj.FailAllError == 0) ? 408 : obj.FailAllError) }, [postdata, callback, tag, url, action]); // 408 is timeout error
  65. obj.PerformNextAjax();
  66. return;
  67. }
  68. obj.pendingAjaxCall.push([postdata, callback, tag, url, action, retry]);
  69. if (obj.socketState == 0) { obj.xxConnectHttpSocket(); }
  70. else if (obj.socketState == 2) { obj.sendRequest(postdata, url, action); }
  71. }
  72. // Websocket relay specific private method (Content Length Encoding)
  73. obj.sendRequest = function (postdata, url, action) {
  74. url = url ? url : '/wsman';
  75. action = action ? action : 'POST';
  76. var h = action + ' ' + url + ' HTTP/1.1\r\n';
  77. if (obj.challengeParams != null) {
  78. var response = hex_md5(hex_md5(obj.user + ':' + obj.challengeParams['realm'] + ':' + obj.pass) + ':' + obj.challengeParams['nonce'] + ':' + nonceHex(obj.noncecounter) + ':' + obj.cnonce + ':' + obj.challengeParams['qop'] + ':' + hex_md5(action + ':' + url + ((obj.challengeParams['qop'] == 'auth-int') ? (':' + hex_md5(postdata)) : '')));
  79. h += 'Authorization: ' + obj.renderDigest({ 'username': obj.user, 'realm': obj.challengeParams['realm'], 'nonce': obj.challengeParams['nonce'], 'uri': url, 'qop': obj.challengeParams['qop'], 'response': response, 'nc': nonceHex(obj.noncecounter++), 'cnonce': obj.cnonce }) + '\r\n';
  80. }
  81. //h += 'Host: ' + obj.host + ':' + obj.port + '\r\nContent-Length: ' + postdata.length + '\r\n\r\n' + postdata; // Use Content-Length
  82. h += 'Host: ' + obj.host + ':' + obj.port + '\r\nTransfer-Encoding: chunked\r\n\r\n' + postdata.length.toString(16).toUpperCase() + '\r\n' + postdata + '\r\n0\r\n\r\n'; // Use Chunked-Encoding
  83. _Send(h);
  84. //obj.Debug("SEND: " + h); // Display send packet
  85. }
  86. // Parse the HTTP digest header and return a list of key & values.
  87. obj.parseDigest = function (header) { return correctedQuoteSplit(header.substring(7)).reduce(function (obj, s) { var parts = s.trim().split('='); obj[parts[0]] = parts[1].replace(new RegExp('\"', 'g'), ''); return obj; }, {}) }
  88. // Split a string on quotes but do not do it when in quotes
  89. function correctedQuoteSplit(str) { return str.split(',').reduce(function (a, c) { if (a.ic) { a.st[a.st.length - 1] += ',' + c } else { a.st.push(c) } if (c.split('"').length % 2 == 0) { a.ic = !a.ic } return a; }, { st: [], ic: false }).st }
  90. function nonceHex(v) { var s = ('00000000' + v.toString(16)); return s.substring(s.length - 8); }
  91. // Websocket relay specific private method
  92. obj.renderDigest = function (params) {
  93. var paramsnames = [];
  94. for (i in params) { paramsnames.push(i); }
  95. return 'Digest ' + paramsnames.reduce(function (s1, ii) { return s1 + ',' + (((ii == 'nc') || (ii == 'qop')) ? (ii + '=' + params[ii]) : (ii + '="' + params[ii] + '"')); }, '').substring(1);
  96. }
  97. // Websocket relay specific private method
  98. obj.xxConnectHttpSocket = function () {
  99. //obj.Debug("xxConnectHttpSocket");
  100. obj.socketParseState = 0;
  101. obj.socketAccumulator = '';
  102. obj.socketHeader = null;
  103. obj.socketData = '';
  104. obj.socketState = 1;
  105. obj.socket = new WebSocket(window.location.protocol.replace('http', 'ws') + '//' + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/webrelay.ashx?p=1&host=' + obj.host + '&port=' + obj.port + '&tls=' + obj.tls + '&tlsv1only=' + obj.tlsv1only + ((user == '*') ? '&serverauth=1' : '') + ((typeof pass === 'undefined') ? ('&serverauth=1&user=' + user) : '')); // The "p=1" indicates to the relay that this is a WSMAN session
  106. obj.socket.binaryType = 'arraybuffer';
  107. obj.socket.onopen = _OnSocketConnected;
  108. obj.socket.onmessage = _OnMessage;
  109. obj.socket.onclose = _OnSocketClosed;
  110. }
  111. // Websocket relay specific private method
  112. function _OnSocketConnected() {
  113. //obj.Debug("xxOnSocketConnected");
  114. obj.socketState = 2;
  115. for (i in obj.pendingAjaxCall) { obj.sendRequest(obj.pendingAjaxCall[i][0], obj.pendingAjaxCall[i][3], obj.pendingAjaxCall[i][4]); }
  116. }
  117. // Websocket relay specific private method
  118. function _OnMessage(e) {
  119. //obj.Debug("_OnSocketData (" + data.byteLength + "): " + data);
  120. obj.socketAccumulator += arrToStr(new Uint8Array(e.data));
  121. while (true) {
  122. if (obj.socketParseState == 0) {
  123. var headersize = obj.socketAccumulator.indexOf('\r\n\r\n');
  124. if (headersize < 0) return;
  125. //obj.Debug(obj.socketAccumulator.substring(0, headersize)); // Display received HTTP header
  126. obj.socketHeader = obj.socketAccumulator.substring(0, headersize).split('\r\n');
  127. if (obj.amtVersion == null) { for (var i in obj.socketHeader) { if (obj.socketHeader[i].indexOf('Server: Intel(R) Active Management Technology ') == 0) { obj.amtVersion = obj.socketHeader[i].substring(46); } } }
  128. obj.socketAccumulator = obj.socketAccumulator.substring(headersize + 4);
  129. obj.socketParseState = 1;
  130. obj.socketData = '';
  131. obj.socketXHeader = { Directive: obj.socketHeader[0].split(' ') };
  132. for (i in obj.socketHeader) {
  133. if (i != 0) {
  134. var x2 = obj.socketHeader[i].indexOf(':');
  135. obj.socketXHeader[obj.socketHeader[i].substring(0, x2).toLowerCase()] = obj.socketHeader[i].substring(x2 + 2);
  136. }
  137. }
  138. }
  139. if (obj.socketParseState == 1) {
  140. var csize = -1;
  141. if ((obj.socketXHeader['connection'] != undefined) && (obj.socketXHeader['connection'].toLowerCase() == 'close') && ((obj.socketXHeader["transfer-encoding"] == undefined) || (obj.socketXHeader["transfer-encoding"].toLowerCase() != 'chunked'))) {
  142. // The body ends with a close, in this case, we will only process the header
  143. csize = 0;
  144. } else if (obj.socketXHeader['content-length'] != undefined) {
  145. // The body length is specified by the content-length
  146. csize = parseInt(obj.socketXHeader['content-length']);
  147. if (obj.socketAccumulator.length < csize) return;
  148. var data = obj.socketAccumulator.substring(0, csize);
  149. obj.socketAccumulator = obj.socketAccumulator.substring(csize);
  150. obj.socketData = data;
  151. csize = 0;
  152. } else {
  153. // The body is chunked
  154. var clen = obj.socketAccumulator.indexOf("\r\n");
  155. if (clen < 0) return; // Chunk length not found, exit now and get more data.
  156. // Chunk length if found, lets see if we can get the data.
  157. csize = parseInt(obj.socketAccumulator.substring(0, clen), 16);
  158. if (isNaN(csize)) { if (obj.websocket) { obj.websocket.close(); } return; } // Critical error, close the socket and exit.
  159. if (obj.socketAccumulator.length < clen + 2 + csize + 2) return;
  160. // We got a chunk with all of the data, handle the chunck now.
  161. var data = obj.socketAccumulator.substring(clen + 2, clen + 2 + csize);
  162. obj.socketAccumulator = obj.socketAccumulator.substring(clen + 2 + csize + 2);
  163. obj.socketData += data;
  164. }
  165. if (csize == 0) {
  166. //obj.Debug("_OnSocketData DONE: (" + obj.socketData.length + "): " + obj.socketData);
  167. _ProcessHttpResponse(obj.socketXHeader, obj.socketData);
  168. obj.socketParseState = 0;
  169. obj.socketHeader = null;
  170. }
  171. }
  172. }
  173. }
  174. // Websocket relay specific private method
  175. function _ProcessHttpResponse(header, data) {
  176. //obj.Debug("_ProcessHttpResponse: " + header.Directive[1]);
  177. var s = parseInt(header.Directive[1]);
  178. if (isNaN(s)) s = 602;
  179. if (s == 401 && ++(obj.authcounter) < 3) {
  180. obj.challengeParams = obj.parseDigest(header['www-authenticate']); // Set the digest parameters, after this, the socket will close and we will auto-retry
  181. if (obj.challengeParams['qop'] != null) {
  182. var qopList = obj.challengeParams['qop'].split(',');
  183. for (var i in qopList) { qopList[i] = qopList[i].trim(); }
  184. if (qopList.indexOf('auth-int') >= 0) { obj.challengeParams['qop'] = 'auth-int'; } else { obj.challengeParams['qop'] = 'auth'; }
  185. }
  186. } else {
  187. var r = obj.pendingAjaxCall.shift();
  188. // if (s != 200) { obj.Debug("Error, status=" + s + "\r\n\r\nreq=" + r[0] + "\r\n\r\nresp=" + data); } // Debug: Display the request & response if something did not work.
  189. obj.authcounter = 0;
  190. obj.ActiveAjaxCount--;
  191. obj.gotNextMessages(data, 'success', { status: s }, r);
  192. obj.PerformNextAjax();
  193. }
  194. }
  195. // Websocket relay specific private method
  196. function _OnSocketClosed(data) {
  197. //obj.Debug("_OnSocketClosed");
  198. obj.socketState = 0;
  199. if (obj.socket != null) { obj.socket.close(); obj.socket = null; }
  200. if (obj.pendingAjaxCall.length > 0) {
  201. var r = obj.pendingAjaxCall.shift();
  202. var retry = r[5];
  203. obj.PerformAjaxExNodeJS2(r[0], r[1], r[2], r[3], r[4], --retry);
  204. }
  205. }
  206. // Websocket relay specific private method
  207. function _Send(x) {
  208. //console.log("SEND: " + x); // DEBUG
  209. if (obj.socketState == 2 && obj.socket != null && obj.socket.readyState == WebSocket.OPEN) {
  210. var b = new Uint8Array(x.length);
  211. for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); }
  212. try { obj.socket.send(b.buffer); } catch (e) { }
  213. }
  214. }
  215. // Private method
  216. obj.gotNextMessages = function (data, status, request, callArgs) {
  217. if (obj.FailAllError == 999) return;
  218. if (obj.FailAllError != 0) { callArgs[1](null, obj.FailAllError, callArgs[2]); return; }
  219. if (request.status != 200) { callArgs[1](null, request.status, callArgs[2]); return; }
  220. callArgs[1](data, 200, callArgs[2]);
  221. }
  222. // Private method
  223. obj.gotNextMessagesError = function (request, status, errorThrown, callArgs) {
  224. if (obj.FailAllError == 999) return;
  225. if (obj.FailAllError != 0) { callArgs[1](null, obj.FailAllError, callArgs[2]); return; }
  226. callArgs[1](obj, null, { Header: { HttpError: request.status } }, request.status, callArgs[2]);
  227. }
  228. // Cancel all pending queries with given status
  229. obj.CancelAllQueries = function (s) {
  230. while (obj.PendingAjax.length > 0) { var x = obj.PendingAjax.shift(); x[1](null, s, x[2]); }
  231. if (obj.websocket != null) { obj.websocket.close(); obj.websocket = null; obj.socketState = 0; }
  232. }
  233. return obj;
  234. }