Skip to content

Commit

Permalink
Added timezone awareness to best practice validation.
Browse files Browse the repository at this point in the history
* Added check for timezone/offset information in timestamps.
* Modified idref/timezone resolution code to be timezone aware.
  • Loading branch information
Bryan Worrell committed Apr 14, 2015
1 parent d257b57 commit d71b2a2
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 9 deletions.
30 changes: 28 additions & 2 deletions sdv/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
# builtin
import os
import contextlib
import datetime
from distutils.version import StrictVersion

# external
import dateutil.parser
from lxml import etree
from distutils.version import StrictVersion

# relative
from . import errors, xmlconst
Expand Down Expand Up @@ -268,4 +270,28 @@ def is_version_equal(x, y):
y: A string version number. Ex: '2.1'
"""
return StrictVersion(x) == StrictVersion(y)
return StrictVersion(x) == StrictVersion(y)


def parse_timestamp(value):
"""Attempts to parse `value` into an instance of ``datetime.datetime``. If
`value` is ``None``, this function will return ``None``.
Args:
value: A timestamp. This can be a string or datetime.datetime value.
"""
if not value:
return None
elif isinstance(value, datetime.datetime):
return value
return dateutil.parser.parse(value)


def has_tzinfo(timestamp):
"""Returns ``True`` if the `timestamp` includes timezone or UTC offset
information.
"""
ts = parse_timestamp(timestamp)
return ts and bool(ts.tzinfo)
28 changes: 21 additions & 7 deletions sdv/validators/stix/best_practice.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,17 +558,24 @@ def _check_timestamp_usage(self, root, namespaces, **kwargs): # noqa
xpath = " | ".join("//%s" % x for x in to_check)
nodes = root.xpath(xpath, namespaces=namespaces)

def _idref_resolves(idref, timestamp):
xpath = "//*[@id='%s' and @timestamp='%s']" % (idref, timestamp)
nodes = root.xpath(xpath, namespaces=namespaces)
return all((nodes is not None, len(nodes) > 0))

for node in nodes:
attrib = node.attrib.get
id_ = attrib('id')
idref = attrib('idref')
timestamp = attrib('timestamp')
warning = None

if timestamp:
tz_set = utils.has_tzinfo(timestamp)

if not tz_set:
warning = BestPracticeWarning(
node = node,
message="Timestamp without timezone information."
)
warning['timestamp'] = timestamp
results.append(warning)

warning = None # overwritten below

if id_ and not timestamp:
warning = BestPracticeWarning(
Expand All @@ -581,7 +588,14 @@ def _idref_resolves(idref, timestamp):
message="IDREF present but missing timestamp"
)
elif idref and timestamp:
if _idref_resolves(idref, timestamp):
resolves = common.idref_timestamp_resolves(
root=root,
idref=idref,
timestamp=timestamp,
namespaces=namespaces
)

if resolves:
continue

warning = BestPracticeWarning(
Expand Down
20 changes: 20 additions & 0 deletions sdv/validators/stix/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,23 @@ def get_document_namespaces(doc):
nsmap.update(element.nsmap)

return nsmap


def idref_timestamp_resolves(root, idref, timestamp, namespaces):
"""Determines if an `idref` and `timestamp` pair resolve to an XML
component under `root`.
"""
def ts(node):
return utils.parse_timestamp(node.get('timestamp'))

timestamp = utils.parse_timestamp(timestamp)
xpath = "//*[@id='{}']".format(idref)
nodes = root.xpath(xpath, namespaces=namespaces)

return any(ts(node) == timestamp for node in nodes)



0 comments on commit d71b2a2

Please sign in to comment.