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 2021-05-17 18:45:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-smbprotocol (Old)
 and      /work/SRC/openSUSE:Factory/.python-smbprotocol.new.2988 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-smbprotocol"

Mon May 17 18:45:00 2021 rev:11 rq:893035 version:1.5.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-smbprotocol/python-smbprotocol.changes    
2021-03-25 14:53:03.828530529 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-smbprotocol.new.2988/python-smbprotocol.changes
  2021-05-17 18:45:14.408642587 +0200
@@ -1,0 +2,13 @@
+Thu May 13 16:29:17 UTC 2021 - Martin Hauke <mar...@gmx.de>
+
+- Update to version 1.5.1
+  * Unified DFS path handling when using any API that uses a
+    transaction to open the file.
+    - This includes smbclient.rename and smbclient.replace
+  * Fixed up smbclient.rename to work with directories.
+  * smbclient.scandir will continue to use the connection cache
+    when getting stat information of a dir entry.
+  * smbclient.shutil.rmtree will continue to use the connection
+    cache when removing child entries.
+
+-------------------------------------------------------------------

Old:
----
  python-smbprotocol-1.5.0.tar.gz

New:
----
  python-smbprotocol-1.5.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-smbprotocol.spec ++++++
--- /var/tmp/diff_new_pack.Xe7pRC/_old  2021-05-17 18:45:15.136639498 +0200
+++ /var/tmp/diff_new_pack.Xe7pRC/_new  2021-05-17 18:45:15.140639481 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-smbprotocol
-Version:        1.5.0
+Version:        1.5.1
 Release:        0
 Summary:        SMBv2/v3 client for Python 2 and 3
 License:        MIT

++++++ python-smbprotocol-1.5.0.tar.gz -> python-smbprotocol-1.5.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/CHANGELOG.md 
new/smbprotocol-1.5.1/CHANGELOG.md
--- old/smbprotocol-1.5.0/CHANGELOG.md  2021-03-25 06:15:16.000000000 +0100
+++ new/smbprotocol-1.5.1/CHANGELOG.md  2021-05-08 01:01:14.000000000 +0200
@@ -1,5 +1,14 @@
 # Changelog
 
+## 1.5.1 - 2021-05-08
+
+* Unified DFS path handling when using any API that uses a transaction to open 
the file
+  * This includes `smbclient.rename` and `smbclient.replace`
+* Fixed up `smbclient.rename` to work with directories
+* `smbclient.scandir` will continue to use the connection cache when getting 
stat information of a dir entry
+* `smbclient.shutil.rmtree` will continue to use the connection cache when 
removing child entries
+
+
 ## 1.5.0 - 2021-03-25
 
 * Added `smbprotocol.exceptions.SMBConnectionClosed` that is raised when 
trying to send or receive data on a connection that has been closed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/setup.py 
new/smbprotocol-1.5.1/setup.py
--- old/smbprotocol-1.5.0/setup.py      2021-03-25 06:15:16.000000000 +0100
+++ new/smbprotocol-1.5.1/setup.py      2021-05-08 01:01:14.000000000 +0200
@@ -18,7 +18,7 @@
 
 setup(
     name='smbprotocol',
-    version='1.5.0',
+    version='1.5.1',
     packages=['smbclient', 'smbprotocol'],
     install_requires=[
         'cryptography>=2.0',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/smbclient/_io.py 
new/smbprotocol-1.5.1/smbclient/_io.py
--- old/smbprotocol-1.5.0/smbclient/_io.py      2021-03-25 06:15:16.000000000 
+0100
+++ new/smbprotocol-1.5.1/smbclient/_io.py      2021-05-08 01:01:14.000000000 
+0200
@@ -45,6 +45,7 @@
     Open,
     QueryDirectoryFlags,
     ShareAccess,
+    SMB2CreateRequest,
     SMB2QueryInfoRequest,
     SMB2QueryInfoResponse,
     SMB2SetInfoRequest,
@@ -136,6 +137,40 @@
     return chunk_size, int(credit_request)
 
 
+def _resolve_dfs(raw_io):
+    """
+    Resolves a DFS path for a failed Open request.
+
+    :param raw_io: The SMBRawIO to resolve the DFS path for.
+    :return: A new Open for each DFS target that was resolved.
+    """
+    if not raw_io.fd.tree_connect.is_dfs_share:
+        return
+
+    # Path is on a DFS root that is linked to another server.
+    client_config = ClientConfig()
+    raw_path = raw_io.name
+
+    referral = dfs_request(raw_io.fd.tree_connect, raw_path[1:])
+    client_config.cache_referral(referral)
+    info = client_config.lookup_referral([p for p in raw_path.split("\\") if 
p])
+    connection_kwargs = getattr(raw_io, '_%s__kwargs' % type(raw_io).__name__, 
{})
+
+    for target in info:
+        new_path = raw_path.replace(info.dfs_path, target.target_path, 1)
+
+        try:
+            tree, fd_path = get_smb_tree(new_path, **connection_kwargs)
+
+        except SMBResponseException as link_exc:
+            log.warning("Failed to connect to DFS link target %s: %s" % 
(str(target), link_exc))
+            continue
+
+        # Record the target that worked for future reference.
+        info.target_hint = target
+        yield Open(tree, fd_path)
+
+
 def ioctl_request(transaction, ctl_code, output_size=0, 
flags=IOCTLFlags.SMB2_0_IOCTL_IS_IOCTL, input_buffer=b""):
     """
     Sends an IOCTL request to the server.
@@ -240,17 +275,21 @@
         """
         Sends a compound request to the server. Optionally opens and closes a 
handle in the same request if the handle
         is not already opened.
-
-        :return: A list of each response for the requests set on the 
transaction.
         """
         remove_index = []
-        if self.raw.closed:
+        if (
+                self.raw.closed and
+                (
+                        not self._actions or
+                        not isinstance(self._actions[0][0], SMB2CreateRequest)
+                )
+        ):
             self.raw.open(transaction=self)
             self._actions.insert(0, self._actions.pop(-1))  # Need to move the 
create to the start
-            remove_index.append(0)
+            remove_index.insert(0, 0)
 
             self.raw.close(transaction=self)
-            remove_index.append(len(self._actions) - 1)
+            remove_index.insert(0, len(self._actions) - 1)
 
         send_msgs = []
         unpack_functions = []
@@ -266,19 +305,61 @@
         # just enumerate the list in 1 line in case it throws an exception.
         failures = []
         responses = []
+        try_again = False
         for idx, func in enumerate(unpack_functions):
+            res = None
+
             try:
-                responses.append(func(requests[idx]))
+                try:
+                    res = func(requests[idx])
+
+                except (PathNotCovered, ObjectNameNotFound, 
ObjectPathNotFound):
+                    # The MS-DFSC docs state that STATUS_PATH_NOT_COVERED is 
used when encountering a link to a
+                    # different server but Samba seems to return the generic 
name or path not found.
+
+                    # If the first action is not a CreateRequest then we can't 
resolve the DFS path in the transaction.
+                    if not (idx == 0 and isinstance(send_msgs[0], 
SMB2CreateRequest)):
+                        raise
+
+                    for smb_open in _resolve_dfs(self.raw):
+                        if smb_open.tree_connect.share_name == 
self.raw.fd.tree_connect.share_name:
+                            continue
+
+                        self.raw.fd = smb_open
+
+                        # In case this is a transaction with an explicit open 
we want to reopen it with the new params
+                        # before trying it again.
+                        self.raw.open(transaction=self)
+                        self._actions[0] = self._actions.pop(-1)
+
+                        try_again = True
+                        break
+
+                    else:
+                        # Either there wasn't any DFS referrals or none of 
them worked, just reraise the error.
+                        raise
+
             except SMBResponseException as exc:
                 failures.append(SMBOSError(exc.status, self.raw.name))
 
-        # If there was a failure, just raise the first exception found.
-        if failures:
+            finally:
+                responses.append(res)
+
+        # Remove the existing open/close actions this transaction added 
internally.
+        for idx in remove_index:
+            del self._actions[idx]
+            del responses[idx]
+
+        if try_again:
+            # If we updated the SMB Tree due to a DFS referral hit then try 
again.
+            self.commit()
+
+        elif failures:
+            # If there was a failure, just raise the first exception found.
             raise failures[0]
 
-        remove_index.sort(reverse=True)  # Need to remove from highest index 
first.
-        [responses.pop(i) for i in remove_index]  # Remove the open and close 
response if commit() did that work.
-        self.results = tuple(responses)
+        else:
+            self.results = tuple(responses)
 
 
 class SMBRawIO(io.RawIOBase):
@@ -358,63 +439,32 @@
         if not self.closed:
             return
 
+        open_at_end = False
+        if not transaction:
+            # The SMBFileTransaction has special logic that deals with DFS 
paths, use that to do the actual open.
+            open_at_end = True
+            transaction = SMBFileTransaction(self)
+
         share_access = _parse_share_access(self.share_access, self.mode)
         create_disposition = _parse_mode(self.mode, invalid=self._INVALID_MODE)
 
-        try:
-            # 
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wpo/feeb3122-cfe0-4b34-821d-e31c036d763c
-            # Impersonation on SMB has little meaning when opening files but 
is important if using RPC so set to a sane
-            # default of Impersonation.
-            open_result = self.fd.create(
-                ImpersonationLevel.Impersonation,
-                self._desired_access,
-                self._file_attributes,
-                share_access,
-                create_disposition,
-                self._create_options,
-                send=(transaction is None),
-            )
-        except (PathNotCovered, ObjectNameNotFound, ObjectPathNotFound) as exc:
-            # The MS-DFSC docs status that STATUS_PATH_NOT_COVERED is used 
when encountering a link to a different
-            # server but Samba seems to return the generic name or path not 
found.
-            if not self.fd.tree_connect.is_dfs_share:
-                raise SMBOSError(exc.status, self.name)
-
-            # Path is on a DFS root that is linked to another server.
-            client_config = ClientConfig()
-            referral = dfs_request(self.fd.tree_connect, self.name[1:])
-            client_config.cache_referral(referral)
-            info = client_config.lookup_referral([p for p in 
self.name.split("\\") if p])
-
-            for target in info:
-                new_path = self.name.replace(info.dfs_path, 
target.target_path, 1)
-
-                try:
-                    tree, fd_path = get_smb_tree(new_path, **self.__kwargs)
-                    self.fd = Open(tree, fd_path)
-                    self.open(transaction=transaction)
-
-                except SMBResponseException as link_exc:
-                    log.warning("Failed to connect to DFS link target %s: %s" 
% (str(target), link_exc))
-
-                else:
-                    # Record the target that worked for future reference.
-                    info.target_hint = target
-                    break
-
-            else:
-                # None of the targets worked so raise the original error.
-                raise SMBOSError(exc.status, self.name)
-
-            return
-
-        except SMBResponseException as exc:
-            raise SMBOSError(exc.status, self.name)
-
-        if transaction is not None:
-            transaction += open_result
-        elif 'a' in self.mode and self.FILE_TYPE != 'pipe':
-            self._offset = self.fd.end_of_file
+        # 
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wpo/feeb3122-cfe0-4b34-821d-e31c036d763c
+        # Impersonation on SMB has little meaning when opening files but is 
important if using RPC so set to a sane
+        # default of Impersonation.
+        transaction += self.fd.create(
+            ImpersonationLevel.Impersonation,
+            self._desired_access,
+            self._file_attributes,
+            share_access,
+            create_disposition,
+            self._create_options,
+            send=False,
+        )
+
+        if open_at_end:
+            transaction.commit()
+            if 'a' in self.mode and self.FILE_TYPE != 'pipe':
+                self._offset = self.fd.end_of_file
 
     def readable(self):
         """ True if file was opened in a read mode. """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/smbclient/_os.py 
new/smbprotocol-1.5.1/smbclient/_os.py
--- old/smbprotocol-1.5.0/smbclient/_os.py      2021-03-25 06:15:16.000000000 
+0100
+++ new/smbprotocol-1.5.1/smbclient/_os.py      2021-05-08 01:01:14.000000000 
+0200
@@ -121,6 +121,16 @@
 ])
 
 
+def is_remote_path(path):  # type: (str) -> bool
+    """
+    Returns True iff the given path is a remote SMB path (rather than a local 
path).
+
+    :param path: The filepath.
+    :return: True iff the given path is a remote SMB path.
+    """
+    return path.startswith('\\\\')
+
+
 def copyfile(src, dst, **kwargs):
     """
     Copy a file to a different location on the same server share. This will 
fail if the src and dst paths are to a
@@ -136,10 +146,10 @@
     norm_src = ntpath.normpath(src)
     norm_dst = ntpath.normpath(dst)
 
-    if not norm_src.startswith('\\\\'):
+    if not is_remote_path(norm_src):
         raise ValueError("src must be an absolute path to where the file 
should be copied from.")
 
-    if not norm_dst.startswith('\\\\'):
+    if not is_remote_path(norm_dst):
         raise ValueError("dst must be an absolute path to where the file 
should be copied to.")
 
     src_root = ntpath.splitdrive(norm_src)[0]
@@ -200,7 +210,7 @@
     norm_src = ntpath.normpath(src)
     norm_dst = ntpath.normpath(dst)
 
-    if not norm_src.startswith('\\\\'):
+    if not is_remote_path(norm_src):
         raise ValueError("src must be the absolute path to where the file is 
hard linked to.")
 
     src_root = ntpath.splitdrive(norm_src)[0]
@@ -516,13 +526,15 @@
     :param kwargs: Common SMB Session arguments for smbclient.
     :return: An iterator of DirEntry objects in the directory.
     """
+    connection_cache = kwargs.get('connection_cache', None)
     with SMBDirectoryIO(path, share_access='rwd', **kwargs) as fd:
         for dir_info in fd.query_directory(search_pattern, 
FileInformationClass.FILE_ID_FULL_DIRECTORY_INFORMATION):
             filename = dir_info['file_name'].get_value().decode('utf-16-le')
             if filename in [u'.', u'..']:
                 continue
 
-            dir_entry = SMBDirEntry(SMBRawIO(u"%s\\%s" % (path, filename), 
**kwargs), dir_info)
+            dir_entry = SMBDirEntry(SMBRawIO(u"%s\\%s" % (path, filename), 
**kwargs), dir_info,
+                                    connection_cache=connection_cache)
             yield dir_entry
 
 
@@ -648,13 +660,13 @@
     :param kwargs: Common SMB Session arguments for smbclient.
     """
     norm_dst = ntpath.normpath(dst)
-    if not norm_dst.startswith('\\\\'):
+    if not is_remote_path(norm_dst):
         raise ValueError("The link dst must be an absolute UNC path for where 
the link is to be created")
 
     norm_src = ntpath.normpath(src)
     print_name = norm_src
 
-    if not norm_src.startswith('\\\\'):
+    if not is_remote_path(norm_src):
         flags = SymbolicLinkFlags.SYMLINK_FLAG_RELATIVE
         substitute_name = norm_src
         dst_dir = ntpath.dirname(norm_dst)
@@ -1027,24 +1039,43 @@
 
 def _rename_information(src, dst, replace_if_exists=False, **kwargs):
     verb = 'replace' if replace_if_exists else 'rename'
-    norm_src = ntpath.normpath(src)
-    norm_dst = ntpath.normpath(dst)
-
-    if not norm_dst.startswith('\\\\'):
+    if not is_remote_path(ntpath.normpath(dst)):
         raise ValueError("dst must be an absolute path to where the file or 
directory should be %sd." % verb)
 
-    src_root = ntpath.splitdrive(norm_src)[0]
-    dst_root, dst_name = ntpath.splitdrive(norm_dst)
-    if src_root.lower() != dst_root.lower():
-        raise ValueError("Cannot %s a file to a different root than the src." 
% verb)
+    raw_args = dict(kwargs)
+    raw_args.update({
+        'mode': 'r',
+        'share_access': 'rwd',
+        'create_options': CreateOptions.FILE_OPEN_REPARSE_POINT,
+    })
+
+    # We open/close the dest (ignoring if the file does not exist) so we can 
resolve the DFS path and determine the
+    # filename src needs to be renamed to.
+    dst_raw = SMBRawIO(dst, 
desired_access=FilePipePrinterAccessMask.FILE_EXECUTE, **raw_args)
+    try:
+        SMBFileTransaction(dst_raw).commit()
+    except SMBOSError as err:
+        if err.errno != errno.ENOENT:
+            raise
 
-    raw = SMBRawIO(src, mode='r', share_access='rwd', 
desired_access=FilePipePrinterAccessMask.DELETE,
-                   create_options=CreateOptions.FILE_OPEN_REPARSE_POINT, 
**kwargs)
-    with SMBFileTransaction(raw) as transaction:
-        file_rename = FileRenameInformation()
-        file_rename['replace_if_exists'] = replace_if_exists
-        file_rename['file_name'] = to_text(dst_name[1:])  # dst_name has \ 
prefix from splitdrive, we remove that.
-        set_info(transaction, file_rename)
+    # We need to open source first so we can get the resolved tree to compare 
the volumes
+    with SMBRawIO(src, desired_access=FilePipePrinterAccessMask.DELETE, 
**raw_args) as src_raw:
+        # We compare the server part using the GUID in case a different alias 
was specified for the server. The GUID
+        # should uniquely identify the server for our cases here.
+        src_guid = src_raw.fd.connection.server_guid
+        src_share = 
ntpath.normpath(src_raw.fd.tree_connect.share_name).split("\\")[-1]
+
+        dst_guid = dst_raw.fd.connection.server_guid
+        dst_share = 
ntpath.normpath(dst_raw.fd.tree_connect.share_name).split("\\")[-1]
+
+        if src_guid != dst_guid or src_share.lower() != dst_share.lower():
+            raise ValueError("Cannot %s a file to a different root than the 
src." % verb)
+
+        with SMBFileTransaction(src_raw) as transaction:
+            file_rename = FileRenameInformation()
+            file_rename['replace_if_exists'] = replace_if_exists
+            file_rename['file_name'] = to_text(dst_raw.fd.file_name)
+            set_info(transaction, file_rename)
 
 
 def _set_basic_information(path, creation_time=0, last_access_time=0, 
last_write_time=0, change_time=0,
@@ -1064,11 +1095,12 @@
 
 class SMBDirEntry(object):
 
-    def __init__(self, raw, dir_info):
+    def __init__(self, raw, dir_info, connection_cache=None):
         self._smb_raw = raw
         self._dir_info = dir_info
         self._stat = None
         self._lstat = None
+        self._connection_cache = connection_cache
 
     def __str__(self):
         return '<{0}: {1!r}>'.format(self.__class__.__name__, 
to_native(self.name))
@@ -1179,16 +1211,16 @@
         if follow_symlinks:
             if not self._stat:
                 if self.is_symlink():
-                    self._stat = stat(self.path)
+                    self._stat = stat(self.path, 
connection_cache=self._connection_cache)
                 else:
                     # Because it's not a symlink lstat will be the same as 
stat so set both.
                     if self._lstat is None:
-                        self._lstat = lstat(self._smb_raw.name)
+                        self._lstat = lstat(self._smb_raw.name, 
connection_cache=self._connection_cache)
                     self._stat = self._lstat
             return self._stat
         else:
             if not self._lstat:
-                self._lstat = lstat(self.path)
+                self._lstat = lstat(self.path, 
connection_cache=self._connection_cache)
             return self._lstat
 
     @classmethod
@@ -1200,7 +1232,7 @@
         dir_info['file_attributes'] = file_stat.st_file_attributes
         dir_info['file_id'] = file_stat.st_ino
 
-        dir_entry = cls(SMBRawIO(path, **kwargs), dir_info)
+        dir_entry = cls(SMBRawIO(path, **kwargs), dir_info, 
connection_cache=kwargs.get('connection_cache', None))
         dir_entry._stat = file_stat
         return dir_entry
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/smbclient/shutil.py 
new/smbprotocol-1.5.1/smbclient/shutil.py
--- old/smbprotocol-1.5.0/smbclient/shutil.py   2021-03-25 06:15:16.000000000 
+0100
+++ new/smbprotocol-1.5.1/smbclient/shutil.py   2021-05-08 01:01:14.000000000 
+0200
@@ -31,6 +31,7 @@
     stat as smbclient_stat,
     symlink,
     SMBDirEntry,
+    is_remote_path,
 )
 
 from smbclient.path import (
@@ -130,7 +131,7 @@
         return raise_not_implemented
 
     norm_src = ntpath.normpath(src)
-    if norm_src.startswith('\\\\'):
+    if is_remote_path(norm_src):
         src_root = ntpath.splitdrive(norm_src)[0]
         islink_func = islink
         readlink_func = readlink
@@ -147,7 +148,7 @@
         src_kwargs = {}
 
     norm_dst = ntpath.normpath(dst)
-    if norm_dst.startswith('\\\\'):
+    if is_remote_path(norm_dst):
         dst_root = ntpath.splitdrive(norm_dst)[0]
         dst_open = open_file
         dst_kwargs = kwargs
@@ -216,7 +217,7 @@
     src_mode = stat.S_IMODE(_get_file_stat(src, follow_symlinks, 
**kwargs).st_mode)
 
     norm_dst = ntpath.normpath(dst)
-    if norm_dst.startswith('\\\\'):
+    if is_remote_path(norm_dst):
         read_only = not (src_mode & stat.S_IWRITE == stat.S_IWRITE and 
src_mode & stat.S_IREAD == stat.S_IREAD)
         _set_file_basic_info(dst, follow_symlinks, read_only=read_only, 
**kwargs)
     else:
@@ -244,7 +245,7 @@
     mtime_ns = getattr(src_stat, 'st_mtime_ns', src_stat.st_mtime * 1000000000)
 
     norm_dst = ntpath.normpath(dst)
-    if norm_dst.startswith('\\\\'):
+    if is_remote_path(norm_dst):
         read_only = not (src_mode & stat.S_IWRITE == stat.S_IWRITE and 
src_mode & stat.S_IREAD == stat.S_IREAD)
         _set_file_basic_info(dst, follow_symlinks, read_only=read_only, 
atime_ns=atime_ns, mtime_ns=mtime_ns, **kwargs)
     else:
@@ -407,19 +408,19 @@
         if dir_entry.is_symlink() and \
                 dir_entry.stat(follow_symlinks=False).st_file_attributes & 
FileAttributes.FILE_ATTRIBUTE_DIRECTORY:
             try:
-                rmdir(dir_entry.path)
+                rmdir(dir_entry.path, **kwargs)
             except OSError:
                 onerror(rmdir, dir_entry.path, sys.exc_info())
         elif dir_entry.is_dir():
-            rmtree(dir_entry.path, ignore_errors, onerror)
+            rmtree(dir_entry.path, ignore_errors, onerror, **kwargs)
         else:
             try:
-                remove(dir_entry.path)
+                remove(dir_entry.path, **kwargs)
             except OSError:
                 onerror(remove, dir_entry.path, sys.exc_info())
 
     try:
-        rmdir(path)
+        rmdir(path, **kwargs)
     except OSError:
         onerror(rmdir, path, sys.exc_info())
 
@@ -427,7 +428,7 @@
 def _copy(src, dst, follow_symlinks, copy_meta_func, **kwargs):
     # Need to check if dst is a UNC path before checking if it's a dir in 
smbclient.path before checking to see if it's
     # a local directory. If either one is a dir, join the filename of src onto 
dst.
-    if ntpath.normpath(dst).startswith('\\\\') and isdir(dst, **kwargs):
+    if is_remote_path(ntpath.normpath(dst)) and isdir(dst, **kwargs):
         dst = ntpath.join(dst, ntpath.basename(src))
     elif os.path.isdir(dst):
         dst = os.path.join(dst, os.path.basename(src))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/smbprotocol/exceptions.py 
new/smbprotocol-1.5.1/smbprotocol/exceptions.py
--- old/smbprotocol-1.5.0/smbprotocol/exceptions.py     2021-03-25 
06:15:16.000000000 +0100
+++ new/smbprotocol-1.5.1/smbprotocol/exceptions.py     2021-05-08 
01:01:14.000000000 +0200
@@ -77,6 +77,7 @@
         error_details = {
             NtStatus.STATUS_OBJECT_NAME_NOT_FOUND: errno.ENOENT,
             NtStatus.STATUS_OBJECT_PATH_NOT_FOUND: errno.ENOENT,
+            NtStatus.STATUS_NOT_FOUND: errno.ENOENT,
             NtStatus.STATUS_OBJECT_NAME_COLLISION: errno.EEXIST,
             NtStatus.STATUS_PRIVILEGE_NOT_HELD: (errno.EACCES, "Required 
privilege not held"),
             NtStatus.STATUS_SHARING_VIOLATION: (errno.EPERM, "The process 
cannot access the file because it is being "
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/tests/conftest.py 
new/smbprotocol-1.5.1/tests/conftest.py
--- old/smbprotocol-1.5.0/tests/conftest.py     2021-03-25 06:15:16.000000000 
+0100
+++ new/smbprotocol-1.5.1/tests/conftest.py     2021-05-08 01:01:14.000000000 
+0200
@@ -7,6 +7,7 @@
 import time
 
 from smbclient import (
+    ClientConfig,
     delete_session,
     mkdir,
 )
@@ -177,7 +178,7 @@
 @pytest.fixture(params=[
     ('share', 4),
     ('share-encrypted', 5),
-])
+], ids=['share', 'share-encrypted'])
 def smb_share(request, smb_real):
     # Use some non ASCII chars to test out edge cases by default.
     share_path = u"%s\\%s" % (smb_real[request.param[1]], u"P??t??s???-[%s] 
????" % time.time())
@@ -198,7 +199,7 @@
     ('', None),  # Root, no referral targets
     ('share', 4),  # Simple referral to a single target
     ('share-encrypted', 5),  # Referral to 2 targets, first is known to be 
broken
-])
+], ids=['dfs-root', 'dfs-single-target', 'dfs-broken-target'])
 def smb_dfs_share(request, smb_real):
     test_folder = u"P??t??s???-[%s] ????" % time.time()
 
@@ -215,3 +216,7 @@
         yield dfs_path
     finally:
         rmtree(target_share_path, username=smb_real[0], password=smb_real[1], 
port=smb_real[3])
+
+        config = ClientConfig()
+        config._domain_cache = []
+        config._referral_cache = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/smbprotocol-1.5.0/tests/test_smbclient_os.py 
new/smbprotocol-1.5.1/tests/test_smbclient_os.py
--- old/smbprotocol-1.5.0/tests/test_smbclient_os.py    2021-03-25 
06:15:16.000000000 +0100
+++ new/smbprotocol-1.5.1/tests/test_smbclient_os.py    2021-05-08 
01:01:14.000000000 +0200
@@ -13,6 +13,7 @@
 import six
 import smbclient  # Tests that we expose this in smbclient/__init__.py
 import stat
+import time
 
 from smbclient._io import (
     query_info,
@@ -25,6 +26,10 @@
     SMBFileIO,
 )
 
+from smbclient.shutil import (
+    rmtree,
+)
+
 from smbprotocol.exceptions import (
     SMBAuthenticationError,
     SMBOSError,
@@ -1048,8 +1053,26 @@
 
 def test_rename_fail_dst_different_root(smb_share):
     expected = "Cannot rename a file to a different root than the src."
+    server = [p for p in ntpath.normpath(smb_share).split("\\") if p][0]
+
     with pytest.raises(ValueError, match=re.escape(expected)):
-        smbclient.rename(smb_share, "\\\\server2\\share\\dst")
+        smbclient.rename(smb_share, "\\\\%s\\dfs\\dst" % server)
+
+
+def test_rename_dir(smb_share):
+    src_dir = ntpath.join(smb_share, 'src')
+    dst_dir = ntpath.join(smb_share, 'dst')
+
+    smbclient.mkdir(src_dir)
+    with smbclient.open_file(ntpath.join(src_dir, 'file.txt'), mode='wb') as 
fd:
+        fd.write(b'test data')
+
+    smbclient.rename(src_dir, dst_dir)
+
+    assert smbclient.listdir(smb_share) == ['dst']
+    assert smbclient.listdir(dst_dir) == ['file.txt']
+    with smbclient.open_file(ntpath.join(dst_dir, 'file.txt'), mode='rb') as 
fd:
+        assert fd.read() == b'test data'
 
 
 def test_renames(smb_share):
@@ -1100,6 +1123,19 @@
     assert actual == u"Content"
 
 
+def test_replace_sharing_violation(smb_share):
+    src_file = ntpath.join(smb_share, 'src.txt')
+    dst_file = ntpath.join(smb_share, 'dst.txt')
+
+    with smbclient.open_file(src_file, mode='wb') as fd:
+        fd.write(b'test data')
+
+    expected = re.escape('The process cannot access the file because it is 
being used by another process')
+    with smbclient.open_file(dst_file, mode='wb'):
+        with pytest.raises(SMBOSError, match=expected):
+            smbclient.replace(src_file, dst_file)
+
+
 def test_rmdir(smb_share):
     dir_name = "%s\\directory" % smb_share
 
@@ -1289,6 +1325,61 @@
         assert entry.is_file(follow_symlinks=False) is False  # broken link 
target
 
 
+def test_scandir_with_cache(smb_real):
+    share_path = u"%s\\%s" % (smb_real[4], u"P??t??s???-[%s] ????" % 
time.time())
+    cache = {}
+    smbclient.mkdir(share_path, username=smb_real[0], password=smb_real[1], 
port=smb_real[3], connection_cache=cache)
+
+    try:
+
+        dir_path = ntpath.join(share_path, 'directory')
+        smbclient.makedirs(dir_path, exist_ok=True, connection_cache=cache)
+
+        for name in ['file.txt', u'unicode ???[????].txt']:
+            with smbclient.open_file(ntpath.join(dir_path, name), mode='w', 
connection_cache=cache) as fd:
+                fd.write(u"content")
+
+        for name in ['subdir1', 'subdir2', u'unicode dir ???[????]', 
'subdir1\\sub']:
+            smbclient.mkdir(ntpath.join(dir_path, name), 
connection_cache=cache)
+
+        count = 0
+        names = []
+        for dir_entry in smbclient.scandir(dir_path, connection_cache=cache):
+            assert isinstance(dir_entry, SMBDirEntry)
+            names.append(dir_entry.name)
+
+            # Test out dir_entry for specific file and dir examples
+            if dir_entry.name == 'subdir1':
+                assert str(dir_entry) == "<SMBDirEntry: 'subdir1'>"
+                assert dir_entry.is_dir() is True
+                assert dir_entry.is_file() is False
+                assert dir_entry.stat(follow_symlinks=False).st_ino == 
dir_entry.inode()
+                assert dir_entry.stat().st_ino == dir_entry.inode()
+            elif dir_entry.name == 'file.txt':
+                assert str(dir_entry) == "<SMBDirEntry: 'file.txt'>"
+                assert dir_entry.is_dir() is False
+                assert dir_entry.is_file() is True
+                assert dir_entry.stat().st_ino == dir_entry.inode()
+                assert dir_entry.stat(follow_symlinks=False).st_ino == 
dir_entry.inode()
+
+            assert dir_entry.is_symlink() is False
+            assert dir_entry.inode() is not None
+            assert dir_entry.inode() == dir_entry.stat().st_ino
+
+            count += 1
+
+        assert count == 5
+        assert u'unicode ???[????].txt' in names
+        assert u'unicode dir ???[????]' in names
+        assert u'subdir2' in names
+        assert u'subdir1' in names
+        assert u'file.txt' in names
+
+    finally:
+        rmtree(share_path, connection_cache=cache)
+        smbclient.reset_connection_cache(connection_cache=cache)
+
+
 def test_stat_directory(smb_share):
     actual = smbclient.stat(smb_share)
     assert isinstance(actual, smbclient.SMBStatResult)
@@ -1973,6 +2064,48 @@
             assert not info.is_file()
 
 
+def test_dfs_path_rename(smb_dfs_share):
+    test_dir = ntpath.join(smb_dfs_share, 'test folder')
+    smbclient.mkdir(test_dir)
+
+    test_file = ntpath.join(smb_dfs_share, 'test file.txt')
+    with smbclient.open_file(test_file, mode='wb') as fd:
+        fd.write(b'test data')
+
+    dst_dfs_path = ntpath.join(test_dir, 'target renamed.txt')
+    smbclient.rename(test_file, dst_dfs_path)
+
+    assert smbclient.listdir(smb_dfs_share) == ['test folder']
+    assert smbclient.listdir(test_dir) == ['target renamed.txt']
+
+    with smbclient.open_file(dst_dfs_path, mode='rb') as fd:
+        assert fd.read() == b'test data'
+
+
+def test_dfs_path_replace(smb_dfs_share):
+    test_dir = ntpath.join(smb_dfs_share, 'test folder')
+    smbclient.mkdir(test_dir)
+
+    test_file = ntpath.join(smb_dfs_share, 'test file.txt')
+    with smbclient.open_file(test_file, mode='wb') as fd:
+        fd.write(b'other data')
+
+    dst_dfs_path = ntpath.join(test_dir, 'target renamed.txt')
+    with smbclient.open_file(dst_dfs_path, mode='wb') as fd:
+        fd.write(b'test data')
+
+    smbclient.replace(dst_dfs_path, test_file)
+
+    actual_contents = smbclient.listdir(smb_dfs_share)
+    assert 'test folder' in actual_contents
+    assert 'test file.txt' in actual_contents
+
+    assert smbclient.listdir(test_dir) == []
+
+    with smbclient.open_file(test_file, mode='rb') as fd:
+        assert fd.read() == b'test data'
+
+
 def test_broken_dfs_path(smb_real):
     dfs_path = u'\\\\' + ntpath.join(smb_real[2], 'dfs', 'broken')
 

Reply via email to