Repository: libcloud
Updated Branches:
  refs/heads/trunk bd17cb936 -> 514cbb334


Fix an edge case bug when consuming data from a channel inside the Paramiko SSH
client.

In case consumed data contained multi byte UTF-8 character and this character
was split over two chunks, an exception would be thrown since we tried to
decode received data after receiving every chunk instead of the end.

This bug was originally caught by Lakshmi Kannan and fix is partially based
on the changes inside StackStorm/st2 which are Apache 2.0 licensed.


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/1a658097
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/1a658097
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/1a658097

Branch: refs/heads/trunk
Commit: 1a6580973ff0b794d0dfc02ec76c27eac7b4671f
Parents: 2b3beef
Author: Tomaz Muraus <[email protected]>
Authored: Sun Jan 24 21:12:49 2016 +0100
Committer: Tomaz Muraus <[email protected]>
Committed: Sun Jan 24 21:12:49 2016 +0100

----------------------------------------------------------------------
 libcloud/compute/ssh.py | 51 ++++++++++++++++++++++++++------------------
 1 file changed, 30 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/1a658097/libcloud/compute/ssh.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/ssh.py b/libcloud/compute/ssh.py
index 3143d5d..b0ccffd 100644
--- a/libcloud/compute/ssh.py
+++ b/libcloud/compute/ssh.py
@@ -201,6 +201,7 @@ class ParamikoSSHClient(BaseSSHClient):
 
     # Maximum number of bytes to read at once from a socket
     CHUNK_SIZE = 1024
+
     # How long to sleep while waiting for command to finish
     SLEEP_DELAY = 1.5
 
@@ -402,41 +403,49 @@ class ParamikoSSHClient(BaseSSHClient):
         """
         Try to consume stdout data from chan if it's receive ready.
         """
-
-        stdout = StringIO()
-        if chan.recv_ready():
-            data = chan.recv(self.CHUNK_SIZE)
-
-            while data:
-                stdout.write(b(data).decode('utf-8'))
-                ready = chan.recv_ready()
-
-                if not ready:
-                    break
-
-                data = chan.recv(self.CHUNK_SIZE)
-
+        stdout = self._consume_data_from_channel(
+            chan=chan,
+            recv_method=chan.recv,
+            recv_ready_method=chan.recv_ready)
         return stdout
 
     def _consume_stderr(self, chan):
         """
         Try to consume stderr data from chan if it's receive ready.
         """
+        stderr = self._consume_data_from_channel(
+            chan=chan,
+            recv_method=chan.recv_stderr,
+            recv_ready_method=chan.recv_stderr_ready)
+        return stderr
 
-        stderr = StringIO()
-        if chan.recv_stderr_ready():
-            data = chan.recv_stderr(self.CHUNK_SIZE)
+    def _consume_data_from_channel(self, chan, recv_method, recv_ready_method):
+        """
+        Try to consume data from the provided channel.
+
+        Keep in mind that data is only consumed if the channel is receive
+        ready.
+        """
+        result = StringIO()
+        result_bytes = bytearray()
+
+        if recv_ready_method():
+            data = recv_method(self.CHUNK_SIZE)
+            result_bytes += b(data)
 
             while data:
-                stderr.write(b(data).decode('utf-8'))
-                ready = chan.recv_stderr_ready()
+                ready = recv_ready_method()
 
                 if not ready:
                     break
 
-                data = chan.recv_stderr(self.CHUNK_SIZE)
+                data = recv_method(self.CHUNK_SIZE)
 
-        return stderr
+        # We only decode data at the end because a single chunk could contain
+        # a part of multi byte UTF-8 character (whole multi bytes character
+        # could be split over two chunks)
+        result.write(result_bytes.decode('utf-8'))
+        return result
 
     def _get_pkey_object(self, key):
         """

Reply via email to