Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(wado-rs): can not decode some multipart properly #50

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 188 additions & 31 deletions bluelight/scripts/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,42 +121,199 @@ function wadorsLoader2(url, onlyload) {

fetch(url, { headers, }).then(async function (res) {
let resBlob = await res.arrayBuffer();
let intArray = new Uint8Array(resBlob);
let charArray = new Array(intArray.length);
for (let i = 0; i < intArray.length; i++) charArray[i] = String.fromCodePoint(intArray[i]);
let responseText = charArray.join('');

function getEndIndex(responseText) {
let startBoundary = responseText.split("\r\n")[0];
let matches = responseText.matchAll(new RegExp(startBoundary, "gi"));
let fileEndIndex = [];
for (let match of matches)
fileEndIndex.push(match.index - 2);
return fileEndIndex.slice(1);
let decodedBuffers = multipartDecode(resBlob);
for (let decodedBuf of decodedBuffers) {
loadDicomDataSet(decodedBuf, !(onlyload == true), url, false);
}
}).finally(() => LoadFileInBatches.finishOne());
}

function getStartIndex(responseText) {
var fileStartIndex = [];
let teststring = ["Content-Type", "Content-Length", "MIME-Version"]
let matchesIndex;
for (let type of teststring) {
let contentTypeMatches = responseText.matchAll(new RegExp(`${type}.*[\r\n|\r|\n]$`, "gim"));
for (let match of contentTypeMatches) {
if (isNaN(match.index)) continue;
if (!matchesIndex || (match.index > matchesIndex.index))
matchesIndex = { index: match.index, length: match['0'].length };
}
/**
* 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, limit) {
offset = offset || 0;
limit = limit || arr.length - offset;
let str = "";
for (let i = offset; i < offset + limit; 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];
}
}
}

/**
* 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;
}
}
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 {Number} 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;
}
fileStartIndex.push(matchesIndex.index + matchesIndex.length + 3);
return fileStartIndex;
}
}

var fileEndIndex = getEndIndex(responseText);
var fileStartIndex = getStartIndex(responseText);
var file = responseText.substring(fileStartIndex[0], fileEndIndex[0]);
var buf = Uint8Array.from(Array.from(file).map(letter => letter.charCodeAt(0)));
loadDicomDataSet(buf, !(onlyload == true), url, false);
}).finally(() => LoadFileInBatches.finishOne());
return -1;
}

/**
* 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
let separator = stringToUint8Array("\r\n\r\n");
let 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 components = [];

let offset = headerIndex + separator.length;

// 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;
}

// Extract data from response message, excluding "\r\n"
const spacingLength = 2;
const length = boundaryIndex - offset - spacingLength;
const data = response.slice(offset, offset + length);

// Add the data to the array of results
components.push(data);

// find the end of the boundary
var boundaryEnd = findToken(
message,
separator,
boundaryIndex + 1,
maxSearchLength
);
if (boundaryEnd === -1) break;
// Move the offset to the end of the identified boundary
offset = boundaryEnd + separator.length;
}

return components;
}

/**
* 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()
);
}

function PdfLoader(pdf, Sop) {
Expand Down
Loading