frame-impl.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { BYTE } from './byte.js';
  2. /**
  3. * Frame class represents a STOMP frame.
  4. *
  5. * @internal
  6. */
  7. export class FrameImpl {
  8. /**
  9. * Frame constructor. `command`, `headers` and `body` are available as properties.
  10. *
  11. * @internal
  12. */
  13. constructor(params) {
  14. const { command, headers, body, binaryBody, escapeHeaderValues, skipContentLengthHeader, } = params;
  15. this.command = command;
  16. this.headers = Object.assign({}, headers || {});
  17. if (binaryBody) {
  18. this._binaryBody = binaryBody;
  19. this.isBinaryBody = true;
  20. }
  21. else {
  22. this._body = body || '';
  23. this.isBinaryBody = false;
  24. }
  25. this.escapeHeaderValues = escapeHeaderValues || false;
  26. this.skipContentLengthHeader = skipContentLengthHeader || false;
  27. }
  28. /**
  29. * body of the frame
  30. */
  31. get body() {
  32. if (!this._body && this.isBinaryBody) {
  33. this._body = new TextDecoder().decode(this._binaryBody);
  34. }
  35. return this._body || '';
  36. }
  37. /**
  38. * body as Uint8Array
  39. */
  40. get binaryBody() {
  41. if (!this._binaryBody && !this.isBinaryBody) {
  42. this._binaryBody = new TextEncoder().encode(this._body);
  43. }
  44. // At this stage it will definitely have a valid value
  45. return this._binaryBody;
  46. }
  47. /**
  48. * deserialize a STOMP Frame from raw data.
  49. *
  50. * @internal
  51. */
  52. static fromRawFrame(rawFrame, escapeHeaderValues) {
  53. const headers = {};
  54. const trim = (str) => str.replace(/^\s+|\s+$/g, '');
  55. // In case of repeated headers, as per standards, first value need to be used
  56. for (const header of rawFrame.headers.reverse()) {
  57. const idx = header.indexOf(':');
  58. const key = trim(header[0]);
  59. let value = trim(header[1]);
  60. if (escapeHeaderValues &&
  61. rawFrame.command !== 'CONNECT' &&
  62. rawFrame.command !== 'CONNECTED') {
  63. value = FrameImpl.hdrValueUnEscape(value);
  64. }
  65. headers[key] = value;
  66. }
  67. return new FrameImpl({
  68. command: rawFrame.command,
  69. headers,
  70. binaryBody: rawFrame.binaryBody,
  71. escapeHeaderValues,
  72. });
  73. }
  74. /**
  75. * @internal
  76. */
  77. toString() {
  78. return this.serializeCmdAndHeaders();
  79. }
  80. /**
  81. * serialize this Frame in a format suitable to be passed to WebSocket.
  82. * If the body is string the output will be string.
  83. * If the body is binary (i.e. of type Unit8Array) it will be serialized to ArrayBuffer.
  84. *
  85. * @internal
  86. */
  87. serialize() {
  88. const cmdAndHeaders = this.serializeCmdAndHeaders();
  89. if (this.isBinaryBody) {
  90. return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer;
  91. }
  92. else {
  93. return cmdAndHeaders + this._body + BYTE.NULL;
  94. }
  95. }
  96. serializeCmdAndHeaders() {
  97. const lines = [this.command];
  98. if (this.skipContentLengthHeader) {
  99. delete this.headers['content-length'];
  100. }
  101. for (const name of Object.keys(this.headers || {})) {
  102. const value = this.headers[name];
  103. if (this.escapeHeaderValues &&
  104. this.command !== 'CONNECT' &&
  105. this.command !== 'CONNECTED') {
  106. lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`);
  107. }
  108. else {
  109. lines.push(`${name}:${value}`);
  110. }
  111. }
  112. if (this.isBinaryBody ||
  113. (!this.isBodyEmpty() && !this.skipContentLengthHeader)) {
  114. lines.push(`content-length:${this.bodyLength()}`);
  115. }
  116. return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF;
  117. }
  118. isBodyEmpty() {
  119. return this.bodyLength() === 0;
  120. }
  121. bodyLength() {
  122. const binaryBody = this.binaryBody;
  123. return binaryBody ? binaryBody.length : 0;
  124. }
  125. /**
  126. * Compute the size of a UTF-8 string by counting its number of bytes
  127. * (and not the number of characters composing the string)
  128. */
  129. static sizeOfUTF8(s) {
  130. return s ? new TextEncoder().encode(s).length : 0;
  131. }
  132. static toUnit8Array(cmdAndHeaders, binaryBody) {
  133. const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders);
  134. const nullTerminator = new Uint8Array([0]);
  135. const uint8Frame = new Uint8Array(uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length);
  136. uint8Frame.set(uint8CmdAndHeaders);
  137. uint8Frame.set(binaryBody, uint8CmdAndHeaders.length);
  138. uint8Frame.set(nullTerminator, uint8CmdAndHeaders.length + binaryBody.length);
  139. return uint8Frame;
  140. }
  141. /**
  142. * Serialize a STOMP frame as per STOMP standards, suitable to be sent to the STOMP broker.
  143. *
  144. * @internal
  145. */
  146. static marshall(params) {
  147. const frame = new FrameImpl(params);
  148. return frame.serialize();
  149. }
  150. /**
  151. * Escape header values
  152. */
  153. static hdrValueEscape(str) {
  154. return str
  155. .replace(/\\/g, '\\\\')
  156. .replace(/\r/g, '\\r')
  157. .replace(/\n/g, '\\n')
  158. .replace(/:/g, '\\c');
  159. }
  160. /**
  161. * UnEscape header values
  162. */
  163. static hdrValueUnEscape(str) {
  164. return str
  165. .replace(/\\r/g, '\r')
  166. .replace(/\\n/g, '\n')
  167. .replace(/\\c/g, ':')
  168. .replace(/\\\\/g, '\\');
  169. }
  170. }
  171. //# sourceMappingURL=frame-impl.js.map