diff options
Diffstat (limited to 'paramiko/message.py')
-rw-r--r-- | paramiko/message.py | 206 |
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) |