Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-smbprotocol for openSUSE:Factory checked in at 2026-04-04 19:07:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-smbprotocol (Old) and /work/SRC/openSUSE:Factory/.python-smbprotocol.new.21863 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-smbprotocol" Sat Apr 4 19:07:54 2026 rev:25 rq:1344560 version:1.16.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-smbprotocol/python-smbprotocol.changes 2026-02-10 21:14:44.129378090 +0100 +++ /work/SRC/openSUSE:Factory/.python-smbprotocol.new.21863/python-smbprotocol.changes 2026-04-04 19:09:49.903064263 +0200 @@ -1,0 +2,9 @@ +Thu Apr 2 06:17:39 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 1.16.1 + * Fix create_context offset when opening a dir/file. + * Fix Memory Leaks in Structure and Structure Subclasses. + * Fix copyfile fallback when server-side SMB copy is unsupported. + * Handle ECONNABORTED and avoid joining the current thread. + +------------------------------------------------------------------- Old: ---- python-smbprotocol-1.16.0.tar.gz New: ---- python-smbprotocol-1.16.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-smbprotocol.spec ++++++ --- /var/tmp/diff_new_pack.hZ6nAL/_old 2026-04-04 19:09:50.387084108 +0200 +++ /var/tmp/diff_new_pack.hZ6nAL/_new 2026-04-04 19:09:50.387084108 +0200 @@ -17,7 +17,7 @@ Name: python-smbprotocol -Version: 1.16.0 +Version: 1.16.1 Release: 0 Summary: SMBv2/v3 client for Python 2 and 3 License: MIT ++++++ python-smbprotocol-1.16.0.tar.gz -> python-smbprotocol-1.16.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/CHANGELOG.md new/smbprotocol-1.16.1/CHANGELOG.md --- old/smbprotocol-1.16.0/CHANGELOG.md 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/CHANGELOG.md 2026-04-02 04:49:28.000000000 +0200 @@ -1,5 +1,13 @@ # Changelog +## 1.16.1 - 2026-04-02 + +* Fix memory leaks in `Structure` and subclasses by converting lambda default values to static methods and using `weakref.proxy` to prevent circular references +* Fix `smbclient.shutil.copyfile` to fallback to client-side copy when server-side SMB copy is not supported (`STATUS_NOT_SUPPORTED`) +* Fix create context offset calculation when opening a file or directory +* Fix potential deadlock when worker thread attempts to disconnect and join itself +* Handle `ECONNABORTED` socket error during connection teardown + ## 1.16.0 - 2026-02-09 * Drop support for Python 3.8, minimum version is now 3.9 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/pyproject.toml new/smbprotocol-1.16.1/pyproject.toml --- old/smbprotocol-1.16.0/pyproject.toml 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/pyproject.toml 2026-04-02 04:49:28.000000000 +0200 @@ -6,7 +6,7 @@ [project] name = "smbprotocol" -version = "1.16.0" +version = "1.16.1" description = "Interact with a server using the SMB 2/3 Protocol" readme = "README.md" requires-python = ">=3.9" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbclient/shutil.py new/smbprotocol-1.16.1/src/smbclient/shutil.py --- old/smbprotocol-1.16.0/src/smbclient/shutil.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbclient/shutil.py 2026-04-02 04:49:28.000000000 +0200 @@ -29,7 +29,9 @@ ) from smbclient.path import isdir, islink, samefile from smbprotocol import MAX_PAYLOAD_SIZE +from smbprotocol.exceptions import SMBOSError from smbprotocol.file_info import FileAttributes, FileBasicInformation +from smbprotocol.header import NtStatus from smbprotocol.open import CreateOptions, FilePipePrinterAccessMask from smbprotocol.structure import DateTimeField @@ -184,8 +186,13 @@ if is_same: raise shutil.Error(f"'{src}' and '{dst}' are the same file, cannot copy") - smbclient_copyfile(src, dst, **kwargs) - return dst + # Attempt server side copy, if it fails fall back to copying the file in chunks using copyfileobj + try: + smbclient_copyfile(src, dst, **kwargs) + return dst + except SMBOSError as err: + if err.ntstatus != NtStatus.STATUS_NOT_SUPPORTED: + raise # Finally we are copying across different roots so we just chunk the data using copyfileobj with src_open(src, mode="rb", **src_open_kwargs) as src_fd, dst_open(dst, mode="wb", **dst_kwargs) as dst_fd: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/connection.py new/smbprotocol-1.16.1/src/smbprotocol/connection.py --- old/smbprotocol-1.16.0/src/smbprotocol/connection.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/connection.py 2026-04-02 04:49:28.000000000 +0200 @@ -241,7 +241,7 @@ "negotiate_context_offset", IntField( size=4, - default=lambda s: self._negotiate_context_offset_value(s), + default=SMB3NegotiateRequest._negotiate_context_offset_value, ), ), ( @@ -263,47 +263,55 @@ ( "padding", BytesField( - size=lambda s: self._padding_size(s), - default=lambda s: b"\x00" * self._padding_size(s), + size=SMB3NegotiateRequest._padding_size, + default=SMB3NegotiateRequest._default_padding_size, ), ), ( "negotiate_context_list", ListField( list_count=lambda s: s["negotiate_context_count"].get_value(), - unpack_func=lambda s, d: self._negotiate_context_list(s, d), + unpack_func=SMB3NegotiateRequest._negotiate_context_list, ), ), ] ) super().__init__() - def _negotiate_context_offset_value(self, structure): + @staticmethod + def _negotiate_context_offset_value(structure): # The offset from the beginning of the SMB2 header to the first, 8-byte # aligned, negotiate context header_size = 64 negotiate_size = structure["structure_size"].get_value() dialect_size = len(structure["dialects"]) - padding_size = self._padding_size(structure) + padding_size = SMB3NegotiateRequest._padding_size(structure) return header_size + negotiate_size + dialect_size + padding_size - def _padding_size(self, structure): + @staticmethod + def _padding_size(structure): # Padding between the end of the buffer value and the first Negotiate # context value so that the first value is 8-byte aligned. Padding is # 4 is there are no dialects specified mod = (structure["dialect_count"].get_value() * 2) % 8 return 0 if mod == 0 else mod - def _negotiate_context_list(self, structure, data): + @staticmethod + def _default_padding_size(structure): + return b"\x00" * SMB3NegotiateRequest._padding_size(structure) + + @staticmethod + def _negotiate_context_list(structure, data): context_count = structure["negotiate_context_count"].get_value() context_list = [] for idx in range(0, context_count): - field, data = self._parse_negotiate_context_entry(data) + field, data = SMB3NegotiateRequest._parse_negotiate_context_entry(data) context_list.append(field) return context_list - def _parse_negotiate_context_entry(self, data): + @staticmethod + def _parse_negotiate_context_entry(data): data_length = struct.unpack("<H", data[2:4])[0] negotiate_context = SMB2NegotiateContextRequest() negotiate_context.unpack(data[: data_length + 8]) @@ -345,7 +353,7 @@ "data", StructureField( size=lambda s: s["data_length"].get_value(), - structure_type=lambda s: self._data_structure_type(s), + structure_type=SMB2NegotiateContextRequest._data_structure_type, ), ), # not actually a field but each list entry must start at the 8 byte @@ -353,15 +361,16 @@ ( "padding", BytesField( - size=lambda s: self._padding_size(s), - default=lambda s: b"\x00" * self._padding_size(s), + size=SMB2NegotiateContextRequest._padding_size, + default=SMB2NegotiateContextRequest._default_padding_size, ), ), ] ) super().__init__() - def _data_structure_type(self, structure): + @staticmethod + def _data_structure_type(structure): con_type = structure["context_type"].get_value() if con_type == NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES: return SMB2PreauthIntegrityCapabilities @@ -372,10 +381,15 @@ elif con_type == NegotiateContextType.SMB2_SIGNING_CAPABILITIES: return SMB2SigningCapabilities - def _padding_size(self, structure): + @staticmethod + def _padding_size(structure): data_size = len(structure["data"]) return 8 - (data_size % 8 or 8) + @staticmethod + def _default_padding_size(structure): + return b"\x00" * SMB2NegotiateContextRequest._padding_size(structure) + class SMB2PreauthIntegrityCapabilities(Structure): """ @@ -548,7 +562,7 @@ "negotiate_context_count", IntField( size=2, - default=lambda s: self._negotiate_context_count_value(s), + default=SMB2NegotiateResponse._negotiate_context_count_value, ), ), ("server_guid", UuidField()), @@ -576,7 +590,7 @@ "negotiate_context_offset", IntField( size=4, - default=lambda s: self._negotiate_context_offset_value(s), + default=SMB2NegotiateResponse._negotiate_context_offset_value, ), ), ( @@ -588,22 +602,23 @@ ( "padding", BytesField( - size=lambda s: self._padding_size(s), - default=lambda s: b"\x00" * self._padding_size(s), + size=SMB2NegotiateResponse._padding_size, + default=SMB2NegotiateResponse._default_padding_size, ), ), ( "negotiate_context_list", ListField( list_count=lambda s: s["negotiate_context_count"].get_value(), - unpack_func=lambda s, d: self._negotiate_context_list(s, d), + unpack_func=SMB2NegotiateResponse._negotiate_context_list, ), ), ] ) super().__init__() - def _negotiate_context_count_value(self, structure): + @staticmethod + def _negotiate_context_count_value(structure): # If the dialect_revision is SMBv3.1.1, this field specifies the # number of negotiate contexts in negotiate_context_list; otherwise # this field must not be used and must be reserved (0). @@ -612,7 +627,8 @@ else: return None - def _negotiate_context_offset_value(self, structure): + @staticmethod + def _negotiate_context_offset_value(structure): # If the dialect_revision is SMBv3.1.1, this field specifies the offset # from the beginning of the SMB2 header to the first 8-byte # aligned negotiate context entry in negotiate_context_list; otherwise @@ -620,12 +636,13 @@ if structure["dialect_revision"].get_value() == Dialects.SMB_3_1_1: buffer_offset = structure["security_buffer_offset"].get_value() buffer_size = structure["security_buffer_length"].get_value() - padding_size = self._padding_size(structure) + padding_size = SMB2NegotiateResponse._padding_size(structure) return buffer_offset + buffer_size + padding_size else: return None - def _padding_size(self, structure): + @staticmethod + def _padding_size(structure): # Padding between the end of the buffer value and the first Negotiate # context value so that the first value is 8-byte aligned. Padding is # not required if there are not negotiate contexts @@ -635,16 +652,22 @@ mod = structure["security_buffer_length"].get_value() % 8 return 0 if mod == 0 else 8 - mod - def _negotiate_context_list(self, structure, data): + @staticmethod + def _default_padding_size(structure): + return b"\x00" * SMB2NegotiateResponse._padding_size(structure) + + @staticmethod + def _negotiate_context_list(structure, data): context_count = structure["negotiate_context_count"].get_value() context_list = [] for idx in range(0, context_count): - field, data = self._parse_negotiate_context_entry(data) + field, data = SMB2NegotiateResponse._parse_negotiate_context_entry(data) context_list.append(field) return context_list - def _parse_negotiate_context_entry(self, data): + @staticmethod + def _parse_negotiate_context_entry(data): data_length = struct.unpack("<H", data[2:4])[0] negotiate_context = SMB2NegotiateContextRequest() negotiate_context.unpack(data[: data_length + 8]) @@ -953,7 +976,7 @@ log.info("Disconnecting transport connection") self.transport.close() - if self._t_worker: + if self._t_worker and self._t_worker.ident != threading.get_ident(): self._t_worker.join(timeout=2) def send( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/create_contexts.py new/smbprotocol-1.16.1/src/smbprotocol/create_contexts.py --- old/smbprotocol-1.16.0/src/smbprotocol/create_contexts.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/create_contexts.py 2026-04-02 04:49:28.000000000 +0200 @@ -158,12 +158,15 @@ ("name_offset", IntField(size=2, default=16)), ("name_length", IntField(size=2, default=lambda s: len(s["buffer_name"]))), ("reserved", IntField(size=2)), - ("data_offset", IntField(size=2, default=lambda s: self._buffer_data_offset(s))), + ("data_offset", IntField(size=2, default=SMB2CreateContextRequest._buffer_data_offset)), ("data_length", IntField(size=4, default=lambda s: len(s["buffer_data"]))), ("buffer_name", BytesField(size=lambda s: s["name_length"].get_value())), ( "padding", - BytesField(size=lambda s: self._padding_size(s), default=lambda s: b"\x00" * self._padding_size(s)), + BytesField( + size=SMB2CreateContextRequest._padding_size, + default=SMB2CreateContextRequest._default_padding_size, + ), ), ("buffer_data", BytesField(size=lambda s: s["data_length"].get_value())), # not actually a field but each list entry must start at the 8 byte @@ -171,20 +174,23 @@ ( "padding2", BytesField( - size=lambda s: self._padding2_size(s), default=lambda s: b"\x00" * self._padding2_size(s) + size=SMB2CreateContextRequest._padding2_size, + default=SMB2CreateContextRequest._default_padding2_size, ), ), ] ) super().__init__() - def _buffer_data_offset(self, structure): + @staticmethod + def _buffer_data_offset(structure): if structure["data_length"].get_value() == 0: return 0 else: return structure["name_offset"].get_value() + len(structure["buffer_name"]) + len(structure["padding"]) - def _padding_size(self, structure): + @staticmethod + def _padding_size(structure): if structure["data_length"].get_value() == 0: return 0 @@ -192,11 +198,20 @@ mod = buffer_name_len % 8 return mod if mod == 0 else 8 - mod - def _padding2_size(self, structure): + @staticmethod + def _default_padding_size(structure): + return b"\x00" * SMB2CreateContextRequest._padding_size(structure) + + @staticmethod + def _padding2_size(structure): data_length = len(structure["buffer_name"]) + len(structure["padding"]) + len(structure["buffer_data"]) mod = data_length % 8 return mod if mod == 0 else 8 - mod + @staticmethod + def _default_padding2_size(structure): + return b"\x00" * SMB2CreateContextRequest._padding2_size(structure) + def get_context_data(self): """ Get the buffer_data value of a context response and try to convert it @@ -278,13 +293,14 @@ # alignment ( "padding", - BytesField(size=lambda s: self._padding_size(s), default=lambda s: b"\x00" * self._padding_size(s)), + BytesField(size=SMB2CreateEABuffer._padding_size, default=SMB2CreateEABuffer._default_padding_size), ), ] ) super().__init__() - def _padding_size(self, structure): + @staticmethod + def _padding_size(structure): if structure["next_entry_offset"].get_value() == 0: return 0 @@ -293,6 +309,10 @@ return mod if mod == 0 else 4 - mod @staticmethod + def _default_padding_size(structure): + return b"\x00" * SMB2CreateEABuffer._padding_size(structure) + + @staticmethod def pack_multiple(messages): """ Converts a list of SMB2CreateEABuffer structures and packs them as a diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/exceptions.py new/smbprotocol-1.16.1/src/smbprotocol/exceptions.py --- old/smbprotocol-1.16.0/src/smbprotocol/exceptions.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/exceptions.py 2026-04-02 04:49:28.000000000 +0200 @@ -696,14 +696,15 @@ size=lambda s: s["byte_count"].get_value(), list_count=lambda s: s["error_context_count"].get_value(), list_type=StructureField(structure_type=SMB2ErrorContextResponse), - unpack_func=lambda s, d: self._error_data_value(s, d), + unpack_func=SMB2ErrorResponse._error_data_value, ), ), ] ) super().__init__() - def _error_data_value(self, structure, data): + @staticmethod + def _error_data_value(structure, data): context_responses = [] while len(data) > 0: @@ -801,12 +802,13 @@ ("reserved", IntField(size=2)), # use the get/set_name functions to get/set these values as they # also (d)encode the text and set the length and offset accordingly - ("path_buffer", BytesField(size=lambda s: self._get_name_length(s, True))), + ("path_buffer", BytesField(size=SMB2SymbolicLinkErrorResponse._get_name_length)), ] ) super().__init__() - def _get_name_length(self, structure, first): + @staticmethod + def _get_name_length(structure, first=True): print_name_len = structure["print_name_length"].get_value() sub_name_len = structure["substitute_name_length"].get_value() return print_name_len + sub_name_len @@ -898,7 +900,7 @@ [ ("structure_size", IntField(size=4, default=lambda s: len(s))), ("notification_type", IntField(size=4, default=3)), - ("resource_name_offset", IntField(size=4, default=lambda s: self._resource_name_offset(s))), + ("resource_name_offset", IntField(size=4, default=SMB2ShareRedirectErrorContext._resource_name_offset)), ("resource_name_length", IntField(size=4, default=lambda s: len(s["resource_name"]))), ("flags", IntField(size=2, default=0)), ("target_type", IntField(size=2, default=0)), @@ -916,7 +918,8 @@ ) super().__init__() - def _resource_name_offset(self, structure): + @staticmethod + def _resource_name_offset(structure): min_structure_size = 24 addr_list_size = len(structure["ip_addr_move_list"]) return min_structure_size + addr_list_size @@ -935,29 +938,36 @@ [ ("type", EnumField(size=4, enum_type=IpAddrType)), ("reserved", IntField(size=4)), - ("ip_address", BytesField(size=lambda s: self._ip_address_size(s))), + ("ip_address", BytesField(size=SMB2MoveDstIpAddrStructure._ip_address_size)), ( "reserved2", BytesField( - size=lambda s: self._reserved2_size(s), default=lambda s: b"\x00" * self._reserved2_size(s) + size=SMB2MoveDstIpAddrStructure._reserved2_size, + default=SMB2MoveDstIpAddrStructure._default_reserved2_size, ), ), ] ) super().__init__() - def _ip_address_size(self, structure): + @staticmethod + def _ip_address_size(structure): if structure["type"].get_value() == IpAddrType.MOVE_DST_IPADDR_V4: return 4 else: return 16 - def _reserved2_size(self, structure): + @staticmethod + def _reserved2_size(structure): if structure["type"].get_value() == IpAddrType.MOVE_DST_IPADDR_V4: return 12 else: return 0 + @staticmethod + def _default_reserved2_size(structure): + return b"\x00" * SMB2MoveDstIpAddrStructure._reserved2_size(structure) + def get_ipaddress(self): # get's the IP address in a human readable format ip_address = self["ip_address"].get_value() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/ioctl.py new/smbprotocol-1.16.1/src/smbprotocol/ioctl.py --- old/smbprotocol-1.16.0/src/smbprotocol/ioctl.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/ioctl.py 2026-04-02 04:49:28.000000000 +0200 @@ -136,7 +136,7 @@ ), ), ("file_id", BytesField(size=16)), - ("input_offset", IntField(size=4, default=lambda s: self._buffer_offset_value(s))), + ("input_offset", IntField(size=4, default=SMB2IOCTLRequest._buffer_offset_value)), ( "input_count", IntField( @@ -145,7 +145,7 @@ ), ), ("max_input_response", IntField(size=4)), - ("output_offset", IntField(size=4, default=lambda s: self._buffer_offset_value(s))), + ("output_offset", IntField(size=4, default=SMB2IOCTLRequest._buffer_offset_value)), ("output_count", IntField(size=4, default=0)), ("max_output_response", IntField(size=4)), ( @@ -161,7 +161,8 @@ ) super().__init__() - def _buffer_offset_value(self, structure): + @staticmethod + def _buffer_offset_value(structure): # The offset from the beginning of the SMB2 header to the value of the # buffer, 0 if no buffer is set if len(structure["buffer"]) > 0: @@ -497,39 +498,46 @@ ( "buffer", StructureField( - size=lambda s: self._get_buffer_size(s), - structure_type=lambda s: self._get_buffer_structure_type(s), + size=SockAddrStorage._get_buffer_size, + structure_type=SockAddrStorage._get_buffer_structure_type, ), ), ( "reserved", BytesField( - size=lambda s: self._get_reserved_size(s), - default=lambda s: b"\x00" * self._get_reserved_size(s), + size=SockAddrStorage._get_reserved_size, + default=SockAddrStorage._default_get_reserved_size, ), ), ] ) super().__init__() - def _get_buffer_size(self, structure): + @staticmethod + def _get_buffer_size(structure): if structure["family"].get_value() == SockAddrFamily.INTER_NETWORK: return 14 else: return 26 - def _get_buffer_structure_type(self, structure): + @staticmethod + def _get_buffer_structure_type(structure): if structure["family"].get_value() == SockAddrFamily.INTER_NETWORK: return SockAddrIn else: return SockAddrIn6 - def _get_reserved_size(self, structure): + @staticmethod + def _get_reserved_size(structure): if structure["family"].get_value() == SockAddrFamily.INTER_NETWORK: return 112 else: return 100 + @staticmethod + def _default_get_reserved_size(structure): + return b"\x00" * SockAddrStorage._get_reserved_size(structure) + class SockAddrIn(Structure): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/open.py new/smbprotocol-1.16.1/src/smbprotocol/open.py --- old/smbprotocol-1.16.0/src/smbprotocol/open.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/open.py 2026-04-02 04:49:28.000000000 +0200 @@ -349,56 +349,65 @@ ("create_disposition", EnumField(size=4, enum_type=CreateDisposition)), ("create_options", FlagField(size=4, flag_type=CreateOptions)), ("name_offset", IntField(size=2, default=120)), # (header size 64) + (structure size 56) - ("name_length", IntField(size=2, default=lambda s: self._name_length(s))), - ("create_contexts_offset", IntField(size=4, default=lambda s: self._create_contexts_offset(s))), + ("name_length", IntField(size=2, default=SMB2CreateRequest._name_length)), + ("create_contexts_offset", IntField(size=4, default=SMB2CreateRequest._create_contexts_offset)), ("create_contexts_length", IntField(size=4, default=lambda s: len(s["buffer_contexts"]))), # Technically these are all under buffer but we split it to make # things easier ( "buffer_path", BytesField( - size=lambda s: self._buffer_path_size(s), + size=SMB2CreateRequest._buffer_path_size, ), ), ( "padding", - BytesField(size=lambda s: self._padding_size(s), default=lambda s: b"\x00" * self._padding_size(s)), + BytesField(size=SMB2CreateRequest._padding_size, default=SMB2CreateRequest._default_padding_size), ), ( "buffer_contexts", ListField( size=lambda s: s["create_contexts_length"].get_value(), list_type=StructureField(structure_type=create_con_req), - unpack_func=lambda s, d: self._buffer_context_list(s, d), + unpack_func=SMB2CreateRequest._buffer_context_list, ), ), ] ) super().__init__() - def _name_length(self, structure): + @staticmethod + def _name_length(structure): buffer_path = structure["buffer_path"].get_value() return len(buffer_path) if buffer_path != b"\x00\x00" else 0 - def _create_contexts_offset(self, structure): + @staticmethod + def _create_contexts_offset(structure): if len(structure["buffer_contexts"]) == 0: return 0 else: return structure["name_offset"].get_value() + len(structure["padding"]) + len(structure["buffer_path"]) - def _buffer_path_size(self, structure): + @staticmethod + def _buffer_path_size(structure): name_length = structure["name_length"].get_value() return name_length if name_length != 0 else 2 - def _padding_size(self, structure): + @staticmethod + def _padding_size(structure): # no padding is needed if there are no contexts if structure["create_contexts_length"].get_value() == 0: return 0 - mod = structure["name_length"].get_value() % 8 + mod = len(structure["buffer_path"]) % 8 return 0 if mod == 0 else 8 - mod - def _buffer_context_list(self, structure, data): + @staticmethod + def _default_padding_size(structure): + return b"\x00" * SMB2CreateRequest._padding_size(structure) + + @staticmethod + def _buffer_context_list(structure, data): context_list = [] last_context = data == b"" while not last_context: @@ -445,27 +454,29 @@ ), ("reserved2", IntField(size=4)), ("file_id", BytesField(size=16)), - ("create_contexts_offset", IntField(size=4, default=lambda s: self._create_contexts_offset(s))), + ("create_contexts_offset", IntField(size=4, default=SMB2CreateResponse._create_contexts_offset)), ("create_contexts_length", IntField(size=4, default=lambda s: len(s["buffer"]))), ( "buffer", ListField( size=lambda s: s["create_contexts_length"].get_value(), list_type=StructureField(structure_type=create_con_req), - unpack_func=lambda s, d: self._buffer_context_list(s, d), + unpack_func=SMB2CreateResponse._buffer_context_list, ), ), ] ) super().__init__() - def _create_contexts_offset(self, structure): + @staticmethod + def _create_contexts_offset(structure): if len(structure["buffer"]) == 0: return 0 else: return 152 - def _buffer_context_list(self, structure, data): + @staticmethod + def _buffer_context_list(structure, data): context_list = [] last_context = data == b"" while not last_context: @@ -596,26 +607,29 @@ ("minimum_count", IntField(size=4)), ("channel", FlagField(size=4, flag_type=ReadWriteChannel)), ("remaining_bytes", IntField(size=4)), - ("read_channel_info_offset", IntField(size=2, default=lambda s: self._get_read_channel_info_offset(s))), - ("read_channel_info_length", IntField(size=2, default=lambda s: self._get_read_channel_info_length(s))), - ("buffer", BytesField(size=lambda s: self._get_buffer_length(s), default=b"\x00")), + ("read_channel_info_offset", IntField(size=2, default=SMB2ReadRequest._get_read_channel_info_offset)), + ("read_channel_info_length", IntField(size=2, default=SMB2ReadRequest._get_read_channel_info_length)), + ("buffer", BytesField(size=SMB2ReadRequest._get_buffer_length, default=b"\x00")), ] ) super().__init__() - def _get_read_channel_info_offset(self, structure): + @staticmethod + def _get_read_channel_info_offset(structure): if structure["channel"].get_value() == 0: return 0 else: return 64 + structure["structure_size"].get_value() - 1 - def _get_read_channel_info_length(self, structure): + @staticmethod + def _get_read_channel_info_length(structure): if structure["channel"].get_value() == 0: return 0 else: return len(structure["buffer"].get_value()) - def _get_buffer_length(self, structure): + @staticmethod + def _get_buffer_length(structure): # buffer should contain 1 byte of \x00 and not be empty if structure["channel"].get_value() == 0: return 1 @@ -673,7 +687,7 @@ ("remaining_bytes", IntField(size=4)), ( "write_channel_info_offset", - IntField(size=2, default=lambda s: self._get_write_channel_info_offset(s)), + IntField(size=2, default=SMB2WriteRequest._get_write_channel_info_offset), ), ("write_channel_info_length", IntField(size=2, default=lambda s: len(s["buffer_channel_info"]))), ("flags", FlagField(size=4, flag_type=WriteFlags)), @@ -683,7 +697,8 @@ ) super().__init__() - def _get_write_channel_info_offset(self, structure): + @staticmethod + def _get_write_channel_info_offset(structure): if len(structure["buffer_channel_info"]) == 0: return 0 else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/security_descriptor.py new/smbprotocol-1.16.1/src/smbprotocol/security_descriptor.py --- old/smbprotocol-1.16.0/src/smbprotocol/security_descriptor.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/security_descriptor.py 2026-04-02 04:49:28.000000000 +0200 @@ -271,14 +271,15 @@ "aces", ListField( list_count=lambda s: s["ace_count"].get_value(), - unpack_func=lambda s, d: self._unpack_aces(s, d), + unpack_func=AclPacket._unpack_aces, ), ), ] ) super().__init__() - def _unpack_aces(self, structure, data): + @staticmethod + def _unpack_aces(structure, data): aces = [] while data != b"" and len(aces) < structure["ace_count"].value: ace_type = struct.unpack("<B", data[:1])[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/structure.py new/smbprotocol-1.16.1/src/smbprotocol/structure.py --- old/smbprotocol-1.16.0/src/smbprotocol/structure.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/structure.py 2026-04-02 04:49:28.000000000 +0200 @@ -9,6 +9,7 @@ import textwrap import types import uuid +import weakref from abc import ABCMeta, abstractmethod from binascii import hexlify @@ -58,7 +59,7 @@ # relies on the full structure (self) being available and error # messages use the field name to be helpful for name, field in self.fields.items(): - field.structure = self + field.structure = weakref.proxy(self) field.name = name field.set_value(field.default) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/src/smbprotocol/transport.py new/smbprotocol-1.16.1/src/smbprotocol/transport.py --- old/smbprotocol-1.16.0/src/smbprotocol/transport.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/src/smbprotocol/transport.py 2026-04-02 04:49:28.000000000 +0200 @@ -145,7 +145,7 @@ except OSError as e: # Windows will raise this error if the socket has been shutdown, Linux return returns an empty byte # string so we just replicate that. - if e.errno not in [errno.ESHUTDOWN, errno.ECONNRESET]: + if e.errno not in [errno.ESHUTDOWN, errno.ECONNRESET, errno.ECONNABORTED]: # Avoid collecting coverage here to avoid CI failing due to race condition differences raise # pragma: no cover b_data = b"" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/smbprotocol-1.16.0/tests/test_smbclient_shutil.py new/smbprotocol-1.16.1/tests/test_smbclient_shutil.py --- old/smbprotocol-1.16.0/tests/test_smbclient_shutil.py 2026-02-09 01:47:32.000000000 +0100 +++ new/smbprotocol-1.16.1/tests/test_smbclient_shutil.py 2026-04-02 04:49:28.000000000 +0200 @@ -9,6 +9,7 @@ import shutil import stat import sys +from unittest.mock import patch import pytest @@ -30,6 +31,7 @@ ) from smbprotocol.exceptions import SMBOSError from smbprotocol.file_info import FileBasicInformation +from smbprotocol.header import NtStatus from smbprotocol.open import CreateOptions, FileAttributes, FilePipePrinterAccessMask if os.name == "nt": @@ -419,6 +421,32 @@ copyfile(src_filename, "/tmp", follow_symlinks=False) +def test_copyfile_falls_back_to_copyfile_obj_when_server_side_copy_not_supported(smb_share): + src_filename = "%s\\source.txt" % smb_share + dst_filename = "%s\\target.txt" % smb_share + + with open_file(src_filename, mode="w") as fd: + fd.write("content") + + with patch("smbclient.shutil.smbclient_copyfile") as mock_copy: + + def raise_not_supported(*args, **kwargs): + raise SMBOSError( + NtStatus.STATUS_NOT_SUPPORTED, + src_filename, + ) + + mock_copy.side_effect = raise_not_supported + actual = copyfile(src=src_filename, dst=dst_filename) + + # Check that initially the copyfile was attempted and then the fallback was used. + assert mock_copy.call_count == 1 + assert actual == dst_filename + + with open_file(dst_filename) as fd: + assert fd.read() == "content" + + def test_copymode_of_file(smb_share): src_filename = "%s\\source.txt" % smb_share dst_filename = "%s\\target.txt" % smb_share
