UNPKG

dicom-microscopy-viewer-changed

Version:
262 lines (223 loc) 7.53 kB
/** * Converts a Uint8Array to a String. * @param {Uint8Array} array that should be converted * @param {Number} offset array offset in case only subset of array items should be extracted (default: 0) * @param {Number} limit maximum number of array items that should be extracted (defaults to length of array) * @returns {String} */ function uint8ArrayToString(arr, offset = 0, limit) { const itemLimit = limit || arr.length - offset; let str = ""; for (let i = offset; i < offset + itemLimit; i++) { str += String.fromCharCode(arr[i]); } return str; } /** * Converts a String to a Uint8Array. * @param {String} str string that should be converted * @returns {Uint8Array} */ function stringToUint8Array(str) { const arr = new Uint8Array(str.length); for (let i = 0, j = str.length; i < j; i++) { arr[i] = str.charCodeAt(i); } return arr; } /** * Identifies the boundary in a multipart/related message header. * @param {String} header message header * @returns {String} boundary */ function identifyBoundary(header) { const parts = header.split("\r\n"); for (let i = 0; i < parts.length; i++) { if (parts[i].substr(0, 2) === "--") { return parts[i]; } } return null; } /** * Checks whether a given token is contained by a message at a given offset. * @param {Uint8Array} message message content * @param {Uint8Array} token substring that should be present * @param {Number} offset offset in message content from where search should start * @returns {Boolean} whether message contains token at offset */ function containsToken(message, token, offset = 0) { if (offset + token.length > message.length) { return false; } let index = offset; for (let i = 0; i < token.length; i++) { if (token[i] !== message[index]) { return false; } index += 1; } return true; } /** * Finds a given token in a message at a given offset. * @param {Uint8Array} message message content * @param {Uint8Array} token substring that should be found * @param {String} offset message body offset from where search should start * @returns {Boolean} whether message has a part at given offset or not */ function findToken(message, token, offset = 0, maxSearchLength) { let searchLength = message.length; if (maxSearchLength) { searchLength = Math.min(offset + maxSearchLength, message.length); } for (let i = offset; i < searchLength; i++) { // If the first value of the message matches // the first value of the token, check if // this is the full token. if (message[i] === token[0]) { if (containsToken(message, token, i)) { return i; } } } return -1; } /** * Create a random GUID * * @return {string} */ function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; } /** * @typedef {Object} MultipartEncodedData * @property {ArrayBuffer} data The encoded Multipart Data * @property {String} boundary The boundary used to divide pieces of the encoded data */ /** * Encode one or more DICOM datasets into a single body so it can be * sent using the Multipart Content-Type. * * @param {ArrayBuffer[]} datasets Array containing each file to be encoded in the multipart body, passed as ArrayBuffers. * @param {String} [boundary] Optional string to define a boundary between each part of the multipart body. If this is not specified, a random GUID will be generated. * @return {MultipartEncodedData} The Multipart encoded data returned as an Object. This contains both the data itself, and the boundary string used to divide it. */ function multipartEncode( datasets, boundary = guid(), contentType = "application/dicom" ) { const contentTypeString = `Content-Type: ${contentType}`; const header = `\r\n--${boundary}\r\n${contentTypeString}\r\n\r\n`; const footer = `\r\n--${boundary}--`; const headerArray = stringToUint8Array(header); const footerArray = stringToUint8Array(footer); const headerLength = headerArray.length; const footerLength = footerArray.length; let length = 0; // Calculate the total length for the final array const contentArrays = datasets.map(datasetBuffer => { const contentArray = new Uint8Array(datasetBuffer); const contentLength = contentArray.length; length += headerLength + contentLength + footerLength; return contentArray; }); // Allocate the array const multipartArray = new Uint8Array(length); // Set the initial header multipartArray.set(headerArray, 0); // Write each dataset into the multipart array let position = 0; contentArrays.forEach(contentArray => { multipartArray.set(headerArray, position); multipartArray.set(contentArray, position + headerLength); position += headerLength + contentArray.length; }); multipartArray.set(footerArray, position); return { data: multipartArray.buffer, boundary }; } /** * Decode a Multipart encoded ArrayBuffer and return the components as an Array. * * @param {ArrayBuffer} response Data encoded as a 'multipart/related' message * @returns {Array} The content */ function multipartDecode(response) { const message = new Uint8Array(response); /* Set a maximum length to search for the header boundaries, otherwise findToken can run for a long time */ const maxSearchLength = 1000; // First look for the multipart mime header const separator = stringToUint8Array("\r\n\r\n"); const headerIndex = findToken(message, separator, 0, maxSearchLength); if (headerIndex === -1) { throw new Error("Response message has no multipart mime header"); } const header = uint8ArrayToString(message, 0, headerIndex); const boundaryString = identifyBoundary(header); if (!boundaryString) { throw new Error("Header of response message does not specify boundary"); } const boundary = stringToUint8Array(boundaryString); const boundaryLength = boundary.length; const components = []; let offset = boundaryLength; // Loop until we cannot find any more boundaries let boundaryIndex; while (boundaryIndex !== -1) { // Search for the next boundary in the message, starting // from the current offset position boundaryIndex = findToken(message, boundary, offset); // If no further boundaries are found, stop here. if (boundaryIndex === -1) { break; } const headerTokenIndex = findToken( message, separator, offset, maxSearchLength ); if (headerTokenIndex === -1) { throw new Error("Response message part has no mime header"); } offset = headerTokenIndex + separator.length; // Extract data from response message, excluding "\r\n" const spacingLength = 2; const data = response.slice(offset, boundaryIndex - spacingLength); // Add the data to the array of results components.push(data); // Move the offset to the end of the current section, // plus the identified boundary offset = boundaryIndex + boundaryLength; } return components; } export { containsToken, findToken, identifyBoundary, uint8ArrayToString, stringToUint8Array, multipartEncode, multipartDecode, guid };