Mercurial > hg > orthanc
comparison Resources/WebAssembly/dcdict.cc @ 2513:97a74f0eac7a
loading DICOM dictionaries in sandboxed environments
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 28 Mar 2018 18:02:07 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
2512:4dcafa8d6633 | 2513:97a74f0eac7a |
---|---|
1 /* | |
2 * | |
3 * Copyright (C) 1994-2016, OFFIS e.V. | |
4 * All rights reserved. See COPYRIGHT file for details. | |
5 * | |
6 * This software and supporting documentation were developed by | |
7 * | |
8 * OFFIS e.V. | |
9 * R&D Division Health | |
10 * Escherweg 2 | |
11 * D-26121 Oldenburg, Germany | |
12 * | |
13 * | |
14 * Module: dcmdata | |
15 * | |
16 * Author: Andrew Hewett | |
17 * | |
18 * Purpose: loadable DICOM data dictionary | |
19 * | |
20 */ | |
21 | |
22 | |
23 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ | |
24 | |
25 #include "dcmtk/ofstd/ofstd.h" | |
26 #include "dcmtk/dcmdata/dcdict.h" | |
27 #include "dcmtk/ofstd/ofdefine.h" | |
28 #include "dcmtk/dcmdata/dcdicent.h" | |
29 #include "dcmtk/dcmdata/dctypes.h" | |
30 | |
31 #define INCLUDE_CSTDLIB | |
32 #define INCLUDE_CSTDIO | |
33 #define INCLUDE_CSTRING | |
34 #define INCLUDE_CCTYPE | |
35 #include "dcmtk/ofstd/ofstdinc.h" | |
36 | |
37 /* | |
38 ** The separator character between fields in the data dictionary file(s) | |
39 */ | |
40 #define DCM_DICT_FIELD_SEPARATOR_CHAR '\t' | |
41 | |
42 /* | |
43 ** Comment character for the data dictionary file(s) | |
44 */ | |
45 #define DCM_DICT_COMMENT_CHAR '#' | |
46 | |
47 /* | |
48 ** THE Global DICOM Data Dictionary | |
49 */ | |
50 | |
51 GlobalDcmDataDictionary dcmDataDict; | |
52 | |
53 | |
54 /* | |
55 ** Member Functions | |
56 */ | |
57 | |
58 static DcmDictEntry* | |
59 makeSkelEntry(Uint16 group, Uint16 element, | |
60 Uint16 upperGroup, Uint16 upperElement, | |
61 DcmEVR evr, const char* tagName, int vmMin, int vmMax, | |
62 const char* standardVersion, | |
63 DcmDictRangeRestriction groupRestriction, | |
64 DcmDictRangeRestriction elementRestriction, | |
65 const char* privCreator) | |
66 { | |
67 DcmDictEntry* e = NULL; | |
68 e = new DcmDictEntry(group, element, upperGroup, upperElement, evr, | |
69 tagName, vmMin, vmMax, standardVersion, OFFalse, privCreator); | |
70 if (e != NULL) { | |
71 e->setGroupRangeRestriction(groupRestriction); | |
72 e->setElementRangeRestriction(elementRestriction); | |
73 } | |
74 return e; | |
75 } | |
76 | |
77 | |
78 OFBool DcmDataDictionary::loadSkeletonDictionary() | |
79 { | |
80 /* | |
81 ** We need to know about Group Lengths to compute them | |
82 */ | |
83 DcmDictEntry* e = NULL; | |
84 e = makeSkelEntry(0x0000, 0x0000, 0xffff, 0x0000, | |
85 EVR_UL, "GenericGroupLength", 1, 1, "GENERIC", | |
86 DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); | |
87 addEntry(e); | |
88 | |
89 /* | |
90 ** We need to know about Items and Delimitation Items to parse | |
91 ** (and construct) sequences. | |
92 */ | |
93 e = makeSkelEntry(0xfffe, 0xe000, 0xfffe, 0xe000, | |
94 EVR_na, "Item", 1, 1, "DICOM", | |
95 DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); | |
96 addEntry(e); | |
97 e = makeSkelEntry(0xfffe, 0xe00d, 0xfffe, 0xe00d, | |
98 EVR_na, "ItemDelimitationItem", 1, 1, "DICOM", | |
99 DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); | |
100 addEntry(e); | |
101 e = makeSkelEntry(0xfffe, 0xe0dd, 0xfffe, 0xe0dd, | |
102 EVR_na, "SequenceDelimitationItem", 1, 1, "DICOM", | |
103 DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL); | |
104 addEntry(e); | |
105 | |
106 skeletonCount = numberOfEntries(); | |
107 return OFTrue; | |
108 } | |
109 | |
110 | |
111 DcmDataDictionary::DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal) | |
112 : hashDict(), | |
113 repDict(), | |
114 skeletonCount(0), | |
115 dictionaryLoaded(OFFalse) | |
116 { | |
117 reloadDictionaries(loadBuiltin, loadExternal); | |
118 } | |
119 | |
120 DcmDataDictionary::~DcmDataDictionary() | |
121 { | |
122 clear(); | |
123 } | |
124 | |
125 | |
126 void DcmDataDictionary::clear() | |
127 { | |
128 hashDict.clear(); | |
129 repDict.clear(); | |
130 skeletonCount = 0; | |
131 dictionaryLoaded = OFFalse; | |
132 } | |
133 | |
134 | |
135 static void | |
136 stripWhitespace(char* s) | |
137 { | |
138 if (s) | |
139 { | |
140 unsigned char c; | |
141 unsigned char *t; | |
142 unsigned char *p; | |
143 t=p=OFreinterpret_cast(unsigned char *, s); | |
144 while ((c = *t++)) if (!isspace(c)) *p++ = c; | |
145 *p = '\0'; | |
146 } | |
147 } | |
148 | |
149 static char* | |
150 stripTrailingWhitespace(char* s) | |
151 { | |
152 if (s == NULL) return s; | |
153 for | |
154 ( | |
155 char* it = s + strlen(s) - 1; | |
156 it >= s && isspace(OFstatic_cast(unsigned char, *it)); | |
157 *it-- = '\0' | |
158 ); | |
159 return s; | |
160 } | |
161 | |
162 static void | |
163 stripLeadingWhitespace(char* s) | |
164 { | |
165 if (s) | |
166 { | |
167 unsigned char c; | |
168 unsigned char *t; | |
169 unsigned char *p; | |
170 t=p=OFreinterpret_cast(unsigned char *, s); | |
171 while (isspace(*t)) t++; | |
172 while ((c = *t++)) *p++ = c; | |
173 *p = '\0'; | |
174 } | |
175 } | |
176 | |
177 static OFBool | |
178 parseVMField(char* vmField, int& vmMin, int& vmMax) | |
179 { | |
180 OFBool ok = OFTrue; | |
181 char c = 0; | |
182 int dummy = 0; | |
183 | |
184 /* strip any whitespace */ | |
185 stripWhitespace(vmField); | |
186 | |
187 if (sscanf(vmField, "%d-%d%c", &vmMin, &dummy, &c) == 3) { | |
188 /* treat "2-2n" like "2-n" for the moment */ | |
189 if ((c == 'n') || (c == 'N')) { | |
190 vmMax = DcmVariableVM; | |
191 } else { | |
192 ok = OFFalse; | |
193 } | |
194 } else if (sscanf(vmField, "%d-%d", &vmMin, &vmMax) == 2) { | |
195 /* range VM (e.g. "2-6") */ | |
196 } else if (sscanf(vmField, "%d-%c", &vmMin, &c) == 2) { | |
197 if ((c == 'n') || (c == 'N')) { | |
198 vmMax = DcmVariableVM; | |
199 } else { | |
200 ok = OFFalse; | |
201 } | |
202 } else if (sscanf(vmField, "%d%c", &vmMin, &c) == 2) { | |
203 /* treat "2n" like "2-n" for the moment */ | |
204 if ((c == 'n') || (c == 'N')) { | |
205 vmMax = DcmVariableVM; | |
206 } else { | |
207 ok = OFFalse; | |
208 } | |
209 } else if (sscanf(vmField, "%d", &vmMin) == 1) { | |
210 /* fixed VM */ | |
211 vmMax = vmMin; | |
212 } else if (sscanf(vmField, "%c", &c) == 1) { | |
213 /* treat "n" like "1-n" */ | |
214 if ((c == 'n') || (c == 'N')) { | |
215 vmMin = 1; | |
216 vmMax = DcmVariableVM; | |
217 } else { | |
218 ok = OFFalse; | |
219 } | |
220 } else { | |
221 ok = OFFalse; | |
222 } | |
223 return ok; | |
224 } | |
225 | |
226 static int | |
227 splitFields(const char* line, char* fields[], int maxFields, char splitChar) | |
228 { | |
229 const char *p; | |
230 int foundFields = 0; | |
231 size_t len; | |
232 | |
233 do { | |
234 #ifdef __BORLANDC__ | |
235 // Borland Builder expects a non-const argument | |
236 p = strchr(OFconst_cast(char *, line), splitChar); | |
237 #else | |
238 p = strchr(line, splitChar); | |
239 #endif | |
240 if (p == NULL) { | |
241 len = strlen(line); | |
242 } else { | |
243 len = p - line; | |
244 } | |
245 fields[foundFields] = OFstatic_cast(char *, malloc(len + 1)); | |
246 strncpy(fields[foundFields], line, len); | |
247 fields[foundFields][len] = '\0'; | |
248 foundFields++; | |
249 line = p + 1; | |
250 } while ((foundFields < maxFields) && (p != NULL)); | |
251 | |
252 return foundFields; | |
253 } | |
254 | |
255 static OFBool | |
256 parseTagPart(char *s, unsigned int& l, unsigned int& h, | |
257 DcmDictRangeRestriction& r) | |
258 { | |
259 OFBool ok = OFTrue; | |
260 char restrictor = ' '; | |
261 | |
262 r = DcmDictRange_Unspecified; /* by default */ | |
263 | |
264 if (sscanf(s, "%x-%c-%x", &l, &restrictor, &h) == 3) { | |
265 switch (restrictor) { | |
266 case 'o': | |
267 case 'O': | |
268 r = DcmDictRange_Odd; | |
269 break; | |
270 case 'e': | |
271 case 'E': | |
272 r = DcmDictRange_Even; | |
273 break; | |
274 case 'u': | |
275 case 'U': | |
276 r = DcmDictRange_Unspecified; | |
277 break; | |
278 default: | |
279 DCMDATA_ERROR("DcmDataDictionary: Unknown range restrictor: " << restrictor); | |
280 ok = OFFalse; | |
281 break; | |
282 } | |
283 } else if (sscanf(s, "%x-%x", &l, &h) == 2) { | |
284 r = DcmDictRange_Even; /* by default */ | |
285 } else if (sscanf(s, "%x", &l) == 1) { | |
286 h = l; | |
287 } else { | |
288 ok = OFFalse; | |
289 } | |
290 return ok; | |
291 } | |
292 | |
293 static OFBool | |
294 parseWholeTagField(char* s, DcmTagKey& key, | |
295 DcmTagKey& upperKey, | |
296 DcmDictRangeRestriction& groupRestriction, | |
297 DcmDictRangeRestriction& elementRestriction, | |
298 char *&privCreator) | |
299 { | |
300 unsigned int gl, gh, el, eh; | |
301 groupRestriction = DcmDictRange_Unspecified; | |
302 elementRestriction = DcmDictRange_Unspecified; | |
303 | |
304 stripLeadingWhitespace(s); | |
305 stripTrailingWhitespace(s); | |
306 | |
307 char gs[64]; | |
308 char es[64]; | |
309 char pc[64]; | |
310 size_t slen = strlen(s); | |
311 | |
312 if (s[0] != '(') return OFFalse; | |
313 if (s[slen - 1] != ')') return OFFalse; | |
314 if (strchr(s, ',') == NULL) return OFFalse; | |
315 | |
316 /* separate the group and element parts */ | |
317 int i = 1; /* after the '(' */ | |
318 int gi = 0; | |
319 for (; s[i] != ',' && s[i] != '\0'; i++) | |
320 { | |
321 gs[gi] = s[i]; | |
322 gi++; | |
323 } | |
324 gs[gi] = '\0'; | |
325 | |
326 if (s[i] == '\0') return OFFalse; /* element part missing */ | |
327 i++; /* after the ',' */ | |
328 | |
329 stripLeadingWhitespace(s + i); | |
330 | |
331 int pi = 0; | |
332 if (s[i] == '\"') /* private creator */ | |
333 { | |
334 i++; // skip opening quotation mark | |
335 for (; s[i] != '\"' && s[i] != '\0'; i++) pc[pi++] = s[i]; | |
336 pc[pi] = '\0'; | |
337 if (s[i] == '\0') return OFFalse; /* closing quotation mark missing */ | |
338 i++; | |
339 stripLeadingWhitespace(s + i); | |
340 if (s[i] != ',') return OFFalse; /* element part missing */ | |
341 i++; /* after the ',' */ | |
342 } | |
343 | |
344 int ei = 0; | |
345 for (; s[i] != ')' && s[i] != '\0'; i++) { | |
346 es[ei] = s[i]; | |
347 ei++; | |
348 } | |
349 es[ei] = '\0'; | |
350 | |
351 /* parse the tag parts into their components */ | |
352 stripWhitespace(gs); | |
353 if (parseTagPart(gs, gl, gh, groupRestriction) == OFFalse) | |
354 return OFFalse; | |
355 | |
356 stripWhitespace(es); | |
357 if (parseTagPart(es, el, eh, elementRestriction) == OFFalse) | |
358 return OFFalse; | |
359 | |
360 if (pi > 0) | |
361 { | |
362 // copy private creator name | |
363 privCreator = new char[strlen(pc) + 1]; // deleted by caller | |
364 if (privCreator) strcpy(privCreator,pc); | |
365 } | |
366 | |
367 key.set(OFstatic_cast(unsigned short, gl), OFstatic_cast(unsigned short, el)); | |
368 upperKey.set(OFstatic_cast(unsigned short, gh), OFstatic_cast(unsigned short, eh)); | |
369 | |
370 return OFTrue; | |
371 } | |
372 | |
373 static OFBool | |
374 onlyWhitespace(const char* s) | |
375 { | |
376 size_t len = strlen(s); | |
377 int charsFound = OFFalse; | |
378 | |
379 for (size_t i = 0; (!charsFound) && (i < len); ++i) { | |
380 charsFound = !isspace(OFstatic_cast(unsigned char, s[i])); | |
381 } | |
382 return (!charsFound)? (OFTrue) : (OFFalse); | |
383 } | |
384 | |
385 static char* | |
386 getLine(char* line, int maxLineLen, FILE* f) | |
387 { | |
388 char* s; | |
389 | |
390 s = fgets(line, maxLineLen, f); | |
391 | |
392 /* strip any trailing white space */ | |
393 stripTrailingWhitespace(line); | |
394 | |
395 return s; | |
396 } | |
397 | |
398 static OFBool | |
399 isaCommentLine(const char* s) | |
400 { | |
401 OFBool isComment = OFFalse; /* assumption */ | |
402 size_t len = strlen(s); | |
403 size_t i = 0; | |
404 for (i = 0; i < len && isspace(OFstatic_cast(unsigned char, s[i])); ++i) /*loop*/; | |
405 isComment = (s[i] == DCM_DICT_COMMENT_CHAR); | |
406 return isComment; | |
407 } | |
408 | |
409 OFBool | |
410 DcmDataDictionary::reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal) | |
411 { | |
412 OFBool result = OFTrue; | |
413 clear(); | |
414 loadSkeletonDictionary(); | |
415 if (loadBuiltin) { | |
416 loadBuiltinDictionary(); | |
417 dictionaryLoaded = (numberOfEntries() > skeletonCount); | |
418 if (!dictionaryLoaded) result = OFFalse; | |
419 } | |
420 if (loadExternal) { | |
421 if (loadExternalDictionaries()) | |
422 dictionaryLoaded = OFTrue; | |
423 else | |
424 result = OFFalse; | |
425 } | |
426 return result; | |
427 } | |
428 | |
429 OFBool | |
430 DcmDataDictionary::loadDictionary(const char* fileName, OFBool errorIfAbsent) | |
431 { | |
432 | |
433 char lineBuf[DCM_MAXDICTLINESIZE + 1]; | |
434 FILE* f = NULL; | |
435 int lineNumber = 0; | |
436 char* lineFields[DCM_MAXDICTFIELDS + 1]; | |
437 int fieldsPresent; | |
438 DcmDictEntry* e; | |
439 int errorsEncountered = 0; | |
440 OFBool errorOnThisLine = OFFalse; | |
441 int i; | |
442 | |
443 DcmTagKey key, upperKey; | |
444 DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; | |
445 DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; | |
446 DcmVR vr; | |
447 char* vrName; | |
448 char* tagName; | |
449 char* privCreator; | |
450 int vmMin, vmMax = 1; | |
451 const char* standardVersion; | |
452 | |
453 /* first, check whether 'fileName' really points to a file (and not to a directory or the like) */ | |
454 if (!OFStandard::fileExists(fileName) || (f = fopen(fileName, "r")) == NULL) { | |
455 if (errorIfAbsent) { | |
456 DCMDATA_ERROR("DcmDataDictionary: Cannot open file: " << fileName); | |
457 } | |
458 return OFFalse; | |
459 } | |
460 | |
461 DCMDATA_DEBUG("DcmDataDictionary: Loading file: " << fileName); | |
462 | |
463 while (getLine(lineBuf, DCM_MAXDICTLINESIZE, f)) { | |
464 lineNumber++; | |
465 | |
466 if (onlyWhitespace(lineBuf)) { | |
467 continue; /* ignore this line */ | |
468 } | |
469 if (isaCommentLine(lineBuf)) { | |
470 continue; /* ignore this line */ | |
471 } | |
472 | |
473 errorOnThisLine = OFFalse; | |
474 | |
475 /* fields are tab separated */ | |
476 fieldsPresent = splitFields(lineBuf, lineFields, | |
477 DCM_MAXDICTFIELDS, | |
478 DCM_DICT_FIELD_SEPARATOR_CHAR); | |
479 | |
480 /* initialize dict entry fields */ | |
481 vrName = NULL; | |
482 tagName = NULL; | |
483 privCreator = NULL; | |
484 vmMin = vmMax = 1; | |
485 standardVersion = "DICOM"; | |
486 | |
487 switch (fieldsPresent) { | |
488 case 0: | |
489 case 1: | |
490 case 2: | |
491 DCMDATA_ERROR("DcmDataDictionary: "<< fileName << ": " | |
492 << "too few fields (line " << lineNumber << ")"); | |
493 errorOnThisLine = OFTrue; | |
494 break; | |
495 default: | |
496 DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " | |
497 << "too many fields (line " << lineNumber << "): "); | |
498 errorOnThisLine = OFTrue; | |
499 break; | |
500 case 5: | |
501 stripWhitespace(lineFields[4]); | |
502 standardVersion = lineFields[4]; | |
503 /* drop through to next case label */ | |
504 case 4: | |
505 /* the VM field is present */ | |
506 if (!parseVMField(lineFields[3], vmMin, vmMax)) { | |
507 DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " | |
508 << "bad VM field (line " << lineNumber << "): " << lineFields[3]); | |
509 errorOnThisLine = OFTrue; | |
510 } | |
511 /* drop through to next case label */ | |
512 case 3: | |
513 if (!parseWholeTagField(lineFields[0], key, upperKey, | |
514 groupRestriction, elementRestriction, privCreator)) | |
515 { | |
516 DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " | |
517 << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); | |
518 errorOnThisLine = OFTrue; | |
519 } else { | |
520 /* all is OK */ | |
521 vrName = lineFields[1]; | |
522 stripWhitespace(vrName); | |
523 | |
524 tagName = lineFields[2]; | |
525 stripWhitespace(tagName); | |
526 } | |
527 } | |
528 | |
529 if (!errorOnThisLine) { | |
530 /* check the VR Field */ | |
531 vr.setVR(vrName); | |
532 if (vr.getEVR() == EVR_UNKNOWN) { | |
533 DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": " | |
534 << "bad VR field (line " << lineNumber << "): " << vrName); | |
535 errorOnThisLine = OFTrue; | |
536 } | |
537 } | |
538 | |
539 if (!errorOnThisLine) { | |
540 e = new DcmDictEntry( | |
541 key.getGroup(), key.getElement(), | |
542 upperKey.getGroup(), upperKey.getElement(), | |
543 vr, tagName, vmMin, vmMax, standardVersion, OFTrue, | |
544 privCreator); | |
545 | |
546 e->setGroupRangeRestriction(groupRestriction); | |
547 e->setElementRangeRestriction(elementRestriction); | |
548 addEntry(e); | |
549 } | |
550 | |
551 for (i = 0; i < fieldsPresent; i++) { | |
552 free(lineFields[i]); | |
553 lineFields[i] = NULL; | |
554 } | |
555 | |
556 delete[] privCreator; | |
557 | |
558 if (errorOnThisLine) { | |
559 errorsEncountered++; | |
560 } | |
561 } | |
562 | |
563 fclose(f); | |
564 | |
565 /* return OFFalse in case of errors and set internal state accordingly */ | |
566 if (errorsEncountered == 0) { | |
567 dictionaryLoaded = OFTrue; | |
568 return OFTrue; | |
569 } | |
570 else { | |
571 dictionaryLoaded = OFFalse; | |
572 return OFFalse; | |
573 } | |
574 } | |
575 | |
576 #ifndef HAVE_GETENV | |
577 | |
578 static | |
579 char* getenv() { | |
580 return NULL; | |
581 } | |
582 | |
583 #endif /* !HAVE_GETENV */ | |
584 | |
585 | |
586 | |
587 OFBool | |
588 DcmDataDictionary::loadExternalDictionaries() | |
589 { | |
590 const char* env = NULL; | |
591 size_t len; | |
592 int sepCnt = 0; | |
593 OFBool msgIfDictAbsent = OFTrue; | |
594 OFBool loadFailed = OFFalse; | |
595 | |
596 env = getenv(DCM_DICT_ENVIRONMENT_VARIABLE); | |
597 if ((env == NULL) || (strlen(env) == 0)) { | |
598 env = DCM_DICT_DEFAULT_PATH; | |
599 msgIfDictAbsent = OFFalse; | |
600 } | |
601 | |
602 if ((env != NULL) && (strlen(env) != 0)) { | |
603 len = strlen(env); | |
604 for (size_t i = 0; i < len; ++i) { | |
605 if (env[i] == ENVIRONMENT_PATH_SEPARATOR) { | |
606 sepCnt++; | |
607 } | |
608 } | |
609 | |
610 if (sepCnt == 0) { | |
611 if (!loadDictionary(env, msgIfDictAbsent)) { | |
612 return OFFalse; | |
613 } | |
614 } else { | |
615 char** dictArray; | |
616 | |
617 dictArray = OFstatic_cast(char **, malloc((sepCnt + 1) * sizeof(char*))); | |
618 | |
619 int ndicts = splitFields(env, dictArray, sepCnt + 1, | |
620 ENVIRONMENT_PATH_SEPARATOR); | |
621 | |
622 for (int ii = 0; ii < ndicts; ii++) { | |
623 if ((dictArray[ii] != NULL) && (strlen(dictArray[ii]) > 0)) { | |
624 if (!loadDictionary(dictArray[ii], msgIfDictAbsent)) { | |
625 loadFailed = OFTrue; | |
626 } | |
627 } | |
628 free(dictArray[ii]); | |
629 } | |
630 free(dictArray); | |
631 } | |
632 } | |
633 | |
634 return (loadFailed) ? (OFFalse) : (OFTrue); | |
635 } | |
636 | |
637 | |
638 void | |
639 DcmDataDictionary::addEntry(DcmDictEntry* e) | |
640 { | |
641 if (e->isRepeating()) { | |
642 /* | |
643 * Find the best position in repeating tag list | |
644 * Existing entries are replaced if the ranges and repetition | |
645 * constraints are the same. | |
646 * If a range represents a subset of an existing range then it | |
647 * will be placed before it in the list. This ensures that a | |
648 * search will find the subset rather than the superset. | |
649 * Otherwise entries are appended to the end of the list. | |
650 */ | |
651 OFBool inserted = OFFalse; | |
652 | |
653 DcmDictEntryListIterator iter(repDict.begin()); | |
654 DcmDictEntryListIterator last(repDict.end()); | |
655 for (; !inserted && iter != last; ++iter) { | |
656 if (e->setEQ(**iter)) { | |
657 /* replace the old entry with the new */ | |
658 DcmDictEntry *old = *iter; | |
659 *iter = e; | |
660 #ifdef PRINT_REPLACED_DICTIONARY_ENTRIES | |
661 DCMDATA_WARN("replacing " << *old); | |
662 #endif | |
663 delete old; | |
664 inserted = OFTrue; | |
665 } else if (e->subset(**iter)) { | |
666 /* e is a subset of the current list position, insert before */ | |
667 repDict.insert(iter, e); | |
668 inserted = OFTrue; | |
669 } | |
670 } | |
671 if (!inserted) { | |
672 /* insert at end */ | |
673 repDict.push_back(e); | |
674 inserted = OFTrue; | |
675 } | |
676 } else { | |
677 hashDict.put(e); | |
678 } | |
679 } | |
680 | |
681 void | |
682 DcmDataDictionary::deleteEntry(const DcmDictEntry& entry) | |
683 { | |
684 DcmDictEntry* e = NULL; | |
685 e = OFconst_cast(DcmDictEntry *, findEntry(entry)); | |
686 if (e != NULL) { | |
687 if (e->isRepeating()) { | |
688 repDict.remove(e); | |
689 delete e; | |
690 } else { | |
691 hashDict.del(entry.getKey(), entry.getPrivateCreator()); | |
692 } | |
693 } | |
694 } | |
695 | |
696 const DcmDictEntry* | |
697 DcmDataDictionary::findEntry(const DcmDictEntry& entry) const | |
698 { | |
699 const DcmDictEntry* e = NULL; | |
700 | |
701 if (entry.isRepeating()) { | |
702 OFBool found = OFFalse; | |
703 DcmDictEntryListConstIterator iter(repDict.begin()); | |
704 DcmDictEntryListConstIterator last(repDict.end()); | |
705 for (; !found && iter != last; ++iter) { | |
706 if (entry.setEQ(**iter)) { | |
707 found = OFTrue; | |
708 e = *iter; | |
709 } | |
710 } | |
711 } else { | |
712 e = hashDict.get(entry, entry.getPrivateCreator()); | |
713 } | |
714 return e; | |
715 } | |
716 | |
717 const DcmDictEntry* | |
718 DcmDataDictionary::findEntry(const DcmTagKey& key, const char *privCreator) const | |
719 { | |
720 /* search first in the normal tags dictionary and if not found | |
721 * then search in the repeating tags list. | |
722 */ | |
723 const DcmDictEntry* e = NULL; | |
724 | |
725 e = hashDict.get(key, privCreator); | |
726 if (e == NULL) { | |
727 /* search in the repeating tags dictionary */ | |
728 OFBool found = OFFalse; | |
729 DcmDictEntryListConstIterator iter(repDict.begin()); | |
730 DcmDictEntryListConstIterator last(repDict.end()); | |
731 for (; !found && iter != last; ++iter) { | |
732 if ((*iter)->contains(key, privCreator)) { | |
733 found = OFTrue; | |
734 e = *iter; | |
735 } | |
736 } | |
737 } | |
738 return e; | |
739 } | |
740 | |
741 const DcmDictEntry* | |
742 DcmDataDictionary::findEntry(const char *name) const | |
743 { | |
744 const DcmDictEntry* e = NULL; | |
745 const DcmDictEntry* ePrivate = NULL; | |
746 | |
747 /* search first in the normal tags dictionary and if not found | |
748 * then search in the repeating tags list. | |
749 */ | |
750 DcmHashDictIterator iter; | |
751 for (iter = hashDict.begin(); (e == NULL) && (iter != hashDict.end()); ++iter) { | |
752 if ((*iter)->contains(name)) { | |
753 e = *iter; | |
754 if (e->getGroup() % 2) | |
755 { | |
756 /* tag is a private tag - continue search to be sure to find non-private keys first */ | |
757 if (!ePrivate) ePrivate = e; | |
758 e = NULL; | |
759 } | |
760 } | |
761 } | |
762 | |
763 if (e == NULL) { | |
764 /* search in the repeating tags dictionary */ | |
765 OFBool found = OFFalse; | |
766 DcmDictEntryListConstIterator iter2(repDict.begin()); | |
767 DcmDictEntryListConstIterator last(repDict.end()); | |
768 for (; !found && iter2 != last; ++iter2) { | |
769 if ((*iter2)->contains(name)) { | |
770 found = OFTrue; | |
771 e = *iter2; | |
772 } | |
773 } | |
774 } | |
775 | |
776 if (e == NULL && ePrivate != NULL) { | |
777 /* no standard key found - use the first private key found */ | |
778 e = ePrivate; | |
779 } | |
780 | |
781 return e; | |
782 } | |
783 | |
784 | |
785 /* ================================================================== */ | |
786 | |
787 | |
788 GlobalDcmDataDictionary::GlobalDcmDataDictionary() | |
789 : dataDict(NULL) | |
790 #ifdef WITH_THREADS | |
791 , dataDictLock() | |
792 #endif | |
793 { | |
794 } | |
795 | |
796 GlobalDcmDataDictionary::~GlobalDcmDataDictionary() | |
797 { | |
798 /* No threads may be active any more, so no locking needed */ | |
799 delete dataDict; | |
800 } | |
801 | |
802 void GlobalDcmDataDictionary::createDataDict() | |
803 { | |
804 /* Make sure only one thread tries to initialize the dictionary */ | |
805 #ifdef WITH_THREADS | |
806 dataDictLock.wrlock(); | |
807 #endif | |
808 #ifdef DONT_LOAD_EXTERNAL_DICTIONARIES | |
809 const OFBool loadExternal = OFFalse; | |
810 #else | |
811 const OFBool loadExternal = OFTrue; | |
812 #endif | |
813 /* Make sure no other thread managed to create the dictionary | |
814 * before we got our write lock. */ | |
815 if (!dataDict) | |
816 dataDict = new DcmDataDictionary(OFTrue /*loadBuiltin*/, loadExternal); | |
817 #ifdef WITH_THREADS | |
818 dataDictLock.unlock(); | |
819 #endif | |
820 } | |
821 | |
822 const DcmDataDictionary& GlobalDcmDataDictionary::rdlock() | |
823 { | |
824 #ifdef WITH_THREADS | |
825 dataDictLock.rdlock(); | |
826 #endif | |
827 if (!dataDict) | |
828 { | |
829 /* dataDictLock must not be locked during createDataDict() */ | |
830 #ifdef WITH_THREADS | |
831 dataDictLock.unlock(); | |
832 #endif | |
833 createDataDict(); | |
834 #ifdef WITH_THREADS | |
835 dataDictLock.rdlock(); | |
836 #endif | |
837 } | |
838 return *dataDict; | |
839 } | |
840 | |
841 DcmDataDictionary& GlobalDcmDataDictionary::wrlock() | |
842 { | |
843 #ifdef WITH_THREADS | |
844 dataDictLock.wrlock(); | |
845 #endif | |
846 if (!dataDict) | |
847 { | |
848 /* dataDictLock must not be locked during createDataDict() */ | |
849 #ifdef WITH_THREADS | |
850 dataDictLock.unlock(); | |
851 #endif | |
852 createDataDict(); | |
853 #ifdef WITH_THREADS | |
854 dataDictLock.wrlock(); | |
855 #endif | |
856 } | |
857 return *dataDict; | |
858 } | |
859 | |
860 void GlobalDcmDataDictionary::unlock() | |
861 { | |
862 #ifdef WITH_THREADS | |
863 dataDictLock.unlock(); | |
864 #endif | |
865 } | |
866 | |
867 OFBool GlobalDcmDataDictionary::isDictionaryLoaded() | |
868 { | |
869 OFBool result = rdlock().isDictionaryLoaded(); | |
870 unlock(); | |
871 return result; | |
872 } | |
873 | |
874 void GlobalDcmDataDictionary::clear() | |
875 { | |
876 wrlock().clear(); | |
877 unlock(); | |
878 } | |
879 | |
880 | |
881 | |
882 | |
883 // Function by the Orthanc project to load a dictionary from a memory | |
884 // buffer, which is necessary in sandboxed environments. This is an | |
885 // adapted version of DcmDataDictionary::loadDictionary(). | |
886 | |
887 | |
888 #include <boost/noncopyable.hpp> | |
889 | |
890 struct OrthancLinesIterator; | |
891 | |
892 // This plain old C class is implemented in "../../Core/Toolbox.h" | |
893 OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content); | |
894 | |
895 bool OrthancLinesIterator_GetLine(std::string& target, | |
896 const OrthancLinesIterator* iterator); | |
897 | |
898 void OrthancLinesIterator_Next(OrthancLinesIterator* iterator); | |
899 | |
900 void OrthancLinesIterator_Free(OrthancLinesIterator* iterator); | |
901 | |
902 | |
903 class LinesIterator : public boost::noncopyable | |
904 { | |
905 private: | |
906 OrthancLinesIterator* iterator_; | |
907 | |
908 public: | |
909 LinesIterator(const std::string& content) : | |
910 iterator_(NULL) | |
911 { | |
912 iterator_ = OrthancLinesIterator_Create(content); | |
913 } | |
914 | |
915 ~LinesIterator() | |
916 { | |
917 if (iterator_ != NULL) | |
918 { | |
919 OrthancLinesIterator_Free(iterator_); | |
920 iterator_ = NULL; | |
921 } | |
922 } | |
923 | |
924 bool GetLine(std::string& target) const | |
925 { | |
926 if (iterator_ != NULL) | |
927 { | |
928 return OrthancLinesIterator_GetLine(target, iterator_); | |
929 } | |
930 else | |
931 { | |
932 return false; | |
933 } | |
934 } | |
935 | |
936 void Next() | |
937 { | |
938 if (iterator_ != NULL) | |
939 { | |
940 OrthancLinesIterator_Next(iterator_); | |
941 } | |
942 } | |
943 }; | |
944 | |
945 | |
946 | |
947 OFBool | |
948 DcmDataDictionary::loadFromMemory(const std::string& content, OFBool errorIfAbsent) | |
949 { | |
950 int lineNumber = 0; | |
951 char* lineFields[DCM_MAXDICTFIELDS + 1]; | |
952 int fieldsPresent; | |
953 DcmDictEntry* e; | |
954 int errorsEncountered = 0; | |
955 OFBool errorOnThisLine = OFFalse; | |
956 int i; | |
957 | |
958 DcmTagKey key, upperKey; | |
959 DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified; | |
960 DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified; | |
961 DcmVR vr; | |
962 char* vrName; | |
963 char* tagName; | |
964 char* privCreator; | |
965 int vmMin, vmMax = 1; | |
966 const char* standardVersion; | |
967 | |
968 LinesIterator iterator(content); | |
969 | |
970 std::string line; | |
971 while (iterator.GetLine(line)) { | |
972 iterator.Next(); | |
973 | |
974 if (line.size() >= DCM_MAXDICTLINESIZE) { | |
975 DCMDATA_ERROR("DcmDataDictionary: Too long line: " << line); | |
976 continue; | |
977 } | |
978 | |
979 lineNumber++; | |
980 | |
981 if (onlyWhitespace(line.c_str())) { | |
982 continue; /* ignore this line */ | |
983 } | |
984 if (isaCommentLine(line.c_str())) { | |
985 continue; /* ignore this line */ | |
986 } | |
987 | |
988 errorOnThisLine = OFFalse; | |
989 | |
990 /* fields are tab separated */ | |
991 fieldsPresent = splitFields(line.c_str(), lineFields, | |
992 DCM_MAXDICTFIELDS, | |
993 DCM_DICT_FIELD_SEPARATOR_CHAR); | |
994 | |
995 /* initialize dict entry fields */ | |
996 vrName = NULL; | |
997 tagName = NULL; | |
998 privCreator = NULL; | |
999 vmMin = vmMax = 1; | |
1000 standardVersion = "DICOM"; | |
1001 | |
1002 switch (fieldsPresent) { | |
1003 case 0: | |
1004 case 1: | |
1005 case 2: | |
1006 DCMDATA_ERROR("DcmDataDictionary: " | |
1007 << "too few fields (line " << lineNumber << ")"); | |
1008 errorOnThisLine = OFTrue; | |
1009 break; | |
1010 default: | |
1011 DCMDATA_ERROR("DcmDataDictionary: " | |
1012 << "too many fields (line " << lineNumber << "): "); | |
1013 errorOnThisLine = OFTrue; | |
1014 break; | |
1015 case 5: | |
1016 stripWhitespace(lineFields[4]); | |
1017 standardVersion = lineFields[4]; | |
1018 /* drop through to next case label */ | |
1019 case 4: | |
1020 /* the VM field is present */ | |
1021 if (!parseVMField(lineFields[3], vmMin, vmMax)) { | |
1022 DCMDATA_ERROR("DcmDataDictionary: " | |
1023 << "bad VM field (line " << lineNumber << "): " << lineFields[3]); | |
1024 errorOnThisLine = OFTrue; | |
1025 } | |
1026 /* drop through to next case label */ | |
1027 case 3: | |
1028 if (!parseWholeTagField(lineFields[0], key, upperKey, | |
1029 groupRestriction, elementRestriction, privCreator)) | |
1030 { | |
1031 DCMDATA_ERROR("DcmDataDictionary: " | |
1032 << "bad Tag field (line " << lineNumber << "): " << lineFields[0]); | |
1033 errorOnThisLine = OFTrue; | |
1034 } else { | |
1035 /* all is OK */ | |
1036 vrName = lineFields[1]; | |
1037 stripWhitespace(vrName); | |
1038 | |
1039 tagName = lineFields[2]; | |
1040 stripWhitespace(tagName); | |
1041 } | |
1042 } | |
1043 | |
1044 if (!errorOnThisLine) { | |
1045 /* check the VR Field */ | |
1046 vr.setVR(vrName); | |
1047 if (vr.getEVR() == EVR_UNKNOWN) { | |
1048 DCMDATA_ERROR("DcmDataDictionary: " | |
1049 << "bad VR field (line " << lineNumber << "): " << vrName); | |
1050 errorOnThisLine = OFTrue; | |
1051 } | |
1052 } | |
1053 | |
1054 if (!errorOnThisLine) { | |
1055 e = new DcmDictEntry( | |
1056 key.getGroup(), key.getElement(), | |
1057 upperKey.getGroup(), upperKey.getElement(), | |
1058 vr, tagName, vmMin, vmMax, standardVersion, OFTrue, | |
1059 privCreator); | |
1060 | |
1061 e->setGroupRangeRestriction(groupRestriction); | |
1062 e->setElementRangeRestriction(elementRestriction); | |
1063 addEntry(e); | |
1064 } | |
1065 | |
1066 for (i = 0; i < fieldsPresent; i++) { | |
1067 free(lineFields[i]); | |
1068 lineFields[i] = NULL; | |
1069 } | |
1070 | |
1071 delete[] privCreator; | |
1072 | |
1073 if (errorOnThisLine) { | |
1074 errorsEncountered++; | |
1075 } | |
1076 } | |
1077 | |
1078 /* return OFFalse in case of errors and set internal state accordingly */ | |
1079 if (errorsEncountered == 0) { | |
1080 dictionaryLoaded = OFTrue; | |
1081 return OFTrue; | |
1082 } | |
1083 else { | |
1084 dictionaryLoaded = OFFalse; | |
1085 return OFFalse; | |
1086 } | |
1087 } |