Skip to content

Commit

Permalink
Add smbclient.liststreams to enumerate ADS streams
Browse files Browse the repository at this point in the history
  • Loading branch information
mon committed Oct 14, 2024
1 parent 42804ca commit b32e586
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/smbclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
getxattr,
link,
listdir,
liststreams,
listxattr,
lstat,
makedirs,
Expand Down
31 changes: 31 additions & 0 deletions src/smbclient/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
FileLinkInformation,
FileRenameInformation,
FileStandardInformation,
FileStreamInformation,
)
from smbprotocol.header import NtStatus
from smbprotocol.ioctl import (
Expand Down Expand Up @@ -1019,6 +1020,36 @@ def setxattr(path, attribute, value, flags=0, follow_symlinks=True, **kwargs):
set_info(transaction, ea_info)


def liststreams(path, follow_symlinks=True, **kwargs) -> list[str]:
"""
Return a list of the alternative data streams on a path. Listed streams can
be opened by appending their name to the original path. An example call for
a file with a single extra stream may return:
```
[":extra_stream:$DATA", "::$DATA"]
```
:param path: The full UNC path to the file to get the list of streams for.
:param follow_symlinks: Whether to follow the symlink at path if encountered.
:param kwargs: Common SMB Session arguments for smbclient.
:return: List of streams on the file with each entry being a string.
"""

raw = SMBRawIO(

Check warning on line 1039 in src/smbclient/_os.py

View check run for this annotation

Codecov / codecov/patch

src/smbclient/_os.py#L1039

Added line #L1039 was not covered by tests
path,
mode="r",
share_access="r",
create_options=0 if follow_symlinks else CreateOptions.FILE_OPEN_REPARSE_POINT,
**kwargs,
)

with SMBFileTransaction(raw) as transaction:
query_info(transaction, FileStreamInformation, output_buffer_length=MAX_PAYLOAD_SIZE)

Check warning on line 1048 in src/smbclient/_os.py

View check run for this annotation

Codecov / codecov/patch

src/smbclient/_os.py#L1047-L1048

Added lines #L1047 - L1048 were not covered by tests

return [s["stream_name"].get_value() for s in transaction.results[0]]

Check warning on line 1050 in src/smbclient/_os.py

View check run for this annotation

Codecov / codecov/patch

src/smbclient/_os.py#L1050

Added line #L1050 was not covered by tests


def _delete(raw_type, path, **kwargs):
# Ensures we delete the symlink (if present) and don't follow it down.
co = CreateOptions.FILE_OPEN_REPARSE_POINT
Expand Down
5 changes: 1 addition & 4 deletions tests/test_smbclient_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,10 +834,7 @@ def test_open_file_with_ads(smb_share):

assert smbclient.listdir(smb_share) == ["file.txt"]

with smbclient.open_file(filename, buffering=0, mode="rb") as fd, SMBFileTransaction(fd) as trans:
query_info(trans, FileStreamInformation, output_buffer_length=1024)

actual = sorted([s["stream_name"].get_value() for s in trans.results[0]])
actual = sorted(smbclient.liststreams(filename))
assert actual == ["::$DATA", ":ads:$DATA"]


Expand Down

0 comments on commit b32e586

Please sign in to comment.