15
15
DataSink , DataSource , DataSourceError , DataStoreMixin ,
16
16
)
17
17
from stix2 .datastore .filters import Filter , FilterSet , apply_common_filters
18
- from stix2 .utils import format_datetime , get_type_from_id , is_marking
18
+ from stix2 .utils import format_datetime , get_type_from_id
19
19
20
20
21
21
def _timestamp2filename (timestamp ):
@@ -329,11 +329,50 @@ def _check_object_from_file(query, filepath, allow_custom, version, encoding):
329
329
return result
330
330
331
331
332
+ def _is_versioned_type_dir (type_path , type_name ):
333
+ """
334
+ Try to detect whether the given directory is for a versioned type of STIX
335
+ object. This is done by looking for a directory whose name is a STIX ID
336
+ of the appropriate type. If found, treat this type as versioned. This
337
+ doesn't work when a versioned type directory is empty (it will be
338
+ mis-classified as unversioned), but this detection is only necessary when
339
+ reading/querying data. If a directory is empty, you'll get no results
340
+ either way.
341
+
342
+ Args:
343
+ type_path: A path to a directory containing one type of STIX object.
344
+ type_name: The STIX type name.
345
+
346
+ Returns:
347
+ True if the directory looks like it contains versioned objects; False
348
+ if not.
349
+
350
+ Raises:
351
+ OSError: If there are errors accessing directory contents or stat()'ing
352
+ files
353
+ """
354
+ id_regex = re .compile (
355
+ r"^" + re .escape (type_name ) +
356
+ r"--[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"
357
+ r"-[0-9a-f]{12}$" ,
358
+ re .I ,
359
+ )
360
+
361
+ for entry in os .listdir (type_path ):
362
+ s = os .stat (os .path .join (type_path , entry ))
363
+ if stat .S_ISDIR (s .st_mode ) and id_regex .match (entry ):
364
+ is_versioned = True
365
+ break
366
+ else :
367
+ is_versioned = False
368
+
369
+ return is_versioned
370
+
371
+
332
372
def _search_versioned (query , type_path , auth_ids , allow_custom , version , encoding ):
333
373
"""
334
374
Searches the given directory, which contains data for STIX objects of a
335
- particular versioned type (i.e. not markings), and return any which match
336
- the query.
375
+ particular versioned type, and return any which match the query.
337
376
338
377
Args:
339
378
query: The query to match against
@@ -390,36 +429,24 @@ def _search_versioned(query, type_path, auth_ids, allow_custom, version, encodin
390
429
391
430
# For backward-compatibility, also search for plain files named after
392
431
# object IDs, in the type directory.
393
- id_files = _get_matching_dir_entries (
394
- type_path , auth_ids , stat .S_ISREG ,
395
- ".json" ,
432
+ backcompat_results = _search_unversioned (
433
+ query , type_path , auth_ids , allow_custom , version , encoding ,
396
434
)
397
- for id_file in id_files :
398
- id_path = os .path .join (type_path , id_file )
399
-
400
- try :
401
- stix_obj = _check_object_from_file (
402
- query , id_path , allow_custom ,
403
- version , encoding ,
404
- )
405
- if stix_obj :
406
- results .append (stix_obj )
407
- except IOError as e :
408
- if e .errno != errno .ENOENT :
409
- raise
410
- # else, file-not-found is ok, just skip
435
+ results .extend (backcompat_results )
411
436
412
437
return results
413
438
414
439
415
- def _search_markings (query , markings_path , auth_ids , allow_custom , version , encoding ):
440
+ def _search_unversioned (
441
+ query , type_path , auth_ids , allow_custom , version , encoding ,
442
+ ):
416
443
"""
417
- Searches the given directory, which contains markings data, and return any
418
- which match the query.
444
+ Searches the given directory, which contains unversioned data, and return
445
+ any objects which match the query.
419
446
420
447
Args:
421
448
query: The query to match against
422
- markings_path : The directory with STIX markings files
449
+ type_path : The directory with STIX files of unversioned type
423
450
auth_ids: Search optimization based on object ID
424
451
allow_custom (bool): Whether to allow custom properties as well unknown
425
452
custom objects.
@@ -441,11 +468,11 @@ def _search_markings(query, markings_path, auth_ids, allow_custom, version, enco
441
468
"""
442
469
results = []
443
470
id_files = _get_matching_dir_entries (
444
- markings_path , auth_ids , stat .S_ISREG ,
471
+ type_path , auth_ids , stat .S_ISREG ,
445
472
".json" ,
446
473
)
447
474
for id_file in id_files :
448
- id_path = os .path .join (markings_path , id_file )
475
+ id_path = os .path .join (type_path , id_file )
449
476
450
477
try :
451
478
stix_obj = _check_object_from_file (
@@ -530,12 +557,14 @@ def _check_path_and_write(self, stix_obj, encoding='utf-8'):
530
557
"""Write the given STIX object to a file in the STIX file directory.
531
558
"""
532
559
type_dir = os .path .join (self ._stix_dir , stix_obj ["type" ])
533
- if is_marking (stix_obj ):
534
- filename = stix_obj ["id" ]
535
- obj_dir = type_dir
536
- else :
560
+
561
+ # All versioned objects should have a "modified" property.
562
+ if "modified" in stix_obj :
537
563
filename = _timestamp2filename (stix_obj ["modified" ])
538
564
obj_dir = os .path .join (type_dir , stix_obj ["id" ])
565
+ else :
566
+ filename = stix_obj ["id" ]
567
+ obj_dir = type_dir
539
568
540
569
file_path = os .path .join (obj_dir , filename + ".json" )
541
570
@@ -649,12 +678,14 @@ def get(self, stix_id, version=None, _composite_filters=None):
649
678
all_data = self .all_versions (stix_id , version = version , _composite_filters = _composite_filters )
650
679
651
680
if all_data :
652
- if is_marking ( stix_id ):
653
- # Markings are unversioned; there shouldn't be more than one
654
- # result.
655
- stix_obj = all_data [0 ]
656
- else :
681
+ # Simple check for a versioned STIX type: see if the objects have a
682
+ # "modified" property. (Need only check one, since they are all of
683
+ # the same type.)
684
+ is_versioned = "modified" in all_data [0 ]
685
+ if is_versioned :
657
686
stix_obj = sorted (all_data , key = lambda k : k ['modified' ])[- 1 ]
687
+ else :
688
+ stix_obj = all_data [0 ]
658
689
else :
659
690
stix_obj = None
660
691
@@ -720,14 +751,15 @@ def query(self, query=None, version=None, _composite_filters=None):
720
751
)
721
752
for type_dir in type_dirs :
722
753
type_path = os .path .join (self ._stix_dir , type_dir )
723
- if type_dir == "marking-definition" :
724
- type_results = _search_markings (
754
+ type_is_versioned = _is_versioned_type_dir (type_path , type_dir )
755
+ if type_is_versioned :
756
+ type_results = _search_versioned (
725
757
query , type_path , auth_ids ,
726
758
self .allow_custom , version ,
727
759
self .encoding ,
728
760
)
729
761
else :
730
- type_results = _search_versioned (
762
+ type_results = _search_unversioned (
731
763
query , type_path , auth_ids ,
732
764
self .allow_custom , version ,
733
765
self .encoding ,
0 commit comments