Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/message.py: 66%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
2#
3# This file is part of paramiko.
4#
5# Paramiko is free software; you can redistribute it and/or modify it under the
6# terms of the GNU Lesser General Public License as published by the Free
7# Software Foundation; either version 2.1 of the License, or (at your option)
8# any later version.
9#
10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13# details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19"""
20Implementation of an SSH2 "message".
21"""
23import struct
24from io import BytesIO
26from paramiko import util
27from paramiko.common import zero_byte, max_byte, one_byte
28from paramiko.util import u
31class Message:
32 """
33 An SSH2 message is a stream of bytes that encodes some combination of
34 strings, integers, bools, and infinite-precision integers. This class
35 builds or breaks down such a byte stream.
37 Normally you don't need to deal with anything this low-level, but it's
38 exposed for people implementing custom extensions, or features that
39 paramiko doesn't support yet.
40 """
42 big_int = 0xFF000000
44 def __init__(self, content=None):
45 """
46 Create a new SSH2 message.
48 :param bytes content:
49 the byte stream to use as the message content (passed in only when
50 decomposing a message).
51 """
52 if content is not None:
53 self.packet = BytesIO(content)
54 else:
55 self.packet = BytesIO()
57 def __bytes__(self):
58 return self.asbytes()
60 def __repr__(self):
61 """
62 Returns a string representation of this object, for debugging.
63 """
64 return "paramiko.Message(" + repr(self.packet.getvalue()) + ")"
66 # TODO 4.0: just merge into __bytes__ (everywhere)
67 def asbytes(self):
68 """
69 Return the byte stream content of this Message, as a `bytes`.
70 """
71 return self.packet.getvalue()
73 def rewind(self):
74 """
75 Rewind the message to the beginning as if no items had been parsed
76 out of it yet.
77 """
78 self.packet.seek(0)
80 def get_remainder(self):
81 """
82 Return the `bytes` of this message that haven't already been parsed and
83 returned.
84 """
85 position = self.packet.tell()
86 remainder = self.packet.read()
87 self.packet.seek(position)
88 return remainder
90 def get_so_far(self):
91 """
92 Returns the `bytes` of this message that have been parsed and
93 returned. The string passed into a message's constructor can be
94 regenerated by concatenating ``get_so_far`` and `get_remainder`.
95 """
96 position = self.packet.tell()
97 self.rewind()
98 return self.packet.read(position)
100 def get_bytes(self, n):
101 """
102 Return the next ``n`` bytes of the message, without decomposing into an
103 int, decoded string, etc. Just the raw bytes are returned. Returns a
104 string of ``n`` zero bytes if there weren't ``n`` bytes remaining in
105 the message.
106 """
107 b = self.packet.read(n)
108 max_pad_size = 1 << 20 # Limit padding to 1 MB
109 if len(b) < n < max_pad_size:
110 return b + zero_byte * (n - len(b))
111 return b
113 def get_byte(self):
114 """
115 Return the next byte of the message, without decomposing it. This
116 is equivalent to `get_bytes(1) <get_bytes>`.
118 :return:
119 the next (`bytes`) byte of the message, or ``b'\000'`` if there
120 aren't any bytes remaining.
121 """
122 return self.get_bytes(1)
124 def get_boolean(self):
125 """
126 Fetch a boolean from the stream.
127 """
128 b = self.get_bytes(1)
129 return b != zero_byte
131 def get_adaptive_int(self):
132 """
133 Fetch an int from the stream.
135 :return: a 32-bit unsigned `int`.
136 """
137 byte = self.get_bytes(1)
138 if byte == max_byte:
139 return util.inflate_long(self.get_binary())
140 byte += self.get_bytes(3)
141 return struct.unpack(">I", byte)[0]
143 def get_int(self):
144 """
145 Fetch an int from the stream.
146 """
147 return struct.unpack(">I", self.get_bytes(4))[0]
149 def get_int64(self):
150 """
151 Fetch a 64-bit int from the stream.
153 :return: a 64-bit unsigned integer (`int`).
154 """
155 return struct.unpack(">Q", self.get_bytes(8))[0]
157 def get_mpint(self):
158 """
159 Fetch a long int (mpint) from the stream.
161 :return: an arbitrary-length integer (`int`).
162 """
163 return util.inflate_long(self.get_binary())
165 # TODO 4.0: depending on where this is used internally or downstream, force
166 # users to specify get_binary instead and delete this.
167 def get_string(self):
168 """
169 Fetch a "string" from the stream. This will actually be a `bytes`
170 object, and may contain unprintable characters. (It's not unheard of
171 for a string to contain another byte-stream message.)
172 """
173 return self.get_bytes(self.get_int())
175 # TODO 4.0: also consider having this take over the get_string name, and
176 # remove this name instead.
177 def get_text(self):
178 """
179 Fetch a Unicode string from the stream.
181 This currently operates by attempting to encode the next "string" as
182 ``utf-8``.
183 """
184 return u(self.get_string())
186 def get_binary(self):
187 """
188 Alias for `get_string` (obtains a bytestring).
189 """
190 return self.get_bytes(self.get_int())
192 def get_list(self):
193 """
194 Fetch a list of `strings <str>` from the stream.
196 These are trivially encoded as comma-separated values in a string.
197 """
198 return self.get_text().split(",")
200 def add_bytes(self, b):
201 """
202 Write bytes to the stream, without any formatting.
204 :param bytes b: bytes to add
205 """
206 self.packet.write(b)
207 return self
209 def add_byte(self, b):
210 """
211 Write a single byte to the stream, without any formatting.
213 :param bytes b: byte to add
214 """
215 self.packet.write(b)
216 return self
218 def add_boolean(self, b):
219 """
220 Add a boolean value to the stream.
222 :param bool b: boolean value to add
223 """
224 if b:
225 self.packet.write(one_byte)
226 else:
227 self.packet.write(zero_byte)
228 return self
230 def add_int(self, n):
231 """
232 Add an integer to the stream.
234 :param int n: integer to add
235 """
236 self.packet.write(struct.pack(">I", n))
237 return self
239 def add_adaptive_int(self, n):
240 """
241 Add an integer to the stream.
243 :param int n: integer to add
244 """
245 if n >= Message.big_int:
246 self.packet.write(max_byte)
247 self.add_string(util.deflate_long(n))
248 else:
249 self.packet.write(struct.pack(">I", n))
250 return self
252 def add_int64(self, n):
253 """
254 Add a 64-bit int to the stream.
256 :param int n: long int to add
257 """
258 self.packet.write(struct.pack(">Q", n))
259 return self
261 def add_mpint(self, z):
262 """
263 Add a long int to the stream, encoded as an infinite-precision
264 integer. This method only works on positive numbers.
266 :param int z: long int to add
267 """
268 self.add_string(util.deflate_long(z))
269 return self
271 # TODO: see the TODO for get_string/get_text/et al, this should change
272 # to match.
273 def add_string(self, s):
274 """
275 Add a bytestring to the stream.
277 :param byte s: bytestring to add
278 """
279 s = util.asbytes(s)
280 self.add_int(len(s))
281 self.packet.write(s)
282 return self
284 def add_list(self, l): # noqa: E741
285 """
286 Add a list of strings to the stream. They are encoded identically to
287 a single string of values separated by commas. (Yes, really, that's
288 how SSH2 does it.)
290 :param l: list of strings to add
291 """
292 self.add_string(",".join(l))
293 return self
295 def _add(self, i):
296 if type(i) is bool:
297 return self.add_boolean(i)
298 elif isinstance(i, int):
299 return self.add_adaptive_int(i)
300 elif type(i) is list:
301 return self.add_list(i)
302 else:
303 return self.add_string(i)
305 # TODO: this would never have worked for unicode strings under Python 3,
306 # guessing nobody/nothing ever used it for that purpose?
307 def add(self, *seq):
308 """
309 Add a sequence of items to the stream. The values are encoded based
310 on their type: bytes, str, int, bool, or list.
312 .. warning::
313 Longs are encoded non-deterministically. Don't use this method.
315 :param seq: the sequence of items
316 """
317 for item in seq:
318 self._add(item)