Skip to content

Commit

Permalink
Use UTC tzinfo datetime values (#252)
Browse files Browse the repository at this point in the history
Stop using a deprecated function that was deprecated in Python 3.12 and
ensure any datetime values unpacked from SMB messages are using the UTC
timezone instead of being a naive tz.
  • Loading branch information
jborean93 authored Nov 24, 2023
1 parent f5314fe commit 37512ee
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 88 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* Added the property `smb_info` on `SMBDirEntry` which returns a named tuple `SMBDirEntryInformation` containing metadata already retrieved in the `scandir` operation.
* This avoid having to call `stat()` to retrieve data like the file attributes or datetime fields that is already available
* Ensure `DateTimeField` values are set to `UTC` timezones as FILETIME values are in UTC
* Stop using `datetime.datetime.utcfromtimestamp()` as it has been deprecated

## 1.12.0 - 2023-11-09

Expand Down
7 changes: 6 additions & 1 deletion src/smbprotocol/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,12 @@ def _parse_value(self, value):
elif isinstance(value, int):
time_microseconds = (value - self.EPOCH_FILETIME) // 10
try:
datetime_value = datetime.datetime(1970, 1, 1) + datetime.timedelta(microseconds=time_microseconds)
datetime_value = datetime.datetime(
year=1970,
month=1,
day=1,
tzinfo=datetime.timezone.utc,
) + datetime.timedelta(microseconds=time_microseconds)
except OverflowError:
# This is unfortunately but 9999 is the max value a datetime can be so we just default to that
datetime_value = datetime.datetime.max
Expand Down
38 changes: 33 additions & 5 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import os
import uuid
from datetime import datetime
from datetime import datetime, timezone

import pytest

Expand Down Expand Up @@ -675,10 +675,24 @@ def test_parse_message(self):
assert actual["max_read_size"].get_value() == 8388608
assert actual["max_write_size"].get_value() == 8388608
assert actual["system_time"].get_value() == datetime(
year=2017, month=11, day=16, hour=10, minute=6, second=17, microsecond=378946
year=2017,
month=11,
day=16,
hour=10,
minute=6,
second=17,
microsecond=378946,
tzinfo=timezone.utc,
)
assert actual["server_start_time"].get_value() == datetime(
year=2017, month=11, day=16, hour=10, minute=3, second=19, microsecond=927194
year=2017,
month=11,
day=16,
hour=10,
minute=3,
second=19,
microsecond=927194,
tzinfo=timezone.utc,
)
assert actual["security_buffer_offset"].get_value() == 128
assert actual["security_buffer_length"].get_value() == 120
Expand Down Expand Up @@ -743,10 +757,24 @@ def test_parse_message_3_1_1(self):
assert actual["max_read_size"].get_value() == 8388608
assert actual["max_write_size"].get_value() == 8388608
assert actual["system_time"].get_value() == datetime(
year=2017, month=11, day=15, hour=11, minute=32, second=12, microsecond=1616
year=2017,
month=11,
day=15,
hour=11,
minute=32,
second=12,
microsecond=1616,
tzinfo=timezone.utc,
)
assert actual["server_start_time"].get_value() == datetime(
year=2017, month=11, day=15, hour=11, minute=27, second=26, microsecond=349606
year=2017,
month=11,
day=15,
hour=11,
minute=27,
second=26,
microsecond=349606,
tzinfo=timezone.utc,
)
assert actual["security_buffer_offset"].get_value() == 128
assert actual["security_buffer_length"].get_value() == 120
Expand Down
10 changes: 5 additions & 5 deletions tests/test_create_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import re
import uuid
from datetime import datetime
from datetime import datetime, timezone

import pytest

Expand Down Expand Up @@ -450,7 +450,7 @@ def test_parse_message(self):
class TestSMB2CreateQueryMaximalAccessRequest:
def test_create_message(self):
message = SMB2CreateQueryMaximalAccessRequest()
message["timestamp"] = datetime.utcfromtimestamp(0)
message["timestamp"] = datetime.fromtimestamp(0, timezone.utc)
expected = b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
actual = message.pack()
assert len(message) == 8
Expand All @@ -462,7 +462,7 @@ def test_parse_message(self):
data = actual.unpack(data)
assert len(actual) == 8
assert data == b""
assert actual["timestamp"].get_value() == datetime.utcfromtimestamp(0)
assert actual["timestamp"].get_value() == datetime.fromtimestamp(0, timezone.utc)


class TestSMB2CreateQueryMaximalAccessResponse:
Expand Down Expand Up @@ -505,7 +505,7 @@ def test_parse_message(self):
class TestSMB2CreateTimewarpToken:
def test_create_message(self):
message = SMB2CreateTimewarpToken()
message["timestamp"] = datetime.utcfromtimestamp(0)
message["timestamp"] = datetime.fromtimestamp(0, timezone.utc)
expected = b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
actual = message.pack()
assert len(message) == 8
Expand All @@ -517,7 +517,7 @@ def test_parse_message(self):
data = actual.unpack(data)
assert len(actual) == 8
assert data == b""
assert actual["timestamp"].get_value() == datetime.utcfromtimestamp(0)
assert actual["timestamp"].get_value() == datetime.fromtimestamp(0, timezone.utc)


class TestSMB2CreateRequestLease:
Expand Down
86 changes: 43 additions & 43 deletions tests/test_file_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)

import uuid
from datetime import datetime
from datetime import datetime, timezone

from smbprotocol.file_info import (
FileAllInformation,
Expand Down Expand Up @@ -135,10 +135,10 @@ def test_parse_message(self):
class TestFileBothDirectoryInformation:
def test_create_message(self):
message = FileBothDirectoryInformation()
message["creation_time"] = datetime.utcfromtimestamp(1024)
message["last_access_time"] = datetime.utcfromtimestamp(1024)
message["last_write_time"] = datetime.utcfromtimestamp(1024)
message["change_time"] = datetime.utcfromtimestamp(1024)
message["creation_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_access_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_write_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["change_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["end_of_file"] = 4
message["allocation_size"] = 1048576
message["file_attributes"] = 32
Expand Down Expand Up @@ -197,10 +197,10 @@ def test_parse_message(self):
assert data == b""
assert actual["next_entry_offset"].get_value() == 0
assert actual["file_index"].get_value() == 0
assert actual["creation_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_access_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_write_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["change_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["creation_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_access_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_write_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["change_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["end_of_file"].get_value() == 4
assert actual["allocation_size"].get_value() == 1048576
assert actual["file_attributes"].get_value() == 32
Expand All @@ -216,10 +216,10 @@ def test_parse_message(self):
class TestFileDirectoryInformation:
def test_create_message(self):
message = FileDirectoryInformation()
message["creation_time"] = datetime.utcfromtimestamp(1024)
message["last_access_time"] = datetime.utcfromtimestamp(1024)
message["last_write_time"] = datetime.utcfromtimestamp(1024)
message["change_time"] = datetime.utcfromtimestamp(1024)
message["creation_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_access_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_write_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["change_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["end_of_file"] = 4
message["allocation_size"] = 1048576
message["file_attributes"] = 32
Expand Down Expand Up @@ -266,10 +266,10 @@ def test_parse_message(self):
assert data == b""
assert actual["next_entry_offset"].get_value() == 0
assert actual["file_index"].get_value() == 0
assert actual["creation_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_access_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_write_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["change_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["creation_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_access_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_write_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["change_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["end_of_file"].get_value() == 4
assert actual["allocation_size"].get_value() == 1048576
assert actual["file_attributes"].get_value() == 32
Expand Down Expand Up @@ -321,10 +321,10 @@ def test_parse_message(self):
class TestFileFullDirectoryInformation:
def test_create_message(self):
message = FileFullDirectoryInformation()
message["creation_time"] = datetime.utcfromtimestamp(1024)
message["last_access_time"] = datetime.utcfromtimestamp(1024)
message["last_write_time"] = datetime.utcfromtimestamp(1024)
message["change_time"] = datetime.utcfromtimestamp(1024)
message["creation_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_access_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_write_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["change_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["end_of_file"] = 4
message["allocation_size"] = 1048576
message["file_attributes"] = 32
Expand Down Expand Up @@ -373,10 +373,10 @@ def test_parse_message(self):
assert data == b""
assert actual["next_entry_offset"].get_value() == 0
assert actual["file_index"].get_value() == 0
assert actual["creation_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_access_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_write_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["change_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["creation_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_access_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_write_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["change_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["end_of_file"].get_value() == 4
assert actual["allocation_size"].get_value() == 1048576
assert actual["file_attributes"].get_value() == 32
Expand Down Expand Up @@ -442,10 +442,10 @@ def test_parse_message(self):
class TestFileIdBothDirectoryInformation:
def test_create_message(self):
message = FileIdBothDirectoryInformation()
message["creation_time"] = datetime.utcfromtimestamp(1024)
message["last_access_time"] = datetime.utcfromtimestamp(1024)
message["last_write_time"] = datetime.utcfromtimestamp(1024)
message["change_time"] = datetime.utcfromtimestamp(1024)
message["creation_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_access_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_write_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["change_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["end_of_file"] = 4
message["allocation_size"] = 1048576
message["file_attributes"] = 32
Expand Down Expand Up @@ -509,10 +509,10 @@ def test_parse_message(self):
assert data == b""
assert actual["next_entry_offset"].get_value() == 0
assert actual["file_index"].get_value() == 0
assert actual["creation_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_access_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_write_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["change_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["creation_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_access_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_write_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["change_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["end_of_file"].get_value() == 4
assert actual["allocation_size"].get_value() == 1048576
assert actual["file_attributes"].get_value() == 32
Expand All @@ -530,10 +530,10 @@ def test_parse_message(self):
class TestFileIdFullDirectoryInformation:
def test_create_message(self):
message = FileIdFullDirectoryInformation()
message["creation_time"] = datetime.utcfromtimestamp(1024)
message["last_access_time"] = datetime.utcfromtimestamp(1024)
message["last_write_time"] = datetime.utcfromtimestamp(1024)
message["change_time"] = datetime.utcfromtimestamp(1024)
message["creation_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_access_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["last_write_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["change_time"] = datetime.fromtimestamp(1024, timezone.utc)
message["end_of_file"] = 4
message["allocation_size"] = 1048576
message["file_attributes"] = 32
Expand Down Expand Up @@ -587,10 +587,10 @@ def test_parse_message(self):
assert data == b""
assert actual["next_entry_offset"].get_value() == 0
assert actual["file_index"].get_value() == 0
assert actual["creation_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_access_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["last_write_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["change_time"].get_value() == datetime.utcfromtimestamp(1024)
assert actual["creation_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_access_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["last_write_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["change_time"].get_value() == datetime.fromtimestamp(1024, timezone.utc)
assert actual["end_of_file"].get_value() == 4
assert actual["allocation_size"].get_value() == 1048576
assert actual["file_attributes"].get_value() == 32
Expand Down Expand Up @@ -782,7 +782,7 @@ class TestFileFsVolumeInformation:

def test_create_message(self):
message = FileFsVolumeInformation()
message["volume_creation_time"] = datetime.utcfromtimestamp(0)
message["volume_creation_time"] = datetime.fromtimestamp(0, timezone.utc)
message["volume_serial_number"] = 10
message["volume_label"] = "café"

Expand All @@ -797,7 +797,7 @@ def test_parse_message(self):
assert len(actual) == 26
assert data == b""

assert actual["volume_creation_time"].get_value() == datetime.utcfromtimestamp(0)
assert actual["volume_creation_time"].get_value() == datetime.fromtimestamp(0, timezone.utc)
assert actual["volume_serial_number"].get_value() == 10
assert actual["volume_label_length"].get_value() == 8
assert actual["supports_objects"].get_value() is False
Expand Down
Loading

0 comments on commit 37512ee

Please sign in to comment.