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

106 statements  

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. 

18 

19""" 

20Implementation of an SSH2 "message". 

21""" 

22 

23import struct 

24from io import BytesIO 

25 

26from paramiko import util 

27from paramiko.common import zero_byte, max_byte, one_byte 

28from paramiko.util import u 

29 

30 

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. 

36 

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 """ 

41 

42 big_int = 0xFF000000 

43 

44 def __init__(self, content=None): 

45 """ 

46 Create a new SSH2 message. 

47 

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() 

56 

57 def __bytes__(self): 

58 return self.asbytes() 

59 

60 def __repr__(self): 

61 """ 

62 Returns a string representation of this object, for debugging. 

63 """ 

64 return "paramiko.Message(" + repr(self.packet.getvalue()) + ")" 

65 

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() 

72 

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) 

79 

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 

89 

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) 

99 

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 

112 

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>`. 

117 

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) 

123 

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 

130 

131 def get_adaptive_int(self): 

132 """ 

133 Fetch an int from the stream. 

134 

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] 

142 

143 def get_int(self): 

144 """ 

145 Fetch an int from the stream. 

146 """ 

147 return struct.unpack(">I", self.get_bytes(4))[0] 

148 

149 def get_int64(self): 

150 """ 

151 Fetch a 64-bit int from the stream. 

152 

153 :return: a 64-bit unsigned integer (`int`). 

154 """ 

155 return struct.unpack(">Q", self.get_bytes(8))[0] 

156 

157 def get_mpint(self): 

158 """ 

159 Fetch a long int (mpint) from the stream. 

160 

161 :return: an arbitrary-length integer (`int`). 

162 """ 

163 return util.inflate_long(self.get_binary()) 

164 

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()) 

174 

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. 

180 

181 This currently operates by attempting to encode the next "string" as 

182 ``utf-8``. 

183 """ 

184 return u(self.get_string()) 

185 

186 def get_binary(self): 

187 """ 

188 Alias for `get_string` (obtains a bytestring). 

189 """ 

190 return self.get_bytes(self.get_int()) 

191 

192 def get_list(self): 

193 """ 

194 Fetch a list of `strings <str>` from the stream. 

195 

196 These are trivially encoded as comma-separated values in a string. 

197 """ 

198 return self.get_text().split(",") 

199 

200 def add_bytes(self, b): 

201 """ 

202 Write bytes to the stream, without any formatting. 

203 

204 :param bytes b: bytes to add 

205 """ 

206 self.packet.write(b) 

207 return self 

208 

209 def add_byte(self, b): 

210 """ 

211 Write a single byte to the stream, without any formatting. 

212 

213 :param bytes b: byte to add 

214 """ 

215 self.packet.write(b) 

216 return self 

217 

218 def add_boolean(self, b): 

219 """ 

220 Add a boolean value to the stream. 

221 

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 

229 

230 def add_int(self, n): 

231 """ 

232 Add an integer to the stream. 

233 

234 :param int n: integer to add 

235 """ 

236 self.packet.write(struct.pack(">I", n)) 

237 return self 

238 

239 def add_adaptive_int(self, n): 

240 """ 

241 Add an integer to the stream. 

242 

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 

251 

252 def add_int64(self, n): 

253 """ 

254 Add a 64-bit int to the stream. 

255 

256 :param int n: long int to add 

257 """ 

258 self.packet.write(struct.pack(">Q", n)) 

259 return self 

260 

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. 

265 

266 :param int z: long int to add 

267 """ 

268 self.add_string(util.deflate_long(z)) 

269 return self 

270 

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. 

276 

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 

283 

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.) 

289 

290 :param l: list of strings to add 

291 """ 

292 self.add_string(",".join(l)) 

293 return self 

294 

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) 

304 

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. 

311 

312 .. warning:: 

313 Longs are encoded non-deterministically. Don't use this method. 

314 

315 :param seq: the sequence of items 

316 """ 

317 for item in seq: 

318 self._add(item)