@cosyte/dicom 0.0.7 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -11,7 +11,7 @@ var __export = (target, all) => {
11
11
  };
12
12
 
13
13
  // src/version.ts
14
- var VERSION = "0.0.7";
14
+ var VERSION = "0.0.10";
15
15
 
16
16
  // src/dictionary/index.ts
17
17
  var dictionary_exports = {};
@@ -11703,6 +11703,12 @@ var BE_VR_STRIDE = Object.freeze({
11703
11703
  UT: 0,
11704
11704
  SQ: 0
11705
11705
  });
11706
+ var KNOWN_VRS = Object.freeze(
11707
+ new Set(Object.keys(BE_VR_STRIDE))
11708
+ );
11709
+ function isRecognizedVr(vr) {
11710
+ return KNOWN_VRS.has(vr);
11711
+ }
11706
11712
 
11707
11713
  // src/parser/warnings.ts
11708
11714
  var WARNING_CODES = {
@@ -11753,7 +11759,11 @@ var WARNING_MESSAGES = Object.freeze({
11753
11759
  DICOM_VR_MISMATCH: "Element ({tag}) on-wire VR is {vr2}; dictionary lists {vr}. Trusting on-wire VR.",
11754
11760
  DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
11755
11761
  DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
11756
- DICOM_NONZERO_RESERVED_BYTES: "Element ({tag}) has non-zero reserved bytes between VR and length (first byte {n}, second byte {n2}); ignoring.",
11762
+ // The tag is deliberately absent, and this is the only *parser* message where
11763
+ // that is true. See `nonzeroReservedBytes`. Kept terse for the same reason the
11764
+ // de-identify messages are: it is emitted once per element, and element count
11765
+ // is attacker-chosen.
11766
+ DICOM_NONZERO_RESERVED_BYTES: "Non-zero reserved bytes between VR and length (first byte {n}, second byte {n2}); ignoring. Tag withheld; the byte offset identifies the element.",
11757
11767
  DICOM_SQ_NOT_DESCENDED: "Element ({tag}) resolved to VR=SQ from the dictionary but its defined-length value is not a valid item stream; kept as opaque bytes and NOT descended, so nothing nested inside it is visible to navigation, and deidentify() cannot audit it. See DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE.",
11758
11768
  DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
11759
11769
  DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
@@ -11788,7 +11798,6 @@ var WARNING_MESSAGES = Object.freeze({
11788
11798
  DICOM_UNSUPPORTED_CHARSET: "(0008,0005) Specific Character Set value {n} names a defined term this build does not support; decoding text as UTF-8 best-effort. The term is not reproduced here - read the element if you need it.",
11789
11799
  DICOM_CHARSET_AMBIGUOUS_SEPARATOR: "(0008,0005) Specific Character Set value {n} is ambiguous under the active code extensions; decoding text as UTF-8 best-effort."
11790
11800
  });
11791
- var KNOWN_VRS = new Set(Object.keys(BE_VR_STRIDE));
11792
11801
  var TAG_SHAPE = /^[0-9A-F]{8}$/u;
11793
11802
  function renderTag(tag) {
11794
11803
  return tag !== void 0 && TAG_SHAPE.test(tag) ? tag : WITHHELD;
@@ -11830,9 +11839,8 @@ function privateTagNoCreator(position, tag) {
11830
11839
  function groupLengthInDataset(position, tag) {
11831
11840
  return build(WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET, position, { tag });
11832
11841
  }
11833
- function nonzeroReservedBytes(position, tag, first, second) {
11842
+ function nonzeroReservedBytes(position, first, second) {
11834
11843
  return build(WARNING_CODES.DICOM_NONZERO_RESERVED_BYTES, position, {
11835
- tag,
11836
11844
  n: first,
11837
11845
  n2: second
11838
11846
  });
@@ -12036,12 +12044,12 @@ function readExplicitElementHeader(cursor, _ctx, emit) {
12036
12044
  const vr = vrSlice.toString("ascii");
12037
12045
  let length;
12038
12046
  let headerLength;
12039
- if (LONG_FORM_VRS.has(vr)) {
12047
+ if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
12040
12048
  const reserved0 = cursor.buffer[cursor.position];
12041
12049
  const reserved1 = cursor.buffer[cursor.position + 1];
12042
12050
  cursor.position += 2;
12043
12051
  if ((reserved0 ?? 0) !== 0 || (reserved1 ?? 0) !== 0) {
12044
- emit(nonzeroReservedBytes({ byteOffset: headerStart }, tag, reserved0 ?? 0, reserved1 ?? 0));
12052
+ emit(nonzeroReservedBytes({ byteOffset: headerStart }, reserved0 ?? 0, reserved1 ?? 0));
12045
12053
  }
12046
12054
  length = cursor.readUInt32();
12047
12055
  headerLength = 12;
@@ -12179,7 +12187,7 @@ function readExplicitLeElement(cursor) {
12179
12187
  const vr = cursor.slice(2).toString("ascii");
12180
12188
  let length;
12181
12189
  let headerLength;
12182
- if (LONG_FORM_VRS.has(vr)) {
12190
+ if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
12183
12191
  cursor.slice(2);
12184
12192
  length = cursor.readUInt32();
12185
12193
  headerLength = 12;
@@ -13453,7 +13461,7 @@ function encodeDatasetElement(el, encoding) {
13453
13461
  }
13454
13462
  const littleEndian = encoding === "explicitLE";
13455
13463
  const vrBuf = buffer.Buffer.from(el.vr, "ascii");
13456
- if (LONG_FORM_VRS.has(el.vr)) {
13464
+ if (LONG_FORM_VRS.has(el.vr) || !isRecognizedVr(el.vr)) {
13457
13465
  return buffer.Buffer.concat([
13458
13466
  uint16(group, littleEndian),
13459
13467
  uint16(element, littleEndian),
@@ -13504,7 +13512,7 @@ function encodeMetaElement(tag, vr, rawValue) {
13504
13512
  const elementBuf = buffer.Buffer.alloc(2);
13505
13513
  elementBuf.writeUInt16LE(element, 0);
13506
13514
  const vrBuf = buffer.Buffer.from(vr, "ascii");
13507
- if (LONG_FORM_VRS.has(vr)) {
13515
+ if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
13508
13516
  const lengthBuf2 = buffer.Buffer.alloc(4);
13509
13517
  lengthBuf2.writeUInt32LE(value.length, 0);
13510
13518
  return buffer.Buffer.concat([
@@ -14699,7 +14707,6 @@ function uidValueMultiplicity(rawBytes) {
14699
14707
  }
14700
14708
 
14701
14709
  // src/deident/embedded.ts
14702
- var KNOWN_VRS2 = new Set(Object.keys(BE_VR_STRIDE));
14703
14710
  var UNDEFINED_LENGTH4 = 4294967295;
14704
14711
  var FFFE_GROUP = 65534;
14705
14712
  var SCANNABLE_CARRIER_VRS = /* @__PURE__ */ new Set([
@@ -14760,7 +14767,7 @@ function decodeAt(value, off, encoding) {
14760
14767
  return next2 > value.length ? void 0 : { next: next2, tag };
14761
14768
  }
14762
14769
  const vr = value.toString("latin1", off + 4, off + 6);
14763
- if (!KNOWN_VRS2.has(vr)) return void 0;
14770
+ if (!KNOWN_VRS.has(vr)) return void 0;
14764
14771
  if (LONG_FORM_VRS.has(vr)) {
14765
14772
  if (off + 12 > value.length) return void 0;
14766
14773
  if (value[off + 6] !== 0 || value[off + 7] !== 0) return void 0;
@@ -14897,7 +14904,6 @@ var BODY_ENCODING2 = {
14897
14904
  };
14898
14905
  var MAX_UNAUDITABLE_SEQUENCE_FINDINGS = 64;
14899
14906
  var MAX_UNDEFINED_VR_FINDINGS = 64;
14900
- var KNOWN_VRS3 = new Set(Object.keys(BE_VR_STRIDE));
14901
14907
  function validateRetain(retain) {
14902
14908
  const valid = new Set(DEIDENTIFY_OPTIONS);
14903
14909
  const active = /* @__PURE__ */ new Set();
@@ -15018,7 +15024,7 @@ function actsOnTag(tag, ctx) {
15018
15024
  return resolveAction(effectiveCode(action, ctx.active)) !== "K";
15019
15025
  }
15020
15026
  function hasUndefinedVr(el) {
15021
- return !KNOWN_VRS3.has(el.vr);
15027
+ return !KNOWN_VRS.has(el.vr);
15022
15028
  }
15023
15029
  function keepOrEmpty(el, ctx, contextPath, out) {
15024
15030
  if (hasUndefinedVr(el)) {
@@ -15082,6 +15088,13 @@ function creatorFor(tag, creators) {
15082
15088
  const { group, element } = splitTag(tag);
15083
15089
  return creators.get(`${String(group)}:${String(element >> 8 & 255)}`);
15084
15090
  }
15091
+ var UNDEFINED_LENGTH5 = 4294967295;
15092
+ var EXPLICIT_SQ_HEADER_BYTES = 12;
15093
+ function itemStreamOverrunsSequence(el, encoding) {
15094
+ if (el.vr !== "SQ" || el.length === UNDEFINED_LENGTH5) return false;
15095
+ const headerBytes = encoding === "implicit" ? 0 : EXPLICIT_SQ_HEADER_BYTES;
15096
+ return el.rawBytes.length - headerBytes > el.length;
15097
+ }
15085
15098
  function keepsPrivate(el, ctx, creators) {
15086
15099
  if (!ctx.active.has("RetainSafePrivate") || ctx.profile === void 0) return false;
15087
15100
  if (isPrivateCreatorElement(el.tag)) {
@@ -15091,7 +15104,7 @@ function keepsPrivate(el, ctx, creators) {
15091
15104
  if (creator === void 0) return false;
15092
15105
  return resolvePrivateTag(ctx.profile, el.tag, creator) !== void 0;
15093
15106
  }
15094
- function processElements(source, ctx, contextPath) {
15107
+ function processElements(source, ctx, contextPath, reservationsUsable) {
15095
15108
  const out = {
15096
15109
  elements: /* @__PURE__ */ new Map(),
15097
15110
  attributes: [],
@@ -15104,7 +15117,8 @@ function processElements(source, ctx, contextPath) {
15104
15117
  const creators = creatorsInScope(source);
15105
15118
  for (const el of source) {
15106
15119
  if (isPrivateTag(el.tag)) {
15107
- if (keepsPrivate(el, ctx, creators)) keepOrEmpty(el, ctx, contextPath, out);
15120
+ if (reservationsUsable && keepsPrivate(el, ctx, creators))
15121
+ keepOrEmpty(el, ctx, contextPath, out);
15108
15122
  else out.removedPrivateTags.push(el.tag);
15109
15123
  continue;
15110
15124
  }
@@ -15112,7 +15126,8 @@ function processElements(source, ctx, contextPath) {
15112
15126
  if (action === void 0) {
15113
15127
  if (el.vr === "SQ") {
15114
15128
  if (isUnauditableSequence(el)) emptyUnauditableSequence(el, ctx, contextPath, out);
15115
- else out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
15129
+ else
15130
+ out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
15116
15131
  } else {
15117
15132
  keepOrEmpty(el, ctx, contextPath, out);
15118
15133
  }
@@ -15120,7 +15135,7 @@ function processElements(source, ctx, contextPath) {
15120
15135
  }
15121
15136
  const resolved = resolveAction(effectiveCode(action, ctx.active));
15122
15137
  if (el.vr === "SQ") {
15123
- applySequenceAction(el, resolved, action, ctx, contextPath, out);
15138
+ applySequenceAction(el, resolved, action, ctx, contextPath, out, reservationsUsable);
15124
15139
  continue;
15125
15140
  }
15126
15141
  let applied;
@@ -15161,11 +15176,12 @@ function processElements(source, ctx, contextPath) {
15161
15176
  }
15162
15177
  return out;
15163
15178
  }
15164
- function descendSequence(el, ctx, contextPath, out) {
15179
+ function descendSequence(el, ctx, contextPath, out, reservationsUsable) {
15180
+ const childReservationsUsable = reservationsUsable && !itemStreamOverrunsSequence(el, ctx.encoding);
15165
15181
  const newItems = [];
15166
15182
  (el.items ?? []).forEach((item, index) => {
15167
15183
  const childPath = [...contextPath, `${el.tag}[${String(index)}]`];
15168
- const inner = processElements(item.elements(), ctx, childPath);
15184
+ const inner = processElements(item.elements(), ctx, childPath, childReservationsUsable);
15169
15185
  out.attributes.push(...inner.attributes);
15170
15186
  out.removedPrivateTags.push(...inner.removedPrivateTags);
15171
15187
  out.embeddedAttributes.push(...inner.embeddedAttributes);
@@ -15176,7 +15192,7 @@ function descendSequence(el, ctx, contextPath, out) {
15176
15192
  });
15177
15193
  return rebuildSequence(el, newItems, ctx.encoding);
15178
15194
  }
15179
- function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
15195
+ function applySequenceAction(el, resolved, action, ctx, contextPath, out, reservationsUsable) {
15180
15196
  let applied;
15181
15197
  switch (resolved) {
15182
15198
  case "X":
@@ -15192,7 +15208,7 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
15192
15208
  emptyUnauditableSequence(el, ctx, contextPath, out);
15193
15209
  applied = "emptied";
15194
15210
  } else {
15195
- out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
15211
+ out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
15196
15212
  applied = "cleaned";
15197
15213
  }
15198
15214
  break;
@@ -15202,7 +15218,7 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
15202
15218
  emptyUnauditableSequence(el, ctx, contextPath, out);
15203
15219
  applied = "emptied";
15204
15220
  } else {
15205
- out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
15221
+ out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
15206
15222
  applied = "kept";
15207
15223
  }
15208
15224
  break;
@@ -15253,7 +15269,7 @@ function deidentify(ds, options = {}) {
15253
15269
  encoding,
15254
15270
  budget: { unauditableSequences: 0, undefinedVrElements: 0 }
15255
15271
  };
15256
- const processed = processElements(ds.elements(), ctx, []);
15272
+ const processed = processElements(ds.elements(), ctx, [], true);
15257
15273
  const {
15258
15274
  elements,
15259
15275
  attributes,