summaryrefslogtreecommitdiff
path: root/paramiko/message.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/message.py')
-rw-r--r--paramiko/message.py206
1 files changed, 113 insertions, 93 deletions
diff --git a/paramiko/message.py b/paramiko/message.py
index c0e8692..da6acf8 100644
--- a/paramiko/message.py
+++ b/paramiko/message.py
@@ -21,52 +21,56 @@ Implementation of an SSH2 "message".
"""
import struct
-import cStringIO
from paramiko import util
+from paramiko.common import zero_byte, max_byte, one_byte, asbytes
+from paramiko.py3compat import long, BytesIO, u, integer_types
class Message (object):
"""
- An SSH2 I{Message} is a stream of bytes that encodes some combination of
- strings, integers, bools, and infinite-precision integers (known in python
- as I{long}s). This class builds or breaks down such a byte stream.
+ An SSH2 message is a stream of bytes that encodes some combination of
+ strings, integers, bools, and infinite-precision integers (known in Python
+ as longs). This class builds or breaks down such a byte stream.
Normally you don't need to deal with anything this low-level, but it's
exposed for people implementing custom extensions, or features that
paramiko doesn't support yet.
"""
+ big_int = long(0xff000000)
+
def __init__(self, content=None):
"""
- Create a new SSH2 Message.
+ Create a new SSH2 message.
- @param content: the byte stream to use as the Message content (passed
- in only when decomposing a Message).
- @type content: string
+ :param str content:
+ the byte stream to use as the message content (passed in only when
+ decomposing a message).
"""
- if content != None:
- self.packet = cStringIO.StringIO(content)
+ if content is not None:
+ self.packet = BytesIO(content)
else:
- self.packet = cStringIO.StringIO()
+ self.packet = BytesIO()
def __str__(self):
"""
- Return the byte stream content of this Message, as a string.
-
- @return: the contents of this Message.
- @rtype: string
+ Return the byte stream content of this message, as a string/bytes obj.
"""
- return self.packet.getvalue()
+ return self.asbytes()
def __repr__(self):
"""
Returns a string representation of this object, for debugging.
-
- @rtype: string
"""
return 'paramiko.Message(' + repr(self.packet.getvalue()) + ')'
+ def asbytes(self):
+ """
+ Return the byte stream content of this Message, as bytes.
+ """
+ return self.packet.getvalue()
+
def rewind(self):
"""
Rewind the message to the beginning as if no items had been parsed
@@ -76,11 +80,8 @@ class Message (object):
def get_remainder(self):
"""
- Return the bytes of this Message that haven't already been parsed and
- returned.
-
- @return: a string of the bytes not parsed yet.
- @rtype: string
+ Return the bytes (as a `str`) of this message that haven't already been
+ parsed and returned.
"""
position = self.packet.tell()
remainder = self.packet.read()
@@ -89,12 +90,9 @@ class Message (object):
def get_so_far(self):
"""
- Returns the bytes of this Message that have been parsed and returned.
- The string passed into a Message's constructor can be regenerated by
- concatenating C{get_so_far} and L{get_remainder}.
-
- @return: a string of the bytes parsed so far.
- @rtype: string
+ Returns the `str` bytes of this message that have been parsed and
+ returned. The string passed into a message's constructor can be
+ regenerated by concatenating ``get_so_far`` and `get_remainder`.
"""
position = self.packet.tell()
self.rewind()
@@ -102,44 +100,51 @@ class Message (object):
def get_bytes(self, n):
"""
- Return the next C{n} bytes of the Message, without decomposing into
- an int, string, etc. Just the raw bytes are returned.
-
- @return: a string of the next C{n} bytes of the Message, or a string
- of C{n} zero bytes, if there aren't C{n} bytes remaining.
- @rtype: string
+ Return the next ``n`` bytes of the message (as a `str`), without
+ decomposing into an int, decoded string, etc. Just the raw bytes are
+ returned. Returns a string of ``n`` zero bytes if there weren't ``n``
+ bytes remaining in the message.
"""
b = self.packet.read(n)
- max_pad_size = 1<<20 # Limit padding to 1 MB
- if len(b) < n and n < max_pad_size:
- return b + '\x00' * (n - len(b))
+ max_pad_size = 1 << 20 # Limit padding to 1 MB
+ if len(b) < n < max_pad_size:
+ return b + zero_byte * (n - len(b))
return b
def get_byte(self):
"""
- Return the next byte of the Message, without decomposing it. This
- is equivalent to L{get_bytes(1)<get_bytes>}.
+ Return the next byte of the message, without decomposing it. This
+ is equivalent to `get_bytes(1) <get_bytes>`.
- @return: the next byte of the Message, or C{'\000'} if there aren't
+ :return:
+ the next (`str`) byte of the message, or ``'\000'`` if there aren't
any bytes remaining.
- @rtype: string
"""
return self.get_bytes(1)
def get_boolean(self):
"""
Fetch a boolean from the stream.
-
- @return: C{True} or C{False} (from the Message).
- @rtype: bool
"""
b = self.get_bytes(1)
- return b != '\x00'
+ return b != zero_byte
def get_int(self):
"""
Fetch an int from the stream.
+ :return: a 32-bit unsigned `int`.
+ """
+ byte = self.get_bytes(1)
+ if byte == max_byte:
+ return util.inflate_long(self.get_binary())
+ byte += self.get_bytes(3)
+ return struct.unpack('>I', byte)[0]
+
+ def get_size(self):
+ """
+ Fetch an int from the stream.
+
@return: a 32-bit unsigned integer.
@rtype: int
"""
@@ -149,8 +154,7 @@ class Message (object):
"""
Fetch a 64-bit int from the stream.
- @return: a 64-bit unsigned integer.
- @rtype: long
+ :return: a 64-bit unsigned integer (`long`).
"""
return struct.unpack('>Q', self.get_bytes(8))[0]
@@ -158,13 +162,20 @@ class Message (object):
"""
Fetch a long int (mpint) from the stream.
- @return: an arbitrary-length integer.
- @rtype: long
+ :return: an arbitrary-length integer (`long`).
"""
- return util.inflate_long(self.get_string())
+ return util.inflate_long(self.get_binary())
def get_string(self):
"""
+ Fetch a `str` from the stream. This could be a byte string and may
+ contain unprintable characters. (It's not unheard of for a string to
+ contain another byte-stream message.)
+ """
+ return self.get_bytes(self.get_size())
+
+ def get_text(self):
+ """
Fetch a string from the stream. This could be a byte string and may
contain unprintable characters. (It's not unheard of for a string to
contain another byte-stream Message.)
@@ -172,24 +183,33 @@ class Message (object):
@return: a string.
@rtype: string
"""
- return self.get_bytes(self.get_int())
+ return u(self.get_bytes(self.get_size()))
+ #return self.get_bytes(self.get_size())
- def get_list(self):
+ def get_binary(self):
"""
- Fetch a list of strings from the stream. These are trivially encoded
- as comma-separated values in a string.
+ Fetch a string from the stream. This could be a byte string and may
+ contain unprintable characters. (It's not unheard of for a string to
+ contain another byte-stream Message.)
- @return: a list of strings.
- @rtype: list of strings
+ @return: a string.
+ @rtype: string
"""
- return self.get_string().split(',')
+ return self.get_bytes(self.get_size())
+
+ def get_list(self):
+ """
+ Fetch a `list` of `strings <str>` from the stream.
+
+ These are trivially encoded as comma-separated values in a string.
+ """
+ return self.get_text().split(',')
def add_bytes(self, b):
"""
Write bytes to the stream, without any formatting.
- @param b: bytes to add
- @type b: str
+ :param str b: bytes to add
"""
self.packet.write(b)
return self
@@ -198,8 +218,7 @@ class Message (object):
"""
Write a single byte to the stream, without any formatting.
- @param b: byte to add
- @type b: str
+ :param str b: byte to add
"""
self.packet.write(b)
return self
@@ -208,31 +227,41 @@ class Message (object):
"""
Add a boolean value to the stream.
- @param b: boolean value to add
- @type b: bool
+ :param bool b: boolean value to add
"""
if b:
- self.add_byte('\x01')
+ self.packet.write(one_byte)
else:
- self.add_byte('\x00')
+ self.packet.write(zero_byte)
return self
- def add_int(self, n):
+ def add_size(self, n):
"""
Add an integer to the stream.
- @param n: integer to add
- @type n: int
+ :param int n: integer to add
"""
self.packet.write(struct.pack('>I', n))
return self
+
+ def add_int(self, n):
+ """
+ Add an integer to the stream.
+
+ :param int n: integer to add
+ """
+ if n >= Message.big_int:
+ self.packet.write(max_byte)
+ self.add_string(util.deflate_long(n))
+ else:
+ self.packet.write(struct.pack('>I', n))
+ return self
def add_int64(self, n):
"""
Add a 64-bit int to the stream.
- @param n: long int to add
- @type n: long
+ :param long n: long int to add
"""
self.packet.write(struct.pack('>Q', n))
return self
@@ -242,8 +271,7 @@ class Message (object):
Add a long int to the stream, encoded as an infinite-precision
integer. This method only works on positive numbers.
- @param z: long int to add
- @type z: long
+ :param long z: long int to add
"""
self.add_string(util.deflate_long(z))
return self
@@ -252,10 +280,10 @@ class Message (object):
"""
Add a string to the stream.
- @param s: string to add
- @type s: str
+ :param str s: string to add
"""
- self.add_int(len(s))
+ s = asbytes(s)
+ self.add_size(len(s))
self.packet.write(s)
return self
@@ -265,38 +293,30 @@ class Message (object):
a single string of values separated by commas. (Yes, really, that's
how SSH2 does it.)
- @param l: list of strings to add
- @type l: list(str)
+ :param list l: list of strings to add
"""
self.add_string(','.join(l))
return self
def _add(self, i):
- if type(i) is str:
- return self.add_string(i)
- elif type(i) is int:
- return self.add_int(i)
- elif type(i) is long:
- if i > 0xffffffffL:
- return self.add_mpint(i)
- else:
- return self.add_int(i)
- elif type(i) is bool:
+ if type(i) is bool:
return self.add_boolean(i)
+ elif isinstance(i, integer_types):
+ return self.add_int(i)
elif type(i) is list:
return self.add_list(i)
else:
- raise Exception('Unknown type')
+ return self.add_string(i)
def add(self, *seq):
"""
Add a sequence of items to the stream. The values are encoded based
on their type: str, int, bool, list, or long.
+
+ .. warning::
+ Longs are encoded non-deterministically. Don't use this method.
- @param seq: the sequence of items
- @type seq: sequence
-
- @bug: longs are encoded non-deterministically. Don't use this method.
+ :param seq: the sequence of items
"""
for item in seq:
self._add(item)